You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2015/09/18 17:56:20 UTC

svn commit: r1703893 - in /sling/trunk/testing: junit/core/src/main/java/org/apache/sling/junit/rules/ teleporter/ teleporter/src/ teleporter/src/main/ teleporter/src/main/java/ teleporter/src/main/java/org/ teleporter/src/main/java/org/apache/ telepor...

Author: bdelacretaz
Date: Fri Sep 18 15:56:19 2015
New Revision: 1703893

URL: http://svn.apache.org/viewvc?rev=1703893&view=rev
Log:
SLING-5040 - new TeleporterRule and teleporter client module

Added:
    sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/ServerSideTeleporter.java
    sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/ServiceGetter.java
    sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/TeleporterRule.java
    sling/trunk/testing/teleporter/   (with props)
    sling/trunk/testing/teleporter/pom.xml
    sling/trunk/testing/teleporter/src/
    sling/trunk/testing/teleporter/src/main/
    sling/trunk/testing/teleporter/src/main/java/
    sling/trunk/testing/teleporter/src/main/java/org/
    sling/trunk/testing/teleporter/src/main/java/org/apache/
    sling/trunk/testing/teleporter/src/main/java/org/apache/sling/
    sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/
    sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/
    sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/
    sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/ClientSideTeleporter.java
    sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/DependencyAnalyzer.java
    sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/MultipartAdapter.java
    sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/TeleporterHttpClient.java
Modified:
    sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/Service.java

Added: sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/ServerSideTeleporter.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/ServerSideTeleporter.java?rev=1703893&view=auto
==============================================================================
--- sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/ServerSideTeleporter.java (added)
+++ sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/ServerSideTeleporter.java Fri Sep 18 15:56:19 2015
@@ -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.junit.rules;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.junit.Activator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+/** Server-side variant of the TeleporterRule, which provides
+ *  access to OSGi services for convenience, but does not do
+ *  much more.
+ */
+class ServerSideTeleporter extends TeleporterRule {
+    private final List<ServiceReference> toUnget = new ArrayList<ServiceReference>();
+    private final BundleContext bundleContext;
+    
+    ServerSideTeleporter() {
+        bundleContext = Activator.getBundleContext();
+        if(bundleContext == null) {
+            throw new IllegalStateException("Null BundleContext, should not happen when this class is used");
+        }
+    }
+    
+    @Override
+    protected void after() {
+        super.after();
+        for(ServiceReference r : toUnget) {
+            if(r != null) {
+                bundleContext.ungetService(r);
+            }
+        }
+    }
+
+    public <T> T getService (Class<T> serviceClass, String ldapFilter) {
+        final ServiceGetter sg = new ServiceGetter(bundleContext, serviceClass, ldapFilter);
+        toUnget.add(sg.serviceReference);
+        return serviceClass.cast(sg.service);
+    }
+}

