FELIX-1416 Add unit and integration tests for new and fixed configuration behaviour

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@798522 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/scr/pom.xml b/scr/pom.xml
index 098bd75..ce4b849 100644
--- a/scr/pom.xml
+++ b/scr/pom.xml
@@ -57,6 +57,37 @@
             <artifactId>kxml2</artifactId>
             <version>2.2.2</version>
         </dependency>
+        
+        <!-- Integration Testing with Pax Exam -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam</artifactId>
+            <version>0.6.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-junit</artifactId>
+            <version>0.6.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-container-default</artifactId>
+            <version>0.6.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.swissbox</groupId>
+            <artifactId>pax-swissbox-tinybundles</artifactId>
+            <version>1.0.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
@@ -96,6 +127,92 @@
                     </instructions>
                 </configuration>
             </plugin>
+
+            <!--
+                configure default compilation for Java 1.3 and integration
+                test compilation for Java 5 (since integration tests use
+                Java Annotations for Pax Exam)
+            -->
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>test-compile-java5</id>
+                        <goals>
+                            <goal>testCompile</goal>
+                        </goals>
+                        <configuration>
+                            <source>1.5</source>
+                            <target>1.5</target>
+                            <testIncludes>
+                                <testInclude>**/integration/**</testInclude>
+                            </testIncludes>
+                            <testExcludes>
+                                <testExclude>**/impl/**</testExclude>
+                            </testExcludes>
+                        </configuration>
+                    </execution>
+                </executions>
+                <configuration>
+                    <testExcludes>
+                        <testExclude>**/integration/**</testExclude>
+                    </testExcludes>
+                </configuration>
+            </plugin>
+
+            <!--
+                Exclude Integration tests in (default) unit tests and
+                conversely enable integration tests for integration testing
+                only. Helper classes are completely excluded from testing. 
+            -->
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>surefire-it</id>
+                        <phase>integration-test</phase>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                        <configuration>
+                            <excludes>
+                                <exclude>**/components/**</exclude>
+                            </excludes>
+                            <includes>
+                                <include>**/integration/*</include>
+                            </includes>
+                        </configuration>
+                    </execution>
+                </executions>
+                <configuration>
+                    <excludes>
+                        <exclude>**/integration/**</exclude>
+                        <exclude>**/components/**</exclude>
+                        <exclude>**/instances/**</exclude>
+                        <exclude>**/instances2/**</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
+    
+    <!-- repositories for Pax Exam and BND tool -->
+    <repositories>
+    	<repository>
+    		<id>ops4j</id>
+    		<name>ops4j</name>
+    		<url>http://repository.ops4j.org/maven2</url>
+    		<snapshots>
+    			<enabled>false</enabled>
+    		</snapshots>
+    	</repository>
+    	<repository>
+    		<id>aqute</id>
+    		<name>aqute</name>
+    		<url>http://www.aqute.biz/repo</url>
+    		<snapshots>
+    			<enabled>false</enabled>
+    		</snapshots>
+    	</repository>
+    </repositories>
 </project>
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/config/ConfiguredComponentHolderTest.java b/scr/src/test/java/org/apache/felix/scr/impl/config/ConfiguredComponentHolderTest.java
new file mode 100644
index 0000000..29e61dc
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/config/ConfiguredComponentHolderTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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.impl.config;
+
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.scr.impl.BundleComponentActivator;
+import org.apache.felix.scr.impl.manager.ImmediateComponentManager;
+import org.apache.felix.scr.impl.metadata.ComponentMetadata;
+import org.apache.felix.scr.impl.metadata.XmlHandler;
+
+
+public class ConfiguredComponentHolderTest extends TestCase
+{
+
+    public void test_none()
+    {
+        // setup a holder
+        final String name = "test.none";
+        final ComponentMetadata cm = createComponentMetadata( name );
+        final TestingConfiguredComponentHolder holder = new TestingConfiguredComponentHolder( cm );
+
+        // assert single component and no map
+        final ImmediateComponentManager cmgr = getSingleManager( holder );
+        assertNotNull( "Expect single component manager", cmgr );
+        assertNull( "Expect no component manager list", getComponentManagers( holder ) );
+
+        // assert no configuration of single component
+        assertFalse( "Expect no configuration", cmgr.hasConfiguration() );
+    }
+
+
+    public void test_singleton()
+    {
+        // setup a holder
+        final String name = "test.singleton";
+        final ComponentMetadata cm = createComponentMetadata( name );
+        final TestingConfiguredComponentHolder holder = new TestingConfiguredComponentHolder( cm );
+
+        // assert single component and no map
+        final ImmediateComponentManager cmgr = getSingleManager( holder );
+        assertNotNull( "Expect single component manager", cmgr );
+        assertNull( "Expect no component manager list", getComponentManagers( holder ) );
+
+        // assert no configuration of single component
+        assertFalse( "Expect no configuration", cmgr.hasConfiguration() );
+
+        // configure with the singleton configuration
+        final Dictionary config = new Hashtable();
+        config.put( "value", name );
+        holder.configurationUpdated( name, config );
+
+        // assert single component and no map
+        final ImmediateComponentManager cmgrAfterConfig = getSingleManager( holder );
+        assertNotNull( "Expect single component manager", cmgrAfterConfig );
+        assertNull( "Expect no component manager list", getComponentManagers( holder ) );
+
+        // assert configuration of single component
+        assertTrue( "Expect configuration after updating it", cmgrAfterConfig.hasConfiguration() );
+        final Dictionary componentConfig = ( ( MockImmediateComponentManager ) cmgrAfterConfig ).getConfiguration();
+        assertEquals( "Expect exact configuration set", config, componentConfig );
+
+        // unconfigure singleton
+        holder.configurationDeleted( name );
+
+        // assert single component and no map
+        final ImmediateComponentManager cmgrAfterUnconfig = getSingleManager( holder );
+        assertNotNull( "Expect single component manager", cmgrAfterUnconfig );
+        assertNull( "Expect no component manager list", getComponentManagers( holder ) );
+
+        // assert no configuration of single component
+        assertFalse( "Expect no configuration", cmgrAfterUnconfig.hasConfiguration() );
+    }
+
+
+    public void test_factory()
+    {
+        // setup a holder
+        final String name = "test.factory";
+        final ComponentMetadata cm = createComponentMetadata( name );
+        final TestingConfiguredComponentHolder holder = new TestingConfiguredComponentHolder( cm );
+
+        // assert single component and no map
+        final ImmediateComponentManager cmgr = getSingleManager( holder );
+        assertNotNull( "Expect single component manager", cmgr );
+        assertNull( "Expect no component manager list", getComponentManagers( holder ) );
+
+        // assert no configuration of single component
+        assertFalse( "Expect no configuration", cmgr.hasConfiguration() );
+
+        // configure with configuration
+        final String pid1 = "test.factory.0001";
+        final Dictionary config1 = new Hashtable();
+        config1.put( "value", pid1 );
+        holder.configurationUpdated( pid1, config1 );
+
+        // assert single component and single-entry map
+        final ImmediateComponentManager cmgrAfterConfig = getSingleManager( holder );
+        final ImmediateComponentManager[] cmgrsAfterConfig = getComponentManagers( holder );
+        assertNotNull( "Expect single component manager", cmgrAfterConfig );
+        assertNotNull( "Expect component manager list", cmgrsAfterConfig );
+        assertEquals( "Expect one component manager in list", 1, cmgrsAfterConfig.length );
+
+        // add another configuration
+        final String pid2 = "test.factory.0002";
+        final Dictionary config2 = new Hashtable();
+        config1.put( "value", pid2 );
+        holder.configurationUpdated( pid2, config2 );
+
+        // assert single component and single-entry map
+        final ImmediateComponentManager cmgrAfterConfig2 = getSingleManager( holder );
+        final ImmediateComponentManager[] cmgrsAfterConfig2 = getComponentManagers( holder );
+        assertNotNull( "Expect single component manager", cmgrAfterConfig2 );
+        assertNotNull( "Expect component manager list", cmgrsAfterConfig2 );
+        assertEquals( "Expect two component manager in list", 2, cmgrsAfterConfig2.length );
+
+        // remove second configuration
+        holder.configurationDeleted( pid2 );
+
+        // assert single component and single-entry map
+        final ImmediateComponentManager cmgrAfterUnConfig2 = getSingleManager( holder );
+        final ImmediateComponentManager[] cmgrsAfterUnConfig2 = getComponentManagers( holder );
+        assertNotNull( "Expect single component manager", cmgrAfterUnConfig2 );
+        assertNotNull( "Expect component manager list", cmgrsAfterUnConfig2 );
+        assertEquals( "Expect one component manager in list", 1, cmgrsAfterUnConfig2.length );
+
+        // add second config again and remove first config -> replace singleton component
+        holder.configurationUpdated( pid2, config2 );
+        holder.configurationDeleted( pid1 );
+
+        // assert single component and single-entry map
+        final ImmediateComponentManager cmgrAfterConfigUnconfig = getSingleManager( holder );
+        final ImmediateComponentManager[] cmgrsAfterConfigUnconfig = getComponentManagers( holder );
+        assertNotNull( "Expect single component manager", cmgrAfterConfigUnconfig );
+        assertNotNull( "Expect component manager list", cmgrsAfterConfigUnconfig );
+        assertEquals( "Expect one component manager in list", 1, cmgrsAfterConfigUnconfig.length );
+
+        // remove second configuration (leaving no configurations)
+        holder.configurationDeleted( pid2 );
+
+        // assert single component and single-entry map
+        final ImmediateComponentManager cmgrAfterAllUnconfig = getSingleManager( holder );
+        final ImmediateComponentManager[] cmgrsAfterAllUnconfig = getComponentManagers( holder );
+        assertNotNull( "Expect single component manager", cmgrAfterAllUnconfig );
+        assertNull( "Expect no component manager list", cmgrsAfterAllUnconfig );
+
+    }
+
+
+    private static ComponentMetadata createComponentMetadata( String name )
+    {
+        final ComponentMetadata metadata = new ComponentMetadata( XmlHandler.DS_VERSION_1_1 );
+        metadata.setName( name );
+
+        return metadata;
+    }
+
+
+    private static ImmediateComponentManager getSingleManager( ConfiguredComponentHolder holder )
+    {
+        try
+        {
+            final Field f = ConfiguredComponentHolder.class.getDeclaredField( "m_singleComponent" );
+            f.setAccessible( true );
+            return ( ImmediateComponentManager ) f.get( holder );
+        }
+        catch ( Throwable t )
+        {
+            fail( "Cannot access getComponentManagers method: " + t );
+            return null; // compiler does not know about "fail" throwing
+        }
+    }
+
+
+    private static ImmediateComponentManager[] getComponentManagers( ConfiguredComponentHolder holder )
+    {
+        try
+        {
+            final Method m = ConfiguredComponentHolder.class.getDeclaredMethod( "getComponentManagers", new Class[]
+                { Boolean.TYPE } );
+            m.setAccessible( true );
+            return ( ImmediateComponentManager[] ) m.invoke( holder, new Object[]
+                { Boolean.FALSE } );
+        }
+        catch ( Throwable t )
+        {
+            fail( "Cannot access getComponentManagers method: " + t );
+            return null; // compiler does not know about "fail" throwing
+        }
+    }
+
+    private static class TestingConfiguredComponentHolder extends ConfiguredComponentHolder
+    {
+        TestingConfiguredComponentHolder( ComponentMetadata metadata )
+        {
+            super( null, metadata );
+        }
+
+
+        protected ImmediateComponentManager createComponentManager()
+        {
+            return new MockImmediateComponentManager( getActivator(), getComponentMetadata() );
+        }
+    }
+
+    private static class MockImmediateComponentManager extends ImmediateComponentManager
+    {
+
+        private Dictionary m_configuration;
+
+
+        public MockImmediateComponentManager( BundleComponentActivator activator, ComponentMetadata metadata )
+        {
+            super( activator, metadata );
+        }
+
+
+        Dictionary getConfiguration()
+        {
+            return m_configuration;
+        }
+
+
+        public boolean hasConfiguration()
+        {
+            return m_configuration != null;
+        }
+
+
+        public void reconfigure( Dictionary configuration )
+        {
+            this.m_configuration = configuration;
+        }
+    }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/ComponentConfigurationTest.java b/scr/src/test/java/org/apache/felix/scr/integration/ComponentConfigurationTest.java
new file mode 100644
index 0000000..6a238e5
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/integration/ComponentConfigurationTest.java
@@ -0,0 +1,547 @@
+/*
+ * 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 static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+import static org.ops4j.pax.exam.container.def.PaxRunnerOptions.scanDir;
+import static org.ops4j.pax.swissbox.tinybundles.core.TinyBundles.withBnd;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.scr.Component;
+import org.apache.felix.scr.ScrService;
+import org.apache.felix.scr.integration.components.MyTinyBundle;
+import org.apache.felix.scr.integration.components.SimpleComponent;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Inject;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.ops4j.pax.swissbox.tinybundles.core.TinyBundles;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+
+@RunWith(JUnit4TestRunner.class)
+public class ComponentConfigurationTest
+{
+
+    @Inject
+    private BundleContext bundleContext;
+
+    private ServiceTracker scrTracker;
+
+    private ServiceTracker configAdminTracker;
+
+    private static final String PROP_NAME = "theValue";
+    private static final Dictionary<String, String> theConfig;
+
+    static
+    {
+        theConfig = new Hashtable<String, String>();
+        theConfig.put( PROP_NAME, PROP_NAME );
+    }
+
+
+    @Configuration
+    public static Option[] configuration()
+    {
+        return options(
+            provision(
+                scanDir( "target" ).filter( "*.jar" )
+                , mavenBundle( "org.ops4j.pax.swissbox", "pax-swissbox-tinybundles", "1.0.0" )
+                , mavenBundle( "org.apache.felix", "org.apache.felix.configadmin", "1.0.10" )
+            )
+//            , PaxRunnerOptions.vmOption( "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=30303" )
+        //            PaxRunnerOptions.timeout( 0 )
+
+        );
+    }
+
+
+    @Before
+    public void setUp()
+    {
+        scrTracker = new ServiceTracker( bundleContext, ScrService.class.getName(), null );
+        scrTracker.open();
+        configAdminTracker = new ServiceTracker( bundleContext, ConfigurationAdmin.class.getName(), null );
+        configAdminTracker.open();
+    }
+
+
+    @After
+    public void tearDown()
+    {
+        configAdminTracker.close();
+        configAdminTracker = null;
+        scrTracker.close();
+        scrTracker = null;
+    }
+
+
+    @Test
+    public void test_SimpleComponent() throws BundleException
+    {
+        //        final InputStream bundleStream = newBundle()
+        final InputStream bundleStream = new MyTinyBundle()
+            .addResource( "OSGI-INF/components.xml", getClass().getResource( "/integration_test_simple_components.xml" ) )
+            .prepare(
+                withBnd()
+                .set( Constants.BUNDLE_SYMBOLICNAME, "simplecomponent" )
+                .set( Constants.BUNDLE_VERSION, "0.0.11" )
+                .set( Constants.IMPORT_PACKAGE, "org.apache.felix.scr.integration.components" )
+                .set( "Service-Component", "OSGI-INF/components.xml" ) ).build( TinyBundles.asStream() );
+
+        final Bundle bundle = bundleContext.installBundle( "test:SimpleComponent", bundleStream );
+
+        bundle.start();
+
+        try
+        {
+            test_configuration_ignore( "SimpleComponent" );
+            test_configuration_optional( "SimpleComponent" );
+            test_configuration_require( "SimpleComponent" );
+
+            test_dynamic_configuration( "DynamicConfigurationComponent" );
+
+            test_factory_configuration( "FactoryConfigurationComponent" );
+        }
+        finally
+        {
+            bundle.stop();
+            bundle.uninstall();
+        }
+    }
+
+
+    private void test_configuration_ignore( final String componentName )
+    {
+        final String pid = componentName + ".configuration.ignore";
+        final Component component = findComponentByName( pid );
+
+        deleteConfig( pid );
+        delay();
+
+        TestCase.assertNotNull( component );
+        TestCase.assertFalse( component.isDefaultEnabled() );
+
+        TestCase.assertEquals( Component.STATE_DISABLED, component.getState() );
+        TestCase.assertNull( SimpleComponent.INSTANCE );
+
+        component.enable();
+        delay();
+
+        TestCase.assertEquals( Component.STATE_ACTIVE, component.getState() );
+        TestCase.assertNotNull( SimpleComponent.INSTANCE );
+        TestCase.assertNull( SimpleComponent.INSTANCE.getProperty( PROP_NAME ) );
+
+        configure( pid );
+        delay();
+
+        TestCase.assertEquals( Component.STATE_ACTIVE, component.getState() );
+        TestCase.assertNotNull( SimpleComponent.INSTANCE );
+        TestCase.assertNull( SimpleComponent.INSTANCE.getProperty( PROP_NAME ) );
+
+        deleteConfig( pid );
+        delay();
+
+        TestCase.assertEquals( Component.STATE_ACTIVE, component.getState() );
+        TestCase.assertNotNull( SimpleComponent.INSTANCE );
+        TestCase.assertNull( SimpleComponent.INSTANCE.getProperty( PROP_NAME ) );
+
+        component.disable();
+        delay();
+
+        TestCase.assertEquals( Component.STATE_DISABLED, component.getState() );
+        TestCase.assertNull( SimpleComponent.INSTANCE );
+    }
+
+
+    private void test_configuration_optional( final String componentName )
+    {
+        final String pid = componentName + ".configuration.optional";
+        final Component component = findComponentByName( pid );
+
+        deleteConfig( pid );
+        delay();
+
+        TestCase.assertNotNull( component );
+        TestCase.assertFalse( component.isDefaultEnabled() );
+
+        TestCase.assertEquals( Component.STATE_DISABLED, component.getState() );
+        TestCase.assertNull( SimpleComponent.INSTANCE );
+
+        component.enable();
+        delay();
+
+        TestCase.assertEquals( Component.STATE_ACTIVE, component.getState() );
+        TestCase.assertNotNull( SimpleComponent.INSTANCE );
+        TestCase.assertNull( SimpleComponent.INSTANCE.getProperty( PROP_NAME ) );
+
+        configure( pid );
+        delay();
+
+        TestCase.assertEquals( Component.STATE_ACTIVE, component.getState() );
+        TestCase.assertNotNull( SimpleComponent.INSTANCE );
+        TestCase.assertEquals( PROP_NAME, SimpleComponent.INSTANCE.getProperty( PROP_NAME ) );
+
+        deleteConfig( pid );
+        delay();
+
+        TestCase.assertEquals( Component.STATE_ACTIVE, component.getState() );
+        TestCase.assertNotNull( SimpleComponent.INSTANCE );
+        TestCase.assertNull( SimpleComponent.INSTANCE.getProperty( PROP_NAME ) );
+
+        component.disable();
+        delay();
+
+        TestCase.assertEquals( Component.STATE_DISABLED, component.getState() );
+        TestCase.assertNull( SimpleComponent.INSTANCE );
+    }
+
+
+    private void test_configuration_require( final String componentName )
+    {
+        final String pid = componentName + ".configuration.require";
+        final Component component = findComponentByName( pid );
+
+        deleteConfig( pid );
+        delay();
+
+        TestCase.assertNotNull( component );
+        TestCase.assertFalse( component.isDefaultEnabled() );
+
+        TestCase.assertEquals( Component.STATE_DISABLED, component.getState() );
+        TestCase.assertNull( SimpleComponent.INSTANCE );
+
+        component.enable();
+        delay();
+
+        TestCase.assertEquals( Component.STATE_UNSATISFIED, component.getState() );
+        TestCase.assertNull( SimpleComponent.INSTANCE );
+
+        configure( pid );
+        delay();
+
+        TestCase.assertEquals( Component.STATE_ACTIVE, component.getState() );
+        TestCase.assertNotNull( SimpleComponent.INSTANCE );
+        TestCase.assertEquals( PROP_NAME, SimpleComponent.INSTANCE.getProperty( PROP_NAME ) );
+
+        deleteConfig( pid );
+        delay();
+
+        TestCase.assertEquals( Component.STATE_UNSATISFIED, component.getState() );
+        TestCase.assertNull( SimpleComponent.INSTANCE );
+
+        component.disable();
+        delay();
+
+        TestCase.assertEquals( Component.STATE_DISABLED, component.getState() );
+        TestCase.assertNull( SimpleComponent.INSTANCE );
+    }
+
+
+    private void test_dynamic_configuration( final String componentName )
+    {
+        final String pid = componentName;
+        final Component component = findComponentByName( pid );
+
+        deleteConfig( pid );
+        delay();
+
+        TestCase.assertNotNull( component );
+        TestCase.assertFalse( component.isDefaultEnabled() );
+
+        TestCase.assertEquals( Component.STATE_DISABLED, component.getState() );
+        TestCase.assertNull( SimpleComponent.INSTANCE );
+
+        component.enable();
+        delay();
+
+        TestCase.assertEquals( Component.STATE_ACTIVE, component.getState() );
+        TestCase.assertNotNull( SimpleComponent.INSTANCE );
+        TestCase.assertNull( SimpleComponent.INSTANCE.getProperty( PROP_NAME ) );
+
+        final SimpleComponent instance = SimpleComponent.INSTANCE;
+
+        configure( pid );
+        delay();
+
+        TestCase.assertEquals( Component.STATE_ACTIVE, component.getState() );
+        TestCase.assertEquals( instance, SimpleComponent.INSTANCE );
+        TestCase.assertEquals( PROP_NAME, SimpleComponent.INSTANCE.getProperty( PROP_NAME ) );
+
+        deleteConfig( pid );
+        delay();
+
+        TestCase.assertEquals( Component.STATE_ACTIVE, component.getState() );
+        TestCase.assertEquals( instance, SimpleComponent.INSTANCE );
+        TestCase.assertNull( SimpleComponent.INSTANCE.getProperty( PROP_NAME ) );
+
+        component.disable();
+        delay();
+
+        TestCase.assertEquals( Component.STATE_DISABLED, component.getState() );
+        TestCase.assertNull( SimpleComponent.INSTANCE );
+    }
+
+
+    private void test_factory_configuration( final String componentName )
+    {
+        final String factoryPid = componentName;
+
+        deleteFactoryConfigurations( factoryPid );
+        delay();
+
+        // one single component exists without configuration
+        final Component[] noConfigurations = findComponentsByName( factoryPid );
+        TestCase.assertNotNull( noConfigurations );
+        TestCase.assertEquals( 1, noConfigurations.length );
+        TestCase.assertEquals( Component.STATE_DISABLED, noConfigurations[0].getState() );
+        TestCase.assertTrue( SimpleComponent.INSTANCES.isEmpty() );
+
+        // enable the component, configuration required, hence unsatisfied
+        noConfigurations[0].enable();
+        delay();
+
+        final Component[] enabledNoConfigs = findComponentsByName( factoryPid );
+        TestCase.assertNotNull( enabledNoConfigs );
+        TestCase.assertEquals( 1, enabledNoConfigs.length );
+        TestCase.assertEquals( Component.STATE_UNSATISFIED, enabledNoConfigs[0].getState() );
+        TestCase.assertTrue( SimpleComponent.INSTANCES.isEmpty() );
+
+        // create two factory configurations expecting two components
+        final String pid0 = createFactoryConfiguration( factoryPid );
+        final String pid1 = createFactoryConfiguration( factoryPid );
+        delay();
+
+        // expect two components, only first is active, second is disabled
+        final Component[] twoConfigs = findComponentsByName( factoryPid );
+        TestCase.assertNotNull( twoConfigs );
+        TestCase.assertEquals( 2, twoConfigs.length );
+        TestCase.assertEquals( Component.STATE_ACTIVE, twoConfigs[0].getState() );
+        TestCase.assertEquals( Component.STATE_DISABLED, twoConfigs[1].getState() );
+        TestCase.assertEquals( 1, SimpleComponent.INSTANCES.size() );
+        TestCase.assertTrue( SimpleComponent.INSTANCES.containsKey( pid0 ) );
+        TestCase.assertFalse( SimpleComponent.INSTANCES.containsKey( pid1 ) );
+
+        // enable second component
+        twoConfigs[1].enable();
+        delay();
+
+        // ensure both components active
+        TestCase.assertEquals( Component.STATE_ACTIVE, twoConfigs[0].getState() );
+        TestCase.assertEquals( Component.STATE_ACTIVE, twoConfigs[1].getState() );
+        TestCase.assertEquals( 2, SimpleComponent.INSTANCES.size() );
+        TestCase.assertTrue( SimpleComponent.INSTANCES.containsKey( pid0 ) );
+        TestCase.assertTrue( SimpleComponent.INSTANCES.containsKey( pid1 ) );
+
+        // delete a configuration
+        deleteConfig( pid0 );
+        delay();
+
+        // expect one component
+        final Component[] oneConfig = findComponentsByName( factoryPid );
+        TestCase.assertNotNull( oneConfig );
+        TestCase.assertEquals( 1, oneConfig.length );
+        TestCase.assertEquals( Component.STATE_ACTIVE, oneConfig[0].getState() );
+        TestCase.assertEquals( 1, SimpleComponent.INSTANCES.size() );
+        TestCase.assertFalse( SimpleComponent.INSTANCES.containsKey( pid0 ) );
+        TestCase.assertTrue( SimpleComponent.INSTANCES.containsKey( pid1 ) );
+
+        // delete second configuration
+        deleteConfig( pid1 );
+        delay();
+
+        // expect a single unsatisifed component
+        final Component[] configsDeleted= findComponentsByName( factoryPid );
+        TestCase.assertNotNull( configsDeleted );
+        TestCase.assertEquals( 1, configsDeleted.length );
+        TestCase.assertEquals( Component.STATE_UNSATISFIED, configsDeleted[0].getState() );
+        TestCase.assertEquals( 0, SimpleComponent.INSTANCES.size() );
+        TestCase.assertFalse( SimpleComponent.INSTANCES.containsKey( pid0 ) );
+        TestCase.assertFalse( SimpleComponent.INSTANCES.containsKey( pid1 ) );
+
+    }
+
+
+    private Component findComponentByName( String name )
+    {
+        ScrService scr = ( ScrService ) scrTracker.getService();
+        if ( scr != null )
+        {
+            Component[] components = scr.getComponents();
+            if ( components != null )
+            {
+                for ( Component component : components )
+                {
+                    if ( name.equals( component.getName() ) )
+                    {
+                        return component;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+
+    private Component[] findComponentsByName( String name )
+    {
+        ScrService scr = ( ScrService ) scrTracker.getService();
+        if ( scr != null )
+        {
+            List<Component> cList = new ArrayList<Component>();
+            Component[] components = scr.getComponents();
+            if ( components != null )
+            {
+                for ( Component component : components )
+                {
+                    if ( name.equals( component.getName() ) )
+                    {
+                        cList.add( component );
+                    }
+                }
+            }
+
+            if ( !cList.isEmpty() )
+            {
+                return cList.toArray( new Component[cList.size()] );
+            }
+        }
+
+        return null;
+    }
+
+
+    private static void delay()
+    {
+        try
+        {
+            Thread.sleep( 300 );
+        }
+        catch ( InterruptedException ie )
+        {
+            // dont care
+        }
+    }
+
+
+    private ConfigurationAdmin getConfigurationAdmin()
+    {
+        ConfigurationAdmin ca = ( ConfigurationAdmin ) configAdminTracker.getService();
+        if ( ca == null )
+        {
+            TestCase.fail( "Missing ConfigurationAdmin service" );
+        }
+        return ca;
+    }
+
+
+    private void configure( String pid )
+    {
+        ConfigurationAdmin ca = getConfigurationAdmin();
+        try
+        {
+            org.osgi.service.cm.Configuration config = ca.getConfiguration( pid, null );
+            config.update( theConfig );
+        }
+        catch ( IOException ioe )
+        {
+            TestCase.fail( "Failed updating configuration " + pid + ": " + ioe.toString() );
+        }
+    }
+
+
+    private void deleteConfig( String pid )
+    {
+        ConfigurationAdmin ca = getConfigurationAdmin();
+        try
+        {
+            org.osgi.service.cm.Configuration config = ca.getConfiguration( pid );
+            config.delete();
+        }
+        catch ( IOException ioe )
+        {
+            TestCase.fail( "Failed deleting configuration " + pid + ": " + ioe.toString() );
+        }
+    }
+
+
+    private String createFactoryConfiguration( String factoryPid )
+    {
+        ConfigurationAdmin ca = getConfigurationAdmin();
+        try
+        {
+            org.osgi.service.cm.Configuration config = ca.createFactoryConfiguration( factoryPid, null );
+            config.update( theConfig );
+            return config.getPid();
+        }
+        catch ( IOException ioe )
+        {
+            TestCase.fail( "Failed updating factory configuration " + factoryPid + ": " + ioe.toString() );
+            return null;
+        }
+    }
+
+
+    private void deleteFactoryConfigurations( String factoryPid )
+    {
+        ConfigurationAdmin ca = getConfigurationAdmin();
+        try
+        {
+            final String filter = "(service.factoryPid=" + factoryPid + ")";
+            org.osgi.service.cm.Configuration[] configs = ca.listConfigurations( filter );
+            if ( configs != null )
+            {
+                for ( org.osgi.service.cm.Configuration configuration : configs )
+                {
+                    configuration.delete();
+                }
+            }
+        }
+        catch ( InvalidSyntaxException ise )
+        {
+            // unexpected
+        }
+        catch ( IOException ioe )
+        {
+            TestCase.fail( "Failed deleting configurations " + factoryPid + ": " + ioe.toString() );
+        }
+    }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/MyTinyBundle.java b/scr/src/test/java/org/apache/felix/scr/integration/components/MyTinyBundle.java
new file mode 100644
index 0000000..caf5e3f
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/MyTinyBundle.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.ops4j.pax.swissbox.tinybundles.core.BuildableBundle;
+import org.ops4j.pax.swissbox.tinybundles.core.TinyBundle;
+import org.ops4j.pax.swissbox.tinybundles.core.metadata.RawBuilder;
+
+public class MyTinyBundle implements TinyBundle {
+
+    private Map<String, URL> m_resources = new HashMap<String, URL>();
+
+    @SuppressWarnings("unchecked")
+    public TinyBundle addClass( Class clazz )
+    {
+        String name = clazz.getName().replaceAll( "\\.", "/" ) + ".class";
+        addResource( name, clazz.getResource( "/" + name ) );
+        return this;
+    }
+
+    public TinyBundle addResource( String name, URL url )
+    {
+        m_resources.put( name, url );
+        return this;
+    }
+
+    public BuildableBundle prepare( BuildableBundle builder )
+    {
+        return builder.setResources( m_resources );
+    }
+
+    public BuildableBundle prepare()
+    {
+        return new RawBuilder().setResources( m_resources );
+    }
+
+}
\ No newline at end of file
diff --git a/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java b/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java
new file mode 100644
index 0000000..ad0ecbe
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/integration/components/SimpleComponent.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+
+
+public class SimpleComponent
+{
+
+    public static SimpleComponent INSTANCE;
+
+    public static final Map<String, SimpleComponent> INSTANCES = new HashMap<String, SimpleComponent>();
+
+    private Map<?, ?> m_config;
+
+
+    @SuppressWarnings("unused")
+    private void activate( Map<?, ?> config )
+    {
+        INSTANCE = this;
+        INSTANCES.put( (String) config.get( Constants.SERVICE_PID), this );
+        setConfig( config );
+    }
+
+
+    @SuppressWarnings("unused")
+    private void configure( ComponentContext context )
+    {
+        setConfig( context.getProperties() );
+    }
+
+
+    @SuppressWarnings("unused")
+    private void deactivate()
+    {
+        INSTANCES.remove( getProperty( Constants.SERVICE_PID ));
+        INSTANCE = null;
+    }
+
+
+    protected void setConfig( Map<?, ?> config )
+    {
+        m_config = config;
+    }
+
+
+    protected void setConfig( Dictionary<?, ?> config )
+    {
+        Map<Object, Object> configMap = new HashMap<Object, Object>();
+        for ( Enumeration<?> ce = config.elements(); ce.hasMoreElements(); )
+        {
+            Object key = ce.nextElement();
+            Object value = config.get( key );
+            configMap.put( key, value );
+        }
+        m_config = configMap;
+    }
+
+
+    public Object getProperty( Object name )
+    {
+        return m_config.get( name );
+    }
+}
diff --git a/scr/src/test/resources/integration_test_simple_components.xml b/scr/src/test/resources/integration_test_simple_components.xml
new file mode 100644
index 0000000..bf0da5c
--- /dev/null
+++ b/scr/src/test/resources/integration_test_simple_components.xml
@@ -0,0 +1,55 @@
+<?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">
+
+    <!-- component ignores configuration -->
+    <scr:component name="SimpleComponent.configuration.ignore"
+        enabled="false"
+        configuration-policy="ignore">
+        <implementation class="org.apache.felix.scr.integration.components.SimpleComponent" />
+    </scr:component>
+
+    <!-- component takes configuration as available -->
+    <scr:component name="SimpleComponent.configuration.optional"
+        enabled="false"
+        configuration-policy="optional" >
+        <implementation class="org.apache.felix.scr.integration.components.SimpleComponent" />
+    </scr:component>
+
+    <!-- component requires configuration -->
+    <scr:component name="SimpleComponent.configuration.require"
+        enabled="false"
+        configuration-policy="require" >
+        <implementation class="org.apache.felix.scr.integration.components.SimpleComponent" />
+    </scr:component>
+
+    <!-- component dynamically updates configuration -->
+    <scr:component name="DynamicConfigurationComponent"
+        enabled="false" modified="configure">
+        <implementation class="org.apache.felix.scr.integration.components.SimpleComponent" />
+    </scr:component>
+
+    <!-- component instances created by factory configuration -->
+    <scr:component name="FactoryConfigurationComponent"
+        enabled="false"
+        configuration-policy="require" >
+        <implementation class="org.apache.felix.scr.integration.components.SimpleComponent" />
+    </scr:component>
+</components>