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