Modified: sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/Service.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/Service.java?rev=1703893&r1=1703892&r2=1703893&view=diff
==============================================================================
--- sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/Service.java (original)
+++ sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/Service.java Fri Sep 18 15:56:19 2015
@@ -22,7 +22,6 @@ import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
 
 /**
  * Allows a test class to obtain a reference to an OSGi service. This rule embodies the logic to get a bundle context,
@@ -55,32 +54,15 @@ public class Service implements TestRule
                     return;
                 }
                 
-                ServiceReference serviceReference = null;
-                if(serviceClass.equals(BundleContext.class)) {
-                    // Special case to provide the BundleContext to tests
-                    Service.this.service = bundleContext;
-                } else {
-                    serviceReference = bundleContext.getServiceReference(serviceClass.getName());
-    
-                    if (serviceReference == null) {
-                        throw new IllegalStateException("unable to get a service reference");
-                    }
-    
-                    final Object service = bundleContext.getService(serviceReference);
-    
-                    if (service == null) {
-                        throw new IllegalStateException("unable to get an instance of the service");
-                    }
-    
-                    Service.this.service = serviceClass.cast(service);
-                }
+                final ServiceGetter sg = new ServiceGetter(bundleContext, serviceClass, null);
+                Service.this.service = serviceClass.cast(sg.service);
 
                 try {
                     base.evaluate();
                 } finally {
                     Service.this.service = null;
-                    if(serviceReference != null) {
-                        bundleContext.ungetService(serviceReference);
+                    if(sg.serviceReference != null) {
+                        bundleContext.ungetService(sg.serviceReference);
                     }
                 }
             }

Added: sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/ServiceGetter.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/ServiceGetter.java?rev=1703893&view=auto
==============================================================================
--- sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/ServiceGetter.java (added)
+++ sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/ServiceGetter.java Fri Sep 18 15:56:19 2015
@@ -0,0 +1,60 @@
+/*
+ * 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.junit.rules;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+/** Implements the logic used to get a service */
+class ServiceGetter {
+    final ServiceReference serviceReference;
+    final Object service;
+
+    ServiceGetter(BundleContext bundleContext, Class<?> serviceClass, String ldapFilter) {
+        Object s;
+        
+        if(ldapFilter != null && !ldapFilter.isEmpty()) {
+            throw new UnsupportedOperationException("LDAP service filter is not supported so far");
+        }
+        
+        if (serviceClass.equals(BundleContext.class)) {
+            // Special case to provide the BundleContext to tests
+            s = serviceClass.cast(bundleContext);
+            serviceReference = null;
+        } else {
+            serviceReference = bundleContext.getServiceReference(serviceClass
+                    .getName());
+
+            if (serviceReference == null) {
+                throw new IllegalStateException(
+                        "unable to get a service reference");
+            }
+
+            final Object service = bundleContext.getService(serviceReference);
+
+            if (service == null) {
+                throw new IllegalStateException(
+                        "unable to get an instance of the service");
+            }
+
+            s = serviceClass.cast(service);
+        }
+        
+        service = s;
+    }
+}

Added: sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/TeleporterRule.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/TeleporterRule.java?rev=1703893&view=auto
==============================================================================
--- sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/TeleporterRule.java (added)
+++ sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/rules/TeleporterRule.java Fri Sep 18 15:56:19 2015
@@ -0,0 +1,85 @@
+/*
+ * 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.junit.rules;
+
+import org.apache.sling.junit.Activator;
+import org.junit.rules.ExternalResource;
+
+/** JUnit Rule used to teleport a server-side test to a Sling instance
+ *  to execute it there. See the launchpad/integration-tests module for
+ *  usage examples (coming soon). 
+ *  A concrete TeleporterRule class is selected to match the different required 
+ *  behaviors of the server-side and client-side variants of this rule.
+ *  The junit.core module only contains the server-side code, to minimize
+ *  its dependencies, and the client-side part is in the sling testing.teleporter
+ *  module.  
+ */
+public abstract class TeleporterRule extends ExternalResource {
+    protected Class<?> classUnderTest;
+
+    /** Name of the implementation class to use by default when running on the client side */ 
+    public static final String DEFAULT_CLIENT_CLASS = "org.apache.sling.testing.teleporter.client.ClientSideTeleporter";
+    
+    protected TeleporterRule() {
+    }
+    
+    private void setClassUnderTest(Class<?> c) {
+        this.classUnderTest = c;
+    }
+    
+    /** Build a TeleporterRule for the given class, using the default
+     *  client class name.
+     */
+    public static TeleporterRule forClass(Class <?> classUnderTest) {
+        return forClass(classUnderTest, null);
+    }
+    
+    /** Build a TeleporterRule for the given class, optionally using
+     *  a specific client class.
+     */
+    public static TeleporterRule forClass(Class <?> classUnderTest, String clientTeleporterClassName) {
+        TeleporterRule result = null;
+        
+        if(Activator.getBundleContext() != null) {
+            // We're running on the server side
+            result = new ServerSideTeleporter();
+        } else {
+            // Client-side. Instantiate the class dynamically to 
+            // avoid bringing its dependencies into this module when
+            // it's running on the server side
+            final String clientClass = clientTeleporterClassName == null ? DEFAULT_CLIENT_CLASS : clientTeleporterClassName;
+            try {
+                result = (TeleporterRule)(TeleporterRule.class.getClassLoader().loadClass(clientClass).newInstance());
+            } catch(Exception e) {
+                throw new RuntimeException("Unable to instantiate Teleporter client " + clientClass, e);
+            }
+        }
+        
+        result.setClassUnderTest(classUnderTest);
+        return result;
+    }
+    
+    /** If running on the server side, get an OSGi service */
+    public final <T> T getService (Class<T> serviceClass) {
+        return getService(serviceClass, null);
+    }
+    
+    /** If running on the server side, get an OSGi service specified by an LDAP service filter */
+    public <T> T getService (Class<T> serviceClass, String ldapFilter) {
+        throw new UnsupportedOperationException("This TeleporterRule does not implement getService()");
+    }
+}

