Author: chetanm
Date: Wed Jan 14 17:54:31 2015
New Revision: 1651751
URL: http://svn.apache.org/r1651751
Log:
SLING-4280 - Enable dumping of remote server logs in case of test failures
- Commons Testing - Added a new class TestInfoPassingClient which passes Sling
test related headers (MDC keys starting with X-Sling) to outgoing request. Also
used by HttpTestBase
- Junit Core - Exposed a TestLogServlet to capture server side logs for test
being executed
- Testing Tools - Added RemoteLogDumper rule which connects to remote server
and obtains the server logs and then dump them to system err stream upon
failure. Also added a TestDescriptionInterceptor
- integration-test - Modified the dependency to use logback as it supports MDC
Added:
sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/TestInfoPassingClient.java
(with props)
sling/trunk/launchpad/integration-tests/src/main/resources/logback-test.xml
(with props)
sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/impl/servlet/TestLogServlet.java
(with props)
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/RemoteLogDumper.java
(with props)
Modified:
sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestBase.java
sling/trunk/launchpad/integration-tests/pom.xml
sling/trunk/testing/junit/core/pom.xml
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestDescriptionInterceptor.java
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestDescriptionRule.java
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestNameLoggingFilter.java
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/package-info.java
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/sling/SlingTestBase.java
Modified:
sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestBase.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestBase.java?rev=1651751&r1=1651750&r2=1651751&view=diff
==============================================================================
---
sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestBase.java
(original)
+++
sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/HttpTestBase.java
Wed Jan 14 17:54:31 2015
@@ -109,7 +109,7 @@ public class HttpTestBase extends TestCa
public TestNode(String parentPath, Map<String, String> properties)
throws IOException {
super(testClient, parentPath, properties);
}
- };
+ }
public static String removeEndingSlash(String str) {
if(str != null && str.endsWith("/")) {
@@ -153,7 +153,7 @@ public class HttpTestBase extends TestCa
}
// setup HTTP client, with authentication (using default Jackrabbit
credentials)
- httpClient = new HttpClient();
+ httpClient = new TestInfoPassingClient();
httpClient.getParams().setAuthenticationPreemptive(true);
Credentials defaultcreds = getDefaultCredentials();
httpClient.getState().setCredentials(new AuthScope(url.getHost(),
url.getPort(), AuthScope.ANY_REALM), defaultcreds);
Added:
sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/TestInfoPassingClient.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/TestInfoPassingClient.java?rev=1651751&view=auto
==============================================================================
---
sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/TestInfoPassingClient.java
(added)
+++
sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/TestInfoPassingClient.java
Wed Jan 14 17:54:31 2015
@@ -0,0 +1,63 @@
+/*
+ * 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.commons.testing.integration;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.commons.httpclient.HostConfiguration;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.HttpState;
+import org.slf4j.MDC;
+
+/**
+ * HttpClient extension which also passes test related headers as part
+ * of outgoing HTTP request
+ */
+public class TestInfoPassingClient extends HttpClient {
+ //Defined in org.apache.sling.testing.tools.junit.TestLogRule
+ private static final String SLING_HEADER_PREFIX = "X-Sling-";
+
+ @Override
+ public int executeMethod(HostConfiguration hostconfig, HttpMethod method,
+ HttpState state) throws IOException {
+ addSlingHeaders(method);
+ return super.executeMethod(hostconfig, method, state);
+ }
+
+ /**
+ * Adds all MDC key-value pairs as HTTP header where the key starts
+ * with 'X-Sling-'
+ */
+ private static void addSlingHeaders(HttpMethod m){
+ Map<?,?> mdc = MDC.getCopyOfContextMap();
+ if (mdc != null) {
+ for (Map.Entry<?, ?> e : mdc.entrySet()) {
+ Object key = e.getKey();
+ if (key instanceof String
+ && ((String)key).startsWith(SLING_HEADER_PREFIX)
+ && e.getValue() instanceof String) {
+ m.addRequestHeader((String) key, (String) e.getValue());
+ }
+ }
+ }
+ }
+}
Propchange:
sling/trunk/bundles/commons/testing/src/main/java/org/apache/sling/commons/testing/integration/TestInfoPassingClient.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: sling/trunk/launchpad/integration-tests/pom.xml
URL:
http://svn.apache.org/viewvc/sling/trunk/launchpad/integration-tests/pom.xml?rev=1651751&r1=1651750&r2=1651751&view=diff
==============================================================================
--- sling/trunk/launchpad/integration-tests/pom.xml (original)
+++ sling/trunk/launchpad/integration-tests/pom.xml Wed Jan 14 17:54:31 2015
@@ -150,6 +150,24 @@
<dependencies>
<dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>1.7.6</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <version>1.7.6</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>1.1.2</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
@@ -173,7 +191,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.commons.testing</artifactId>
- <version>2.0.15-SNAPSHOT</version>
+ <version>2.0.17-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
@@ -230,11 +248,6 @@
<scope>compile</scope>
</dependency>
<dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-simple</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>xmlunit</groupId>
<artifactId>xmlunit</artifactId>
<version>1.3</version>
@@ -265,7 +278,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.testing.tools</artifactId>
- <version>1.0.6</version>
+ <version>1.0.9-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.sling</groupId>
Added:
sling/trunk/launchpad/integration-tests/src/main/resources/logback-test.xml
URL:
http://svn.apache.org/viewvc/sling/trunk/launchpad/integration-tests/src/main/resources/logback-test.xml?rev=1651751&view=auto
==============================================================================
--- sling/trunk/launchpad/integration-tests/src/main/resources/logback-test.xml
(added)
+++ sling/trunk/launchpad/integration-tests/src/main/resources/logback-test.xml
Wed Jan 14 17:54:31 2015
@@ -0,0 +1,51 @@
+<?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.
+ -->
+
+<configuration>
+
+ <appender name="file" class="ch.qos.logback.core.FileAppender">
+ <file>target/sling-tests.log</file>
+ <encoder>
+ <pattern>%date{HH:mm:ss.SSS} %-5level %-40([%thread] %F:%L)
%msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="sift" class="ch.qos.logback.classic.sift.SiftingAppender">
+ <discriminator>
+ <Key>testclass</Key>
+ <DefaultValue>junit</DefaultValue>
+ </discriminator>
+ <sift>
+ <appender name="FILE-${testname}"
class="ch.qos.logback.core.FileAppender">
+ <File>target/surefire-reports/${testclass}.log</File>
+ <layout class="ch.qos.logback.classic.PatternLayout">
+ <Pattern>%date{HH:mm:ss.SSS} %-5level %-40([%thread]
%F:%L) %msg%n</Pattern>
+ </layout>
+ </appender>
+ </sift>
+ </appender>
+
+ <root level="INFO">
+ <appender-ref ref="file" />
+ <appender-ref ref="sift" />
+ </root>
+
+</configuration>
\ No newline at end of file
Propchange:
sling/trunk/launchpad/integration-tests/src/main/resources/logback-test.xml
------------------------------------------------------------------------------
svn:eol-style = native
Modified: sling/trunk/testing/junit/core/pom.xml
URL:
http://svn.apache.org/viewvc/sling/trunk/testing/junit/core/pom.xml?rev=1651751&r1=1651750&r2=1651751&view=diff
==============================================================================
--- sling/trunk/testing/junit/core/pom.xml (original)
+++ sling/trunk/testing/junit/core/pom.xml Wed Jan 14 17:54:31 2015
@@ -160,11 +160,23 @@
<version>${jacoco.version}</version>
<scope>provided</scope>
</dependency>
- <dependency>
+ <dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${hamcrest.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>1.0.13</version>
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.osgi</artifactId>
+ <version>2.2.2</version>
+ </dependency>
</dependencies>
</project>
Added:
sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/impl/servlet/TestLogServlet.java
URL:
http://svn.apache.org/viewvc/sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/impl/servlet/TestLogServlet.java?rev=1651751&view=auto
==============================================================================
---
sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/impl/servlet/TestLogServlet.java
(added)
+++
sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/impl/servlet/TestLogServlet.java
Wed Jan 14 17:54:31 2015
@@ -0,0 +1,290 @@
+/*
+ * 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.impl.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.PatternLayout;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Layout;
+import ch.qos.logback.core.read.CyclicBufferAppender;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.junit.runner.Description;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+@Component(immediate=true, metatype=true,
+ label = "Apache Sling Test Log Collector",
+ description = "Servlet that exposes logs collected for a particular
test execution"
+)
+public class TestLogServlet extends HttpServlet {
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ //These name should be kept in sync with
+ // org.apache.sling.testing.tools.junit.RemoteLogDumper
+ public static final String TEST_NAME = "X-Sling-Test-Name";
+ public static final String TEST_CLASS = "X-Sling-Test-Class";
+
+ @Property(value="/system/sling/testlog")
+ static final String SERVLET_PATH_NAME = "servlet.path";
+
+ static final int DEFAULT_SIZE = 1000;
+ @Property(intValue = DEFAULT_SIZE,
+ label = "Log Buffer Size",
+ description = "Size of in memory log buffer. Only recent logs upto
buffer size would be retained"
+ )
+ static final String LOG_BUFFER_SIZE = "log.buffer.size";
+
+ private static final String DEFAULT_PATTERN = "%d{dd.MM.yyyy HH:mm:ss.SSS}
*%level* [%thread] %logger %msg%n";
+
+ @Property(label = "Log Pattern",
+ description = "Message Pattern for formatting the log messages",
+ value = DEFAULT_PATTERN
+ )
+ private static final String PROP_MSG_PATTERN = "logPattern";
+
+ /** Non-null if we are registered with HttpService */
+ private String servletPath;
+
+ @Reference
+ private HttpService httpService;
+
+ private CyclicBufferAppender<ILoggingEvent> appender;
+
+ private Layout<ILoggingEvent> layout;
+
+ private ServiceRegistration filter;
+
+ private volatile Description currentTest;
+
+ @Activate
+ protected void activate(BundleContext ctx, Map<String, ?> config) throws
Exception {
+ registerServlet(config);
+ registerAppender(config);
+ registerFilter(ctx);
+ createLayout(config);
+ }
+
+ @Deactivate
+ protected void deactivate() throws Exception {
+ deregisterFilter();
+ deregisterServlet();
+ deregisterAppender();
+ stopLayout();
+ }
+
+ public void testRunStarted(Description description) {
+ if (description != null && !description.equals(currentTest)){
+ currentTest = description;
+ resetAppender();
+ log.info("Starting test execution {}", description);
+ }
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse
response)
+ throws ServletException, IOException {
+ final PrintWriter pw = response.getWriter();
+ final String className = request.getParameter(TEST_CLASS);
+ final String testName = request.getParameter(TEST_NAME);
+
+ //If className and testName explicitly specified check if the logs
+ //are being collected for expected test
+ if (className != null && testName != null){
+ Description expected =
Description.createTestDescription(className, testName);
+
+ if (!expected.equals(currentTest)){
+ pw.printf("Test name mismatch : Current test [%s], Expected
test [%s]%n", currentTest, expected);
+ return;
+ }
+ }
+
+ //Detach the appender so that we can extract its content safely
+ rootLogger().detachAppender(appender);
+ try {
+ for (int i = 0; i < appender.getLength(); i++) {
+ pw.print(layout.doLayout(appender.get(i)));
+ }
+ resetAppender();
+ } finally {
+ rootLogger().addAppender(appender);
+ }
+ }
+
+ private void resetAppender() {
+ synchronized (appender) {
+ appender.reset();
+ }
+ }
+
+ private void registerAppender(Map<String, ?> config) {
+ int size = PropertiesUtil.toInteger(config.get(LOG_BUFFER_SIZE),
DEFAULT_SIZE);
+ appender = new CyclicBufferAppender<ILoggingEvent>();
+ appender.setMaxSize(size);
+ appender.setContext(getContext());
+ appender.setName("TestLogCollector");
+ appender.start();
+ rootLogger().addAppender(appender);
+ }
+
+ private void deregisterAppender() {
+ if (appender != null) {
+ rootLogger().detachAppender(appender);
+ appender.stop();
+ appender = null;
+ }
+ }
+
+ private void createLayout(Map<String, ?> config) {
+ String pattern = PropertiesUtil.toString(config.get(PROP_MSG_PATTERN),
DEFAULT_PATTERN);
+ PatternLayout pl = new PatternLayout();
+ pl.setPattern(pattern);
+ pl.setOutputPatternAsHeader(false);
+ pl.setContext(getContext());
+ pl.start();
+
+ layout = pl;
+ }
+
+ private void stopLayout() {
+ if (layout != null){
+ layout.stop();
+ }
+ }
+
+ private void registerServlet(Map<String, ?> config) throws
ServletException, NamespaceException {
+ servletPath = getServletPath(config);
+ if(servletPath == null) {
+ log.info("Servlet path is null, not registering with HttpService");
+ } else {
+ httpService.registerServlet(servletPath, this, null, null);
+ log.info("Servlet registered at {}", servletPath);
+ }
+ }
+
+ private void deregisterServlet() {
+ if(servletPath != null) {
+ httpService.unregister(servletPath);
+ log.info("Servlet unregistered from path {}", servletPath);
+ }
+ servletPath = null;
+ }
+
+ private void registerFilter(BundleContext ctx) {
+ Properties props = new Properties();
+ props.put(Constants.SERVICE_DESCRIPTION, "Filter to extract testName
from request headers");
+ props.put(Constants.SERVICE_VENDOR,
ctx.getBundle().getHeaders().get(Constants.BUNDLE_VENDOR));
+
+ props.put("pattern", "/.*");
+ filter = ctx.registerService(Filter.class.getName(), new
TestNameLoggingFilter(), props);
+ }
+
+ private void deregisterFilter() {
+ if (filter != null) {
+ filter.unregister();
+ }
+ }
+
+ private class TestNameLoggingFilter implements Filter {
+
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException,
ServletException {
+
+ final HttpServletRequest httpRequest = (HttpServletRequest)
request;
+ final String className = httpRequest.getHeader(TEST_CLASS);
+ final String testName = httpRequest.getHeader(TEST_NAME);
+
+ if (className == null || testName == null) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ try {
+ MDC.put(TEST_NAME, testName);
+ MDC.put(TEST_CLASS, className);
+
+ testRunStarted(Description.createTestDescription(className,
testName));
+
+ chain.doFilter(request, response);
+ } finally {
+
+ MDC.remove(TEST_NAME);
+ MDC.remove(TEST_CLASS);
+ }
+
+ }
+
+ public void destroy() {
+
+ }
+ }
+
+ //~------------------------------------------------< utility >
+
+ /**
+ * Return the path at which to mount this servlet, or null
+ * if it must not be mounted.
+ * @param ctx
+ */
+ private static String getServletPath(Map<String, ?> config) {
+ String result = (String)config.get(SERVLET_PATH_NAME);
+ if(result != null && result.trim().length() == 0) {
+ result = null;
+ }
+ return result;
+ }
+
+ private static LoggerContext getContext(){
+ return (LoggerContext) LoggerFactory.getILoggerFactory();
+ }
+
+ private static ch.qos.logback.classic.Logger rootLogger() {
+ return
getContext().getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
+ }
+}
Propchange:
sling/trunk/testing/junit/core/src/main/java/org/apache/sling/junit/impl/servlet/TestLogServlet.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/RemoteLogDumper.java
URL:
http://svn.apache.org/viewvc/sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/RemoteLogDumper.java?rev=1651751&view=auto
==============================================================================
---
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/RemoteLogDumper.java
(added)
+++
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/RemoteLogDumper.java
Wed Jan 14 17:54:31 2015
@@ -0,0 +1,140 @@
+/*
+ * 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.tools.junit;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+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.apache.sling.testing.tools.sling.SlingInstanceState;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.slf4j.MDC;
+
+/**
+ * The RemoteLogDumper Rule fetches logs which are generated due to execution
of test from the
+ * remote server and dumps them locally upon test failure. This simplifies
determining failure
+ * cause by providing all required data locally. This would be specially
useful when running test
+ * in CI server where server logs gets cluttered with all other test executions
+ *
+ * <pre>
+ * public class LoginTestIT {
+ *
+ * @Rule
+ * public TestRule logDumper = new RemoteLogDumper();
+ *
+ * @Test
+ * public void remoteLogin() {
+ * //Make calls to remote server
+ * assertEquals("testA", name.getMethodName());
+ * }
+ *
+ * }
+ * </pre>
+ */
+public class RemoteLogDumper extends TestWatcher {
+ public static final String TEST_CLASS = "X-Sling-Test-Class";
+ public static final String TEST_NAME = "X-Sling-Test-Name";
+ /**
+ * Path for the org.apache.sling.junit.impl.servlet.TestLogServlet
+ */
+ static final String SERVLET_PATH = "/system/sling/testlog";
+
+ @Override
+ protected void finished(Description description) {
+ MDC.remove(TEST_CLASS);
+ MDC.remove(TEST_NAME);
+ }
+
+ @Override
+ protected void starting(Description description) {
+ MDC.put(TEST_CLASS, description.getClassName());
+ MDC.put(TEST_NAME, description.getMethodName());
+ }
+
+ @Override
+ protected void failed(Throwable e, Description description) {
+ final String baseUrl = getServerBaseUrl();
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw);
+
+ if (baseUrl != null) {
+ try {
+ DefaultHttpClient httpClient = new DefaultHttpClient();
+ RequestExecutor executor = new RequestExecutor(httpClient);
+ RequestBuilder rb = new RequestBuilder(baseUrl);
+
+ Request r = rb.buildGetRequest(SERVLET_PATH,
+ TEST_CLASS, description.getClassName(),
+ TEST_NAME, description.getMethodName());
+
+ executor.execute(r);
+ int statusCode =
executor.getResponse().getStatusLine().getStatusCode();
+
+ String msg = e.getMessage();
+ if (msg != null) {
+ pw.println(msg);
+ }
+
+ if (statusCode == 200){
+ pw.printf("=============== Logs from server [%s] for
[%s]===================%n",
+ baseUrl, description.getMethodName());
+ pw.print(executor.getContent());
+
pw.println("========================================================");
+ } else {
+ pw.printf("Not able to fetch logs from [%s%s]. " +
+ "TestLogServer probably not configured %n",
baseUrl, SERVLET_PATH);
+ }
+
+ } catch (Throwable t) {
+ System.err.printf("Error occurred while fetching test logs
from server [%s] %n", baseUrl);
+ t.printStackTrace(System.err);
+ }
+
+ System.err.print(sw.toString());
+ }
+ }
+
+ private static String getServerBaseUrl() {
+ SlingInstanceState testState =
SlingInstanceState.getInstance(SlingInstanceState.DEFAULT_INSTANCE_NAME);
+ String baseUrl = testState.getServerBaseUrl();
+ if (testState.isServerReady()) {
+ return baseUrl;
+ } else if (baseUrl == null) {
+ //Running via older HttpTestBase
+ baseUrl =
removeEndingSlash(System.getProperty("launchpad.http.server.url"));
+ }
+
+ if (baseUrl == null){
+ baseUrl = "http://localhost:8888";
+ }
+ return baseUrl;
+ }
+
+ private static String removeEndingSlash(String str) {
+ if(str != null && str.endsWith("/")) {
+ return str.substring(0, str.length() - 1);
+ }
+ return str;
+ }
+}
Propchange:
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/RemoteLogDumper.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestDescriptionInterceptor.java
URL:
http://svn.apache.org/viewvc/sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestDescriptionInterceptor.java?rev=1651751&r1=1651750&r2=1651751&view=diff
==============================================================================
---
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestDescriptionInterceptor.java
(original)
+++
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestDescriptionInterceptor.java
Wed Jan 14 17:54:31 2015
@@ -16,13 +16,14 @@
*/
package org.apache.sling.testing.tools.junit;
+import java.io.IOException;
+import java.util.Map;
+
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.protocol.HttpContext;
-import org.junit.runner.Description;
-
-import java.io.IOException;
+import org.slf4j.MDC;
/**
* HttpClient interceptor that propagates the current test name as part HTTP
request headers.
@@ -32,15 +33,28 @@ import java.io.IOException;
*
* @see MDC http://www.slf4j.org/manual.html
*/
-public class TestDescriptionInterceptor implements HttpRequestInterceptor{
- public static final String TEST_NAME_HEADER = "sling.test.name";
- public static final String TEST_CLASS_HEADER = "sling.test.class";
+public class TestDescriptionInterceptor implements HttpRequestInterceptor {
+ private static final String SLING_HEADER_PREFIX = "X-Sling-";
public void process(HttpRequest httpRequest, HttpContext httpContext)
throws HttpException, IOException {
- final Description desc =
TestDescriptionRule.getCurrentTestDescription();
- if(desc != null){
- httpRequest.addHeader(TEST_NAME_HEADER,desc.getMethodName());
- httpRequest.addHeader(TEST_CLASS_HEADER,desc.getClassName());
+ addSlingHeaders(httpRequest);
+ }
+
+ /**
+ * Adds all MDC key-value pairs as HTTP header where the key starts
+ * with 'X-Sling-'
+ */
+ private static void addSlingHeaders(HttpRequest m){
+ Map<?,?> mdc = MDC.getCopyOfContextMap();
+ if (mdc != null) {
+ for (Map.Entry<?, ?> e : mdc.entrySet()) {
+ Object key = e.getKey();
+ if (key instanceof String
+ && ((String)key).startsWith(SLING_HEADER_PREFIX)
+ && e.getValue() instanceof String) {
+ m.addHeader((String)key, (String)e.getValue());
+ }
+ }
}
}
}
\ No newline at end of file
Modified:
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestDescriptionRule.java
URL:
http://svn.apache.org/viewvc/sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestDescriptionRule.java?rev=1651751&r1=1651750&r2=1651751&view=diff
==============================================================================
---
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestDescriptionRule.java
(original)
+++
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestDescriptionRule.java
Wed Jan 14 17:54:31 2015
@@ -18,25 +18,21 @@ package org.apache.sling.testing.tools.j
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
+import org.slf4j.MDC;
/**
* Junit rule which exposes the current executing test's description as a
thread local instance
*/
public class TestDescriptionRule extends TestWatcher {
-
- private static final ThreadLocal<Description> currentTestDescription = new
ThreadLocal<Description>();
-
- @Override
- protected void finished(Description description) {
- currentTestDescription.remove();
- }
-
@Override
protected void starting(Description description) {
- currentTestDescription.set(description);
+ MDC.put(RemoteLogDumper.TEST_CLASS, description.getClassName());
+ MDC.put(RemoteLogDumper.TEST_NAME, description.getMethodName());
}
- public static Description getCurrentTestDescription(){
- return currentTestDescription.get();
+ @Override
+ protected void finished(Description description) {
+ MDC.remove(RemoteLogDumper.TEST_CLASS);
+ MDC.remove(RemoteLogDumper.TEST_NAME);
}
}
Modified:
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestNameLoggingFilter.java
URL:
http://svn.apache.org/viewvc/sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestNameLoggingFilter.java?rev=1651751&r1=1651750&r2=1651751&view=diff
==============================================================================
---
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestNameLoggingFilter.java
(original)
+++
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/TestNameLoggingFilter.java
Wed Jan 14 17:54:31 2015
@@ -16,8 +16,8 @@
*/
package org.apache.sling.testing.tools.junit;
-import static
org.apache.sling.testing.tools.junit.TestDescriptionInterceptor.TEST_CLASS_HEADER;
-import static
org.apache.sling.testing.tools.junit.TestDescriptionInterceptor.TEST_NAME_HEADER;
+import static org.apache.sling.testing.tools.junit.RemoteLogDumper.TEST_CLASS;
+import static org.apache.sling.testing.tools.junit.RemoteLogDumper.TEST_NAME;
import java.io.IOException;
@@ -54,17 +54,17 @@ public class TestNameLoggingFilter imple
public void doFilter(ServletRequest servletRequest, ServletResponse
servletResponse, FilterChain filterChain)
throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest)
servletRequest;
- final String className = httpRequest.getHeader(TEST_CLASS_HEADER);
+ final String className = httpRequest.getHeader(TEST_CLASS);
if(className == null) {
filterChain.doFilter(servletRequest,servletResponse);
return;
}
- final String testName = httpRequest.getHeader(TEST_NAME_HEADER);
+ final String testName = httpRequest.getHeader(TEST_NAME);
try {
- MDC.put(TEST_NAME_HEADER,testName);
- MDC.put(TEST_CLASS_HEADER,className);
+ MDC.put(TEST_NAME,testName);
+ MDC.put(TEST_CLASS,className);
log.info("Starting request as part of test ==== {}.{}
====",className,testName);
@@ -73,8 +73,8 @@ public class TestNameLoggingFilter imple
} finally {
log.info("Finishing request as part of test ==== {}.{}
====",className,testName);
- MDC.remove(TEST_NAME_HEADER);
- MDC.remove(TEST_CLASS_HEADER);
+ MDC.remove(TEST_NAME);
+ MDC.remove(TEST_CLASS);
}
}
Modified:
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/package-info.java
URL:
http://svn.apache.org/viewvc/sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/package-info.java?rev=1651751&r1=1651750&r2=1651751&view=diff
==============================================================================
---
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/package-info.java
(original)
+++
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/junit/package-info.java
Wed Jan 14 17:54:31 2015
@@ -17,7 +17,7 @@
* under the License.
*/
-@Version("1.0.9")
+@Version("2.0.0")
package org.apache.sling.testing.tools.junit;
import aQute.bnd.annotation.Version;
Modified:
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/sling/SlingTestBase.java
URL:
http://svn.apache.org/viewvc/sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/sling/SlingTestBase.java?rev=1651751&r1=1651750&r2=1651751&view=diff
==============================================================================
---
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/sling/SlingTestBase.java
(original)
+++
sling/trunk/testing/tools/src/main/java/org/apache/sling/testing/tools/sling/SlingTestBase.java
Wed Jan 14 17:54:31 2015
@@ -31,6 +31,7 @@ import org.apache.http.impl.client.Defau
import org.apache.sling.testing.tools.http.RequestBuilder;
import org.apache.sling.testing.tools.http.RequestExecutor;
import org.apache.sling.testing.tools.jarexec.JarExecutor;
+import org.apache.sling.testing.tools.junit.TestDescriptionInterceptor;
import org.apache.sling.testing.tools.osgi.WebconsoleClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -78,6 +79,7 @@ public class SlingTestBase implements Sl
this.slingTestState = slingTestState;
this.systemProperties = systemProperties;
this.keepJarRunning =
"true".equals(systemProperties.getProperty(KEEP_JAR_RUNNING_PROP));
+ this.httpClient.addRequestInterceptor(new
TestDescriptionInterceptor());
final String configuredUrl =
systemProperties.getProperty(TEST_SERVER_URL_PROP,
systemProperties.getProperty("launchpad.http.server.url"));