package net.thucydides.junit.runners;
import net.thucydides.core.batches.BatchManager;
import net.thucydides.core.guice.Injectors;
import net.thucydides.core.model.DataTable;
import net.thucydides.core.model.TestOutcome;
import net.thucydides.core.reports.AcceptanceTestReporter;
import net.thucydides.core.reports.ReportService;
import net.thucydides.core.webdriver.Configuration;
import net.thucydides.core.webdriver.WebDriverFactory;
import net.thucydides.junit.ThucydidesJUnitSystemProperties;
import net.thucydides.junit.annotations.Concurrent;
import org.apache.commons.lang3.StringUtils;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.Suite;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Run a Thucydides test suite using a set of data.
* Similar to the JUnit parameterized tests, but better ;-).
*
*/
public class ThucydidesParameterizedRunner extends Suite {
private static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
private final List<Runner> runners = new ArrayList<Runner>();
private final Configuration configuration;
private ReportService reportService;
private final ParameterizedTestsOutcomeAggregator parameterizedTestsOutcomeAggregator = ParameterizedTestsOutcomeAggregator.from(this);
/**
* Test runner used for testing purposes.
* @param klass The test class to run
* @param configuration current system configuration (usually mocked)
* @param webDriverFactory a webdriver factory (can be mocked)
* @param batchManager a batch manager to process batched testing
* @throws Throwable - cause anything can happen!
*/
public ThucydidesParameterizedRunner(final Class<?> klass,
Configuration configuration,
final WebDriverFactory webDriverFactory,
final BatchManager batchManager
) throws Throwable {
super(klass, Collections.<Runner>emptyList());
this.configuration = configuration;
if (runTestsInParallelFor(klass)) {
scheduleParallelTestRunsFor(klass);
}
DataDrivenAnnotations testClassAnnotations = getTestAnnotations();
if (testClassAnnotations.hasTestDataDefined()) {
buildTestRunnersForEachDataSetUsing(webDriverFactory, batchManager);
} else if (testClassAnnotations.hasTestDataSourceDefined()) {
buildTestRunnersFromADataSourceUsing(webDriverFactory, batchManager);
}
}
private void scheduleParallelTestRunsFor(final Class<?> klass) {
setScheduler(new ParameterizedRunnerScheduler(klass, getThreadCountFor(klass)));
}
protected boolean runTestsInParallelFor(final Class<?> klass) {
return (klass.getAnnotation(Concurrent.class) != null);
}
protected int getThreadCountFor(final Class<?> klass) {
Concurrent concurrent = klass.getAnnotation(Concurrent.class);
String threadValue = getThreadParameter(concurrent);
int threads = (AVAILABLE_PROCESSORS * 2);
if (StringUtils.isNotEmpty(threadValue)) {
if (StringUtils.isNumeric(threadValue)) {
threads = Integer.valueOf(threadValue);
} else if (threadValue.endsWith("x")) {
threads = getRelativeThreadCount(threadValue);
}
}
return threads;
}
private String getThreadParameter(Concurrent concurrent) {
String systemPropertyThreadValue =
configuration.getEnvironmentVariables().getProperty(ThucydidesJUnitSystemProperties.CONCURRENT_THREADS.getName());
String annotatedThreadValue = concurrent.threads();
return (StringUtils.isNotEmpty(systemPropertyThreadValue) ? systemPropertyThreadValue : annotatedThreadValue);
}
private int getRelativeThreadCount(final String threadValue) {
try {
String threadCount = threadValue.substring(0, threadValue.length() - 1);
return Integer.valueOf(threadCount) * AVAILABLE_PROCESSORS;
} catch (NumberFormatException cause) {
throw new IllegalArgumentException("Illegal thread value: " + threadValue, cause);
}
}
private void buildTestRunnersForEachDataSetUsing(final WebDriverFactory webDriverFactory,
final BatchManager batchManager) throws Throwable {
DataTable parametersTable = getTestAnnotations().getParametersTableFromTestDataAnnotation();
for (int i = 0; i < parametersTable.getRows().size(); i++) {
Class<?> testClass = getTestClass().getJavaClass();
ThucydidesRunner runner = new TestClassRunnerForParameters(testClass,
configuration,
webDriverFactory,
batchManager,
parametersTable,
i);
runner.useQualifier(from(parametersTable.getRows().get(i).getValues()));
runners.add(runner);
}
}
private void buildTestRunnersFromADataSourceUsing(final WebDriverFactory webDriverFactory,
final BatchManager batchManager) throws Throwable {
List<?> testCases = getTestAnnotations().getDataAsInstancesOf(getTestClass().getJavaClass());
DataTable parametersTable = getTestAnnotations().getParametersTableFromTestDataSource();
for (int i = 0; i < testCases.size(); i++) {
Object testCase = testCases.get(i);
ThucydidesRunner runner = new TestClassRunnerForInstanciatedTestCase(testCase,
configuration,
webDriverFactory,
batchManager,
parametersTable,
i);
runner.useQualifier(getQualifierFor(testCase));
runners.add(runner);
}
}
private String getQualifierFor(final Object testCase) {
return QualifierFinder.forTestCase(testCase).getQualifier();
}
private DataDrivenAnnotations getTestAnnotations() {
return DataDrivenAnnotations.forClass(getTestClass());
}
private String from(final Collection testData) {
StringBuffer testDataQualifier = new StringBuffer();
boolean firstEntry = true;
for (Object testDataValue : testData) {
if (!firstEntry) {
testDataQualifier.append("/");
}
testDataQualifier.append(testDataValue);
firstEntry = false;
}
return testDataQualifier.toString();
}
/**
* Only called reflectively. Do not use programmatically.
* @param klass The test class to run
* @throws Throwable Cause shit happens
*/
public ThucydidesParameterizedRunner(final Class<?> klass) throws Throwable {
this(klass, Injectors.getInjector().getInstance(Configuration.class), new WebDriverFactory(),
Injectors.getInjector().getInstance(BatchManager.class));
}
@Override
protected List<Runner> getChildren() {
return runners;
}
@Override
public void run(final RunNotifier notifier) {
try {
super.run(notifier);
} finally {
generateReports();
}
}
public void generateReports() {
generateReportsFor(parameterizedTestsOutcomeAggregator.aggregateTestOutcomesByTestMethods());
}
private void generateReportsFor(List<TestOutcome> testOutcomes) {
getReportService().generateReportsFor(testOutcomes);
}
private ReportService getReportService() {
if (reportService == null) {
reportService = new ReportService(getOutputDirectory(), getDefaultReporters());
}
return reportService;
}
private Collection<AcceptanceTestReporter> getDefaultReporters() {
return ReportService.getDefaultReporters();
}
private File getOutputDirectory() {
return this.configuration.getOutputDirectory();
}
public void subscribeReporter(final AcceptanceTestReporter reporter) {
getReportService().subscribe(reporter);
}
public List<Runner> getRunners() {
return runners;
}
}