http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e06e79ab/utils/test-support/src/main/java/org/apache/brooklyn/test/PlatformTestSelectorListener.java ---------------------------------------------------------------------- diff --git a/utils/test-support/src/main/java/org/apache/brooklyn/test/PlatformTestSelectorListener.java b/utils/test-support/src/main/java/org/apache/brooklyn/test/PlatformTestSelectorListener.java new file mode 100644 index 0000000..0282eee --- /dev/null +++ b/utils/test-support/src/main/java/org/apache/brooklyn/test/PlatformTestSelectorListener.java @@ -0,0 +1,57 @@ +/* + * 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.brooklyn.test; + +import org.testng.IInvokedMethod; +import org.testng.IInvokedMethodListener; +import org.testng.ITestResult; +import org.testng.SkipException; + +public class PlatformTestSelectorListener implements IInvokedMethodListener { + private static final String GROUP_UNIX = "UNIX"; + private static final String GROUP_WINDOWS = "Windows"; + + public static boolean isWindows() { + return System.getProperty("os.name").startsWith("Windows"); + } + + @Override + public void beforeInvocation(IInvokedMethod method, ITestResult testResult) { + boolean isUnixTest = false; + boolean isWinTest = false; + + String[] groups = method.getTestMethod().getGroups(); + for (String group : groups) { + isUnixTest = isUnixTest || group.equalsIgnoreCase(GROUP_UNIX); + isWinTest = isWinTest || group.equalsIgnoreCase(GROUP_WINDOWS); + } + + boolean isWinPlatform = isWindows(); + if (isUnixTest || isWinTest) { + if (isWinPlatform && isUnixTest && !isWinTest) { + throw new SkipException("Skipping unix-specific test."); + } else if (!isWinPlatform && isWinTest && !isUnixTest) { + throw new SkipException("Skipping windows-specific test."); + } + } + } + + @Override + public void afterInvocation(IInvokedMethod method, ITestResult testResult) {} +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e06e79ab/utils/test-support/src/main/java/org/apache/brooklyn/test/StatusListener.java ---------------------------------------------------------------------- diff --git a/utils/test-support/src/main/java/org/apache/brooklyn/test/StatusListener.java b/utils/test-support/src/main/java/org/apache/brooklyn/test/StatusListener.java new file mode 100644 index 0000000..1803967 --- /dev/null +++ b/utils/test-support/src/main/java/org/apache/brooklyn/test/StatusListener.java @@ -0,0 +1,100 @@ +/* + * 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.brooklyn.test; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.IClass; +import org.testng.ITestContext; +import org.testng.ITestListener; +import org.testng.ITestResult; + +/** + * adapted from the following class: + * + * @see org.jclouds.test.testng.UnitTestStatusListener + * + * normally not used, preferring instead LoggingVerboseReporter which prints out config info + */ +public class StatusListener implements ITestListener { + + public static final Logger log = LoggerFactory.getLogger(StatusListener.class); + + /** + * Holds test classes actually running in all threads. + */ + private ThreadLocal<IClass> threadTestClass = new ThreadLocal<IClass>(); + private ThreadLocal<Long> threadTestStart = new ThreadLocal<Long>(); + + private AtomicInteger failed = new AtomicInteger(0); + private AtomicInteger succeded = new AtomicInteger(0); + private AtomicInteger skipped = new AtomicInteger(0); + + //TODO instead of system.out.println we should log -- *and* perhaps write to sysout if logger doesn't? + protected static void log(String msg) { + log.info(msg); + } + + public void onTestStart(ITestResult res) { + log("Starting test " + getTestDesc(res)); + threadTestClass.set(res.getTestClass()); + threadTestStart.set(System.currentTimeMillis()); + } + + synchronized public void onTestSuccess(ITestResult arg0) { + log(getThreadId() + " Test " + getTestDesc(arg0) + " succeeded: " + (System.currentTimeMillis() - threadTestStart.get()) + "ms"); + succeded.incrementAndGet(); + printStatus(); + } + + synchronized public void onTestFailure(ITestResult arg0) { + log(getThreadId() + " Test " + getTestDesc(arg0) + " failed: "+arg0.getThrowable()); + failed.incrementAndGet(); + printStatus(); + } + + synchronized public void onTestSkipped(ITestResult arg0) { + System.out.println(getThreadId() + " Test " + getTestDesc(arg0) + " skipped."); + skipped.incrementAndGet(); + printStatus(); + } + + public void onTestFailedButWithinSuccessPercentage(ITestResult arg0) { + } + + public void onStart(ITestContext arg0) { + } + + public void onFinish(ITestContext arg0) { + } + + private String getThreadId() { + return "[" + Thread.currentThread().getName() + "]"; + } + + private String getTestDesc(ITestResult res) { + return res.getMethod().getMethodName() + "(" + res.getTestClass().getName() + ")"; + } + + private void printStatus() { + log("Test suite progress: tests succeeded: " + succeded.get() + ", failed: " + failed.get() + ", skipped: " + skipped.get() + "."); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e06e79ab/utils/test-support/src/main/java/org/apache/brooklyn/test/TestResourceUnavailableException.java ---------------------------------------------------------------------- diff --git a/utils/test-support/src/main/java/org/apache/brooklyn/test/TestResourceUnavailableException.java b/utils/test-support/src/main/java/org/apache/brooklyn/test/TestResourceUnavailableException.java new file mode 100644 index 0000000..c49a248 --- /dev/null +++ b/utils/test-support/src/main/java/org/apache/brooklyn/test/TestResourceUnavailableException.java @@ -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.brooklyn.test; + +import com.google.common.base.Throwables; +import org.testng.SkipException; + +import java.io.IOException; +import java.io.InputStream; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Indicates that a test cannot be run as a necessary resource is not available. + * + * <p>This exception should be used by tests that need a particular test resource (i.e., a file that is conventionally + * in the <code>src/test/resources</code> folder, and that available as a classpath resource at runtime) is not + * available. It will cause TestNG to mark the test as "skipped" with a suitable message.</p> + * + * <p>Some tests require binary files (such as OSGi bundles) which under Apache conventions are not able to be + * distributed in our source code release. This exception allows such tests to become "optional" so that they do not + * cause test failures when running the tests on a source distribution.</p> + * + * <p>Note that the single-string constructors expect the string to be the simple name of the classpath resource that + * is not available. The exception message is then derived from this. The two-string constructors take both the + * resource name and an explicit exception message.</p> + */ +@SuppressWarnings("UnusedDeclaration") +public class TestResourceUnavailableException extends SkipException { + + private final String resourceName; + + /** + * Asserts that a resource is available on the classpath; otherwise, throws {@link TestResourceUnavailableException} + * + * Note that this will use the same classloader that was used to load this class. + * + * @param resourceName the classpath resource name, e.g. + * <code>/brooklyn/osgi/brooklyn-test-osgi-entities.jar</code> + */ + public static void throwIfResourceUnavailable(Class<?> relativeToClass, String resourceName) { + checkNotNull(relativeToClass, relativeToClass); + checkNotNull(resourceName, "resourceName"); + checkArgument(!resourceName.isEmpty(), "resourceName must not be empty"); + InputStream resource = relativeToClass.getResourceAsStream(resourceName); + if (resource == null) + throw new TestResourceUnavailableException(resourceName); + + // just make sure we clean up the resource + try { + resource.close(); + } catch (IOException e) { + Throwables.propagate(e); + } + } + + /** + * Instantiate an exception, giving the name of the unavailable resource. + * + * @param resourceName the name of the resource on the classpath, e.g. + * <code>/brooklyn/osgi/brooklyn-test-osgi-entities.jar</code> + */ + public TestResourceUnavailableException(String resourceName) { + super(messageFromResourceName(resourceName)); + this.resourceName = resourceName; + } + + /** + * Instantiate an exception, giving the name of the unavailable resource. + * + * @param resourceName the name of the resource on the classpath, e.g. + * <code>/brooklyn/osgi/brooklyn-test-osgi-entities.jar</code> + * @param cause the underlying exception that caused this one + */ + public TestResourceUnavailableException(String resourceName, Throwable cause) { + super(messageFromResourceName(resourceName), cause); + this.resourceName = resourceName; + } + + /** + * Instantiate an exception, giving the name of the unavailable resource. + * + * @param resourceName the name of the resource on the classpath, e.g. + * <code>/brooklyn/osgi/brooklyn-test-osgi-entities.jar</code> + * @param skipMessage the message associated with the exception + */ + public TestResourceUnavailableException(String resourceName, String skipMessage) { + super(skipMessage); + this.resourceName = resourceName; + } + + /** + * Instantiate an exception, giving the name of the unavailable resource. + * + * @param resourceName the name of the resource on the classpath, e.g. + * <code>/brooklyn/osgi/brooklyn-test-osgi-entities.jar</code> + * @param skipMessage the message associated with the exception + * @param cause the underlying exception that caused this one + */ + public TestResourceUnavailableException(String resourceName, String skipMessage, Throwable cause) { + super(skipMessage, cause); + this.resourceName = resourceName; + } + + private static String messageFromResourceName(String resourceName) { + return String.format("Test resource '%s' not found; test skipped.", resourceName); + } + + /** + * Get the name of the classpath resource that could not be loaded. + * + * @return the name of the classpath resource whose absence caused this exception. + */ + public String getResourceName() { + return resourceName; + } + + @Override + public boolean isSkip() { + return true; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e06e79ab/utils/test-support/src/main/java/org/apache/brooklyn/test/VerboseReporter.java ---------------------------------------------------------------------- diff --git a/utils/test-support/src/main/java/org/apache/brooklyn/test/VerboseReporter.java b/utils/test-support/src/main/java/org/apache/brooklyn/test/VerboseReporter.java new file mode 100644 index 0000000..0ec78e1 --- /dev/null +++ b/utils/test-support/src/main/java/org/apache/brooklyn/test/VerboseReporter.java @@ -0,0 +1,343 @@ +/* + * 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.brooklyn.test; + +import java.util.List; + +import org.testng.ITestContext; +import org.testng.ITestNGMethod; +import org.testng.ITestResult; +import org.testng.TestListenerAdapter; +import org.testng.internal.ConstructorOrMethod; +import org.testng.internal.Utils; + +/** + * Reporter printing out detailed messages about what TestNG + * is going to run and what is the status of what has been just run. + * + * To see messages from this reporter, either run Ant in verbose mode ('ant -v') + * or set verbose level to 5 or higher + * + * @author Lukas Jungmann + * @since 6.4 + * + * THIS IS AN EXACT COPY OF THE CLASS IN org.testng.reporters + * EXCEPT FOR THIS COMMENT AND log(String) CHANGED TO BE PROTECTED + */ +public class VerboseReporter extends TestListenerAdapter { + + /** + * Default prefix for messages printed out by this reporter + * + */ + public static final String LISTENER_PREFIX = "[VerboseTestNG] "; + private String suiteName; + private final String prefix; + + private enum Status { + + SUCCESS(ITestResult.SUCCESS), FAILURE(ITestResult.FAILURE), SKIP(ITestResult.SKIP), + SUCCESS_PERCENTAGE_FAILURE(ITestResult.SUCCESS_PERCENTAGE_FAILURE), STARTED(ITestResult.STARTED); + private final int code; + + private Status(int i) { + code = i; + } + + @SuppressWarnings("unused") + public int getCode() { + return code; + } + } + + /** + * Default constructor + */ + public VerboseReporter() { + this(LISTENER_PREFIX); + } + + /** + * Create VerboseReporter with custom prefix + * + * @param prefix prefix for messages printed out by this reporter + */ + public VerboseReporter(String prefix) { + this.prefix = prefix; + } + + @Override + public void beforeConfiguration(ITestResult tr) { + super.beforeConfiguration(tr); + logTestResult(Status.STARTED, tr, true); + } + + @Override + public void onConfigurationFailure(ITestResult tr) { + super.onConfigurationFailure(tr); + logTestResult(Status.FAILURE, tr, true); + } + + @Override + public void onConfigurationSkip(ITestResult tr) { + super.onConfigurationSkip(tr); + logTestResult(Status.SKIP, tr, true); + } + + @Override + public void onConfigurationSuccess(ITestResult tr) { + super.onConfigurationSuccess(tr); + logTestResult(Status.SUCCESS, tr, true); + } + + @Override + public void onTestStart(ITestResult tr) { + logTestResult(Status.STARTED, tr, false); + } + + @Override + public void onTestFailure(ITestResult tr) { + super.onTestFailure(tr); + logTestResult(Status.FAILURE, tr, false); + } + + @Override + public void onTestFailedButWithinSuccessPercentage(ITestResult tr) { + super.onTestFailedButWithinSuccessPercentage(tr); + logTestResult(Status.SUCCESS_PERCENTAGE_FAILURE, tr, false); + } + + @Override + public void onTestSkipped(ITestResult tr) { + super.onTestSkipped(tr); + logTestResult(Status.SKIP, tr, false); + } + + @Override + public void onTestSuccess(ITestResult tr) { + super.onTestSuccess(tr); + logTestResult(Status.SUCCESS, tr, false); + } + + @Override + public void onStart(ITestContext ctx) { + suiteName = ctx.getName();//ctx.getSuite().getXmlSuite().getFileName(); + log("RUNNING: Suite: \"" + suiteName + "\" containing \"" + ctx.getAllTestMethods().length + "\" Tests (config: " + ctx.getSuite().getXmlSuite().getFileName() + ")"); + } + + @Override + public void onFinish(ITestContext context) { + logResults(); + suiteName = null; + } + + private ITestNGMethod[] resultsToMethods(List<ITestResult> results) { + ITestNGMethod[] result = new ITestNGMethod[results.size()]; + int i = 0; + for (ITestResult tr : results) { + result[i++] = tr.getMethod(); + } + return result; + } + + /** + * Print out test summary + */ + private void logResults() { + // + // Log test summary + // + ITestNGMethod[] ft = resultsToMethods(getFailedTests()); + StringBuilder sb = new StringBuilder("\n===============================================\n"); + sb.append(" ").append(suiteName).append("\n"); + sb.append(" Tests run: ").append(Utils.calculateInvokedMethodCount(getAllTestMethods())); + sb.append(", Failures: ").append(Utils.calculateInvokedMethodCount(ft)); + sb.append(", Skips: ").append(Utils.calculateInvokedMethodCount(resultsToMethods(getSkippedTests()))); + int confFailures = getConfigurationFailures().size(); + int confSkips = getConfigurationSkips().size(); + if (confFailures > 0 || confSkips > 0) { + sb.append("\n").append(" Configuration Failures: ").append(confFailures); + sb.append(", Skips: ").append(confSkips); + } + sb.append("\n==============================================="); + log(sb.toString()); + } + + /** + * Log meaningful message for passed in arguments. + * Message itself is of form: + * $status: "$suiteName" - $methodDeclaration ($actualArguments) finished in $x ms ($run of $totalRuns) + * + * @param st status of passed in itr + * @param itr test result to be described + * @param isConfMethod is itr describing configuration method + */ + private void logTestResult(Status st, ITestResult itr, boolean isConfMethod) { + StringBuilder sb = new StringBuilder(); + String stackTrace = ""; + switch (st) { + case STARTED: + sb.append("INVOKING"); + break; + case SKIP: + sb.append("SKIPPED"); + stackTrace = itr.getThrowable() != null + ? Utils.stackTrace(itr.getThrowable(), false)[0] : ""; + break; + case FAILURE: + sb.append("FAILED"); + stackTrace = itr.getThrowable() != null + ? Utils.stackTrace(itr.getThrowable(), false)[0] : ""; + break; + case SUCCESS: + sb.append("PASSED"); + break; + case SUCCESS_PERCENTAGE_FAILURE: + sb.append("PASSED with failures"); + break; + default: + //not happen + throw new RuntimeException("Unsupported test status:" + itr.getStatus()); + } + if (isConfMethod) { + sb.append(" CONFIGURATION: "); + } else { + sb.append(": "); + } + ITestNGMethod tm = itr.getMethod(); + int identLevel = sb.length(); + sb.append(getMethodDeclaration(tm)); + Object[] params = itr.getParameters(); + Class<?>[] paramTypes = tm.getConstructorOrMethod().getParameterTypes(); + if (null != params && params.length > 0) { + // The error might be a data provider parameter mismatch, so make + // a special case here + if (params.length != paramTypes.length) { + sb.append("Wrong number of arguments were passed by the Data Provider: found "); + sb.append(params.length); + sb.append(" but expected "); + sb.append(paramTypes.length); + } else { + sb.append("(value(s): "); + for (int i = 0; i < params.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(Utils.toString(params[i], paramTypes[i])); + } + sb.append(")"); + + } + } + if (Status.STARTED != st) { + sb.append(" finished in "); + sb.append(itr.getEndMillis() - itr.getStartMillis()); + sb.append(" ms"); + if (!Utils.isStringEmpty(tm.getDescription())) { + sb.append("\n"); + for (int i = 0; i < identLevel; i++) { + sb.append(" "); + } + sb.append(tm.getDescription()); + } + if (tm.getInvocationCount() > 1) { + sb.append(" ("); + sb.append(tm.getCurrentInvocationCount()); + sb.append(" of "); + sb.append(tm.getInvocationCount()); + sb.append(")"); + } + if (!Utils.isStringEmpty(stackTrace)) { + sb.append("\n").append(stackTrace.substring(0, stackTrace.lastIndexOf(System.getProperty("line.separator")))); + } + } else { + if (!isConfMethod && tm.getInvocationCount() > 1) { + sb.append(" success: "); + sb.append(tm.getSuccessPercentage()); + sb.append("%"); + } + } + log(sb.toString()); + } + + protected void log(String message) { + //prefix all output lines + System.out.println(message.replaceAll("(?m)^", prefix)); + } + + /** + * + * @param method method to be described + * @return FQN of a class + method declaration for a method passed in + * ie. test.triangle.CheckCount.testCheckCount(java.lang.String) + */ + private String getMethodDeclaration(ITestNGMethod method) { + //see Utils.detailedMethodName + //perhaps should rather adopt the original method instead + ConstructorOrMethod m = method.getConstructorOrMethod(); + StringBuilder buf = new StringBuilder(); + buf.append("\""); + if (suiteName != null) { + buf.append(suiteName); + } else { + buf.append("UNKNOWN"); + } + buf.append("\""); + buf.append(" - "); + if (method.isBeforeSuiteConfiguration()) { + buf.append("@BeforeSuite "); + } else if (method.isBeforeTestConfiguration()) { + buf.append("@BeforeTest "); + } else if (method.isBeforeClassConfiguration()) { + buf.append("@BeforeClass "); + } else if (method.isBeforeGroupsConfiguration()) { + buf.append("@BeforeGroups "); + } else if (method.isBeforeMethodConfiguration()) { + buf.append("@BeforeMethod "); + } else if (method.isAfterMethodConfiguration()) { + buf.append("@AfterMethod "); + } else if (method.isAfterGroupsConfiguration()) { + buf.append("@AfterGroups "); + } else if (method.isAfterClassConfiguration()) { + buf.append("@AfterClass "); + } else if (method.isAfterTestConfiguration()) { + buf.append("@AfterTest "); + } else if (method.isAfterSuiteConfiguration()) { + buf.append("@AfterSuite "); + } + buf.append(m.getDeclaringClass().getName()); + buf.append("."); + buf.append(m.getName()); + buf.append("("); + int i = 0; + for (Class<?> p : m.getParameterTypes()) { + if (i++ > 0) { + buf.append(", "); + } + buf.append(p.getName()); + } + buf.append(")"); + return buf.toString(); + } + + @Override + public String toString() { + return "VerboseReporter{" + "suiteName=" + suiteName + '}'; + } +}
