From e9fd35a303a5f5f41726c3216c8acd7598a3a494 Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Wed, 7 Feb 2018 17:13:32 -0500 Subject: Add ExtractAppender to facilite junit tests 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 --- .../test/log/logback/ExtractAppenderTest.java | 484 +++++++++++++++++++++ 1 file changed, 484 insertions(+) create mode 100644 utils-test/src/test/java/org/onap/policy/common/utils/test/log/logback/ExtractAppenderTest.java (limited to 'utils-test/src/test/java/org/onap/policy/common/utils/test/log/logback/ExtractAppenderTest.java') 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 index 00000000..a9a69252 --- /dev/null +++ b/utils-test/src/test/java/org/onap/policy/common/utils/test/log/logback/ExtractAppenderTest.java @@ -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 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 queue = new LinkedList() { + 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 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 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 queue = new LinkedList() { + 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 strList(String... strings) { + return Arrays.asList(strings); + } + + @FunctionalInterface + public interface VoidFunction { + public void apply(String text) throws InterruptedException; + } +} -- cgit 1.2.3-korg