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);

Reply via email to