Propchange: sling/trunk/testing/teleporter/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Fri Sep 18 15:56:19 2015
@@ -0,0 +1,19 @@
+target
+sling
+bin
+logs
+jackrabbit-repository
+derby.log
+*.iml
+*.ipr
+*.iws
+.settings
+.project
+.classpath
+.externalToolBuilders
+maven-eclipse.xml
+jackrabbit
+
+
+
+

Added: sling/trunk/testing/teleporter/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/testing/teleporter/pom.xml?rev=1703893&view=auto
==============================================================================
--- sling/trunk/testing/teleporter/pom.xml (added)
+++ sling/trunk/testing/teleporter/pom.xml Fri Sep 18 15:56:19 2015
@@ -0,0 +1,66 @@
+<?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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.sling</groupId>
+    <artifactId>sling</artifactId>
+    <version>23</version>
+    <relativePath/>
+  </parent>
+
+  <artifactId>org.apache.sling.testing.teleporter</artifactId>
+  <version>0.1.1-SNAPSHOT</version>
+  <packaging>jar</packaging>
+  <name>Apache Sling side Test Teleporter</name>
+  <description>Client-side implementation of the Teleporter mechanism for server-side tests</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.tinybundles</groupId>
+      <artifactId>tinybundles</artifactId>
+      <version>2.0.0</version>
+    </dependency>
+    <dependency>
+        <groupId>org.apache.maven.shared</groupId>
+        <artifactId>maven-dependency-analyzer</artifactId>
+        <version>1.6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.junit.core</artifactId>
+      <version>1.0.11-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>

