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 2015/08/20 00:54:18 UTC

[30/36] incubator-brooklyn git commit: Rename o.a.b.sensor.enricher to o.a.b.core.enricher

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/enricher/stock/CustomAggregatingEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/enricher/stock/CustomAggregatingEnricherTest.java b/core/src/test/java/org/apache/brooklyn/enricher/stock/CustomAggregatingEnricherTest.java
new file mode 100644
index 0000000..dbc84ed
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/enricher/stock/CustomAggregatingEnricherTest.java
@@ -0,0 +1,556 @@
+/*
+ * 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.enricher.stock;
+
+import java.util.Collection;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.location.SimulatedLocation;
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.enricher.stock.Enrichers;
+import org.apache.brooklyn.entity.group.BasicGroup;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class CustomAggregatingEnricherTest extends BrooklynAppUnitTestSupport {
+
+    public static final Logger log = LoggerFactory.getLogger(CustomAggregatingEnricherTest.class);
+            
+    private static final long TIMEOUT_MS = 10*1000;
+    private static final long SHORT_WAIT_MS = 50;
+    
+    TestEntity entity;
+    SimulatedLocation loc;
+    
+    AttributeSensor<Integer> intSensor;
+    AttributeSensor<Double> doubleSensor;
+    AttributeSensor<Integer> target;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        intSensor = new BasicAttributeSensor<Integer>(Integer.class, "int sensor");
+        doubleSensor = new BasicAttributeSensor<Double>(Double.class, "double sensor");
+        target = new BasicAttributeSensor<Integer>(Integer.class, "target sensor");
+        loc = mgmt.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class));
+        app.start(ImmutableList.of(loc));
+    }
+    
+    @Test
+    public void testSummingEnricherWithNoProducersDefaultsToNull() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromChildren()
+                .build());
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 50), entity, target, null);
+    }
+
+    @Test
+    public void testSummingEnricherWithNoProducers() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromChildren()
+                .defaultValueForUnreportedSensors(11)
+                .valueToReportIfNoSensors(40)
+                .build());
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 40);
+    }
+
+    @Test
+    public void testSummingEnricherWhenNoSensorValuesYet() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromHardcodedProducers(ImmutableList.of(entity))
+                .defaultValueForUnreportedSensors(11)
+                .valueToReportIfNoSensors(40)
+                .build());
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 11);
+    }
+
+    @Test
+    public void testSummingEnricherWhenNoSensorValuesYetDefaultsToNull() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromHardcodedProducers(ImmutableList.of(entity))
+                .build());
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 50), entity, target, null);
+    }
+
+    @Test
+    public void testSummingEnricherWithNoValues() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromHardcodedProducers(ImmutableList.of(entity))
+                .build());
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 50), entity, target, null);
+    }
+    
+    @Test
+    public void testSummingEnricherWithOneValue() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromHardcodedProducers(ImmutableList.of(entity))
+                .build());
+
+        entity.setAttribute(intSensor, 1);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 1);
+    }
+    
+    @Test
+    public void testSummingEnricherWhenNullSensorValue() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromHardcodedProducers(ImmutableList.of(entity))
+                .build());
+
+        entity.setAttribute(intSensor, null);
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 50), entity, target, null);
+    }
+    
+    @Test
+    public void testSummingEnricherWhenDefaultValueForUnreportedSensors() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromHardcodedProducers(ImmutableList.of(entity))
+                .defaultValueForUnreportedSensors(3)
+                .valueToReportIfNoSensors(5)
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 3);
+        
+        entity.setAttribute(intSensor, null);
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 50), entity, target, 3);
+        
+        entity.setAttribute(intSensor, 1);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 1);
+        
+        entity.setAttribute(intSensor, 7);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 7);
+    }
+    
+    @Test
+    public void testMultipleProducersSum() {
+        TestEntity producer1 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); 
+        TestEntity producer2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity producer3 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromHardcodedProducers(ImmutableList.of(producer1, producer2, producer3))
+                .build());
+
+        producer3.setAttribute(intSensor, 1);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 1);
+
+        producer1.setAttribute(intSensor, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 3);
+
+        producer2.setAttribute(intSensor, 4);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 7);
+    }
+    
+    @Test
+    public void testAveragingEnricherWhenNoAndNullSensorValues() {
+        TestEntity producer1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(doubleSensor)
+                .computingAverage()
+                .fromHardcodedProducers(ImmutableList.of(producer1))
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 50), entity, doubleSensor, null);
+        
+        producer1.setAttribute(intSensor, null);
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 50), entity, doubleSensor, null);
+    }
+
+    @Test
+    public void testAveragingEnricherWhenDefaultValueForUnreportedSensors() {
+        TestEntity producer1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(doubleSensor)
+                .computingAverage()
+                .fromHardcodedProducers(ImmutableList.of(producer1))
+                .defaultValueForUnreportedSensors(3)
+                .valueToReportIfNoSensors(5)
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 3d);
+        
+        producer1.setAttribute(intSensor, null);
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 50), entity, doubleSensor, 3d);
+        
+        producer1.setAttribute(intSensor, 4);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 4d);
+    }
+
+    @Test
+    public void testAveragingEnricherWhenNoSensors() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(doubleSensor)
+                .computingAverage()
+                .fromChildren()
+                .defaultValueForUnreportedSensors(3)
+                .valueToReportIfNoSensors(5)
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 5d);
+    }
+
+    @Test
+    public void testAveragingEnricherWhenNoProducersDefaultsToNull() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(doubleSensor)
+                .computingAverage()
+                .fromChildren()
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 50), entity, doubleSensor, null);
+    }
+
+    @Test
+    public void testMultipleProducersAverage() {
+        TestEntity producer1 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); 
+        TestEntity producer2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity producer3 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(doubleSensor)
+                .computingAverage()
+                .fromHardcodedProducers(ImmutableList.of(producer1, producer2, producer3))
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 50), entity, doubleSensor, null);
+
+        producer1.setAttribute(intSensor, 3);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 3d);
+        
+        producer2.setAttribute(intSensor, 1);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 2d);
+
+        producer3.setAttribute(intSensor, 5);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 3d);
+
+        producer2.setAttribute(intSensor, 4);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 4d);
+    }
+    
+    @Test
+    public void testMultipleProducersAverageDefaultingZero() {
+        TestEntity producer1 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); 
+        TestEntity producer2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity producer3 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(doubleSensor)
+                .computingAverage()
+                .fromHardcodedProducers(ImmutableList.of(producer1, producer2, producer3))
+                .defaultValueForUnreportedSensors(0)
+                .valueToReportIfNoSensors(0)
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 0d);
+
+        producer1.setAttribute(intSensor, 3);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 1d);
+
+        producer2.setAttribute(intSensor, 3);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 2d);
+
+        producer3.setAttribute(intSensor, 3);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, doubleSensor, 3d);
+    }
+    
+    @Test
+    public void testAggregatesNewMembersOfGroup() {
+        BasicGroup group = app.createAndManageChild(EntitySpec.create(BasicGroup.class));
+        TestEntity p1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity p2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        log.debug("created {} and the entities it will contain {} {}", new Object[] {group, p1, p2});
+
+        group.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromMembers()
+                .defaultValueForUnreportedSensors(0)
+                .valueToReportIfNoSensors(0)
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(group, target, 0);
+
+        group.addMember(p1);
+        p1.setAttribute(intSensor, 1);
+        EntityTestUtils.assertAttributeEqualsEventually(group, target, 1);
+
+        group.addMember(p2);
+        p2.setAttribute(intSensor, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(group, target, 3);
+
+        group.removeMember(p2);
+        EntityTestUtils.assertAttributeEqualsEventually(group, target, 1);
+    }
+    
+    @Test(groups = "Integration", invocationCount=50)
+    public void testAggregatesGroupMembersFiftyTimes() {
+        testAggregatesNewMembersOfGroup();
+    }
+    
+    @Test
+    public void testAggregatesExistingMembersOfGroup() {
+        BasicGroup group = app.addChild(EntitySpec.create(BasicGroup.class));
+        TestEntity p1 = app.getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestEntity.class).parent(group)); 
+        TestEntity p2 = app.getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestEntity.class).parent(group)); 
+        group.addMember(p1);
+        group.addMember(p2);
+        p1.setAttribute(intSensor, 1);
+        Entities.manage(group);
+        
+        group.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromMembers()
+                .build());
+
+
+        EntityTestUtils.assertAttributeEqualsEventually(group, target, 1);
+
+        p2.setAttribute(intSensor, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(group, target, 3);
+        
+        group.removeMember(p2);
+        EntityTestUtils.assertAttributeEqualsEventually(group, target, 1);
+    }
+    
+    @Test
+    public void testAggregatesMembersOfProducer() {
+        BasicGroup group = app.addChild(EntitySpec.create(BasicGroup.class));
+        TestEntity p1 = app.getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestEntity.class).parent(group)); 
+        TestEntity p2 = app.getManagementContext().getEntityManager().createEntity(EntitySpec.create(TestEntity.class).parent(group)); 
+        group.addMember(p1);
+        group.addMember(p2);
+        p1.setAttribute(intSensor, 1);
+        Entities.manage(group);
+        
+        app.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .from(group)
+                .fromMembers()
+                .build());
+
+
+        EntityTestUtils.assertAttributeEqualsEventually(app, target, 1);
+
+        p2.setAttribute(intSensor, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(app, target, 3);
+        
+        group.removeMember(p2);
+        EntityTestUtils.assertAttributeEqualsEventually(app, target, 1);
+    }
+    
+    @Test
+    public void testAppliesFilterWhenAggregatingMembersOfGroup() {
+        BasicGroup group = app.createAndManageChild(EntitySpec.create(BasicGroup.class));
+        TestEntity p1 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity p2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity p3 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        group.addMember(p1);
+        group.addMember(p2);
+        p1.setAttribute(intSensor, 1);
+        p2.setAttribute(intSensor, 2);
+        p3.setAttribute(intSensor, 4);
+        
+        group.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromMembers()
+                .entityFilter(Predicates.equalTo((Entity)p1))
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(group, target, 1);
+        
+        group.addMember(p3);
+        EntityTestUtils.assertAttributeEqualsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), group, target, 1);
+    }
+    
+    @Test
+    public void testAggregatesNewChidren() {
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromChildren()
+                .defaultValueForUnreportedSensors(0)
+                .valueToReportIfNoSensors(0)
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 0);
+
+        TestEntity p1 = entity.createAndManageChild(EntitySpec.create(TestEntity.class));
+        p1.setAttribute(intSensor, 1);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 1);
+
+        TestEntity p2 = entity.createAndManageChild(EntitySpec.create(TestEntity.class));
+        p2.setAttribute(intSensor, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 3);
+
+        Entities.unmanage(p2);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 1);
+    }
+    
+    @Test
+    public void testAggregatesExistingChildren() {
+        TestEntity p1 = entity.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity p2 = entity.createAndManageChild(EntitySpec.create(TestEntity.class));
+        p1.setAttribute(intSensor, 1);
+        
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromChildren()
+                .build());
+
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 1);
+
+        p2.setAttribute(intSensor, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 3);
+        
+        Entities.unmanage(p2);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 1);
+    }
+    
+    @Test
+    public void testAggregatesChildrenOfProducer() {
+        TestEntity p1 = entity.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity p2 = entity.createAndManageChild(EntitySpec.create(TestEntity.class));
+        p1.setAttribute(intSensor, 1);
+        
+        app.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .from(entity)
+                .fromChildren()
+                .build());
+
+
+        EntityTestUtils.assertAttributeEqualsEventually(app, target, 1);
+
+        p2.setAttribute(intSensor, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(app, target, 3);
+        
+        Entities.unmanage(p2);
+        EntityTestUtils.assertAttributeEqualsEventually(app, target, 1);
+    }
+    
+    @Test
+    public void testAppliesFilterWhenAggregatingChildrenOfGroup() {
+        TestEntity p1 = entity.createAndManageChild(EntitySpec.create(TestEntity.class));
+        p1.setAttribute(intSensor, 1);
+        
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computingSum()
+                .fromChildren()
+                .entityFilter(Predicates.equalTo((Entity)p1))
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 1);
+        
+        TestEntity p2 = entity.createAndManageChild(EntitySpec.create(TestEntity.class));
+        p2.setAttribute(intSensor, 2);
+        EntityTestUtils.assertAttributeEqualsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), entity, target, 1);
+    }
+    
+    @Test
+    public void testCustomAggregatingFunction() {
+        TestEntity producer1 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); 
+        Function<Collection<Integer>,Integer> aggregator = new Function<Collection<Integer>, Integer>() {
+            public Integer apply(Collection<Integer> input) { 
+                int result = 1;
+                for (Integer in : input) result += in*in;
+                return result;
+            }
+        };
+        
+        entity.addEnricher(Enrichers.builder()
+                .aggregating(intSensor)
+                .publishing(target)
+                .computing(aggregator)
+                .fromHardcodedProducers(ImmutableList.of(producer1))
+                .defaultValueForUnreportedSensors(0)
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 1);
+        
+        // Event by producer
+        producer1.setAttribute(intSensor, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, target, 5);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/enricher/stock/EnrichersTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/enricher/stock/EnrichersTest.java b/core/src/test/java/org/apache/brooklyn/enricher/stock/EnrichersTest.java
new file mode 100644
index 0000000..e5c48fa
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/enricher/stock/EnrichersTest.java
@@ -0,0 +1,501 @@
+/*
+ * 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.enricher.stock;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.Enricher;
+import org.apache.brooklyn.api.sensor.SensorEvent;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityAdjuncts;
+import org.apache.brooklyn.core.entity.RecordingSensorEventListener;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.enricher.stock.Enrichers;
+import org.apache.brooklyn.entity.group.BasicGroup;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.CollectionFunctionals;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.guava.Functionals;
+import org.apache.brooklyn.util.text.StringFunctions;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.reflect.TypeToken;
+
+@SuppressWarnings("serial")
+public class EnrichersTest extends BrooklynAppUnitTestSupport {
+
+    public static final AttributeSensor<Integer> NUM1 = Sensors.newIntegerSensor("test.num1");
+    public static final AttributeSensor<Integer> NUM2 = Sensors.newIntegerSensor("test.num2");
+    public static final AttributeSensor<Integer> NUM3 = Sensors.newIntegerSensor("test.num3");
+    public static final AttributeSensor<String> STR1 = Sensors.newStringSensor("test.str1");
+    public static final AttributeSensor<String> STR2 = Sensors.newStringSensor("test.str2");
+    public static final AttributeSensor<Set<Object>> SET1 = Sensors.newSensor(new TypeToken<Set<Object>>() {}, "test.set1", "set1 descr");
+    public static final AttributeSensor<Long> LONG1 = Sensors.newLongSensor("test.long1");
+    public static final AttributeSensor<Map<String,String>> MAP1 = Sensors.newSensor(new TypeToken<Map<String,String>>() {}, "test.map1", "map1 descr");
+    @SuppressWarnings("rawtypes")
+    public static final AttributeSensor<Map> MAP2 = Sensors.newSensor(Map.class, "test.map2");
+    
+    private TestEntity entity;
+    private TestEntity entity2;
+    private BasicGroup group;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        group = app.createAndManageChild(EntitySpec.create(BasicGroup.class));
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testAdding() {
+        Enricher enr = entity.addEnricher(Enrichers.builder()
+                .combining(NUM1, NUM2)
+                .publishing(NUM3)
+                .computingSum()
+                .build());
+        
+        Assert.assertEquals(EntityAdjuncts.getNonSystemEnrichers(entity), ImmutableList.of(enr));
+        
+        entity.setAttribute(NUM1, 2);
+        entity.setAttribute(NUM2, 3);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, NUM3, 5);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testCombiningWithCustomFunction() {
+        entity.addEnricher(Enrichers.builder()
+                .combining(NUM1, NUM2)
+                .publishing(NUM3)
+                .computing(Functions.constant(1))
+                .build());
+        
+        entity.setAttribute(NUM1, 2);
+        entity.setAttribute(NUM2, 3);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, NUM3, 1);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test(groups="Integration") // because takes a second
+    public void testCombiningRespectsUnchanged() {
+        entity.addEnricher(Enrichers.builder()
+                .combining(NUM1, NUM2)
+                .<Object>publishing(NUM3)
+                .computing(new Function<Iterable<Integer>, Object>() {
+                        @Override public Object apply(Iterable<Integer> input) {
+                            if (input != null && Iterables.contains(input, 123)) {
+                                return Enrichers.sum(input, 0, 0, new TypeToken<Integer>(){});
+                            } else {
+                                return Entities.UNCHANGED;
+                            }
+                        }})
+                .build());
+        
+        entity.setAttribute(NUM1, 123);
+        entity.setAttribute(NUM2, 3);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, NUM3, 126);
+        
+        entity.setAttribute(NUM1, 2);
+        EntityTestUtils.assertAttributeEqualsContinually(entity, NUM3, 126);
+    }
+    
+    @Test
+    public void testFromEntity() {
+        entity.addEnricher(Enrichers.builder()
+                .transforming(NUM1)
+                .publishing(NUM1)
+                .computing(Functions.<Integer>identity())
+                .from(entity2)
+                .build());
+        
+        entity2.setAttribute(NUM1, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, NUM1, 2);
+    }
+    
+    @Test
+    public void testTransforming() {
+        entity.addEnricher(Enrichers.builder()
+                .transforming(STR1)
+                .publishing(STR2)
+                .computing(StringFunctions.append("mysuffix"))
+                .build());
+        
+        entity.setAttribute(STR1, "myval");
+        EntityTestUtils.assertAttributeEqualsEventually(entity, STR2, "myvalmysuffix");
+    }
+
+    @Test
+    public void testTransformingCastsResult() {
+        entity.addEnricher(Enrichers.builder()
+                .transforming(NUM1)
+                .publishing(LONG1)
+                .computing(Functions.constant(Long.valueOf(1)))
+                .build());
+        
+        entity.setAttribute(NUM1, 123);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, LONG1, Long.valueOf(1));
+    }
+
+    @Test
+    public void testTransformingFromEvent() {
+        entity.addEnricher(Enrichers.builder()
+                .transforming(STR1)
+                .publishing(STR2)
+                .computingFromEvent(new Function<SensorEvent<String>, String>() {
+                    @Override public String apply(SensorEvent<String> input) {
+                        return input.getValue() + "mysuffix";
+                    }})
+                .build());
+        
+        entity.setAttribute(STR1, "myval");
+        EntityTestUtils.assertAttributeEqualsEventually(entity, STR2, "myvalmysuffix");
+    }
+
+    @Test(groups="Integration") // because takes a second
+    public void testTransformingRespectsUnchangedButWillRepublish() {
+        RecordingSensorEventListener<String> record = new RecordingSensorEventListener<>();
+        app.getManagementContext().getSubscriptionManager().subscribe(entity, STR2, record);
+        
+        entity.addEnricher(Enrichers.builder()
+                .transforming(STR1)
+                .<Object>publishing(STR2)
+                .computing(new Function<String, Object>() {
+                        @Override public Object apply(String input) {
+                            return ("ignoredval".equals(input)) ? Entities.UNCHANGED : input;
+                        }})
+                .build());
+        Asserts.assertThat(record.getEvents(), CollectionFunctionals.sizeEquals(0));
+
+        entity.setAttribute(STR1, "myval");
+        Asserts.eventually(Suppliers.ofInstance(record), CollectionFunctionals.sizeEquals(1));
+        EntityTestUtils.assertAttributeEquals(entity, STR2, "myval");
+
+        entity.setAttribute(STR1, "ignoredval");
+        EntityTestUtils.assertAttributeEqualsContinually(entity, STR2, "myval");
+
+        entity.setAttribute(STR1, "myval2");
+        Asserts.eventually(Suppliers.ofInstance(record), CollectionFunctionals.sizeEquals(2));
+        EntityTestUtils.assertAttributeEquals(entity, STR2, "myval2");
+
+        entity.setAttribute(STR1, "myval2");
+        entity.setAttribute(STR1, "myval2");
+        entity.setAttribute(STR1, "myval3");
+        Asserts.eventually(Suppliers.ofInstance(record), CollectionFunctionals.sizeEquals(5));
+    }
+
+    public void testTransformingSuppressDuplicates() {
+        RecordingSensorEventListener<String> record = new RecordingSensorEventListener<>();
+        app.getManagementContext().getSubscriptionManager().subscribe(entity, STR2, record);
+
+        entity.addEnricher(Enrichers.builder()
+                .transforming(STR1)
+                .publishing(STR2)
+                .computing(Functions.<String>identity())
+                .suppressDuplicates(true)
+                .build());
+
+        entity.setAttribute(STR1, "myval");
+        Asserts.eventually(Suppliers.ofInstance(record), CollectionFunctionals.sizeEquals(1));
+        EntityTestUtils.assertAttributeEquals(entity, STR2, "myval");
+
+        entity.setAttribute(STR1, "myval2");
+        entity.setAttribute(STR1, "myval2");
+        entity.setAttribute(STR1, "myval3");
+        EntityTestUtils.assertAttributeEqualsContinually(entity, STR2, "myval3");
+        Asserts.assertThat(record.getEvents(), CollectionFunctionals.sizeEquals(3));
+    }
+
+    @Test
+    public void testPropagating() {
+        entity.addEnricher(Enrichers.builder()
+                .propagating(ImmutableList.of(STR1))
+                .from(entity2)
+                .build());
+        
+        entity2.setAttribute(STR1, "myval");
+        EntityTestUtils.assertAttributeEqualsEventually(entity, STR1, "myval");
+        
+        entity2.setAttribute(STR1, null);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, STR1, null);
+    }
+    
+    @Test
+    public void testPropagatingAndRenaming() {
+        entity.addEnricher(Enrichers.builder()
+                .propagating(ImmutableMap.of(STR1, STR2))
+                .from(entity2)
+                .build());
+        
+        entity2.setAttribute(STR1, "myval");
+        EntityTestUtils.assertAttributeEqualsEventually(entity, STR2, "myval");
+    }
+    
+    // FIXME What is default? members? children? fail?
+    @Test
+    public void testAggregatingGroupSum() {
+        TestEntity child1 = group.addChild(EntitySpec.create(TestEntity.class));
+        Entities.manage(child1);
+        group.addMember(entity);
+        group.addMember(entity2);
+        group.addEnricher(Enrichers.builder()
+                .aggregating(NUM1)
+                .publishing(NUM2)
+                .fromMembers()
+                .computingSum()
+                .build());
+        
+        child1.setAttribute(NUM1, 1);
+        entity.setAttribute(NUM1, 2);
+        entity2.setAttribute(NUM1, 3);
+        EntityTestUtils.assertAttributeEqualsEventually(group, NUM2, 5);
+    }
+    
+    @Test
+    public void testAggregatingChildrenSum() {
+        group.addMember(entity);
+        TestEntity child1 = group.addChild(EntitySpec.create(TestEntity.class));
+        Entities.manage(child1);
+        TestEntity child2 = group.addChild(EntitySpec.create(TestEntity.class));
+        Entities.manage(child2);
+        group.addEnricher(Enrichers.builder()
+                .aggregating(NUM1)
+                .publishing(NUM2)
+                .fromChildren()
+                .computingSum()
+                .build());
+        
+        entity.setAttribute(NUM1, 1);
+        child1.setAttribute(NUM1, 2);
+        child2.setAttribute(NUM1, 3);
+        EntityTestUtils.assertAttributeEqualsEventually(group, NUM2, 5);
+    }
+
+    @Test
+    public void testAggregatingExcludingBlankString() {
+        group.addMember(entity);
+        group.addMember(entity2);
+        group.addEnricher(Enrichers.builder()
+                .aggregating(STR1)
+                .publishing(SET1)
+                .fromMembers()
+                .excludingBlank()
+                .computing(new Function<Collection<?>, Set<Object>>() {
+                    @Override public Set<Object> apply(Collection<?> input) {
+                        // accept null values, so don't use ImmutableSet
+                        return (input == null) ? ImmutableSet.<Object>of() : MutableSet.<Object>copyOf(input);
+                    }})
+                .build());
+        
+        entity.setAttribute(STR1, "1");
+        entity2.setAttribute(STR1, "2");
+        EntityTestUtils.assertAttributeEqualsEventually(group, SET1, ImmutableSet.<Object>of("1", "2"));
+        
+        entity.setAttribute(STR1, "3");
+        entity2.setAttribute(STR1, null);
+        EntityTestUtils.assertAttributeEqualsEventually(group, SET1, ImmutableSet.<Object>of("3"));
+        
+        entity.setAttribute(STR1, "");
+        entity2.setAttribute(STR1, "4");
+        EntityTestUtils.assertAttributeEqualsEventually(group, SET1, ImmutableSet.<Object>of("4"));
+    }
+
+    @Test
+    public void testAggregatingExcludingNull() {
+        group.addMember(entity);
+        group.addEnricher(Enrichers.builder()
+                .aggregating(NUM1)
+                .publishing(SET1)
+                .fromMembers()
+                .excludingBlank()
+                .computing(new Function<Collection<?>, Set<Object>>() {
+                    @Override public Set<Object> apply(Collection<?> input) {
+                        // accept null values, so don't use ImmutableSet
+                        return (input == null) ? ImmutableSet.<Object>of() : MutableSet.<Object>copyOf(input);
+                    }})
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(group, SET1, ImmutableSet.<Object>of());
+
+        entity.setAttribute(NUM1, 1);
+        EntityTestUtils.assertAttributeEqualsEventually(group, SET1, ImmutableSet.<Object>of(1));
+        
+        entity.setAttribute(NUM1, null);
+        EntityTestUtils.assertAttributeEqualsEventually(group, SET1, ImmutableSet.<Object>of());
+        
+        entity.setAttribute(NUM1, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(group, SET1, ImmutableSet.<Object>of(2));
+    }
+
+    @Test
+    public void testAggregatingCastsResult() {
+        group.addMember(entity);
+        group.addEnricher(Enrichers.builder()
+                .aggregating(NUM1)
+                .publishing(LONG1)
+                .fromMembers()
+                .computing(Functions.constant(Long.valueOf(1)))
+                .build());
+        
+        entity.setAttribute(NUM1, 123);
+        EntityTestUtils.assertAttributeEqualsEventually(group, LONG1, Long.valueOf(1));
+    }
+    
+    @Test(groups="Integration") // because takes a second
+    public void testAggregatingRespectsUnchanged() {
+        group.addMember(entity);
+        group.addEnricher(Enrichers.builder()
+                .aggregating(NUM1)
+                .<Object>publishing(LONG1)
+                .fromMembers()
+                .computing(new Function<Iterable<Integer>, Object>() {
+                        @Override public Object apply(Iterable<Integer> input) {
+                            if (input != null && Iterables.contains(input, 123)) {
+                                return Enrichers.sum(input, 0, 0, new TypeToken<Integer>(){});
+                            } else {
+                                return Entities.UNCHANGED;
+                            }
+                        }})
+                .build());
+        
+        entity.setAttribute(NUM1, 123);
+        EntityTestUtils.assertAttributeEqualsEventually(group, LONG1, Long.valueOf(123));
+        
+        entity.setAttribute(NUM1, 987654);
+        EntityTestUtils.assertAttributeEqualsContinually(group, LONG1, Long.valueOf(123));
+    }
+    @Test
+    public void testUpdatingMap1() {
+        entity.addEnricher(Enrichers.builder()
+                .updatingMap(MAP1)
+                .from(LONG1)
+                .computing(Functionals.ifEquals(-1L).value("-1 is not allowed"))
+                .build());
+        
+        doUpdatingMapChecks(MAP1);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void testUpdatingMap2() {
+        entity.addEnricher(Enrichers.builder()
+                .updatingMap((AttributeSensor)MAP2)
+                .from(LONG1)
+                .computing(Functionals.ifEquals(-1L).value("-1 is not allowed"))
+                .build());
+        
+        doUpdatingMapChecks(MAP2);
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    protected void doUpdatingMapChecks(AttributeSensor mapSensor) {
+        EntityTestUtils.assertAttributeEqualsEventually(entity, mapSensor, MutableMap.<String,String>of());
+        
+        entity.setAttribute(LONG1, -1L);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, mapSensor, MutableMap.<String,String>of(
+            LONG1.getName(), "-1 is not allowed"));
+        
+        entity.setAttribute(LONG1, 1L);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, mapSensor, MutableMap.<String,String>of());
+    }
+
+    private static AttributeSensor<Object> LIST_SENSOR = Sensors.newSensor(Object.class, "sensor.list");
+    
+    @Test
+    public void testJoinerDefault() {
+        entity.addEnricher(Enrichers.builder()
+                .joining(LIST_SENSOR)
+                .publishing(TestEntity.NAME)
+                .build());
+        // null values ignored, and it quotes
+        entity.setAttribute(LIST_SENSOR, MutableList.<String>of("a", "\"b").append(null));
+        EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, "\"a\",\"\\\"b\"");
+        
+        // empty list causes ""
+        entity.setAttribute(LIST_SENSOR, MutableList.<String>of().append(null));
+        EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, "");
+        
+        // null causes null
+        entity.setAttribute(LIST_SENSOR, null);
+        EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, null);
+    }
+
+    @Test
+    public void testJoinerUnquoted() {
+        entity.setAttribute(LIST_SENSOR, MutableList.<String>of("a", "\"b", "ccc").append(null));
+        entity.addEnricher(Enrichers.builder()
+            .joining(LIST_SENSOR)
+            .publishing(TestEntity.NAME)
+            .minimum(1)
+            .maximum(2)
+            .separator(":")
+            .quote(false)
+            .build());
+        // in this case, it should be immediately available upon adding the enricher
+        EntityTestUtils.assertAttributeEquals(entity, TestEntity.NAME, "a:\"b");
+        
+        // empty list causes null here, because below the minimum
+        entity.setAttribute(LIST_SENSOR, MutableList.<String>of().append(null));
+        EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, null);
+    }
+
+    @Test
+    public void testJoinerMinMax() {
+        entity.addEnricher(Enrichers.builder()
+                .joining(LIST_SENSOR)
+                .publishing(TestEntity.NAME)
+                .minimum(2)
+                .maximum(4)
+                .quote(false)
+                .build());
+        // null values ignored, and it quotes
+        entity.setAttribute(LIST_SENSOR, MutableList.<String>of("a", "b"));
+        EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, "a,b");
+        
+        // empty list causes ""
+        entity.setAttribute(LIST_SENSOR, MutableList.<String>of("x"));
+        EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, null);
+        
+        // null causes null
+        entity.setAttribute(LIST_SENSOR, MutableList.<String>of("a", "b", "c", "d", "e"));
+        EntityTestUtils.assertAttributeEqualsEventually(entity, TestEntity.NAME, "a,b,c,d");
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/enricher/stock/SensorPropagatingEnricherDeprecatedTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/enricher/stock/SensorPropagatingEnricherDeprecatedTest.java b/core/src/test/java/org/apache/brooklyn/enricher/stock/SensorPropagatingEnricherDeprecatedTest.java
new file mode 100644
index 0000000..a52e2ee
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/enricher/stock/SensorPropagatingEnricherDeprecatedTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.enricher.stock;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.SensorEvent;
+import org.apache.brooklyn.api.sensor.SensorEventListener;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.enricher.stock.SensorPropagatingEnricher;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.javalang.AtomicReferences;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+
+public class SensorPropagatingEnricherDeprecatedTest extends BrooklynAppUnitTestSupport {
+
+    private TestEntity entity;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+    }
+    
+    @Test
+    public void testPropagatesSpecificSensor() {
+        app.addEnricher(SensorPropagatingEnricher.newInstanceListeningTo(entity, TestEntity.NAME));
+
+        // name propagated
+        entity.setAttribute(TestEntity.NAME, "foo");
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.NAME, "foo");
+        
+        // sequence not propagated
+        entity.setAttribute(TestEntity.SEQUENCE, 2);
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 100), app, TestEntity.SEQUENCE, null);
+    }
+    
+    @Test
+    public void testPropagatesAllSensors() {
+        app.addEnricher(SensorPropagatingEnricher.newInstanceListeningToAllSensors(entity));
+
+        // all attributes propagated
+        entity.setAttribute(TestEntity.NAME, "foo");
+        entity.setAttribute(TestEntity.SEQUENCE, 2);
+        
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.NAME, "foo");
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.SEQUENCE, 2);
+        
+        // notification-sensor propagated
+        final AtomicReference<Integer> notif = new AtomicReference<Integer>();
+        app.subscribe(app, TestEntity.MY_NOTIF, new SensorEventListener<Integer>() {
+                @Override public void onEvent(SensorEvent<Integer> event) {
+                    notif.set(event.getValue());
+                }});
+        entity.emit(TestEntity.MY_NOTIF, 7);
+        Asserts.eventually(AtomicReferences.supplier(notif), Predicates.equalTo(7));
+    }
+    
+    @Test
+    public void testPropagatesAllBut() {
+        app.addEnricher(SensorPropagatingEnricher.newInstanceListeningToAllSensorsBut(entity, TestEntity.SEQUENCE)) ;
+
+        // name propagated
+        entity.setAttribute(TestEntity.NAME, "foo");
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.NAME, "foo");
+        
+        // sequence not propagated
+        entity.setAttribute(TestEntity.SEQUENCE, 2);
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 100), app, TestEntity.SEQUENCE, null);
+    }
+    
+    @Test
+    public void testPropagatingAsDifferentSensor() {
+        final AttributeSensor<String> ANOTHER_ATTRIBUTE = Sensors.newStringSensor("another.attribute", "");
+        app.addEnricher(SensorPropagatingEnricher.newInstanceRenaming(entity, ImmutableMap.of(TestEntity.NAME, ANOTHER_ATTRIBUTE)));
+
+        // name propagated as different attribute
+        entity.setAttribute(TestEntity.NAME, "foo");
+        EntityTestUtils.assertAttributeEqualsEventually(app, ANOTHER_ATTRIBUTE, "foo");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/enricher/stock/SensorPropagatingEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/enricher/stock/SensorPropagatingEnricherTest.java b/core/src/test/java/org/apache/brooklyn/enricher/stock/SensorPropagatingEnricherTest.java
new file mode 100644
index 0000000..c660df1
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/enricher/stock/SensorPropagatingEnricherTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.enricher.stock;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.EnricherSpec;
+import org.apache.brooklyn.api.sensor.SensorEvent;
+import org.apache.brooklyn.api.sensor.SensorEventListener;
+import org.apache.brooklyn.core.sensor.BasicNotificationSensor;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.enricher.stock.Enrichers;
+import org.apache.brooklyn.enricher.stock.Propagator;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.javalang.AtomicReferences;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class SensorPropagatingEnricherTest extends BrooklynAppUnitTestSupport {
+
+    private TestEntity entity;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+    }
+    
+    @Test
+    public void testPropagatesSpecificSensor() {
+        app.addEnricher(Enrichers.builder()
+                .propagating(TestEntity.NAME)
+                .from(entity)
+                .build());
+
+        // name propagated
+        entity.setAttribute(TestEntity.NAME, "foo");
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.NAME, "foo");
+        
+        // sequence not propagated
+        entity.setAttribute(TestEntity.SEQUENCE, 2);
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 100), app, TestEntity.SEQUENCE, null);
+    }
+    
+    @Test
+    public void testPropagatesCurrentValue() {
+        entity.setAttribute(TestEntity.NAME, "foo");
+        
+        app.addEnricher(Enrichers.builder()
+                .propagating(TestEntity.NAME)
+                .from(entity)
+                .build());
+
+        // name propagated
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.NAME, "foo");
+    }
+    
+    @Test
+    public void testPropagatesAllStaticSensors() {
+        app.addEnricher(Enrichers.builder()
+                .propagatingAll()
+                .from(entity)
+                .build());
+
+        // all attributes propagated
+        entity.setAttribute(TestEntity.NAME, "foo");
+        entity.setAttribute(TestEntity.SEQUENCE, 2);
+        
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.NAME, "foo");
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.SEQUENCE, 2);
+        
+        // notification-sensor propagated
+        final AtomicReference<Integer> notif = new AtomicReference<Integer>();
+        app.subscribe(app, TestEntity.MY_NOTIF, new SensorEventListener<Integer>() {
+                @Override public void onEvent(SensorEvent<Integer> event) {
+                    notif.set(event.getValue());
+                }});
+        entity.emit(TestEntity.MY_NOTIF, 7);
+        Asserts.eventually(AtomicReferences.supplier(notif), Predicates.equalTo(7));
+    }
+    
+    @Test
+    public void testPropagatesAllSensorsIncludesDynamicallyAdded() {
+        AttributeSensor<String> dynamicAttribute = Sensors.newStringSensor("test.dynamicsensor.strattrib");
+        BasicNotificationSensor<String> dynamicNotificationSensor = new BasicNotificationSensor(String.class, "test.dynamicsensor.strnotif");
+        
+        app.addEnricher(Enrichers.builder()
+                .propagatingAll()
+                .from(entity)
+                .build());
+
+        entity.setAttribute(dynamicAttribute, "foo");
+        
+        EntityTestUtils.assertAttributeEqualsEventually(app, dynamicAttribute, "foo");
+        
+        // notification-sensor propagated
+        final AtomicReference<String> notif = new AtomicReference<String>();
+        app.subscribe(app, dynamicNotificationSensor, new SensorEventListener<String>() {
+                @Override public void onEvent(SensorEvent<String> event) {
+                    notif.set(event.getValue());
+                }});
+        entity.emit(dynamicNotificationSensor, "mynotifval");
+        Asserts.eventually(AtomicReferences.supplier(notif), Predicates.equalTo("mynotifval"));
+    }
+    
+    @Test
+    public void testPropagatesAllBut() {
+        app.addEnricher(Enrichers.builder()
+                .propagatingAllBut(TestEntity.SEQUENCE)
+                .from(entity)
+                .build());
+
+        // name propagated
+        entity.setAttribute(TestEntity.NAME, "foo");
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.NAME, "foo");
+        
+        // sequence not propagated
+        entity.setAttribute(TestEntity.SEQUENCE, 2);
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 100), app, TestEntity.SEQUENCE, null);
+    }
+    
+    @Test
+    public void testPropagatingAsDifferentSensor() {
+        final AttributeSensor<String> ANOTHER_ATTRIBUTE = Sensors.newStringSensor("another.attribute", "");
+        
+        app.addEnricher(Enrichers.builder()
+                .propagating(ImmutableMap.of(TestEntity.NAME, ANOTHER_ATTRIBUTE))
+                .from(entity)
+                .build());
+
+        // name propagated as different attribute
+        entity.setAttribute(TestEntity.NAME, "foo");
+        EntityTestUtils.assertAttributeEqualsEventually(app, ANOTHER_ATTRIBUTE, "foo");
+    }
+    
+    @Test
+    public void testEnricherSpecPropagatesSpecificSensor() throws Exception {
+        app.addEnricher(EnricherSpec.create(Propagator.class)
+                .configure(MutableMap.builder()
+                        .putIfNotNull(Propagator.PRODUCER, entity)
+                        .putIfNotNull(Propagator.PROPAGATING, ImmutableList.of(TestEntity.NAME))
+                        .build()));
+
+        // name propagated
+        entity.setAttribute(TestEntity.NAME, "foo");
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.NAME, "foo");
+        
+        // sequence not propagated
+        entity.setAttribute(TestEntity.SEQUENCE, 2);
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 100), app, TestEntity.SEQUENCE, null);
+    }
+    
+    @Test
+    public void testEnricherSpecPropagatesSpecificSensorAndMapsOthers() throws Exception {
+        final AttributeSensor<String> ANOTHER_ATTRIBUTE = Sensors.newStringSensor("another.attribute", "");
+        
+        app.addEnricher(EnricherSpec.create(Propagator.class)
+                .configure(MutableMap.builder()
+                        .putIfNotNull(Propagator.PRODUCER, entity)
+                        .putIfNotNull(Propagator.SENSOR_MAPPING, ImmutableMap.of(TestEntity.NAME, ANOTHER_ATTRIBUTE))
+                        .putIfNotNull(Propagator.PROPAGATING, ImmutableList.of(TestEntity.SEQUENCE))
+                        .build()));
+
+        // name propagated as alternative sensor
+        entity.setAttribute(TestEntity.NAME, "foo");
+        EntityTestUtils.assertAttributeEqualsEventually(app, ANOTHER_ATTRIBUTE, "foo");
+        
+        // sequence also propagated
+        entity.setAttribute(TestEntity.SEQUENCE, 2);
+        EntityTestUtils.assertAttributeEqualsEventually(app, TestEntity.SEQUENCE, 2);
+
+        // name not propagated as original sensor
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", 100), app, TestEntity.NAME, null);
+    }
+    
+    @Test
+    public void testEnricherSpecThrowsOnPropagatesAndPropagatesAllSet() throws Exception {
+        try {
+            app.addEnricher(EnricherSpec.create(Propagator.class)
+                    .configure(MutableMap.builder()
+                            .put(Propagator.PRODUCER, entity)
+                            .put(Propagator.PROPAGATING, ImmutableList.of(TestEntity.NAME))
+                            .put(Propagator.PROPAGATING_ALL, true)
+                            .build()));
+        } catch (Exception e) {
+            IllegalStateException ise = Exceptions.getFirstThrowableOfType(e, IllegalStateException.class);
+            if (ise == null) throw e;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/enricher/stock/TransformingEnricherDeprecatedTest.groovy
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/enricher/stock/TransformingEnricherDeprecatedTest.groovy b/core/src/test/java/org/apache/brooklyn/enricher/stock/TransformingEnricherDeprecatedTest.groovy
new file mode 100644
index 0000000..d1f264d
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/enricher/stock/TransformingEnricherDeprecatedTest.groovy
@@ -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.enricher.stock
+
+import java.util.concurrent.Callable
+
+import org.apache.brooklyn.api.entity.EntitySpec
+import org.apache.brooklyn.api.sensor.AttributeSensor
+import org.apache.brooklyn.core.test.entity.TestApplication
+import org.apache.brooklyn.core.test.entity.TestEntity
+import org.apache.brooklyn.enricher.stock.SensorTransformingEnricher;
+import org.apache.brooklyn.core.entity.Entities
+import org.apache.brooklyn.core.location.SimulatedLocation
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor
+import org.apache.brooklyn.test.TestUtils
+import org.apache.brooklyn.util.collections.MutableMap
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.testng.Assert
+import org.testng.annotations.AfterMethod
+import org.testng.annotations.BeforeMethod
+import org.testng.annotations.Test
+
+public class TransformingEnricherDeprecatedTest {
+
+    public static final Logger log = LoggerFactory.getLogger(TransformingEnricherDeprecatedTest.class);
+            
+    private static final long TIMEOUT_MS = 10*1000;
+//    private static final long SHORT_WAIT_MS = 250;
+    
+    TestApplication app;
+    TestEntity producer;
+    AttributeSensor<Integer> intSensorA;
+    AttributeSensor<Long> target;
+
+    @BeforeMethod()
+    public void before() {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        producer = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        intSensorA = new BasicAttributeSensor<Integer>(Integer.class, "int.sensor.a");
+        target = new BasicAttributeSensor<Long>(Long.class, "long.sensor.target");
+        
+        app.start(Arrays.asList(new SimulatedLocation()));
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void after() {
+        if (app!=null) Entities.destroyAll(app.getManagementContext());
+    }
+    
+    @Test
+    public void testTransformingEnricher() throws InterruptedException {
+        final SensorTransformingEnricher e1 = new SensorTransformingEnricher<Integer,Long>(intSensorA, target, 
+            { 2*it });
+        
+        producer.setAttribute(intSensorA, 3);
+        //ensure previous values get picked up
+        producer.addEnricher(e1);
+
+        TestUtils.assertEventually(MutableMap.of("timeout", TIMEOUT_MS), 
+                new Callable<Object>() { public Object call() {
+                    Assert.assertEquals(producer.getAttribute(target), (Long)((long)6));
+                    return null;
+                }});
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/enricher/stock/TransformingEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/enricher/stock/TransformingEnricherTest.java b/core/src/test/java/org/apache/brooklyn/enricher/stock/TransformingEnricherTest.java
new file mode 100644
index 0000000..ee8fc9e
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/enricher/stock/TransformingEnricherTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.enricher.stock;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.location.SimulatedLocation;
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.enricher.stock.Enrichers;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.math.MathFunctions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+
+public class TransformingEnricherTest extends BrooklynAppUnitTestSupport {
+
+    public static final Logger log = LoggerFactory.getLogger(TransformingEnricherTest.class);
+            
+    TestEntity producer;
+    AttributeSensor<Integer> intSensorA;
+    AttributeSensor<Long> target;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        producer = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        intSensorA = new BasicAttributeSensor<Integer>(Integer.class, "int.sensor.a");
+        target = new BasicAttributeSensor<Long>(Long.class, "long.sensor.target");
+        
+        app.start(ImmutableList.of(new SimulatedLocation()));
+    }
+    
+    @Test
+    public void testTransformingEnricher() throws Exception {
+        //ensure previous values get picked up
+        producer.setAttribute(intSensorA, 3);
+
+        producer.addEnricher(Enrichers.builder()
+                .transforming(intSensorA)
+                //.computing(MathFunctions.times(2)) // TODO calling it before "publishing" means it doesn't check return type!
+                .publishing(target)
+                .computing((Function)MathFunctions.times(2)) // TODO doesn't match strongly typed int->long
+                .build());
+
+        EntityTestUtils.assertAttributeEqualsEventually(producer, target, 6L);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/enricher/stock/YamlRollingTimeWindowMeanEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/enricher/stock/YamlRollingTimeWindowMeanEnricherTest.java b/core/src/test/java/org/apache/brooklyn/enricher/stock/YamlRollingTimeWindowMeanEnricherTest.java
new file mode 100644
index 0000000..af683ac
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/enricher/stock/YamlRollingTimeWindowMeanEnricherTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.enricher.stock;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.SubscriptionContext;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.EnricherSpec;
+import org.apache.brooklyn.core.entity.AbstractApplication;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
+import org.apache.brooklyn.core.sensor.BasicSensorEvent;
+import org.apache.brooklyn.enricher.stock.YamlRollingTimeWindowMeanEnricher;
+import org.apache.brooklyn.enricher.stock.YamlTimeWeightedDeltaEnricher;
+import org.apache.brooklyn.enricher.stock.YamlRollingTimeWindowMeanEnricher.ConfidenceQualifiedNumber;
+import org.apache.brooklyn.entity.stock.BasicEntity;
+import org.apache.brooklyn.util.time.Duration;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class YamlRollingTimeWindowMeanEnricherTest {
+    
+    AbstractApplication app;
+    
+    BasicEntity producer;
+
+    AttributeSensor<Integer> intSensor;
+    AttributeSensor<Double> avgSensor, deltaSensor;
+    
+    Duration timePeriod = Duration.ONE_SECOND;
+    
+    YamlTimeWeightedDeltaEnricher<Double> delta;
+    YamlRollingTimeWindowMeanEnricher<Double> averager;
+    
+    ConfidenceQualifiedNumber average;
+    SubscriptionContext subscription;
+    
+    @SuppressWarnings("unchecked")
+    @BeforeMethod
+    public void before() {
+        app = new AbstractApplication() {};
+        Entities.startManagement(app);
+        producer = app.addChild(EntitySpec.create(BasicEntity.class));
+
+        intSensor = new BasicAttributeSensor<Integer>(Integer.class, "int sensor");
+        deltaSensor = new BasicAttributeSensor<Double>(Double.class, "delta sensor");
+        avgSensor = new BasicAttributeSensor<Double>(Double.class, "avg sensor");
+            
+        delta = producer.addEnricher(EnricherSpec.create(YamlTimeWeightedDeltaEnricher.class)
+                .configure(YamlTimeWeightedDeltaEnricher.PRODUCER, producer)
+                .configure(YamlTimeWeightedDeltaEnricher.SOURCE_SENSOR, intSensor)
+                .configure(YamlTimeWeightedDeltaEnricher.TARGET_SENSOR, deltaSensor));
+
+        averager = producer.addEnricher(EnricherSpec.create(YamlRollingTimeWindowMeanEnricher.class)
+                .configure(YamlRollingTimeWindowMeanEnricher.PRODUCER, producer)
+                .configure(YamlRollingTimeWindowMeanEnricher.SOURCE_SENSOR, deltaSensor)
+                .configure(YamlRollingTimeWindowMeanEnricher.TARGET_SENSOR, avgSensor)
+                .configure(YamlRollingTimeWindowMeanEnricher.WINDOW_DURATION, timePeriod));
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+        
+    @Test
+    public void testDefaultAverageWhenEmpty() {
+        ConfidenceQualifiedNumber average = averager.getAverage(0, 0);
+        assertEquals(average.value, 0d);
+        assertEquals(average.confidence, 0.0d);
+    }
+    
+    protected BasicSensorEvent<Integer> newIntSensorEvent(int value, long timestamp) {
+        return new BasicSensorEvent<Integer>(intSensor, producer, value, timestamp);
+    }
+    protected BasicSensorEvent<Double> newDeltaSensorEvent(double value, long timestamp) {
+        return new BasicSensorEvent<Double>(deltaSensor, producer, value, timestamp);
+    }
+
+    @Test
+    public void testNoRecentValuesAverage() {
+        averager.onEvent(newDeltaSensorEvent(10, 0));
+        average = averager.getAverage(timePeriod.toMilliseconds()+1000, 0);
+        assertEquals(average.value, 10d);
+        assertEquals(average.confidence, 0d);
+    }
+
+    @Test
+    public void testNoRecentValuesUsesLastForAverage() {
+        averager.onEvent(newDeltaSensorEvent(10, 0));
+        averager.onEvent(newDeltaSensorEvent(20, 10));
+        average = averager.getAverage(timePeriod.toMilliseconds()+1000, 0);
+        assertEquals(average.value, 20d);
+        assertEquals(average.confidence, 0d);
+    }
+
+    @Test
+    public void testSingleValueTimeAverage() {
+        averager.onEvent(newDeltaSensorEvent(10, 1000));
+        average = averager.getAverage(1000, 0);
+        assertEquals(average.confidence, 0d);
+    }
+
+    @Test
+    public void testTwoValueAverageForPeriod() {
+        averager.onEvent(newDeltaSensorEvent(10, 1000));
+        averager.onEvent(newDeltaSensorEvent(10, 2000));
+        average = averager.getAverage(2000, 0);
+        assertEquals(average.value, 10 /1d);
+        assertEquals(average.confidence, 1d);
+    }
+
+    @Test
+    public void testMonospacedAverage() {
+        averager.onEvent(newDeltaSensorEvent(10, 1000));
+        averager.onEvent(newDeltaSensorEvent(20, 1250));
+        averager.onEvent(newDeltaSensorEvent(30, 1500));
+        averager.onEvent(newDeltaSensorEvent(40, 1750));
+        averager.onEvent(newDeltaSensorEvent(50, 2000));
+        average = averager.getAverage(2000, 0);
+        assertEquals(average.value, (20+30+40+50)/4d);
+        assertEquals(average.confidence, 1d);
+    }
+
+    @Test
+    public void testWeightedAverage() {
+        averager.onEvent(newDeltaSensorEvent(10, 1000));
+        averager.onEvent(newDeltaSensorEvent(20, 1100));
+        averager.onEvent(newDeltaSensorEvent(30, 1300));
+        averager.onEvent(newDeltaSensorEvent(40, 1600));
+        averager.onEvent(newDeltaSensorEvent(50, 2000));
+        
+        average = averager.getAverage(2000, 0);
+        assertEquals(average.value, (20*0.1d)+(30*0.2d)+(40*0.3d)+(50*0.4d));
+        assertEquals(average.confidence, 1d);
+    }
+
+    @Test
+    public void testConfidenceDecay() {
+        averager.onEvent(newDeltaSensorEvent(10, 1000));
+        averager.onEvent(newDeltaSensorEvent(20, 1250));
+        averager.onEvent(newDeltaSensorEvent(30, 1500));
+        averager.onEvent(newDeltaSensorEvent(40, 1750));
+        averager.onEvent(newDeltaSensorEvent(50, 2000));
+
+        average = averager.getAverage(2250, 0);
+        assertEquals(average.value, (30+40+50)/3d);
+        assertEquals(average.confidence, 0.75d);
+        average = averager.getAverage(2500, 0);
+        assertEquals(average.value, (40+50)/2d);
+        assertEquals(average.confidence, 0.5d);
+        average = averager.getAverage(2750, 0);
+        assertEquals(average.value, 50d);
+        assertEquals(average.confidence, 0.25d);
+        average = averager.getAverage(3000, 0);
+        assertEquals(average.value, 50d);
+        assertEquals(average.confidence, 0d);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/enricher/stock/YamlTimeWeightedDeltaEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/enricher/stock/YamlTimeWeightedDeltaEnricherTest.java b/core/src/test/java/org/apache/brooklyn/enricher/stock/YamlTimeWeightedDeltaEnricherTest.java
new file mode 100644
index 0000000..41b75a7
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/enricher/stock/YamlTimeWeightedDeltaEnricherTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.enricher.stock;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.SubscriptionContext;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.EnricherSpec;
+import org.apache.brooklyn.core.entity.AbstractApplication;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
+import org.apache.brooklyn.core.sensor.BasicSensorEvent;
+import org.apache.brooklyn.enricher.stock.YamlTimeWeightedDeltaEnricher;
+import org.apache.brooklyn.entity.stock.BasicEntity;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class YamlTimeWeightedDeltaEnricherTest {
+    
+    AbstractApplication app;
+    
+    BasicEntity producer;
+
+    AttributeSensor<Integer> intSensor;
+    AttributeSensor<Double> avgSensor, deltaSensor;
+    SubscriptionContext subscription;
+    
+    @BeforeMethod
+    public void before() {
+        app = new AbstractApplication() {};
+        Entities.startManagement(app);
+        producer = app.addChild(EntitySpec.create(BasicEntity.class));
+
+        intSensor = new BasicAttributeSensor<Integer>(Integer.class, "int sensor");
+        deltaSensor = new BasicAttributeSensor<Double>(Double.class, "delta sensor");
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+    
+    @Test
+    public void testMonospaceTimeWeightedDeltaEnricher() {
+        @SuppressWarnings("unchecked")
+        YamlTimeWeightedDeltaEnricher<Integer> delta = producer.addEnricher(EnricherSpec.create(YamlTimeWeightedDeltaEnricher.class)
+            .configure(YamlTimeWeightedDeltaEnricher.PRODUCER, producer)
+            .configure(YamlTimeWeightedDeltaEnricher.SOURCE_SENSOR, intSensor)
+            .configure(YamlTimeWeightedDeltaEnricher.TARGET_SENSOR, deltaSensor));
+        
+        delta.onEvent(newIntSensorEvent(0, 0));
+        assertEquals(producer.getAttribute(deltaSensor), null);
+        delta.onEvent(newIntSensorEvent(0, 1000));
+        assertEquals(producer.getAttribute(deltaSensor), 0d);
+        delta.onEvent(newIntSensorEvent(1, 2000));
+        assertEquals(producer.getAttribute(deltaSensor), 1d);
+        delta.onEvent(newIntSensorEvent(3, 3000));
+        assertEquals(producer.getAttribute(deltaSensor), 2d);
+        delta.onEvent(newIntSensorEvent(8, 4000));
+        assertEquals(producer.getAttribute(deltaSensor), 5d);
+    }
+    
+    protected BasicSensorEvent<Integer> newIntSensorEvent(int value, long timestamp) {
+        return new BasicSensorEvent<Integer>(intSensor, producer, value, timestamp);
+    }
+    
+    @Test
+    public void testVariableTimeWeightedDeltaEnricher() {
+        @SuppressWarnings("unchecked")
+        YamlTimeWeightedDeltaEnricher<Integer> delta = producer.addEnricher(EnricherSpec.create(YamlTimeWeightedDeltaEnricher.class)
+            .configure(YamlTimeWeightedDeltaEnricher.PRODUCER, producer)
+            .configure(YamlTimeWeightedDeltaEnricher.SOURCE_SENSOR, intSensor)
+            .configure(YamlTimeWeightedDeltaEnricher.TARGET_SENSOR, deltaSensor));
+        
+        delta.onEvent(newIntSensorEvent(0, 0));
+        delta.onEvent(newIntSensorEvent(0, 2000));
+        assertEquals(producer.getAttribute(deltaSensor), 0d);
+        delta.onEvent(newIntSensorEvent(3, 5000));
+        assertEquals(producer.getAttribute(deltaSensor), 1d);
+        delta.onEvent(newIntSensorEvent(7, 7000));
+        assertEquals(producer.getAttribute(deltaSensor), 2d);
+        delta.onEvent(newIntSensorEvent(12, 7500));
+        assertEquals(producer.getAttribute(deltaSensor), 10d);
+        delta.onEvent(newIntSensorEvent(15, 9500));
+        assertEquals(producer.getAttribute(deltaSensor), 1.5d);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6f15e8a6/core/src/test/java/org/apache/brooklyn/sensor/enricher/BasicEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/sensor/enricher/BasicEnricherTest.java b/core/src/test/java/org/apache/brooklyn/sensor/enricher/BasicEnricherTest.java
deleted file mode 100644
index 04c183f..0000000
--- a/core/src/test/java/org/apache/brooklyn/sensor/enricher/BasicEnricherTest.java
+++ /dev/null
@@ -1,119 +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.sensor.enricher;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.sensor.Enricher;
-import org.apache.brooklyn.api.sensor.EnricherSpec;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.BasicConfigKey;
-import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
-import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
-import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
-import org.apache.brooklyn.core.test.entity.TestApplication;
-import org.apache.brooklyn.core.test.entity.TestApplicationNoEnrichersImpl;
-import org.apache.brooklyn.sensor.enricher.AbstractEnricher;
-import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.core.flags.SetFromFlag;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.Iterables;
-
-/**
- * Test that enricher can be created and accessed, by construction and by spec
- */
-public class BasicEnricherTest extends BrooklynAppUnitTestSupport {
-    
-    // TODO These tests are a copy of BasicPolicyTest, which is a code smell.
-    // However, the src/main/java code does not contain as much duplication.
-
-    protected void setUpApp() {
-        EntitySpec<TestApplication> appSpec = EntitySpec.create(TestApplication.class, TestApplicationNoEnrichersImpl.class)
-                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, shouldSkipOnBoxBaseDirResolution());
-        app = ApplicationBuilder.newManagedApp(appSpec, mgmt);
-    }
-
-    public static class MyEnricher extends AbstractEnricher {
-        @SetFromFlag("intKey")
-        public static final BasicConfigKey<Integer> INT_KEY = new BasicConfigKey<Integer>(Integer.class, "bkey", "b key");
-        
-        @SetFromFlag("strKey")
-        public static final ConfigKey<String> STR_KEY = new BasicConfigKey<String>(String.class, "akey", "a key");
-        public static final ConfigKey<Integer> INT_KEY_WITH_DEFAULT = new BasicConfigKey<Integer>(Integer.class, "ckey", "c key", 1);
-        public static final ConfigKey<String> STR_KEY_WITH_DEFAULT = new BasicConfigKey<String>(String.class, "strKey", "str key", "str key default");
-        
-        MyEnricher(Map<?,?> flags) {
-            super(flags);
-        }
-        
-        public MyEnricher() {
-            super();
-        }
-    }
-    
-    @Test
-    public void testAddInstance() throws Exception {
-        MyEnricher enricher = new MyEnricher();
-        enricher.setDisplayName("Bob");
-        enricher.config().set(MyEnricher.STR_KEY, "aval");
-        enricher.config().set(MyEnricher.INT_KEY, 2);
-        app.addEnricher(enricher);
-        
-        assertEquals(enricher.getDisplayName(), "Bob");
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
-        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
-    }
-    
-    @Test
-    public void testAddSpec() throws Exception {
-        MyEnricher enricher = app.addEnricher(EnricherSpec.create(MyEnricher.class)
-            .displayName("Bob")
-            .configure(MyEnricher.STR_KEY, "aval").configure(MyEnricher.INT_KEY, 2));
-        
-        assertEquals(enricher.getDisplayName(), "Bob");
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
-        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
-    }
-        
-    @Test
-    public void testTagsFromSpec() throws Exception {
-        MyEnricher enricher = app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(99).uniqueTag("x"));
-
-        assertEquals(enricher.tags().getTags(), MutableSet.of("x", 99));
-        assertEquals(enricher.getUniqueTag(), "x");
-    }
-
-    @Test
-    public void testSameUniqueTagEnricherNotAddedTwice() throws Exception {
-        app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(99).uniqueTag("x"));
-        app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(94).uniqueTag("x"));
-        
-        assertEquals(app.getEnrichers().size(), 1);
-        // the more recent one should dominate
-        Enricher enricher = Iterables.getOnlyElement(app.getEnrichers());
-        Assert.assertTrue(enricher.tags().containsTag(94));
-        Assert.assertFalse(enricher.tags().containsTag(99));
-    }
-
-}