1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 package org.apache.hadoop.hbase;
19 
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.StringWriter;
23 import java.lang.management.ManagementFactory;
24 import java.lang.management.ThreadInfo;
25 import java.lang.management.ThreadMXBean;
26 import java.lang.reflect.InvocationTargetException;
27 import java.util.Arrays;
28 import java.util.Random;
29 import java.util.Set;
30 import java.util.concurrent.CountDownLatch;
31 import java.util.concurrent.TimeoutException;
32 import java.util.concurrent.atomic.AtomicInteger;
33 import java.util.regex.Pattern;
34 
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.impl.Log4JLogger;
37 import org.apache.hadoop.fs.FileUtil;
38 import org.apache.hadoop.util.StringUtils;
39 import org.apache.hadoop.util.Time;
40 import org.apache.log4j.Layout;
41 import org.apache.log4j.Logger;
42 import org.apache.log4j.WriterAppender;
43 import org.junit.Assert;
44 import org.mockito.invocation.InvocationOnMock;
45 import org.mockito.stubbing.Answer;
46 
47 import com.google.common.base.Joiner;
48 import com.google.common.base.Supplier;
49 import com.google.common.collect.Sets;
50 
51 /**
52  * Test provides some very generic helpers which might be used across the tests
53  */
54 public abstract class GenericTestUtils {
55 
56   private static final AtomicInteger sequence = new AtomicInteger();
57 
58   /**
59    * Extracts the name of the method where the invocation has happened
60    * @return String name of the invoking method
61    */
getMethodName()62   public static String getMethodName() {
63     return Thread.currentThread().getStackTrace()[2].getMethodName();
64   }
65 
66   /**
67    * Generates a process-wide unique sequence number.
68    * @return an unique sequence number
69    */
uniqueSequenceId()70   public static int uniqueSequenceId() {
71     return sequence.incrementAndGet();
72   }
73 
74   /**
75    * Assert that a given file exists.
76    */
assertExists(File f)77   public static void assertExists(File f) {
78     Assert.assertTrue("File " + f + " should exist", f.exists());
79   }
80 
81   /**
82    * List all of the files in 'dir' that match the regex 'pattern'.
83    * Then check that this list is identical to 'expectedMatches'.
84    * @throws IOException if the dir is inaccessible
85    */
assertGlobEquals(File dir, String pattern, String ... expectedMatches)86   public static void assertGlobEquals(File dir, String pattern,
87       String ... expectedMatches) throws IOException {
88 
89     Set<String> found = Sets.newTreeSet();
90     for (File f : FileUtil.listFiles(dir)) {
91       if (f.getName().matches(pattern)) {
92         found.add(f.getName());
93       }
94     }
95     Set<String> expectedSet = Sets.newTreeSet(
96         Arrays.asList(expectedMatches));
97     Assert.assertEquals("Bad files matching " + pattern + " in " + dir,
98         Joiner.on(",").join(expectedSet),
99         Joiner.on(",").join(found));
100   }
101 
assertExceptionContains(String string, Throwable t)102   public static void assertExceptionContains(String string, Throwable t) {
103     String msg = t.getMessage();
104     Assert.assertTrue(
105         "Expected to find '" + string + "' but got unexpected exception:"
106         + StringUtils.stringifyException(t), msg.contains(string));
107   }
108 
waitFor(Supplier<Boolean> check, int checkEveryMillis, int waitForMillis)109   public static void waitFor(Supplier<Boolean> check,
110       int checkEveryMillis, int waitForMillis)
111       throws TimeoutException, InterruptedException
112   {
113     long st = Time.now();
114     do {
115       boolean result = check.get();
116       if (result) {
117         return;
118       }
119 
120       Thread.sleep(checkEveryMillis);
121     } while (Time.now() - st < waitForMillis);
122 
123     throw new TimeoutException("Timed out waiting for condition. " +
124         "Thread diagnostics:\n" +
125         TimedOutTestsListener.buildThreadDiagnosticString());
126   }
127 
128   public static class LogCapturer {
129     private StringWriter sw = new StringWriter();
130     private WriterAppender appender;
131     private Logger logger;
132 
captureLogs(Log l)133     public static LogCapturer captureLogs(Log l) {
134       Logger logger = ((Log4JLogger)l).getLogger();
135       LogCapturer c = new LogCapturer(logger);
136       return c;
137     }
138 
139 
LogCapturer(Logger logger)140     private LogCapturer(Logger logger) {
141       this.logger = logger;
142       Layout layout = Logger.getRootLogger().getAppender("stdout").getLayout();
143       WriterAppender wa = new WriterAppender(layout, sw);
144       logger.addAppender(wa);
145     }
146 
getOutput()147     public String getOutput() {
148       return sw.toString();
149     }
150 
stopCapturing()151     public void stopCapturing() {
152       logger.removeAppender(appender);
153 
154     }
155   }
156 
157 
158   /**
159    * Mockito answer helper that triggers one latch as soon as the
160    * method is called, then waits on another before continuing.
161    */
162   public static class DelayAnswer implements Answer<Object> {
163     private final Log LOG;
164 
165     private final CountDownLatch fireLatch = new CountDownLatch(1);
166     private final CountDownLatch waitLatch = new CountDownLatch(1);
167     private final CountDownLatch resultLatch = new CountDownLatch(1);
168 
169     private final AtomicInteger fireCounter = new AtomicInteger(0);
170     private final AtomicInteger resultCounter = new AtomicInteger(0);
171 
172     // Result fields set after proceed() is called.
173     private volatile Throwable thrown;
174     private volatile Object returnValue;
175 
DelayAnswer(Log log)176     public DelayAnswer(Log log) {
177       this.LOG = log;
178     }
179 
180     /**
181      * Wait until the method is called.
182      */
waitForCall()183     public void waitForCall() throws InterruptedException {
184       fireLatch.await();
185     }
186 
187     /**
188      * Tell the method to proceed.
189      * This should only be called after waitForCall()
190      */
proceed()191     public void proceed() {
192       waitLatch.countDown();
193     }
194 
195     @Override
answer(InvocationOnMock invocation)196     public Object answer(InvocationOnMock invocation) throws Throwable {
197       LOG.info("DelayAnswer firing fireLatch");
198       fireCounter.getAndIncrement();
199       fireLatch.countDown();
200       try {
201         LOG.info("DelayAnswer waiting on waitLatch");
202         waitLatch.await();
203         LOG.info("DelayAnswer delay complete");
204       } catch (InterruptedException ie) {
205         throw new IOException("Interrupted waiting on latch", ie);
206       }
207       return passThrough(invocation);
208     }
209 
passThrough(InvocationOnMock invocation)210     protected Object passThrough(InvocationOnMock invocation) throws Throwable {
211       try {
212         Object ret = invocation.callRealMethod();
213         returnValue = ret;
214         return ret;
215       } catch (Throwable t) {
216         thrown = t;
217         throw t;
218       } finally {
219         resultCounter.incrementAndGet();
220         resultLatch.countDown();
221       }
222     }
223 
224     /**
225      * After calling proceed(), this will wait until the call has
226      * completed and a result has been returned to the caller.
227      */
waitForResult()228     public void waitForResult() throws InterruptedException {
229       resultLatch.await();
230     }
231 
232     /**
233      * After the call has gone through, return any exception that
234      * was thrown, or null if no exception was thrown.
235      */
getThrown()236     public Throwable getThrown() {
237       return thrown;
238     }
239 
240     /**
241      * After the call has gone through, return the call's return value,
242      * or null in case it was void or an exception was thrown.
243      */
getReturnValue()244     public Object getReturnValue() {
245       return returnValue;
246     }
247 
getFireCount()248     public int getFireCount() {
249       return fireCounter.get();
250     }
251 
getResultCount()252     public int getResultCount() {
253       return resultCounter.get();
254     }
255   }
256 
257   /**
258    * An Answer implementation that simply forwards all calls through
259    * to a delegate.
260    *
261    * This is useful as the default Answer for a mock object, to create
262    * something like a spy on an RPC proxy. For example:
263    * <code>
264    *    NamenodeProtocol origNNProxy = secondary.getNameNode();
265    *    NamenodeProtocol spyNNProxy = Mockito.mock(NameNodeProtocol.class,
266    *        new DelegateAnswer(origNNProxy);
267    *    doThrow(...).when(spyNNProxy).getBlockLocations(...);
268    *    ...
269    * </code>
270    */
271   public static class DelegateAnswer implements Answer<Object> {
272     private final Object delegate;
273     private final Log log;
274 
DelegateAnswer(Object delegate)275     public DelegateAnswer(Object delegate) {
276       this(null, delegate);
277     }
278 
DelegateAnswer(Log log, Object delegate)279     public DelegateAnswer(Log log, Object delegate) {
280       this.log = log;
281       this.delegate = delegate;
282     }
283 
284     @Override
answer(InvocationOnMock invocation)285     public Object answer(InvocationOnMock invocation) throws Throwable {
286       try {
287         if (log != null) {
288           log.info("Call to " + invocation + " on " + delegate,
289               new Exception("TRACE"));
290         }
291         return invocation.getMethod().invoke(
292             delegate, invocation.getArguments());
293       } catch (InvocationTargetException ite) {
294         throw ite.getCause();
295       }
296     }
297   }
298 
299   /**
300    * An Answer implementation which sleeps for a random number of milliseconds
301    * between 0 and a configurable value before delegating to the real
302    * implementation of the method. This can be useful for drawing out race
303    * conditions.
304    */
305   public static class SleepAnswer implements Answer<Object> {
306     private final int maxSleepTime;
307     private static Random r = new Random();
308 
SleepAnswer(int maxSleepTime)309     public SleepAnswer(int maxSleepTime) {
310       this.maxSleepTime = maxSleepTime;
311     }
312 
313     @Override
answer(InvocationOnMock invocation)314     public Object answer(InvocationOnMock invocation) throws Throwable {
315       boolean interrupted = false;
316       try {
317         Thread.sleep(r.nextInt(maxSleepTime));
318       } catch (InterruptedException ie) {
319         interrupted = true;
320       }
321       try {
322         return invocation.callRealMethod();
323       } finally {
324         if (interrupted) {
325           Thread.currentThread().interrupt();
326         }
327       }
328     }
329   }
330 
assertMatches(String output, String pattern)331   public static void assertMatches(String output, String pattern) {
332     Assert.assertTrue("Expected output to match /" + pattern + "/" +
333         " but got:\n" + output,
334         Pattern.compile(pattern).matcher(output).find());
335   }
336 
assertValueNear(long expected, long actual, long allowedError)337   public static void assertValueNear(long expected, long actual, long allowedError) {
338     assertValueWithinRange(expected - allowedError, expected + allowedError, actual);
339   }
340 
assertValueWithinRange(long expectedMin, long expectedMax, long actual)341   public static void assertValueWithinRange(long expectedMin, long expectedMax,
342       long actual) {
343     Assert.assertTrue("Expected " + actual + " to be in range (" + expectedMin + ","
344         + expectedMax + ")", expectedMin <= actual && actual <= expectedMax);
345   }
346 
347   /**
348    * Assert that there are no threads running whose name matches the
349    * given regular expression.
350    * @param regex the regex to match against
351    */
assertNoThreadsMatching(String regex)352   public static void assertNoThreadsMatching(String regex) {
353     Pattern pattern = Pattern.compile(regex);
354     ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
355 
356     ThreadInfo[] infos = threadBean.getThreadInfo(threadBean.getAllThreadIds(), 20);
357     for (ThreadInfo info : infos) {
358       if (info == null) continue;
359       if (pattern.matcher(info.getThreadName()).matches()) {
360         Assert.fail("Leaked thread: " + info + "\n" +
361             Joiner.on("\n").join(info.getStackTrace()));
362       }
363     }
364   }
365 }
366