FELIX-3557 tests for circular dependency behavior

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1350816 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/CircularReferenceTest.java b/scr/src/test/java/org/apache/felix/scr/integration/CircularReferenceTest.java
new file mode 100644
index 0000000..59a460c
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/integration/CircularReferenceTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.felix.scr.integration;
+
+import junit.framework.TestCase;
+import org.apache.felix.scr.Component;
+import org.apache.felix.scr.integration.components.circular.A;
+import org.apache.felix.scr.integration.components.circular.B;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+/**
+ * @version $Rev:$ $Date:$
+ */
+@RunWith(JUnit4TestRunner.class)
+public class CircularReferenceTest extends ComponentTestBase
+{
+
+    static
+    {
+        // uncomment to enable debugging of this test class
+//        paxRunnerVmOption = DEBUG_VM_OPTION;
+
+        descriptorFile = "/integration_test_circular.xml";
+    }
+
+
+    /**
+     * A and B have mandatory dependencies on each other.  Neither should start.
+     */
+    @Test
+    public void test_A11_B11()
+    {
+        String componentNameA = "1.A.1.1.dynamic";
+        final Component componentA = findComponentByName( componentNameA );
+        TestCase.assertNotNull( componentA );
+        TestCase.assertEquals( Component.STATE_UNSATISFIED, componentA.getState() );
+
+        String componentNameB = "1.B.1.1.dynamic";
+        final Component componentB = findComponentByName( componentNameB );
+        TestCase.assertNotNull( componentB );
+        TestCase.assertEquals( Component.STATE_UNSATISFIED, componentB.getState() );
+
+
+    }
+
+    /**
+     * A > 1.1 > B > 0..n > A Both should start (A first), but B should not have an A reference.
+     */
+    @Test
+    public void test_A11_B0n_immediate_A_first()
+    {
+        String componentNameA = "2.A.1.1.dynamic";
+        final Component componentA = findComponentByName( componentNameA );
+        TestCase.assertNotNull( componentA );
+        TestCase.assertEquals( Component.STATE_ACTIVE, componentA.getState() );
+        A a = ( A ) componentA.getComponentInstance().getInstance();
+        assertEquals( 1, a.getBs().size());
+
+        String componentNameB = "2.B.0.n.dynamic";
+        final Component componentB = findComponentByName( componentNameB );
+        TestCase.assertNotNull( componentB );
+        TestCase.assertEquals( Component.STATE_ACTIVE, componentB.getState() );
+        B b = ( B ) componentB.getComponentInstance().getInstance();
+        //Currently felix binds A.  I think this is wrong.
+//        assertEquals( 0, b.getAs().size() );
+    }
+    /**
+     * A > 1.1 > B > 0..n > A Both should start (B first), and B should have an A reference.
+     */
+    @Test
+    public void test_A11_B0n_immediate_B_first()
+    {
+        String componentNameA = "3.A.1.1.dynamic";
+        final Component componentA = findComponentByName( componentNameA );
+        TestCase.assertNotNull( componentA );
+        TestCase.assertEquals( Component.STATE_ACTIVE, componentA.getState() );
+        A a = ( A ) componentA.getComponentInstance().getInstance();
+        assertEquals( 1, a.getBs().size());
+
+        String componentNameB = "3.B.0.n.dynamic";
+        final Component componentB = findComponentByName( componentNameB );
+        TestCase.assertNotNull( componentB );
+        TestCase.assertEquals( Component.STATE_ACTIVE, componentB.getState() );
+        B b = ( B ) componentB.getComponentInstance().getInstance();
+        assertEquals( 1, b.getAs().size() );
+    }
+    /**
+     * A > 1.1 > B > 0..n > A Both should start, but B should not have an A reference.
+     */
+    @Test
+    public void test_A11_B0n_delayed_A_first() throws InvalidSyntaxException
+    {
+        String componentNameA = "4.A.1.1.dynamic";
+        final Component componentA = findComponentByName( componentNameA );
+        TestCase.assertNotNull( componentA );
+        TestCase.assertEquals( Component.STATE_REGISTERED, componentA.getState() );
+
+        String componentNameB = "4.B.0.n.dynamic";
+        final Component componentB = findComponentByName( componentNameB );
+        TestCase.assertNotNull( componentB );
+        TestCase.assertEquals( Component.STATE_REGISTERED, componentB.getState() );
+
+        ServiceReference[] serviceReferences = bundleContext.getServiceReferences( A.class.getName(), "(service.pid=" + componentNameA + ")" );
+        TestCase.assertEquals( 1, serviceReferences.length );
+        ServiceReference serviceReference = serviceReferences[0];
+        Object service = bundleContext.getService( serviceReference );
+        assertNotNull( service );
+
+
+        A a = ( A ) componentA.getComponentInstance().getInstance();
+        assertEquals( 1, a.getBs().size() );
+        B b = ( B ) componentB.getComponentInstance().getInstance();
+        assertEquals( 0, b.getAs().size() );
+    }
+    /**
+     * A > 1.1 > B > 0..n > A Both should start, but B should not have an A reference.
+     */
+    @Test
+    public void test_A11_B0n_delayed_B_first() throws InvalidSyntaxException
+    {
+        String componentNameA = "4.A.1.1.dynamic";
+        final Component componentA = findComponentByName( componentNameA );
+        TestCase.assertNotNull( componentA );
+        TestCase.assertEquals( Component.STATE_REGISTERED, componentA.getState() );
+
+        String componentNameB = "4.B.0.n.dynamic";
+        final Component componentB = findComponentByName( componentNameB );
+        TestCase.assertNotNull( componentB );
+        TestCase.assertEquals( Component.STATE_REGISTERED, componentB.getState() );
+
+        ServiceReference[] serviceReferencesB = bundleContext.getServiceReferences( B.class.getName(), "(service.pid=" + componentNameB + ")" );
+        TestCase.assertEquals( 1, serviceReferencesB.length );
+        ServiceReference serviceReferenceB = serviceReferencesB[0];
+        Object serviceB = bundleContext.getService( serviceReferenceB );
+        assertNotNull( serviceB );
+
+        ServiceReference[] serviceReferencesA = bundleContext.getServiceReferences( A.class.getName(), "(service.pid=" + componentNameA + ")" );
+        TestCase.assertEquals( 1, serviceReferencesA.length );
+        ServiceReference serviceReferenceA = serviceReferencesA[0];
+        Object serviceA = bundleContext.getService( serviceReferenceA );
+        assertNotNull( serviceA );
+
+
+        A a = ( A ) componentA.getComponentInstance().getInstance();
+        assertEquals( 1, a.getBs().size() );
+        B b = ( B ) componentB.getComponentInstance().getInstance();
+        assertEquals( 0, b.getAs().size() );
+
+
+        //disabling (removing the A service registration) and re-enabling will
+        //result in a service event to B, so B will bind A.
+        componentA.disable();
+        delay();
+        componentA.enable();
+        delay();
+        ServiceReference[] serviceReferencesA1 = bundleContext.getServiceReferences( A.class.getName(), "(service.pid=" + componentNameA + ")" );
+        TestCase.assertEquals( 1, serviceReferencesA1.length );
+        ServiceReference serviceReferenceA1 = serviceReferencesA1[0];
+        Object serviceA1 = bundleContext.getService( serviceReferenceA1 );
+        assertNotNull( serviceA1 );
+
+        A a1 = ( A ) componentA.getComponentInstance().getInstance();
+        assertEquals( 1, a1.getBs().size() );
+        B b1 = ( B ) componentB.getComponentInstance().getInstance();
+        assertEquals( 1, b1.getAs().size() );
+
+    }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java b/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java
index b9d87e7..a9b4f92 100644
--- a/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java
+++ b/scr/src/test/java/org/apache/felix/scr/integration/ComponentTestBase.java
@@ -98,7 +98,7 @@
 
     @ProbeBuilder
     public TestProbeBuilder extendProbe(TestProbeBuilder builder) {
-        builder.setHeader("Export-Package", "org.apache.felix.scr.integration.components,org.apache.felix.scr.integration.components.activatesignature");
+        builder.setHeader("Export-Package", "org.apache.felix.scr.integration.components,org.apache.felix.scr.integration.components.activatesignature,org.apache.felix.scr.integration.components.circular");
         builder.setHeader("Import-Package", "org.apache.felix.scr,org.apache.felix.scr.component;mandatory:=\"status\"; status=\"provisional\"");
         builder.setHeader("Bundle-ManifestVersion", "2");
         return builder;
@@ -352,7 +352,7 @@
                 .set(Constants.BUNDLE_SYMBOLICNAME, "simplecomponent")
                 .set(Constants.BUNDLE_VERSION, "0.0.11")
                 .set(Constants.IMPORT_PACKAGE,
-                        "org.apache.felix.scr.integration.components,org.apache.felix.scr.integration.components.activatesignature")
+                        "org.apache.felix.scr.integration.components,org.apache.felix.scr.integration.components.activatesignature,org.apache.felix.scr.integration.components.circular")
                 .set("Service-Component", "OSGI-INF/components.xml")
             .build(withBnd());
 
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/circular/A.java b/scr/src/test/java/org/apache/felix/scr/integration/components/circular/A.java
new file mode 100644
index 0000000..db9e4ac
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/circular/A.java
@@ -0,0 +1,58 @@
+/*
+ * 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.felix.scr.integration.components.circular;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.service.component.ComponentContext;
+
+/**
+ * @version $Rev:$ $Date:$
+ */
+public class A
+{
+
+    private List<B> bs = new ArrayList<B>();
+
+    private boolean activated;
+
+    private void activate(ComponentContext cc)
+    {
+        activated = true;
+    }
+
+    private void setB(B b)
+    {
+        bs.add( b );
+    }
+
+    private void unsetB(B b)
+    {
+        bs.remove( b );
+    }
+
+    public List<B> getBs()
+    {
+        return bs;
+    }
+
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/circular/B.java b/scr/src/test/java/org/apache/felix/scr/integration/components/circular/B.java
new file mode 100644
index 0000000..e9dd112
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/circular/B.java
@@ -0,0 +1,58 @@
+/*
+ * 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.felix.scr.integration.components.circular;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.service.component.ComponentContext;
+
+/**
+ * @version $Rev:$ $Date:$
+ */
+public class B
+{
+
+    private List<A> as = new ArrayList<A>();
+
+    private boolean activated;
+
+    private void activate(ComponentContext cc)
+    {
+        activated = true;
+    }
+
+    private void setA(A b)
+    {
+        as.add( b );
+    }
+
+    private void unsetA(A b)
+    {
+        as.remove( b );
+    }
+
+    public List<A> getAs()
+    {
+        return as;
+    }
+
+}
diff --git a/scr/src/test/resources/integration_test_circular.xml b/scr/src/test/resources/integration_test_circular.xml
new file mode 100644
index 0000000..2a408c8
--- /dev/null
+++ b/scr/src/test/resources/integration_test_circular.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+
+    <!-- A, B both 1.1 dynamic.  Neither should register or activate -->
+    <scr:component name="1.A.1.1.dynamic"
+        enabled="true"
+        configuration-policy="ignore">
+        <implementation class="org.apache.felix.scr.integration.components.circular.A" />
+        <service>
+            <provide interface="org.apache.felix.scr.integration.components.circular.A" />
+        </service>
+        <reference
+            name="b"
+            interface="org.apache.felix.scr.integration.components.circular.B"
+            cardinality="1..1"
+            policy="dynamic"
+            bind="setB"
+            unbind="unsetB"
+            target="(service.pid=1.B.1.1.dynamic)"
+        />
+        <property name="service.pid" value="1.A.1.1.dynamic" />
+    </scr:component>
+    <scr:component name="1.B.1.1.dynamic"
+        enabled="true"
+        configuration-policy="ignore">
+        <implementation class="org.apache.felix.scr.integration.components.circular.B" />
+        <service>
+            <provide interface="org.apache.felix.scr.integration.components.circular.B" />
+        </service>
+        <reference
+            name="a"
+            interface="org.apache.felix.scr.integration.components.circular.A"
+            cardinality="1..1"
+            policy="dynamic"
+            bind="setA"
+            unbind="unsetA"
+            target="(service.pid=1.A.1.1.dynamic)"
+        />
+        <property name="service.pid" value="1.B.1.1.dynamic" />
+    </scr:component>
+
+    <!-- A 1.1 dynamic. B 0..n dynamic  both immediate.  Both should start (A first) and B should have no reference to A -->
+    <scr:component name="2.A.1.1.dynamic"
+        enabled="true"
+        immediate="true"
+        configuration-policy="ignore">
+        <implementation class="org.apache.felix.scr.integration.components.circular.A" />
+        <service>
+            <provide interface="org.apache.felix.scr.integration.components.circular.A" />
+        </service>
+        <reference
+            name="b"
+            interface="org.apache.felix.scr.integration.components.circular.B"
+            cardinality="1..1"
+            policy="dynamic"
+            bind="setB"
+            unbind="unsetB"
+            target="(service.pid=2.B.0.n.dynamic)"
+        />
+        <property name="service.pid" value="2.A.1.1.dynamic" />
+    </scr:component>
+    <scr:component name="2.B.0.n.dynamic"
+        enabled="true"
+        immediate="true"
+        configuration-policy="ignore">
+        <implementation class="org.apache.felix.scr.integration.components.circular.B" />
+        <service>
+            <provide interface="org.apache.felix.scr.integration.components.circular.B" />
+        </service>
+        <reference
+            name="a"
+            interface="org.apache.felix.scr.integration.components.circular.A"
+            cardinality="0..n"
+            policy="dynamic"
+            bind="setA"
+            unbind="unsetA"
+            target="(service.pid=2.A.1.1.dynamic)"
+        />
+        <property name="service.pid" value="2.B.0.n.dynamic" />
+    </scr:component>
+
+    <!-- A 1.1 dynamic. B 0..n dynamic  both immediate.  Both should start (B first) and B should have a reference to A -->
+    <scr:component name="3.B.0.n.dynamic"
+        enabled="true"
+        immediate="true"
+        configuration-policy="ignore">
+        <implementation class="org.apache.felix.scr.integration.components.circular.B" />
+        <service>
+            <provide interface="org.apache.felix.scr.integration.components.circular.B" />
+        </service>
+        <reference
+            name="a"
+            interface="org.apache.felix.scr.integration.components.circular.A"
+            cardinality="0..n"
+            policy="dynamic"
+            bind="setA"
+            unbind="unsetA"
+            target="(service.pid=3.A.1.1.dynamic)"
+        />
+        <property name="service.pid" value="3.B.0.n.dynamic" />
+    </scr:component>
+    <scr:component name="3.A.1.1.dynamic"
+        enabled="true"
+        immediate="true"
+        configuration-policy="ignore">
+        <implementation class="org.apache.felix.scr.integration.components.circular.A" />
+        <service>
+            <provide interface="org.apache.felix.scr.integration.components.circular.A" />
+        </service>
+        <reference
+            name="b"
+            interface="org.apache.felix.scr.integration.components.circular.B"
+            cardinality="1..1"
+            policy="dynamic"
+            bind="setB"
+            unbind="unsetB"
+            target="(service.pid=3.B.0.n.dynamic)"
+        />
+        <property name="service.pid" value="3.A.1.1.dynamic" />
+    </scr:component>
+
+    <!-- A 1.1 dynamic. B 0..n dynamic  both delayed.  Both should start and B should have no reference to A -->
+    <scr:component name="4.A.1.1.dynamic"
+        enabled="true"
+        immediate="false"
+        configuration-policy="ignore">
+        <implementation class="org.apache.felix.scr.integration.components.circular.A" />
+        <service>
+            <provide interface="org.apache.felix.scr.integration.components.circular.A" />
+        </service>
+        <reference
+            name="b"
+            interface="org.apache.felix.scr.integration.components.circular.B"
+            cardinality="1..1"
+            policy="dynamic"
+            bind="setB"
+            unbind="unsetB"
+            target="(service.pid=4.B.0.n.dynamic)"
+        />
+        <property name="service.pid" value="4.A.1.1.dynamic" />
+    </scr:component>
+    <scr:component name="4.B.0.n.dynamic"
+        enabled="true"
+        immediate="false"
+        configuration-policy="ignore">
+        <implementation class="org.apache.felix.scr.integration.components.circular.B" />
+        <service>
+            <provide interface="org.apache.felix.scr.integration.components.circular.B" />
+        </service>
+        <reference
+            name="a"
+            interface="org.apache.felix.scr.integration.components.circular.A"
+            cardinality="0..n"
+            policy="dynamic"
+            bind="setA"
+            unbind="unsetA"
+            target="(service.pid=4.A.1.1.dynamic)"
+        />
+        <property name="service.pid" value="4.B.0.n.dynamic" />
+    </scr:component>
+
+
+</components>