You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2016/01/13 19:06:01 UTC

[2/6] incubator-brooklyn git commit: Added LoopOverGroupMembersTestCase. Removed some targeting functionality from BaseTest and added it to a new Interface called TargetabeTestComponent Made TestCase and ParallelTestCase TargetabeTestComponent Infrastruc

Added LoopOverGroupMembersTestCase. Removed some targeting functionality from BaseTest and added it to a new Interface called TargetabeTestComponent
Made TestCase and ParallelTestCase TargetabeTestComponent
InfrastructureDeploymentTestCaseImpl can now deploy multiple specs
TestEffector will now check assertions
InfrastructureDeploymentTestCaseImpl made more resilient


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/74a22487
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/74a22487
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/74a22487

Branch: refs/heads/master
Commit: 74a22487a6359bc2c39ae4590bc1496d1357121a
Parents: 7bcb392
Author: Graeme-Miller <gr...@cloudsoftcorp.com>
Authored: Mon Dec 14 16:32:38 2015 +0000
Committer: Graeme-Miller <gr...@cloudsoftcorp.com>
Committed: Thu Jan 7 16:44:39 2016 +0000

----------------------------------------------------------------------
 .../brooklyn/test/framework/AbstractTest.java   |  77 -----
 .../brooklyn/test/framework/BaseTest.java       |  31 +-
 .../InfrastructureDeploymentTestCase.java       |  11 +-
 .../InfrastructureDeploymentTestCaseImpl.java   |  52 +++-
 .../test/framework/ParallelTestCase.java        |   4 +-
 .../test/framework/ParallelTestCaseImpl.java    |  10 +-
 .../test/framework/SimpleShellCommandTest.java  |  12 +-
 .../framework/SimpleShellCommandTestImpl.java   |  39 +--
 .../brooklyn/test/framework/TestCase.java       |   4 +-
 .../brooklyn/test/framework/TestCaseImpl.java   |  15 +-
 .../test/framework/TestEffectorImpl.java        |  40 ++-
 .../test/framework/TestFrameworkAssertions.java |  11 +-
 .../test/framework/TestHttpCallImpl.java        |  27 +-
 .../brooklyn/test/framework/TestSensorImpl.java |  21 +-
 .../test/framework/TestEffectorTest.java        |  65 +++++
 .../test/framework/entity/TestEntity.java       |   3 +
 .../test/framework/entity/TestEntityImpl.java   |   5 +
 .../framework/LoopOverGroupMembersTestCase.java |  45 +++
 .../LoopOverGroupMembersTestCaseImpl.java       | 134 +++++++++
 .../test/framework/TargetableTestComponent.java |  53 ++++
 .../framework/TargetableTestComponentImpl.java  |  83 ++++++
 .../InfrastructureDeploymentTestCaseTest.java   | 267 +++++++++++++++++
 .../LoopOverGroupMembersTestCaseTest.java       | 286 +++++++++++++++++++
 .../framework/entity/TestInfrastructure.java    |  31 ++
 .../entity/TestInfrastructureImpl.java          |  46 +++
 25 files changed, 1180 insertions(+), 192 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java
