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.test;
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.Level;
42 import org.apache.log4j.LogManager;
43 import org.apache.log4j.Logger;
44 import org.apache.log4j.WriterAppender;
45 import org.junit.Assert;
46 import org.junit.Assume;
47 import org.mockito.invocation.InvocationOnMock;
48 import org.mockito.stubbing.Answer;
49 
50 import com.google.common.base.Joiner;
51 import com.google.common.base.Supplier;
52 import com.google.common.collect.Sets;
53 
54 /**
55  * Test provides some very generic helpers which might be used across the tests
56  */
57 public abstract class GenericTestUtils {
58 
59   private static final AtomicInteger sequence = new AtomicInteger();
60 
61   @SuppressWarnings("unchecked")
disableLog(Log log)62   public static void disableLog(Log log) {
63     // We expect that commons-logging is a wrapper around Log4j.
64     disableLog((Log4JLogger) log);
65   }
66 
toLog4j(org.slf4j.Logger logger)67   public static Logger toLog4j(org.slf4j.Logger logger) {
68     return LogManager.getLogger(logger.getName());
69   }
70 
disableLog(Log4JLogger log)71   public static void disableLog(Log4JLogger log) {
72     log.getLogger().setLevel(Level.OFF);
73   }
74 
disableLog(Logger logger)75   public static void disableLog(Logger logger) {
76     logger.setLevel(Level.OFF);
77   }
78 
disableLog(org.slf4j.Logger logger)79   public static void disableLog(org.slf4j.Logger logger) {
80     disableLog(toLog4j(logger));
81   }
82 
83   @SuppressWarnings("unchecked")
setLogLevel(Log log, Level level)84   public static void setLogLevel(Log log, Level level) {
85     // We expect that commons-logging is a wrapper around Log4j.
86     setLogLevel((Log4JLogger) log, level);
87   }
88 
setLogLevel(Log4JLogger log, Level level)89   public static void setLogLevel(Log4JLogger log, Level level) {
90     log.getLogger().setLevel(level);
91   }
92 
setLogLevel(Logger logger, Level level)93   public static void setLogLevel(Logger logger, Level level) {
94     logger.setLevel(level);
95   }
96 
setLogLevel(org.slf4j.Logger logger, Level level)97   public static void setLogLevel(org.slf4j.Logger logger, Level level) {
98     setLogLevel(toLog4j(logger), level);
99   }
100 
101   /**
102    * Extracts the name of the method where the invocation has happened
103    * @return String name of the invoking method
104    */
getMethodName()105   public static String getMethodName() {
106     return Thread.currentThread().getStackTrace()[2].getMethodName();
107   }
108 
109   /**
110    * Generates a process-wide unique sequence number.
111    * @return an unique sequence number
112    */
uniqueSequenceId()113   public static int uniqueSequenceId() {
114     return sequence.incrementAndGet();
115   }
116 
117   /**
118    * Assert that a given file exists.
119    */
assertExists(File f)120   public static void assertExists(File f) {
121     Assert.assertTrue("File " + f + " should exist", f.exists());
122   }
123 
124   /**
125    * List all of the files in 'dir' that match the regex 'pattern'.
126    * Then check that this list is identical to 'expectedMatches'.
127    * @throws IOException if the dir is inaccessible
128    */
assertGlobEquals(File dir, String pattern, String ... expectedMatches)129   public static void assertGlobEquals(File dir, String pattern,
130       String ... expectedMatches) throws IOException {
131 
132     Set<String> found = Sets.newTreeSet();
133     for (File f : FileUtil.listFiles(dir)) {
134       if (f.getName().matches(pattern)) {
135         found.add(f.getName());
136       }
137     }
138     Set<String> expectedSet = Sets.newTreeSet(
139         Arrays.asList(expectedMatches));
140     Assert.assertEquals("Bad files matching " + pattern + " in " + dir,
141         Joiner.on(",").join(expectedSet),
142         Joiner.on(",").join(found));
143   }
144 
assertExceptionContains(String string, Throwable t)145   public static void assertExceptionContains(String string, Throwable t) {
146     String msg = t.getMessage();
147     Assert.assertTrue(
148         "Expected to find '" + string + "' but got unexpected exception:"
149         + StringUtils.stringifyException(t), msg.contains(string));
150   }
151 
waitFor(Supplier<Boolean> check, int checkEveryMillis, int waitForMillis)152   public static void waitFor(Supplier<Boolean> check,
153       int checkEveryMillis, int waitForMillis)
154       throws TimeoutException, InterruptedException
155   {
156     long st = Time.now();
157     do {
158       boolean result = check.get();
159       if (result) {
160         return;
161       }
162 
163       Thread.sleep(checkEveryMillis);
164     } while (Time.now() - st < waitForMillis);
165 
166     throw new TimeoutException("Timed out waiting for condition. " +
167         "Thread diagnostics:\n" +
168         TimedOutTestsListener.buildThreadDiagnosticString());
169   }
170 
171   public static class LogCapturer {
172     private StringWriter sw = new StringWriter();
173     private WriterAppender appender;
174     private Logger logger;
175 
captureLogs(Log l)176     public static LogCapturer captureLogs(Log l) {
177       Logger logger = ((Log4JLogger)l).getLogger();
178       LogCapturer c = new LogCapturer(logger);
179       return c;
180     }
181 
182 
LogCapturer(Logger logger)183     private LogCapturer(Logger logger) {
184       this.logger = logger;
185       Layout layout = Logger.getRootLogger().getAppender("stdout").getLayout();
186       WriterAppender wa = new WriterAppender(layout, sw);
187       logger.addAppender(wa);
188     }
189 
getOutput()190     public String getOutput() {
191       return sw.toString();
192     }
193 
stopCapturing()194     public void stopCapturing() {
195       logger.removeAppender(appender);
196 
197     }
198   }
199 
200 
201   /**
202    * Mockito answer helper that triggers one latch as soon as the
203    * method is called, then waits on another before continuing.
204    */
205   public static class DelayAnswer implements Answer<Object> {
206     private final Log LOG;
207 
208     private final CountDownLatch fireLatch = new CountDownLatch(1);
209     private final CountDownLatch waitLatch = new CountDownLatch(1);
210     private final CountDownLatch resultLatch = new CountDownLatch(1);
211 
212     private final AtomicInteger fireCounter = new AtomicInteger(0);
213     private final AtomicInteger resultCounter = new AtomicInteger(0);
214 
215     // Result fields set after proceed() is called.
216     private volatile Throwable thrown;
217     private volatile Object returnValue;
218 
DelayAnswer(Log log)219     public DelayAnswer(Log log) {
220       this.LOG = log;
221     }
222 
223     /**
224      * Wait until the method is called.
225      */
waitForCall()226     public void waitForCall() throws InterruptedException {
227       fireLatch.await();
228     }
229 
230     /**
231      * Tell the method to proceed.
232      * This should only be called after waitForCall()
233      */
proceed()234     public void proceed() {
235       waitLatch.countDown();
236     }
237 
238     @Override
answer(InvocationOnMock invocation)239     public Object answer(InvocationOnMock invocation) throws Throwable {
240       LOG.info("DelayAnswer firing fireLatch");
241       fireCounter.getAndIncrement();
242       fireLatch.countDown();
243       try {
244         LOG.info("DelayAnswer waiting on waitLatch");
245         waitLatch.await();
246         LOG.info("DelayAnswer delay complete");
247       } catch (InterruptedException ie) {
248         throw new IOException("Interrupted waiting on latch", ie);
249       }
250       return passThrough(invocation);
251     }
252 
passThrough(InvocationOnMock invocation)253     protected Object passThrough(InvocationOnMock invocation) throws Throwable {
254       try {
255         Object ret = invocation.callRealMethod();
256         returnValue = ret;
257         return ret;
258       } catch (Throwable t) {
259         thrown = t;
260         throw t;
261       } finally {
262         resultCounter.incrementAndGet();
263         resultLatch.countDown();
264       }
265     }
266 
267     /**
268      * After calling proceed(), this will wait until the call has
269      * completed and a result has been returned to the caller.
270      */
waitForResult()271     public void waitForResult() throws InterruptedException {
272       resultLatch.await();
273     }
274 
275     /**
276      * After the call has gone through, return any exception that
277      * was thrown, or null if no exception was thrown.
278      */
getThrown()279     public Throwable getThrown() {
280       return thrown;
281     }
282 
283     /**
284      * After the call has gone through, return the call's return value,
285      * or null in case it was void or an exception was thrown.
286      */
getReturnValue()287     public Object getReturnValue() {
288       return returnValue;
289     }
290 
getFireCount()291     public int getFireCount() {
292       return fireCounter.get();
293     }
294 
getResultCount()295     public int getResultCount() {
296       return resultCounter.get();
297     }
298   }
299 
300   /**
301    * An Answer implementation that simply forwards all calls through
302    * to a delegate.
303    *
304    * This is useful as the default Answer for a mock object, to create
305    * something like a spy on an RPC proxy. For example:
306    * <code>
307    *    NamenodeProtocol origNNProxy = secondary.getNameNode();
308    *    NamenodeProtocol spyNNProxy = Mockito.mock(NameNodeProtocol.class,
309    *        new DelegateAnswer(origNNProxy);
310    *    doThrow(...).when(spyNNProxy).getBlockLocations(...);
311    *    ...
312    * </code>
313    */
314   public static class DelegateAnswer implements Answer<Object> {
315     private final Object delegate;
316     private final Log log;
317 
DelegateAnswer(Object delegate)318     public DelegateAnswer(Object delegate) {
319       this(null, delegate);
320     }
321 
DelegateAnswer(Log log, Object delegate)322     public DelegateAnswer(Log log, Object delegate) {
323       this.log = log;
324       this.delegate = delegate;
325     }
326 
327     @Override
answer(InvocationOnMock invocation)328     public Object answer(InvocationOnMock invocation) throws Throwable {
329       try {
330         if (log != null) {
331           log.info("Call to " + invocation + " on " + delegate,
332               new Exception("TRACE"));
333         }
334         return invocation.getMethod().invoke(
335             delegate, invocation.getArguments());
336       } catch (InvocationTargetException ite) {
337         throw ite.getCause();
338       }
339     }
340   }
341 
342   /**
343    * An Answer implementation which sleeps for a random number of milliseconds
344    * between 0 and a configurable value before delegating to the real
345    * implementation of the method. This can be useful for drawing out race
346    * conditions.
347    */
348   public static class SleepAnswer implements Answer<Object> {
349     private final int maxSleepTime;
350     private static Random r = new Random();
351 
SleepAnswer(int maxSleepTime)352     public SleepAnswer(int maxSleepTime) {
353       this.maxSleepTime = maxSleepTime;
354     }
355 
356     @Override
answer(InvocationOnMock invocation)357     public Object answer(InvocationOnMock invocation) throws Throwable {
358       boolean interrupted = false;
359       try {
360         Thread.sleep(r.nextInt(maxSleepTime));
361       } catch (InterruptedException ie) {
362         interrupted = true;
363       }
364       try {
365         return invocation.callRealMethod();
366       } finally {
367         if (interrupted) {
368           Thread.currentThread().interrupt();
369         }
370       }
371     }
372   }
373 
assertDoesNotMatch(String output, String pattern)374   public static void assertDoesNotMatch(String output, String pattern) {
375     Assert.assertFalse("Expected output to match /" + pattern + "/" +
376         " but got:\n" + output,
377         Pattern.compile(pattern).matcher(output).find());
378   }
379 
assertMatches(String output, String pattern)380   public static void assertMatches(String output, String pattern) {
381     Assert.assertTrue("Expected output to match /" + pattern + "/" +
382         " but got:\n" + output,
383         Pattern.compile(pattern).matcher(output).find());
384   }
385 
assertValueNear(long expected, long actual, long allowedError)386   public static void assertValueNear(long expected, long actual, long allowedError) {
387     assertValueWithinRange(expected - allowedError, expected + allowedError, actual);
388   }
389 
assertValueWithinRange(long expectedMin, long expectedMax, long actual)390   public static void assertValueWithinRange(long expectedMin, long expectedMax,
391       long actual) {
392     Assert.assertTrue("Expected " + actual + " to be in range (" + expectedMin + ","
393         + expectedMax + ")", expectedMin <= actual && actual <= expectedMax);
394   }
395 
396   /**
397    * Assert that there are no threads running whose name matches the
398    * given regular expression.
399    * @param regex the regex to match against
400    */
assertNoThreadsMatching(String regex)401   public static void assertNoThreadsMatching(String regex) {
402     Pattern pattern = Pattern.compile(regex);
403     ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
404 
405     ThreadInfo[] infos = threadBean.getThreadInfo(threadBean.getAllThreadIds(), 20);
406     for (ThreadInfo info : infos) {
407       if (info == null) continue;
408       if (pattern.matcher(info.getThreadName()).matches()) {
409         Assert.fail("Leaked thread: " + info + "\n" +
410             Joiner.on("\n").join(info.getStackTrace()));
411       }
412     }
413   }
414 
415   /**
416    * Skip test if native build profile of Maven is not activated.
417    * Sub-project using this must set 'runningWithNative' property to true
418    * in the definition of native profile in pom.xml.
419    */
assumeInNativeProfile()420   public static void assumeInNativeProfile() {
421     Assume.assumeTrue(
422         Boolean.valueOf(System.getProperty("runningWithNative", "false")));
423   }
424 }
425