Added: sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/ClientSideTeleporter.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/ClientSideTeleporter.java?rev=1703893&view=auto
==============================================================================
--- sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/ClientSideTeleporter.java (added)
+++ sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/ClientSideTeleporter.java Fri Sep 18 15:56:19 2015
@@ -0,0 +1,120 @@
+/*
+ * 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.testing.teleporter.client;
+
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.sling.junit.rules.TeleporterRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.ops4j.pax.tinybundles.core.TinyBundle;
+import org.ops4j.pax.tinybundles.core.TinyBundles;
+import org.osgi.framework.Constants;
+
+public class ClientSideTeleporter extends TeleporterRule {
+
+    // TODO All this should be configurable with Options
+    private final int testReadyTimeoutSeconds = 5;
+    private String baseUrl;
+    private final Set<String> dependencyAnalyzerIncludes = new HashSet<String>();
+    private final Set<String> dependencyAnalyzerExcludes = new HashSet<String>();
+    private final Set<Class<?>> embeddedClasses = new HashSet<Class<?>>();
+
+    private InputStream buildTestBundle(Class<?> c, Collection<Class<?>> embeddedClasses, String bundleSymbolicName) {
+        final TinyBundle b = TinyBundles.bundle()
+            .set(Constants.BUNDLE_SYMBOLICNAME, bundleSymbolicName)
+            .set("Sling-Test-Regexp", c.getName() + ".*")
+            .add(c);
+        for(Class<?> clz : embeddedClasses) {
+            b.add(clz);
+        }
+        return b.build(TinyBundles.withBnd());
+    }
+    
+    protected void setBaseUrl(String url) {
+        baseUrl = url;
+        if(baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() -1);
+        }
+    }
+    
+    public void embedClass(Class<?> c) {
+        embeddedClasses.add(c);
+    }
+    
+    private String installTestBundle(TeleporterHttpClient httpClient) throws MalformedURLException, IOException {
+        final SimpleDateFormat fmt = new SimpleDateFormat("HH-mm-ss-");
+        final String bundleSymbolicName = getClass().getSimpleName() + "." + fmt.format(new Date()) + "." + UUID.randomUUID();
+        final InputStream bundle = buildTestBundle(classUnderTest, embeddedClasses, bundleSymbolicName);
+        httpClient.installBundle(bundle, bundleSymbolicName);
+        return bundleSymbolicName;
+    }
+    
+    @Override
+    public Statement apply(final Statement base, final Description description) {
+        if(baseUrl == null) {
+            // TODO - need a more flexible mechanism + need to wait for Sling to be ready
+            final String propName = "launchpad.http.server.url";
+            baseUrl = System.getProperty(propName);
+            if(baseUrl == null || baseUrl.isEmpty()) {
+                fail("Missing system property " + propName);
+            }
+        }
+        if(baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+        
+        final DependencyAnalyzer da = DependencyAnalyzer.forClass(classUnderTest);
+        for(String include : dependencyAnalyzerIncludes) {
+            da.include(include);
+        }
+        for(String exclude : dependencyAnalyzerExcludes) {
+            da.exclude(exclude);
+        }
+        for(Class<?> c : da.getDependencies()) {
+            embeddedClasses.add(c);
+        }
+
+        final TeleporterHttpClient httpClient = new TeleporterHttpClient(baseUrl);
+        // As this is not a ClassRule (which wouldn't map the test results correctly in an IDE)
+        // we currently create and install a test bundle for every test method. It might be good
+        // to optimize this, but as those test bundles are usually very small that doesn't seem
+        // to be a real problem in terms of performance.
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                final String bundleSymbolicName = installTestBundle(httpClient);
+                final String testPath = description.getClassName() + "/" + description.getMethodName();
+                try {
+                    httpClient.runTests(testPath, testReadyTimeoutSeconds);
+                } finally {
+                    httpClient.uninstallBundle(bundleSymbolicName);
+                }
+            }
+        };
+    }
+}

Added: sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/DependencyAnalyzer.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/DependencyAnalyzer.java?rev=1703893&view=auto
==============================================================================
--- sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/DependencyAnalyzer.java (added)
+++ sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/DependencyAnalyzer.java Fri Sep 18 15:56:19 2015
@@ -0,0 +1,137 @@
+/*
+ * 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.testing.teleporter.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.maven.shared.dependency.analyzer.asm.DependencyClassFileVisitor;
+
+/** Find the class dependencies of a class, recursively, 
+ *  using the maven-dependency-analyzer and optionally considering only
+ *  specific package prefixes to avoid exploring the whole graph.
+ */
+class DependencyAnalyzer {
+    private final Class<?> [] classes;
+    private final Set<String> dependencyNames = new HashSet<String>();
+    private final Set<String> includes = new HashSet<String>();
+    private final Set<String> excludes = new HashSet<String>();
+    
+    private DependencyAnalyzer(Class <?> ... classes) {
+        this.classes = classes;
+    }
+    
+    static DependencyAnalyzer forClass(Class<?> ... classes) {
+        return new DependencyAnalyzer(classes);
+    }
+    
+    /** Classes with names that match this prefix will be included,
+     *  unless they also match an exclude prefix. 
+     */
+    DependencyAnalyzer include(String prefix) {
+        includes.add(prefix);
+        return this;
+    }
+    
+    /** Classes with names that match this prefix will not be included,
+     *  even if their name matches an include pattern */
+    DependencyAnalyzer exclude(String prefix) {
+        excludes.add(prefix);
+        return this;
+    }
+    
+    /** Get the aggregate dependencies of our classes, based on a recursive
+     *  analysis that takes our include/exclude prefixes into account
+     */
+    Collection<Class<?>> getDependencies() {
+        final Set<Class<?>> result = new HashSet<Class<?>>();
+        for(Class<?> c : classes) {
+            analyze(c);
+        }
+        for(String dep : dependencyNames) {
+            result.add(toClass(dep));
+        }
+        return result;
+    }
+    
+    /** Analyze a single class, recursively */
+    private void analyze(Class<?> c) {
+        final Set<String> deps = new HashSet<String>();
+        final String path = "/" + c.getName().replace('.', '/') + ".class";
+        final InputStream input = getClass().getResourceAsStream(path);
+        if(input == null) {
+            throw new RuntimeException("Class resource not found: " + path);
+        }
+        try {
+            try {
+                final DependencyClassFileVisitor v = new DependencyClassFileVisitor();
+                v.visitClass(c.getName(), input);
+                deps.addAll(v.getDependencies());
+            } finally {
+                input.close();
+            }
+        } catch(IOException ioe) {
+            throw new RuntimeException("IOException while reading " + path);
+        }
+        
+        // Keep only accepted dependencies, and recursively analyze them
+        for(String dep : deps) {
+            if(dep.equals(c.getName())) {
+                continue;
+            }
+            if(accept(dep)) {
+                dependencyNames.add(dep);
+                analyze(toClass(dep));
+            }
+        }
+    }
+    
+    /** True if given class name matches our include/exclude prefixes */
+    private boolean accept(String className) {
+        boolean result = false;
+        
+        for(String s : includes) {
+            if(className.startsWith(s)) {
+                result = true;
+                break;
+            }
+        }
+        
+        // Excludes win over includes
+        if(result) {
+            for(String s : excludes) {
+                if(className.startsWith(s)) {
+                    result = false;
+                    break;
+                }
+            }
+        }
+        return result;
+    }
+    
+    /** Convert a class name to its Class object */
+    private Class<?> toClass(String className) {
+        try {
+            return getClass().getClassLoader().loadClass(className);
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException("Class not found :" + className, e);
+        }
+    }
+}
\ No newline at end of file

