View Javadoc

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     */
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     */
70    public static int uniqueSequenceId() {
71      return sequence.incrementAndGet();
72    }
73    
74    /**
75     * Assert that a given file exists.
76     */
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     */
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   
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 
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     
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 
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     
147     public String getOutput() {
148       return sw.toString();
149     }
150     
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     
176     public DelayAnswer(Log log) {
177       this.LOG = log;
178     }
179 
180     /**
181      * Wait until the method is called.
182      */
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      */
191     public void proceed() {
192       waitLatch.countDown();
193     }
194   
195     @Override
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 
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      */
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      */
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      */
244     public Object getReturnValue() {
245       return returnValue;
246     }
247     
248     public int getFireCount() {
249       return fireCounter.get();
250     }
251     
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     
275     public DelegateAnswer(Object delegate) {
276       this(null, delegate);
277     }
278     
279     public DelegateAnswer(Log log, Object delegate) {
280       this.log = log;
281       this.delegate = delegate;
282     }
283 
284     @Override
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     
309     public SleepAnswer(int maxSleepTime) {
310       this.maxSleepTime = maxSleepTime;
311     }
312     
313     @Override
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 
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   
337   public static void assertValueNear(long expected, long actual, long allowedError) {
338     assertValueWithinRange(expected - allowedError, expected + allowedError, actual);
339   }
340   
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    */
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 }