brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aleds...@apache.org
Subject [15/62] [abbrv] incubator-brooklyn git commit: rename core’s o.a.b.entity to o.a.b.core.entity
Date Wed, 19 Aug 2015 21:20:49 GMT
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/hello/LocalEntitiesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/hello/LocalEntitiesTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/hello/LocalEntitiesTest.java
new file mode 100644
index 0000000..0ffa550
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/hello/LocalEntitiesTest.java
@@ -0,0 +1,282 @@
+/*
+ * 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.core.entity.hello;
+
+import static org.apache.brooklyn.sensor.core.DependentConfiguration.attributeWhenReady;
+import static org.apache.brooklyn.sensor.core.DependentConfiguration.transform;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+import groovy.lang.Closure;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.EntityManager;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.sensor.SensorEvent;
+import org.apache.brooklyn.api.sensor.SensorEventListener;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+import org.apache.brooklyn.location.core.SimulatedLocation;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.time.Time;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/** tests effector invocation and a variety of sensor accessors and subscribers */
+public class LocalEntitiesTest extends BrooklynAppUnitTestSupport {
+    
+    public static final Logger log = LoggerFactory.getLogger(LocalEntitiesTest.class);
+    
+    private SimulatedLocation loc;
+    private EntityManager entityManager;
+            
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = new SimulatedLocation();
+        entityManager = mgmt.getEntityManager();
+    }
+
+    @Test
+    public void testEffectorUpdatesAttributeSensor() {
+        HelloEntity h = app.createAndManageChild(EntitySpec.create(HelloEntity.class));
+        app.start(ImmutableList.of(loc));
+        
+        h.setAge(5);
+        assertEquals((Integer)5, h.getAttribute(HelloEntity.AGE));
+    }
+
+    //REVIEW 1459 - new test
+    //subscriptions get notified in separate thread
+    @Test
+    public void testEffectorEmitsAttributeSensor() throws Exception {
+        final HelloEntity h = app.createAndManageChild(EntitySpec.create(HelloEntity.class));
+        app.start(ImmutableList.of(loc));
+        
+        final AtomicReference<SensorEvent<?>> evt = new AtomicReference<SensorEvent<?>>();
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        app.getSubscriptionContext().subscribe(h, HelloEntity.AGE, new SensorEventListener<Integer>() {
+            @Override public void onEvent(SensorEvent<Integer> event) {
+                evt.set(event);
+                latch.countDown();
+            }});
+        
+        long startTime = System.currentTimeMillis();
+        
+        h.invoke(HelloEntity.SET_AGE, ImmutableMap.of("age", 5));
+        assertTrue(latch.await(5000, TimeUnit.MILLISECONDS));
+
+        // observed intermittent failure 2 May 2012. and 14 Jun "after 2 ms". spurious wakeups.
+        // code added above to guard against this. (if problem does not recur, remove these comments!)
+        assertNotNull(evt.get(), "null response after "+(System.currentTimeMillis()-startTime)+" ms");
+        assertEquals(HelloEntity.AGE, evt.get().getSensor());
+        assertEquals(h, evt.get().getSource());
+        assertEquals(5, evt.get().getValue());
+        assertTrue(System.currentTimeMillis() - startTime < 5000);  //shouldn't have blocked for all 5s
+    }
+    
+    //REVIEW 1459 - new test
+    @Test
+    public void testEffectorEmitsTransientSensor() throws Exception {
+        HelloEntity h = app.createAndManageChild(EntitySpec.create(HelloEntity.class));
+        app.start(ImmutableList.of(loc));
+        
+        final AtomicReference<SensorEvent<?>> evt = new AtomicReference<SensorEvent<?>>();
+        app.getSubscriptionContext().subscribe(h, HelloEntity.ITS_MY_BIRTHDAY, new SensorEventListener<Object>() {
+            @Override public void onEvent(SensorEvent<Object> event) {
+                evt.set(event);
+                synchronized (evt) {
+                    evt.notifyAll();
+                }
+            }});
+        
+        long startTime = System.currentTimeMillis();
+        synchronized (evt) {
+            h.setAge(5);
+            evt.wait(5000);
+        }
+        assertNotNull(evt.get());
+        assertEquals(HelloEntity.ITS_MY_BIRTHDAY, evt.get().getSensor());
+        assertEquals(h, evt.get().getSource());
+        assertNull(evt.get().getValue());
+        assertTrue(System.currentTimeMillis() - startTime < 5000);  //shouldn't have blocked for all 5s
+    }
+
+    @Test
+    public void testSendMultipleInOrderThenUnsubscribe() throws Exception {
+        HelloEntity h = app.createAndManageChild(EntitySpec.create(HelloEntity.class));
+        app.start(ImmutableList.of(loc));
+
+        final List<Integer> data = Lists.newArrayList();
+        final CountDownLatch latch = new CountDownLatch(5);
+        
+        app.getSubscriptionContext().subscribe(h, HelloEntity.AGE, new SensorEventListener<Integer>() {
+            @Override public void onEvent(SensorEvent<Integer> event) {
+                data.add(event.getValue());
+                Time.sleep((int)(20*Math.random()));
+                log.info("Thread "+Thread.currentThread()+" notify on subscription received for "+event.getValue()+", data is "+data);
+                latch.countDown();
+            }});
+        
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        for (int i = 1; i <= 5; i++) {
+            h.setAge(i);
+        }
+        assertTrue(latch.await(5000, TimeUnit.MILLISECONDS));
+
+        app.getSubscriptionContext().unsubscribeAll();
+        h.setAge(6);
+        long totalTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
+        
+        // TODO guava util for (1..5)
+        Asserts.continually(MutableMap.of("timeout", 50), Suppliers.ofInstance(data), Predicates.<Object>equalTo(ImmutableList.of(1,2,3,4,5)));
+        assertTrue(totalTime < 2000, "totalTime="+totalTime);  //shouldn't have blocked for anywhere close to 2s (Aled says TODO: too time sensitive for BuildHive?)
+    }
+
+    @Test
+    public void testConfigSetFromAttribute() {
+        app.setConfig(HelloEntity.MY_NAME, "Bob");
+        
+        HelloEntity dad = app.createAndManageChild(EntitySpec.create(HelloEntity.class));
+        HelloEntity son = entityManager.createEntity(EntitySpec.create(HelloEntity.class).parent(dad));
+        Entities.manage(son);
+        
+        //config is inherited
+        assertEquals("Bob", app.getConfig(HelloEntity.MY_NAME));
+        assertEquals("Bob", dad.getConfig(HelloEntity.MY_NAME));
+        assertEquals("Bob", son.getConfig(HelloEntity.MY_NAME));
+        
+        //attributes are not
+        app.setAttribute(HelloEntity.FAVOURITE_NAME, "Carl");
+        assertEquals("Carl", app.getAttribute(HelloEntity.FAVOURITE_NAME));
+        assertEquals(null, dad.getAttribute(HelloEntity.FAVOURITE_NAME));
+    }
+    @Test
+    public void testConfigSetFromAttributeWhenReady() throws Exception {
+        app.setConfig(HelloEntity.MY_NAME, "Bob");
+        
+        final HelloEntity dad = app.createAndManageChild(EntitySpec.create(HelloEntity.class));
+        final HelloEntity son = entityManager.createEntity(EntitySpec.create(HelloEntity.class)
+                .parent(dad)
+                .configure(HelloEntity.MY_NAME, attributeWhenReady(dad, HelloEntity.FAVOURITE_NAME
+                        /* third param is closure; defaults to groovy truth (see google), but could be e.g.
+                           , { it!=null && it.length()>0 && it!="Jebediah" }
+                         */ )));
+        Entities.manage(son);
+        
+        app.start(ImmutableList.of(loc));
+         
+        final Semaphore s1 = new Semaphore(0);
+        final Object[] sonsConfig = new Object[1];
+        Thread t = new Thread(new Runnable() {
+            public void run() {
+                log.info("started");
+                s1.release();
+                log.info("getting config "+sonsConfig[0]);
+                sonsConfig[0] = son.getConfig(HelloEntity.MY_NAME);
+                log.info("got config {}", sonsConfig[0]);
+                s1.release();
+            }});
+                
+        log.info("starting");
+        long startTime = System.currentTimeMillis();
+        t.start();
+        log.info("waiting {}", System.identityHashCode(sonsConfig));
+        if (!s1.tryAcquire(2, TimeUnit.SECONDS)) fail("race mismatch, missing permits");
+        
+        //thread should be blocking on call to getConfig
+        assertTrue(t.isAlive());
+        assertTrue(System.currentTimeMillis() - startTime < 1500);
+        synchronized (sonsConfig) {
+            assertEquals(null, sonsConfig[0]);
+            for (Task tt : ((EntityInternal)dad).getExecutionContext().getTasks()) { log.info("task at dad:  {}, {}", tt, tt.getStatusDetail(false)); }
+            for (Task tt : ((EntityInternal)son).getExecutionContext().getTasks()) { log.info("task at son:  {}, {}", tt, tt.getStatusDetail(false)); }
+            ((EntityLocal)dad).setAttribute(HelloEntity.FAVOURITE_NAME, "Dan");
+            if (!s1.tryAcquire(2, TimeUnit.SECONDS)) fail("race mismatch, missing permits");
+        }
+        log.info("dad: "+dad.getAttribute(HelloEntity.FAVOURITE_NAME));
+        log.info("son: "+son.getConfig(HelloEntity.MY_NAME));
+        
+        //shouldn't have blocked for very long at all
+        assertTrue(System.currentTimeMillis() - startTime < 1500);
+        //and sons config should now pick up the dad's attribute
+        assertEquals(sonsConfig[0], "Dan");
+    }
+    
+    @Test
+    public void testConfigSetFromAttributeWhenReadyTransformations() {
+        app.setConfig(HelloEntity.MY_NAME, "Bob");
+        
+        HelloEntity dad = app.createAndManageChild(EntitySpec.create(HelloEntity.class));
+        HelloEntity son = entityManager.createEntity(EntitySpec.create(HelloEntity.class)
+                .parent(dad)
+                .configure(HelloEntity.MY_NAME, transform(attributeWhenReady(dad, HelloEntity.FAVOURITE_NAME), new Function<String,String>() {
+                    public String apply(String input) {
+                        return input+input.charAt(input.length()-1)+"y";
+                    }})));
+        Entities.manage(son);
+        
+        app.start(ImmutableList.of(loc));
+        ((EntityLocal)dad).setAttribute(HelloEntity.FAVOURITE_NAME, "Dan");
+        assertEquals(son.getConfig(HelloEntity.MY_NAME), "Danny");
+    }
+    
+    @Test
+    public void testConfigSetFromAttributeWhenReadyNullTransformations() {
+        app.setConfig(HelloEntity.MY_NAME, "Bob");
+        
+        HelloEntity dad = app.createAndManageChild(EntitySpec.create(HelloEntity.class));
+        // the unnecessary (HelloEntity) cast is required as a work-around to an IntelliJ issue that prevents Brooklyn from launching from the IDE
+        HelloEntity son = (HelloEntity)entityManager.createEntity(EntitySpec.create(HelloEntity.class)
+                .parent(dad)
+                .configure(HelloEntity.MY_NAME, transform(attributeWhenReady(dad, HelloEntity.FAVOURITE_NAME, (Closure)null), new Function<String,String>() {
+                    public String apply(String input) {
+                        return input+input.charAt(input.length()-1)+"y";
+                    }})));
+        Entities.manage(son);
+        
+        app.start(ImmutableList.of(loc));
+        ((EntityLocal)dad).setAttribute(HelloEntity.FAVOURITE_NAME, "Dan");
+        assertEquals(son.getConfig(HelloEntity.MY_NAME), "Danny");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/internal/ConfigMapGroovyTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/internal/ConfigMapGroovyTest.groovy b/core/src/test/java/org/apache/brooklyn/core/entity/internal/ConfigMapGroovyTest.groovy
new file mode 100644
index 0000000..44e67df
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/internal/ConfigMapGroovyTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * 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.core.entity.internal;
+
+import static org.testng.Assert.assertEquals
+import static org.testng.Assert.assertTrue
+
+import org.apache.brooklyn.core.test.entity.TestApplication
+import org.apache.brooklyn.core.entity.Entities
+import org.apache.brooklyn.core.entity.internal.ConfigMapTest.MyOtherEntity
+import org.apache.brooklyn.core.entity.internal.ConfigMapTest.MySubEntity
+import org.testng.annotations.AfterMethod
+import org.testng.annotations.BeforeMethod
+import org.testng.annotations.Test
+
+public class ConfigMapGroovyTest {
+
+    private TestApplication app;
+    private MySubEntity entity;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        entity = new MySubEntity(app);
+        Entities.manage(entity);
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testGetConfigOfTypeClosureReturnsClosure() throws Exception {
+        MyOtherEntity entity2 = new MyOtherEntity(app);
+        entity2.setConfig(MyOtherEntity.CLOSURE_KEY, { return "abc" } );
+        Entities.manage(entity2);
+        
+        Closure configVal = entity2.getConfig(MyOtherEntity.CLOSURE_KEY);
+        assertTrue(configVal instanceof Closure, "configVal="+configVal);
+        assertEquals(configVal.call(), "abc");
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/internal/ConfigMapTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/internal/ConfigMapTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/internal/ConfigMapTest.java
new file mode 100644
index 0000000..448ca07
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/internal/ConfigMapTest.java
@@ -0,0 +1,298 @@
+/*
+ * 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.core.entity.internal;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import groovy.lang.Closure;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ExecutionManager;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.config.ConfigMap;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.ConfigPredicates;
+import org.apache.brooklyn.core.entity.AbstractEntity;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.sensor.core.BasicAttributeSensorAndConfigKey.IntegerAttributeSensorAndConfigKey;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.task.BasicTask;
+import org.apache.brooklyn.util.core.task.DeferredSupplier;
+import org.testng.Assert;
+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.ImmutableSet;
+import com.google.common.util.concurrent.MoreExecutors;
+
+public class ConfigMapTest extends BrooklynAppUnitTestSupport {
+
+    private static final int TIMEOUT_MS = 10*1000;
+
+    private MySubEntity entity;
+    private ExecutorService executor;
+    private ExecutionManager executionManager;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        entity = new MySubEntity(app);
+        Entities.manage(entity);
+        executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
+        executionManager = mgmt.getExecutionManager();
+    }
+
+    @AfterMethod(alwaysRun=true)
+    @Override
+    public void tearDown() throws Exception {
+        if (executor != null) executor.shutdownNow();
+        super.tearDown();
+    }
+
+    @Test
+    public void testGetConfigKeysReturnsFromSuperAndInterfacesAndSubClass() throws Exception {
+        assertEquals(entity.getEntityType().getConfigKeys(), ImmutableSet.of(
+                MySubEntity.SUPER_KEY_1, MySubEntity.SUPER_KEY_2, MySubEntity.SUB_KEY_2, MySubEntity.INTERFACE_KEY_1));
+    }
+
+    @Test
+    public void testConfigKeyDefaultUsesValueInSubClass() throws Exception {
+        assertEquals(entity.getConfig(MyBaseEntity.SUPER_KEY_1), "overridden superKey1 default");
+    }
+
+    @Test
+    public void testConfigureFromKey() throws Exception {
+        MySubEntity entity2 = new MySubEntity(MutableMap.of(MySubEntity.SUPER_KEY_1, "changed"), app);
+        Entities.manage(entity2);
+        assertEquals(entity2.getConfig(MySubEntity.SUPER_KEY_1), "changed");
+    }
+
+    @Test
+    public void testConfigureFromSuperKey() throws Exception {
+        MySubEntity entity2 = new MySubEntity(MutableMap.of(MyBaseEntity.SUPER_KEY_1, "changed"), app);
+        Entities.manage(entity2);
+        assertEquals(entity2.getConfig(MySubEntity.SUPER_KEY_1), "changed");
+    }
+
+    @Test
+    public void testConfigSubMap() throws Exception {
+        entity.setConfig(MyBaseEntity.SUPER_KEY_1, "s1");
+        entity.setConfig(MySubEntity.SUB_KEY_2, "s2");
+        ConfigMap sub = entity.getConfigMap().submap(ConfigPredicates.matchingGlob("sup*"));
+        Assert.assertEquals(sub.getConfigRaw(MyBaseEntity.SUPER_KEY_1, true).get(), "s1");
+        Assert.assertFalse(sub.getConfigRaw(MySubEntity.SUB_KEY_2, true).isPresent());
+    }
+
+    @Test(expectedExceptions=IllegalArgumentException.class)
+    public void testFailFastOnInvalidConfigKeyCoercion() throws Exception {
+        MyOtherEntity entity2 = new MyOtherEntity(app);
+        ConfigKey<Integer> key = MyOtherEntity.INT_KEY;
+        entity2.setConfig((ConfigKey)key, "thisisnotanint");
+    }
+
+    @Test
+    public void testGetConfigOfPredicateTaskReturnsCoercedClosure() throws Exception {
+        MyOtherEntity entity2 = new MyOtherEntity(app);
+        entity2.setConfig(MyOtherEntity.PREDICATE_KEY, Predicates.notNull());
+        Entities.manage(entity2);
+
+        Predicate predicate = entity2.getConfig(MyOtherEntity.PREDICATE_KEY);
+        assertTrue(predicate instanceof Predicate, "predicate="+predicate);
+        assertTrue(predicate.apply(1));
+        assertFalse(predicate.apply(null));
+    }
+
+    @Test
+    public void testGetConfigWithDeferredSupplierReturnsSupplied() throws Exception {
+        DeferredSupplier<Integer> supplier = new DeferredSupplier<Integer>() {
+            volatile int next = 0;
+            public Integer get() {
+                return next++;
+            }
+        };
+
+        MyOtherEntity entity2 = new MyOtherEntity(app);
+        entity2.setConfig(MyOtherEntity.INT_KEY, supplier);
+        Entities.manage(entity2);
+
+        assertEquals(entity2.getConfig(MyOtherEntity.INT_KEY), Integer.valueOf(0));
+        assertEquals(entity2.getConfig(MyOtherEntity.INT_KEY), Integer.valueOf(1));
+    }
+
+    @Test
+    public void testGetConfigWithFutureWaitsForResult() throws Exception {
+        LatchingCallable work = new LatchingCallable("abc");
+        Future<String> future = executor.submit(work);
+
+        final MyOtherEntity entity2 = new MyOtherEntity(app);
+        entity2.setConfig((ConfigKey)MyOtherEntity.STRING_KEY, future);
+        Entities.manage(entity2);
+
+        Future<String> getConfigFuture = executor.submit(new Callable<String>() {
+            public String call() {
+                return entity2.getConfig(MyOtherEntity.STRING_KEY);
+            }});
+
+        assertTrue(work.latchCalled.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertFalse(getConfigFuture.isDone());
+
+        work.latchContinued.countDown();
+        assertEquals(getConfigFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS), "abc");
+    }
+
+    @Test
+    public void testGetConfigWithExecutedTaskWaitsForResult() throws Exception {
+        LatchingCallable work = new LatchingCallable("abc");
+        Task<String> task = executionManager.submit(work);
+
+        final MyOtherEntity entity2 = new MyOtherEntity(app);
+        entity2.setConfig(MyOtherEntity.STRING_KEY, task);
+        Entities.manage(entity2);
+
+        Future<String> getConfigFuture = executor.submit(new Callable<String>() {
+            public String call() {
+                return entity2.getConfig(MyOtherEntity.STRING_KEY);
+            }});
+
+        assertTrue(work.latchCalled.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertFalse(getConfigFuture.isDone());
+
+        work.latchContinued.countDown();
+        assertEquals(getConfigFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS), "abc");
+        assertEquals(work.callCount.get(), 1);
+    }
+
+    @Test
+    public void testGetConfigWithUnexecutedTaskIsExecutedAndWaitsForResult() throws Exception {
+        LatchingCallable work = new LatchingCallable("abc");
+        Task<String> task = new BasicTask<String>(work);
+
+        final MyOtherEntity entity2 = new MyOtherEntity(app);
+        entity2.setConfig(MyOtherEntity.STRING_KEY, task);
+        Entities.manage(entity2);
+
+        Future<String> getConfigFuture = executor.submit(new Callable<String>() {
+            public String call() {
+                return entity2.getConfig(MyOtherEntity.STRING_KEY);
+            }});
+
+        assertTrue(work.latchCalled.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertFalse(getConfigFuture.isDone());
+
+        work.latchContinued.countDown();
+        assertEquals(getConfigFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS), "abc");
+        assertEquals(work.callCount.get(), 1);
+    }
+
+    public static class MyBaseEntity extends AbstractEntity {
+        public static final ConfigKey<String> SUPER_KEY_1 = ConfigKeys.newStringConfigKey("superKey1", "superKey1 key", "superKey1 default");
+        public static final ConfigKey<String> SUPER_KEY_2 = ConfigKeys.newStringConfigKey("superKey2", "superKey2 key", "superKey2 default");
+        
+        public MyBaseEntity() {
+        }
+        public MyBaseEntity(Map flags) {
+            super(flags);
+        }
+        public MyBaseEntity(Map flags, Entity parent) {
+            super(flags, parent);
+        }
+        public MyBaseEntity(Entity parent) {
+            super(parent);
+        }
+    }
+
+    public static class MySubEntity extends MyBaseEntity implements MyInterface {
+        public static final ConfigKey<String> SUPER_KEY_1 = ConfigKeys.newConfigKeyWithDefault(MyBaseEntity.SUPER_KEY_1, "overridden superKey1 default");
+        public static final ConfigKey<String> SUB_KEY_2 = ConfigKeys.newStringConfigKey("subKey2", "subKey2 key", "subKey2 default");
+        
+        MySubEntity() {
+        }
+        MySubEntity(Map flags) {
+            super(flags);
+        }
+        MySubEntity(Map flags, Entity parent) {
+            super(flags, parent);
+        }
+        MySubEntity(Entity parent) {
+            super(parent);
+        }
+    }
+
+    public interface MyInterface {
+        public static final ConfigKey<String> INTERFACE_KEY_1 = ConfigKeys.newStringConfigKey("interfaceKey1", "interface key 1", "interfaceKey1 default");
+    }
+
+    public static class MyOtherEntity extends AbstractEntity {
+        public static final ConfigKey<Integer> INT_KEY = ConfigKeys.newIntegerConfigKey("intKey", "int key", 1);
+        public static final ConfigKey<String> STRING_KEY = ConfigKeys.newStringConfigKey("stringKey", "string key", null);
+        public static final ConfigKey<Object> OBJECT_KEY = ConfigKeys.newConfigKey(Object.class, "objectKey", "object key", null);
+        public static final ConfigKey<Closure> CLOSURE_KEY = ConfigKeys.newConfigKey(Closure.class, "closureKey", "closure key", null);
+        public static final ConfigKey<Future> FUTURE_KEY = ConfigKeys.newConfigKey(Future.class, "futureKey", "future key", null);
+        public static final ConfigKey<Task> TASK_KEY = ConfigKeys.newConfigKey(Task.class, "taskKey", "task key", null);
+        public static final ConfigKey<Predicate> PREDICATE_KEY = ConfigKeys.newConfigKey(Predicate.class, "predicateKey", "predicate key", null);
+        public static final IntegerAttributeSensorAndConfigKey SENSOR_AND_CONFIG_KEY = new IntegerAttributeSensorAndConfigKey("sensorConfigKey", "sensor+config key", 1);
+        
+        public MyOtherEntity() {
+        }
+        public MyOtherEntity(Map flags) {
+            super(flags);
+        }
+        public MyOtherEntity(Map flags, Entity parent) {
+            super(flags, parent);
+        }
+        public MyOtherEntity(Entity parent) {
+            super(parent);
+        }
+    }
+
+    static class LatchingCallable<T> implements Callable<T> {
+        final CountDownLatch latchCalled = new CountDownLatch(1);
+        final CountDownLatch latchContinued = new CountDownLatch(1);
+        final AtomicInteger callCount = new AtomicInteger(0);
+        final T result;
+        
+        public LatchingCallable(T result) {
+            this.result = result;
+        }
+        
+        @Override
+        public T call() throws Exception {
+            callCount.incrementAndGet();
+            latchCalled.countDown();
+            latchContinued.await();
+            return result;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/internal/EntityConfigMapUsageLegacyTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/internal/EntityConfigMapUsageLegacyTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/internal/EntityConfigMapUsageLegacyTest.java
new file mode 100644
index 0000000..801b3e5
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/internal/EntityConfigMapUsageLegacyTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.core.entity.internal;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.brooklyn.sensor.core.DependentConfiguration;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.time.Time;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.core.test.entity.TestEntityImpl;
+import org.apache.brooklyn.location.core.SimulatedLocation;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Callables;
+
+/**
+ * Test that configuration properties are usable and inherited correctly.
+ * 
+ * Uses legacy mechanism of calling entity constructors.
+ */
+public class EntityConfigMapUsageLegacyTest extends BrooklynAppUnitTestSupport {
+    private ConfigKey<Integer> intKey = ConfigKeys.newIntegerConfigKey("bkey", "b key");
+    private ConfigKey<String> strKey = ConfigKeys.newStringConfigKey("akey", "a key");
+    private ConfigKey<Integer> intKeyWithDefault = ConfigKeys.newIntegerConfigKey("ckey", "c key", 1);
+    private ConfigKey<String> strKeyWithDefault = ConfigKeys.newStringConfigKey("strKey", "str key", "str key default");
+    
+    @Test
+    public void testConfigPassedInAtConstructorIsAvailable() throws Exception {
+        TestEntity entity = new TestEntityImpl(MutableMap.of("config", MutableMap.of(strKey, "aval", intKey, 2)), app);
+        Entities.manage(entity);
+        
+        assertEquals(entity.getConfig(strKey), "aval");
+        assertEquals(entity.getConfig(intKey), Integer.valueOf(2));
+    }
+    
+    @Test
+    public void testConfigSetToGroovyTruthFalseIsAvailable() throws Exception {
+        TestEntity entity = new TestEntityImpl(MutableMap.of("config", MutableMap.of(intKeyWithDefault, 0)), app);
+        Entities.manage(entity);
+        
+        assertEquals(entity.getConfig(intKeyWithDefault), (Integer)0);
+    }
+    
+    @Test
+    public void testInheritedConfigSetToGroovyTruthFalseIsAvailable() throws Exception {
+        TestEntity parent = new TestEntityImpl(MutableMap.of("config", MutableMap.of(intKeyWithDefault, 0)), app);
+        TestEntity entity = new TestEntityImpl(parent);
+        Entities.manage(parent);
+        
+        assertEquals(entity.getConfig(intKeyWithDefault), (Integer)0);
+    }
+    
+    @Test
+    public void testConfigSetToNullIsAvailable() throws Exception {
+        TestEntity entity = new TestEntityImpl(MutableMap.of("config", MutableMap.of(strKeyWithDefault, null)), app);
+        Entities.manage(entity);
+        
+        assertEquals(entity.getConfig(strKeyWithDefault), null);
+    }
+    
+    @Test
+    public void testInheritedConfigSetToNullIsAvailable() throws Exception {
+        TestEntity parent = new TestEntityImpl(MutableMap.of("config", MutableMap.of(strKeyWithDefault, null)), app);
+        TestEntity entity = new TestEntityImpl(parent);
+        Entities.manage(parent);
+
+        assertEquals(entity.getConfig(strKeyWithDefault), null);
+    }
+    
+    @Test
+    public void testConfigCanBeSetOnEntity() throws Exception {
+        TestEntity entity = new TestEntityImpl(app);
+        entity.setConfig(strKey, "aval");
+        entity.setConfig(intKey, 2);
+        Entities.manage(entity);
+        
+        assertEquals(entity.getConfig(strKey), "aval");
+        assertEquals(entity.getConfig(intKey), (Integer)2);
+    }
+    
+    @Test
+    public void testConfigInheritedFromParent() throws Exception {
+        TestEntity parent = new TestEntityImpl(MutableMap.of("config", MutableMap.of(strKey, "aval")), app);
+        parent.setConfig(intKey, 2);
+        TestEntity entity = new TestEntityImpl(parent);
+        Entities.manage(parent);
+        
+        assertEquals(entity.getConfig(strKey), "aval");
+        assertEquals(entity.getConfig(intKey), (Integer)2);
+    }
+    
+    @Test
+    public void testConfigInConstructorOverridesParentValue() throws Exception {
+        TestEntity parent = new TestEntityImpl(MutableMap.of("config", MutableMap.of(strKey, "aval")), app);
+        TestEntity entity = new TestEntityImpl(MutableMap.of("config", MutableMap.of(strKey, "diffval")), parent);
+        Entities.manage(parent);
+
+        assertEquals("diffval", entity.getConfig(strKey));
+    }
+    
+    @Test
+    public void testConfigSetterOverridesParentValue() throws Exception {
+        TestEntity parent = new TestEntityImpl(MutableMap.of("config", MutableMap.of(strKey, "aval")), app);
+        TestEntity entity = new TestEntityImpl(parent);
+        entity.setConfig(strKey, "diffval");
+        Entities.manage(parent);
+        
+        assertEquals("diffval", entity.getConfig(strKey));
+    }
+    
+    @Test
+    public void testConfigSetterOverridesConstructorValue() throws Exception {
+        TestEntity entity = new TestEntityImpl(MutableMap.of("config", MutableMap.of(strKey, "aval")), app);
+        entity.setConfig(strKey, "diffval");
+        Entities.manage(entity);
+        
+        assertEquals("diffval", entity.getConfig(strKey));
+    }
+
+    @Test
+    public void testConfigSetOnParentInheritedByExistingChildrenBeforeStarted() throws Exception {
+        TestEntity entity = new TestEntityImpl(app);
+        app.setConfig(strKey,"aval");
+        Entities.manage(entity);
+
+        assertEquals("aval", entity.getConfig(strKey));
+    }
+
+    @Test
+    public void testConfigInheritedThroughManyGenerations() throws Exception {
+        TestEntity e = new TestEntityImpl(app);
+        TestEntity e2 = new TestEntityImpl(e);
+        app.setConfig(strKey,"aval");
+        Entities.manage(e);
+
+        assertEquals("aval", app.getConfig(strKey));
+        assertEquals("aval", e.getConfig(strKey));
+        assertEquals("aval", e2.getConfig(strKey));
+    }
+
+    @Test(enabled=false)
+    public void testConfigCannotBeSetAfterApplicationIsStarted() throws Exception {
+        TestEntity entity = new TestEntityImpl(app);
+        Entities.manage(entity);
+        app.start(ImmutableList.of(new SimulatedLocation()));
+        
+        try {
+            app.setConfig(strKey,"aval");
+            fail();
+        } catch (IllegalStateException e) {
+            // success
+        }
+        
+        assertEquals(null, entity.getConfig(strKey));
+    }
+    
+    @Test
+    public void testConfigReturnsDefaultValueIfNotSet() throws Exception {
+        TestEntity entity = new TestEntityImpl(app);
+        Entities.manage(entity);
+        assertEquals(entity.getConfig(TestEntity.CONF_NAME), "defaultval");
+    }
+    
+    @Test
+    public void testGetFutureConfigWhenReady() throws Exception {
+        TestEntity entity = new TestEntityImpl(app);
+        entity.setConfig(TestEntity.CONF_NAME, DependentConfiguration.whenDone(Callables.returning("aval")));
+        Entities.manage(entity);
+        app.start(ImmutableList.of(new SimulatedLocation()));
+        
+        assertEquals(entity.getConfig(TestEntity.CONF_NAME), "aval");
+    }
+    
+    @Test
+    public void testGetFutureConfigBlocksUntilReady() throws Exception {
+        TestEntity entity = new TestEntityImpl(app);
+        final CountDownLatch latch = new CountDownLatch(1);
+        entity.setConfig(TestEntity.CONF_NAME, DependentConfiguration.whenDone(new Callable<String>() {
+            @Override public String call() throws Exception {
+                latch.await();
+                return "aval";
+            }}));
+        Entities.manage(entity);
+        app.start(ImmutableList.of(new SimulatedLocation()));
+        
+        Thread t = new Thread(new Runnable() {
+            public void run() {
+                Time.sleep(10);
+                latch.countDown();
+            }});
+        try {
+            long starttime = System.currentTimeMillis();
+            t.start();
+            assertEquals(entity.getConfig(TestEntity.CONF_NAME), "aval");
+            long endtime = System.currentTimeMillis();
+            
+            assertTrue((endtime - starttime) >= 10, "starttime="+starttime+"; endtime="+endtime);
+            
+        } finally {
+            t.interrupt();
+        }
+    }
+    
+    @Test
+    public void testGetAttributeWhenReadyConfigReturnsWhenSet() throws Exception {
+        TestEntity entity = new TestEntityImpl(app);
+        TestEntity entity2 = new TestEntityImpl(app);
+        entity.setConfig(TestEntity.CONF_NAME, DependentConfiguration.attributeWhenReady(entity2, TestEntity.NAME));
+        Entities.manage(entity);
+        Entities.manage(entity2);
+        app.start(ImmutableList.of(new SimulatedLocation()));
+        
+        entity2.setAttribute(TestEntity.NAME, "aval");
+        assertEquals(entity.getConfig(TestEntity.CONF_NAME), "aval");
+    }
+    
+    @Test
+    public void testGetAttributeWhenReadyWithPostProcessingConfigReturnsWhenSet() throws Exception {
+        TestEntity entity = new TestEntityImpl(app);
+        TestEntity entity2 = new TestEntityImpl(app);
+        entity.setConfig(TestEntity.CONF_NAME, DependentConfiguration.attributePostProcessedWhenReady(entity2, TestEntity.NAME, Predicates.notNull(), new Function<String,String>() {
+            @Override public String apply(String input) {
+                return (input == null) ? null : input+"mysuffix";
+            }}));
+        Entities.manage(entity);
+        Entities.manage(entity2);
+        app.start(ImmutableList.of(new SimulatedLocation()));
+        
+        entity2.setAttribute(TestEntity.NAME, "aval");
+        assertEquals(entity.getConfig(TestEntity.CONF_NAME), "avalmysuffix");
+    }
+    
+    @Test
+    public void testGetAttributeWhenReadyConfigBlocksUntilSet() throws Exception {
+        TestEntity entity = new TestEntityImpl(app);
+        final TestEntity entity2 = new TestEntityImpl(app);
+        entity.setConfig(TestEntity.CONF_NAME, DependentConfiguration.attributeWhenReady(entity2, TestEntity.NAME));
+        Entities.manage(entity);
+        Entities.manage(entity2);
+        app.start(ImmutableList.of(new SimulatedLocation()));
+
+        // previously was just sleep 10, and (endtime-starttime > 10); failed with exactly 10ms        
+        final long sleepTime = 20;
+        final long earlyReturnGrace = 5;
+        Thread t = new Thread(new Runnable() {
+            @Override public void run() {
+                Time.sleep(sleepTime);
+                entity2.setAttribute(TestEntity.NAME, "aval");
+            }});
+        try {
+            long starttime = System.currentTimeMillis();
+            t.start();
+            assertEquals(entity.getConfig(TestEntity.CONF_NAME), "aval");
+            long endtime = System.currentTimeMillis();
+            
+            assertTrue((endtime - starttime) >= (sleepTime - earlyReturnGrace), "starttime=$starttime; endtime=$endtime");
+            
+        } finally {
+            t.interrupt();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/internal/EntityConfigMapUsageTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/internal/EntityConfigMapUsageTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/internal/EntityConfigMapUsageTest.java
new file mode 100644
index 0000000..5773e5e
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/internal/EntityConfigMapUsageTest.java
@@ -0,0 +1,318 @@
+/*
+ * 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.core.entity.internal;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.sensor.core.DependentConfiguration;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.apache.brooklyn.location.core.SimulatedLocation;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Callables;
+
+/**
+ * Test that configuration properties are usable and inherited correctly.
+ */
+public class EntityConfigMapUsageTest extends BrooklynAppUnitTestSupport {
+    private static final int EARLY_RETURN_GRACE = 10;
+    
+    private BasicConfigKey<Integer> intKey = new BasicConfigKey<Integer>(Integer.class, "bkey", "b key");
+    private ConfigKey<String> strKey = new BasicConfigKey<String>(String.class, "akey", "a key");
+    private ConfigKey<Integer> intKeyWithDefault = new BasicConfigKey<Integer>(Integer.class, "ckey", "c key", 1);
+    private ConfigKey<String> strKeyWithDefault = new BasicConfigKey<String>(String.class, "strKey", "str key", "str key default");
+    
+    private List<SimulatedLocation> locs;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        locs = ImmutableList.of(new SimulatedLocation());
+    }
+
+    @Test
+    public void testConfigPassedInAtConstructionIsAvailable() throws Exception {
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(strKey, "aval")
+                .configure(intKey, 2));
+
+        assertEquals(entity.getConfig(strKey), "aval");
+        assertEquals(entity.getConfig(intKey), (Integer)2);
+    }
+    
+    @Test
+    public void testConfigSetToGroovyTruthFalseIsAvailable() throws Exception {
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(intKeyWithDefault, 0));
+        
+        assertEquals(entity.getConfig(intKeyWithDefault), (Integer)0);
+    }
+    
+    @Test
+    public void testInheritedConfigSetToGroovyTruthFalseIsAvailable() throws Exception {
+        TestEntity parent = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(intKeyWithDefault, 0));
+        TestEntity entity = parent.createAndManageChild(EntitySpec.create(TestEntity.class));
+        
+        assertEquals(entity.getConfig(intKeyWithDefault), (Integer)0);
+    }
+    
+    @Test
+    public void testConfigSetToNullIsAvailable() throws Exception {
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(strKeyWithDefault, (String)null));
+        
+        assertEquals(entity.getConfig(strKeyWithDefault), null);
+    }
+    
+    @Test
+    public void testInheritedConfigSetToNullIsAvailable() throws Exception {
+        TestEntity parent = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(strKeyWithDefault, (String)null));
+        TestEntity entity = parent.createAndManageChild(EntitySpec.create(TestEntity.class));
+        
+        assertEquals(entity.getConfig(strKeyWithDefault), null);
+    }
+    
+    @Test
+    public void testInheritedConfigAvailableDeepInHierarchy() throws Exception {
+        TestEntity parent = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(strKeyWithDefault, "customval"));
+        TestEntity entity = parent.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity entity2 = entity.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity entity3 = entity2.createAndManageChild(EntitySpec.create(TestEntity.class));
+        
+        assertEquals(entity.getConfig(strKeyWithDefault), "customval");
+        assertEquals(entity2.getConfig(strKeyWithDefault), "customval");
+        assertEquals(entity3.getConfig(strKeyWithDefault), "customval");
+    }
+    
+    @Test
+    public void testConfigCanBeSetOnEntity() throws Exception {
+        TestEntity entity = app.addChild(EntitySpec.create(TestEntity.class));
+        ((EntityLocal)entity).setConfig(strKey, "aval");
+        ((EntityLocal)entity).setConfig(intKey, 2);
+        Entities.manage(entity);
+        
+        assertEquals(entity.getConfig(strKey), "aval");
+        assertEquals(entity.getConfig(intKey), (Integer)2);
+    }
+    
+    @Test
+    public void testConfigInheritedFromParent() throws Exception {
+        TestEntity parent = app.addChild(EntitySpec.create(TestEntity.class)
+                .configure(strKey, "aval"));
+        ((EntityLocal)parent).setConfig(intKey, 2);
+        Entities.manage(parent);
+        TestEntity entity = parent.createAndManageChild(EntitySpec.create(TestEntity.class));
+        
+        assertEquals(entity.getConfig(strKey), "aval");
+        assertEquals(2, entity.getConfig(intKey), (Integer)2);
+    }
+    
+    @Test
+    public void testConfigAtConstructionOverridesParentValue() throws Exception {
+        TestEntity parent = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(strKey, "aval"));
+        TestEntity entity = parent.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(strKey, "diffval"));
+        
+        assertEquals(entity.getConfig(strKey), "diffval");
+    }
+    
+    @Test
+    public void testConfigSetterOverridesParentValue() throws Exception {
+        TestEntity parent = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(strKey, "aval"));
+        TestEntity entity = parent.createAndManageChild(EntitySpec.create(TestEntity.class));
+        ((EntityLocal)entity).setConfig(strKey, "diffval");
+        
+        assertEquals(entity.getConfig(strKey), "diffval");
+    }
+    
+    @Test
+    public void testConfigSetterOverridesConstructorValue() throws Exception {
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(strKey, "aval"));
+        ((EntityLocal)entity).setConfig(strKey, "diffval");
+        Entities.manage(entity);
+        
+        assertEquals(entity.getConfig(strKey), "diffval");
+    }
+
+    @Test
+    public void testConfigSetOnParentInheritedByExistingChildrenBeforeStarted() throws Exception {
+        TestEntity parent = app.addChild(EntitySpec.create(TestEntity.class));
+        TestEntity entity = parent.createChild(EntitySpec.create(TestEntity.class));
+        ((EntityLocal)parent).setConfig(strKey,"aval");
+        Entities.manage(entity);
+        
+        assertEquals(entity.getConfig(strKey), "aval");
+    }
+
+    @Test
+    public void testConfigInheritedThroughManyGenerations() throws Exception {
+        TestEntity e = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(strKey, "aval"));
+        TestEntity e2 = e.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity e3 = e2.createAndManageChild(EntitySpec.create(TestEntity.class));
+        
+        assertEquals(e.getConfig(strKey), "aval");
+        assertEquals(e2.getConfig(strKey), "aval");
+        assertEquals(e3.getConfig(strKey), "aval");
+    }
+
+    // This has been relaxed to a warning, with a message saying "may not be supported in future versions"
+    @Test(enabled=false)
+    public void testConfigCannotBeSetAfterApplicationIsStarted() throws Exception {
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        app.start(locs);
+        
+        try {
+            ((EntityLocal)app).setConfig(strKey,"aval");
+            fail();
+        } catch (IllegalStateException e) {
+            // success
+        }
+        
+        assertEquals(entity.getConfig(strKey), null);
+    }
+    
+    @Test
+    public void testConfigReturnsDefaultValueIfNotSet() throws Exception {
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        assertEquals(entity.getConfig(TestEntity.CONF_NAME), "defaultval");
+    }
+    
+    @Test
+    public void testGetFutureConfigWhenReady() throws Exception {
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(TestEntity.CONF_NAME, DependentConfiguration.whenDone(Callables.returning("aval"))));
+        app.start(locs);
+        
+        assertEquals(entity.getConfig(TestEntity.CONF_NAME), "aval");
+    }
+    
+    @Test
+    public void testGetFutureConfigBlocksUntilReady() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(TestEntity.CONF_NAME, DependentConfiguration.whenDone(new Callable<String>() {
+                        public String call() {
+                            try {
+                                latch.await(); return "aval";
+                            } catch (InterruptedException e) {
+                                throw Exceptions.propagate(e);
+                            }
+                        }})));
+        app.start(locs);
+        
+        Thread t = new Thread(new Runnable() {
+                public void run() {
+                    try {
+                        Thread.sleep(10+EARLY_RETURN_GRACE); latch.countDown();
+                    } catch (InterruptedException e) {
+                        throw Exceptions.propagate(e);
+                    }
+                }});
+        try {
+            long starttime = System.currentTimeMillis();
+            t.start();
+            assertEquals(entity.getConfig(TestEntity.CONF_NAME), "aval");
+            long endtime = System.currentTimeMillis();
+            
+            assertTrue((endtime - starttime) >= 10, "starttime="+starttime+"; endtime="+endtime);
+            
+        } finally {
+            t.interrupt();
+        }
+    }
+    
+    @Test
+    public void testGetAttributeWhenReadyConfigReturnsWhenSet() throws Exception {
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(TestEntity.CONF_NAME, DependentConfiguration.attributeWhenReady(entity, TestEntity.NAME)));
+        app.start(locs);
+        
+        ((EntityLocal)entity).setAttribute(TestEntity.NAME, "aval");
+        assertEquals(entity2.getConfig(TestEntity.CONF_NAME), "aval");
+    }
+    
+    @Test
+    public void testGetAttributeWhenReadyWithPostProcessingConfigReturnsWhenSet() throws Exception {
+        TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(TestEntity.CONF_NAME, DependentConfiguration.attributePostProcessedWhenReady(entity, TestEntity.NAME, Predicates.notNull(), new Function<String,String>() {
+                        public String apply(String input) {
+                            return input+"mysuffix";
+                        }})));
+        app.start(locs);
+        
+        ((EntityLocal)entity).setAttribute(TestEntity.NAME, "aval");
+        assertEquals(entity2.getConfig(TestEntity.CONF_NAME), "avalmysuffix");
+    }
+    
+    @Test
+    public void testGetAttributeWhenReadyConfigBlocksUntilSet() throws Exception {
+        final TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+                .configure(TestEntity.CONF_NAME, DependentConfiguration.attributeWhenReady(entity, TestEntity.NAME)));
+        app.start(locs);
+        
+        Thread t = new Thread(new Runnable() {
+            public void run() {
+                try {
+                    Thread.sleep(10+EARLY_RETURN_GRACE);
+                    ((EntityLocal)entity).setAttribute(TestEntity.NAME, "aval");
+                } catch (InterruptedException e) {
+                    throw Exceptions.propagate(e);
+                }
+            }});
+        try {
+            long starttime = System.currentTimeMillis();
+            t.start();
+            assertEquals(entity2.getConfig(TestEntity.CONF_NAME), "aval");
+            long endtime = System.currentTimeMillis();
+            
+            assertTrue((endtime - starttime) > 10, "starttime="+starttime+"; endtime="+endtime);
+            
+        } finally {
+            t.interrupt();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/lifecycle/LifecycleTransitionTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/lifecycle/LifecycleTransitionTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/lifecycle/LifecycleTransitionTest.java
new file mode 100644
index 0000000..8b76355
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/lifecycle/LifecycleTransitionTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.core.entity.lifecycle;
+
+import static org.testng.Assert.assertTrue;
+
+import java.util.Date;
+
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle.Transition;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle.TransitionCoalesceFunction;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class LifecycleTransitionTest {
+    @DataProvider(name = "states")
+    public Object[][] generateLifecycleStates() {
+        Object[][] states = new Object[Lifecycle.values().length][];
+        int i = 0;
+        for (Lifecycle state : Lifecycle.values()) {
+            System.out.println(":" + state);
+            states[i] = new Object[] {state};
+            i++;
+        }
+        return states;
+    }
+
+    @Test(dataProvider="states")
+    public void testTransitionCoalesce(Lifecycle state) {
+        Transition t = new Transition(state, new Date());
+        String serialized = t.toString();
+        Transition t2 = new TransitionCoalesceFunction().apply(serialized);
+        assertTrue(t.equals(t2), "Deserialized Lifecycle.Transition not equal to original");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogicTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogicTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogicTest.java
new file mode 100644
index 0000000..f461e4b
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogicTest.java
@@ -0,0 +1,314 @@
+/*
+ * 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.core.entity.lifecycle;
+
+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.api.sensor.Enricher;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityAdjuncts;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
+import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceNotUpLogic;
+import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceProblemsLogic;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.core.test.entity.TestEntityImpl.TestEntityWithoutEnrichers;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+@Test
+public class ServiceStateLogicTest extends BrooklynAppUnitTestSupport {
+    
+    private static final Logger log = LoggerFactory.getLogger(ServiceStateLogicTest.class);
+    
+    final static String INDICATOR_KEY_1 = "test-indicator-1";
+    final static String INDICATOR_KEY_2 = "test-indicator-2";
+
+    protected TestEntity entity;
+
+    @Override
+    protected void setUpApp() {
+        super.setUpApp();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+    }
+
+
+    public void testManuallySettingIndicatorsOnEntities() {
+        // if we set a not up indicator, entity service up should become false
+        ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're pretending to block service up");
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false);
+        
+        // but state will not change unless we also set either a problem or expected state
+        assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, null);
+        ServiceProblemsLogic.updateProblemsIndicator(entity, INDICATOR_KEY_1, "We're pretending to block service state also");
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
+        // and if we clear the not up indicator, service up becomes true, but there is a problem, so it shows on-fire
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
+        // if we then clear the problem also, state goes to RUNNING
+        ServiceProblemsLogic.clearProblemsIndicator(entity, INDICATOR_KEY_1);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+        // now add not-up indicator again, and it reverts to up=false, state=stopped
+        ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're again pretending to block service up");
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
+        // but if we expect it to be running it will show on fire (because service is not up)
+        ServiceStateLogic.setExpectedState(entity, Lifecycle.RUNNING);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
+        // and if we again clear the not up indicator it will deduce up=true and state=running
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+    }
+
+    public void testAppStoppedAndEntityNullBeforeStarting() {
+        // AbstractApplication has default logic to ensure service is not up if it hasn't been started,
+        // (this can be removed by updating the problem indicator associated with the SERVICE_STATE_ACTUAL sensor)
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false);
+        // and from that it imputes stopped state
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
+        // TestEntity has no such indicators however
+        assertAttributeEquals(entity, Attributes.SERVICE_UP, null);
+        assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, null);
+    }
+    
+    public void testAllUpAndRunningAfterStart() {
+        app.start(ImmutableList.<Location>of());
+        
+        assertAttributeEquals(app, Attributes.SERVICE_UP, true);
+        assertAttributeEquals(entity, Attributes.SERVICE_UP, true);
+        // above should be immediate, entity should then derive RUNNING from expected state, and then so should app from children
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+    }
+    
+    public void testStopsNicelyToo() {
+        app.start(ImmutableList.<Location>of());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
+        app.stop();
+        
+        assertAttributeEquals(app, Attributes.SERVICE_UP, false);
+        assertAttributeEquals(entity, Attributes.SERVICE_UP, false);
+        // above should be immediate, app and entity should then derive STOPPED from the expected state
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+    }
+
+    public void testTwoIndicatorsAreBetterThanOne() {        
+        // if we set a not up indicator, entity service up should become false
+        ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're pretending to block service up");
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false);
+        ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_2, "We're also pretending to block service up");
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1);
+        // clearing one indicator is not sufficient
+        assertAttributeEquals(entity, Attributes.SERVICE_UP, false);
+        
+        // but it does not become true when both are cleared
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_2);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+    }
+
+    @Test(invocationCount=100, groups="Integration")
+    public void testManuallySettingIndicatorsOnApplicationsManyTimes() throws Exception {
+        testManuallySettingIndicatorsOnApplications();
+    }
+
+    public void testManuallySettingIndicatorsOnApplications() throws Exception {
+        // indicators on application are more complicated because it is configured with additional indicators from its children
+        // test a lot of situations, including reconfiguring some of the quorum config
+        
+        // to begin with, an entity has not reported anything, so the ComputeServiceIndicatorsFromChildren ignores it
+        // but the AbstractApplication has emitted a not-up indicator because it has not been started
+        // both as asserted by this other test:
+        testAppStoppedAndEntityNullBeforeStarting();
+        
+        // if we clear the not up indicator, the app will show as up, and as running, because it has no reporting children 
+        ServiceNotUpLogic.clearNotUpIndicator(app, Attributes.SERVICE_STATE_ACTUAL);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
+        // if we then put a not-up indicator on the TestEntity, it publishes false, but app is still up. State
+        // won't propagate due to it's SERVICE_STATE_ACTUAL (null) being in IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES
+        ServiceNotUpLogic.updateNotUpIndicator(entity, INDICATOR_KEY_1, "We're also pretending to block service up");
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false);
+        assertAttributeEqualsContinually(app, Attributes.SERVICE_UP, true);
+        assertAttributeEqualsContinually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        // the entity still has no opinion about its state
+        assertAttributeEqualsContinually(entity, Attributes.SERVICE_STATE_ACTUAL, null);
+        
+        // switching the entity state to one not in IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES will propagate the up state
+        ServiceStateLogic.setExpectedState(entity, Lifecycle.RUNNING);
+        assertAttributeEqualsContinually(entity, Attributes.SERVICE_UP, false);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+
+        
+        // if the entity expects to be stopped, it will report stopped
+        ServiceStateLogic.setExpectedState(entity, Lifecycle.STOPPED);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        // and the app will ignore the entity state, so becomes running
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true);
+        
+        // if we clear the not-up indicator, both the entity and the app report service up (with the entity first)
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1);
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, true);
+        // but entity is still stopped because that is what is expected there, and that's okay even if service is apparently up
+        assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        // the app however is running, because the default state quorum check is "all are healthy"
+        assertAttributeEqualsContinually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        assertAttributeEqualsContinually(app, Attributes.SERVICE_UP, true);
+        
+        // if we change the state quorum check for the app to be "all are healthy and at least one running" *then* it shows stopped
+        // (normally this would be done in `initEnrichers` of course)
+        Enricher appChildrenBasedEnricher = EntityAdjuncts.tryFindWithUniqueTag(app.getEnrichers(), ComputeServiceIndicatorsFromChildrenAndMembers.DEFAULT_UNIQUE_TAG).get();
+        appChildrenBasedEnricher.config().set(ComputeServiceIndicatorsFromChildrenAndMembers.RUNNING_QUORUM_CHECK, QuorumChecks.allAndAtLeastOne());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
+        // if entity is expected running, then it will show running because service is up; this is reflected at app and at entity
+        ServiceStateLogic.setExpectedState(entity, Lifecycle.RUNNING);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true);
+        assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        
+        // now, when the entity is unmanaged, the app goes on fire because don't have "at least one running"
+        Entities.unmanage(entity);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        // but UP_QUORUM_CHECK is still the default atLeastOneUnlessEmpty; so serviceUp=true
+        assertAttributeEqualsContinually(app, Attributes.SERVICE_UP, true);
+        
+        // if we change its up-quorum to atLeastOne then state becomes stopped (because there is no expected state; has not been started)
+        appChildrenBasedEnricher.config().set(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, QuorumChecks.atLeastOne());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false);
+        
+        // if we now start it will successfully start (because unlike entities it does not wait for service up) 
+        // but will remain down and will go on fire
+        app.start(ImmutableList.<Location>of());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
+        // restoring up-quorum to "atLeastOneUnlessEmpty" causes it to become RUNNING, because happy with empty
+        appChildrenBasedEnricher.config().set(ComputeServiceIndicatorsFromChildrenAndMembers.UP_QUORUM_CHECK, QuorumChecks.atLeastOneUnlessEmpty());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true);
+        // but running-quorum is still allAndAtLeastOne, so remains on-fire
+        assertAttributeEqualsContinually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
+        // now add a child, it's still up and running because null values are ignored by default (i.e. we're still "empty")
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        assertAttributeEqualsContinually(app, Attributes.SERVICE_UP, true);
+        assertAttributeEqualsContinually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        
+        // tell it not to ignore null values for children states, and it will go onfire (but still be service up)
+        appChildrenBasedEnricher.config().set(ComputeServiceIndicatorsFromChildrenAndMembers.IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES, 
+            ImmutableSet.<Lifecycle>of());
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+        assertAttributeEquals(app, Attributes.SERVICE_UP, true);
+        
+        // tell it not to ignore null values for service up and it will go service down
+        appChildrenBasedEnricher.config().set(ComputeServiceIndicatorsFromChildrenAndMembers.IGNORE_ENTITIES_WITH_SERVICE_UP_NULL, false);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, false);
+        
+        // set the entity to RUNNING and the app will be healthy again
+        ServiceNotUpLogic.clearNotUpIndicator(entity, INDICATOR_KEY_1);
+        ServiceStateLogic.setExpectedState(entity, Lifecycle.RUNNING);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true);
+        assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        assertAttributeEquals(entity, Attributes.SERVICE_UP, true);
+        assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+    }
+
+    @Test
+    public void testQuorumWithStringStates() {
+        final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+                .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(TestEntityWithoutEnrichers.class))
+                .configure(DynamicCluster.INITIAL_SIZE, 1));
+
+        cluster.start(ImmutableList.of(app.newSimulatedLocation()));
+        EntityTestUtils.assertGroupSizeEqualsEventually(cluster, 1);
+
+        //manually set state to healthy as enrichers are disabled
+        EntityInternal child = (EntityInternal) cluster.getMembers().iterator().next();
+        child.setAttribute(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+        child.setAttribute(Attributes.SERVICE_UP, Boolean.TRUE);
+
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+
+        //set untyped service state, the quorum check should be able to handle coercion
+        AttributeSensor<Object> stateSensor = Sensors.newSensor(Object.class, Attributes.SERVICE_STATE_ACTUAL.getName());
+        child.setAttribute(stateSensor, "running");
+
+        EntityTestUtils.assertAttributeEqualsContinually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
+    }
+
+    private static <T> void assertAttributeEqualsEventually(Entity x, AttributeSensor<T> sensor, T value) {
+        try {
+            EntityTestUtils.assertAttributeEqualsEventually(ImmutableMap.of("timeout", Duration.seconds(3)), x, sensor, value);
+        } catch (Throwable e) {
+            log.warn("Expected "+x+" eventually to have "+sensor+" = "+value+"; instead:");
+            Entities.dumpInfo(x);
+            throw Exceptions.propagate(e);
+        }
+    }
+    private static <T> void assertAttributeEqualsContinually(Entity x, AttributeSensor<T> sensor, T value) {
+        try {
+            EntityTestUtils.assertAttributeEqualsContinually(ImmutableMap.of("timeout", Duration.millis(25)), x, sensor, value);
+        } catch (Throwable e) {
+            log.warn("Expected "+x+" continually to have "+sensor+" = "+value+"; instead:");
+            Entities.dumpInfo(x);
+            throw Exceptions.propagate(e);
+        }
+    }
+    private static <T> void assertAttributeEquals(Entity x, AttributeSensor<T> sensor, T value) {
+        try {
+            EntityTestUtils.assertAttributeEquals(x, sensor, value);
+        } catch (Throwable e) {
+            log.warn("Expected "+x+" to have "+sensor+" = "+value+"; instead:");
+            Entities.dumpInfo(x);
+            throw Exceptions.propagate(e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c27cf1d0/core/src/test/java/org/apache/brooklyn/core/entity/proxying/ApplicationBuilderOverridingTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/proxying/ApplicationBuilderOverridingTest.java b/core/src/test/java/org/apache/brooklyn/core/entity/proxying/ApplicationBuilderOverridingTest.java
new file mode 100644
index 0000000..5a8003d
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/proxying/ApplicationBuilderOverridingTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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.core.entity.proxying;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.entity.AbstractEntity;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.objs.proxy.EntityProxy;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class ApplicationBuilderOverridingTest {
+
+    private static final long TIMEOUT_MS = 10*1000;
+    
+    private ManagementContext spareManagementContext;
+    private Application app;
+    private ExecutorService executor;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        spareManagementContext = new LocalManagementContextForTests();
+        executor = Executors.newCachedThreadPool();
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+        app = null;
+        if (spareManagementContext != null) Entities.destroyAll(spareManagementContext);
+        spareManagementContext = null;
+    }
+
+    @Test
+    public void testUsesDefaultBasicApplicationClass() {
+        app = new ApplicationBuilder() {
+            @Override public void doBuild() {}
+        }.manage();
+        
+        assertEquals(app.getEntityType().getName(), BasicApplication.class.getCanonicalName());
+        assertIsProxy(app);
+    }
+    
+    @Test
+    public void testUsesSuppliedApplicationClass() {
+        app = new ApplicationBuilder(EntitySpec.create(TestApplication.class)) {
+            @Override public void doBuild() {}
+        }.manage();
+        
+        assertEquals(app.getEntityType().getName(), TestApplication.class.getName());
+    }
+
+    @Test
+    public void testUsesSuppliedManagementContext() {
+        app = new ApplicationBuilder() {
+            @Override public void doBuild() {}
+        }.manage(spareManagementContext);
+        
+        assertEquals(app.getManagementContext(), spareManagementContext);
+    }
+
+    @Test
+    public void testCreatesChildEntity() {
+        final AtomicReference<TestEntity> expectedChild = new AtomicReference<TestEntity>();
+        app = new ApplicationBuilder() {
+            @Override public void doBuild() {
+                expectedChild.set(addChild(EntitySpec.create(TestEntity.class)));
+            }
+        }.manage();
+        
+        assertIsProxy(expectedChild.get());
+        assertEquals(ImmutableSet.copyOf(app.getChildren()), ImmutableSet.of(expectedChild.get()));
+        assertEquals(expectedChild.get().getParent(), app);
+    }
+
+    @Test
+    public void testAppHierarchyIsManaged() {
+        app = new ApplicationBuilder() {
+            @Override public void doBuild() {
+                Entity entity = addChild(EntitySpec.create(TestEntity.class));
+                assertFalse(getManagementContext().getEntityManager().isManaged(entity));
+            }
+        }.manage();
+        
+        assertIsManaged(app);
+        assertIsManaged(Iterables.get(app.getChildren(), 0));
+    }
+
+    @Test(expectedExceptions=IllegalStateException.class)
+    public void testRentrantCallToManageForbidden() {
+        ManagementContext secondManagementContext = new LocalManagementContext();
+        try {
+            app = new ApplicationBuilder() {
+                @Override public void doBuild() {
+                    manage(spareManagementContext);
+                }
+            }.manage(secondManagementContext);
+        } finally {
+            Entities.destroyAll(secondManagementContext);
+        }
+    }
+
+    @Test(expectedExceptions=IllegalStateException.class)
+    public void testMultipleCallsToManageForbidden() {
+        ApplicationBuilder appBuilder = new ApplicationBuilder() {
+            @Override public void doBuild() {
+            }
+        };
+        app = appBuilder.manage();
+        
+        appBuilder.manage(spareManagementContext);
+    }
+
+    @Test(expectedExceptions=IllegalStateException.class)
+    public void testCallToConfigureAfterManageForbidden() {
+        ApplicationBuilder appBuilder = new ApplicationBuilder() {
+            @Override public void doBuild() {
+            }
+        };
+        app = appBuilder.manage();
+        appBuilder.configure(ImmutableMap.of());
+    }
+
+    @Test(expectedExceptions=IllegalStateException.class)
+    public void testCallToSetDisplayNameAfterManageForbidden() {
+        ApplicationBuilder appBuilder = new ApplicationBuilder() {
+            @Override public void doBuild() {
+            }
+        };
+        app = appBuilder.manage(spareManagementContext);
+        appBuilder.appDisplayName("myname");
+    }
+
+    @Test
+    public void testConcurrentCallToManageForbidden() throws Exception {
+        final CountDownLatch inbuildLatch = new CountDownLatch(1);
+        final CountDownLatch continueLatch = new CountDownLatch(1);
+        final ApplicationBuilder builder = new ApplicationBuilder() {
+            @Override public void doBuild() {
+                try {
+                    inbuildLatch.countDown();
+                    continueLatch.await();
+                } catch (InterruptedException e) {
+                    throw Exceptions.propagate(e);
+                }
+            }
+        };
+        Future<StartableApplication> future = executor.submit(new Callable<StartableApplication>() {
+            public StartableApplication call() {
+                return builder.manage();
+            }
+        });
+        
+        inbuildLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        
+        try {
+            app = builder.manage(spareManagementContext);
+            fail();
+        } catch (IllegalStateException e) {
+            // expected
+        }
+        
+        continueLatch.countDown();
+        app = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    }
+
+    private void assertIsProxy(Entity e) {
+        assertFalse(e instanceof AbstractEntity, "e="+e+";e.class="+e.getClass());
+        assertTrue(e instanceof EntityProxy, "e="+e+";e.class="+e.getClass());
+    }
+    
+    private void assertIsManaged(Entity e) {
+        assertTrue(((EntityInternal)e).getManagementSupport().isDeployed(), "e="+e);
+    }
+}



Mime
View raw message