Add ExtractAppender to facilite junit tests 65/30765/8
authorJim Hahn <jrh3@att.com>
Wed, 7 Feb 2018 22:13:32 +0000 (17:13 -0500)
committerJim Hahn <jrh3@att.com>
Thu, 8 Feb 2018 15:32:53 +0000 (10:32 -0500)
Added ExtractAppender to provide a way for junit tests to capture
data sent to a logger.
Changed logback.version to 1.2.3 and moved the property to the
top-level pom.
Updated license date in top-level pom.
Refactored ExceptionsText, adding ErrorsTester and
ThrowablesTester classes to reduce sonar issues.

Change-Id: Ief7d08972bf4e7037b59c2afe4b77b252f2ad60a
Issue-ID: POLICY-582
Signed-off-by: Jim Hahn <jrh3@att.com>
13 files changed:
pom.xml
utils-test/pom.xml
utils-test/src/main/java/org/onap/policy/common/utils/test/ConstructionError.java [new file with mode: 0644]
utils-test/src/main/java/org/onap/policy/common/utils/test/ErrorsTester.java [new file with mode: 0644]
utils-test/src/main/java/org/onap/policy/common/utils/test/ExceptionsTester.java
utils-test/src/main/java/org/onap/policy/common/utils/test/ThrowablesTester.java [new file with mode: 0644]
utils-test/src/main/java/org/onap/policy/common/utils/test/log/logback/ExtractAppender.java [new file with mode: 0644]
utils-test/src/test/java/org/onap/policy/common/utils/test/ConstructionErrorTest.java [new file with mode: 0644]
utils-test/src/test/java/org/onap/policy/common/utils/test/ErrorsTesterTest.java [new file with mode: 0644]
utils-test/src/test/java/org/onap/policy/common/utils/test/ExceptionsTesterTest.java
utils-test/src/test/java/org/onap/policy/common/utils/test/ThrowablesTesterTest.java [new file with mode: 0644]
utils-test/src/test/java/org/onap/policy/common/utils/test/log/logback/ExtractAppenderTest.java [new file with mode: 0644]
utils-test/src/test/resources/logback-test.xml [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index 67454f3..cd37e22 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
   ============LICENSE_START=======================================================
   ONAP Policy Engine - Drools PDP
   ================================================================================
-  Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+  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.
@@ -41,6 +41,7 @@
        <properties>
                <maven.compiler.source>1.8</maven.compiler.source>
                <maven.compiler.target>1.8</maven.compiler.target>
+               <logback.version>1.2.3</logback.version>
                <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
                <nexusproxy>https://nexus.onap.org</nexusproxy>
                 <sitePath>/content/sites/site/${project.groupId}/${project.artifactId}/${project.version}</sitePath>
index f9b314f..31a2736 100644 (file)
                        <artifactId>junit</artifactId>
                        <version>4.11</version>
                </dependency>
+               <dependency>
+                       <groupId>ch.qos.logback</groupId>
+                       <artifactId>logback-classic</artifactId>
+                       <version>${logback.version}</version>
+               </dependency>
        </dependencies>
 
        <build>
diff --git a/utils-test/src/main/java/org/onap/policy/common/utils/test/ConstructionError.java b/utils-test/src/main/java/org/onap/policy/common/utils/test/ConstructionError.java
new file mode 100644 (file)
index 0000000..336dc60
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Integrity Audit
+ * ================================================================================
+ * Copyright (C) 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.common.utils.test;
+
+/**
+ * An error that occurred while trying to construct an object for a junit test.
+ */
+public class ConstructionError extends AssertionError {
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * 
+        */
+       public ConstructionError() {
+               super();
+       }
+
+       /**
+        * @param message
+        */
+       public ConstructionError(String message) {
+               super(message);
+       }
+
+       /**
+        * @param cause
+        */
+       public ConstructionError(Throwable cause) {
+               super(cause);
+       }
+
+       /**
+        * @param message
+        * @param cause
+        */
+       public ConstructionError(String message, Throwable cause) {
+               super(message, cause);
+       }
+
+}
diff --git a/utils-test/src/main/java/org/onap/policy/common/utils/test/ErrorsTester.java b/utils-test/src/main/java/org/onap/policy/common/utils/test/ErrorsTester.java
new file mode 100644 (file)
index 0000000..12b4844
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Integrity Monitor
+ * ================================================================================
+ * Copyright (C) 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.common.utils.test;
+
+/**
+ * Used to test various Error subclasses. Uses reflection to identify the
+ * constructors that the subclass supports.
+ */
+public class ErrorsTester extends ThrowablesTester {
+
+       /**
+        * Runs tests, on an Error subclass, for all of the standard constructors.
+        * If the Error subclass does not support a given type of constructor, then
+        * it skips that test. Does <i>not</i> throw an exception if no standard
+        * constructors are found.
+        * 
+        * @param claz
+        *            subclass to be tested
+        * @return the number of constructors that were found/tested
+        * @throws ConstructionError
+        *             if the Error subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Error> int testError(Class<T> claz) {
+               return testThrowable(claz);
+       }
+}
index 8705c48..156fca8 100644 (file)
@@ -22,7 +22,6 @@ package org.onap.policy.common.utils.test;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.lang.reflect.Constructor;
@@ -31,7 +30,7 @@ import java.lang.reflect.Constructor;
  * Used to test various Exception subclasses. Uses reflection to identify the
  * constructors that the subclass supports.
  */
-public class ExceptionsTester {
+public class ExceptionsTester extends ThrowablesTester {
 
        /**
         * Runs tests, on an Exception subclass, for all of the standard
@@ -40,144 +39,43 @@ public class ExceptionsTester {
         * 
         * @param claz
         *            subclass to be tested
-        * @return the number of constructors that the test found/tested
-        * @throws Exception
-        *             if the Exception cannot be constructed
+        * @return the number of constructors that were found/tested
+        * @throws ConstructionError
+        *             if the Exception subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
         */
-       public <T extends Exception> int test(Class<T> claz) throws Exception {
-               int ncons = 0;
+       public <T extends Exception> int test(Class<T> claz) {
+               int ncons = testException(claz);
 
-               ncons += testDefault(claz);
-               ncons += testString(claz);
-               ncons += testThrowable(claz);
-               ncons += testException(claz);
-               ncons += testStringThrowable(claz);
-               ncons += testStringException(claz);
-               ncons += testStringThrowableBooleanBoolean(claz);
-               ncons += testStringExceptionBooleanBoolean(claz);
-               
                assertTrue(ncons > 0);
 
                return ncons;
        }
 
        /**
-        * Tests exceptions created via the default constructor. Verifies that:
-        * <ul>
-        * <li><i>toString()</i> returns a non-null value</li>
-        * <li><i>getMessage()</i> returns null</li>
-        * <li><i>getCause()</i> returns null</li>
-        * </ul>
-        * 
-        * If the Exception subclass does not support this type of constructor, then
-        * this method simply returns.
-        * 
-        * @param claz
-        *            subclass to be tested
-        * @return {@code 1}, if the subclass supports this type of constructor,
-        *         {@code 0} otherwise
-        * @throws Exception
-        *             if the Exception cannot be constructed
-        */
-       public <T extends Exception> int testDefault(Class<T> claz) throws Exception {
-               Constructor<T> cons;
-
-               try {
-                       cons = claz.getConstructor();
-
-               } catch (NoSuchMethodException | SecurityException e) {
-                       // this constructor is not defined so nothing to test
-                       return 0;
-               }
-
-               T ex = cons.newInstance();
-
-               assertNotNull(ex.toString());
-               assertNull(ex.getMessage());
-               assertNull(ex.getCause());
-
-               return 1;
-       }
-
-       /**
-        * Tests exceptions created via the constructor that takes just a String.
-        * Verifies that:
-        * <ul>
-        * <li><i>toString()</i> returns a non-null value</li>
-        * <li><i>getMessage()</i> returns the original message passed to the
-        * constructor</li>
-        * <li><i>getCause()</i> returns null</li>
-        * </ul>
-        * 
-        * If the Exception subclass does not support this type of constructor, then
-        * this method simply returns.
-        * 
-        * @param claz
-        *            subclass to be tested
-        * @return {@code 1}, if the subclass supports this type of constructor,
-        *         {@code 0} otherwise
-        * @throws Exception
-        *             if the Exception cannot be constructed
-        */
-       public <T extends Exception> int testString(Class<T> claz) throws Exception {
-               Constructor<T> cons;
-
-               try {
-                       cons = claz.getConstructor(String.class);
-
-               } catch (NoSuchMethodException | SecurityException e) {
-                       // this constructor is not defined so nothing to test
-                       return 0;
-               }
-
-               T ex = cons.newInstance("hello");
-
-               assertNotNull(ex.toString());
-               assertEquals("hello", ex.getMessage());
-               assertNull(ex.getCause());
-
-               return 1;
-       }
-
-       /**
-        * Tests exceptions created via the constructor that takes just a Throwable.
-        * Verifies that:
-        * <ul>
-        * <li><i>toString()</i> returns a non-null value</li>
-        * <li><i>getMessage()</i> returns the cause's message</li>
-        * <li><i>getCause()</i> returns the original cause passed to the
-        * constructor</li>
-        * </ul>
-        * 
-        * If the Exception subclass does not support this type of constructor, then
-        * this method simply returns.
+        * Runs tests, on an Exception subclass, for all of the standard
+        * constructors. If the Exception subclass does not support a given type of
+        * constructor, then it skips that test. Does <i>not</i> throw an exception
+        * if no standard constructors are found.
         * 
         * @param claz
         *            subclass to be tested
-        * @return {@code 1}, if the subclass supports this type of constructor,
-        *         {@code 0} otherwise
-        * @throws Exception
-        *             if the Exception cannot be constructed
+        * @return the number of constructors that were found/tested
+        * @throws ConstructionError
+        *             if the Exception subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
         */
-       public <T extends Exception> int testThrowable(Class<T> claz) throws Exception {
-               Constructor<T> cons;
-
-               try {
-                       cons = claz.getConstructor(Throwable.class);
-
-               } catch (NoSuchMethodException | SecurityException e) {
-                       // this constructor is not defined so nothing to test
-                       return 0;
-               }
-
-               Throwable cause = new Throwable("expected exception");
-               T ex = cons.newInstance(cause);
+       public <T extends Exception> int testException(Class<T> claz) {
+               int ncons = 0;
 
-               assertEquals(ex.getMessage(), ex.getMessage());
-               assertNotNull(ex.toString());
-               assertEquals(cause, ex.getCause());
+               ncons += testThrowable(claz);
+               ncons += testException_Exception(claz);
+               ncons += testException_StringException(claz);
+               ncons += testException_StringExceptionBooleanBoolean(claz);
 
-               return 1;
+               return ncons;
        }
 
        /**
@@ -197,22 +95,19 @@ public class ExceptionsTester {
         *            subclass to be tested
         * @return {@code 1}, if the subclass supports this type of constructor,
         *         {@code 0} otherwise
-        * @throws Exception
-        *             if the Exception cannot be constructed
+        * @throws ConstructionError
+        *             if the Exception subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
         */
-       public <T extends Exception> int testException(Class<T> claz) throws Exception {
-               Constructor<T> cons;
-
-               try {
-                       cons = claz.getConstructor(Exception.class);
-
-               } catch (NoSuchMethodException | SecurityException e) {
-                       // this constructor is not defined so nothing to test
+       public <T extends Exception> int testException_Exception(Class<T> claz) {
+               Constructor<T> cons = getConstructor(claz, "exception", Exception.class);
+               if (cons == null) {
                        return 0;
                }
 
-               Exception cause = new Exception("expected exception");
-               T ex = cons.newInstance(cause);
+               Exception cause = new Exception(EXPECTED_EXCEPTION_MSG);
+               T ex = newInstance(cons, cause);
 
                assertNotNull(ex.toString());
                assertEquals(ex.getMessage(), ex.getMessage());
@@ -221,48 +116,6 @@ public class ExceptionsTester {
                return 1;
        }
 
-       /**
-        * Tests exceptions created via the constructor that takes a String and a
-        * Throwable. Verifies that:
-        * <ul>
-        * <li><i>toString()</i> returns a non-null value</li>
-        * <li><i>getMessage()</i> returns the original message passed to the
-        * constructor</li>
-        * <li><i>getCause()</i> returns the original cause passed to the
-        * constructor</li>
-        * </ul>
-        * 
-        * If the Exception subclass does not support this type of constructor, then
-        * this method simply returns.
-        * 
-        * @param claz
-        *            subclass to be tested
-        * @return {@code 1}, if the subclass supports this type of constructor,
-        *         {@code 0} otherwise
-        * @throws Exception
-        *             if the Exception cannot be constructed
-        */
-       public <T extends Exception> int testStringThrowable(Class<T> claz) throws Exception {
-               Constructor<T> cons;
-
-               try {
-                       cons = claz.getConstructor(String.class, Throwable.class);
-
-               } catch (NoSuchMethodException | SecurityException e) {
-                       // this constructor is not defined so nothing to test
-                       return 0;
-               }
-
-               Throwable cause = new Throwable("expected exception");
-               T ex = cons.newInstance("world", cause);
-
-               assertNotNull(ex.toString());
-               assertEquals("world", ex.getMessage());
-               assertEquals(cause, ex.getCause());
-
-               return 1;
-       }
-
        /**
         * Tests exceptions created via the constructor that takes a String and an
         * Exception. Verifies that:
@@ -281,22 +134,17 @@ public class ExceptionsTester {
         *            subclass to be tested
         * @return {@code 1}, if the subclass supports this type of constructor,
         *         {@code 0} otherwise
-        * @throws Exception
-        *             if the Exception cannot be constructed
+        * @throws ConstructionError
+        *             if the Exception subclass cannot be constructed
         */
-       public <T extends Exception> int testStringException(Class<T> claz) throws Exception {
-               Constructor<T> cons;
-
-               try {
-                       cons = claz.getConstructor(String.class, Exception.class);
-
-               } catch (NoSuchMethodException | SecurityException e) {
-                       // this constructor is not defined so nothing to test
+       public <T extends Exception> int testException_StringException(Class<T> claz) {
+               Constructor<T> cons = getConstructor(claz, "string-exception", String.class, Exception.class);
+               if (cons == null) {
                        return 0;
                }
 
-               Exception cause = new Exception("expected exception");
-               T ex = cons.newInstance("world", cause);
+               Exception cause = new Exception(EXPECTED_EXCEPTION_MSG);
+               T ex = newInstance(cons, "world", cause);
 
                assertNotNull(ex.toString());
                assertEquals("world", ex.getMessage());
@@ -305,52 +153,6 @@ public class ExceptionsTester {
                return 1;
        }
 
-       /**
-        * Tests exceptions created via the constructor that takes a String, a
-        * Throwable, and two booleans. Verifies that:
-        * <ul>
-        * <li><i>toString()</i> returns a non-null value</li>
-        * <li><i>getMessage()</i> returns the original message passed to the
-        * constructor</li>
-        * <li><i>getCause()</i> returns the original cause passed to the
-        * constructor</li>
-        * <li>suppressed exceptions can be added, if enabled</li>
-        * <li>the stack trace can be added, if enabled</li>
-        * </ul>
-        * 
-        * If the Exception subclass does not support this type of constructor, then
-        * this method simply returns.
-        * 
-        * @param claz
-        *            subclass to be tested
-        * @return {@code 1}, if the subclass supports this type of constructor,
-        *         {@code 0} otherwise
-        * @throws Exception
-        *             if the Exception cannot be constructed
-        */
-       public <T extends Exception> int testStringThrowableBooleanBoolean(Class<T> claz) throws Exception {
-               Constructor<T> cons;
-
-               try {
-                       cons = claz.getConstructor(String.class, Throwable.class, Boolean.TYPE, Boolean.TYPE);
-
-               } catch (NoSuchMethodException | SecurityException e) {
-                       // this constructor is not defined so nothing to test
-                       return 0;
-               }
-
-               // test each combination of "message" and "cause"
-               testMessageCauseCombos(cons);
-
-               // test each combination of the boolean flags
-               testSuppressStack(cons);
-               testSuppressNoStack(cons);
-               testNoSuppressStack(cons);
-               testNoSuppressNoStack(cons);
-
-               return 1;
-       }
-
        /**
         * Tests exceptions created via the constructor that takes a String, an
         * Exception, and two booleans. Verifies that:
@@ -371,218 +173,22 @@ public class ExceptionsTester {
         *            subclass to be tested
         * @return {@code 1}, if the subclass supports this type of constructor,
         *         {@code 0} otherwise
-        * @throws Exception
-        *             if the Exception cannot be constructed
+        * @throws ConstructionError
+        *             if the Exception subclass cannot be constructed
         */
-       public <T extends Exception> int testStringExceptionBooleanBoolean(Class<T> claz) throws Exception {
-               Constructor<T> cons;
-
-               try {
-                       cons = claz.getConstructor(String.class, Exception.class, Boolean.TYPE, Boolean.TYPE);
-
-               } catch (NoSuchMethodException | SecurityException e) {
-                       // this constructor is not defined so nothing to test
+       public <T extends Exception> int testException_StringExceptionBooleanBoolean(Class<T> claz) {
+               Constructor<T> cons = getConstructor(claz, "string-exception-flags", String.class, Exception.class,
+                               Boolean.TYPE, Boolean.TYPE);
+               if (cons == null) {
                        return 0;
                }
 
                // test each combination of "message" and "cause"
-               testMessageCauseCombos(cons);
+               testThrowable_MessageCauseCombos(cons);
 
                // test each combination of the boolean flags
-               testFlagCombos(cons);
+               testThrowable_FlagCombos(cons);
 
                return 1;
        }
-
-       /**
-        * Tests each combination of values for the "message" and the "cause" when
-        * using the constructor that takes a String, a Throwable/Exception, and two
-        * booleans. Verifies that expected values are returned by <i>toString()/i>,
-        * <i>getMessage()</i>, and <i>getCause()</i>.
-        * </ul>
-        * 
-        * @param cons
-        *            constructor to be invoked
-        * @throws Exception
-        *             if the Exception cannot be constructed
-        */
-       private <T extends Exception> void testMessageCauseCombos(Constructor<T> cons) throws Exception {
-               T ex;
-               Exception cause = new Exception("expected throwable");
-
-               ex = cons.newInstance(null, null, true, true);
-               assertNotNull(ex.toString());
-               assertNull(ex.getMessage());
-               assertNull(ex.getCause());
-
-               ex = cons.newInstance("abc", null, true, true);
-               assertNotNull(ex.toString());
-               assertEquals("abc", ex.getMessage());
-               assertNull(ex.getCause());
-
-               ex = cons.newInstance(null, cause, true, true);
-               assertNotNull(ex.toString());
-               assertNull(ex.getMessage());
-               assertEquals(cause, ex.getCause());
-
-               ex = cons.newInstance("xyz", cause, true, true);
-               assertNotNull(ex.toString());
-               assertEquals("xyz", ex.getMessage());
-               assertEquals(cause, ex.getCause());
-       }
-
-       /**
-        * Tests each combination of values for the "message" and the "cause" when
-        * using the constructor that takes a String, a Throwable/Exception, and two
-        * booleans. Verifies that expected values are returned by <i>toString()/i>,
-        * <i>getMessage()</i>, and <i>getCause()</i>.
-        * </ul>
-        * 
-        * @param cons
-        *            constructor to be invoked
-        * @throws Exception
-        *             if the Exception cannot be constructed
-        */
-       public <T extends Exception> void testFlagCombos(Constructor<T> cons) throws Exception {
-               testSuppressStack(cons);
-               testSuppressNoStack(cons);
-               testNoSuppressStack(cons);
-               testNoSuppressNoStack(cons);
-       }
-
-       /**
-        * Tests exceptions constructed with {@code enableSuppression=true} and
-        * {@code writableStackTrace=true}. Verifies that:
-        * <ul>
-        * <li><i>toString()</i> returns a non-null value</li>
-        * <li><i>getMessage()</i> returns the original message passed to the
-        * constructor</li>
-        * <li><i>getCause()</i> returns the original cause passed to the
-        * constructor</li>
-        * <li>suppressed exceptions are added</li>
-        * <li>the stack trace is added</li>
-        * </ul>
-        * 
-        * @param cons
-        *            the exception's class constructor
-        * @throws Exception
-        *             if the Exception cannot be constructed
-        */
-       public <T extends Exception> void testSuppressStack(Constructor<T> cons) throws Exception {
-               Exception cause = new Exception("expected exception");
-               Throwable supr = new Throwable("expected suppressed exception");
-               T ex = cons.newInstance("yes,yes", cause, true, true);
-
-               ex.addSuppressed(supr);
-
-               assertNotNull(ex.toString());
-               assertEquals("yes,yes", ex.getMessage());
-               assertEquals(cause, ex.getCause());
-
-               assertEquals(1, ex.getSuppressed().length);
-               assertEquals(supr, ex.getSuppressed()[0]);
-
-               assertTrue(ex.getStackTrace().length > 0);
-       }
-
-       /**
-        * Tests exceptions constructed with {@code enableSuppression=true} and
-        * {@code writableStackTrace=false}. Verifies that:
-        * <ul>
-        * <li><i>toString()</i> returns a non-null value</li>
-        * <li><i>getMessage()</i> returns the original message passed to the
-        * constructor</li>
-        * <li><i>getCause()</i> returns the original cause passed to the
-        * constructor</li>
-        * <li>suppressed exceptions are added</li>
-        * <li>the stack trace is <i>not</i> added</li>
-        * </ul>
-        * 
-        * @param cons
-        *            the exception's class constructor
-        * @throws Exception
-        *             if the Exception cannot be constructed
-        */
-       public <T extends Exception> void testSuppressNoStack(Constructor<T> cons) throws Exception {
-               Exception cause = new Exception("expected exception");
-               Throwable supr = new Throwable("expected suppressed exception");
-               T ex = cons.newInstance("yes,no", cause, true, false);
-
-               ex.addSuppressed(supr);
-
-               assertNotNull(ex.toString());
-               assertEquals("yes,no", ex.getMessage());
-               assertEquals(cause, ex.getCause());
-
-               assertEquals(1, ex.getSuppressed().length);
-               assertEquals(supr, ex.getSuppressed()[0]);
-
-               assertEquals(0, ex.getStackTrace().length);
-       }
-
-       /**
-        * Tests exceptions constructed with {@code enableSuppression=false} and
-        * {@code writableStackTrace=true}. Verifies that:
-        * <ul>
-        * <li><i>toString()</i> returns a non-null value</li>
-        * <li><i>getMessage()</i> returns the original message passed to the
-        * constructor</li>
-        * <li><i>getCause()</i> returns the original cause passed to the
-        * constructor</li>
-        * <li>suppressed exceptions are <i>not</i> added</li>
-        * <li>the stack trace is added</li>
-        * </ul>
-        * 
-        * @param cons
-        *            the exception's class constructor
-        * @throws Exception
-        *             if the Exception cannot be constructed
-        */
-       public <T extends Exception> void testNoSuppressStack(Constructor<T> cons) throws Exception {
-               Exception cause = new Exception("expected exception");
-               Throwable supr = new Throwable("expected suppressed exception");
-               T ex = cons.newInstance("no,yes", cause, false, true);
-
-               ex.addSuppressed(supr);
-
-               assertNotNull(ex.toString());
-               assertEquals("no,yes", ex.getMessage());
-               assertEquals(cause, ex.getCause());
-
-               assertEquals(0, ex.getSuppressed().length);
-
-               assertTrue(ex.getStackTrace().length > 0);
-       }
-
-       /**
-        * Tests exceptions constructed with {@code enableSuppression=false} and
-        * {@code writableStackTrace=false}. Verifies that:
-        * <ul>
-        * <li><i>toString()</i> returns a non-null value</li>
-        * <li><i>getMessage()</i> returns the original message passed to the
-        * constructor</li>
-        * <li><i>getCause()</i> returns the original cause passed to the
-        * constructor</li>
-        * <li>suppressed exceptions are <i>not</i> added</li>
-        * <li>the stack trace is <i>not</i> added</li>
-        * 
-        * @param cons
-        *            the exception's class constructor
-        * @throws Exception
-        *             if the Exception cannot be constructed
-        */
-       public <T extends Exception> void testNoSuppressNoStack(Constructor<T> cons) throws Exception {
-               Exception cause = new Exception("expected exception");
-               Throwable supr = new Throwable("expected suppressed exception");
-               T ex = cons.newInstance("no,no", cause, false, false);
-
-               ex.addSuppressed(supr);
-
-               assertNotNull(ex.toString());
-               assertEquals("no,no", ex.getMessage());
-               assertEquals(cause, ex.getCause());
-
-               assertEquals(0, ex.getSuppressed().length);
-               assertEquals(0, ex.getStackTrace().length);
-       }
 }
diff --git a/utils-test/src/main/java/org/onap/policy/common/utils/test/ThrowablesTester.java b/utils-test/src/main/java/org/onap/policy/common/utils/test/ThrowablesTester.java
new file mode 100644 (file)
index 0000000..bd8db4f
--- /dev/null
@@ -0,0 +1,515 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Integrity Monitor
+ * ================================================================================
+ * Copyright (C) 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.common.utils.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Used to test various Throwable subclasses. Uses reflection to identify the
+ * constructors that the subclass supports.
+ */
+public class ThrowablesTester {
+
+       private static Logger logger = LoggerFactory.getLogger(ThrowablesTester.class);
+
+       public static final String EXPECTED_EXCEPTION_MSG = "expected exception";
+       private static final String EXPECTED_SUPPRESSED_EXCEPTION_MSG = "expected suppressed exception";
+
+       /**
+        * Passed as a "cause" to constructors.
+        */
+       public static final Exception CAUSE = new Exception(EXPECTED_EXCEPTION_MSG);
+
+       /**
+        * Passed to new objects via the <i>addSuppressed()</i> method..
+        */
+       public static final Throwable SUPPRESSED = new Throwable(EXPECTED_SUPPRESSED_EXCEPTION_MSG);
+
+       /**
+        * Runs tests, on an Throwable subclass, for all of the standard
+        * constructors. If the Throwable subclass does not support a given type of
+        * constructor, then it skips that test. Does <i>not</i> throw an exception
+        * if no standard constructors are found.
+        * 
+        * @param claz
+        *            subclass to be tested
+        * @return the number of constructors that were found/tested
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> int testThrowable(Class<T> claz) {
+               int ncons = 0;
+
+               ncons += testThrowable_Default(claz);
+               ncons += testThrowable_StringConstuctor(claz);
+               ncons += testThrowable_Throwable(claz);
+               ncons += testThrowable_StringThrowable(claz);
+               ncons += testThrowable_StringThrowableBooleanBoolean(claz);
+
+               return ncons;
+       }
+
+       /**
+        * Tests Throwable objects created via the default constructor. Verifies
+        * that:
+        * <ul>
+        * <li><i>toString()</i> returns a non-null value</li>
+        * <li><i>getMessage()</i> returns null</li>
+        * <li><i>getCause()</i> returns null</li>
+        * </ul>
+        * 
+        * If the Throwable subclass does not support this type of constructor, then
+        * this method simply returns.
+        * 
+        * @param claz
+        *            subclass to be tested
+        * @return {@code 1}, if the subclass supports this type of constructor,
+        *         {@code 0} otherwise
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> int testThrowable_Default(Class<T> claz) {
+               Constructor<T> cons = getConstructor(claz, "default");
+               if (cons == null) {
+                       return 0;
+               }
+
+               T ex = newInstance(cons);
+
+               assertNotNull(ex.toString());
+               assertNull(ex.getMessage());
+               assertNull(ex.getCause());
+
+               return 1;
+       }
+
+       /**
+        * Tests Throwable objects created via the constructor that takes just a
+        * String. Verifies that:
+        * <ul>
+        * <li><i>toString()</i> returns a non-null value</li>
+        * <li><i>getMessage()</i> returns the original message passed to the
+        * constructor</li>
+        * <li><i>getCause()</i> returns null</li>
+        * </ul>
+        * 
+        * If the Throwable subclass does not support this type of constructor, then
+        * this method simply returns.
+        * 
+        * @param claz
+        *            subclass to be tested
+        * @return {@code 1}, if the subclass supports this type of constructor,
+        *         {@code 0} otherwise
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> int testThrowable_StringConstuctor(Class<T> claz) {
+               Constructor<T> cons = getConstructor(claz, "string", String.class);
+               if (cons == null) {
+                       return 0;
+               }
+
+               T ex = newInstance(cons, "hello");
+
+               assertNotNull(ex.toString());
+               assertEquals("hello", ex.getMessage());
+               assertNull(ex.getCause());
+
+               return 1;
+       }
+
+       /**
+        * Tests Throwable objects created via the constructor that takes just a
+        * Throwable. Verifies that:
+        * <ul>
+        * <li><i>toString()</i> returns a non-null value</li>
+        * <li><i>getMessage()</i> returns the cause's message</li>
+        * <li><i>getCause()</i> returns the original cause passed to the
+        * constructor</li>
+        * </ul>
+        * 
+        * If the Throwable subclass does not support this type of constructor, then
+        * this method simply returns.
+        * 
+        * @param claz
+        *            subclass to be tested
+        * @return {@code 1}, if the subclass supports this type of constructor,
+        *         {@code 0} otherwise
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> int testThrowable_Throwable(Class<T> claz) {
+               Constructor<T> cons = getConstructor(claz, "throwable", Throwable.class);
+               if (cons == null) {
+                       return 0;
+               }
+
+               T ex = newInstance(cons, CAUSE);
+
+               assertEquals(ex.getMessage(), ex.getMessage());
+               assertNotNull(ex.toString());
+               assertEquals(CAUSE, ex.getCause());
+
+               return 1;
+       }
+
+       /**
+        * Tests Throwable objects created via the constructor that takes a String
+        * and a Throwable. Verifies that:
+        * <ul>
+        * <li><i>toString()</i> returns a non-null value</li>
+        * <li><i>getMessage()</i> returns the original message passed to the
+        * constructor</li>
+        * <li><i>getCause()</i> returns the original cause passed to the
+        * constructor</li>
+        * </ul>
+        * 
+        * If the Throwable subclass does not support this type of constructor, then
+        * this method simply returns.
+        * 
+        * @param claz
+        *            subclass to be tested
+        * @return {@code 1}, if the subclass supports this type of constructor,
+        *         {@code 0} otherwise
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> int testThrowable_StringThrowable(Class<T> claz) {
+               Constructor<T> cons = getConstructor(claz, "string-throwable", String.class, Throwable.class);
+               if (cons == null) {
+                       return 0;
+               }
+
+               T ex = newInstance(cons, "world", CAUSE);
+
+               assertNotNull(ex.toString());
+               assertEquals("world", ex.getMessage());
+               assertEquals(CAUSE, ex.getCause());
+
+               return 1;
+       }
+
+       /**
+        * Tests Throwable objects created via the constructor that takes a String,
+        * a Throwable, and two booleans. Verifies that:
+        * <ul>
+        * <li><i>toString()</i> returns a non-null value</li>
+        * <li><i>getMessage()</i> returns the original message passed to the
+        * constructor</li>
+        * <li><i>getCause()</i> returns the original cause passed to the
+        * constructor</li>
+        * <li>suppressed exceptions can be added, if enabled</li>
+        * <li>the stack trace can be added, if enabled</li>
+        * </ul>
+        * 
+        * If the Throwable subclass does not support this type of constructor, then
+        * this method simply returns.
+        * 
+        * @param claz
+        *            subclass to be tested
+        * @return {@code 1}, if the subclass supports this type of constructor,
+        *         {@code 0} otherwise
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> int testThrowable_StringThrowableBooleanBoolean(Class<T> claz) {
+               Constructor<T> cons = getConstructor(claz, "string-throwable-flags", String.class, Throwable.class,
+                               Boolean.TYPE, Boolean.TYPE);
+               if (cons == null) {
+                       return 0;
+               }
+
+               // test each combination of "message" and "cause"
+               testThrowable_MessageCauseCombos(cons);
+
+               // test each combination of the boolean flags
+               testThrowable_SuppressStack(cons);
+               testThrowable_SuppressNoStack(cons);
+               testThrowable_NoSuppressStack(cons);
+               testThrowable_NoSuppressNoStack(cons);
+
+               return 1;
+       }
+
+       /**
+        * Tests each combination of values for the "message" and the "cause" when
+        * using the constructor that takes a String, a Throwable/Exception, and two
+        * booleans. Verifies that expected values are returned by <i>toString()/i>,
+        * <i>getMessage()</i>, and <i>getCause()</i>.
+        * </ul>
+        * 
+        * @param cons
+        *            constructor to be invoked
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> void testThrowable_MessageCauseCombos(Constructor<T> cons) {
+               T ex;
+
+               ex = newInstance(cons, null, null, true, true);
+               assertNotNull(ex.toString());
+               assertNull(ex.getMessage());
+               assertNull(ex.getCause());
+
+               ex = newInstance(cons, "abc", null, true, true);
+               assertNotNull(ex.toString());
+               assertEquals("abc", ex.getMessage());
+               assertNull(ex.getCause());
+
+               ex = newInstance(cons, null, CAUSE, true, true);
+               assertNotNull(ex.toString());
+               assertNull(ex.getMessage());
+               assertEquals(CAUSE, ex.getCause());
+
+               ex = newInstance(cons, "xyz", CAUSE, true, true);
+               assertNotNull(ex.toString());
+               assertEquals("xyz", ex.getMessage());
+               assertEquals(CAUSE, ex.getCause());
+       }
+
+       /**
+        * Tests each combination of values for the "message" and the "cause" when
+        * using the constructor that takes a String, a Throwable/Exception, and two
+        * booleans. Verifies that expected values are returned by <i>toString()/i>,
+        * <i>getMessage()</i>, and <i>getCause()</i>.
+        * </ul>
+        * 
+        * @param cons
+        *            constructor to be invoked
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> void testThrowable_FlagCombos(Constructor<T> cons) {
+               testThrowable_SuppressStack(cons);
+               testThrowable_SuppressNoStack(cons);
+               testThrowable_NoSuppressStack(cons);
+               testThrowable_NoSuppressNoStack(cons);
+       }
+
+       /**
+        * Tests Throwable objects constructed with {@code enableSuppression=true}
+        * and {@code writableStackTrace=true}. Verifies that:
+        * <ul>
+        * <li><i>toString()</i> returns a non-null value</li>
+        * <li><i>getMessage()</i> returns the original message passed to the
+        * constructor</li>
+        * <li><i>getCause()</i> returns the original cause passed to the
+        * constructor</li>
+        * <li>suppressed exceptions are added</li>
+        * <li>the stack trace is added</li>
+        * </ul>
+        * 
+        * @param cons
+        *            the throwable's class constructor
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> void testThrowable_SuppressStack(Constructor<T> cons) {
+               T ex = newInstance(cons, "yes,yes", CAUSE, true, true);
+
+               ex.addSuppressed(SUPPRESSED);
+
+               assertNotNull(ex.toString());
+               assertEquals("yes,yes", ex.getMessage());
+               assertEquals(CAUSE, ex.getCause());
+
+               assertEquals(1, ex.getSuppressed().length);
+               assertEquals(SUPPRESSED, ex.getSuppressed()[0]);
+
+               assertTrue(ex.getStackTrace().length > 0);
+       }
+
+       /**
+        * Tests Throwable objects constructed with {@code enableSuppression=true}
+        * and {@code writableStackTrace=false}. Verifies that:
+        * <ul>
+        * <li><i>toString()</i> returns a non-null value</li>
+        * <li><i>getMessage()</i> returns the original message passed to the
+        * constructor</li>
+        * <li><i>getCause()</i> returns the original cause passed to the
+        * constructor</li>
+        * <li>suppressed exceptions are added</li>
+        * <li>the stack trace is <i>not</i> added</li>
+        * </ul>
+        * 
+        * @param cons
+        *            the throwable's class constructor
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> void testThrowable_SuppressNoStack(Constructor<T> cons) {
+               T ex = newInstance(cons, "yes,no", CAUSE, true, false);
+
+               ex.addSuppressed(SUPPRESSED);
+
+               assertNotNull(ex.toString());
+               assertEquals("yes,no", ex.getMessage());
+               assertEquals(CAUSE, ex.getCause());
+
+               assertEquals(1, ex.getSuppressed().length);
+               assertEquals(SUPPRESSED, ex.getSuppressed()[0]);
+
+               assertEquals(0, ex.getStackTrace().length);
+       }
+
+       /**
+        * Tests Throwable objects constructed with {@code enableSuppression=false}
+        * and {@code writableStackTrace=true}. Verifies that:
+        * <ul>
+        * <li><i>toString()</i> returns a non-null value</li>
+        * <li><i>getMessage()</i> returns the original message passed to the
+        * constructor</li>
+        * <li><i>getCause()</i> returns the original cause passed to the
+        * constructor</li>
+        * <li>suppressed exceptions are <i>not</i> added</li>
+        * <li>the stack trace is added</li>
+        * </ul>
+        * 
+        * @param cons
+        *            the throwable's class constructor
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> void testThrowable_NoSuppressStack(Constructor<T> cons) {
+               T ex = newInstance(cons, "no,yes", CAUSE, false, true);
+
+               ex.addSuppressed(SUPPRESSED);
+
+               assertNotNull(ex.toString());
+               assertEquals("no,yes", ex.getMessage());
+               assertEquals(CAUSE, ex.getCause());
+
+               assertEquals(0, ex.getSuppressed().length);
+
+               assertTrue(ex.getStackTrace().length > 0);
+       }
+
+       /**
+        * Tests Throwable objects constructed with {@code enableSuppression=false}
+        * and {@code writableStackTrace=false}. Verifies that:
+        * <ul>
+        * <li><i>toString()</i> returns a non-null value</li>
+        * <li><i>getMessage()</i> returns the original message passed to the
+        * constructor</li>
+        * <li><i>getCause()</i> returns the original cause passed to the
+        * constructor</li>
+        * <li>suppressed exceptions are <i>not</i> added</li>
+        * <li>the stack trace is <i>not</i> added</li>
+        * 
+        * @param cons
+        *            the throwable's class constructor
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        * @throws AssertionError
+        *             if the constructed objects fail to pass various tests
+        */
+       public <T extends Throwable> void testThrowable_NoSuppressNoStack(Constructor<T> cons) {
+               T ex = newInstance(cons, "no,no", CAUSE, false, false);
+
+               ex.addSuppressed(SUPPRESSED);
+
+               assertNotNull(ex.toString());
+               assertEquals("no,no", ex.getMessage());
+               assertEquals(CAUSE, ex.getCause());
+
+               assertEquals(0, ex.getSuppressed().length);
+               assertEquals(0, ex.getStackTrace().length);
+       }
+
+       /**
+        * Attempts to get a constructor for objects of a given type.
+        * 
+        * @param claz
+        *            class of objects whose constructor is to be gotten
+        * @param testType
+        *            type of test being run
+        * @param argTypes
+        *            argument types to be passed to the constructor
+        * @return the desired constructor, or {@code null} if the desired
+        *         constructor is not available
+        */
+       protected <T extends Throwable> Constructor<T> getConstructor(Class<T> claz, String testType,
+                       Class<?>... argTypes) {
+
+               try {
+                       return claz.getConstructor(argTypes);
+
+               } catch (NoSuchMethodException | SecurityException e) {
+                       // this constructor is not defined so nothing to test
+                       logger.debug("skipped test due to no " + testType + " constructor for: " + claz);
+                       return null;
+               }
+       }
+
+       /**
+        * Creates a new instance of an Throwable subclass.
+        * 
+        * @param cons
+        *            subclass constructor
+        * @param args
+        *            arguments to be passed to the constructor
+        * @return a new instance of the Throwable subclass
+        * @throws ConstructionError
+        *             if the Throwable subclass cannot be constructed
+        */
+       protected <T extends Throwable> T newInstance(Constructor<T> cons, Object... args) {
+               try {
+                       return cons.newInstance(args);
+
+               } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
+                               | InvocationTargetException e) {
+
+                       throw new ConstructionError(e);
+               }
+
+       }
+}
diff --git a/utils-test/src/main/java/org/onap/policy/common/utils/test/log/logback/ExtractAppender.java b/utils-test/src/main/java/org/onap/policy/common/utils/test/log/logback/ExtractAppender.java
new file mode 100644 (file)
index 0000000..f012464
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Integrity Audit
+ * ================================================================================
+ * Copyright (C) 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.common.utils.test.log.logback;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+
+/**
+ * This is an appender that is intended for use by JUnit tests that wish to
+ * capture logged messages. The appender takes an optional list of regular
+ * expressions that are used to identify and extract data of interest.
+ * <p/>
+ * If no patterns are provided, then every logged message is recorded. However,
+ * if patterns are provided, then only messages that match one of the patterns
+ * are recorded. In addition, if the pattern contains a capture group that is
+ * non-null, only the captured group is recorded. Otherwise, the entire portion
+ * of the message that matches the pattern is recorded.
+ * <p/>
+ * All operations are thread-safe.
+ */
+public class ExtractAppender extends AppenderBase<ILoggingEvent> {
+
+       /**
+        * Extracted text is placed here.
+        */
+       private final Queue<String> extracted;
+
+       /**
+        * Regular expressions/Patterns to be used to extract text. Uses a
+        * LinkedHashMap so that order is preserved.
+        */
+       private final LinkedHashMap<String, Pattern> patterns;
+
+       /**
+        * Records every message that is logged.
+        */
+       public ExtractAppender() {
+               this(new LinkedList<>());
+       }
+
+       /**
+        * Records portions of messages that match one of the regular expressions.
+        * 
+        * @param regex
+        *            regular expression (i.e., {@link Pattern}) to match
+        */
+       public ExtractAppender(String... regex) {
+               this(new LinkedList<>(), regex);
+       }
+
+       /**
+        * Rather than allocating an internal queue to store matched messages,
+        * messages are recorded in the specified target queue using the
+        * {@link Queue#offer(Object)} method. Note: whenever the queue is used, it
+        * will be synchronized to prevent simultaneous accesses.
+        * 
+        * @param target
+        * @param regex
+        *            regular expression (i.e., {@link Pattern}) to match
+        */
+       public ExtractAppender(Queue<String> target, String... regex) {
+               extracted = target;
+               patterns = new LinkedHashMap<>(regex.length);
+
+               for (String re : regex) {
+                       patterns.put(re, Pattern.compile(re));
+               }
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see ch.qos.logback.core.AppenderBase#append(Object)
+        */
+       @Override
+       protected void append(ILoggingEvent event) {
+
+               String msg = event.getMessage();
+
+               synchronized (patterns) {
+                       if (patterns.isEmpty()) {
+                               addExtraction(msg);
+                               return;
+                       }
+
+                       for (Pattern p : patterns.values()) {
+                               Matcher m = p.matcher(msg);
+
+                               if (m.find()) {
+                                       addGroupMatch(m);
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Adds the first match group to {@link #extracted}.
+        * 
+        * @param mat
+        *            the matcher containing the groups
+        * @return {@code true} if a group was found, {@code false} otherwise
+        */
+       private void addGroupMatch(Matcher mat) {
+               int ngroups = mat.groupCount();
+
+               for (int x = 1; x <= ngroups; ++x) {
+                       String txt = mat.group(x);
+
+                       if (txt != null) {
+                               addExtraction(txt);
+                               return;
+                       }
+               }
+
+               addExtraction(mat.group());
+       }
+
+       /**
+        * Adds an item to {@link #extracted}, in a thread-safe manner. It uses the
+        * queue's <i>offer()</i> method so that the queue can discard the item if
+        * it so chooses, without generating an exception.
+        * 
+        * @param txt
+        *            text to be added
+        */
+       private void addExtraction(String txt) {
+               synchronized (extracted) {
+                       extracted.offer(txt);
+               }
+       }
+
+       /**
+        * Gets the text that has been extracted.
+        * 
+        * @return a copy of the text that has been extracted
+        */
+       public List<String> getExtracted() {
+               synchronized (extracted) {
+                       return new ArrayList<>(extracted);
+               }
+       }
+
+       /**
+        * Clears the list of extracted text.
+        */
+       public void clearExtractions() {
+               synchronized (extracted) {
+                       extracted.clear();
+               }
+       }
+
+       /**
+        * Adds a pattern to be matched by this appender.
+        * 
+        * @param regex
+        *            regular expression (i.e., {@link Pattern}) to match
+        */
+       public void setPattern(String regex) {
+               synchronized (patterns) {
+                       patterns.put(regex, Pattern.compile(regex));
+               }
+       }
+
+}
diff --git a/utils-test/src/test/java/org/onap/policy/common/utils/test/ConstructionErrorTest.java b/utils-test/src/test/java/org/onap/policy/common/utils/test/ConstructionErrorTest.java
new file mode 100644 (file)
index 0000000..4a6cd90
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Integrity Audit
+ * ================================================================================
+ * Copyright (C) 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.common.utils.test;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * 
+ */
+public class ConstructionErrorTest extends ErrorsTester {
+
+       @Test
+       public void test() throws Exception {
+               assertEquals(4, testError(ConstructionError.class));
+       }
+
+}
diff --git a/utils-test/src/test/java/org/onap/policy/common/utils/test/ErrorsTesterTest.java b/utils-test/src/test/java/org/onap/policy/common/utils/test/ErrorsTesterTest.java
new file mode 100644 (file)
index 0000000..ae684d5
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Integrity Monitor
+ * ================================================================================
+ * Copyright (C) 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.common.utils.test;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class ErrorsTesterTest {
+
+       @Test
+       public void test() {
+               assertEquals(2, new ErrorsTester().testError(SimpleError.class));
+               assertEquals(5, new ErrorsTester().testError(StaticError.class));
+       }
+
+       /**
+        * Used to test a simple success case.
+        */
+       public static class SimpleError extends Error {
+               private static final long serialVersionUID = 1L;
+
+               public SimpleError() {
+                       super();
+               }
+
+               public SimpleError(String message) {
+                       super(message);
+               }
+       }
+
+       /**
+        * Used to test the exhaustive success case.
+        */
+       public static class StaticError extends Error {
+               private static final long serialVersionUID = 1L;
+
+               public StaticError() {
+                       super();
+               }
+
+               public StaticError(String message) {
+                       super(message);
+               }
+
+               public StaticError(Throwable cause) {
+                       super(cause);
+               }
+
+               public StaticError(String message, Throwable cause) {
+                       super(message, cause);
+               }
+
+               public StaticError(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+                       super(message, cause, enableSuppression, writableStackTrace);
+               }
+       }
+
+}
index c6af77a..3848acd 100644 (file)
@@ -27,124 +27,26 @@ import org.junit.Test;
 public class ExceptionsTesterTest {
 
        @Test
-       public void test() throws Exception {
+       public void test() {
                assertEquals(2, new ExceptionsTester().test(SimpleException.class));
                assertEquals(8, new ExceptionsTester().test(StaticException.class));
        }
 
        @Test(expected = AssertionError.class)
-       public void testIgnoreMessageException() throws Exception {
-               new ExceptionsTester().test(IgnoreMessageException.class);
-       }
-
-       @Test(expected = AssertionError.class)
-       public void testIgnoreCauseException() throws Exception {
-               new ExceptionsTester().test(IgnoreCauseException.class);
-       }
-
-       @Test(expected = AssertionError.class)
-       public void testNonStaticException() throws Exception {
+       public void testNoConstructorsException() {
                new ExceptionsTester().test(NoConstructorsException.class);
        }
 
-       @Test(expected = AssertionError.class)
-       public void testAlwaysSuppressException() throws Exception {
-               new ExceptionsTester().test(AlwaysSuppressException.class);
-       }
-
-       @Test(expected = AssertionError.class)
-       public void testNeverSuppressException() throws Exception {
-               new ExceptionsTester().test(NeverSuppressException.class);
-       }
-
-       @Test(expected = AssertionError.class)
-       public void testAlwaysWritableException() throws Exception {
-               new ExceptionsTester().test(AlwaysWritableException.class);
-       }
-
-       @Test(expected = AssertionError.class)
-       public void testNeverWritableException() throws Exception {
-               new ExceptionsTester().test(NeverWritableException.class);
-       }
-
        /**
-        * Used to test a failure case - message text is ignored.
-        */
-       public static class IgnoreMessageException extends Exception {
-               private static final long serialVersionUID = 1L;
-
-               public IgnoreMessageException(String message) {
-                       super("bogus");
-               }
-       }
-
-       /**
-        * Used to test a failure case - cause is ignored.
-        */
-       public static class IgnoreCauseException extends Exception {
-               private static final long serialVersionUID = 1L;
-
-               public IgnoreCauseException(Throwable cause) {
-                       super(new Exception("another cause"));
-               }
-       }
-
-       /**
-        * Used to test a failure case - this has no standard constructions.
+        * Used to test a failure case - this has no standard constructors. The only
+        * constructor it has takes an "int", thus it is not one of the standard
+        * constructors.
         */
        public static class NoConstructorsException extends Exception {
                private static final long serialVersionUID = 1L;
 
                public NoConstructorsException(int value) {
-                       super(String.valueOf(value));
-               }
-       }
-
-       /**
-        * Used to test a failure case - always suppresses.
-        */
-       public static class AlwaysSuppressException extends Exception {
-               private static final long serialVersionUID = 1L;
-
-               public AlwaysSuppressException(String message, Throwable cause, boolean enableSuppression,
-                               boolean writableStackTrace) {
-                       super(message, cause, true, writableStackTrace);
-               }
-       }
-
-       /**
-        * Used to test a failure case - never suppresses.
-        */
-       public static class NeverSuppressException extends Exception {
-               private static final long serialVersionUID = 1L;
-
-               public NeverSuppressException(String message, Throwable cause, boolean enableSuppression,
-                               boolean writableStackTrace) {
-                       super(message, cause, false, writableStackTrace);
-               }
-       }
-
-       /**
-        * Used to test a failure case - always allows stack writes.
-        */
-       public static class AlwaysWritableException extends Exception {
-               private static final long serialVersionUID = 1L;
-
-               public AlwaysWritableException(String message, Throwable cause, boolean enableSuppression,
-                               boolean writableStackTrace) {
-                       super(message, cause, enableSuppression, true);
-               }
-       }
-
-       /**
-        * Used to test a failure case - never allows stack writes.
-        */
-       public static class NeverWritableException extends Exception {
-               private static final long serialVersionUID = 1L;
-
-               public NeverWritableException(String message, Throwable cause, boolean enableSuppression,
-                               boolean writableStackTrace) {
-                       super(message, cause, enableSuppression, false);
+                       super();
                }
        }
 
diff --git a/utils-test/src/test/java/org/onap/policy/common/utils/test/ThrowablesTesterTest.java b/utils-test/src/test/java/org/onap/policy/common/utils/test/ThrowablesTesterTest.java
new file mode 100644 (file)
index 0000000..cb96e98
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Integrity Monitor
+ * ================================================================================
+ * Copyright (C) 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.common.utils.test;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class ThrowablesTesterTest {
+
+       @Test
+       public void test() {
+               assertEquals(2, new ThrowablesTester().testThrowable(SimpleThrowable.class));
+               assertEquals(5, new ThrowablesTester().testThrowable(StaticThrowable.class));
+       }
+
+       @Test
+       public void testNoConstructorsThrowable() {
+               // this will not throw an error, but it should return 0, as there are
+               // no matching constructors
+               assertEquals(0, new ThrowablesTester().testThrowable(NoConstructorsThrowable.class));
+       }
+
+       @Test(expected = AssertionError.class)
+       public void testIgnoreMessageThrowable() {
+               new ThrowablesTester().testThrowable(IgnoreMessageThrowable.class);
+       }
+
+       @Test(expected = AssertionError.class)
+       public void testIgnoreCauseThrowable() {
+               new ThrowablesTester().testThrowable(IgnoreCauseThrowable.class);
+       }
+
+       @Test(expected = AssertionError.class)
+       public void testAlwaysSuppressThrowable() {
+               new ThrowablesTester().testThrowable(AlwaysSuppressThrowable.class);
+       }
+
+       @Test(expected = AssertionError.class)
+       public void testNeverSuppressThrowable() {
+               new ThrowablesTester().testThrowable(NeverSuppressThrowable.class);
+       }
+
+       @Test(expected = AssertionError.class)
+       public void testAlwaysWritableThrowable() {
+               new ThrowablesTester().testThrowable(AlwaysWritableThrowable.class);
+       }
+
+       @Test(expected = AssertionError.class)
+       public void testNeverWritableThrowable() {
+               new ThrowablesTester().testThrowable(NeverWritableThrowable.class);
+       }
+
+       @Test(expected = ConstructionError.class)
+       public void testThrowInstantiationException() {
+               new ThrowablesTester().testThrowable(ThrowInstantiationException.class);
+       }
+
+       /**
+        * Used to test a failure case - message text is ignored.
+        */
+       public static class IgnoreMessageThrowable extends Throwable {
+               private static final long serialVersionUID = 1L;
+
+               public IgnoreMessageThrowable(String message) {
+                       super("bogus");
+               }
+       }
+
+       /**
+        * Used to test a failure case - cause is ignored.
+        */
+       public static class IgnoreCauseThrowable extends Throwable {
+               private static final long serialVersionUID = 1L;
+
+               public IgnoreCauseThrowable(Throwable cause) {
+                       super(new Throwable("another cause"));
+               }
+       }
+
+       /**
+        * Used to test a failure case - this has no standard constructors. The only
+        * constructor it has takes an "int", thus it is not one of the standard
+        * constructors.
+        */
+       public static class NoConstructorsThrowable extends Throwable {
+               private static final long serialVersionUID = 1L;
+
+               public NoConstructorsThrowable(int value) {
+                       super();
+               }
+       }
+
+       /**
+        * Used to test a failure case - always suppresses.
+        */
+       public static class AlwaysSuppressThrowable extends Throwable {
+               private static final long serialVersionUID = 1L;
+
+               public AlwaysSuppressThrowable(String message, Throwable cause, boolean enableSuppression,
+                               boolean writableStackTrace) {
+                       super(message, cause, true, writableStackTrace);
+               }
+       }
+
+       /**
+        * Used to test a failure case - never suppresses.
+        */
+       public static class NeverSuppressThrowable extends Throwable {
+               private static final long serialVersionUID = 1L;
+
+               public NeverSuppressThrowable(String message, Throwable cause, boolean enableSuppression,
+                               boolean writableStackTrace) {
+                       super(message, cause, false, writableStackTrace);
+               }
+       }
+
+       /**
+        * Used to test a failure case - always allows stack writes.
+        */
+       public static class AlwaysWritableThrowable extends Throwable {
+               private static final long serialVersionUID = 1L;
+
+               public AlwaysWritableThrowable(String message, Throwable cause, boolean enableSuppression,
+                               boolean writableStackTrace) {
+                       super(message, cause, enableSuppression, true);
+               }
+       }
+
+       /**
+        * Used to test a failure case - never allows stack writes.
+        */
+       public static class NeverWritableThrowable extends Throwable {
+               private static final long serialVersionUID = 1L;
+
+               public NeverWritableThrowable(String message, Throwable cause, boolean enableSuppression,
+                               boolean writableStackTrace) {
+                       super(message, cause, enableSuppression, false);
+               }
+       }
+
+       /**
+        * Used to test a failure case - throws InstantiationException when
+        * constructed.
+        */
+       public static class ThrowInstantiationException extends Throwable {
+               private static final long serialVersionUID = 1L;
+
+               public ThrowInstantiationException(String message, Throwable cause, boolean enableSuppression,
+                               boolean writableStackTrace) throws InstantiationException {
+
+                       throw new InstantiationException(ThrowablesTester.EXPECTED_EXCEPTION_MSG);
+               }
+       }
+
+       /**
+        * Used to test a simple success case.
+        */
+       public static class SimpleThrowable extends Throwable {
+               private static final long serialVersionUID = 1L;
+
+               public SimpleThrowable() {
+                       super();
+               }
+
+               public SimpleThrowable(String message) {
+                       super(message);
+               }
+       }
+
+       /**
+        * Used to test the exhaustive success case.
+        */
+       public static class StaticThrowable extends Throwable {
+               private static final long serialVersionUID = 1L;
+
+               public StaticThrowable() {
+                       super();
+               }
+
+               public StaticThrowable(String message) {
+                       super(message);
+               }
+
+               public StaticThrowable(Throwable cause) {
+                       super(cause);
+               }
+
+               public StaticThrowable(String message, Throwable cause) {
+                       super(message, cause);
+               }
+
+               public StaticThrowable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+                       super(message, cause, enableSuppression, writableStackTrace);
+               }
+       }
+
+}
diff --git a/utils-test/src/test/java/org/onap/policy/common/utils/test/log/logback/ExtractAppenderTest.java b/utils-test/src/test/java/org/onap/policy/common/utils/test/log/logback/ExtractAppenderTest.java
new file mode 100644 (file)
index 0000000..a9a6925
--- /dev/null
@@ -0,0 +1,484 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Integrity Audit
+ * ================================================================================
+ * Copyright (C) 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.common.utils.test.log.logback;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+
+public class ExtractAppenderTest {
+
+       /**
+        * Milliseconds to wait for a thread to terminate.
+        */
+       private static final long THREAD_WAIT_MS = 5000l;
+
+       private static Logger logger;
+
+       private List<Thread> threads;
+
+       @BeforeClass
+       public static void setUpBeforeClass() throws Exception {
+               logger = (Logger) LoggerFactory.getLogger(ExtractAppenderTest.class);
+               logger.setLevel(Level.INFO);
+       }
+
+       @Before
+       public void setUp() throws Exception {
+               threads = new LinkedList<>();
+       }
+
+       @After
+       public void tearDown() throws Exception {
+               logger.detachAndStopAllAppenders();
+
+               for (Thread p : threads) {
+                       p.interrupt();
+                       p.join(THREAD_WAIT_MS);
+               }
+       }
+
+       @Test
+       public void testExtractAppender() {
+               AtomicInteger count = new AtomicInteger(0);
+
+               ExtractAppender p = new ExtractAppender() {
+                       @Override
+                       protected void append(ILoggingEvent event) {
+                               count.incrementAndGet();
+                               super.append(event);
+                       }
+               };
+
+               addAppender(p);
+
+               logger.info("hello");
+               logger.info("world");
+
+               // "append" should always be called
+               assertEquals(2, count.get());
+
+               // appender with no patterns - everything should match
+               assertEquals(strList("hello", "world"), p.getExtracted());
+
+               // add a pattern and verify match
+               p.setPattern("abc[0-9]");
+               logger.info("hello abc1");
+
+               // this should not match
+               logger.info("hello def2");
+
+               assertEquals(4, count.get());
+               assertEquals(strList("hello", "world", "abc1"), p.getExtracted());
+       }
+
+       @Test
+       public void testExtractAppenderStringArray() {
+               AtomicInteger count = new AtomicInteger(0);
+
+               ExtractAppender p = new ExtractAppender("abc[0-9]", "def[0-9]") {
+                       @Override
+                       protected void append(ILoggingEvent event) {
+                               count.incrementAndGet();
+                               super.append(event);
+                       }
+               };
+
+               addAppender(p);
+
+               logger.info("hello abc1 world");
+               logger.info("world ghi2 world"); // no match
+               logger.info("world def3 world");
+               logger.info("hello abc4");
+               logger.info("abc5 world");
+               logger.info("hello def6");
+               logger.info("ghi7 world"); // no match
+               logger.info("def8 world");
+
+               // "append" should always be called
+               assertEquals(8, count.get());
+
+               assertEquals(strList("abc1", "def3", "abc4", "abc5", "def6", "def8"), p.getExtracted());
+
+               p.setPattern("ghi[0-9]");
+               logger.info("hello abc9");
+               logger.info("hello ghi9");
+
+               // this should not match
+               logger.info("hello xyz");
+
+               assertEquals(11, count.get());
+               assertEquals(strList("abc1", "def3", "abc4", "abc5", "def6", "def8", "abc9", "ghi9"), p.getExtracted());
+       }
+
+       @Test
+       public void testExtractAppenderQueueStringArray() {
+               // no. of matches allowed in the list
+               int nallowed = 3;
+               
+               AtomicInteger count = new AtomicInteger(0);
+               
+               LinkedList<String> queue = new LinkedList<String>() {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       public boolean offer(String e) {
+                               if(count.incrementAndGet() <= nallowed) {
+                                       return super.offer(e);
+                                       
+                               } else {
+                                       return false;
+                               }
+                       }
+               };
+
+               ExtractAppender p = new ExtractAppender(queue, "abc[0-9]");
+               addAppender(p);
+
+               // these shouldn't match
+               for(int x = 0;  x < 10;  ++x) {
+                       logger.info("xyz");
+               }
+               
+               int nmatches = 10;
+
+               LinkedList<String> expected = new LinkedList<>();
+               
+               for(int x = 0;  x < nmatches;  ++x) {
+                       String msg = "abc" + x;
+                       logger.info(msg + " world");
+                       
+                       if(x < nallowed) {
+                               expected.add(msg);
+                       }
+               }
+
+               // "offer" should always be called for a match
+               assertEquals(nmatches, count.get());
+               
+               assertEquals(expected, p.getExtracted());
+       }
+
+       @Test
+       public void testAppendILoggingEvent_NoPatterns() {
+               ExtractAppender p = makeAppender();
+               
+               logger.info("hello");
+               logger.info("world");
+               
+               assertEquals(strList("hello", "world"), p.getExtracted());
+       }
+
+       @Test
+       public void testAppendILoggingEvent_MatchFirstPattern() {
+               ExtractAppender p = makeAppender("abc[0-9]", "def[0-9]");
+               
+               logger.info("hello abc1");
+               logger.info("world xyz2");
+               
+               assertEquals(strList("abc1"), p.getExtracted());
+       }
+
+       @Test
+       public void testAppendILoggingEvent_MatchLastPattern() {
+               ExtractAppender p = makeAppender("abc[0-9]", "def[0-9]");
+               
+               logger.info("hello def1");
+               logger.info("world xyz2");
+               
+               assertEquals(strList("def1"), p.getExtracted());
+       }
+
+       @Test
+       public void testAppendILoggingEvent_Group1() {
+               ExtractAppender p = makeAppender("hello (abc)|(xyz)", "def[0-9]");
+               
+               logger.info("hello abc, world!");
+               logger.info("world abc");
+               
+               assertEquals(strList("abc"), p.getExtracted());
+       }
+
+       @Test
+       public void testAppendILoggingEvent_Group3() {
+               ExtractAppender p = makeAppender("hello (abc)|(pdq)|(xyz)", "def[0-9]");
+               
+               logger.info("say hello xyz, world!");
+               logger.info("world abc");
+               
+               assertEquals(strList("xyz"), p.getExtracted());
+       }
+
+       @Test
+       public void testAppendILoggingEvent_NoGroup() {
+               ExtractAppender p = makeAppender("hello abc");
+               
+               logger.info("say hello abc, world!");
+               logger.info("world abc");
+               
+               assertEquals(strList("hello abc"), p.getExtracted());
+       }
+
+       @Test
+       public void testGetExtracted() {
+               ExtractAppender p = makeAppender("abc[1-9]");
+
+               logger.info("hello abc1 world");
+               logger.info("world ghi2 world"); // no match
+               logger.info("hello abc3");
+
+               List<String> oldlst = p.getExtracted();
+               assertEquals(strList("abc1", "abc3"), oldlst);
+               assertEquals(oldlst, p.getExtracted());
+
+               logger.info("abc9");
+               assertEquals(strList("abc1", "abc3", "abc9"), p.getExtracted());
+       }
+
+       @Test
+       public void testClearExtractions() {
+               ExtractAppender p = makeAppender("abc[1-9]");
+
+               logger.info("hello abc1 world");
+               logger.info("world ghi2 world");
+               logger.info("hello abc3");
+
+               assertEquals(strList("abc1", "abc3"), p.getExtracted());
+
+               p.clearExtractions();
+
+               // list should be empty now
+               assertEquals(strList(), p.getExtracted());
+
+               logger.info("hello abc4 world");
+               logger.info("world ghi5 world");
+               logger.info("hello abc6");
+
+               // list should only contain the new items
+               assertEquals(strList("abc4", "abc6"), p.getExtracted());
+       }
+
+       @Test
+       public void testSetPattern() {
+               ExtractAppender p = makeAppender("abc[1-9]");
+
+               logger.info("hello abc1 world");
+               logger.info("world ghi2 world"); // no match
+               logger.info("hello abc3");
+
+               assertEquals(strList("abc1", "abc3"), p.getExtracted());
+
+               p.setPattern("ghi[0-9]");
+
+               logger.info("world ghi4 world"); // this should match now
+               logger.info("hello abc5"); // this should still match
+               logger.info("hello xyz5"); // no match
+
+               assertEquals(strList("abc1", "abc3", "ghi4", "abc5"), p.getExtracted());
+       }
+
+       /**
+        * Launches threads doing everything in parallel to ensure nothing crashes.
+        * 
+        * @throws Exception
+        */
+       @Test
+       public void test_MultiThreaded() throws Exception {
+               // when to stop
+               long tend = System.currentTimeMillis() + 250;
+
+               // maximum number of items allowed in the extraction list
+               int maxItems = 10;
+
+               // this will be set if one of the threads generates an error
+               AtomicBoolean err = new AtomicBoolean(false);
+
+               // extracted messages go here - this is a finite-length queue since
+               // we don't know how many messages may actually be logged
+               LinkedList<String> queue = new LinkedList<String>() {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       public boolean offer(String e) {
+                               if (size() < maxItems) {
+                                       return super.offer(e);
+                               } else {
+                                       return false;
+                               }
+                       }
+               };
+
+               ExtractAppender app = new ExtractAppender(queue, "abc[1-9]");
+               addAppender(app);
+
+               // create some threads to add another pattern
+               addThread(tend, err, xtxt -> {
+                       app.setPattern("def[0-9]");
+               });
+
+               // create some threads to log "abc" messages
+               addThread(tend, err, xtxt -> {
+                       logger.info("hello abc" + xtxt + "world!");
+               });
+
+               // create some threads to log "def" messages
+               addThread(tend, err, xtxt -> {
+                       logger.info("hello def" + xtxt + "world!");
+               });
+
+               // create some threads to get extractions
+               addThread(tend, err, xtxt -> {
+                       app.getExtracted();
+               });
+
+               // create some threads to clear extractions
+               addThread(tend, err, xtxt -> {
+                       app.clearExtractions();
+
+                       // don't want to clear the list too frequently
+                       // so sleep a bit in between
+                       try {
+                               Thread.sleep(10 + Integer.valueOf(xtxt));
+
+                       } catch (InterruptedException e) {
+                               Thread.currentThread().interrupt();
+                               throw e;
+                       }
+               });
+
+               /*
+                * Finally ready to start.
+                */
+
+               // start all of the threads
+               for (Thread t : threads) {
+                       t.setDaemon(true);
+                       t.start();
+               }
+
+               // wait for each thread to stop
+               for (Thread t : threads) {
+                       t.join(THREAD_WAIT_MS);
+                       assertFalse(t.isAlive());
+               }
+               
+               // ensure none of the threads threw an exception
+               assertFalse(err.get());
+       }
+
+       /**
+        * Adds multiple threads to perform some function repeatedly until the given
+        * time is reached.
+        * 
+        * @param tend
+        *            time, in milliseconds, when the test should terminate
+        * @param haderr
+        *            this will be set to {@code true} if the function throws an
+        *            exception other than an InterruptedException
+        * @param func
+        *            function to be repeatedly invoked
+        */
+       private void addThread(long tend, AtomicBoolean haderr, VoidFunction func) {
+               // number of threads of each type to create
+               int neach = 3;
+
+               for (int x = 0; x < neach; ++x) {
+                       String xtxt = String.valueOf(x);
+
+                       threads.add(new Thread() {
+                               @Override
+                               public void run() {
+                                       try {
+                                               while (System.currentTimeMillis() < tend) {
+                                                       func.apply(xtxt);
+                                               }
+
+                                       } catch (InterruptedException ex) {
+                                               Thread.currentThread().interrupt();
+
+                                       } catch (Exception ex) {
+                                               haderr.set(true);
+                                       }
+                               }
+                       });
+
+               }
+       }
+
+       /**
+        * Makes an appender that recognizes the given set of strings.
+        * 
+        * @param strings
+        *            regular expressions to be matched
+        * @return a new appender
+        */
+       private ExtractAppender makeAppender(String... strings) {
+               ExtractAppender p = new ExtractAppender(strings);
+
+               addAppender(p);
+
+               return p;
+       }
+
+       /**
+        * Adds an appender to the logger.
+        * 
+        * @param app
+        *            appender to be added
+        */
+       private void addAppender(ExtractAppender app) {
+               app.setContext(logger.getLoggerContext());
+               app.start();
+
+               logger.addAppender(app);
+       }
+
+       /**
+        * Converts an array of strings into a list of strings.
+        * 
+        * @param strings
+        *            array of strings
+        * @return a list of the strings
+        */
+       private List<String> strList(String... strings) {
+               return Arrays.asList(strings);
+       }
+
+       @FunctionalInterface
+       public interface VoidFunction {
+               public void apply(String text) throws InterruptedException;
+       }
+}
diff --git a/utils-test/src/test/resources/logback-test.xml b/utils-test/src/test/resources/logback-test.xml
new file mode 100644 (file)
index 0000000..20869a4
--- /dev/null
@@ -0,0 +1,37 @@
+<!--
+  ============LICENSE_START=======================================================
+  integrity-monitor
+  ================================================================================
+  Copyright (C) 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=========================================================
+  -->
+
+<!-- Controls the output of logs for JUnit tests -->
+
+<configuration>
+
+       <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+               <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+                       <Pattern>
+                               %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M\(%line\) - %msg%n
+                       </Pattern>
+               </encoder>
+       </appender>
+       
+       <root level="debug">
+               <appender-ref ref="STDOUT" />
+       </root>
+
+</configuration>