Added: sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/MultipartAdapter.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/MultipartAdapter.java?rev=1703893&view=auto
==============================================================================
--- sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/MultipartAdapter.java (added)
+++ sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/MultipartAdapter.java Fri Sep 18 15:56:19 2015
@@ -0,0 +1,79 @@
+/*
+ * 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.testing.teleporter.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+
+/** Minimal HTTP client functionality for multipart POST requests */
+class MultipartAdapter {
+    final OutputStream out;
+    final PrintWriter pw;
+    final String boundary = "______________" + Double.toHexString(Math.random()) + "______________";
+    final String eol = "\r\n";
+    final String dashes = "--";
+    final String charset;
+    int counter = 0;
+    
+    MultipartAdapter(HttpURLConnection c, String charset) throws IOException {
+        this.charset = charset;
+        c.setUseCaches(false);
+        c.setDoOutput(true);
+        c.setInstanceFollowRedirects(false);
+        c.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+        out = c.getOutputStream();
+        pw = new PrintWriter(new OutputStreamWriter(out, charset), true); 
+    }
+    
+    void close() throws IOException {
+        pw.append(dashes).append(boundary).append(dashes).append(eol).flush();
+        out.close();
+    }
+    
+    MultipartAdapter parameter(String name, String value) {
+        pw.append(dashes).append(boundary).append(eol);
+        pw.append("Content-Disposition: form-data; name=\"" + name + "\"").append(eol);
+        pw.append("Content-Type: text/plain; charset=").append(charset).append(eol);
+        pw.append(eol).append(value).append(eol).flush();
+        return this;
+    }
+    
+    MultipartAdapter file(String fieldName, String filename, String contentType, InputStream data) throws IOException {
+        pw.append(dashes).append(boundary).append(eol);
+        pw.append("Content-Disposition: form-data; name=\"").append(fieldName).append("\"");
+        pw.append("; filename=\"").append(filename).append("\"").append(eol);
+        pw.append("Content-Type: ").append(contentType).append(eol);
+        pw.append("Content-Transfer-Encoding: binary").append(eol);
+        pw.append(eol).flush();
+        copy(data, out);
+        pw.append(eol).flush();
+        return this;
+    }
+    
+    private void copy(InputStream is, OutputStream os) throws IOException {
+        final byte[] buffer = new byte[16384];
+        int n=0;
+        while((n = is.read(buffer, 0, buffer.length)) > 0) {
+            os.write(buffer, 0, n);
+        }
+        os.flush();
+    }
+}

