This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-hc-junit-bridge.git
commit c8d4256396cb73752a1cf582b7367078d5ea8103 Author: Bertrand Delacretaz <bdelacre...@apache.org> AuthorDate: Fri Jul 18 12:33:52 2014 +0000 SLING-2788 - move bridge module from whiteboard to extensions git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1611616 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 116 ++++++++++++ .../sling/hc/junitbridge/HealthCheckTest.java | 76 ++++++++ .../hc/junitbridge/HealthCheckTestsProvider.java | 118 ++++++++++++ .../sling/hc/junitbridge/JUnitTestBridge.java | 44 +++++ .../sling/hc/junitbridge/TestBridgeContext.java | 45 +++++ .../tests/HealthCheckTestsProviderTest.java | 198 +++++++++++++++++++++ 6 files changed, 597 insertions(+) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..33f1002 --- /dev/null +++ b/pom.xml @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8"?> +<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/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>19</version> + <relativePath>../../../../parent/pom.xml</relativePath> + </parent> + + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.hc.junit.bridge</artifactId> + <packaging>bundle</packaging> + <version>0.9.9-SNAPSHOT</version> + + <name>Apache Sling Health Check JUnit Bridge</name> + <inceptionYear>2013</inceptionYear> + + <description> + Makes Sling Health Checks available as server-side JUnit tests, to + allow them to be used as part of integration tests. + </description> + + <properties> + <sling.java.version>6</sling.java.version> + </properties> + + <scm> + <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/healthcheck/junit-bridge</connection> + <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/healthcheck/junit-bridge</developerConnection> + <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/healthcheck/junit-bridge</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> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.scr.annotations</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.6.2</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>1.9.5</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>2.5</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>1.6.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.osgi</artifactId> + <version>2.2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.hc.core</artifactId> + <version>1.1.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.junit.core</artifactId> + <version>1.0.6</version> + <scope>provided</scope> + </dependency> + </dependencies> +</project> diff --git a/src/main/java/org/apache/sling/hc/junitbridge/HealthCheckTest.java b/src/main/java/org/apache/sling/hc/junitbridge/HealthCheckTest.java new file mode 100644 index 0000000..73f3d72 --- /dev/null +++ b/src/main/java/org/apache/sling/hc/junitbridge/HealthCheckTest.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The SF 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.hc.junitbridge; + +import junit.framework.TestCase; + +import org.apache.sling.hc.api.HealthCheck; +import org.apache.sling.hc.api.Result; +import org.apache.sling.hc.api.ResultLog; +import org.apache.sling.hc.util.HealthCheckMetadata; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +public class HealthCheckTest extends TestCase { + private final HealthCheck hc; + private final HealthCheckMetadata metadata; + private final BundleContext bundleContext; + private final ServiceReference serviceRef; + + HealthCheckTest(TestBridgeContext context, ServiceReference ref) { + super("testHealthCheck"); + bundleContext = context.getBundleContext(); + serviceRef = ref; + this.hc = (HealthCheck)bundleContext.getService(ref); + this.metadata = new HealthCheckMetadata(ref); + } + + @Override + public String getName() { + return metadata.getName(); + } + + /** Execute our health check and dump its log + * messages > INFO if it fails */ + public void testHealthCheck() { + try { + final Result r = hc.execute(); + final StringBuilder failMsg = new StringBuilder(); + if(!r.isOk()) { + failMsg.append(metadata.getName()); + failMsg.append("\n"); + for(ResultLog.Entry log : r) { + if(log.getStatus().compareTo(Result.Status.INFO) > 0) { + if(failMsg.length() > 0) { + failMsg.append("\n"); + } + failMsg.append(log.getStatus().toString()); + failMsg.append(" - "); + failMsg.append(log.getMessage()); + } + } + } + if(failMsg.length() > 0) { + fail("Health Check failed: " + failMsg.toString()); + } + } finally { + // TODO is that ok? service not used anymore after this? + bundleContext.ungetService(serviceRef); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/hc/junitbridge/HealthCheckTestsProvider.java b/src/main/java/org/apache/sling/hc/junitbridge/HealthCheckTestsProvider.java new file mode 100644 index 0000000..7008aa2 --- /dev/null +++ b/src/main/java/org/apache/sling/hc/junitbridge/HealthCheckTestsProvider.java @@ -0,0 +1,118 @@ +/* + * 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 SF 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.hc.junitbridge; + +import java.util.ArrayList; +import java.util.List; + +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.Service; +import org.apache.sling.commons.osgi.PropertiesUtil; +import org.apache.sling.junit.TestsProvider; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Bridge Health Checks into the Sling JUnit server-side test + * framework, based on their tags. + */ +@Component(metatype=true) +@Service +public class HealthCheckTestsProvider implements TestsProvider { + + private String servicePid; + private long lastModified; + private BundleContext bundleContext; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + public static final String TEST_NAME_PREFIX = "HealthChecks("; + public static final String TEST_NAME_SUFFIX = ")"; + + @Property(cardinality=2147483647, + label="Health Check Tags", + description="Groups of health check tags to execute as JUnit tests. Use the standard Health Checks 'includeThis,-omitThat' syntax") + public static final String PROP_TAG_GROUPS = "health.check.tag.groups"; + + private String [] tagGroups; + + @Activate + protected void activate(ComponentContext ctx) { + bundleContext = ctx.getBundleContext(); + tagGroups = PropertiesUtil.toStringArray(ctx.getProperties().get(PROP_TAG_GROUPS)); + if(tagGroups == null) { + tagGroups = new String[]{}; + log.warn("No tag groups configured via {}, Health Checks won't be available as JUnit tests", PROP_TAG_GROUPS); + } + servicePid = (String)ctx.getProperties().get(Constants.SERVICE_PID); + lastModified = System.currentTimeMillis(); + } + + @Deactivate + protected void deactivate() { + bundleContext = null; + servicePid = null; + lastModified = -1; + } + + @Override + public Class<?> createTestClass(String testName) throws ClassNotFoundException { + // The test name is like "Health Checks(foo,bar)" and we need just 'foo,bar' + String tagString = null; + try { + tagString = testName.substring(0, testName.length() - TEST_NAME_SUFFIX.length()).substring(TEST_NAME_PREFIX.length()); + } catch(Exception e) { + throw new RuntimeException("Invalid test name:" + testName); + } + + JUnitTestBridge.setThreadContext(new TestBridgeContext(bundleContext, splitTags(tagString))); + return JUnitTestBridge.class; + } + + private String [] splitTags(String tags) { + final List<String> result = new ArrayList<String>(); + for(String tag: tags.split(",")) { + result.add(tag.trim()); + } + return result.toArray(new String[]{}); + } + + @Override + public String getServicePid() { + return servicePid; + } + + @Override + public List<String> getTestNames() { + final List<String> result = new ArrayList<String>(); + for(String t : tagGroups) { + result.add(TEST_NAME_PREFIX + t + TEST_NAME_SUFFIX); + } + return result; + } + + @Override + public long lastModified() { + return lastModified; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/hc/junitbridge/JUnitTestBridge.java b/src/main/java/org/apache/sling/hc/junitbridge/JUnitTestBridge.java new file mode 100644 index 0000000..c611c40 --- /dev/null +++ b/src/main/java/org/apache/sling/hc/junitbridge/JUnitTestBridge.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The SF 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.hc.junitbridge; + +import static org.junit.Assert.assertNotNull; +import junit.framework.TestSuite; + +import org.junit.runner.RunWith; +import org.junit.runners.AllTests; +import org.osgi.framework.ServiceReference; + +@RunWith(AllTests.class) +public class JUnitTestBridge { + private static ThreadLocal<TestBridgeContext> testContext = new ThreadLocal<TestBridgeContext>(); + + static void setThreadContext(TestBridgeContext c) { + testContext.set(c); + } + + public static junit.framework.Test suite() { + final TestBridgeContext context = testContext.get(); + assertNotNull("Expecting non-null TestBridgeContext, via ThreadLocal", context); + TestSuite suite = new TestSuite(); + for(ServiceReference ref : context.getFilter().getTaggedHealthCheckServiceReferences(context.getTags())) { + suite.addTest(new HealthCheckTest(context, ref)); + } + return suite; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/hc/junitbridge/TestBridgeContext.java b/src/main/java/org/apache/sling/hc/junitbridge/TestBridgeContext.java new file mode 100644 index 0000000..030dfe5 --- /dev/null +++ b/src/main/java/org/apache/sling/hc/junitbridge/TestBridgeContext.java @@ -0,0 +1,45 @@ +/* + * 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 SF 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.hc.junitbridge; + +import org.apache.sling.hc.util.HealthCheckFilter; +import org.osgi.framework.BundleContext; + +class TestBridgeContext { + private final String [] tags; + private final HealthCheckFilter filter; + private final BundleContext bundleContext; + + TestBridgeContext(BundleContext bundleContext, String [] tags) { + this.bundleContext = bundleContext; + this.tags = tags; + this.filter = new HealthCheckFilter(bundleContext); + } + + String [] getTags() { + return tags; + } + + HealthCheckFilter getFilter() { + return filter; + } + + BundleContext getBundleContext() { + return bundleContext; + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/hc/junitbridge/tests/HealthCheckTestsProviderTest.java b/src/test/java/org/apache/sling/hc/junitbridge/tests/HealthCheckTestsProviderTest.java new file mode 100644 index 0000000..4931534 --- /dev/null +++ b/src/test/java/org/apache/sling/hc/junitbridge/tests/HealthCheckTestsProviderTest.java @@ -0,0 +1,198 @@ +package org.apache.sling.hc.junitbridge.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.apache.sling.hc.api.HealthCheck; +import org.apache.sling.hc.api.Result; +import org.apache.sling.hc.junitbridge.HealthCheckTestsProvider; +import org.apache.sling.hc.util.FormattingResultLog; +import org.apache.sling.junit.TestsProvider; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.JUnitCore; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.ComponentContext; + +/** Test the HealthCheckTestsProvider, which + * uses everything else. + */ +public class HealthCheckTestsProviderTest { + private TestsProvider provider; + private long setupTimestamp; + private final Random random = new Random(); + + static abstract class HcLogSetter { + abstract FormattingResultLog setLog(FormattingResultLog log); + }; + + private static final Map<String, HcLogSetter> LOG_SETTERS = new HashMap<String, HcLogSetter>(); + + static { + LOG_SETTERS.put("PASS_HC", new HcLogSetter() { + @Override + FormattingResultLog setLog(FormattingResultLog log) { + log.info("pass"); + return log; + } + }); + LOG_SETTERS.put("OK_HC", new HcLogSetter() { + @Override + FormattingResultLog setLog(FormattingResultLog log) { + log.debug("ok"); + return log; + } + }); + LOG_SETTERS.put("FAIL_HC", new HcLogSetter() { + @Override + FormattingResultLog setLog(FormattingResultLog log) { + log.warn("fail"); + return log; + } + }); + LOG_SETTERS.put("BAD_HC", new HcLogSetter() { + @Override + FormattingResultLog setLog(FormattingResultLog log) { + log.warn("bad"); + return log; + } + }); + } + + // Our fake tags represent a number of + // passing (P) or failing (F) fake HCs + final String [] TAG_GROUPS = { + "a,b", + "some,tags", + "justOne" + }; + + private static String testName(String tagGroup) { + return HealthCheckTestsProvider.TEST_NAME_PREFIX + tagGroup + HealthCheckTestsProvider.TEST_NAME_SUFFIX; + } + + /** Return ServiceReferences that point to our test HealthChecks */ + private ServiceReference [] getMockReferences(BundleContext bc, String OSGiFilter) { + + final List<ServiceReference> refs = new ArrayList<ServiceReference>(); + + for(String key : LOG_SETTERS.keySet()) { + if(OSGiFilter.contains(key)) { + final HcLogSetter hls = LOG_SETTERS.get(key); + final ServiceReference ref = Mockito.mock(ServiceReference.class); + Mockito.when(ref.getProperty(Constants.SERVICE_ID)).thenReturn(random.nextLong()); + Mockito.when(ref.getProperty(HealthCheck.NAME)).thenReturn("someTest"); + final HealthCheck hc = new HealthCheck() { + @Override + public Result execute() { + final FormattingResultLog log = new FormattingResultLog(); + return new Result(hls.setLog(log)); + } + }; + Mockito.when(bc.getService(ref)).thenReturn(hc); + + refs.add(ref); + } + } + + return refs.toArray(new ServiceReference[]{}); + } + + @Before + public void setup() throws InvalidSyntaxException { + setupTimestamp = System.currentTimeMillis(); + final ComponentContext ctx = Mockito.mock(ComponentContext.class); + + // context properties + final Dictionary<String, Object> props = new Hashtable<String, Object>(); + props.put(HealthCheckTestsProvider.PROP_TAG_GROUPS, TAG_GROUPS); + props.put(Constants.SERVICE_PID, getClass().getName()); + Mockito.when(ctx.getProperties()).thenReturn(props); + + // bundle context + final BundleContext bc = Mockito.mock(BundleContext.class); + Mockito.when(ctx.getBundleContext()).thenReturn(bc); + + // HealthCheck ServiceReferences mocks + Mockito.when(bc.getServiceReferences(Mockito.anyString(), Mockito.anyString())).thenAnswer( + new Answer<ServiceReference[]> () { + @Override + public ServiceReference[] answer(InvocationOnMock invocation) throws Throwable { + return getMockReferences(bc, (String)invocation.getArguments()[1]); + } + }); + + provider = new HealthCheckTestsProvider() { + { + activate(ctx); + } + }; + } + + @Test + public void testGetTestNames() { + final List<String> names = provider.getTestNames(); + assertEquals(TAG_GROUPS.length, names.size()); + for(String tag : TAG_GROUPS) { + final String expected = testName(tag); + assertTrue("Expecting test names to contain " + expected + ", " + names, names.contains(expected)); + } + } + + @Test + public void testServicePid() { + assertEquals(getClass().getName(), provider.getServicePid()); + } + + @Test + public void testLastModified() { + assertTrue(provider.lastModified() >= setupTimestamp); + } + + @Test + public void testNoFailuresHealthCheck() throws ClassNotFoundException { + final Class<?> c = provider.createTestClass(testName("PASS_HC")); + assertNotNull("Expecting non-null test class", c); + final org.junit.runner.Result r = JUnitCore.runClasses(c); + assertEquals(0, r.getFailureCount()); + assertEquals(1, r.getRunCount()); + } + + @Test + public void testFailingHealthCheck() throws ClassNotFoundException { + final Class<?> c = provider.createTestClass(testName("FAIL_HC and BAD_HC")); + assertNotNull("Expecting non-null test class", c); + final org.junit.runner.Result r = JUnitCore.runClasses(c); + assertEquals(2, r.getFailureCount()); + assertEquals(2, r.getRunCount()); + } + + @Test + public void testPassAndFailHealthCheck() throws ClassNotFoundException { + final Class<?> c = provider.createTestClass(testName("FAIL_HC and PASS_HC and OK_HC and BAD_HC")); + assertNotNull("Expecting non-null test class", c); + final org.junit.runner.Result r = JUnitCore.runClasses(c); + assertEquals(2, r.getFailureCount()); + assertEquals(4, r.getRunCount()); + } + + @Test(expected=RuntimeException.class) + public void testInvalidTestName() throws ClassNotFoundException { + provider.createTestClass("foo"); + } +} \ No newline at end of file -- To stop receiving notification emails like this one, please contact "commits@sling.apache.org" <commits@sling.apache.org>.