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}