Add coverage to test-transaction 08/70008/2
authorJim Hahn <jrh3@att.com>
Mon, 8 Oct 2018 16:23:06 +0000 (12:23 -0400)
committerJim Hahn <jrh3@att.com>
Mon, 8 Oct 2018 17:59:36 +0000 (13:59 -0400)
Fixed bug in test-transaction, wherein it was sometimes using the controller
instead of the controller name as the key into the map.
Changed test to use latch instead of sleep.
Added a little more register/unregister testing.

Change-Id: I2647b44cf164038d211063bd499c25af70c2d9d7
Issue-ID: POLICY-1148
Signed-off-by: Jim Hahn <jrh3@att.com>
feature-test-transaction/src/main/java/org/onap/policy/drools/testtransaction/TestTransaction.java
feature-test-transaction/src/test/java/org/onap/policy/drools/testtransaction/TestTransactionTest.java
feature-test-transaction/src/test/java/org/onap/policy/drools/testtransaction/TestTransactionTest2.java [new file with mode: 0644]

index c778bf6..452825c 100644 (file)
@@ -65,29 +65,29 @@ class TTImpl implements TestTransaction {
 
     @Override
     public synchronized void register(PolicyController controller) {
-        if (this.controllers.containsValue(controller)) {
-            final TTControllerTask controllerTask = this.controllers.get(controller.getName());
-            if (controllerTask.isAlive()) {
-                return;
-            }
-
-            // continue : unregister, register operation
+        TTControllerTask controllerTask = this.controllers.get(controller.getName());
+        if (controllerTask != null && controllerTask.isAlive()) {
+            return;
         }
 
-        final TTControllerTask controllerTask = new TTControllerTask(controller);
+        // continue : unregister, register operation
+
+        controllerTask = makeControllerTask(controller);
         this.controllers.put(controller.getName(), controllerTask);
     }
 
     @Override
     public synchronized void unregister(PolicyController controller) {
-        if (!this.controllers.containsValue(controller)) {
-            return;
+        final TTControllerTask controllerTask = this.controllers.remove(controller.getName());
+        if (controllerTask != null) {
+            controllerTask.stop();
         }
+    }
 
-        final TTControllerTask controllerTask = this.controllers.get(controller.getName());
-        controllerTask.stop();
+    // these may be overridden by junit tests
 
-        this.controllers.remove(controller.getName());
+    protected TTControllerTask makeControllerTask(PolicyController controller) {
+        return new TTControllerTask(controller);
     }
 }
 
@@ -103,7 +103,7 @@ class TTControllerTask implements Runnable {
     protected final PolicyController controller;
 
     protected volatile boolean alive = true;
-    protected final Thread thread = new Thread(this);
+    protected final Thread thread = makeThread(this);
 
     public TTControllerTask(PolicyController controller) {
         this.controller = controller;
@@ -123,7 +123,7 @@ class TTControllerTask implements Runnable {
         this.alive = false;
         this.thread.interrupt();
         try {
-            this.thread.join(1000);
+            joinThread(1000);
         } catch (final InterruptedException e) {
             logger.error("TestTransaction thread threw", e);
             this.thread.interrupt();
@@ -163,13 +163,13 @@ class TTControllerTask implements Runnable {
                     return;
                 }
 
-                if (!Thread.currentThread().isInterrupted()) {
-                    Thread.sleep(TestTransaction.DEFAULT_TT_TASK_SLEEP);
+                if (!getCurrentThread().isInterrupted()) {
+                    doSleep(TestTransaction.DEFAULT_TT_TASK_SLEEP);
                 }
             }
         } catch (final InterruptedException e) {
             logger.info("{}: stopping ...", this, e);
-            Thread.currentThread().interrupt();
+            getCurrentThread().interrupt();
         } catch (final IllegalArgumentException e) {
             logger.error(
                     "{}: controller {} has not been enabled for testing: ",
@@ -245,4 +245,22 @@ class TTControllerTask implements Runnable {
         builder.append("]");
         return builder.toString();
     }
+
+    // these may be overridden by junit tests
+    
+    protected Thread makeThread(Runnable action) {
+        return new Thread(action);
+    }
+
+    protected void joinThread(long waitTimeMs) throws InterruptedException {
+        this.thread.join(waitTimeMs);
+    }
+
+    protected void doSleep(long sleepMs) throws InterruptedException {
+        Thread.sleep(sleepMs);
+    }
+
+    protected Thread getCurrentThread() {
+        return Thread.currentThread();
+    }
 }
index 09be93f..40b1ff9 100644 (file)
@@ -29,7 +29,8 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Properties;
 import java.util.Set;
-
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.onap.policy.drools.persistence.SystemPersistence;
@@ -75,30 +76,46 @@ public class TestTransactionTest {
                 PolicyEngine.manager.createPolicyController(TEST_CONTROLLER_NAME, controllerProperties);
         assertNotNull(PolicyController.factory.get(TEST_CONTROLLER_NAME));
         logger.info(controller.toString());
-
-        TestTransaction.manager.register(controller);
+        
+        CountDownLatch latch = new CountDownLatch(1);
+        
+        // use our own impl so we can decrement the latch when run() completes
+        TTImpl impl = new TTImpl() {
+            @Override
+            protected TTControllerTask makeControllerTask(PolicyController controller) {
+                return new TTControllerTask(controller) {
+                    @Override
+                    public void run() {
+                        super.run();
+                        latch.countDown();
+                    }
+                };
+            }            
+        };
+
+        impl.register(controller);
         assertNotNull(TestTransaction.manager);
 
         /*
          * Unregistering the controller should terminate its TestTransaction thread if it hasn't already
          * been terminated
          */
-        TestTransaction.manager.unregister(controller);
+        impl.unregister(controller);
 
-        Thread ttThread = this.getThread("tt-controller-task-" + TEST_CONTROLLER_NAME);
+        Thread ttThread = getThread(latch, "tt-controller-task-" + TEST_CONTROLLER_NAME);
         assertEquals(null, ttThread);
     }
 
     /**
      * Returns thread object based on String name.
-     * 
+     * @param latch indicates when the thread has finished running 
      * @param threadName thread name
      * @return the thread
      * @throws InterruptedException exception
      */
-    public Thread getThread(String threadName) throws InterruptedException {
+    public Thread getThread(CountDownLatch latch, String threadName) throws InterruptedException {
         // give a chance to the transaction thread to be spawned/destroyed
-        Thread.sleep(5000L);
+        latch.await(5, TimeUnit.SECONDS);
 
         final Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
         for (final Thread thread : threadSet) {
diff --git a/feature-test-transaction/src/test/java/org/onap/policy/drools/testtransaction/TestTransactionTest2.java b/feature-test-transaction/src/test/java/org/onap/policy/drools/testtransaction/TestTransactionTest2.java
new file mode 100644 (file)
index 0000000..7c31ba9
--- /dev/null
@@ -0,0 +1,409 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-test-transaction
+ * ================================================================================
+ * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.drools.testtransaction;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EventObject;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.drools.controller.DroolsController;
+import org.onap.policy.drools.core.PolicyContainer;
+import org.onap.policy.drools.system.PolicyController;
+
+public class TestTransactionTest2 {
+
+    private static final int MAX_SLEEP_COUNT = 3;
+    private static final String EXPECTED = "expected exception";
+    private static final String CONTROLLER1 = "controller-a";
+    private static final String CONTROLLER2 = "controller-b";
+    private static final String CONTROLLER3 = "controller-c";
+    private static final String SESSION1 = "session-a";
+    private static final String SESSION2 = "session-b";
+    private static final List<String> sessions = Arrays.asList(SESSION1, SESSION2);
+    private static final List<Object> facts = Arrays.asList(0L);
+
+    private Thread theThread;
+    private PolicyController controller;
+    private PolicyController controller2;
+    private PolicyController controller3;
+    private Runnable theAction;
+    private long waitJoinMs;
+    private long doSleepMs;
+    private DroolsController drools;
+    private PolicyContainer container;
+    private Map<String, TTControllerTask> name2task;
+    private TTControllerTask task;
+    private TTControllerTask task2;
+    private TTControllerTask task3;
+    private TestTransTImplTester impl;
+
+    /**
+     * Initialize objects for each test.
+     */
+    @Before
+    public void setUp() {
+        theThread = mock(Thread.class);
+        controller = mock(PolicyController.class);
+        controller2 = mock(PolicyController.class);
+        controller3 = mock(PolicyController.class);
+        theAction = null;
+        waitJoinMs = -1;
+        doSleepMs = -1;
+        drools = mock(DroolsController.class);
+        container = mock(PolicyContainer.class);
+        task2 = mock(TTControllerTask.class);
+        task3 = mock(TTControllerTask.class);
+        name2task = new TreeMap<>();
+
+        when(drools.getSessionNames()).thenReturn(sessions);
+        when(drools.isBrained()).thenReturn(true);
+        when(drools.factQuery(anyString(), anyString(), anyString(), anyBoolean())).thenReturn(facts);
+        when(drools.getContainer()).thenReturn(container);
+
+        when(controller.getName()).thenReturn(CONTROLLER1);
+        when(controller.getDrools()).thenReturn(drools);
+        when(controller.isAlive()).thenReturn(true);
+
+        when(controller2.getName()).thenReturn(CONTROLLER2);
+        when(controller2.getDrools()).thenReturn(drools);
+        when(controller2.isAlive()).thenReturn(true);
+
+        when(controller3.getName()).thenReturn(CONTROLLER3);
+        when(controller3.getDrools()).thenReturn(drools);
+        when(controller3.isAlive()).thenReturn(true);
+
+        task = new TestTransControllerTaskTester(controller);
+
+        name2task.put(CONTROLLER1, task);
+        name2task.put(CONTROLLER2, task2);
+        name2task.put(CONTROLLER3, task3);
+
+        impl = new TestTransTImplTester();
+    }
+
+    @Test
+    public void testTestTransactionImpl() {
+        assertNotNull(TTImpl.manager);
+    }
+
+    @Test
+    public void testTestTransactionImplRegister_testTestTransactionImplUnregister() {
+        task = mock(TTControllerTask.class);
+        when(task.isAlive()).thenReturn(true);
+        name2task.put(CONTROLLER1, task);
+
+        impl.register(controller);
+        impl.register(controller2);
+
+        // re-register
+        impl.register(controller);
+
+        // re-register when task is not running
+
+        // give controller3 same name as controller1 -> task3 replaces task
+        when(controller3.getName()).thenReturn(CONTROLLER1);
+        name2task.put(CONTROLLER1, task3);
+        when(task.isAlive()).thenReturn(false);
+        impl.register(controller3);
+
+        impl.unregister(controller);
+        verify(task, never()).stop();
+        verify(task2, never()).stop();
+        verify(task3).stop();
+
+        impl.unregister(controller2);
+        verify(task2).stop();
+
+        // unregister again - stop() should not be called again
+        impl.unregister(controller3);
+        verify(task3).stop();
+        
+        // unregister original controller - no stop() should be called again
+        impl.unregister(controller);
+        verify(task, never()).stop();
+        verify(task2).stop();
+        verify(task3).stop();
+    }
+
+    @Test
+    public void testTestTransactionControllerTaskFactory() throws Exception {
+        task = new TTControllerTask(controller) {
+            @Override
+            protected Thread makeThread(Runnable action) {
+                return theThread;
+            }
+
+            @Override
+            protected void joinThread(long waitTimeMs) throws InterruptedException {
+                // do nothing
+            }
+        };
+
+        task.doSleep(1);
+        assertEquals(Thread.currentThread(), task.getCurrentThread());
+    }
+
+    @Test
+    public void testTestTransactionControllerTask() {
+        assertEquals(task, theAction);
+        assertTrue(task.isAlive());
+        assertEquals(controller, task.getController());
+        assertEquals(theThread, task.getThread());
+
+        verify(theThread).start();
+    }
+
+    @Test
+    public void testTestTransactionControllerTaskGetController() {
+        assertEquals(controller, task.getController());
+    }
+
+    @Test
+    public void testTestTransactionControllerTaskGetThread() {
+        assertEquals(theThread, task.getThread());
+    }
+
+    @Test
+    public void testTestTransactionControllerTaskStop() throws Exception {
+        task.stop();
+        assertFalse(task.isAlive());
+        verify(theThread).interrupt();
+        assertTrue(waitJoinMs > 0);
+
+        // throw interrupt during join()
+        setUp();
+        task = new TestTransControllerTaskTester(controller) {
+            @Override
+            protected void joinThread(long waitTimeMs) throws InterruptedException {
+                waitJoinMs = waitTimeMs;
+                throw new InterruptedException(EXPECTED);
+            }
+        };
+        task.stop();
+        assertFalse(task.isAlive());
+        verify(theThread, times(2)).interrupt();
+        assertTrue(waitJoinMs > 0);
+    }
+
+    @Test
+    public void testTestTransactionControllerTaskRun() {
+        task.run();
+        assertFalse(task.isAlive());
+        verify(theThread, never()).interrupt();
+        verify(controller, times(MAX_SLEEP_COUNT + 1)).isAlive();
+        assertTrue(doSleepMs > 0);
+
+        // not brained
+        setUp();
+        when(drools.isBrained()).thenReturn(false);
+        task.run();
+        assertFalse(task.isAlive());
+        verify(controller, never()).isAlive();
+        assertEquals(-1, doSleepMs);
+
+        // controller not running
+        setUp();
+        when(controller.isAlive()).thenReturn(false);
+        task.run();
+        assertFalse(task.isAlive());
+        assertEquals(-1, doSleepMs);
+
+        // controller is locked
+        setUp();
+        when(controller.isLocked()).thenReturn(true);
+        task.run();
+        assertFalse(task.isAlive());
+        assertEquals(-1, doSleepMs);
+
+        // un-brain during sleep
+        setUp();
+        task = new TestTransControllerTaskTester(controller) {
+            @Override
+            protected void doSleep(long sleepMs) throws InterruptedException {
+                when(drools.isBrained()).thenReturn(false);
+                super.doSleep(sleepMs);
+            }
+        };
+        task.run();
+        assertFalse(task.isAlive());
+        // only hit top of the loop twice
+        verify(controller, times(2)).isAlive();
+        assertTrue(doSleepMs > 0);
+
+        // stop during sleep
+        setUp();
+        task = new TestTransControllerTaskTester(controller) {
+            @Override
+            protected void doSleep(long sleepMs) throws InterruptedException {
+                task.stop();
+                super.doSleep(sleepMs);
+            }
+        };
+        task.run();
+        assertFalse(task.isAlive());
+        // only hit top of the loop twice
+        verify(controller, times(2)).isAlive();
+        assertTrue(doSleepMs > 0);
+
+        // isInterrupted() returns true the first time, interrupt next time
+        setUp();
+        AtomicInteger count = new AtomicInteger(1);
+        when(theThread.isInterrupted()).thenAnswer(args -> {
+            if (count.decrementAndGet() >= 0) {
+                return true;
+            } else {
+                throw new InterruptedException(EXPECTED);
+            }
+        });
+        task.run();
+        assertFalse(task.isAlive());
+        verify(controller, times(2)).isAlive();
+        // doSleep() should not be called
+        assertEquals(-1, doSleepMs);
+
+        // interrupt during sleep
+        setUp();
+        task = new TestTransControllerTaskTester(controller) {
+            @Override
+            protected void doSleep(long sleepMs) throws InterruptedException {
+                super.doSleep(sleepMs);
+                throw new InterruptedException(EXPECTED);
+            }
+        };
+        task.run();
+        assertFalse(task.isAlive());
+        verify(theThread).interrupt();
+        // only hit top of the loop once
+        verify(controller).isAlive();
+        assertTrue(doSleepMs > 0);
+
+        // stop() during factQuery()
+        setUp();
+        when(drools.factQuery(anyString(), anyString(), anyString(), anyBoolean())).thenAnswer(args -> {
+            task.stop();
+            return facts;
+        });
+        task.run();
+        assertFalse(task.isAlive());
+        // only hit top of the loop once
+        verify(controller).isAlive();
+
+        // exception during isBrained() check
+        setUp();
+        when(drools.isBrained()).thenThrow(new IllegalArgumentException(EXPECTED));
+        task.run();
+        assertFalse(task.isAlive());
+
+        // other exception during isBrained() check
+        setUp();
+        when(drools.isBrained()).thenThrow(new RuntimeException(EXPECTED));
+        task.run();
+        assertFalse(task.isAlive());
+    }
+
+    @Test
+    public void testTestTransactionControllerTaskInjectTxIntoSessions() {
+        task.run();
+        verify(container, times(MAX_SLEEP_COUNT * sessions.size())).insert(anyString(), any(EventObject.class));
+
+        // null facts
+        setUp();
+        when(drools.factQuery(anyString(), anyString(), anyString(), anyBoolean())).thenReturn(null);
+        task.run();
+        verify(container, never()).insert(anyString(), any());
+
+        // empty fact list
+        setUp();
+        when(drools.factQuery(anyString(), anyString(), anyString(), anyBoolean())).thenReturn(Collections.emptyList());
+        task.run();
+        verify(container, never()).insert(anyString(), any());
+    }
+
+    @Test
+    public void testTestTransactionControllerTaskToString() {
+        assertTrue(task.toString().startsWith("TTControllerTask ["));
+    }
+
+    /**
+     * TestTransaction with overridden methods.
+     */
+    private class TestTransTImplTester extends TTImpl {
+
+        @Override
+        protected TTControllerTask makeControllerTask(PolicyController controller) {
+            return name2task.get(controller.getName());
+        }
+    }
+
+    /**
+     * Controller task with overridden methods.
+     */
+    private class TestTransControllerTaskTester extends TTControllerTask {
+        private int sleepCount = MAX_SLEEP_COUNT;
+
+        public TestTransControllerTaskTester(PolicyController controller) {
+            super(controller);
+        }
+
+        @Override
+        protected Thread makeThread(Runnable action) {
+            theAction = action;
+            return theThread;
+        }
+
+        @Override
+        protected void joinThread(long waitTimeMs) throws InterruptedException {
+            waitJoinMs = waitTimeMs;
+        }
+
+        @Override
+        protected void doSleep(long sleepMs) throws InterruptedException {
+            doSleepMs = sleepMs;
+
+            if (--sleepCount <= 0) {
+                when(controller.isAlive()).thenReturn(false);
+            }
+        }
+
+        @Override
+        protected Thread getCurrentThread() {
+            return thread;
+        }
+    }
+}