/* * Copyright 2012 the original author or authors. * * Licensed 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.gradle.api.internal.tasks.testing.junit.result; import org.gradle.api.tasks.testing.*; import org.gradle.internal.serialize.PlaceholderException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; /** * Collects the test results into memory and spools the test output to file during execution (to avoid holding it all in memory). */ public class TestReportDataCollector implements TestListener, TestOutputListener { private final Map<String, TestClassResult> results; private final TestOutputStore.Writer outputWriter; private final Map<TestDescriptor, TestMethodResult> currentTestMethods = new HashMap<TestDescriptor, TestMethodResult>(); private long internalIdCounter = 1; public TestReportDataCollector(Map<String, TestClassResult> results, TestOutputStore.Writer outputWriter) { this.results = results; this.outputWriter = outputWriter; } @Override public void beforeSuite(TestDescriptor suite) { } @Override public void afterSuite(TestDescriptor suite, TestResult result) { if (result.getResultType() == TestResult.ResultType.FAILURE && !result.getExceptions().isEmpty()) { //there are some exceptions attached to the suite. Let's make sure they are reported to the user. //this may happen for example when suite initialisation fails and no tests are executed TestMethodResult methodResult = new TestMethodResult(internalIdCounter++, "execution failure"); for (Throwable throwable : result.getExceptions()) { methodResult.addFailure(failureMessage(throwable), stackTrace(throwable), exceptionClassName(throwable)); } methodResult.completed(result); TestClassResult classResult = new TestClassResult(internalIdCounter++, suite.getName(), result.getStartTime()); classResult.add(methodResult); results.put(suite.getName(), classResult); } } @Override public void beforeTest(TestDescriptor testDescriptor) { TestMethodResult methodResult = new TestMethodResult(internalIdCounter++, testDescriptor.getName()); currentTestMethods.put(testDescriptor, methodResult); } @Override public void afterTest(TestDescriptor testDescriptor, TestResult result) { String className = testDescriptor.getClassName(); TestMethodResult methodResult = currentTestMethods.remove(testDescriptor).completed(result); for (Throwable throwable : result.getExceptions()) { methodResult.addFailure(failureMessage(throwable), stackTrace(throwable), exceptionClassName(throwable)); } TestClassResult classResult = results.get(className); if (classResult == null) { classResult = new TestClassResult(internalIdCounter++, className, result.getStartTime()); results.put(className, classResult); } else if (classResult.getStartTime() == 0) { //class results may be created earlier, where we don't yet have access to the start time classResult.setStartTime(result.getStartTime()); } classResult.add(methodResult); } private String failureMessage(Throwable throwable) { try { return throwable.toString(); } catch (Throwable t) { String exceptionClassName = exceptionClassName(throwable); return String.format("Could not determine failure message for exception of type %s: %s", exceptionClassName, t); } } private String exceptionClassName(Throwable throwable) { return throwable instanceof PlaceholderException ? ((PlaceholderException) throwable).getExceptionClassName() : throwable.getClass().getName(); } private String stackTrace(Throwable throwable) { try { StringWriter stringWriter = new StringWriter(); PrintWriter writer = new PrintWriter(stringWriter); throwable.printStackTrace(writer); writer.close(); return stringWriter.toString(); } catch (Throwable t) { StringWriter stringWriter = new StringWriter(); PrintWriter writer = new PrintWriter(stringWriter); t.printStackTrace(writer); writer.close(); return stringWriter.toString(); } } @Override public void onOutput(TestDescriptor testDescriptor, TestOutputEvent outputEvent) { String className = testDescriptor.getClassName(); if (className == null) { //this means that we receive an output before even starting any class (or too late). //we don't have a place for such output in any of the reports so skipping. //Unfortunately, this happens pretty often with current level of TestNG support //because output events emitted by constructor, beforeTest, beforeClass // are sent before test start event is started and there is no parent class event emitted by TestNG. //In short, the TestNG support could be better. See also TestNGOutputEventsIntegrationTest return; } TestClassResult classResult = results.get(className); if (classResult == null) { //it's possible that we receive an output for a suite here //in this case we will create the test result for a suite that normally would not be created //feels like this scenario should modelled more explicitly classResult = new TestClassResult(internalIdCounter++, className, 0); results.put(className, classResult); } TestMethodResult methodResult = currentTestMethods.get(testDescriptor); if (methodResult == null) { outputWriter.onOutput(classResult.getId(), outputEvent); } else { outputWriter.onOutput(classResult.getId(), methodResult.getId(), outputEvent); } } }