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