package com.gfk.senbot.framework.cucumber; import gherkin.formatter.Formatter; import gherkin.formatter.JSONFormatter; import gherkin.formatter.Reporter; import gherkin.formatter.model.Background; import gherkin.formatter.model.Examples; import gherkin.formatter.model.Feature; import gherkin.formatter.model.Match; import gherkin.formatter.model.Result; import gherkin.formatter.model.Scenario; import gherkin.formatter.model.ScenarioOutline; import gherkin.formatter.model.Step; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.EachTestNotifier; import org.junit.runner.Description; import org.junit.runner.RunWith; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; import org.junit.runner.notification.StoppedByUserException; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.Parameterized; import org.junit.runners.ParentRunner; import org.junit.runners.model.InitializationError; import org.junit.runners.model.RunnerScheduler; import org.junit.runners.model.Statement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gfk.senbot.framework.context.CucumberManager; import com.gfk.senbot.framework.context.SeleniumManager; import com.gfk.senbot.framework.context.SenBotContext; import com.gfk.senbot.framework.context.TestEnvironment; import cucumber.api.junit.Cucumber; import cucumber.runtime.Runtime; import cucumber.runtime.RuntimeOptions; import cucumber.runtime.formatter.FormatterFactory; import cucumber.runtime.io.MultiLoader; import cucumber.runtime.io.ResourceLoader; import cucumber.runtime.junit.Assertions; import cucumber.runtime.junit.FeatureRunner; import cucumber.runtime.junit.JUnitReporter; import cucumber.runtime.junit.RuntimeOptionsFactory; import cucumber.runtime.model.CucumberFeature; import cucumber.runtime.snippets.SummaryPrinter; /** * This is the aggregation of the {@link Parameterized} runner, that allows to * run tests in parallel with the {@link Cucumber} runner. The aggregation had * to be done, as Java allows only one {@link RunWith} annotiation. This class * is mostly a copy from {@link Cucumber}. * * @author joostschouten * */ public class ParameterizedCucumber extends Parameterized { private static Logger log = LoggerFactory.getLogger(ParameterizedCucumber.class); private final JUnitReporter jUnitReporter; private final List<Runner> children = new ArrayList<Runner>(); private final Runtime runtime; /** * Constructor looping though the {@link Parameterized} {@link Runner}'s and * adding the {@link CucumberFeature}'s found as children to each one * ensuring the {@link TestEnvironment} is available for each {@link Runner} * . * * @param klass * @throws Throwable */ public ParameterizedCucumber(Class<?> klass) throws Throwable { super(klass); log.debug("Initializing the ParameterizedCucumber runner"); final ClassLoader classLoader = klass.getClassLoader(); Assertions.assertNoCucumberAnnotatedMethods(klass); // Class<? extends Annotation>[] annotationClasses = new Class[]{CucumberOptions.class, Options.class}; // RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(klass, annotationClasses); RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(klass); final RuntimeOptions runtimeOptions = runtimeOptionsFactory.create(); ResourceLoader resourceLoader = new MultiLoader(classLoader); // ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader); // runtime = new Runtime(resourceLoader, classFinder, classLoader, runtimeOptions); runtime = new Runtime(resourceLoader, classLoader, runtimeOptions); final ThreadAwareFormatter threadAwareWrappedFormatter = new ThreadAwareFormatter(runtimeOptions, classLoader); Reporter threadAwareReporter = new ThreadAwareReporter(threadAwareWrappedFormatter, classLoader, runtimeOptions); runtimeOptions.formatters.clear(); runtimeOptions.formatters.add(threadAwareWrappedFormatter); jUnitReporter = new JUnitReporter(threadAwareReporter, threadAwareWrappedFormatter, runtimeOptions.strict); //overwrite the reporter so we can alter the path to write to List<CucumberFeature> cucumberFeatures = runtimeOptions.cucumberFeatures(resourceLoader); List<Runner> parameterizedChildren = super.getChildren(); final SeleniumManager seleniumManager = SenBotContext.getSenBotContext().getSeleniumManager(); final CucumberManager cucumberManager = SenBotContext.getSenBotContext().getCucumberManager(); for (int i = 0; i < parameterizedChildren.size(); i++) { BlockJUnit4ClassRunner paramRunner = (BlockJUnit4ClassRunner) parameterizedChildren.get(i); final TestEnvironment environment = seleniumManager.getSeleniumTestEnvironments().get(i); log.debug("Load runners for test envrironment: " + environment.toString()); for (final CucumberFeature cucumberFeature : cucumberFeatures) { Feature originalFeature = cucumberFeature.getGherkinFeature(); log.debug("Load runner for test cucumberFeature: " + originalFeature.getDescription() + " on evironment " + environment.toString()); ThreadPoolFeatureRunnerScheduler environmentFeatureRunner = new ThreadPoolFeatureRunnerScheduler(environment, cucumberFeature, runtime, jUnitReporter, cucumberManager.getFeatureFileTimeout()); setScheduler(environmentFeatureRunner); children.add(environmentFeatureRunner); } } } /** * @return A list of all children of the runner */ @Override public List<Runner> getChildren() { return children; } /** * Runs a child * * @param child * The child you want to run * @param notifier * The notifier */ @Override protected void runChild(Runner child, RunNotifier notifier) { child.run(notifier); } /** * Runs all * * @param notifier * The notifier */ @Override public void run(RunNotifier notifier) { log.debug("Run called on ParameterizedCucumber"); super.run(notifier); jUnitReporter.done(); new SummaryPrinter(System.out).print(runtime); jUnitReporter.close(); log.debug("Run finished on ParameterizedCucumber"); } /** * A thread aware {@link Reporter} wrapper to be able to deal with the parallel multithreaded environment * * @author joostschouten * */ private final class ThreadAwareReporter implements Reporter { private final ThreadAwareFormatter threadAwareWrappedFormatter; private final ClassLoader classLoader; private final RuntimeOptions runtimeOptions; ThreadLocal<Reporter> threadLocal; private ThreadAwareReporter(ThreadAwareFormatter threadAwareWrappedFormatter, final ClassLoader classLoader, final RuntimeOptions runtimeOptions) { this.threadAwareWrappedFormatter = threadAwareWrappedFormatter; this.classLoader = classLoader; this.runtimeOptions = runtimeOptions; threadLocal = new ThreadLocal<Reporter>() { @Override protected Reporter initialValue() { return runtimeOptions.reporter(classLoader); } }; } private Reporter getWrapped() { return threadLocal.get(); } @Override public void write(String arg0) { getWrapped().write(arg0); } @Override public void result(Result arg0) { Formatter wrapped = threadAwareWrappedFormatter.getWrapped(); if(wrapped instanceof JSONFormatter) { ((JSONFormatter) wrapped).result(arg0); } else { getWrapped().result(arg0); } } @Override public void match(Match arg0) { getWrapped().match(arg0); } @Override public void embedding(String arg0, byte[] arg1) { getWrapped().embedding(arg0, arg1); } @Override public void before(Match arg0, Result arg1) { getWrapped().before(arg0, arg1); } @Override public void after(Match arg0, Result arg1) { getWrapped().after(arg0, arg1); } } private final class ThreadAwareFormatter implements Formatter, Reporter { private final FormatterFactory FORMATTER_FACTORY = new FormatterFactory(); private final RuntimeOptions runtimeOptions; private final ClassLoader classLoader; private ThreadLocal<Formatter> wrapped; private Collection<Formatter> known = new HashSet<Formatter>(); private ThreadAwareFormatter(final RuntimeOptions runtimeOptions, final ClassLoader classLoader) { this.runtimeOptions = runtimeOptions; this.classLoader = classLoader; wrapped = new ThreadLocal<Formatter>() { @Override protected Formatter initialValue() { Formatter created = FORMATTER_FACTORY.create("json:target/test-results/result_" + Thread.currentThread().getId() + ".json"); known.add(created); return created; // Formatter formatter = runtimeOptions.formatter(classLoader); // return formatter; } }; } public Formatter getWrapped() { return wrapped.get(); } @Override public void background(Background arg0) { getWrapped().background(arg0); } @Override public void close() { for(Formatter formatter : known) { formatter.close(); } } @Override public void done() { for(Formatter formatter : known) { formatter.done(); } } @Override public void eof() { getWrapped().eof(); } @Override public void examples(Examples arg0) { getWrapped().examples(arg0); } @Override public void feature(Feature arg0) { getWrapped().feature(arg0); } @Override public void scenario(Scenario arg0) { getWrapped().scenario(arg0); } @Override public void scenarioOutline(ScenarioOutline arg0) { getWrapped().scenarioOutline(arg0); } @Override public void step(Step arg0) { getWrapped().step(arg0); } @Override public void syntaxError(String arg0, String arg1, List<String> arg2, String arg3, Integer arg4) { getWrapped().syntaxError(arg0, arg1, arg2, arg3, arg4); } @Override public void uri(String arg0) { getWrapped().uri(arg0); } @Override public void after(Match arg0, Result arg1) { Reporter wrappedReporter = getWrappedReporter(); if(wrappedReporter != null) { wrappedReporter.after(arg0, arg1); } } private Reporter getWrappedReporter() { if(Reporter.class.isAssignableFrom(getWrapped().getClass())) { return (Reporter) getWrapped(); } return null; } @Override public void before(Match arg0, Result arg1) { Reporter wrappedReporter = getWrappedReporter(); if(wrappedReporter != null) { wrappedReporter.before(arg0, arg1); } } @Override public void embedding(String arg0, byte[] arg1) { Reporter wrappedReporter = getWrappedReporter(); if(wrappedReporter != null) { wrappedReporter.embedding(arg0, arg1); } } @Override public void match(Match arg0) { Reporter wrappedReporter = getWrappedReporter(); if(wrappedReporter != null) { wrappedReporter.match(arg0); } } @Override public void result(Result arg0) { Reporter wrappedReporter = getWrappedReporter(); if(wrappedReporter != null) { wrappedReporter.result(arg0); } } @Override public void write(String arg0) { Reporter wrappedReporter = getWrappedReporter(); if(wrappedReporter != null) { wrappedReporter.write(arg0); } } } /** * Allow for Parallelisation so that each fature can be run in a different * thread * * @author joostschouten * */ private static class ThreadPoolFeatureRunnerScheduler extends FeatureRunner implements RunnerScheduler { private ExecutorService executor; private TestEnvironment environment; private CucumberFeature cucumberFeature; private JUnitReporter jUnitReporter; private int featureFileTimout; public ThreadPoolFeatureRunnerScheduler(TestEnvironment environment, CucumberFeature cucumberFeature, Runtime runtime, JUnitReporter jUnitReporter, int featureFileTimout) throws InitializationError { super(cucumberFeature, runtime, jUnitReporter); this.environment = environment; this.cucumberFeature = cucumberFeature; this.jUnitReporter = jUnitReporter; this.featureFileTimout = featureFileTimout; Integer defaultNumThreads = SenBotContext.getSenBotContext().getCucumberManager().getParallelFeatureThreads(); String threads = System.getProperty("junit.parallel.threads", defaultNumThreads.toString()); int numThreads = Integer.parseInt(threads); executor = Executors.newFixedThreadPool(numThreads); } @Override public void run(RunNotifier notifier) { log.debug("Scenario run call started on: " + getDescription().toString()); try { SenBotContext.getSenBotContext().getSeleniumManager().associateTestEnvironment(environment); super.run(notifier); SenBotContext.getSenBotContext().getSeleniumManager().deAssociateTestEnvironment(); environment.cleanupDriver(); } catch (Exception e) { throw new RuntimeException(e); } log.debug("Scenario run call finished on: " + getDescription().toString()); } /** * Add the Selenium TestEnvironment to the name of the Feature */ @Override public String getName() { return super.getName() + " <Selenium env - " + environment.toPrettyString() + ">"; } @Override protected Description describeChild(ParentRunner child) { return child.getDescription(); } @Override public void finished() { executor.shutdown(); try { executor.awaitTermination(featureFileTimout, TimeUnit.SECONDS); } catch (Exception exc) { log.error("The maximum timeout of " + featureFileTimout + " seconds has been exceded for feature file: " + getName() + ". It will be terminated."); } } @Override public void schedule(Runnable childStatement) { executor.submit(childStatement); } } }