SLING-837 copy JCR Install testcases to the launchpad/testing module
and include them in the jcrinstall-tests profile

git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk@736674 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 18a85fe..8b32e6f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,6 +80,33 @@
             started. Useful to manually test the integration testing webapp.
         -->
         <integration.test.wait>false</integration.test.wait>
+        
+        <!--
+            Defines the part of the tree, which must not be compiled in
+            normal test scenarios. The jcrinstall-tests profile will
+            compile these classes to be included in a full-test scenario. 
+        -->
+        <jcrinstall.integration.test.code.path>
+            **/jcrinstall/**
+        </jcrinstall.integration.test.code.path>
+
+        <!--
+            Where to copy our test bundles
+            Used when profile jcrinstall-tests is active
+         -->
+        <sling.testbundles.path>${project.build.directory}/testbundles</sling.testbundles.path>
+        
+        <!--
+            Can be used to run tests with more or less bundles
+            Used when profile jcrinstall-tests is active
+         -->
+        <sling.test.scale.factor>1</sling.test.scale.factor>
+        
+        <!--
+             How long to wait for the expected number of bundles to be active, in tests
+            Used when profile jcrinstall-tests is active
+         -->
+        <sling.test.bundles.wait.seconds>20</sling.test.bundles.wait.seconds>
     </properties>
 
     <build>
@@ -214,6 +241,25 @@
                 </executions>
             </plugin>
 
+            <!--
+                Exclude the JCRInstall test classes from default build
+                See jcrinstall-tests for the setup of compilation of these
+                classes for a full test
+            -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <sumpf>gurk</sumpf>
+                    <testExcludes>
+                        <testExclude>
+                            ${jcrinstall.integration.test.code.path}
+                        </testExclude>
+                    </testExcludes>
+                    <verbose>true</verbose>
+                </configuration>
+            </plugin>
+
             <!-- 
               Include sling-app launcher classes and resources, but no Sling 
               bundles (we want to specify our own versions) 
@@ -353,6 +399,20 @@
                                                 http://${test.host}:${http.port}/${project.build.finalName}/${webdav.workspace.path}
                                             </value>
                                         </property>
+                                        
+                                        <!-- system properties used for profile jcrinstall-tests -->
+                                        <property>
+                                            <name>sling.testbundles.path</name>
+                                            <value>${sling.testbundles.path}</value>
+                                        </property>
+                                        <property>
+                                            <name>sling.test.scale.factor</name>
+                                            <value>${sling.test.scale.factor}</value>
+                                        </property>
+                                        <property>
+                                            <name>sling.test.bundles.wait.seconds</name>
+                                            <value>${sling.test.bundles.wait.seconds}</value>
+                                        </property>
                                     </systemProperties>
                                 </configuration>
                             </execution>
@@ -432,6 +492,96 @@
                 </plugins>
             </build>
         </profile>
+        
+        <!--
+            The jcrinstall-tests profile includes the integration tests
+            for the JCRInstall functionality in the integration tests. By
+            default this profile is disabled.
+            For CI builds it is recommended to activate this profile, for
+            normal builds you may omit this profile 
+        -->
+        <profile>
+            <id>jcrinstall-tests</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-dependency-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>copy-jcrinstall-dependencies</id>
+                                <goals>
+                                    <goal>copy-dependencies</goal>
+                                </goals>
+                                <configuration>
+                                    <outputDirectory>
+                                        ${project.build.directory}/launchpad-bundles/resources/0
+                                    </outputDirectory>
+                                    <excludeTransitive>true</excludeTransitive>
+                                    <includeArtifactIds>
+                                        org.apache.sling.jcr.jcrinstall,org.apache.sling.jcr.jcrinstall.testbundles.observer,org.apache.sling.jcr.jcrinstall.servlet,org.apache.sling.runmode
+                                    </includeArtifactIds>
+                                </configuration>
+                            </execution>
+                       </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>compile-jcrinstall-tests</id>
+                                <goals>
+                                    <goal>testCompile</goal>
+                                </goals>
+                                <configuration>
+                                    <testIncludes>
+                                        <testInclude>
+                                            ${jcrinstall.integration.test.code.path}
+                                        </testInclude>
+                                    </testIncludes>
+                                    <!-- overwrite global setting here -->
+                                    <testExcludes>
+                                        <testExclude>
+                                            dummymarker
+                                        </testExclude>
+                                    </testExcludes>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.sling</groupId>
+                    <artifactId>org.apache.sling.jcr.jcrinstall</artifactId>
+                    <version>2.0.3-incubator-SNAPSHOT</version>
+                    <scope>provided</scope>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.sling</groupId>
+                    <artifactId>org.apache.sling.jcr.jcrinstall.testbundles.observer</artifactId>
+                    <version>2.0.3-incubator-SNAPSHOT</version>
+                    <scope>provided</scope>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.sling</groupId>
+                    <artifactId>org.apache.sling.jcr.jcrinstall.servlet</artifactId>
+                    <version>2.0.3-incubator-SNAPSHOT</version>
+                    <scope>provided</scope>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.sling</groupId>
+                    <artifactId>org.apache.sling.runmode</artifactId>
+                    <version>2.0.3-incubator-SNAPSHOT</version>
+                    <scope>provided</scope>
+                </dependency>
+            </dependencies>
+        </profile>
     </profiles>
 
     <dependencies>
@@ -463,6 +613,19 @@
             <scope>provided</scope>
         </dependency>
 
+        <!-- JCR Install - needs to be in a profile -->
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>biz.aQute</groupId>
+            <artifactId>bnd</artifactId>
+            <version>0.0.169</version>
+            <scope>test</scope>
+        </dependency>
+
         <!-- Dependencies for the Test Build and Run -->
         <dependency>
             <groupId>org.apache.sling</groupId>
diff --git a/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/HttpPingTest.java b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/HttpPingTest.java
new file mode 100644
index 0000000..e1586da
--- /dev/null
+++ b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/HttpPingTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sling.launchpad.webapp.integrationtest.jcrinstall;
+
+import org.apache.commons.httpclient.methods.GetMethod;
+
+/** Ping the Sling server to verify that our integration test
+ *  setup is ok.
+ */
+public class HttpPingTest extends JcrinstallTestBase {
+    
+    public void testWebServerRoot() throws Exception
+    {
+        final String url = HTTP_BASE_URL + "/";
+        assertHttpStatus(url, 403, "Root should return 403 as no redirect is setup in the repository");
+    }
+    
+    public void test404() throws Exception
+    {
+        assertHttpStatus(HTTP_BASE_URL + "/someNonExistentUrl", 404);
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/InstallClonedBundlesTest.java b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/InstallClonedBundlesTest.java
new file mode 100644
index 0000000..1e53fe4
--- /dev/null
+++ b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/InstallClonedBundlesTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.sling.launchpad.webapp.integrationtest.jcrinstall;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/** Try installing some cloned bundles */
+public class InstallClonedBundlesTest extends JcrinstallTestBase {
+	
+	public void testInstallAndRemoveBundles() throws Exception {
+		final int activeBeforeTest = getActiveBundlesCount();
+		final List<String> installed = new LinkedList<String>();
+		
+		final int nBundles = 10 * scaleFactor;
+		for(int i=0 ; i < nBundles; i++) {
+			installed.add(installClonedBundle(null, null));
+		}
+		
+		assertActiveBundleCount("after adding bundles", 
+				activeBeforeTest + nBundles, defaultBundlesTimeout);
+		
+		for(String path : installed) {
+			removeClonedBundle(path);
+		}
+		
+		assertActiveBundleCount("after removing added bundles", 
+				activeBeforeTest, defaultBundlesTimeout);
+	}
+}
diff --git a/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/JcrinstallTestBase.java b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/JcrinstallTestBase.java
new file mode 100644
index 0000000..6fde418
--- /dev/null
+++ b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/JcrinstallTestBase.java
@@ -0,0 +1,220 @@
+/*
+ * 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.sling.launchpad.webapp.integrationtest.jcrinstall;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.sling.commons.testing.integration.HttpTestBase;
+import org.apache.sling.launchpad.webapp.integrationtest.jcrinstall.util.BundleCloner;
+import org.osgi.framework.Bundle;
+
+/** Base class for jcrinstall test cases */
+public class JcrinstallTestBase extends HttpTestBase {
+	
+	public static final String JCRINSTALL_STATUS_PATH = "/system/sling/jcrinstall";
+	public static final String DEFAULT_INSTALL_PATH = "/libs/integration-testing/install";
+	public static final String DEFAULT_BUNDLE_NAME_PATTERN = "observer";
+	private static long bundleCounter = System.currentTimeMillis();
+	private static Set<String> installedClones;
+	public static final String SCALE_FACTOR_PROP = "sling.test.scale.factor";
+	public static final String DEFAULT_TIMEOUT_PROP = "sling.test.bundles.wait.seconds";
+	protected int scaleFactor;
+	protected int defaultBundlesTimeout;
+	
+    private class ShutdownThread extends Thread {
+        @Override
+        public void run() {
+            try {
+                System.out.println("Deleting " + installedClones.size() + " cloned bundles...");
+                for(String path : installedClones) {
+                	testClient.delete(WEBDAV_BASE_URL + path);
+                }
+            } catch(Exception e) {
+                System.out.println("Exception in ShutdownThread:" + e);
+            }
+        }
+        
+    };
+    
+    @Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		scaleFactor = requireIntProperty(SCALE_FACTOR_PROP);
+		defaultBundlesTimeout = requireIntProperty(DEFAULT_TIMEOUT_PROP);
+		enableJcrinstallService(true);
+    }
+    
+    @Override
+	protected void tearDown() throws Exception {
+		// TODO Auto-generated method stub
+		super.tearDown();
+		enableJcrinstallService(true);
+	}
+
+
+
+	protected int requireIntProperty(String systemPropertyKey) throws Exception {
+    	final String s = System.getProperty(systemPropertyKey);
+    	if(s == null) {
+    		throw new Exception("Missing system property '" + systemPropertyKey + "'");
+    	}
+    	return Integer.valueOf(s);
+    }
+    
+    /** Fail test if active bundles count is not expectedCount, after 
+     * 	at most timeoutSeconds */
+    protected void assertActiveBundleCount(String message, int expectedCount, int timeoutSeconds) throws IOException {
+    	final long start = System.currentTimeMillis();
+    	final long timeout = start + timeoutSeconds * 1000L;
+    	int count = 0;
+    	int lastCount = -1;
+    	while(System.currentTimeMillis() < timeout) {
+    		count = getActiveBundlesCount();
+    		if(count != lastCount) {
+    			// continue until the count is stable for at least one second
+    			lastCount = count;
+        		sleep(1000);
+    			continue;
+    		} else if(count == expectedCount) {
+    			return;
+    		}
+    		sleep(500);
+    	}
+    	final long delta = System.currentTimeMillis() - start;
+    	fail(message + ": expected " + expectedCount + " active bundles, found " + count
+    			+ " after waiting " + delta / 1000.0 + " seconds");
+    }
+    
+    protected void sleep(long millis) {
+    	try {
+    		Thread.sleep(millis);
+    	} catch(InterruptedException ignore) {
+    	}
+    }
+    
+    protected int getActiveBundlesCount() throws IOException {
+    	final String key = "bundles.in.state." + Bundle.ACTIVE;
+    	final Properties props = getJcrInstallProperties();
+    	int result = 0;
+    	if(props.containsKey(key)) {
+    		result = Integer.valueOf(props.getProperty(key));
+    	}
+    	return result;
+    }
+    
+    protected boolean getJcrinstallServiceEnabled() throws IOException {
+    	final Properties props = getJcrInstallProperties();
+    	return "true".equals(props.get("jcrinstall.enabled"));
+    }
+    
+    protected void enableJcrinstallService(boolean enable) throws IOException {
+    	if(enable != getJcrinstallServiceEnabled()) {
+            final PostMethod post = new PostMethod(HTTP_BASE_URL + JCRINSTALL_STATUS_PATH);
+            post.setFollowRedirects(false);
+            post.addParameter("enabled", String.valueOf(enable));
+            final int status = httpClient.executeMethod(post);
+            assertEquals("Expected status 200 in enableJcrinstallService", 200, status);
+            assertEquals("Expected jcrinstall.enabled to be " + enable, enable, getJcrinstallServiceEnabled());
+    	}
+    }
+    
+    /** Return the Properties found at /system/sling/jcrinstall */ 
+    protected Properties getJcrInstallProperties() throws IOException {
+    	final String content = getContent(HTTP_BASE_URL + JCRINSTALL_STATUS_PATH, CONTENT_TYPE_PLAIN);
+    	final Properties result = new Properties();
+    	result.load(new ByteArrayInputStream(content.getBytes("UTF-8")));
+    	return result;
+    }
+
+    /** Remove a cloned bundle that had been installed before */ 
+    protected void removeClonedBundle(String path) throws IOException {
+    	testClient.delete(WEBDAV_BASE_URL + path);
+    	installedClones.remove(path);
+    }
+    
+	/** Generate a clone of one of our test bundles, with unique bundle name and
+	 * 	symbolic name, and install it via WebDAV. 
+	 * @param bundleNamePattern The first test bundle that contains this pattern
+	 * 	is used as a source. If null, uses DEFAULT_BUNDLE_NAME_PATTERN
+	 * @param installPath if null, use DEFAULT_INSTALL_PATH
+	 * @return the path of the installed bundle
+	 */
+	protected String installClonedBundle(String bundleNamePattern, String installPath) throws Exception {
+		if(bundleNamePattern == null) {
+			bundleNamePattern = DEFAULT_BUNDLE_NAME_PATTERN;
+		}
+		if(installPath == null) {
+			installPath = DEFAULT_INSTALL_PATH;
+		}
+		
+		// find test bundle to clone
+		final File testBundlesDir = new File(System.getProperty("sling.testbundles.path"));
+		if(!testBundlesDir.isDirectory()) {
+			throw new IOException(testBundlesDir.getAbsolutePath() + " is not a directory");
+		}
+		File bundleSrc = null;
+		for(String bundle : testBundlesDir.list()) {
+			if(bundle.contains(bundleNamePattern)) {
+				bundleSrc = new File(testBundlesDir, bundle);
+				break;
+			}
+		}
+		
+		// clone bundle
+		final File outputDir = new File(testBundlesDir, "cloned-bundles");
+		outputDir.mkdirs();
+		final String bundleId = bundleNamePattern + "_clone_" + bundleCounter++;
+		final File clone = new File(outputDir, bundleId + ".jar");
+		new BundleCloner().cloneBundle(bundleSrc, clone, bundleId, bundleId);
+		
+		// install clone by copying to repository - jcrinstall should then pick it up
+		FileInputStream fis = new FileInputStream(clone);
+		final String path = installPath + "/" + clone.getName();
+		final String url = WEBDAV_BASE_URL + path;
+		try {
+			testClient.mkdirs(WEBDAV_BASE_URL, installPath);
+			testClient.upload(url, fis);
+			setupBundlesCleanup();
+			installedClones.add(path);
+		} finally {
+			if(fis != null) {
+				fis.close();
+			}
+		}
+		
+		return path;
+	}
+	
+	/** If not done yet, register a shutdown hook to delete cloned bundles that
+	 * 	we installed.
+	 */
+	private void setupBundlesCleanup() {
+		synchronized (JcrinstallTestBase.class) {
+			if(installedClones == null) {
+				installedClones = new HashSet<String>();
+				Runtime.getRuntime().addShutdownHook(new ShutdownThread());
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/StopAndChangeBundlesTest.java b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/StopAndChangeBundlesTest.java
new file mode 100644
index 0000000..235e0ce
--- /dev/null
+++ b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/StopAndChangeBundlesTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.sling.launchpad.webapp.integrationtest.jcrinstall;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/** Test replacing bundles while jcrinstall is disabled
+ */
+public class StopAndChangeBundlesTest extends JcrinstallTestBase {
+	
+	public void testStopAndRestart() throws Exception {
+		final int activeBeforeTest = getActiveBundlesCount();
+		final List<String> installed = new LinkedList<String>();
+		
+		final int nBundlesA = 7 * scaleFactor;
+		final int nBundlesB = 13 * scaleFactor;
+		
+		assertActiveBundleCount("before adding bundles", 
+				activeBeforeTest, defaultBundlesTimeout);
+		
+		// Add a first set of bundles A
+		for(int i=0 ; i < nBundlesA; i++) {
+			installed.add(installClonedBundle(null, null));
+		}
+		
+		// And check that they all start
+		assertActiveBundleCount("after adding bundles", 
+				activeBeforeTest + nBundlesA, defaultBundlesTimeout);
+		
+		// Disable jcrinstall (to simulate repository going away),
+		// delete set A and add another set of bundles B
+		enableJcrinstallService(false);
+		
+		for(String path : installed) {
+			removeClonedBundle(path);
+		}
+		installed.clear();
+		
+		for(int i=0 ; i < nBundlesB; i++) {
+			installed.add(installClonedBundle(null, null));
+		}
+		
+		// Before reactivating, bundles count must be the initial count,
+		// as jcrinstall brings the start level down
+		assertActiveBundleCount("after replacing bundles", 
+				activeBeforeTest, defaultBundlesTimeout);
+		
+		// Re-enable and verify that only original bundles + set B are active
+		enableJcrinstallService(true);
+		assertActiveBundleCount("after re-enabling jcrinstall", 
+				activeBeforeTest + nBundlesB, defaultBundlesTimeout);
+		
+		// Remove everything and check
+		for(String path : installed) {
+			removeClonedBundle(path);
+		}
+		
+		assertActiveBundleCount("after removing all added bundles", 
+				activeBeforeTest, defaultBundlesTimeout);
+	}
+}
diff --git a/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/StopAndRestartTest.java b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/StopAndRestartTest.java
new file mode 100644
index 0000000..bf3041a
--- /dev/null
+++ b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/StopAndRestartTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.sling.launchpad.webapp.integrationtest.jcrinstall;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/** Verify that stopping and restarting jcrinstall (simulating a disappearing
+ * 	repository) works as expected.
+ */
+public class StopAndRestartTest extends JcrinstallTestBase {
+	
+	public void testStopAndRestart() throws Exception {
+		final int activeBeforeTest = getActiveBundlesCount();
+		final List<String> installed = new LinkedList<String>();
+		
+		final int nBundles = 10 * scaleFactor;
+		for(int i=0 ; i < nBundles; i++) {
+			installed.add(installClonedBundle(null, null));
+		}
+		
+		assertActiveBundleCount("after adding bundles", 
+				activeBeforeTest + nBundles, defaultBundlesTimeout);
+		
+		enableJcrinstallService(false);
+		
+		assertActiveBundleCount("after disabling jcrinstall", 
+				activeBeforeTest, defaultBundlesTimeout);
+		
+		enableJcrinstallService(true);
+		
+		assertActiveBundleCount("after re-enabling jcrinstall", 
+				activeBeforeTest + nBundles, defaultBundlesTimeout);
+		
+		for(String path : installed) {
+			removeClonedBundle(path);
+		}
+		
+		assertActiveBundleCount("after removing added bundles", 
+				activeBeforeTest, defaultBundlesTimeout);
+	}
+}
diff --git a/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/util/BundleCloner.java b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/util/BundleCloner.java
new file mode 100644
index 0000000..3a68d8c
--- /dev/null
+++ b/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/jcrinstall/util/BundleCloner.java
@@ -0,0 +1,55 @@
+/*
+ * 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.sling.launchpad.webapp.integrationtest.jcrinstall.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import aQute.bnd.main.bnd;
+
+/** Generate a clone of an OSGi bundle, with its
+ * 	own Bundle-Name and Bundle-SymbolicName.
+ */
+public class BundleCloner {
+	private final bnd bnd = new bnd();
+
+	/** Create a clone of supplied bundle */
+	public void cloneBundle(File bundle, File output, String name, String symbolicName) throws Exception {
+		int options = 0;
+		
+		Properties props = new Properties();
+		props.put("Bundle-Name", name);
+		props.put("Bundle-SymbolicName", symbolicName);
+		File properties = File.createTempFile(getClass().getSimpleName(), "properties");
+		final OutputStream out = new FileOutputStream(properties);
+		
+		File classpath[] = null;
+		try {
+			props.store(out, getClass().getSimpleName());
+			bnd.doWrap(properties, bundle, output, classpath, options, null);
+		} finally {
+			if(out != null) {
+				out.close();
+			}
+			if(properties != null) {
+				properties.delete();
+			}
+		}
+	}
+}