Repository: brooklyn-server Updated Branches: refs/heads/master 615c63574 -> bfaaa909e
Test framework: avoid repeated stacktrace logging Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/816dc56a Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/816dc56a Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/816dc56a Branch: refs/heads/master Commit: 816dc56a6eec32f426b5d6a758e4b8890d523e3e Parents: b11c87d Author: Aled Sage <aled.s...@gmail.com> Authored: Thu Dec 1 11:39:40 2016 +0000 Committer: Aled Sage <aled.s...@gmail.com> Committed: Thu Dec 1 16:16:19 2016 +0000 ---------------------------------------------------------------------- .../brooklyn/test/framework/TestSensorTest.java | 62 +++++++++++++++++++- .../org/apache/brooklyn/test/LogWatcher.java | 24 +++++++- .../apache/brooklyn/util/repeat/Repeater.java | 9 ++- 3 files changed, 92 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/816dc56a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSensorTest.java ---------------------------------------------------------------------- diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSensorTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSensorTest.java index d0a5ebc..906b25f 100644 --- a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSensorTest.java +++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestSensorTest.java @@ -19,6 +19,9 @@ package org.apache.brooklyn.test.framework; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -28,20 +31,25 @@ import java.util.concurrent.Executors; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.entity.Attributes; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.EntityAsserts; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.core.entity.trait.Startable; import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; import org.apache.brooklyn.core.test.entity.TestApplication; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.test.LogWatcher; +import org.apache.brooklyn.test.LogWatcher.EventPredicates; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException; +import org.apache.brooklyn.util.repeat.Repeater; import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; @@ -50,11 +58,14 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import ch.qos.logback.classic.spi.ILoggingEvent; + public class TestSensorTest extends BrooklynAppUnitTestSupport { private static final AttributeSensorAndConfigKey<Boolean, Boolean> BOOLEAN_SENSOR = ConfigKeys.newSensorAndConfigKey(Boolean.class, "boolean-sensor", "Boolean Sensor"); @@ -332,6 +343,55 @@ public class TestSensorTest extends BrooklynAppUnitTestSupport { assertStartFails(app, NullPointerException.class, Asserts.DEFAULT_LONG_TIMEOUT); } + @Test + public void testDoesNotLogStacktraceRepeatedly() throws Exception { + final long time = System.currentTimeMillis(); + final String sensorValue = String.format("%s%s%s", Identifiers.makeRandomId(8), time, Identifiers.makeRandomId(8)); + + // Test case will repeatedly fail until we have finished our logging assertions. + // Then we'll let it complete by setting the sensor. + TestSensor testCase = app.createAndManageChild(EntitySpec.create(TestSensor.class) + .configure(TestSensor.TIMEOUT, Asserts.DEFAULT_LONG_TIMEOUT) + .configure(TestSensor.TARGET_ENTITY, app) + .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName()) + .configure(TestSensor.ASSERTIONS, newListAssertion("matches", String.format(".*%s.*", time)))); + + String loggerName = Repeater.class.getName(); + ch.qos.logback.classic.Level logLevel = ch.qos.logback.classic.Level.DEBUG; + Predicate<ILoggingEvent> repeatedFailureMsgMatcher = EventPredicates.containsMessage("repeated failure; excluding stacktrace"); + Predicate<ILoggingEvent> stacktraceMatcher = EventPredicates.containsExceptionStackLine(TestFrameworkAssertions.class, "checkActualAgainstAssertions"); + Predicate<ILoggingEvent> filter = Predicates.or(repeatedFailureMsgMatcher, stacktraceMatcher); + LogWatcher watcher = new LogWatcher(loggerName, logLevel, filter); + + watcher.start(); + try { + // Invoke async; will let it complete after we see the log messages we expect + Task<?> task = Entities.invokeEffector(app, app, Startable.START, ImmutableMap.of("locations", locs)); + + // Expect "excluding stacktrace" message at least once + List<ILoggingEvent> repeatedFailureMsgEvents = watcher.assertHasEventEventually(repeatedFailureMsgMatcher); + assertTrue(repeatedFailureMsgEvents.size() > 0, "repeatedFailureMsgEvents="+repeatedFailureMsgEvents.size()); + + // Expect stacktrace just once + List<ILoggingEvent> stacktraceEvents = watcher.assertHasEventEventually(stacktraceMatcher); + assertEquals(Integer.valueOf(stacktraceEvents.size()), Integer.valueOf(1), "stacktraceEvents="+stacktraceEvents.size()); + + //Set STRING sensor + app.sensors().set(STRING_SENSOR, sensorValue); + task.get(Asserts.DEFAULT_LONG_TIMEOUT); + + assertTestSensorSucceeds(testCase); + + // And for good measure (in case we just checked too early last time), check again + // that we didn't get another stacktrace + stacktraceEvents = watcher.getEvents(stacktraceMatcher); + assertEquals(Integer.valueOf(stacktraceEvents.size()), Integer.valueOf(1), "stacktraceEvents="+stacktraceEvents.size()); + + } finally { + watcher.close(); + } + } + protected void assertStartFails(TestApplication app, Class<? extends Throwable> clazz) throws Exception { assertStartFails(app, clazz, null); } @@ -360,7 +420,7 @@ public class TestSensorTest extends BrooklynAppUnitTestSupport { Entity entity = Iterables.find(Entities.descendantsWithoutSelf(app), Predicates.instanceOf(TestSensor.class)); assertTestSensorFails((TestSensor) entity); } - + protected void assertTestSensorSucceeds(TestSensor entity) { EntityAsserts.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); EntityAsserts.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/816dc56a/test-support/src/main/java/org/apache/brooklyn/test/LogWatcher.java ---------------------------------------------------------------------- diff --git a/test-support/src/main/java/org/apache/brooklyn/test/LogWatcher.java b/test-support/src/main/java/org/apache/brooklyn/test/LogWatcher.java index 6baa852..ba29ad7 100644 --- a/test-support/src/main/java/org/apache/brooklyn/test/LogWatcher.java +++ b/test-support/src/main/java/org/apache/brooklyn/test/LogWatcher.java @@ -26,6 +26,7 @@ import java.io.Closeable; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -36,6 +37,7 @@ import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import ch.qos.logback.classic.Level; @@ -134,11 +136,25 @@ public class LogWatcher implements Closeable { assertFalse(events.isEmpty()); } - public void assertHasEventEventually() { + public List<ILoggingEvent> assertHasEventEventually() { Asserts.succeedsEventually(new Runnable() { public void run() { assertFalse(events.isEmpty()); }}); + return getEvents(); + } + + public List<ILoggingEvent> assertHasEventEventually(final Predicate<? super ILoggingEvent> filter) { + final AtomicReference<List<ILoggingEvent>> result = new AtomicReference<>(); + Asserts.succeedsEventually(new Runnable() { + public void run() { + synchronized (events) { + Iterable<ILoggingEvent> filtered = Iterables.filter(events, filter); + assertFalse(Iterables.isEmpty(filtered)); + result.set(ImmutableList.copyOf(filtered)); + } + }}); + return result.get(); } public List<ILoggingEvent> getEvents() { @@ -146,4 +162,10 @@ public class LogWatcher implements Closeable { return ImmutableList.copyOf(events); } } + + public List<ILoggingEvent> getEvents(Predicate<? super ILoggingEvent> filter) { + synchronized (events) { + return ImmutableList.copyOf(Iterables.filter(events, filter)); + } + } } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/816dc56a/utils/common/src/main/java/org/apache/brooklyn/util/repeat/Repeater.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/repeat/Repeater.java b/utils/common/src/main/java/org/apache/brooklyn/util/repeat/Repeater.java index 581a61b..a7fdc42 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/repeat/Repeater.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/repeat/Repeater.java @@ -315,6 +315,7 @@ public class Repeater implements Callable<Boolean> { Preconditions.checkState(exitCondition != null, "until() method has not been called to set the exit condition"); Preconditions.checkState(delayOnIteration != null, "every() method (or other delaySupplier() / backoff() method) has not been called to set the loop delay"); + boolean hasLoggedTransientException = false; Throwable lastError = null; int iterations = 0; CountdownTimer timer = timeLimit!=null ? CountdownTimer.newInstanceStarted(timeLimit) : CountdownTimer.newInstancePaused(Duration.PRACTICALLY_FOREVER); @@ -334,8 +335,14 @@ public class Repeater implements Callable<Boolean> { try { lastError = null; done = exitCondition.call(); + hasLoggedTransientException = false; } catch (Throwable e) { - if (log.isDebugEnabled()) log.debug(description, e); + if (hasLoggedTransientException) { + if (log.isDebugEnabled()) log.debug(description + " (repeated failure; excluding stacktrace): " + e); + } else { + if (log.isDebugEnabled()) log.debug(description, e); + hasLoggedTransientException = true; + } lastError = e; if (rethrowImmediatelyCondition.apply(e)) throw Exceptions.propagate(e); }