001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import java.io.File;
021import java.io.IOException;
022import java.lang.management.ManagementFactory;
023import java.lang.management.ThreadInfo;
024import java.lang.management.ThreadMXBean;
025import java.lang.reflect.InvocationTargetException;
026import java.util.Arrays;
027import java.util.Random;
028import java.util.Set;
029import java.util.concurrent.CountDownLatch;
030import java.util.concurrent.TimeoutException;
031import java.util.concurrent.atomic.AtomicInteger;
032import java.util.regex.Pattern;
033
034import org.apache.hadoop.fs.FileUtil;
035import org.apache.hadoop.util.Time;
036import org.apache.log4j.Logger;
037import org.junit.Assert;
038import org.mockito.invocation.InvocationOnMock;
039import org.mockito.stubbing.Answer;
040
041import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
042import org.apache.hbase.thirdparty.com.google.common.base.Supplier;
043import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
044
045/**
046 * Test provides some very generic helpers which might be used across the tests
047 */
048public abstract class GenericTestUtils {
049
050  private static final AtomicInteger sequence = new AtomicInteger();
051
052  /**
053   * Extracts the name of the method where the invocation has happened
054   * @return String name of the invoking method
055   */
056  public static String getMethodName() {
057    return Thread.currentThread().getStackTrace()[2].getMethodName();
058  }
059
060  /**
061   * Generates a process-wide unique sequence number.
062   * @return an unique sequence number
063   */
064  public static int uniqueSequenceId() {
065    return sequence.incrementAndGet();
066  }
067
068  /**
069   * Assert that a given file exists.
070   */
071  public static void assertExists(File f) {
072    Assert.assertTrue("File " + f + " should exist", f.exists());
073  }
074
075  /**
076   * List all of the files in 'dir' that match the regex 'pattern'.
077   * Then check that this list is identical to 'expectedMatches'.
078   * @throws IOException if the dir is inaccessible
079   */
080  public static void assertGlobEquals(File dir, String pattern,
081      String ... expectedMatches) throws IOException {
082
083    Set<String> found = Sets.newTreeSet();
084    for (File f : FileUtil.listFiles(dir)) {
085      if (f.getName().matches(pattern)) {
086        found.add(f.getName());
087      }
088    }
089    Set<String> expectedSet = Sets.newTreeSet(
090        Arrays.asList(expectedMatches));
091    Assert.assertEquals("Bad files matching " + pattern + " in " + dir,
092        Joiner.on(",").join(expectedSet),
093        Joiner.on(",").join(found));
094  }
095
096  public static void waitFor(Supplier<Boolean> check,
097      int checkEveryMillis, int waitForMillis)
098      throws TimeoutException, InterruptedException
099  {
100    long st = Time.now();
101    do {
102      boolean result = check.get();
103      if (result) {
104        return;
105      }
106
107      Thread.sleep(checkEveryMillis);
108    } while (Time.now() - st < waitForMillis);
109
110    throw new TimeoutException("Timed out waiting for condition. " +
111        "Thread diagnostics:\n" +
112        TimedOutTestsListener.buildThreadDiagnosticString());
113  }
114
115  /**
116   * Mockito answer helper that triggers one latch as soon as the
117   * method is called, then waits on another before continuing.
118   */
119  public static class DelayAnswer implements Answer<Object> {
120    private final Logger LOG;
121
122    private final CountDownLatch fireLatch = new CountDownLatch(1);
123    private final CountDownLatch waitLatch = new CountDownLatch(1);
124    private final CountDownLatch resultLatch = new CountDownLatch(1);
125
126    private final AtomicInteger fireCounter = new AtomicInteger(0);
127    private final AtomicInteger resultCounter = new AtomicInteger(0);
128
129    // Result fields set after proceed() is called.
130    private volatile Throwable thrown;
131    private volatile Object returnValue;
132
133    public DelayAnswer(Logger log) {
134      this.LOG = log;
135    }
136
137    /**
138     * Wait until the method is called.
139     */
140    public void waitForCall() throws InterruptedException {
141      fireLatch.await();
142    }
143
144    /**
145     * Tell the method to proceed.
146     * This should only be called after waitForCall()
147     */
148    public void proceed() {
149      waitLatch.countDown();
150    }
151
152    @Override
153    public Object answer(InvocationOnMock invocation) throws Throwable {
154      LOG.info("DelayAnswer firing fireLatch");
155      fireCounter.getAndIncrement();
156      fireLatch.countDown();
157      try {
158        LOG.info("DelayAnswer waiting on waitLatch");
159        waitLatch.await();
160        LOG.info("DelayAnswer delay complete");
161      } catch (InterruptedException ie) {
162        throw new IOException("Interrupted waiting on latch", ie);
163      }
164      return passThrough(invocation);
165    }
166
167    protected Object passThrough(InvocationOnMock invocation) throws Throwable {
168      try {
169        Object ret = invocation.callRealMethod();
170        returnValue = ret;
171        return ret;
172      } catch (Throwable t) {
173        thrown = t;
174        throw t;
175      } finally {
176        resultCounter.incrementAndGet();
177        resultLatch.countDown();
178      }
179    }
180
181    /**
182     * After calling proceed(), this will wait until the call has
183     * completed and a result has been returned to the caller.
184     */
185    public void waitForResult() throws InterruptedException {
186      resultLatch.await();
187    }
188
189    /**
190     * After the call has gone through, return any exception that
191     * was thrown, or null if no exception was thrown.
192     */
193    public Throwable getThrown() {
194      return thrown;
195    }
196
197    /**
198     * After the call has gone through, return the call's return value,
199     * or null in case it was void or an exception was thrown.
200     */
201    public Object getReturnValue() {
202      return returnValue;
203    }
204
205    public int getFireCount() {
206      return fireCounter.get();
207    }
208
209    public int getResultCount() {
210      return resultCounter.get();
211    }
212  }
213
214  /**
215   * An Answer implementation that simply forwards all calls through
216   * to a delegate.
217   *
218   * This is useful as the default Answer for a mock object, to create
219   * something like a spy on an RPC proxy. For example:
220   * <code>
221   *    NamenodeProtocol origNNProxy = secondary.getNameNode();
222   *    NamenodeProtocol spyNNProxy = Mockito.mock(NameNodeProtocol.class,
223   *        new DelegateAnswer(origNNProxy);
224   *    doThrow(...).when(spyNNProxy).getBlockLocations(...);
225   *    ...
226   * </code>
227   */
228  public static class DelegateAnswer implements Answer<Object> {
229    private final Object delegate;
230    private final Logger log;
231
232    public DelegateAnswer(Object delegate) {
233      this(null, delegate);
234    }
235
236    public DelegateAnswer(Logger log, Object delegate) {
237      this.log = log;
238      this.delegate = delegate;
239    }
240
241    @Override
242    public Object answer(InvocationOnMock invocation) throws Throwable {
243      try {
244        if (log != null) {
245          log.info("Call to " + invocation + " on " + delegate,
246              new Exception("TRACE"));
247        }
248        return invocation.getMethod().invoke(
249            delegate, invocation.getArguments());
250      } catch (InvocationTargetException ite) {
251        throw ite.getCause();
252      }
253    }
254  }
255
256  /**
257   * An Answer implementation which sleeps for a random number of milliseconds
258   * between 0 and a configurable value before delegating to the real
259   * implementation of the method. This can be useful for drawing out race
260   * conditions.
261   */
262  public static class SleepAnswer implements Answer<Object> {
263    private final int maxSleepTime;
264    private static Random r = new Random();
265
266    public SleepAnswer(int maxSleepTime) {
267      this.maxSleepTime = maxSleepTime;
268    }
269
270    @Override
271    public Object answer(InvocationOnMock invocation) throws Throwable {
272      boolean interrupted = false;
273      try {
274        Thread.sleep(r.nextInt(maxSleepTime));
275      } catch (InterruptedException ie) {
276        interrupted = true;
277      }
278      try {
279        return invocation.callRealMethod();
280      } finally {
281        if (interrupted) {
282          Thread.currentThread().interrupt();
283        }
284      }
285    }
286  }
287
288  public static void assertMatches(String output, String pattern) {
289    Assert.assertTrue("Expected output to match /" + pattern + "/" +
290        " but got:\n" + output,
291        Pattern.compile(pattern).matcher(output).find());
292  }
293
294  public static void assertValueNear(long expected, long actual, long allowedError) {
295    assertValueWithinRange(expected - allowedError, expected + allowedError, actual);
296  }
297
298  public static void assertValueWithinRange(long expectedMin, long expectedMax,
299      long actual) {
300    Assert.assertTrue("Expected " + actual + " to be in range (" + expectedMin + ","
301        + expectedMax + ")", expectedMin <= actual && actual <= expectedMax);
302  }
303
304  /**
305   * Assert that there are no threads running whose name matches the
306   * given regular expression.
307   * @param regex the regex to match against
308   */
309  public static void assertNoThreadsMatching(String regex) {
310    Pattern pattern = Pattern.compile(regex);
311    ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
312
313    ThreadInfo[] infos = threadBean.getThreadInfo(threadBean.getAllThreadIds(), 20);
314    for (ThreadInfo info : infos) {
315      if (info == null) continue;
316      if (pattern.matcher(info.getThreadName()).matches()) {
317        Assert.fail("Leaked thread: " + info + "\n" +
318            Joiner.on("\n").join(info.getStackTrace()));
319      }
320    }
321  }
322}