/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio.heartbeat; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; /** * Unit tests for {@link HeartbeatThread}. This test uses * {@link alluxio.heartbeat.HeartbeatScheduler} to have synchronous tests. * * Instructions for testing heartbeats using {@link ScheduledTimer}. * * Using {@link ScheduledTimer} for testing heartbeats removes the dependence on sleep() for * thread timing. Instead, test cases can dictate an ordering between threads. Here are the * steps required for using {@link ScheduledTimer}. * * 1. Set the timer class to use {@link ScheduledTimer}. This tells the heartbeat context * that the given heartbeat thread will be using a schedule based timer, instead of a * sleeping based timer. This is done with: * * HeartbeatContext.setTimerClass(THREAD_NAME, HeartbeatContext.SCHEDULED_TIMER_CLASS); * * 2. Call await() to make sure the first heartbeat is ready to run. In tests, the result * should be within an assertTrue(). Here is an example: * * Assert.assertTrue(HeartbeatScheduler.await(THREAD_NAME, 5, TimeUnit.SECONDS)); * * 3. Call schedule() and await() each time the heartbeat thread should be triggered. Test * cases can now dictate the behavior of the heartbeat thread by calling schedule() and * await() right afterwards. After await() returns successfully, it is guaranteed that the * heartbeat thread ran exactly once. Here is an example of scheduling the heartbeat to run: * * HeartbeatScheduler.schedule(THREAD_NAME); * Assert.assertTrue(HeartbeatScheduler.await(THREAD_NAME, 5, TimeUnit.SECONDS)); */ public final class HeartbeatThreadTest { private static final String THREAD_NAME = "heartbeat-thread-test-thread-name"; private static final int NUMBER_OF_THREADS = 10; private ExecutorService mExecutorService; @Before public void before() { mExecutorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS); } @After public void after() { mExecutorService.shutdownNow(); } /** * This is a basic test of the heartbeat scheduler logic. It steps through the execution of a * single dummy executor. */ @Test public void serialHeartbeatThread() throws Exception { FutureTask<Void> task = new FutureTask<>(new DummyHeartbeatTestCallable()); Thread thread = new Thread(task); thread.start(); thread.join(); task.get(); } /** * This is a stress test of the heartbeat scheduler logic. It concurrently steps through the * execution of multiple dummy executors. */ @Test public void concurrentHeartbeatThread() throws Exception { List<FutureTask<Void>> tasks = new LinkedList<>(); // Start the threads. for (int i = 0; i < NUMBER_OF_THREADS; i++) { FutureTask<Void> task = new FutureTask<>(new DummyHeartbeatTestCallable(i)); Thread thread = new Thread(task); thread.start(); tasks.add(task); } // Wait for the threads to finish. for (FutureTask<Void> task: tasks) { task.get(); } } /** * Executes a dummy heartbeat executor using {@link HeartbeatScheduler}. */ private class DummyHeartbeatTestCallable implements Callable<Void> { private final String mThreadName; /** * Creates a new {@link DummyHeartbeatTestCallable}. */ public DummyHeartbeatTestCallable() { mThreadName = THREAD_NAME; } /** * Creates a new {@link DummyHeartbeatTestCallable}. * * @param id the thread id */ public DummyHeartbeatTestCallable(int id) { mThreadName = THREAD_NAME + "-" + id; } @Override public Void call() throws Exception { try (ManuallyScheduleHeartbeat.Resource r = new ManuallyScheduleHeartbeat.Resource(Arrays.asList(mThreadName))) { DummyHeartbeatExecutor executor = new DummyHeartbeatExecutor(); HeartbeatThread ht = new HeartbeatThread(mThreadName, executor, 1); // Run the HeartbeatThread. mExecutorService.submit(ht); final int numIterations = 5000; for (int i = 0; i < numIterations; i++) { HeartbeatScheduler.execute(mThreadName); } Assert.assertEquals("The executor counter is wrong.", numIterations, executor.getCounter()); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } return null; } } /** * The dummy heartbeat executor. */ private class DummyHeartbeatExecutor implements HeartbeatExecutor { private int mCounter = 0; @Override public void heartbeat() { mCounter++; } @Override public void close() { // Nothing to clean up } public int getCounter() { return mCounter; } } }