deleted file mode 100644
index 434be8e..0000000
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.mgmt.ExecutionContext;
-import org.apache.brooklyn.api.mgmt.Task;
-import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent;
-import org.apache.brooklyn.core.entity.AbstractEntity;
-import org.apache.brooklyn.util.core.task.Tasks;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.ExecutionException;
-
-/**
- * Abstract base class for tests, providing common target lookup.
- */
-public abstract class AbstractTest extends AbstractEntity implements BaseTest {
-
-    private static final Logger LOG = LoggerFactory.getLogger(AbstractTest.class);
-
-    /**
-     * Find the target entity using "target" config key, if entity provided directly in config, or by doing an implicit
-     * lookup using DSL ($brooklyn:component("myNginX")), if id of entity provided as "targetId" config key.
-     *
-     * @return The target entity.
-     * @throws @RuntimeException if no target can be determined.
-     */
-    public Entity resolveTarget() {
-        return resolveTarget(getExecutionContext(), this);
-    }
-
-    /**
-     * Find the target entity in the given execution context.
-     *
-     * @see {@link #resolveTarget()}.
-     */
-    public static Entity resolveTarget(ExecutionContext executionContext, Entity entity) {
-        Entity target = entity.getConfig(TARGET_ENTITY);
-        if (null == target) {
-            target = getTargetById(executionContext, entity);
-        }
-        return target;
-    }
-
-    private static Entity getTargetById(ExecutionContext executionContext, Entity entity) {
-        String targetId = entity.getConfig(TARGET_ID);
-        final Task<Entity> targetLookup = new DslComponent(targetId).newTask();
-        Entity target = null;
-        try {
-            target = Tasks.resolveValue(targetLookup, Entity.class, executionContext, "Finding entity " + targetId);
-            LOG.debug("Found target by id {}", targetId);
-        } catch (final ExecutionException | InterruptedException e) {
-            LOG.error("Error finding target {}", targetId);
-            Exceptions.propagate(e);
-        }
-        return target;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
index a97b9db..f5eb30b 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
@@ -18,34 +18,20 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
 import com.google.common.collect.ImmutableList;
-import com.google.common.reflect.TypeToken;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.mgmt.ExecutionContext;
+
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.util.time.Duration;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
 /**
  * A base interface for all tests.
  */
-public interface BaseTest extends Entity, Startable {
-
-    /**
-     * The target entity to test (optional, use either this or targetId).
-     */
-    ConfigKey<Entity> TARGET_ENTITY = ConfigKeys.newConfigKey(Entity.class, "target", "Entity under test");
-
-    /**
-     * Id of the target entity to test (optional, use either this or target).
-     */
-    ConfigKey<String> TARGET_ID = ConfigKeys.newStringConfigKey("targetId", "Id of the entity under test");
+public interface BaseTest extends TargetableTestComponent, Startable {
 
     /**
      * The assertions to be made.
@@ -59,12 +45,5 @@ public interface BaseTest extends Entity, Startable {
     ConfigKey<Duration> TIMEOUT = ConfigKeys.newConfigKey(Duration.class, "timeout", "Time to wait on result",
         new Duration(1L, TimeUnit.SECONDS));
 
-    /**
-     * Get the target of the test.
-     *
-     * @return The target.
-     * @throws IllegalArgumentException if the target cannot be found.
-     */
-    Entity resolveTarget();
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCase.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCase.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCase.java
index 5f368df..a3ffa53 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCase.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCase.java
@@ -18,7 +18,10 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.List;
+
 import com.google.common.reflect.TypeToken;
+
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.config.ConfigKey;
@@ -30,19 +33,19 @@ import org.apache.brooklyn.entity.software.base.SoftwareProcess;
  * Created by graememiller on 04/12/2015.
  */
 @ImplementedBy(value = InfrastructureDeploymentTestCaseImpl.class)
-public interface InfrastructureDeploymentTestCase extends TestCase {
+public interface InfrastructureDeploymentTestCase extends TargetableTestComponent {
 
     /**
-     * Entity spec to deploy. This will be deployed second, after the INFRASTRUCTURE_SPEC has been deployed. This will be deployed to the DEPLOYMENT_LOCATION.
+     * Entity specs to deploy. These will be deployed second, after the INFRASTRUCTURE_SPEC has been deployed. These specs will be deployed to the DEPLOYMENT_LOCATION.
      * All children will be deployed after this
      */
-    ConfigKey<EntitySpec<SoftwareProcess>> ENTITY_SPEC_TO_DEPLOY = ConfigKeys.newConfigKey(new TypeToken<EntitySpec<SoftwareProcess>>(){}, "infrastructure.deployment.entity.spec", "Entity spec to deploy to infrastructure");
+    ConfigKey<List<EntitySpec<? extends SoftwareProcess>>> ENTITY_SPEC_TO_DEPLOY = ConfigKeys.newConfigKey(new TypeToken<List<EntitySpec<? extends SoftwareProcess>>>(){}, "infrastructure.deployment.entity.specs", "Entity specs to deploy to infrastructure");
 
 
     /**
      * Infrastructure to deploy. This will be deployed first, then the ENTITY_SPEC_TO_DEPLOY will be deployed, then any children
      */
-    ConfigKey<EntitySpec<StartableApplication>> INFRASTRUCTURE_SPEC = ConfigKeys.newConfigKey(new TypeToken<EntitySpec<StartableApplication>>(){}, "infrastructure.deployment.spec", "Infrastructure to deploy");
+    ConfigKey<EntitySpec<? extends StartableApplication>> INFRASTRUCTURE_SPEC = ConfigKeys.newConfigKey(new TypeToken<EntitySpec<? extends StartableApplication>>(){}, "infrastructure.deployment.spec", "Infrastructure to deploy");
 
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseImpl.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseImpl.java
index 900c0a0..55f3e63 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseImpl.java
@@ -18,16 +18,20 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.Collection;
+import java.util.List;
+
 import com.google.common.collect.ImmutableList;
+
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.core.annotation.EffectorParam;
+import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.entity.software.base.SoftwareProcess;
 
-import java.util.Collection;
-
 /**
  * Created by graememiller on 04/12/2015.
  */
@@ -35,23 +39,55 @@ public class InfrastructureDeploymentTestCaseImpl extends TestCaseImpl implement
 
     @Override
     public void start(@EffectorParam(name = "locations") Collection<? extends Location> locations) {
+        setServiceState(false, Lifecycle.STARTING);
+
         //Create the infrastructure
-        EntitySpec<StartableApplication> infrastructureSpec = config().get(INFRASTRUCTURE_SPEC);
+        EntitySpec<? extends StartableApplication> infrastructureSpec = config().get(INFRASTRUCTURE_SPEC);
+        if (infrastructureSpec == null) {
+            setServiceState(false, Lifecycle.ON_FIRE);
+            throw new IllegalArgumentException(INFRASTRUCTURE_SPEC + " not configured");
+        }
+
         StartableApplication infrastructure = this.addChild(infrastructureSpec);
         infrastructure.start(locations);
 
         //Get the location
         String deploymentLocationSensorName = config().get(DEPLOYMENT_LOCATION_SENSOR_NAME);
-        Location locationToDeployTo = infrastructure.sensors().get(Sensors.newSensor(Location.class, deploymentLocationSensorName));
+        if (deploymentLocationSensorName == null) {
+            setServiceState(false, Lifecycle.ON_FIRE);
+            throw new IllegalArgumentException(DEPLOYMENT_LOCATION_SENSOR_NAME + " not configured");
+        }
 
+        Location locationToDeployTo = infrastructure.sensors().get(Sensors.newSensor(Location.class, deploymentLocationSensorName));
+        if (locationToDeployTo == null) {
+            setServiceState(false, Lifecycle.ON_FIRE);
+            throw new IllegalArgumentException("Infrastructure does not have a location configured on sensor "+deploymentLocationSensorName);
+        }
 
         //Start the child entity
-        EntitySpec<SoftwareProcess> entityToDeploySpec = config().get(ENTITY_SPEC_TO_DEPLOY);
-        SoftwareProcess entityToDeploy = this.addChild(entityToDeploySpec);
-        entityToDeploy.start(ImmutableList.of(locationToDeployTo));
-
+        List<EntitySpec<? extends SoftwareProcess>> entitySpecsToDeploy = config().get(ENTITY_SPEC_TO_DEPLOY);
+        if (entitySpecsToDeploy == null || entitySpecsToDeploy.isEmpty()) {
+            setServiceState(false, Lifecycle.ON_FIRE);
+            throw new IllegalArgumentException(ENTITY_SPEC_TO_DEPLOY + " not configured");
+        }
+        for (EntitySpec<? extends SoftwareProcess> softwareProcessEntitySpec : entitySpecsToDeploy) {
+            SoftwareProcess entityToDeploy = this.addChild(softwareProcessEntitySpec);
+            entityToDeploy.start(ImmutableList.of(locationToDeployTo));
+        }
 
         //Defer to super class to start children
         super.start(locations);
+        setServiceState(true, Lifecycle.RUNNING);
+    }
+
+    /**
+     * Sets the state of the Entity. Useful so that the GUI shows the correct icon.
+     *
+     * @param serviceUpState     Whether or not the entity is up.
+     * @param serviceStateActual The actual state of the entity.
+     */
+    private void setServiceState(final boolean serviceUpState, final Lifecycle serviceStateActual) {
+        sensors().set(Attributes.SERVICE_UP, serviceUpState);
+        sensors().set(Attributes.SERVICE_STATE_ACTUAL, serviceStateActual);
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCase.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCase.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCase.java
index 63fe60f..96f54b8 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCase.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCase.java
@@ -18,9 +18,7 @@
  */
 package org.apache.brooklyn.test.framework;
 
-import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.ImplementedBy;
-import org.apache.brooklyn.core.entity.trait.Startable;
 
 /**
  * This implementation will start all child entities in parallel.
@@ -28,5 +26,5 @@ import org.apache.brooklyn.core.entity.trait.Startable;
  * @author Chris Burke
  */
 @ImplementedBy(value = ParallelTestCaseImpl.class)
-public interface ParallelTestCase extends Entity, Startable {
+public interface ParallelTestCase extends TargetableTestComponent {
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCaseImpl.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCaseImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCaseImpl.java
index 2fcd83c..469bc3d 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCaseImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCaseImpl.java
@@ -20,23 +20,23 @@ package org.apache.brooklyn.test.framework;
 
 import java.util.Collection;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.mgmt.TaskAdaptable;
-import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.entity.trait.StartableMethods;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * This implementation will start all child entities in parallel.
  * 
  * @author Chris Burke
  */
-public class ParallelTestCaseImpl extends AbstractEntity implements ParallelTestCase {
+public class ParallelTestCaseImpl extends TargetableTestComponentImpl implements ParallelTestCase {
 
     private static final Logger logger = LoggerFactory.getLogger(ParallelTestCaseImpl.class);
 
@@ -136,7 +136,7 @@ public class ParallelTestCaseImpl extends AbstractEntity implements ParallelTest
      * @param serviceStateActual The actual state of the entity.
      */
     private void setServiceState(final boolean serviceUpState, final Lifecycle serviceStateActual) {
-        sensors().set(SERVICE_UP, serviceUpState);
+        sensors().set(Attributes.SERVICE_UP, serviceUpState);
         sensors().set(Attributes.SERVICE_STATE_ACTUAL, serviceStateActual);
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
index f9ffda6..abd0aea 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
@@ -18,9 +18,13 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKey;
+
+import java.util.Map;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.reflect.TypeToken;
+
 import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
@@ -28,12 +32,6 @@ import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
 import org.apache.brooklyn.entity.software.base.SoftwareProcess;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKey;
-
 /**
  * Tests using a simple command execution.
  */

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
index 6f2ef12..f523893 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
@@ -18,21 +18,41 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.ON_FIRE;
+import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.RUNNING;
+import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.STARTING;
+import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.STOPPED;
+import static org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.setExpectedState;
+import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.checkAssertions;
+import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
+import static org.apache.brooklyn.util.text.Strings.isBlank;
+import static org.apache.brooklyn.util.text.Strings.isNonBlank;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Splitter;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableMap;
+
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.mgmt.TaskFactory;
-import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.location.Machines;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.framework.TestFrameworkAssertions.AssertionSupport;
 import org.apache.brooklyn.util.collections.MutableList;
-import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.ssh.SshTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
@@ -40,22 +60,9 @@ import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.*;
-
-import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.*;
-import static org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.setExpectedState;
-import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.checkAssertions;
-import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
-import static org.apache.brooklyn.util.text.Strings.isBlank;
-import static org.apache.brooklyn.util.text.Strings.isNonBlank;
 
 // TODO assertions below should use TestFrameworkAssertions but that class needs to be improved to give better error messages
-public class SimpleShellCommandTestImpl extends AbstractTest implements SimpleShellCommandTest {
+public class SimpleShellCommandTestImpl extends TargetableTestComponentImpl implements SimpleShellCommandTest {
 
     private static final Logger LOG = LoggerFactory.getLogger(SimpleShellCommandTestImpl.class);
     private static final int A_LINE = 80;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
index 34a2e34..b6d6f61 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
@@ -18,9 +18,7 @@
  */
 package org.apache.brooklyn.test.framework;
 
-import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.ImplementedBy;
-import org.apache.brooklyn.core.entity.trait.Startable;
 
 /**
  * Entity that logically groups other test entities
@@ -28,5 +26,5 @@ import org.apache.brooklyn.core.entity.trait.Startable;
  * @author m4rkmckenna
  */
 @ImplementedBy(value = TestCaseImpl.class)
-public interface TestCase extends Entity, Startable {
+public interface TestCase extends TargetableTestComponent {
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCaseImpl.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCaseImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCaseImpl.java
index 1508778..eb3a04e 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCaseImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCaseImpl.java
@@ -18,24 +18,25 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.Collection;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.collect.Lists;
+
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collection;
 
 /**
  * {@inheritDoc}
  */
-public class TestCaseImpl extends AbstractEntity implements TestCase {
+public class TestCaseImpl extends TargetableTestComponentImpl implements TestCase {
 
     private static final Logger LOG = LoggerFactory.getLogger(TestCaseImpl.class);
 
@@ -46,7 +47,7 @@ public class TestCaseImpl extends AbstractEntity implements TestCase {
         ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
         try {
             for (final Entity childEntity : getChildren()) {
-                Boolean serviceUp = childEntity.sensors().get(SERVICE_UP);
+                Boolean serviceUp = childEntity.sensors().get(Attributes.SERVICE_UP);
                 if (childEntity instanceof Startable && !Boolean.TRUE.equals(serviceUp)){
                     ((Startable) childEntity).start(locations);
                 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
index 817ef6a..c00bef2 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
@@ -18,7 +18,19 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+
 import org.apache.brooklyn.api.effector.Effector;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
@@ -30,16 +42,11 @@ import org.apache.brooklyn.core.mgmt.internal.EffectorUtils;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collection;
-import java.util.Map;
 
 /**
  *
  */
-public class TestEffectorImpl extends AbstractTest implements TestEffector {
+public class TestEffectorImpl extends TargetableTestComponentImpl implements TestEffector {
     private static final Logger LOG = LoggerFactory.getLogger(TestEffectorImpl.class);
 
 
@@ -60,14 +67,27 @@ public class TestEffectorImpl extends AbstractTest implements TestEffector {
             if (effector.isAbsentOrNull()) {
                 throw new AssertionError(String.format("No effector with name [%s]", effectorName));
             }
-            final Task<?> effectorResult;
+            final Task<?> effectorTask;
             if (effectorParams == null || effectorParams.isEmpty()) {
-                effectorResult = Entities.invokeEffector(this, targetEntity, effector.get());
+                effectorTask = Entities.invokeEffector(this, targetEntity, effector.get());
             } else {
-                effectorResult = Entities.invokeEffector(this, targetEntity, effector.get(), effectorParams);
+                effectorTask = Entities.invokeEffector(this, targetEntity, effector.get(), effectorParams);
             }
+
+            final Object effectorResult = effectorTask.get(timeout);
+
+            final List<Map<String, Object>> assertions = getAssertions(this, ASSERTIONS);
+            if(assertions != null && !assertions.isEmpty()){
+                TestFrameworkAssertions.checkAssertions(ImmutableMap.of("timeout", timeout), assertions, effectorName, new Supplier<String>() {
+                    @Override
+                    public String get() {
+                        return (String)effectorResult;
+                    }
+                });
+            }
+
             //Add result of effector to sensor
-            sensors().set(EFFECTOR_RESULT, effectorResult.get(timeout));
+            sensors().set(EFFECTOR_RESULT, effectorResult);
             sensors().set(SERVICE_UP, true);
             ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
         } catch (Throwable t) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java
index 0e61419..3c00020 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java
@@ -18,9 +18,15 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
 import com.google.common.base.Joiner;
 import com.google.common.base.Supplier;
 import com.google.common.reflect.TypeToken;
+
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.test.Asserts;
@@ -30,11 +36,6 @@ import org.apache.brooklyn.util.exceptions.FatalConfigurationRuntimeException;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Strings;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
 
 /**
  * Utility class to evaluate test-framework assertions

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
index 67451ed..e3e4456 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
@@ -18,28 +18,31 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.http.HttpTool;
 import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
 
 /**
  * {@inheritDoc}
  */
-public class TestHttpCallImpl extends AbstractTest implements TestHttpCall {
+public class TestHttpCallImpl extends TargetableTestComponentImpl implements TestHttpCall {
 
     private static final Logger LOG = LoggerFactory.getLogger(TestHttpCallImpl.class);
 
@@ -58,12 +61,12 @@ public class TestHttpCallImpl extends AbstractTest implements TestHttpCall {
 
         try {
             doRequestAndCheckAssertions(ImmutableMap.of("timeout", timeout), assertions, target, url);
-            sensors().set(SERVICE_UP, true);
+            sensors().set(Attributes.SERVICE_UP, true);
             ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
 
         } catch (Throwable t) {
             LOG.info("{} Url [{}] test failed", this, url);
-            sensors().set(SERVICE_UP, false);
+            sensors().set(Attributes.SERVICE_UP, false);
             ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(t);
         }
@@ -105,7 +108,7 @@ public class TestHttpCallImpl extends AbstractTest implements TestHttpCall {
      */
     public void stop() {
         ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
-        sensors().set(SERVICE_UP, false);
+        sensors().set(Attributes.SERVICE_UP, false);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
index d042991..ff2596e 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
@@ -18,11 +18,22 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.api.client.util.Objects;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
@@ -31,19 +42,11 @@ import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
 
 /**
  * {@inheritDoc}
  */
-public class TestSensorImpl extends AbstractTest implements TestSensor {
+public class TestSensorImpl extends TargetableTestComponentImpl implements TestSensor {
 
     private static final Logger LOG = LoggerFactory.getLogger(TestSensorImpl.class);
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
index b6f3c4a..d0c6b8c 100644
--- a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
+++ b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
@@ -28,13 +28,19 @@ import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.test.entity.TestApplication;
 import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
 import org.apache.brooklyn.test.framework.entity.TestEntity;
+import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import static org.apache.brooklyn.core.entity.trait.Startable.SERVICE_UP;
+import static org.apache.brooklyn.test.Asserts.fail;
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.util.List;
+import java.util.Map;
+
 /**
  * @author m4rkmckenna on 27/10/2015.
  */
@@ -83,6 +89,65 @@ public class TestEffectorTest {
     }
 
     @Test
+    public void testEffectorPositiveAssertions() {
+        final TestCase testCase = app.createAndManageChild(EntitySpec.create(TestCase.class));
+        final TestEntity testEntity = testCase.addChild(EntitySpec.create(TestEntity.class));
+
+        String stringToReturn = "Hello World!";
+
+        Map<String, String> effectorParams = ImmutableMap.of("stringToReturn", stringToReturn);
+
+        List<Map<String, Object>> assertions = ImmutableList.<Map<String, Object>>of(
+                ImmutableMap.<String, Object>of(TestFrameworkAssertions.EQUAL_TO, stringToReturn),
+                ImmutableMap.<String, Object>of(TestFrameworkAssertions.CONTAINS, "Hello")
+        );
+
+        final TestEffector testEffector = testCase.addChild(EntitySpec.create(TestEffector.class)
+                .configure(TestEffector.TARGET_ENTITY, testEntity)
+                .configure(TestEffector.EFFECTOR_NAME, "effectorReturnsString")
+                .configure(TestEffector.EFFECTOR_PARAMS, effectorParams)
+                .configure(TestEffector.ASSERTIONS, assertions));
+
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+
+        assertThat(testEffector.sensors().get(TestEffector.EFFECTOR_RESULT)).isEqualTo(stringToReturn);
+        assertThat(testEffector.sensors().get(SERVICE_UP)).isTrue().withFailMessage("Service should be up");
+    }
+
+    @Test
+    public void testEffectorNegativeAssertions() {
+        final TestCase testCase = app.createAndManageChild(EntitySpec.create(TestCase.class));
+        final TestEntity testEntity = testCase.addChild(EntitySpec.create(TestEntity.class));
+
+        String stringToReturn = "Goodbye World!";
+
+        Map<String, String> effectorParams = ImmutableMap.of("stringToReturn", stringToReturn);
+
+        List<Map<String, Object>> assertions = ImmutableList.<Map<String, Object>>of(
+                ImmutableMap.<String, Object>of(TestFrameworkAssertions.EQUAL_TO, "Not the string I expected"),
+                ImmutableMap.<String, Object>of(TestFrameworkAssertions.CONTAINS, "Hello")
+        );
+
+        final TestEffector testEffector = testCase.addChild(EntitySpec.create(TestEffector.class)
+                .configure(TestEffector.TARGET_ENTITY, testEntity)
+                .configure(TestEffector.EFFECTOR_NAME, "effectorReturnsString")
+                .configure(TestEffector.EFFECTOR_PARAMS, effectorParams)
+                .configure(TestEffector.ASSERTIONS, assertions));
+
+        try {
+            app.start(ImmutableList.of(app.newSimulatedLocation()));
+            fail("Should have thrown execption");
+        } catch (Throwable throwable) {
+            Throwable firstInteresting = Exceptions.getFirstInteresting(throwable);
+            assertThat(firstInteresting).isNotNull();
+            assertThat(throwable).isNotNull();
+            assertThat(firstInteresting).isInstanceOf(AssertionError.class);
+        }
+
+        assertThat(testEffector.sensors().get(SERVICE_UP)).isFalse().withFailMessage("Service should not be up");
+    }
+
+    @Test
     public void testComplexffector() {
         final TestCase testCase = app.createAndManageChild(EntitySpec.create(TestCase.class));
         final TestEntity testEntity = testCase.addChild(EntitySpec.create(TestEntity.class));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntity.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntity.java b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntity.java
index a0abcf1..4de9587 100644
--- a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntity.java
+++ b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntity.java
@@ -48,6 +48,9 @@ public interface TestEntity extends Entity, Startable {
                              @EffectorParam(name = "booleanValue") final Boolean booleanValue,
                              @EffectorParam(name = "longValue") final Long longValue);
 
+    @Effector
+    String effectorReturnsString(@EffectorParam(name = "stringToReturn") final String stringToReturn);
+
     class TestPojo {
         private final String stringValue;
         private final Boolean booleanValue;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntityImpl.java
----------------------------------------------------------------------
diff --git a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntityImpl.java b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntityImpl.java
index 50ef967..7f066a7 100644
--- a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntityImpl.java
+++ b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntityImpl.java
@@ -56,4 +56,9 @@ public class TestEntityImpl extends AbstractEntity implements TestEntity {
         sensors().set(COMPLEX_EFFECTOR_LONG, longValue);
         return new TestPojo(stringValue, booleanValue, longValue);
     }
+
+    @Override
+    public String effectorReturnsString(@EffectorParam(name = "stringToReturn") String stringToReturn) {
+        return stringToReturn;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCase.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCase.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCase.java
new file mode 100644
index 0000000..4e76604
--- /dev/null
+++ b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCase.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import com.google.common.reflect.TypeToken;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
+/**
+ * Created by graememiller on 11/12/2015.
+ */
+@ImplementedBy(value = LoopOverGroupMembersTestCaseImpl.class)
+public interface LoopOverGroupMembersTestCase extends TargetableTestComponent {
+
+    /**
+     * The test spec that will be run against each member of the group
+     */
+    @SetFromFlag("testSpec")
+    ConfigKey<EntitySpec<? extends TargetableTestComponent>> TEST_SPEC = ConfigKeys.newConfigKey(
+            new TypeToken<EntitySpec<? extends TargetableTestComponent>>(){},
+            "test.spec",
+            "Test spec. The test case will create one of these per child");
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCaseImpl.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCaseImpl.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCaseImpl.java
new file mode 100644
index 0000000..f4bb06d
--- /dev/null
+++ b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCaseImpl.java
@@ -0,0 +1,134 @@
+/*
+ * 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;
+
+import java.util.Collection;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.annotation.EffectorParam;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+
+/**
+ * Created by graememiller on 11/12/2015.
+ */
+public class LoopOverGroupMembersTestCaseImpl extends TargetableTestComponentImpl implements LoopOverGroupMembersTestCase {
+
+    private static final Logger logger = LoggerFactory.getLogger(LoopOverGroupMembersTestCaseImpl.class);
+
+    @Override
+    public void start(@EffectorParam(name = "locations") Collection<? extends Location> locations) {
+        // Let everyone know we're starting up (so that the GUI shows the correct icon).
+        sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STARTING);
+
+        Entity target = resolveTarget();
+        if (target == null) {
+            logger.debug("Tasks NOT successfully run. LoopOverGroupMembersTestCaseImpl group not set");
+            setServiceState(false, Lifecycle.ON_FIRE);
+            return;
+        }
+
+        if (!(target instanceof Group)) {
+            logger.debug("Tasks NOT successfully run. LoopOverGroupMembersTestCaseImpl target is not a group");
+            setServiceState(false, Lifecycle.ON_FIRE);
+            return;
+        }
+
+        EntitySpec<? extends TargetableTestComponent> testSpec = config().get(TEST_SPEC);
+        if (testSpec == null) {
+            logger.debug("Tasks NOT successfully run. LoopOverGroupMembersTestCaseImpl test spec not set");
+            setServiceState(false, Lifecycle.ON_FIRE);
+            return;
+        }
+
+        Group group = (Group) target;
+
+        Collection<Entity> children = group.getMembers();
+        boolean allSuccesful = true;
+        for (Entity child : children) {
+            testSpec.configure(TestCase.TARGET_ENTITY, child);
+
+            try {
+                TargetableTestComponent targetableTestComponent = this.addChild(testSpec);
+                targetableTestComponent.start(locations);
+            } catch (Throwable t) {
+                allSuccesful = false;
+            }
+        }
+
+        if (allSuccesful) {
+            // Let everyone know we've started up successfully (changes the icon in the GUI).
+            logger.debug("Tasks successfully run. Update state of {} to RUNNING.", this);
+            setServiceState(true, Lifecycle.RUNNING);
+        } else {
+            // Let everyone know we've npt started up successfully (changes the icon in the GUI).
+            logger.debug("Tasks NOT successfully run. Update state of {} to ON_FIRE.", this);
+            setServiceState(false, Lifecycle.ON_FIRE);
+        }
+
+    }
+
+    @Override
+    public void stop() {
+        // Let everyone know we're stopping (so that the GUI shows the correct icon).
+        sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPING);
+
+        try {
+            for (Entity child : this.getChildren()) {
+                if (child instanceof Startable) ((Startable) child).stop();
+            }
+
+            // Let everyone know we've stopped successfully (changes the icon in the GUI).
+            logger.debug("Tasks successfully run. Update state of {} to STOPPED.", this);
+            setServiceState(false, Lifecycle.STOPPED);
+        } catch (Throwable t) {
+            logger.debug("Tasks NOT successfully run. Update state of {} to ON_FIRE.", this);
+            setServiceState(false, Lifecycle.ON_FIRE);
+            throw Exceptions.propagate(t);
+        }
+    }
+
+    @Override
+    public void restart() {
+        final Collection<Location> locations = Lists.newArrayList(getLocations());
+        stop();
+        start(locations);
+    }
+
+    /**
+     * Sets the state of the Entity. Useful so that the GUI shows the correct icon.
+     *
+     * @param serviceUpState     Whether or not the entity is up.
+     * @param serviceStateActual The actual state of the entity.
+     */
+    private void setServiceState(final boolean serviceUpState, final Lifecycle serviceStateActual) {
+        sensors().set(SERVICE_UP, serviceUpState);
+        sensors().set(Attributes.SERVICE_STATE_ACTUAL, serviceStateActual);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java
new file mode 100644
index 0000000..8ae44ad
--- /dev/null
+++ b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+
+/**
+ * Entity that can target another entity for the purpouse of testing
+ *
+ * @author m4rkmckenna
+ */
+@ImplementedBy(value = TargetableTestComponentImpl.class)
+public interface TargetableTestComponent extends Entity, Startable {
+
+    /**
+     * The target entity to test (optional, use either this or targetId).
+     */
+    AttributeSensorAndConfigKey<Entity, Entity> TARGET_ENTITY = ConfigKeys.newSensorAndConfigKey(Entity.class, "target", "Entity under test");
+
+    /**
+     * Id of the target entity to test (optional, use either this or target).
+     */
+    AttributeSensorAndConfigKey<String, String> TARGET_ID = ConfigKeys.newStringSensorAndConfigKey("targetId", "Id of the entity under test");
+
+    /**
+     * Get the target of the test.
+     *
+     * @return The target.
+     * @throws IllegalArgumentException if the target cannot be found.
+     */
+    Entity resolveTarget();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
new file mode 100644
index 0000000..5b133bd
--- /dev/null
+++ b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import java.util.concurrent.ExecutionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ExecutionContext;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent;
+import org.apache.brooklyn.core.entity.AbstractEntity;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+
+/**
+ * Class that can resolve the target for a test component
+ */
+public abstract class TargetableTestComponentImpl extends AbstractEntity implements TargetableTestComponent {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TargetableTestComponentImpl.class);
+
+    /**
+     * Find the target entity using "target" config key, if entity provided directly in config, or by doing an implicit
+     * lookup using DSL ($brooklyn:component("myNginX")), if id of entity provided as "targetId" config key.
+     *
+     * @return The target entity.
+     * @throws @RuntimeException if no target can be determined.
+     */
+    public Entity resolveTarget() {
+        return resolveTarget(getExecutionContext(), this);
+    }
+
+    /**
+     * Find the target entity in the given execution context.
+     *
+     * @see {@link #resolveTarget()}.
+     */
+    public static Entity resolveTarget(ExecutionContext executionContext, Entity entity) {
+        Entity target = entity.getConfig(TARGET_ENTITY);
+        if (null == target) {
+            target = getTargetById(executionContext, entity);
+        }
+        return target;
+    }
+
+    private static Entity getTargetById(ExecutionContext executionContext, Entity entity) {
+        String targetId = entity.getConfig(TARGET_ID);
+
+        if(targetId == null){
+            return null;
+        }
+
+        final Task<Entity> targetLookup = new DslComponent(targetId).newTask();
+        Entity target = null;
+        try {
+            target = Tasks.resolveValue(targetLookup, Entity.class, executionContext, "Finding entity " + targetId);
+            LOG.debug("Found target by id {}", targetId);
+        } catch (final ExecutionException | InterruptedException e) {
+            LOG.error("Error finding target {}", targetId);
+            Exceptions.propagate(e);
+        }
+        return target;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/74a22487/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseTest.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseTest.java b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseTest.java
new file mode 100644
index 0000000..fa0e864
--- /dev/null
+++ b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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;
+
+import static org.apache.brooklyn.core.entity.trait.Startable.SERVICE_UP;
+import static org.apache.brooklyn.test.Asserts.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.TypeToken;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.test.framework.entity.TestInfrastructure;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Identifiers;
+
+/**
+ * @author Graeme Miller on 27/10/2015.
+ */
+public class InfrastructureDeploymentTestCaseTest {
+
+    private TestApplication app;
+    private ManagementContext managementContext;
+    private LocalhostMachineProvisioningLocation loc;
+    private LocalhostMachineProvisioningLocation infrastructureLoc;
+    private String LOC_NAME = "location";
+    private String INFRASTRUCTURE_LOC_NAME = "Infrastructure location";
+
+    private static final AttributeSensorAndConfigKey<Location, Location> DEPLOYMENT_LOCATION_SENSOR =
+            ConfigKeys.newSensorAndConfigKey(
+                    new TypeToken<Location>() {
+                    },
+                    "deploymentLocationSensor", "The location to deploy to");
+
+    @BeforeMethod
+    public void setup() {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        managementContext = app.getManagementContext();
+
+        loc = managementContext.getLocationManager()
+                .createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
+                        .configure("name", LOC_NAME));
+
+        infrastructureLoc = managementContext.getLocationManager()
+                .createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
+                        .configure("name", INFRASTRUCTURE_LOC_NAME));
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testVanilla() {
+        EntitySpec<TestInfrastructure> infrastructureSpec = EntitySpec.create(TestInfrastructure.class);
+        infrastructureSpec.configure(DEPLOYMENT_LOCATION_SENSOR, infrastructureLoc);
+
+        List<EntitySpec<? extends SoftwareProcess>> testSpecs = ImmutableList.<EntitySpec<? extends SoftwareProcess>>of(EntitySpec.create(EmptySoftwareProcess.class));
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.INFRASTRUCTURE_SPEC, infrastructureSpec);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.ENTITY_SPEC_TO_DEPLOY, testSpecs);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.DEPLOYMENT_LOCATION_SENSOR_NAME, DEPLOYMENT_LOCATION_SENSOR.getName());
+
+        app.start(ImmutableList.of(loc));
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isTrue();
+        assertThat(infrastructureDeploymentTestCase.getChildren().size()).isEqualTo(2);
+
+        boolean seenInfrastructure = false;
+        boolean seenEntity = false;
+
+        for (Entity entity : infrastructureDeploymentTestCase.getChildren()) {
+            if (entity instanceof BasicApplication) {
+                assertThat(entity.getLocations().size()).isEqualTo(1);
+                assertThat(entity.getLocations().iterator().next().getDisplayName()).isEqualTo(LOC_NAME);
+                assertThat(entity.sensors().get(SERVICE_UP)).isTrue();
+
+                seenInfrastructure = true;
+            } else if (entity instanceof EmptySoftwareProcess) {
+                assertThat(entity.getLocations().size()).isEqualTo(1);
+                assertThat(entity.getLocations().iterator().next().getDisplayName()).isEqualTo(INFRASTRUCTURE_LOC_NAME);
+                assertThat(entity.sensors().get(SERVICE_UP)).isTrue();
+
+                seenEntity = true;
+            } else {
+                fail("Unknown child of InfrastructureDeploymentTestCase");
+            }
+        }
+
+        assertThat(seenInfrastructure).isTrue();
+        assertThat(seenEntity).isTrue();
+    }
+
+    @Test
+    public void testMultipleSpec() {
+        EntitySpec<TestInfrastructure> infrastructureSpec = EntitySpec.create(TestInfrastructure.class);
+        infrastructureSpec.configure(DEPLOYMENT_LOCATION_SENSOR, infrastructureLoc);
+
+        List<EntitySpec<? extends SoftwareProcess>> testSpecs = ImmutableList.<EntitySpec<? extends SoftwareProcess>>of
+                (EntitySpec.create(EmptySoftwareProcess.class),
+                        (EntitySpec.create(EmptySoftwareProcess.class)));
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.INFRASTRUCTURE_SPEC, infrastructureSpec);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.ENTITY_SPEC_TO_DEPLOY, testSpecs);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.DEPLOYMENT_LOCATION_SENSOR_NAME, DEPLOYMENT_LOCATION_SENSOR.getName());
+
+        app.start(ImmutableList.of(loc));
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isTrue();
+        assertThat(infrastructureDeploymentTestCase.getChildren().size()).isEqualTo(3);
+
+        boolean seenInfrastructure = false;
+        int entitiesSeen = 0;
+
+        for (Entity entity : infrastructureDeploymentTestCase.getChildren()) {
+            if (entity instanceof BasicApplication) {
+                assertThat(entity.getLocations().size()).isEqualTo(1);
+                assertThat(entity.getLocations().iterator().next().getDisplayName()).isEqualTo(LOC_NAME);
+                assertThat(entity.sensors().get(SERVICE_UP)).isTrue();
+
+                seenInfrastructure = true;
+            } else if (entity instanceof EmptySoftwareProcess) {
+                assertThat(entity.getLocations().size()).isEqualTo(1);
+                assertThat(entity.getLocations().iterator().next().getDisplayName()).isEqualTo(INFRASTRUCTURE_LOC_NAME);
+                assertThat(entity.sensors().get(SERVICE_UP)).isTrue();
+
+                entitiesSeen++;
+            } else {
+                fail("Unknown child of InfrastructureDeploymentTestCase");
+            }
+        }
+
+        assertThat(seenInfrastructure).isTrue();
+        assertThat(entitiesSeen).isEqualTo(2);
+    }
+
+    @Test
+    public void testNoInfrastructureSpec() {
+        List<EntitySpec<? extends SoftwareProcess>> testSpecs = ImmutableList.<EntitySpec<? extends SoftwareProcess>>of(EntitySpec.create(EmptySoftwareProcess.class));
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.ENTITY_SPEC_TO_DEPLOY, testSpecs);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.DEPLOYMENT_LOCATION_SENSOR_NAME, DEPLOYMENT_LOCATION_SENSOR.getName());
+
+        try {
+            app.start(ImmutableList.of(app.newSimulatedLocation()));
+            fail("Should have thrown execption");
+        } catch (Throwable throwable) {
+            Throwable firstInteresting = Exceptions.getFirstInteresting(throwable);
+            assertThat(firstInteresting).isNotNull();
+            assertThat(throwable).isNotNull();
+            assertThat(firstInteresting).isInstanceOf(IllegalArgumentException.class);
+        }
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+
+    @Test
+    public void testNoEntitySpec() {
+        EntitySpec<TestInfrastructure> infrastructureSpec = EntitySpec.create(TestInfrastructure.class);
+        infrastructureSpec.configure(DEPLOYMENT_LOCATION_SENSOR, infrastructureLoc);
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.INFRASTRUCTURE_SPEC, infrastructureSpec);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.DEPLOYMENT_LOCATION_SENSOR_NAME, DEPLOYMENT_LOCATION_SENSOR.getName());
+
+        try {
+            app.start(ImmutableList.of(app.newSimulatedLocation()));
+            fail("Should have thrown execption");
+        } catch (Throwable throwable) {
+            Throwable firstInteresting = Exceptions.getFirstInteresting(throwable);
+            assertThat(firstInteresting).isNotNull();
+            assertThat(throwable).isNotNull();
+            assertThat(firstInteresting).isInstanceOf(IllegalArgumentException.class);
+        }
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+
+    @Test
+    public void testNoDeploymentLocation() {
+        EntitySpec<TestInfrastructure> infrastructureSpec = EntitySpec.create(TestInfrastructure.class);
+        infrastructureSpec.configure(DEPLOYMENT_LOCATION_SENSOR, infrastructureLoc);
+
+        List<EntitySpec<? extends SoftwareProcess>> testSpecs = ImmutableList.<EntitySpec<? extends SoftwareProcess>>of(EntitySpec.create(EmptySoftwareProcess.class));
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.INFRASTRUCTURE_SPEC, infrastructureSpec);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.ENTITY_SPEC_TO_DEPLOY, testSpecs);
+
+        try {
+            app.start(ImmutableList.of(app.newSimulatedLocation()));
+            fail("Should have thrown execption");
+        } catch (Throwable throwable) {
+            Throwable firstInteresting = Exceptions.getFirstInteresting(throwable);
+            assertThat(firstInteresting).isNotNull();
+            assertThat(throwable).isNotNull();
+            assertThat(firstInteresting).isInstanceOf(IllegalArgumentException.class);
+        }
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+
+    @Test
+    public void testInfrastrucutreHasNoLocation() {
+        EntitySpec<TestInfrastructure> infrastructureSpec = EntitySpec.create(TestInfrastructure.class);
+
+        List<EntitySpec<? extends SoftwareProcess>> testSpecs = ImmutableList.<EntitySpec<? extends SoftwareProcess>>of(EntitySpec.create(EmptySoftwareProcess.class));
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.INFRASTRUCTURE_SPEC, infrastructureSpec);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.ENTITY_SPEC_TO_DEPLOY, testSpecs);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.DEPLOYMENT_LOCATION_SENSOR_NAME, DEPLOYMENT_LOCATION_SENSOR.getName());
+
+        try {
+            app.start(ImmutableList.of(app.newSimulatedLocation()));
+            fail("Should have thrown execption");
+        } catch (Throwable throwable) {
+            Throwable firstInteresting = Exceptions.getFirstInteresting(throwable);
+            assertThat(firstInteresting).isNotNull();
+            assertThat(throwable).isNotNull();
+            assertThat(firstInteresting).isInstanceOf(IllegalArgumentException.class);
+        }
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+}