Added: sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/TeleporterHttpClient.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/TeleporterHttpClient.java?rev=1703893&view=auto
==============================================================================
--- sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/TeleporterHttpClient.java (added)
+++ sling/trunk/testing/teleporter/src/main/java/org/apache/sling/testing/teleporter/client/TeleporterHttpClient.java Fri Sep 18 15:56:19 2015
@@ -0,0 +1,155 @@
+/*
+ * 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.testing.teleporter.client;
+
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.DatatypeConverter;
+
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runners.model.MultipleFailureException;
+
+
+/** Barebones HTTP client that supports just what the teleporter needs,
+ *  with no dependencies outstide of java.*. Prevents us from imposing
+ *  a particular HTTP client version. 
+ */
+class TeleporterHttpClient {
+    private final String CHARSET = "UTF-8";
+    private final String baseUrl;
+    
+    // TODO dynamic/configurable!!
+    private final String credentials = "admin:admin";
+    
+    TeleporterHttpClient(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    public void setCredentials(URLConnection c) {
+        final String basicAuth = "Basic " + new String(DatatypeConverter.printBase64Binary(credentials.getBytes()));
+        c.setRequestProperty ("Authorization", basicAuth);
+    }
+    
+    void installBundle(InputStream bundle, String bundleSymbolicName) throws MalformedURLException, IOException {
+        // Equivalent of
+        // curl -u admin:admin -F action=install -Fbundlestart=1 -Fbundlefile=@somefile.jar http://localhost:8080/system/console/bundles
+        final String url = baseUrl + "/system/console/bundles";
+        final String contentType = "application/octet-stream";
+        final HttpURLConnection c = (HttpURLConnection)new URL(url).openConnection();
+        
+        try {
+            setCredentials(c);
+            new MultipartAdapter(c, CHARSET)
+            .parameter("action", "install")
+            .parameter("bundlestart", "1")
+            .file("bundlefile", bundleSymbolicName + ".jar", contentType, bundle)
+            .close();
+            final int status = c.getResponseCode();
+            if(status != 302) {
+                throw new IOException("Got status code " + status + " for " + url);
+            }
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    void uninstallBundle(String bundleSymbolicName) throws MalformedURLException, IOException {
+        // equivalent of
+        // curl -u admin:admin -F action=uninstall http://localhost:8080/system/console/bundles/$N
+        final String url = baseUrl + "/system/console/bundles/" + bundleSymbolicName;
+        final HttpURLConnection c = (HttpURLConnection)new URL(url).openConnection();
+        
+        try {
+            setCredentials(c);
+            new MultipartAdapter(c, CHARSET)
+            .parameter("action", "uninstall")
+            .close();
+            final int status = c.getResponseCode();
+            if(status != 200) {
+                throw new IOException("Got status code " + status + " for " + url);
+            }
+        } finally {
+            c.disconnect();
+        }
+    }
+    
+    private int getHttpGetStatus(String url) throws MalformedURLException, IOException {
+        final HttpURLConnection c = (HttpURLConnection)new URL(url).openConnection();
+        c.setUseCaches(false);
+        c.setDoOutput(true);
+        c.setDoInput(true);
+        c.setInstanceFollowRedirects(false);
+        try {
+            return c.getResponseCode();
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    void runTests(String testSelectionPath, int testReadyTimeoutSeconds) throws MalformedURLException, IOException, MultipleFailureException {
+        final String testUrl = baseUrl + "/system/sling/junit/" + testSelectionPath + ".junit_result";
+        
+        // Wait for non-404 response that signals that test bundle is ready
+        final long timeout = System.currentTimeMillis() + (testReadyTimeoutSeconds * 1000L);
+        while(true) {
+            if(getHttpGetStatus(testUrl) == 200) {
+                break;
+            }
+            if(System.currentTimeMillis() > timeout) {
+                fail("Timeout waiting for test at " + testUrl + " (" + testReadyTimeoutSeconds + " seconds)");
+                break;
+            }
+        }
+        
+        final HttpURLConnection c = (HttpURLConnection)new URL(testUrl).openConnection();
+        try {
+            c.setRequestMethod("POST");
+            c.setUseCaches(false);
+            c.setDoOutput(true);
+            c.setDoInput(true);
+            c.setInstanceFollowRedirects(false);
+            final int status = c.getResponseCode();
+            if(status != 200) {
+                throw new IOException("Got status code " + status + " for " + testUrl);
+            }
+        
+            final Result result = (Result)new ObjectInputStream(c.getInputStream()).readObject();
+            if(result.getFailureCount() > 0) {
+                final List<Throwable> failures = new ArrayList<Throwable>(result.getFailureCount());
+                for (Failure f : result.getFailures()) {
+                    failures.add(f.getException());
+                }
+                throw new MultipleFailureException(failures);
+            }
+        } catch(ClassNotFoundException e) {
+            throw new IOException("Exception reading test results:" + e, e);
+        } finally {
+            c.disconnect();
+        }
+    }
+}
\ No newline at end of file