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