/** * Copyright (c) 2014-2017 by the respective copyright holders. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.core.common; import static org.junit.Assert.*; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Test class for the {@link QueueingThreadPoolExecutor} class, abbreviated here * QueueingTPE. * * @author Jochen Hiller - Initial contribution */ public class QueueingThreadPoolExecutorTest { /** * we know that the QueuingTPE uses a core pool timeout of 10 seconds. Will * be needed to check if all threads are down after this timeout. */ private final static int CORE_POOL_TIMEOUT = 10000; /** * We can enable logging for all test cases. */ @Before public void setUp() { // enable to see logging. See below how to include slf4j-simple // enableLogging(); disableLogging(); } /** * Creates QueueingTPE instances. By default there will be NO thread * created, check it. */ @Test public void testCreateInstance() { String poolName = "testCreateInstance"; QueueingThreadPoolExecutor.createInstance(poolName, 1); QueueingThreadPoolExecutor.createInstance(poolName, 2); QueueingThreadPoolExecutor.createInstance(poolName, 5); QueueingThreadPoolExecutor.createInstance(poolName, 10); QueueingThreadPoolExecutor.createInstance(poolName, 1000); QueueingThreadPoolExecutor.createInstance(poolName, 10000); QueueingThreadPoolExecutor.createInstance(poolName, 100000); QueueingThreadPoolExecutor.createInstance(poolName, 1000000); // no threads created assertFalse(areThreadsFromPoolRunning(poolName)); } /** * Tests what happens when poolName == null. */ @Test(expected = IllegalArgumentException.class) public void testCreateInstanceInvalidArgsPoolNameNull() throws InterruptedException { QueueingThreadPoolExecutor.createInstance(null, 1); } @Test(expected = IllegalArgumentException.class) public void testCreateInstanceInvalidArgsPoolSize0() { QueueingThreadPoolExecutor.createInstance("test", 0); } @Test(expected = IllegalArgumentException.class) public void testCreateInstanceInvalidArgsPoolSizeMinus1() { QueueingThreadPoolExecutor.createInstance("test", -1); } /** * This test tests behavior of QueueingTPE for a pool of core=1, max=2 when * no tasks have been scheduled. Same assumptions as above. */ @Test public void testQueuingTPEPoolSize2() throws InterruptedException { String poolName = "testQueuingTPEPoolSize2"; ThreadPoolExecutor pool = QueueingThreadPoolExecutor.createInstance(poolName, 2); assertEquals(pool.getActiveCount(), 0); assertEquals(pool.allowsCoreThreadTimeOut(), true); assertEquals(pool.getCompletedTaskCount(), 0); assertEquals(pool.getCorePoolSize(), 1); assertEquals(pool.getMaximumPoolSize(), 2); assertEquals(pool.getLargestPoolSize(), 0); assertEquals(pool.getQueue().size(), 0); // now expect that no threads have been created assertFalse(areThreadsFromPoolRunning(poolName)); // no need to wait after shutdown as no threads created pool.shutdown(); } @Test(expected = IllegalArgumentException.class) public void testPoolWithBlankPoolName() throws InterruptedException { basicTestForPoolName(" "); } @Test(expected = IllegalArgumentException.class) public void testPoolWithEmptyPoolName() throws InterruptedException { basicTestForPoolName(""); } /** * Basic tests of a pool with a given name. Checks thread creation and * cleanup. */ protected void basicTestForPoolName(String poolName) throws InterruptedException { ThreadPoolExecutor pool = QueueingThreadPoolExecutor.createInstance(poolName, 2); pool.execute(createRunnable100ms()); pool.execute(createRunnable100ms()); assertTrue(isPoolThreadActive(poolName, 1)); assertTrue(isPoolThreadActive(poolName, 2)); // no queue thread // needs to wait CORE_POOL_TIMEOUT + x until all threads are down again pool.shutdown(); Thread.sleep(CORE_POOL_TIMEOUT + 1000); assertFalse(areThreadsFromPoolRunning(poolName)); } /** * Test basic thread creation, including thread settings (name, prio, * daemon). */ protected void basicTestPoolSize2ThreadSettings(String poolName) throws InterruptedException { ThreadPoolExecutor pool = QueueingThreadPoolExecutor.createInstance(poolName, 2); // pool 2 tasks, threads must exist pool.execute(createRunnable10s()); assertEquals(pool.getActiveCount(), 1); assertTrue(isPoolThreadActive(poolName, 1)); Thread t1 = getThread(poolName + "-1"); assertEquals(t1.isDaemon(), false); // thread will be NORM prio or max prio of this thread group, which can // < than NORM int prio1 = Math.min(t1.getThreadGroup().getMaxPriority(), Thread.NORM_PRIORITY); assertEquals(t1.getPriority(), prio1); pool.execute(createRunnable10s()); assertEquals(pool.getActiveCount(), 2); assertTrue(isPoolThreadActive(poolName, 2)); Thread t2 = getThread(poolName + "-2"); assertEquals(t2.isDaemon(), false); // thread will be NORM prio or max prio of this thread group, which can // < than NORM int prio2 = Math.min(t2.getThreadGroup().getMaxPriority(), Thread.NORM_PRIORITY); assertEquals(t2.getPriority(), prio2); // 2 more tasks, will be queued, no threads pool.execute(createRunnable1s()); // as pool size is 2, no more active threads, will stay at 2 assertEquals(pool.getActiveCount(), 2); assertFalse(isPoolThreadActive(poolName, 3)); assertEquals(pool.getQueue().size(), 1); pool.execute(createRunnable1s()); assertEquals(pool.getActiveCount(), 2); assertFalse(isPoolThreadActive(poolName, 4)); assertEquals(pool.getQueue().size(), 2); // 0 are yet executed assertEquals(pool.getCompletedTaskCount(), 0); // needs to wait CORE_POOL_TIMEOUT + 2sec-queue-thread + x until all // threads are down again pool.shutdown(); Thread.sleep(CORE_POOL_TIMEOUT + 3000); assertFalse(areThreadsFromPoolRunning(poolName)); } /** * Tests what happens when wrong rejected execution handler will be used. */ @Test(expected = UnsupportedOperationException.class) public void testSetInvalidRejectionHandler() throws InterruptedException { String poolName = "testShutdownNoEntriesIntoQueueAnymore"; ThreadPoolExecutor pool = QueueingThreadPoolExecutor.createInstance(poolName, 2); pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); } // helper methods private void disableLogging() { // disable logging System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "error"); System.clearProperty("org.slf4j.simpleLogger.logFile"); System.clearProperty("org.slf4j.simpleLogger.showDateTime"); } private boolean isPoolThreadActive(String poolName, int id) { return getThread(poolName + "-" + id) != null; } /** * Search for thread with given name. * * @return found thread or null */ private Thread getThread(String threadName) { // get top level thread group ThreadGroup g = Thread.currentThread().getThreadGroup(); while (g.getParent() != null) { g = g.getParent(); } // make buffer 10 entries bigger Thread[] l = new Thread[g.activeCount() + 10]; int n = g.enumerate(l); for (int i = 0; i < n; i++) { // enable printout to see threads // System.out.println("getThread:" + l[i]); if (l[i].getName().equals(threadName)) { return l[i]; } } return null; } private boolean areThreadsFromPoolRunning(String poolName) { // get top level thread group ThreadGroup g = Thread.currentThread().getThreadGroup(); while (g.getParent() != null) { g = g.getParent(); } boolean foundThreads = false; // make buffer 10 entries bigger Thread[] l = new Thread[g.activeCount() + 10]; int n = g.enumerate(l); for (int i = 0; i < n; i++) { // we can only test if name is at least one character, // otherwise there will be threads found (handles poolName="") if (poolName.length() > 0) { if (l[i].getName().startsWith(poolName)) { // enable printout to see threads // System.out.println("areThreadsFromPoolRunning: " + // l[i].toString()); foundThreads = true; } } } return foundThreads; } // Runnables for testing private Runnable createRunnable100ms() { return new Runnable100ms(); } private Runnable createRunnable1s() { return new Runnable1s(); } private Runnable createRunnable10s() { return new Runnable10s(); } private static abstract class AbstractRunnable implements Runnable { private static AtomicInteger runs = new AtomicInteger(0); protected Logger logger = LoggerFactory.getLogger(this.getClass()); protected void sleep(int milliseconds) { try { Thread.sleep(milliseconds); } catch (InterruptedException e) { // ignore logger.info("interrupted"); } } @Override public void run() { logger.info("run"); runs.incrementAndGet(); } } private static class Runnable100ms extends AbstractRunnable { @Override public void run() { super.run(); sleep(100); } } private static class Runnable1s extends AbstractRunnable { @Override public void run() { super.run(); sleep(1 * 1000); } } private static class Runnable10s extends AbstractRunnable { @Override public void run() { super.run(); sleep(10 * 1000); } } }