TestSensor: support abortConditions
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/3e1171f7 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/3e1171f7 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/3e1171f7 Branch: refs/heads/master Commit: 3e1171f7f8023da97d041d0f5dd5953182430a31 Parents: 967c2f0 Author: Aled Sage <aled.s...@gmail.com> Authored: Tue Jul 26 22:16:30 2016 +0100 Committer: Aled Sage <aled.s...@gmail.com> Committed: Mon Aug 8 14:48:25 2016 +0100 ---------------------------------------------------------------------- .../brooklyn/test/framework/AbortError.java | 31 ++++ .../test/framework/TestFrameworkAssertions.java | 155 ++++++++++++++++--- ...leShellCommandDeprecatedIntegrationTest.java | 13 +- .../framework/TestFrameworkAssertionsTest.java | 54 ++++++- .../brooklyn/test/framework/TestSensorTest.java | 128 +++++++++++++-- .../brooklyn/util/exceptions/Exceptions.java | 24 +++ .../apache/brooklyn/util/repeat/Repeater.java | 20 ++- 7 files changed, 368 insertions(+), 57 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e1171f7/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbortError.java ---------------------------------------------------------------------- diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbortError.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbortError.java new file mode 100644 index 0000000..dd82d01 --- /dev/null +++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbortError.java @@ -0,0 +1,31 @@ +/* + * 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.brooklyn.test.framework; + +public class AbortError extends Error { + private static final long serialVersionUID = -2922419711728467414L; + + public AbortError(String msg) { + super(msg); + } + + public AbortError(String msg, Throwable cause) { + super(msg, cause); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e1171f7/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java ---------------------------------------------------------------------- diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java index 0227bf1..e1ed825 100644 --- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java +++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.Callable; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.config.ConfigKey; @@ -32,14 +33,17 @@ import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.exceptions.CompoundRuntimeException; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.FatalConfigurationRuntimeException; +import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.repeat.Repeater; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import com.google.common.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; @@ -62,9 +66,9 @@ public class TestFrameworkAssertions { public static final String UNKNOWN_CONDITION = "unknown condition"; public static class AssertionOptions { - protected Map<String,?> flags; - protected List<? extends Map<String, ?>> assertions; - protected List<? extends Map<String, ?>> abortConditions; + protected Map<String,Object> flags = MutableMap.of(); + protected List<? extends Map<String, ?>> assertions = ImmutableList.of(); + protected List<? extends Map<String, ?>> abortConditions = ImmutableList.of(); protected String target; protected Supplier<?> supplier; @@ -73,18 +77,11 @@ public class TestFrameworkAssertions { this.supplier = supplier; } public AssertionOptions flags(Map<String,?> val) { - this.flags = val; + this.flags.putAll(val); return this; } public AssertionOptions timeout(Duration val) { - if (flags == null) { - flags = ImmutableMap.of("timeout", val); - } else { - MutableMap.<String, Object>builder() - .putAll(flags) - .put("timeout", val) - .build(); - } + this.flags.put("timeout", val); return this; } public AssertionOptions assertions(Map<String, ?> val) { @@ -216,20 +213,58 @@ public class TestFrameworkAssertions { support.validate(); } + // TODO Copied from Asserts.toDuration + private static Duration toDuration(Object duration, Duration defaultVal) { + if (duration == null) + return defaultVal; + else + return Duration.of(duration); + } + protected static <T> void checkAssertionsEventually(AssertionSupport support, final AssertionOptions options) { if (options.assertions == null || options.assertions.isEmpty()) { return; } + Map<String, ?> flags = options.flags; + + // To speed up tests, default is for the period to start small and increase... + // TODO ignoring "period" and "maxAttempts" + Integer maxAttempts = (Integer) flags.get("maxAttempts"); + Duration timeout = toDuration(flags.get("timeout"), (maxAttempts == null ? Asserts.DEFAULT_LONG_TIMEOUT : Duration.PRACTICALLY_FOREVER)); + Duration fixedPeriod = toDuration(flags.get("period"), null); + Duration minPeriod = (fixedPeriod != null) ? fixedPeriod : toDuration(flags.get("minPeriod"), Duration.millis(1)); + Duration maxPeriod = (fixedPeriod != null) ? fixedPeriod : toDuration(flags.get("maxPeriod"), Duration.millis(500)); + Predicate<Throwable> rethrowImmediatelyPredicate = Predicates.or(ImmutableList.of( + Predicates.instanceOf(AbortError.class), + Predicates.instanceOf(InterruptedException.class), + Predicates.instanceOf(RuntimeInterruptedException.class))); + try { - Asserts.succeedsEventually(options.flags, new Runnable() { - @Override - public void run() { - Object actual = options.supplier.get(); - for (Map<String, ?> assertionMap : options.assertions) { - checkActualAgainstAssertions(assertionMap, options.target, actual); - } - } - }); + Repeater.create() + .until(new Callable<Boolean>() { + public Boolean call() { + try { + Object actual = options.supplier.get(); + + for (Map<String, ?> abortMap : options.abortConditions) { + checkActualAgainstAbortConditions(abortMap, options.target, actual); + } + for (Map<String, ?> assertionMap : options.assertions) { + checkActualAgainstAssertions(assertionMap, options.target, actual); + } + return true; + } catch (AssertionError e) { + throw e; + } catch (Throwable t) { + throw t; + } + }}) + .limitIterationsTo(maxAttempts != null ? maxAttempts : Integer.MAX_VALUE) + .limitTimeTo(timeout) + .backoff(minPeriod, 1.2, maxPeriod) + .rethrowExceptionImmediately(rethrowImmediatelyPredicate) + .runRequiringTrue(); + } catch (AssertionError t) { support.fail(t); } catch (Throwable t) { @@ -266,6 +301,8 @@ public class TestFrameworkAssertions { if (Objects.equals(actual, expected)) { failAssertion(target, condition, expected, actual); } + break; + case IS_NULL : if (isTrue(expected) != (null == actual)) { failAssertion(target, condition, expected, actual); @@ -314,6 +351,74 @@ public class TestFrameworkAssertions { } } + protected static <T> void checkActualAgainstAbortConditions(Map<String, ?> assertions, String target, T actual) { + for (Map.Entry<String, ?> assertion : assertions.entrySet()) { + String condition = assertion.getKey().toString(); + Object expected = assertion.getValue(); + switch (condition) { + + case IS_EQUAL_TO : + case EQUAL_TO : + case EQUALS : + if (null != actual && actual.equals(expected)) { + abort(target, condition, expected, actual); + } + break; + + case NOT_EQUAL : + if (!Objects.equals(actual, expected)) { + abort(target, condition, expected, actual); + } + break; + + case IS_NULL : + if (isTrue(expected) == (null == actual)) { + abort(target, condition, expected, actual); + } + break; + + case NOT_NULL : + if (isTrue(expected) == (null != actual)) { + abort(target, condition, expected, actual); + } + break; + + case CONTAINS : + if (null != actual && actual.toString().contains(expected.toString())) { + abort(target, condition, expected, actual); + } + break; + + case IS_EMPTY : + if (isTrue(expected) == (null == actual || Strings.isEmpty(actual.toString()))) { + abort(target, condition, expected, actual); + } + break; + + case NOT_EMPTY : + if (isTrue(expected) == ((null != actual && Strings.isNonEmpty(actual.toString())))) { + abort(target, condition, expected, actual); + } + break; + + case MATCHES : + if (null != actual && actual.toString().matches(expected.toString())) { + abort(target, condition, expected, actual); + } + break; + + case HAS_TRUTH_VALUE : + if (isTrue(expected) == isTrue(actual)) { + abort(target, condition, expected, actual); + } + break; + + default: + abort(target, condition, expected, actual); + } + } + } + static void failAssertion(String target, String assertion, Object expected, Object actual) { throw new AssertionError(Joiner.on(' ').join( Objects.toString(target), @@ -324,6 +429,12 @@ public class TestFrameworkAssertions { Objects.toString(actual))); } + static void abort(String target, String assertion, Object expected, Object actual) { + throw new AbortError(Objects.toString(target) + " matched abort criteria '" + + Objects.toString(assertion) + " " + Objects.toString(expected) + "', found " + + Objects.toString(actual)); + } + private static boolean isTrue(Object object) { return null != object && Boolean.valueOf(object.toString()); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e1171f7/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleShellCommandDeprecatedIntegrationTest.java ---------------------------------------------------------------------- diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleShellCommandDeprecatedIntegrationTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleShellCommandDeprecatedIntegrationTest.java index 34ffc6d..f8fdbde 100644 --- a/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleShellCommandDeprecatedIntegrationTest.java +++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleShellCommandDeprecatedIntegrationTest.java @@ -102,16 +102,14 @@ public class SimpleShellCommandDeprecatedIntegrationTest extends BrooklynAppUnit } } - private List<Map<String, Object>> makeAssertions(Map<String, Object> ...maps) { - ArrayList<Map<String, Object>> assertions = new ArrayList<>(); - for (Map<String, Object> map : maps) { + private List<Map<String, ?>> makeAssertions(Map<String, ?> ...maps) { + ArrayList<Map<String, ?>> assertions = new ArrayList<>(); + for (Map<String, ?> map : maps) { assertions.add(map); } return assertions; } - - @Test(groups = "Integration") public void shouldSucceedUsingSuccessfulExitAsDefaultCondition() { TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(TestApplication.LOCALHOST_MACHINE_SPEC)); @@ -140,7 +138,7 @@ public class SimpleShellCommandDeprecatedIntegrationTest extends BrooklynAppUnit try { app.start(ImmutableList.<Location>of()); } catch (Throwable t) { - Asserts.expectedFailureContains(t, "exit code equals 0"); + Asserts.expectedFailureContains(t, "exit code expected equals 0 but found 1"); } assertThat(uptime.sensors().get(SERVICE_UP)).isFalse() @@ -193,8 +191,9 @@ public class SimpleShellCommandDeprecatedIntegrationTest extends BrooklynAppUnit try { app.start(ImmutableList.<Location>of()); + Asserts.shouldHaveFailedPreviously(); } catch (Exception e) { - Asserts.expectedFailureContains(e, "exit code equals 1", "exit code equals 255"); + Asserts.expectedFailureContains(e, "exit code expected equals 1", "exit code expected equals 255"); } assertThat(ServiceStateLogic.getExpectedState(uptime)).isEqualTo(Lifecycle.ON_FIRE) http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e1171f7/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestFrameworkAssertionsTest.java ---------------------------------------------------------------------- diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestFrameworkAssertionsTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestFrameworkAssertionsTest.java index 0b660e3..9e79bfe 100644 --- a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestFrameworkAssertionsTest.java +++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestFrameworkAssertionsTest.java @@ -35,7 +35,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.TimeUnit; public class TestFrameworkAssertionsTest { private static final Logger LOG = LoggerFactory.getLogger(TestFrameworkAssertionsTest.class); @@ -71,7 +70,7 @@ public class TestFrameworkAssertionsTest { } @Test(dataProvider = "positiveTestsDP") - public void positiveTest(final Object data, final List<Map<String, Object>> assertions) { + public void positiveTest(final Object data, final List<Map<String, ?>> assertions) { final Supplier<Object> supplier = new Supplier<Object>() { @Override public Object get() { @@ -79,7 +78,30 @@ public class TestFrameworkAssertionsTest { return data; } }; - TestFrameworkAssertions.checkAssertionsEventually(new AssertionOptions(Objects.toString(data), supplier).timeout(Duration.seconds(2)).assertions(assertions)); + TestFrameworkAssertions.checkAssertionsEventually(new AssertionOptions(Objects.toString(data), supplier) + .timeout(Asserts.DEFAULT_LONG_TIMEOUT).assertions(assertions)); + } + + @Test(dataProvider = "positiveTestsDP") + public void positiveAbortTest(final Object data, final List<Map<String, ?>> abortConditions) { + final Supplier<Object> supplier = new Supplier<Object>() { + @Override + public Object get() { + LOG.info("Supplier invoked for data [{}]", data); + return data; + } + }; + + for (Map<String, ?> map : abortConditions) { + try { + TestFrameworkAssertions.checkAssertionsEventually(new AssertionOptions(Objects.toString(data), supplier) + .timeout(Asserts.DEFAULT_LONG_TIMEOUT).abortConditions(map) + .assertions(ImmutableMap.of("equals", "wrong-value-never-equals"))); + Asserts.shouldHaveFailedPreviously(); + } catch (AbortError e) { + // success + } + } } @DataProvider @@ -115,7 +137,7 @@ public class TestFrameworkAssertionsTest { } @Test(dataProvider = "negativeTestsDP") - public void negativeTests(final Object data, String condition, Object expected, final List<Map<String, Object>> assertions) { + public void negativeTests(final Object data, String condition, Object expected, final List<Map<String, ?>> assertions) { final Supplier<Object> supplier = new Supplier<Object>() { @Override public Object get() { @@ -133,7 +155,31 @@ public class TestFrameworkAssertionsTest { } catch (AssertionError e) { Asserts.expectedFailureContains(e, Objects.toString(data), condition, expected.toString()); } + } + @Test(dataProvider = "negativeTestsDP") + public void negativeAbortTest(final Object data, String condition, Object expected, final List<Map<String, ?>> assertions) { + final Supplier<Object> supplier = new Supplier<Object>() { + @Override + public Object get() { + LOG.info("Supplier invoked for data [{}]", data); + return data; + } + }; + + // It should always try at least once, so we can use a very small timeout + Duration timeout = Duration.millis(1); + + // The abort-condition should never hold, so it should always fail due to the timeout rather than + // aborting. + try { + TestFrameworkAssertions.checkAssertionsEventually(new AssertionOptions(Objects.toString(data), supplier) + .timeout(timeout).abortConditions(assertions) + .assertions(assertions)); + Asserts.shouldHaveFailedPreviously(); + } catch (AssertionError e) { + Asserts.expectedFailureContains(e, Objects.toString(data), condition, expected.toString()); + } } @Test http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e1171f7/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 17435fe..d0a5ebc 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 @@ -22,24 +22,31 @@ package org.apache.brooklyn.test.framework; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutorService; +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.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.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.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException; import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; import org.apache.commons.lang3.builder.ToStringBuilder; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -57,47 +64,76 @@ public class TestSensorTest extends BrooklynAppUnitTestSupport { private List<Location> locs = ImmutableList.of(); private String testId; + private ExecutorService executor; @BeforeMethod(alwaysRun=true) @Override public void setUp() throws Exception { super.setUp(); testId = Identifiers.makeRandomId(8); + executor = Executors.newCachedThreadPool(); } + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + if (executor != null) executor.shutdownNow(); + super.tearDown(); + } + @Test public void testAssertEqual() throws Exception { int testInteger = 100; //Add Sensor Test for BOOLEAN sensor - app.createAndManageChild(EntitySpec.create(TestSensor.class) + TestSensor testCaseBool = app.createAndManageChild(EntitySpec.create(TestSensor.class) .configure(TestSensor.TARGET_ENTITY, app) .configure(TestSensor.SENSOR_NAME, BOOLEAN_SENSOR.getName()) .configure(TestSensor.ASSERTIONS, newMapAssertion("equals", true))); //Add Sensor Test for STRING sensor - app.createAndManageChild(EntitySpec.create(TestSensor.class) + TestSensor testCaseStr = app.createAndManageChild(EntitySpec.create(TestSensor.class) .configure(TestSensor.TARGET_ENTITY, app) .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName()) .configure(TestSensor.ASSERTIONS, newListAssertion("equals", testId))); //Add Sensor Test for INTEGER sensor - app.createAndManageChild(EntitySpec.create(TestSensor.class) + TestSensor testCaseInt = app.createAndManageChild(EntitySpec.create(TestSensor.class) .configure(TestSensor.TARGET_ENTITY, app) .configure(TestSensor.SENSOR_NAME, INTEGER_SENSOR.getName()) .configure(TestSensor.ASSERTIONS, newListAssertion("equals", testInteger))); - //Set BOOLEAN Sensor to true + //Set sensors, so test-cases will immediately succeed app.sensors().set(BOOLEAN_SENSOR, Boolean.TRUE); - - // Give a value to INTEGER sensor app.sensors().set(INTEGER_SENSOR, testInteger); - - //Set STRING sensor to random string app.sensors().set(STRING_SENSOR, testId); app.start(locs); + + assertTestSensorSucceeds(testCaseBool); + assertTestSensorSucceeds(testCaseStr); + assertTestSensorSucceeds(testCaseInt); + } + @Test + public void testAssertEqualsWhenSensorSetLater() throws Exception { + 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("equals", testId))); + + // Wait long enough that we expect the assertion to have been attempted + executor.submit(new Runnable() { + public void run() { + Time.sleep(Duration.millis(250)); + app.sensors().set(STRING_SENSOR, testId); + }}); + + app.start(locs); + + assertTestSensorSucceeds(testCase); } + @Test public void testAssertEqualFailure() throws Exception { //Add Sensor Test for BOOLEAN sensor @@ -127,21 +163,23 @@ public class TestSensorTest extends BrooklynAppUnitTestSupport { @Test public void testAssertNull() throws Exception { //Add Sensor Test for BOOLEAN sensor - app.createAndManageChild(EntitySpec.create(TestSensor.class) + TestSensor testCaseBool = app.createAndManageChild(EntitySpec.create(TestSensor.class) .configure(TestSensor.TARGET_ENTITY, app) .configure(TestSensor.SENSOR_NAME, BOOLEAN_SENSOR.getName()) .configure(TestSensor.ASSERTIONS, newMapAssertion("isNull", true))); //Add Sensor Test for STRING sensor - app.createAndManageChild(EntitySpec.create(TestSensor.class) + TestSensor testCaseStr = app.createAndManageChild(EntitySpec.create(TestSensor.class) .configure(TestSensor.TARGET_ENTITY, app) .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName()) .configure(TestSensor.ASSERTIONS, newListAssertion("notNull", true))); - //Set STRING sensor to random string + //Set STRING sensor (to non-null); leave bool sensor as null app.sensors().set(STRING_SENSOR, testId); app.start(locs); - + + assertTestSensorSucceeds(testCaseBool); + assertTestSensorSucceeds(testCaseStr); } @@ -165,11 +203,11 @@ public class TestSensorTest extends BrooklynAppUnitTestSupport { final String sensorValue = String.format("%s%s%s", Identifiers.makeRandomId(8), time, Identifiers.makeRandomId(8)); //Add Sensor Test for STRING sensor - app.createAndManageChild(EntitySpec.create(TestSensor.class) + TestSensor testCaseStr = app.createAndManageChild(EntitySpec.create(TestSensor.class) .configure(TestSensor.TARGET_ENTITY, app) .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName()) .configure(TestSensor.ASSERTIONS, newListAssertion("matches", String.format(".*%s.*", time)))); - app.createAndManageChild(EntitySpec.create(TestSensor.class) + TestSensor testCaseBool = app.createAndManageChild(EntitySpec.create(TestSensor.class) .configure(TestSensor.TARGET_ENTITY, app) .configure(TestSensor.SENSOR_NAME, BOOLEAN_SENSOR.getName()) .configure(TestSensor.ASSERTIONS, newMapAssertion("matches", "true"))); @@ -178,8 +216,10 @@ public class TestSensorTest extends BrooklynAppUnitTestSupport { app.sensors().set(STRING_SENSOR, sensorValue); app.sensors().set(BOOLEAN_SENSOR, true); - app.start(locs); + + assertTestSensorSucceeds(testCaseStr); + assertTestSensorSucceeds(testCaseBool); } @Test @@ -214,7 +254,7 @@ public class TestSensorTest extends BrooklynAppUnitTestSupport { @Test public void testAssertMatchesOnNonStringSensor() throws Exception { //Add Sensor Test for OBJECT sensor - app.createAndManageChild(EntitySpec.create(TestSensor.class) + TestSensor testCaseObj = app.createAndManageChild(EntitySpec.create(TestSensor.class) .configure(TestSensor.TARGET_ENTITY, app) .configure(TestSensor.SENSOR_NAME, OBJECT_SENSOR.getName()) .configure(TestSensor.ASSERTIONS, newListAssertion("matches", ".*TestObject.*id=.*"))); @@ -222,7 +262,54 @@ public class TestSensorTest extends BrooklynAppUnitTestSupport { app.sensors().set(OBJECT_SENSOR, new TestObject()); app.start(locs); + + assertTestSensorSucceeds(testCaseObj); + } + + @Test + public void testAbortsIfConditionSatisfied() throws Exception { + final AttributeSensor<Lifecycle> serviceStateSensor = Sensors.newSensor(Lifecycle.class, + "test.service.state", "Actual lifecycle state of the service (for testing)"); + + TestEntity entity = app.addChild(EntitySpec.create(TestEntity.class)); + + app.createAndManageChild(EntitySpec.create(TestSensor.class) + .configure(TestSensor.TIMEOUT, Duration.ONE_MINUTE) + .configure(TestSensor.TARGET_ENTITY, entity) + .configure(TestSensor.SENSOR_NAME, serviceStateSensor.getName()) + .configure(TestSensor.ASSERTIONS, newMapAssertion("equals", Lifecycle.RUNNING)) + .configure(TestSensor.ABORT_CONDITIONS, newMapAssertion("equals", Lifecycle.ON_FIRE))); + + entity.sensors().set(serviceStateSensor, Lifecycle.ON_FIRE); + assertStartFails(app, AbortError.class, Asserts.DEFAULT_LONG_TIMEOUT); + } + @Test + public void testDoesNotAbortIfConditionUnsatisfied() throws Exception { + final AttributeSensor<Lifecycle> serviceStateSensor = Sensors.newSensor(Lifecycle.class, + "test.service.state", "Actual lifecycle state of the service (for testing)"); + + final TestEntity entity = app.addChild(EntitySpec.create(TestEntity.class)); + + TestSensor testCase = app.createAndManageChild(EntitySpec.create(TestSensor.class) + .configure(TestSensor.TIMEOUT, Asserts.DEFAULT_LONG_TIMEOUT) + .configure(TestSensor.TARGET_ENTITY, entity) + .configure(TestSensor.SENSOR_NAME, serviceStateSensor.getName()) + .configure(TestSensor.ASSERTIONS, newMapAssertion("equals", Lifecycle.RUNNING)) + .configure(TestSensor.ABORT_CONDITIONS, newMapAssertion("equals", Lifecycle.ON_FIRE))); + + // Set the state to running while we are starting (so that the abort-condition will have + // been checked). + entity.sensors().set(serviceStateSensor, Lifecycle.STARTING); + executor.submit(new Runnable() { + public void run() { + Time.sleep(Duration.millis(50)); + entity.sensors().set(serviceStateSensor, Lifecycle.RUNNING); + }}); + + app.start(locs); + + assertTestSensorSucceeds(testCase); } @Test @@ -271,6 +358,15 @@ 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); + } + + protected void assertTestSensorFails(TestSensor entity) { EntityAsserts.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE); EntityAsserts.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e1171f7/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java index 2997f49..421cc15 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java @@ -182,6 +182,30 @@ public class Exceptions { } } + /** + * Indicates whether this exception is "fatal" - i.e. in normal programming, should not be + * caught but should instead be propagating so the call-stack fails. For example, an interrupt + * should cause the task to abort rather than catching and ignoring (or "handling" incorrectly). + */ + public static boolean isFatal(Throwable throwable) { + return (throwable instanceof InterruptedException) + || (throwable instanceof RuntimeInterruptedException) + || (throwable instanceof Error); + } + + public static Predicate<Throwable> isFatalPredicate() { + return IsFatalPredicate.INSTANCE; + } + + private static class IsFatalPredicate implements Predicate<Throwable> { + private static final IsFatalPredicate INSTANCE = new IsFatalPredicate(); + + @Override + public boolean apply(Throwable input) { + return input != null && isFatal(input); + } + } + /** returns the first exception of the given type, or null */ @SuppressWarnings("unchecked") public static <T extends Throwable> T getFirstThrowableOfType(Throwable from, Class<T> clazz) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3e1171f7/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 b1df990..9dfc9fb 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 @@ -28,11 +28,9 @@ import javax.annotation.Nullable; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.ReferenceWithError; -import org.apache.brooklyn.util.repeat.Repeater; import org.apache.brooklyn.util.time.CountdownTimer; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,6 +38,7 @@ import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; +import com.google.common.base.Predicates; import com.google.common.base.Stopwatch; import com.google.common.util.concurrent.Callables; @@ -83,7 +82,7 @@ public class Repeater implements Callable<Boolean> { private Duration timeLimit = null; private int iterationLimit = 0; private boolean rethrowException = false; - private boolean rethrowExceptionImmediately = false; + private Predicate<? super Throwable> rethrowImmediatelyCondition = Exceptions.isFatalPredicate(); private boolean warnOnUnRethrownException = true; public Repeater() { @@ -243,7 +242,12 @@ public class Repeater implements Callable<Boolean> { * @return {@literal this} to aid coding in a fluent style. */ public Repeater rethrowExceptionImmediately() { - this.rethrowExceptionImmediately = true; + this.rethrowImmediatelyCondition = Predicates.alwaysTrue(); + return this; + } + + public Repeater rethrowExceptionImmediately(Predicate<? super Throwable> val) { + this.rethrowImmediatelyCondition = checkNotNull(val, "rethrowExceptionImmediately predicate"); return this; } @@ -321,19 +325,19 @@ public class Repeater implements Callable<Boolean> { try { body.call(); - } catch (Exception e) { + } catch (Throwable e) { log.warn(description, e); - if (rethrowExceptionImmediately) throw Exceptions.propagate(e); + if (rethrowImmediatelyCondition.apply(e)) throw Exceptions.propagate(e); } boolean done = false; try { lastError = null; done = exitCondition.call(); - } catch (Exception e) { + } catch (Throwable e) { if (log.isDebugEnabled()) log.debug(description, e); lastError = e; - if (rethrowExceptionImmediately) throw Exceptions.propagate(e); + if (rethrowImmediatelyCondition.apply(e)) throw Exceptions.propagate(e); } if (done) { if (log.isDebugEnabled()) log.debug("{}: condition satisfied", description);