This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.junit.healthcheck-1.0.4 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-junit-healthcheck.git
commit eddf9185599a9406e2d8d0401a474e300964297d Author: Bertrand Delacretaz <[email protected]> AuthorDate: Fri Feb 25 16:59:06 2011 +0000 SLING-1984 - support for running individual tests remotely from an IDE. Based on a contribution by Pooja Kothari, thanks! git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/junit/remote@1074632 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 48 ++++++++++- .../junit/remote/exported/ExampleRemoteTest.java | 49 +++++++++++ .../remote/httpclient/RemoteTestHttpClient.java | 91 ++++++++++++++++++++ .../sling/junit/remote/ide/ExecutionResult.java | 53 ++++++++++++ .../sling/junit/remote/ide/SerializedRenderer.java | 97 ++++++++++++++++++++++ .../junit/remote/ide/SlingRemoteExecutionRule.java | 96 +++++++++++++++++++++ .../remote/testrunner/SlingRemoteTestRunner.java | 41 +++------ 7 files changed, 445 insertions(+), 30 deletions(-) diff --git a/pom.xml b/pom.xml index 2e06348..81102e1 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ <artifactId>org.apache.sling.junit.remote</artifactId> <version>0.1.1-SNAPSHOT</version> - <packaging>jar</packaging> + <packaging>bundle</packaging> <name>Apache Sling JUnit Remote Tests Runners</name> <description>Utilities to run server-side JUnit tests remotely</description> @@ -39,13 +39,49 @@ <url>http://svn.apache.org/viewvc/sling/trunk/testing/junit/remote</url> </scm> + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Export-Package>org.apache.sling.junit.remote.exported.*</Export-Package> + <Import-Package> + org.apache.http.*; resolution:=optional, + org.apache.sling.testing.tools.http; resolution:=optional, + org.junit.internal.*; resolution:=optional, + * + </Import-Package> + <Sling-Test-Regexp>.*Test</Sling-Test-Regexp> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + <dependencies> <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + </dependency> + <dependency> <groupId>org.apache.sling</groupId> <artifactId>org.apache.sling.junit.core</artifactId> <version>0.1.1-SNAPSHOT</version> </dependency> <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.scr.annotations</artifactId> + <version>1.4.0</version> + <scope>provided</scope> + </dependency> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> @@ -57,6 +93,12 @@ <version>1.5.11</version> </dependency> <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>1.5.11</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.apache.sling</groupId> <artifactId>org.apache.sling.testing.tools</artifactId> <version>0.1.1-SNAPSHOT</version> @@ -66,5 +108,9 @@ <artifactId>org.apache.sling.commons.json</artifactId> <version>2.0.6</version> </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + </dependency> </dependencies> </project> diff --git a/src/main/java/org/apache/sling/junit/remote/exported/ExampleRemoteTest.java b/src/main/java/org/apache/sling/junit/remote/exported/ExampleRemoteTest.java new file mode 100644 index 0000000..063a4f4 --- /dev/null +++ b/src/main/java/org/apache/sling/junit/remote/exported/ExampleRemoteTest.java @@ -0,0 +1,49 @@ +/* + * 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.remote.exported; + +import static org.junit.Assert.fail; +import org.apache.sling.junit.remote.ide.SlingRemoteExecutionRule; +import org.junit.Rule; +import org.junit.Test; + +/** Test that can be run remotely on a Sling instance from an IDE, by + * setting the {@link SlingRemoteExecutionRule.SLING_REMOTE_TEST_URL} + * system property in the IDE setup, to the URL of + * the Sling JUnit servlet (like http://localhost:8080/system/sling/junit) + */ +public class ExampleRemoteTest { + + @Rule + public SlingRemoteExecutionRule execRule = new SlingRemoteExecutionRule(); + + @Test + public void testAlwaysPasses() { + } + + @Test + public void testAlwaysFails() { + fail("This test always fails"); + } + + @Test + public void testFailsSometimes() { + if(Math.random() < 0.5) { + fail("This test fails sometimes"); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/junit/remote/httpclient/RemoteTestHttpClient.java b/src/main/java/org/apache/sling/junit/remote/httpclient/RemoteTestHttpClient.java new file mode 100644 index 0000000..007109a --- /dev/null +++ b/src/main/java/org/apache/sling/junit/remote/httpclient/RemoteTestHttpClient.java @@ -0,0 +1,91 @@ +/* + * 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.remote.httpclient; + +import java.io.IOException; + +import org.apache.http.ParseException; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.sling.testing.tools.http.Request; +import org.apache.sling.testing.tools.http.RequestBuilder; +import org.apache.sling.testing.tools.http.RequestExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** HTTP client that executes tests remotely */ +public class RemoteTestHttpClient { + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final String junitServletUrl; + private StringBuilder subpath; + private boolean consumeContent; + private static final String SLASH = "/"; + private static final String DOT = "."; + + public RemoteTestHttpClient(String junitServletUrl, boolean consumeContent) { + if(junitServletUrl == null) { + throw new IllegalArgumentException("JUnit servlet URL is null, cannot run tests"); + } + this.junitServletUrl = junitServletUrl; + this.consumeContent = consumeContent; + } + + public RequestExecutor runTests(String testClassesSelector, String testMethodSelector, String extension) + throws ClientProtocolException, IOException { + final RequestBuilder builder = new RequestBuilder(junitServletUrl); + + // Optionally let the client to consume the response entity + final RequestExecutor executor = new RequestExecutor(new DefaultHttpClient()) { + @Override + protected void consumeEntity() throws ParseException, IOException { + if(consumeContent) { + super.consumeEntity(); + } + } + }; + + // POST request executes the tests + subpath = new StringBuilder(); + if(!junitServletUrl.endsWith(SLASH)) { + subpath.append(SLASH); + } + subpath.append(testClassesSelector); + if(!extension.startsWith(DOT)) { + subpath.append(DOT); + } + subpath.append(extension); + if(testMethodSelector != null && testMethodSelector.length() > 0) { + subpath.append("/"); + subpath.append(testMethodSelector); + } + + log.info("Executing test remotely, path={} JUnit servlet URL={}", + subpath, junitServletUrl); + final Request r = builder.buildPostRequest(subpath.toString()); + executor.execute(r).assertStatus(200); + + return executor; + } + + /** If called after runTests, returns the path used to + * run tests on the remote JUnit servlet + */ + public String getTestExecutionPath() { + return subpath == null ? null : subpath.toString(); + } +} diff --git a/src/main/java/org/apache/sling/junit/remote/ide/ExecutionResult.java b/src/main/java/org/apache/sling/junit/remote/ide/ExecutionResult.java new file mode 100644 index 0000000..19cf76d --- /dev/null +++ b/src/main/java/org/apache/sling/junit/remote/ide/ExecutionResult.java @@ -0,0 +1,53 @@ +/* + * 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.remote.ide; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; + +public class ExecutionResult implements Serializable { + private static final long serialVersionUID = 7935484811381524530L; + private final Throwable throwable; + + public ExecutionResult(Result result) { + if (result.getFailureCount() > 0) { + final List<Throwable> failures = new ArrayList<Throwable>(result.getFailureCount()); + for (Failure f : result.getFailures()) { + failures.add(f.getException()); + } + + // TODO MultipleFailureException is an internal JUnit class - + // we don't have it when running server-side in Sling + // throwable = new MultipleFailureException(failures); + throwable = failures.get(0); + } else { + throwable = null; + } + } + + public Throwable getException() { + return throwable; + } + + public boolean isFailure() { + return throwable != null; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/junit/remote/ide/SerializedRenderer.java b/src/main/java/org/apache/sling/junit/remote/ide/SerializedRenderer.java new file mode 100644 index 0000000..ddb0dc3 --- /dev/null +++ b/src/main/java/org/apache/sling/junit/remote/ide/SerializedRenderer.java @@ -0,0 +1,97 @@ +/* + * 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.remote.ide; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Collection; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.junit.Renderer; +import org.apache.sling.junit.RequestParser; +import org.junit.runner.Result; +import org.junit.runner.notification.RunListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Renderer for Sling JUnit server-side testing, which + * renders test results in binary form. + * Used to send results, and especially Exceptions, as + * is to a remote IDE. + */ +@Component(immediate=false) +@Service +public class SerializedRenderer extends RunListener implements Renderer { + + public static final String EXTENSION = "serialized"; + private ObjectOutputStream outputStream; + private final Logger log = LoggerFactory.getLogger(getClass()); + + /** @inheritDoc */ + public boolean appliesTo(RequestParser p) { + return EXTENSION.equals(p.getExtension()); + } + + /** @inheritDoc */ + public void setup(HttpServletResponse response, String pageTitle) + throws IOException, UnsupportedEncodingException { + response.setContentType("application/x-java-serialized-object"); + outputStream = new ObjectOutputStream(response.getOutputStream()); + } + + /** @inheritDoc */ + public void cleanup() { + try { + outputStream.flush(); + outputStream.close(); + } catch (IOException e) { + log.warn("Exception in cleanup()", e); + } + outputStream = null; + } + + /** @inheritDoc */ + public RunListener getRunListener() { + return this; + } + + /** @inheritDoc */ + public void info(String role, String info) { + } + + /** @inheritDoc */ + public void link(String info, String url, String method) { + } + + /** @inheritDoc */ + public void list(String role, Collection<String> data) { + } + + /** @inheritDoc */ + public void title(int level, String title) { + } + + @Override + public void testRunFinished(Result result) throws IOException { + final ExecutionResult er = new ExecutionResult(result); + outputStream.writeObject(er); + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/junit/remote/ide/SlingRemoteExecutionRule.java b/src/main/java/org/apache/sling/junit/remote/ide/SlingRemoteExecutionRule.java new file mode 100644 index 0000000..9590ab8 --- /dev/null +++ b/src/main/java/org/apache/sling/junit/remote/ide/SlingRemoteExecutionRule.java @@ -0,0 +1,96 @@ +/* + * 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.remote.ide; + +import java.io.ObjectInputStream; + +import org.apache.http.HttpEntity; +import org.apache.sling.junit.remote.httpclient.RemoteTestHttpClient; +import org.apache.sling.testing.tools.http.RequestExecutor; +import org.junit.rules.MethodRule; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SlingRemoteExecutionRule implements MethodRule { + private static final Logger log = + LoggerFactory.getLogger(SlingRemoteExecutionRule.class); + + /** Name of the system property that activates remote test execution */ + public static final String SLING_REMOTE_TEST_URL = "sling.remote.test.url"; + + public Statement apply(final Statement base, final FrameworkMethod method, Object target) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + if (tryRemoteEvaluation(method)) { + return; + } + base.evaluate(); + } + }; + } + + /** + * Execute test remotely if the corresponding system property is set + * + * @return <code>true</code> if the method was executed remotely and passed. + * If the test was <b>not</b> executed remotely then + * <code>false</code> is returned to indicate that test should be + * executed locally + */ + private boolean tryRemoteEvaluation(FrameworkMethod method) throws Throwable { + String remoteUrl = System.getProperty(SLING_REMOTE_TEST_URL); + if(remoteUrl != null) { + remoteUrl = remoteUrl.trim(); + if(remoteUrl.length() > 0) { + invokeRemote(remoteUrl, method); + return true; + } + } + return false; + } + + private void invokeRemote(String remoteUrl, FrameworkMethod method) throws Throwable { + final String testClassesSelector = method.getMethod().getDeclaringClass().getName(); + final String methodName = method.getMethod().getName(); + + final RemoteTestHttpClient testHttpClient = new RemoteTestHttpClient(remoteUrl, false); + final RequestExecutor executor = testHttpClient.runTests( + testClassesSelector, methodName, "serialized" + ); + log.debug("Ran test {} method {} at URL {}", + new Object[] { testClassesSelector, methodName, remoteUrl }); + + final HttpEntity entity = executor.getResponse().getEntity(); + if (entity != null) { + try { + final Object o = new ObjectInputStream(entity.getContent()).readObject(); + if( !(o instanceof ExecutionResult) ) { + throw new IllegalStateException("Expected an ExecutionResult, got a " + o.getClass().getName()); + } + final ExecutionResult result = (ExecutionResult)o; + if (result.isFailure()) { + throw result.getException(); + } + } finally { + entity.consumeContent(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/junit/remote/testrunner/SlingRemoteTestRunner.java b/src/main/java/org/apache/sling/junit/remote/testrunner/SlingRemoteTestRunner.java index 428f090..d8e9118 100644 --- a/src/main/java/org/apache/sling/junit/remote/testrunner/SlingRemoteTestRunner.java +++ b/src/main/java/org/apache/sling/junit/remote/testrunner/SlingRemoteTestRunner.java @@ -21,12 +21,10 @@ import static org.junit.Assert.assertEquals; import java.util.LinkedList; import java.util.List; -import org.apache.http.impl.client.DefaultHttpClient; import org.apache.sling.commons.json.JSONArray; import org.apache.sling.commons.json.JSONObject; import org.apache.sling.commons.json.JSONTokener; -import org.apache.sling.testing.tools.http.Request; -import org.apache.sling.testing.tools.http.RequestBuilder; +import org.apache.sling.junit.remote.httpclient.RemoteTestHttpClient; import org.apache.sling.testing.tools.http.RequestExecutor; import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.EachTestNotifier; @@ -47,8 +45,7 @@ import org.slf4j.LoggerFactory; public class SlingRemoteTestRunner extends ParentRunner<SlingRemoteTest> { private static final Logger log = LoggerFactory.getLogger(SlingRemoteTestRunner.class); private final SlingRemoteTestParameters testParameters; - private RequestExecutor executor; - private RequestBuilder builder; + private RemoteTestHttpClient testHttpClient; private final Class<?> testClass; private final List<SlingRemoteTest> children = new LinkedList<SlingRemoteTest>(); @@ -72,32 +69,18 @@ public class SlingRemoteTestRunner extends ParentRunner<SlingRemoteTest> { } private void maybeExecuteTests() throws Exception { - if(executor != null) { + if(testHttpClient != null) { + // Tests already ran return; } - // Setup request execution - executor = new RequestExecutor(new DefaultHttpClient()); - if(testParameters.getJunitServletUrl() == null) { - throw new IllegalStateException("Server base URL is null, cannot run tests"); - } - builder = new RequestBuilder(testParameters.getJunitServletUrl()); - - // POST request executes the tests - final StringBuilder subpath = new StringBuilder(); - subpath.append("/"); - subpath.append(testParameters.getTestClassesSelector()); - subpath.append(".json"); - final String testMethodSelector = testParameters.getTestMethodSelector(); - if(testMethodSelector != null && testMethodSelector.length() > 0) { - subpath.append("/"); - subpath.append(testMethodSelector); - } - final Request r = builder.buildPostRequest(subpath.toString()); - executor.execute(r) - .assertStatus(200) - .assertContentType("application/json"); - + testHttpClient = new RemoteTestHttpClient(testParameters.getJunitServletUrl(), true); + final RequestExecutor executor = testHttpClient.runTests( + testParameters.getTestClassesSelector(), + testParameters.getTestMethodSelector(), + "json" + ); + executor.assertContentType("application/json"); final JSONArray json = new JSONArray(new JSONTokener((executor.getContent()))); // Response contains an array of objects identified by @@ -111,7 +94,7 @@ public class SlingRemoteTestRunner extends ParentRunner<SlingRemoteTest> { } log.info("Server-side tests executed at {} with path {}", - testParameters.getJunitServletUrl(), subpath); + testParameters.getJunitServletUrl(), testHttpClient.getTestExecutionPath()); // Check that number of tests is as expected assertEquals("Expecting " + testParameters.getExpectedNumberOfTests() + " tests", -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
