package net.thucydides.core.reports;
import com.google.common.util.concurrent.*;
import net.thucydides.core.guice.Injectors;
import net.thucydides.core.model.TestOutcome;
import net.thucydides.core.util.EnvironmentVariables;
import net.thucydides.core.webdriver.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Generates different Thucydides reports in a given output directory.
*/
@SuppressWarnings("restriction")
public class ReportService {
/**
* Where will the reports go?
*/
private File outputDirectory;
/**
* These classes generate the reports from the test results.
*/
private List<AcceptanceTestReporter> subscribedReporters;
private final static Logger LOGGER = LoggerFactory.getLogger(ReportService.class);
@Inject
public ReportService(final Configuration configuration) {
this(configuration.getOutputDirectory(), getDefaultReporters());
}
/**
* Reports are generated using the test results in a given directory.
* The actual reports are generated using a set of reporter objects. The report service passes test outcomes
* to the reporter objects, which generate different types of reports.
*
* @param outputDirectory Where the test data is stored, and where the generated reports will go.
* @param subscribedReporters A set of reporters that generate the actual reports.
*/
public ReportService(final File outputDirectory, final Collection<AcceptanceTestReporter> subscribedReporters) {
this.outputDirectory = outputDirectory;
getSubscribedReporters().addAll(subscribedReporters);
}
public void setOutputDirectory(File outputDirectory) {
this.outputDirectory = outputDirectory;
}
public List<AcceptanceTestReporter> getSubscribedReporters() {
if (subscribedReporters == null) {
subscribedReporters = new ArrayList<>();
}
return subscribedReporters;
}
public void subscribe(final AcceptanceTestReporter reporter) {
getSubscribedReporters().add(reporter);
}
public void useQualifier(final String qualifier) {
for (AcceptanceTestReporter reporter : getSubscribedReporters()) {
reporter.setQualifier(qualifier);
}
}
/**
* A test runner can generate reports via Reporter instances that subscribe
* to the test runner. The test runner tells the reporter what directory to
* place the reports in. Then, at the end of the test, the test runner
* notifies these reporters of the test outcomes. The reporter's job is to
* process each test run outcome and do whatever is appropriate.
*
* @param testOutcomeResults A list of test outcomes to use in report generation.
* These may be stored in memory (e.g. by a Listener instance) or read from the XML
* test results.
*/
public void generateReportsFor(final List<TestOutcome> testOutcomeResults) {
final TestOutcomes allTestOutcomes = TestOutcomes.of(testOutcomeResults);
for (final AcceptanceTestReporter reporter : getSubscribedReporters()) {
generateReportsFor(reporter, allTestOutcomes);
}
}
private void generateReportsFor(final AcceptanceTestReporter reporter, final TestOutcomes testOutcomes) {
LOGGER.info("Generating reports using: " + reporter);
long t0 = System.currentTimeMillis();
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(8));
List<? extends TestOutcome> outcomes = testOutcomes.getOutcomes();
final AtomicInteger remainingReportCount = new AtomicInteger(outcomes.size());
for (final TestOutcome outcome : outcomes) {
final ListenableFuture<TestOutcome> future = executorService.submit(new Callable<TestOutcome>() {
@Override
public TestOutcome call() throws Exception {
return outcome;
}
});
future.addListener(new Runnable() {
@Override
public void run() {
generateQueuedReport(future, testOutcomes, reporter);
}
}, MoreExecutors.sameThreadExecutor());
Futures.addCallback(future, new FutureCallback<TestOutcome>() {
@Override
public void onSuccess(TestOutcome result) {
remainingReportCount.decrementAndGet();
LOGGER.info("Report generated for " + result.getCompleteName());
}
@Override
public void onFailure(Throwable t) {
LOGGER.info("Report generated failed " + t.getMessage());
}
});
}
waitForReportGenerationToFinish(remainingReportCount);
LOGGER.info("Reports generated in: " + (System.currentTimeMillis() - t0));
}
private void generateQueuedReport(ListenableFuture<TestOutcome> future, TestOutcomes testOutcomes, AcceptanceTestReporter reporter) {
try {
final TestOutcome outcome = future.get();
LOGGER.info("Processing test outcome " + outcome.getCompleteName());
generateReportFor(outcome, testOutcomes, reporter);
} catch (InterruptedException e) {
throw new RuntimeException("Report generation failure", e);
} catch (ExecutionException e) {
throw new RuntimeException("Report generation failure", e);
}
}
private void waitForReportGenerationToFinish(AtomicInteger reportCount) {
while (reportCount.get() > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
}
/**
* The default reporters applicable for standard test runs.
*
* @return a list of default reporters.
*/
public static List<AcceptanceTestReporter> getDefaultReporters() {
List<AcceptanceTestReporter> reporters = new ArrayList<AcceptanceTestReporter>();
FormatConfiguration formatConfiguration
= new FormatConfiguration(Injectors.getInjector().getProvider(EnvironmentVariables.class).get());
ServiceLoader<AcceptanceTestReporter> reporterServiceLoader = ServiceLoader.load(AcceptanceTestReporter.class);
Iterator<AcceptanceTestReporter> reporterImplementations = reporterServiceLoader.iterator();
// Service.providers(AcceptanceTestReporter.class);
LOGGER.info("Reporting formats: " + formatConfiguration.getFormats());
while (reporterImplementations.hasNext()) {
AcceptanceTestReporter reporter = reporterImplementations.next();
LOGGER.info("Found reporter: " + reporter + "(format = " + reporter.getFormat() + ")");
if (!reporter.getFormat().isPresent() || formatConfiguration.getFormats().contains(reporter.getFormat().get())) {
LOGGER.info("Registering reporter: " + reporter);
reporters.add(reporter);
}
}
return reporters;
}
private void generateReportFor(final TestOutcome testOutcome,
final TestOutcomes allTestOutcomes,
final AcceptanceTestReporter reporter) {
try {
LOGGER.info(reporter + ": Generating report for test outcome: " + testOutcome.getCompleteName());
reporter.setOutputDirectory(outputDirectory);
reporter.generateReportFor(testOutcome, allTestOutcomes);
} catch (IOException e) {
throw new ReportGenerationFailedError(
"Failed to generate reports using " + reporter, e);
}
}
}