Repository: ignite Updated Branches: refs/heads/master 5eb871e19 -> a3b624d9f
IGNITE-8570 Improved GridToStringLogger - Fixes #4786. Signed-off-by: Alexey Goncharuk <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/a3b624d9 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/a3b624d9 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/a3b624d9 Branch: refs/heads/master Commit: a3b624d9f34012eab1711dac14dc71af3c5bd9c4 Parents: 5eb871e Author: pereslegin-pa <[email protected]> Authored: Thu Oct 18 19:08:44 2018 +0300 Committer: Alexey Goncharuk <[email protected]> Committed: Thu Oct 18 19:08:44 2018 +0300 ---------------------------------------------------------------------- .../ignite/testframework/GridStringLogger.java | 3 + .../testframework/ListeningTestLogger.java | 205 +++++++++ .../ignite/testframework/LogListener.java | 427 ++++++++++++++++++ .../test/ListeningTestLoggerTest.java | 428 +++++++++++++++++++ .../ignite/testsuites/IgniteBasicTestSuite.java | 3 + 5 files changed, 1066 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/a3b624d9/modules/core/src/test/java/org/apache/ignite/testframework/GridStringLogger.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/GridStringLogger.java b/modules/core/src/test/java/org/apache/ignite/testframework/GridStringLogger.java index 9056dd6..0b09d3e 100644 --- a/modules/core/src/test/java/org/apache/ignite/testframework/GridStringLogger.java +++ b/modules/core/src/test/java/org/apache/ignite/testframework/GridStringLogger.java @@ -26,7 +26,10 @@ import org.jetbrains.annotations.Nullable; /** * Logger which logs to string buffer. + * + * @deprecated Use {@link ListeningTestLogger} instead. */ +@Deprecated public class GridStringLogger implements IgniteLogger { /** Initial string builder capacity in bytes */ private static final int INITIAL = 1024 * 33; http://git-wip-us.apache.org/repos/asf/ignite/blob/a3b624d9/modules/core/src/test/java/org/apache/ignite/testframework/ListeningTestLogger.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/ListeningTestLogger.java b/modules/core/src/test/java/org/apache/ignite/testframework/ListeningTestLogger.java new file mode 100644 index 0000000..1b05f4c --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testframework/ListeningTestLogger.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.ignite.testframework; + +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Consumer; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.internal.util.typedef.X; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Implementation of {@link org.apache.ignite.IgniteLogger} that performs any actions when certain message is logged. + * It can be useful in tests to ensure that a specific message was (or was not) printed to the log. + */ +public class ListeningTestLogger implements IgniteLogger { + /** + * If set to {@code true}, enables debug and trace log messages processing. + */ + private final boolean dbg; + + /** + * Logger to echo all messages, limited by {@code dbg} flag. + */ + private final IgniteLogger echo; + + /** + * Registered log messages listeners. + */ + private final Collection<Consumer<String>> lsnrs = new CopyOnWriteArraySet<>(); + + /** + * Default constructor. + */ + public ListeningTestLogger() { + this(false); + } + + /** + * @param dbg If set to {@code true}, enables debug and trace log messages processing. + */ + public ListeningTestLogger(boolean dbg) { + this(dbg, null); + } + + /** + * @param dbg If set to {@code true}, enables debug and trace log messages processing. + * @param echo Logger to echo all messages, limited by {@code dbg} flag. + */ + public ListeningTestLogger(boolean dbg, @Nullable IgniteLogger echo) { + this.dbg = dbg; + this.echo = echo; + } + + /** + * Registers message listener. + * + * @param lsnr Message listener. + */ + public void registerListener(@NotNull LogListener lsnr) { + lsnr.reset(); + + lsnrs.add(lsnr); + } + + /** + * Registers message listener. + * <p> + * NOTE listener is executed in the thread causing the logging, so it is not recommended to throw any exceptions + * from it. Use {@link LogListener} to create message predicates with assertions. + * + * @param lsnr Message listener. + * @see LogListener + */ + public void registerListener(@NotNull Consumer<String> lsnr) { + lsnrs.add(lsnr); + } + + /** + * Unregisters message listener. + * + * @param lsnr Message listener. + */ + public void unregisterListener(@NotNull Consumer<String> lsnr) { + lsnrs.remove(lsnr); + } + + /** + * Clears all listeners. + */ + public void clearListeners() { + lsnrs.clear(); + } + + /** {@inheritDoc} */ + @Override public ListeningTestLogger getLogger(Object ctgr) { + return this; + } + + /** {@inheritDoc} */ + @Override public void trace(String msg) { + if (!dbg) + return; + + if (echo != null) + echo.trace(msg); + + applyListeners(msg); + } + + /** {@inheritDoc} */ + @Override public void debug(String msg) { + if (!dbg) + return; + + if (echo != null) + echo.debug(msg); + + applyListeners(msg); + } + + /** {@inheritDoc} */ + @Override public void info(String msg) { + if (echo != null) + echo.info(msg); + + applyListeners(msg); + } + + /** {@inheritDoc} */ + @Override public void warning(String msg, @Nullable Throwable t) { + if (echo != null) + echo.warning(msg, t); + + applyListeners(msg); + + if (t != null) + applyListeners(X.getFullStackTrace(t)); + } + + /** {@inheritDoc} */ + @Override public void error(String msg, @Nullable Throwable t) { + if (echo != null) + echo.error(msg, t); + + applyListeners(msg); + + if (t != null) + applyListeners(X.getFullStackTrace(t)); + } + + /** {@inheritDoc} */ + @Override public boolean isTraceEnabled() { + return dbg; + } + + /** {@inheritDoc} */ + @Override public boolean isDebugEnabled() { + return dbg; + } + + /** {@inheritDoc} */ + @Override public boolean isInfoEnabled() { + return true; + } + + /** {@inheritDoc} */ + @Override public boolean isQuiet() { + return false; + } + + /** {@inheritDoc} */ + @Override public String fileName() { + return null; + } + + /** + * Applies listeners whose pattern is found in the message. + * + * @param msg Message to check. + */ + private void applyListeners(String msg) { + if (msg == null) + return; + + for (Consumer<String> lsnr : lsnrs) + lsnr.accept(msg); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/a3b624d9/modules/core/src/test/java/org/apache/ignite/testframework/LogListener.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/LogListener.java b/modules/core/src/test/java/org/apache/ignite/testframework/LogListener.java new file mode 100644 index 0000000..d349f06 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testframework/LogListener.java @@ -0,0 +1,427 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.ignite.testframework; + +import java.time.temporal.ValueRange; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * The basic listener for custom log contents checking in {@link ListeningTestLogger}.<br><br> + * + * Supports {@link #matches(String) substring}, {@link #matches(Pattern) regular expression} or + * {@link #matches(Predicate) predicate} listeners and the following optional modifiers: + * <ul> + * <li>{@link Builder#times times()} sets the exact number of occurrences</li> + * <li>{@link Builder#atLeast atLeast()} sets the minimum number of occurrences</li> + * <li>{@link Builder#atMost atMost()} sets the maximum number of occurrences</li> + * </ul> + * {@link Builder#atLeast atLeast()} and {@link Builder#atMost atMost()} can be used together.<br><br> + * + * If the expected number of occurrences is not specified for the listener, + * then at least one occurence is expected by default. In other words:<pre> + * + * {@code LogListener.matches(msg).build();} + * + * is equivalent to + * + * {@code LogListener.matches(msg).atLeast(1).build();} + * </pre> + * + * If only the expected maximum number of occurrences is specified, then + * the minimum number of entries for successful validation is zero. In other words:<pre> + * + * {@code LogListener.matches(msg).atMost(10).build();} + * + * is equivalent to + * + * {@code LogListener.matches(msg).atLeast(0).atMost(10).build();} + * </pre> + */ +public abstract class LogListener implements Consumer<String> { + /** + * Checks that all conditions are met. + * + * @throws AssertionError If some condition failed. + */ + public abstract void check() throws AssertionError; + + /** + * Reset listener state. + */ + abstract void reset(); + + /** + * Creates new listener builder. + * + * @param substr Substring to search for in a log message. + * @return Log message listener builder. + */ + public static Builder matches(String substr) { + return new Builder().andMatches(substr); + } + + /** + * Creates new listener builder. + * + * @param regexp Regular expression to search for in a log message. + * @return Log message listener builder. + */ + public static Builder matches(Pattern regexp) { + return new Builder().andMatches(regexp); + } + + /** + * Creates new listener builder. + * + * @param pred Log message predicate. + * @return Log message listener builder. + */ + public static Builder matches(Predicate<String> pred) { + return new Builder().andMatches(pred); + } + + /** + * Log listener builder. + */ + public static class Builder { + /** */ + private final CompositeMessageListener lsnr = new CompositeMessageListener(); + + /** */ + private Node prev; + + /** + * Add new substring predicate. + * + * @param substr Substring. + * @return current builder instance. + */ + public Builder andMatches(String substr) { + addLast(new Node(substr, msg -> { + if (substr.isEmpty()) + return msg.isEmpty() ? 1 : 0; + + int cnt = 0; + + for (int idx = 0; (idx = msg.indexOf(substr, idx)) != -1; idx++) + ++cnt; + + return cnt; + })); + + return this; + } + + /** + * Add new regular expression predicate. + * + * @param regexp Regular expression. + * @return current builder instance. + */ + public Builder andMatches(Pattern regexp) { + addLast(new Node(regexp.toString(), msg -> { + int cnt = 0; + + Matcher matcher = regexp.matcher(msg); + + while (matcher.find()) + ++cnt; + + return cnt; + })); + + return this; + } + + /** + * Add new log message predicate. + * + * @param pred Log message predicate. + * @return current builder instance. + */ + public Builder andMatches(Predicate<String> pred) { + addLast(new Node(null, msg -> pred.test(msg) ? 1 : 0)); + + return this; + } + + /** + * Set expected number of matches.<br> + * Each log message may contain several matches that will be counted, + * except {@code Predicate} which can have only one match for message. + * + * @param n Expected number of matches. + * @return current builder instance. + */ + public Builder times(int n) { + if (prev != null) + prev.cnt = n; + + return this; + } + + /** + * Set expected minimum number of matches.<br> + * Each log message may contain several matches that will be counted, + * except {@code Predicate} which can have only one match for message. + * + * @param n Expected number of matches. + * @return current builder instance. + */ + public Builder atLeast(int n) { + if (prev != null) { + prev.min = n; + + prev.cnt = null; + } + + return this; + } + + /** + * Set expected maximum number of matches.<br> + * Each log message may contain several matches that will be counted, + * except {@code Predicate} which can have only one match for message. + * + * @param n Expected number of matches. + * @return current builder instance. + */ + public Builder atMost(int n) { + if (prev != null) { + prev.max = n; + + prev.cnt = null; + } + + return this; + } + + /** + * Set custom message for assertion error. + * + * @param msg Custom message. + * @return current builder instance. + */ + public Builder orError(String msg) { + if (prev != null) + prev.msg = msg; + + return this; + } + + /** + * Constructs message listener. + * + * @return Log message listener. + */ + public LogListener build() { + addLast(null); + + return lsnr.lsnrs.size() == 1 ? lsnr.lsnrs.get(0) : lsnr; + } + + /** + * @param node Log listener attributes. + */ + private void addLast(Node node) { + if (prev != null) + lsnr.add(prev.listener()); + + prev = node; + } + + /** */ + private Builder() {} + + /** + * Mutable attributes for log listener. + */ + static final class Node { + /** */ + final String subj; + + /** */ + final Function<String, Integer> func; + + /** */ + String msg; + + /** */ + Integer min; + + /** */ + Integer max; + + /** */ + Integer cnt; + + /** */ + Node(String subj, Function<String, Integer> func) { + this.subj = subj; + this.func = func; + } + + /** */ + LogMessageListener listener() { + ValueRange range; + + if (cnt != null) + range = ValueRange.of(cnt, cnt); + else if (min == null && max == null) + range = ValueRange.of(1, Integer.MAX_VALUE); + else + range = ValueRange.of(min == null ? 0 : min, max == null ? Integer.MAX_VALUE : max); + + return new LogMessageListener(func, range, subj, msg); + } + } + } + + /** */ + private static class LogMessageListener extends LogListener { + /** */ + private final Function<String, Integer> func; + + /** */ + private final AtomicReference<Throwable> err = new AtomicReference<>(); + + /** */ + private final AtomicInteger matches = new AtomicInteger(); + + /** */ + private final ValueRange exp; + + /** */ + private final String subj; + + /** */ + private final String errMsg; + + /** + * @param subj Search subject. + * @param exp Expected occurrences. + * @param func Function of counting matches in the message. + * @param errMsg Custom error message. + */ + private LogMessageListener( + @NotNull Function<String, Integer> func, + @NotNull ValueRange exp, + @Nullable String subj, + @Nullable String errMsg + ) { + this.func = func; + this.exp = exp; + this.subj = subj == null ? func.toString() : subj; + this.errMsg = errMsg; + } + + /** {@inheritDoc} */ + @Override public void accept(String msg) { + if (err.get() != null) + return; + + try { + int cnt = func.apply(msg); + + if (cnt > 0) + matches.addAndGet(cnt); + } catch (Throwable t) { + err.compareAndSet(null, t); + + if (t instanceof VirtualMachineError) + throw t; + } + } + + /** {@inheritDoc} */ + @Override public void check() { + errCheck(); + + int matchesCnt = matches.get(); + + if (!exp.isValidIntValue(matchesCnt)) { + String err = errMsg != null ? errMsg : + "\"" + subj + "\" matches " + matchesCnt + " times, expected: " + + (exp.getMaximum() == exp.getMinimum() ? exp.getMinimum() : exp) + "."; + + throw new AssertionError(err); + } + } + + /** {@inheritDoc} */ + @Override void reset() { + matches.set(0); + } + + /** + * Check that there were no runtime errors. + */ + private void errCheck() { + Throwable t = err.get(); + + if (t instanceof Error) + throw (Error) t; + + if (t instanceof RuntimeException) + throw (RuntimeException) t; + + assert t == null : t; + } + } + + /** */ + private static class CompositeMessageListener extends LogListener { + /** */ + private final List<LogMessageListener> lsnrs = new ArrayList<>(); + + /** {@inheritDoc} */ + @Override public void check() { + for (LogMessageListener lsnr : lsnrs) + lsnr.check(); + } + + /** {@inheritDoc} */ + @Override void reset() { + for (LogMessageListener lsnr : lsnrs) + lsnr.reset(); + } + + /** {@inheritDoc} */ + @Override public void accept(String msg) { + for (LogMessageListener lsnr : lsnrs) + lsnr.accept(msg); + } + + /** + * @param lsnr Listener. + */ + private void add(LogMessageListener lsnr) { + lsnrs.add(lsnr); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/a3b624d9/modules/core/src/test/java/org/apache/ignite/testframework/test/ListeningTestLoggerTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/test/ListeningTestLoggerTest.java b/modules/core/src/test/java/org/apache/ignite/testframework/test/ListeningTestLoggerTest.java new file mode 100644 index 0000000..a888017 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testframework/test/ListeningTestLoggerTest.java @@ -0,0 +1,428 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.ignite.testframework.test; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteVersionUtils; +import org.apache.ignite.logger.NullLogger; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.ListeningTestLogger; +import org.apache.ignite.testframework.LogListener; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +import static org.apache.ignite.testframework.GridTestUtils.assertThrows; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause; + +/** + * Test. + */ +@SuppressWarnings("ThrowableNotThrown") +public class ListeningTestLoggerTest extends GridCommonAbstractTest { + /** */ + private final ListeningTestLogger log = new ListeningTestLogger(false, super.log); + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + cfg.setGridLogger(log); + + return cfg; + } + + /** + * Basic example of using listening logger - checks that all running instances of Ignite print product version. + * + * @throws Exception If failed. + */ + public void testIgniteVersionLogging() throws Exception { + int gridCnt = 4; + + LogListener lsnr = LogListener.matches(IgniteVersionUtils.VER_STR).atLeast(gridCnt).build(); + + log.registerListener(lsnr); + + try { + startGridsMultiThreaded(gridCnt); + + lsnr.check(); + } finally { + stopAllGrids(); + } + } + + /** + * Checks that re-register works fine. + */ + public void testUnregister() { + String msg = "catch me"; + + LogListener lsnr1 = LogListener.matches(msg).times(1).build(); + LogListener lsnr2 = LogListener.matches(msg).times(2).build(); + + log.registerListener(lsnr1); + log.registerListener(lsnr2); + + log.info(msg); + + log.unregisterListener(lsnr1); + + log.info(msg); + + lsnr1.check(); + lsnr2.check(); + + // Repeat these steps to ensure that the state is cleared during registration. + log.registerListener(lsnr1); + log.registerListener(lsnr2); + + log.info(msg); + + log.unregisterListener(lsnr1); + + log.info(msg); + + lsnr1.check(); + lsnr2.check(); + } + + /** + * Ensures that listener will be re-registered only once. + */ + public void testRegister() { + AtomicInteger cntr = new AtomicInteger(); + + LogListener lsnr3 = LogListener.matches(m -> cntr.incrementAndGet() > 0).build(); + + log.registerListener(lsnr3); + log.registerListener(lsnr3); + + log.info("1"); + + assertEquals(1, cntr.get()); + } + + /** + * Checks basic API. + */ + public void testBasicApi() { + String errMsg = "Word started with \"a\" not found."; + + LogListener lsnr = LogListener.matches(Pattern.compile("a[a-z]+")).orError(errMsg) + .andMatches("Exception message.").andMatches(".java:").build(); + + log.registerListener(lsnr); + + log.info("Something new."); + + assertThrows(log(), () -> { + lsnr.check(); + + return null; + }, AssertionError.class, errMsg); + + log.error("There was an error.", new RuntimeException("Exception message.")); + + lsnr.check(); + } + + /** + * Checks blank lines matching. + */ + public void testEmptyLine() { + LogListener emptyLineLsnr = LogListener.matches("").build(); + + log.registerListener(emptyLineLsnr); + + log.info(""); + + emptyLineLsnr.check(); + } + + /** */ + public void testPredicateExceptions() { + LogListener lsnr = LogListener.matches(msg -> { + assertFalse(msg.contains("Target")); + + return true; + }).build(); + + log.registerListener(lsnr); + + log.info("Ignored message."); + log.info("Target message."); + + assertThrowsWithCause(lsnr::check, AssertionError.class); + + // Check custom exception. + LogListener lsnr2 = LogListener.matches(msg -> { + throw new IllegalStateException("Illegal state"); + }).orError("ignored blah-blah").build(); + + log.registerListener(lsnr2); + + log.info("1"); + log.info("2"); + + assertThrowsWithCause(lsnr2::check, IllegalStateException.class); + } + + /** + * Validates listener range definition. + */ + public void testRange() { + String msg = "range"; + + LogListener lsnr2 = LogListener.matches(msg).times(2).build(); + LogListener lsnr2_3 = LogListener.matches(msg).atLeast(2).atMost(3).build(); + + log.registerListener(lsnr2); + log.registerListener(lsnr2_3); + + log.info(msg); + log.info(msg); + + lsnr2.check(); + lsnr2_3.check(); + + log.info(msg); + + assertThrowsWithCause(lsnr2::check, AssertionError.class); + + lsnr2_3.check(); + + log.info(msg); + + assertThrowsWithCause(lsnr2_3::check, AssertionError.class); + } + + /** + * Checks that substring was not found in the log messages. + */ + public void testNotPresent() { + String msg = "vacuum"; + + LogListener notPresent = LogListener.matches(msg).times(0).build(); + + log.registerListener(notPresent); + + log.info("1"); + + notPresent.check(); + + log.info(msg); + + assertThrowsWithCause(notPresent::check, AssertionError.class); + } + + /** + * Checks that the substring is found at least twice. + */ + public void testAtLeast() { + String msg = "at least"; + + LogListener atLeast2 = LogListener.matches(msg).atLeast(2).build(); + + log.registerListener(atLeast2); + + log.info(msg); + + assertThrowsWithCause(atLeast2::check, AssertionError.class); + + log.info(msg); + + atLeast2.check(); + } + + /** + * Checks that the substring is found no more than twice. + */ + public void testAtMost() { + String msg = "at most"; + + LogListener atMost2 = LogListener.matches(msg).atMost(2).build(); + + log.registerListener(atMost2); + + atMost2.check(); + + log.info(msg); + log.info(msg); + + atMost2.check(); + + log.info(msg); + + assertThrowsWithCause(atMost2::check, AssertionError.class); + } + + /** + * Checks that only last value is taken into account. + */ + public void testMultiRange() { + String msg = "multi range"; + + LogListener atMost3 = LogListener.matches(msg).times(1).times(2).atMost(3).build(); + + log.registerListener(atMost3); + + for (int i = 0; i < 6; i++) { + if (i < 4) + atMost3.check(); + else + assertThrowsWithCause(atMost3::check, AssertionError.class); + + log.info(msg); + } + + LogListener lsnr4 = LogListener.matches(msg).atLeast(2).atMost(3).times(4).build(); + + log.registerListener(lsnr4); + + for (int i = 1; i < 6; i++) { + log.info(msg); + + if (i == 4) + lsnr4.check(); + else + assertThrowsWithCause(lsnr4::check, AssertionError.class); + } + } + + /** + * Checks that matches are counted for each message. + */ + public void testMatchesPerMessage() { + LogListener lsnr = LogListener.matches("aa").times(4).build(); + + log.registerListener(lsnr); + + log.info("aabaab"); + log.info("abaaab"); + + lsnr.check(); + + LogListener newLineLsnr = LogListener.matches("\n").times(5).build(); + + log.registerListener(newLineLsnr); + + log.info("\n1\n2\n\n3\n"); + + newLineLsnr.check(); + + LogListener regexpLsnr = LogListener.matches(Pattern.compile("(?i)hi|hello")).times(3).build(); + + log.registerListener(regexpLsnr); + + log.info("Hi! Hello!"); + log.info("Hi folks"); + + regexpLsnr.check(); + } + + /** + * Check thread safety. + * + * @throws Exception If failed. + */ + public void testMultithreaded() throws Exception { + int iterCnt = 50_000; + int threadCnt = 6; + int total = threadCnt * iterCnt; + int rndNum = ThreadLocalRandom.current().nextInt(iterCnt); + + LogListener lsnr = LogListener.matches("abba").times(total) + .andMatches(Pattern.compile("(?i)abba")).times(total * 2) + .andMatches("ab").times(total) + .andMatches("ba").times(total) + .build(); + + LogListener mtLsnr = LogListener.matches("abba").build(); + + log.registerListener(lsnr); + + GridTestUtils.runMultiThreaded(() -> { + for (int i = 0; i < iterCnt; i++) { + if (rndNum == i) + log.registerListener(mtLsnr); + + log.info("It is the abba(ABBA) message."); + } + }, threadCnt, "test-listening-log"); + + lsnr.check(); + mtLsnr.check(); + } + + /** + * Check "echo" logger. + */ + public void testEchoLogger() { + IgniteLogger echo = new StringLogger(); + + ListeningTestLogger log = new ListeningTestLogger(true, echo); + + log.error("1"); + log.warning("2"); + log.info("3"); + log.debug("4"); + log.trace("5"); + + assertEquals("12345", echo.toString()); + } + + /** */ + private static class StringLogger extends NullLogger { + /** */ + private final StringBuilder buf = new StringBuilder(); + + /** {@inheritDoc} */ + @Override public void trace(String msg) { + buf.append(msg); + } + + /** {@inheritDoc} */ + @Override public void debug(String msg) { + buf.append(msg); + } + + /** {@inheritDoc} */ + @Override public void info(String msg) { + buf.append(msg); + } + + /** {@inheritDoc} */ + @Override public void warning(String msg, Throwable t) { + buf.append(msg); + } + + /** {@inheritDoc} */ + @Override public void error(String msg, Throwable t) { + buf.append(msg); + } + + /** {@inheritDoc} */ + @Override public String toString() { + return buf.toString(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/a3b624d9/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java index ac2bed3..32cd36e 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java @@ -87,6 +87,7 @@ import org.apache.ignite.startup.properties.NotStringSystemPropertyTest; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.GridAbstractTest; import org.apache.ignite.testframework.test.ConfigVariationsTestSuiteBuilderTest; +import org.apache.ignite.testframework.test.ListeningTestLoggerTest; import org.apache.ignite.testframework.test.ParametersTest; import org.apache.ignite.testframework.test.VariationsIteratorTest; import org.apache.ignite.util.AttributeNodeFilterSelfTest; @@ -217,6 +218,8 @@ public class IgniteBasicTestSuite extends TestSuite { suite.addTestSuite(CacheRebalanceConfigValidationTest.class); + suite.addTestSuite(ListeningTestLoggerTest.class); + return suite; } }
