diff --git a/core/pom.xml b/core/pom.xml index e7fa75639d..9d21f0e7bb 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 @@ -22,6 +23,10 @@ info.cukes cucumber-html + + com.beust + jcommander + com.thoughtworks.xstream diff --git a/core/src/main/java/cucumber/DateFormat.java b/core/src/main/java/cucumber/DateFormat.java index cba6af376b..ae43c95e53 100644 --- a/core/src/main/java/cucumber/DateFormat.java +++ b/core/src/main/java/cucumber/DateFormat.java @@ -29,7 +29,7 @@ *
  * @Given("^the date is (\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})$")
  * public void the_date_is(@DateFormat("yyyy-MM-dd'T'HH:mm:ss") Calendar cal) {
- *     this.date = date;
+ *     this.cal = cal;
  * }
  * 
*

diff --git a/core/src/main/java/cucumber/cli/DefaultRuntimeFactory.java b/core/src/main/java/cucumber/cli/DefaultRuntimeFactory.java deleted file mode 100644 index a6572ca5ae..0000000000 --- a/core/src/main/java/cucumber/cli/DefaultRuntimeFactory.java +++ /dev/null @@ -1,13 +0,0 @@ -package cucumber.cli; - -import cucumber.io.ResourceLoader; -import cucumber.runtime.Runtime; - -import java.util.List; - -public class DefaultRuntimeFactory implements RuntimeFactory { - @Override - public cucumber.runtime.Runtime createRuntime(ResourceLoader resourceLoader, List gluePaths, ClassLoader classLoader, boolean dryRun) { - return new Runtime(resourceLoader, gluePaths, classLoader, dryRun); - } -} diff --git a/core/src/main/java/cucumber/cli/Main.java b/core/src/main/java/cucumber/cli/Main.java index 8a2ae329bd..bdcd22bca0 100644 --- a/core/src/main/java/cucumber/cli/Main.java +++ b/core/src/main/java/cucumber/cli/Main.java @@ -1,108 +1,25 @@ package cucumber.cli; -import cucumber.formatter.FormatterFactory; -import cucumber.formatter.MultiFormatter; import cucumber.io.FileResourceLoader; import cucumber.runtime.Runtime; -import cucumber.runtime.snippets.SummaryPrinter; -import gherkin.formatter.Formatter; -import gherkin.formatter.Reporter; +import cucumber.runtime.RuntimeOptions; -import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.util.ResourceBundle; -import java.util.Stack; - -import static java.util.Arrays.asList; public class Main { - private static final String USAGE = "TODO - Write the help"; static final String VERSION = ResourceBundle.getBundle("cucumber.version").getString("cucumber-jvm.version"); public static void main(String[] argv) throws Throwable { - run(argv, Thread.currentThread().getContextClassLoader(), new DefaultRuntimeFactory()); + run(argv, Thread.currentThread().getContextClassLoader()); } - public static void run(String[] argv, ClassLoader classLoader, RuntimeFactory runtimeFactory) throws IOException { - List featurePaths = new ArrayList(); - List gluePaths = new ArrayList(); - List filters = new ArrayList(); - Stack format = new Stack(); - List args = new ArrayList(asList(argv)); - String dotCucumber = null; - boolean isDryRun = false; - - FormatterFactory formatterFactory = new FormatterFactory(classLoader); - MultiFormatter multiFormatter = new MultiFormatter(classLoader); - - while (!args.isEmpty()) { - String arg = args.remove(0); - - if (arg.equals("--help") || arg.equals("-h")) { - System.out.println(USAGE); - System.exit(0); - } else if (arg.equals("--version") || arg.equals("-v")) { - System.out.println(VERSION); - System.exit(0); - } else if (arg.equals("--glue") || arg.equals("-g")) { - String gluePath = args.remove(0); - gluePaths.add(gluePath); - } else if (arg.equals("--tags") || arg.equals("-t")) { - filters.add(args.remove(0)); - } else if (arg.equals("--format") || arg.equals("-f")) { - format.push(args.remove(0)); - } else if (arg.equals("--out") || arg.equals("-o")) { - File out = new File(args.remove(0)); - Formatter formatter = formatterFactory.createFormatter(format.pop(), out); - multiFormatter.add(formatter); - } else if (arg.equals("--dotcucumber")) { - dotCucumber = args.remove(0); - } else if (arg.equals("--dry-run") || arg.equals("-d")) { - isDryRun = true; - } else { - // TODO: Use PathWithLines and add line filter if any - featurePaths.add(arg); - } - } - - //Grab any formatters left on the stack and create a multiformatter for them to stdout - // yes this will be ugly, but maybe people are crazy - if (!format.isEmpty()) { - multiFormatter.add(formatterFactory.createFormatter(format.pop(), System.out)); - } else { - //Default formatter is progress unless otherwise specified or if they have piped all their other formatters - // to an output thing - multiFormatter.add(formatterFactory.createFormatter("progress", System.out)); - } - - if (gluePaths.isEmpty()) { - System.out.println("Missing option: --glue"); - System.exit(1); - } + public static void run(String[] argv, ClassLoader classLoader) throws IOException { + RuntimeOptions runtimeOptions = new RuntimeOptions(argv); - Runtime runtime = runtimeFactory.createRuntime(new FileResourceLoader(), gluePaths, classLoader, isDryRun); - - if (dotCucumber != null) { - writeDotCucumber(featurePaths, dotCucumber, runtime); - } - Formatter formatter = multiFormatter.formatterProxy(); - Reporter reporter = multiFormatter.reporterProxy(); - runtime.run(featurePaths, filters, formatter, reporter); - formatter.done(); - printSummary(runtime); - formatter.close(); + Runtime runtime = new Runtime(new FileResourceLoader(), classLoader, runtimeOptions); + runtime.writeStepdefsJson(); + runtime.run(); System.exit(runtime.exitStatus()); } - - private static void writeDotCucumber(List featurePaths, String dotCucumberPath, Runtime runtime) throws IOException { - File dotCucumber = new File(dotCucumberPath); - dotCucumber.mkdirs(); - runtime.writeStepdefsJson(featurePaths, dotCucumber); - } - - private static void printSummary(Runtime runtime) { - new SummaryPrinter(System.out).print(runtime); - } } diff --git a/core/src/main/java/cucumber/cli/RuntimeFactory.java b/core/src/main/java/cucumber/cli/RuntimeFactory.java deleted file mode 100644 index 985ceedb5a..0000000000 --- a/core/src/main/java/cucumber/cli/RuntimeFactory.java +++ /dev/null @@ -1,10 +0,0 @@ -package cucumber.cli; - -import cucumber.io.ResourceLoader; -import cucumber.runtime.Runtime; - -import java.util.List; - -public interface RuntimeFactory { - Runtime createRuntime(ResourceLoader fileResourceLoader, List gluePaths, ClassLoader classLoader, boolean dryRun); -} diff --git a/core/src/main/java/cucumber/formatter/CucumberPrettyFormatter.java b/core/src/main/java/cucumber/formatter/CucumberPrettyFormatter.java new file mode 100644 index 0000000000..3278d487f2 --- /dev/null +++ b/core/src/main/java/cucumber/formatter/CucumberPrettyFormatter.java @@ -0,0 +1,9 @@ +package cucumber.formatter; + +import gherkin.formatter.PrettyFormatter; + +public class CucumberPrettyFormatter extends PrettyFormatter { + public CucumberPrettyFormatter(Appendable out) { + super(out, true, true); + } +} diff --git a/core/src/main/java/cucumber/formatter/FormatterConverter.java b/core/src/main/java/cucumber/formatter/FormatterConverter.java new file mode 100644 index 0000000000..e6ff62ecd1 --- /dev/null +++ b/core/src/main/java/cucumber/formatter/FormatterConverter.java @@ -0,0 +1,126 @@ +package cucumber.formatter; + +import com.beust.jcommander.IStringConverter; +import cucumber.runtime.CucumberException; +import gherkin.formatter.Formatter; +import gherkin.formatter.JSONFormatter; +import gherkin.formatter.JSONPrettyFormatter; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.Arrays.asList; + +public class FormatterConverter implements IStringConverter { + private Class[] CTOR_ARGS = new Class[]{Appendable.class, File.class}; + + private static final Map> FORMATTER_CLASSES = new HashMap>() {{ + put("junit", JUnitFormatter.class); + put("html", HTMLFormatter.class); + put("pretty", CucumberPrettyFormatter.class); + put("progress", ProgressFormatter.class); + put("json", JSONFormatter.class); + put("json-pretty", JSONPrettyFormatter.class); + }}; + private static final Pattern FORMATTER_WITH_FILE_PATTERN = Pattern.compile("([^:]+):(.*)"); + private Appendable defaultOut = System.out; + + @Override + public Formatter convert(String formatterString) { + Matcher formatterWithFile = FORMATTER_WITH_FILE_PATTERN.matcher(formatterString); + String formatterName; + Object ctorArg; + if (formatterWithFile.matches()) { + formatterName = formatterWithFile.group(1); + ctorArg = new File(formatterWithFile.group(2)); + } else { + formatterName = formatterString; + ctorArg = defaultOutIfAvailable(); + } + Class formatterClass = formatterClass(formatterName); + try { + return instantiate(formatterClass, ctorArg); + } catch (IOException e) { + throw new CucumberException(e); + } + } + + private Formatter instantiate(Class formatterClass, Object ctorArg) throws IOException { + Constructor constructor; + + for (Class ctorArgClass : CTOR_ARGS) { + constructor = findConstructor(formatterClass, ctorArgClass); + if (constructor != null) { + ctorArg = convert(ctorArg, ctorArgClass); + if (ctorArg != null) { + try { + return constructor.newInstance(ctorArg); + } catch (InstantiationException e) { + throw new CucumberException(e); + } catch (IllegalAccessException e) { + throw new CucumberException(e); + } catch (InvocationTargetException e) { + throw new CucumberException(e.getTargetException()); + } + } + } + } + throw new CucumberException(String.format("%s must have a single-argument constructor that takes one of the following: %s", formatterClass, asList(CTOR_ARGS))); + } + + private Object convert(Object ctorArg, Class ctorArgClass) throws IOException { + if (ctorArgClass.isAssignableFrom(ctorArg.getClass())) { + return ctorArg; + } + if (ctorArgClass.equals(File.class) && ctorArg instanceof File) { + return ctorArg; + } + if (ctorArgClass.equals(Appendable.class) && ctorArg instanceof File) { + return new FileWriter((File) ctorArg); + } + return null; + } + + private Constructor findConstructor(Class formatterClass, Class ctorArgClass) { + try { + return formatterClass.getConstructor(ctorArgClass); + } catch (NoSuchMethodException e) { + return null; + } + } + + private Class formatterClass(String formatterName) { + Class formatterClass = FORMATTER_CLASSES.get(formatterName); + if (formatterClass == null) { + formatterClass = loadClass(formatterName); + } + return formatterClass; + } + + private Class loadClass(String className) { + try { + return (Class) Thread.currentThread().getContextClassLoader().loadClass(className); + } catch (ClassNotFoundException e) { + throw new CucumberException("Couldn't load formatter class: " + className, e); + } + } + + private Appendable defaultOutIfAvailable() { + try { + if (defaultOut != null) { + return defaultOut; + } else { + throw new CucumberException("Only one formatter can use STDOUT. If you use more than one formatter you must specify output path with FORMAT:PATH"); + } + } finally { + defaultOut = null; + } + } +} diff --git a/core/src/main/java/cucumber/formatter/FormatterFactory.java b/core/src/main/java/cucumber/formatter/FormatterFactory.java deleted file mode 100644 index 8727715fd4..0000000000 --- a/core/src/main/java/cucumber/formatter/FormatterFactory.java +++ /dev/null @@ -1,66 +0,0 @@ -package cucumber.formatter; - -import cucumber.runtime.CucumberException; -import gherkin.formatter.Formatter; -import gherkin.formatter.JSONFormatter; -import gherkin.formatter.JSONPrettyFormatter; -import gherkin.formatter.PrettyFormatter; - -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -public class FormatterFactory { - - private final ClassLoader classLoader; - - private static final Map BUILTIN_FORMATTERS = new HashMap() {{ - put("progress", ProgressFormatter.class.getName()); - put("html", HTMLFormatter.class.getName()); - put("json", JSONFormatter.class.getName()); - put("json-pretty", JSONPrettyFormatter.class.getName()); - put("pretty", PrettyFormatter.class.getName()); - put("junit", JUnitFormatter.class.getName()); - }}; - - public FormatterFactory(ClassLoader classLoader) { - this.classLoader = classLoader; - } - - public Formatter createFormatter(String formatterName, Object out) { - String className = BUILTIN_FORMATTERS.containsKey(formatterName) ? BUILTIN_FORMATTERS.get(formatterName) : formatterName; - return createFormatterFromClassName(className, out); - } - - private Formatter createFormatterFromClassName(String className, Object out) { - try { - Class ctorArgClass = Appendable.class; - if (out instanceof File) { - File file = (File) out; - out = file; - ctorArgClass = File.class; - } - - Class formatterClass = getFormatterClass(className); - // TODO: Remove these if statements. We should fix PrettyFormatter and ProgressFormatter to only take a single Appendable arg. - // Whether or not to use Monochrome is tricky. Maybe always enforce another 2nd argument for that - if (PrettyFormatter.class.isAssignableFrom(formatterClass)) { - return formatterClass.getConstructor(ctorArgClass, Boolean.TYPE, Boolean.TYPE).newInstance(out, false, true); - } else if (ProgressFormatter.class.isAssignableFrom(formatterClass)) { - return formatterClass.getConstructor(ctorArgClass, Boolean.TYPE).newInstance(out, false); - } else { - return formatterClass.getConstructor(ctorArgClass).newInstance(out); - } - } catch (Exception e) { - throw new CucumberException(String.format("Error creating instance of: %s outputting to %s", className, out), e); - } - } - - private Class getFormatterClass(String className) { - try { - return (Class) classLoader.loadClass(className); - } catch (ClassNotFoundException e) { - throw new CucumberException("Formatter class not found: " + className, e); - } - } -} diff --git a/core/src/main/java/cucumber/formatter/MultiFormatter.java b/core/src/main/java/cucumber/formatter/MultiFormatter.java deleted file mode 100644 index e5bd8b5804..0000000000 --- a/core/src/main/java/cucumber/formatter/MultiFormatter.java +++ /dev/null @@ -1,52 +0,0 @@ -package cucumber.formatter; - -import gherkin.formatter.Formatter; -import gherkin.formatter.Reporter; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.List; - -/** - * Utility for creating a formatter that delegates to multiple underlying formatters. - */ -public class MultiFormatter { - private final List formatters = new ArrayList(); - private final ClassLoader classLoader; - - public MultiFormatter(ClassLoader classLoader) { - this.classLoader = classLoader; - } - - public void add(Formatter formatter) { - formatters.add(formatter); - } - - public Formatter formatterProxy() { - return (Formatter) Proxy.newProxyInstance(classLoader, new Class[]{Formatter.class}, new InvocationHandler() { - @Override - public Object invoke(Object target, Method method, Object[] args) throws Throwable { - for (Formatter formatter : formatters) { - method.invoke(formatter, args); - } - return null; - } - }); - } - - public Reporter reporterProxy() { - return (Reporter) Proxy.newProxyInstance(classLoader, new Class[]{Reporter.class}, new InvocationHandler() { - @Override - public Object invoke(Object target, Method method, Object[] args) throws Throwable { - for (Formatter formatter : formatters) { - if (formatter instanceof Reporter) { - method.invoke(formatter, args); - } - } - return null; - } - }); - } -} diff --git a/core/src/main/java/cucumber/formatter/NullReporter.java b/core/src/main/java/cucumber/formatter/NullReporter.java deleted file mode 100644 index aaa7523146..0000000000 --- a/core/src/main/java/cucumber/formatter/NullReporter.java +++ /dev/null @@ -1,77 +0,0 @@ -package cucumber.formatter; - -import gherkin.formatter.Formatter; -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.io.InputStream; -import java.util.List; - -public class NullReporter implements Reporter, Formatter { - @Override - public void uri(String s) { - } - - @Override - public void feature(Feature feature) { - } - - @Override - public void background(Background background) { - } - - @Override - public void scenario(Scenario scenario) { - } - - @Override - public void scenarioOutline(ScenarioOutline scenarioOutline) { - } - - @Override - public void examples(Examples examples) { - } - - @Override - public void step(Step step) { - } - - @Override - public void eof() { - } - - @Override - public void syntaxError(String state, String event, List legalEvents, String uri, int line) { - } - - @Override - public void done() { - } - - @Override - public void close() { - } - - @Override - public void result(Result result) { - } - - @Override - public void match(Match match) { - } - - @Override - public void embedding(String mimeType, InputStream data) { - } - - @Override - public void write(String text) { - } -} diff --git a/core/src/main/java/cucumber/formatter/ProgressFormatter.java b/core/src/main/java/cucumber/formatter/ProgressFormatter.java index 4e61a1d3b7..4a2ac4d3e1 100644 --- a/core/src/main/java/cucumber/formatter/ProgressFormatter.java +++ b/core/src/main/java/cucumber/formatter/ProgressFormatter.java @@ -37,8 +37,8 @@ public class ProgressFormatter implements Formatter, Reporter { private final NiceAppendable out; private final boolean monochrome; - public ProgressFormatter(Appendable appendable, boolean monochrome) { - this.monochrome = monochrome; + public ProgressFormatter(Appendable appendable) { + this.monochrome = false; out = new NiceAppendable(appendable); } diff --git a/core/src/main/java/cucumber/runtime/Runtime.java b/core/src/main/java/cucumber/runtime/Runtime.java index c9c84ad19e..7c671cfad4 100644 --- a/core/src/main/java/cucumber/runtime/Runtime.java +++ b/core/src/main/java/cucumber/runtime/Runtime.java @@ -4,7 +4,7 @@ import cucumber.io.ResourceLoader; import cucumber.runtime.converters.LocalizedXStreams; import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.model.CucumberTagStatement; +import cucumber.runtime.snippets.SummaryPrinter; import gherkin.I18n; import gherkin.formatter.Formatter; import gherkin.formatter.Reporter; @@ -16,7 +16,6 @@ import gherkin.formatter.model.Step; import gherkin.formatter.model.Tag; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -24,8 +23,6 @@ import java.util.List; import java.util.Set; -import static cucumber.runtime.model.CucumberFeature.load; - /** * This is the main entry point for running Cucumber features. */ @@ -35,39 +32,38 @@ public class Runtime implements UnreportedStepExecutor { private static final byte ERRORS = 0x1; private final UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker(); + private final Glue glue; + private final RuntimeOptions runtimeOptions; private final List errors = new ArrayList(); private final Collection backends; - private final boolean isDryRun; private final ResourceLoader resourceLoader; //TODO: These are really state machine variables, and I'm not sure the runtime is the best place for this state machine //They really should be created each time a scenario is run, not in here private boolean skipNextStep = false; private ScenarioResultImpl scenarioResult = null; + private ClassLoader classLoader; - public Runtime(ResourceLoader resourceLoader, List gluePaths, ClassLoader classLoader) { - this(resourceLoader, gluePaths, classLoader, false); - } - - public Runtime(ResourceLoader resourceLoader, List gluePaths, ClassLoader classLoader, boolean isDryRun) { - this(resourceLoader, gluePaths, classLoader, loadBackends(resourceLoader, classLoader), isDryRun); + public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, RuntimeOptions runtimeOptions) { + this(resourceLoader, classLoader, loadBackends(resourceLoader, classLoader), runtimeOptions); } - public Runtime(ResourceLoader resourceLoader, List gluePaths, ClassLoader classLoader, Collection backends, boolean isDryRun) { + public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, Collection backends, RuntimeOptions runtimeOptions) { + this.resourceLoader = resourceLoader; + this.classLoader = classLoader; if (backends.isEmpty()) { throw new CucumberException("No backends were found. Please make sure you have a backend module on your CLASSPATH."); } this.backends = backends; - this.resourceLoader = resourceLoader; - this.isDryRun = isDryRun; glue = new RuntimeGlue(undefinedStepsTracker, new LocalizedXStreams(classLoader)); for (Backend backend : backends) { - backend.loadGlue(glue, gluePaths); + backend.loadGlue(glue, runtimeOptions.glue); backend.setUnreportedStepExecutor(this); } + this.runtimeOptions = runtimeOptions; } private static Collection loadBackends(ResourceLoader resourceLoader, ClassLoader classLoader) { @@ -80,33 +76,27 @@ public void addError(Throwable error) { /** * This is the main entry point. Used from CLI, but not from JUnit. - * - * @param featurePaths - * @param filters - * @param formatter - * @param reporter */ - public void run(List featurePaths, final List filters, Formatter formatter, Reporter reporter) { - for (CucumberFeature cucumberFeature : load(resourceLoader, featurePaths, filters)) { - run(cucumberFeature, formatter, reporter); + public void run() { + for (CucumberFeature cucumberFeature : runtimeOptions.cucumberFeatures(resourceLoader)) { + run(cucumberFeature); } + Formatter formatter = runtimeOptions.formatter(classLoader); + + formatter.done(); + printSummary(); + formatter.close(); } - /** - * Runs an individual feature, not all the features. Used from CLI, but not from JUnit. - * - * @param cucumberFeature - * @param formatter - * @param reporter - */ - public void run(CucumberFeature cucumberFeature, Formatter formatter, Reporter reporter) { - formatter.uri(cucumberFeature.getUri()); - formatter.feature(cucumberFeature.getFeature()); - for (CucumberTagStatement cucumberTagStatement : cucumberFeature.getFeatureElements()) { - //Run the scenario, it should handle before and after hooks - cucumberTagStatement.run(formatter, reporter, this); - } - formatter.eof(); + private void run(CucumberFeature cucumberFeature) { + Formatter formatter = runtimeOptions.formatter(classLoader); + Reporter reporter = runtimeOptions.reporter(classLoader); + cucumberFeature.run(formatter, reporter, this); + } + + private void printSummary() { + // TODO: inject a SummaryPrinter in the ctor + new SummaryPrinter(System.out).print(this); } public void buildBackendWorlds(Reporter reporter) { @@ -125,10 +115,6 @@ public void disposeBackendWorlds() { } } - public boolean isDryRun() { - return isDryRun; - } - public List getErrors() { return errors; } @@ -222,7 +208,7 @@ public void runStep(String uri, Step step, Reporter reporter, I18n i18n) { return; } - if (isDryRun()) { + if (runtimeOptions.dryRun) { skipNextStep = true; } @@ -249,7 +235,7 @@ public void runStep(String uri, Step step, Reporter reporter, I18n i18n) { } } - public void writeStepdefsJson(List featurePaths, File dotCucumber) throws IOException { - glue.writeStepdefsJson(featurePaths, dotCucumber); + public void writeStepdefsJson() throws IOException { + glue.writeStepdefsJson(runtimeOptions.featurePaths, runtimeOptions.dotCucumber); } } diff --git a/core/src/main/java/cucumber/runtime/RuntimeGlue.java b/core/src/main/java/cucumber/runtime/RuntimeGlue.java index 39d2eb2a4b..0b4c03cba9 100644 --- a/core/src/main/java/cucumber/runtime/RuntimeGlue.java +++ b/core/src/main/java/cucumber/runtime/RuntimeGlue.java @@ -100,13 +100,15 @@ private List stepDefinitionMatches(String uri, Step step) { @Override public void writeStepdefsJson(List featurePaths, File dotCucumber) throws IOException { - List features = load(new FileResourceLoader(), featurePaths, NO_FILTERS); - List metaStepdefs = new StepdefGenerator().generate(stepDefinitionsByPattern.values(), features); - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - String json = gson.toJson(metaStepdefs); - - FileWriter stepdefsJson = new FileWriter(new File(dotCucumber, "stepdefs.json")); - stepdefsJson.append(json); - stepdefsJson.close(); + if (dotCucumber != null) { + List features = load(new FileResourceLoader(), featurePaths, NO_FILTERS); + List metaStepdefs = new StepdefGenerator().generate(stepDefinitionsByPattern.values(), features); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String json = gson.toJson(metaStepdefs); + + FileWriter stepdefsJson = new FileWriter(new File(dotCucumber, "stepdefs.json")); + stepdefsJson.append(json); + stepdefsJson.close(); + } } } diff --git a/core/src/main/java/cucumber/runtime/RuntimeOptions.java b/core/src/main/java/cucumber/runtime/RuntimeOptions.java new file mode 100644 index 0000000000..91e7ff1a41 --- /dev/null +++ b/core/src/main/java/cucumber/runtime/RuntimeOptions.java @@ -0,0 +1,102 @@ +package cucumber.runtime; + +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.IStringConverterFactory; +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import cucumber.formatter.FormatterConverter; +import cucumber.formatter.ProgressFormatter; +import cucumber.io.ResourceLoader; +import cucumber.runtime.model.CucumberFeature; +import gherkin.formatter.Formatter; +import gherkin.formatter.Reporter; + +import java.io.File; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; + +import static cucumber.runtime.model.CucumberFeature.load; + +public class RuntimeOptions { + @Parameter(names = {"-g", "--glue"}, description = "Where cucumber looks for step definitions and hooks.") + public List glue = new ArrayList(); + + @Parameter(names = {"--dotcucumber"}, description = "Where to output .cucumber files (for code completion).") + public File dotCucumber; + + @Parameter(names = {"--dry-run"}, description = "Don't run anything, just format the features.") + public boolean dryRun; + + @Parameter(names = {"--tags"}, description = "Only execute scenarios matching TAG_EXPRESSION.") + public List tags = new ArrayList(); + + @Parameter(names = {"--strict"}, description = "Fail if there are any undefined or pending steps.") + public boolean strict; + + @Parameter(names = {"--format"}, description = "Formatter to use.") + public List formatters = new ArrayList(); + + @Parameter(description = "Feature paths") + public List featurePaths = new ArrayList(); + + public RuntimeOptions(String... args) { + JCommander cmd = new JCommander(this); + cmd.addConverterFactory(new FormatterFactory()); + cmd.setProgramName("cucumber"); + cmd.parse(args); + + if (formatters.isEmpty()) { + formatters.add(new ProgressFormatter(System.out)); + } + } + + public List cucumberFeatures(ResourceLoader resourceLoader) { + return load(resourceLoader, featurePaths, filters()); + } + + public Formatter formatter(ClassLoader classLoader) { + return (Formatter) Proxy.newProxyInstance(classLoader, new Class[]{Formatter.class}, new InvocationHandler() { + @Override + public Object invoke(Object target, Method method, Object[] args) throws Throwable { + for (Formatter formatter : formatters) { + method.invoke(formatter, args); + } + return null; + } + }); + } + + public Reporter reporter(ClassLoader classLoader) { + return (Reporter) Proxy.newProxyInstance(classLoader, new Class[]{Reporter.class}, new InvocationHandler() { + @Override + public Object invoke(Object target, Method method, Object[] args) throws Throwable { + for (Formatter formatter : formatters) { + if (formatter instanceof Reporter) { + method.invoke(formatter, args); + } + } + return null; + } + }); + } + + private List filters() { + List filters = new ArrayList(); + filters.addAll(tags); + // TODO: Add lines and patterns (names) + return filters; + } + + public static class FormatterFactory implements IStringConverterFactory { + @Override + public Class> getConverter(Class forType) { + if (forType.equals(Formatter.class)) { + return FormatterConverter.class; + } else return null; + } + } + +} diff --git a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java b/core/src/main/java/cucumber/runtime/model/CucumberFeature.java index 0692f3b217..13dded52a0 100644 --- a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java +++ b/core/src/main/java/cucumber/runtime/model/CucumberFeature.java @@ -4,7 +4,10 @@ import cucumber.io.ResourceLoader; import cucumber.runtime.CucumberException; import cucumber.runtime.FeatureBuilder; +import cucumber.runtime.Runtime; import gherkin.I18n; +import gherkin.formatter.Formatter; +import gherkin.formatter.Reporter; import gherkin.formatter.model.Background; import gherkin.formatter.model.Examples; import gherkin.formatter.model.Feature; @@ -94,6 +97,18 @@ public String getUri() { return uri; } + public void run(Formatter formatter, Reporter reporter, Runtime runtime) { + formatter.uri(getUri()); + formatter.feature(getFeature()); + + for (CucumberTagStatement cucumberTagStatement : getFeatureElements()) { + //Run the scenario, it should handle before and after hooks + cucumberTagStatement.run(formatter, reporter, runtime); + } + formatter.eof(); + + } + private static class CucumberFeatureUriComparator implements Comparator { @Override public int compare(CucumberFeature a, CucumberFeature b) { diff --git a/core/src/test/java/cucumber/formatter/FormatterConverterTest.java b/core/src/test/java/cucumber/formatter/FormatterConverterTest.java new file mode 100644 index 0000000000..44d635e9b2 --- /dev/null +++ b/core/src/test/java/cucumber/formatter/FormatterConverterTest.java @@ -0,0 +1,38 @@ +package cucumber.formatter; + +import gherkin.formatter.Formatter; +import org.junit.Test; + +import java.io.IOException; + +import static cucumber.formatter.TempDir.createTempDirectory; +import static cucumber.formatter.TempDir.createTempFile; +import static org.junit.Assert.assertEquals; + +public class FormatterConverterTest { + private FormatterConverter fc = new FormatterConverter(); + + @Test + public void instantiates_junit_formatter_with_file_arg() { + Formatter formatter = fc.convert("junit:some_file.xml"); + assertEquals(JUnitFormatter.class, formatter.getClass()); + } + + @Test + public void instantiates_html_formatter_with_dir_arg() throws IOException { + Formatter formatter = fc.convert("html:" + createTempDirectory().getAbsolutePath()); + assertEquals(HTMLFormatter.class, formatter.getClass()); + } + + @Test + public void instantiates_pretty_formatter_with_file_arg() throws IOException { + Formatter formatter = fc.convert("pretty:" + createTempFile().getAbsolutePath()); + assertEquals(CucumberPrettyFormatter.class, formatter.getClass()); + } + + @Test + public void instantiates_pretty_formatter_without_file_arg() { + Formatter formatter = fc.convert("pretty"); + assertEquals(CucumberPrettyFormatter.class, formatter.getClass()); + } +} diff --git a/core/src/test/java/cucumber/formatter/FormatterFactoryTest.java b/core/src/test/java/cucumber/formatter/FormatterFactoryTest.java deleted file mode 100644 index d5a1b70f1f..0000000000 --- a/core/src/test/java/cucumber/formatter/FormatterFactoryTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package cucumber.formatter; - -import gherkin.formatter.Formatter; -import gherkin.formatter.JSONFormatter; -import gherkin.formatter.JSONPrettyFormatter; -import gherkin.formatter.PrettyFormatter; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; - -public class FormatterFactoryTest { - - private final FormatterFactory formatterFactory = new FormatterFactory(Thread.currentThread().getContextClassLoader()); - - @Test - public void shouldInstantiateJsonFormatter() { - assertThat(formatterFactory.createFormatter("json", System.out), is(JSONFormatter.class)); - } - - @Test - public void shouldInstantiateJsonPrettyFormatter() { - assertThat(formatterFactory.createFormatter("json-pretty", System.out), is(JSONPrettyFormatter.class)); - } - - @Test - public void shouldInstantiatePrettyFormatter() { - assertThat(formatterFactory.createFormatter("pretty", System.out), is(PrettyFormatter.class)); - } - - @Test - public void shouldInstantiateProgressFormatter() { - assertThat(formatterFactory.createFormatter("progress", System.out), is(ProgressFormatter.class)); - } - - @Test - public void shouldInstantiateHtmlFormatter() { - assertThat(formatterFactory.createFormatter("html", new File(System.getProperty("user.dir"))), is(HTMLFormatter.class)); - } - - @Test - public void shouldInstantiateJUnitFormatter() throws IOException { - assertThat(formatterFactory.createFormatter("junit", File.createTempFile("cucumber-jvm", "report.xml")), is(JUnitFormatter.class)); - } - - @Test - public void shouldInstantiateCustomFormatterFromClassNameWithAppender() { - StringWriter writer = new StringWriter(); - Formatter formatter = formatterFactory.createFormatter(TestFormatter.class.getName(), writer); - assertThat(formatter, is(TestFormatter.class)); - assertSame(writer, ((TestFormatter) formatter).appendable); - } - - @Test - public void shouldInstantiateCustomFormatterFromClassNameWithDirFile() { - File dir = new File(System.getProperty("user.dir")); - Formatter formatter = formatterFactory.createFormatter(TestFormatter.class.getName(), dir); - assertThat(formatter, is(TestFormatter.class)); - assertSame(dir, ((TestFormatter) formatter).dir); - } - -} diff --git a/core/src/test/java/cucumber/formatter/HTMLFormatterTest.java b/core/src/test/java/cucumber/formatter/HTMLFormatterTest.java index 1275a3d271..a07faab9de 100644 --- a/core/src/test/java/cucumber/formatter/HTMLFormatterTest.java +++ b/core/src/test/java/cucumber/formatter/HTMLFormatterTest.java @@ -1,8 +1,5 @@ package cucumber.formatter; -import cucumber.io.ClasspathResourceLoader; -import cucumber.runtime.Backend; -import cucumber.runtime.Runtime; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -15,14 +12,10 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.util.List; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; public class HTMLFormatterTest { @@ -30,8 +23,8 @@ public class HTMLFormatterTest { @Before public void writeReport() throws IOException { - outputDir = createTempDirectory(); - runFeaturesWithFormatter(asList("cucumber/formatter/HTMLFormatterTest.feature"), outputDir); + outputDir = TempDir.createTempDirectory(); + runFeaturesWithFormatter(outputDir); } @Test @@ -55,30 +48,10 @@ public void writes_valid_report_js() throws IOException { } } - private void runFeaturesWithFormatter(final List featurePaths, File outputDir) throws IOException { + private void runFeaturesWithFormatter(File outputDir) throws IOException { final HTMLFormatter f = new HTMLFormatter(outputDir); - final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - final ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader); - final List gluePaths = emptyList(); - final Runtime runtime = new Runtime(resourceLoader, gluePaths, classLoader, asList(mock(Backend.class)), false); - runtime.run(featurePaths, emptyList(), f, f); + f.uri("some.feature"); f.done(); f.close(); } - - private static File createTempDirectory() throws IOException { - File temp = File.createTempFile("temp", Long.toString(System.nanoTime())); - - if (!(temp.delete())) { - throw new IOException("Could not delete temp file: " + temp.getAbsolutePath()); - } - - if (!(temp.mkdir())) { - throw new IOException("Could not create temp directory: " + temp.getAbsolutePath()); - } - - temp.deleteOnExit(); - - return temp; - } } diff --git a/core/src/test/java/cucumber/formatter/JUnitFormatterTest.java b/core/src/test/java/cucumber/formatter/JUnitFormatterTest.java index 8dcb2f9853..66c1c9381b 100644 --- a/core/src/test/java/cucumber/formatter/JUnitFormatterTest.java +++ b/core/src/test/java/cucumber/formatter/JUnitFormatterTest.java @@ -3,61 +3,63 @@ import cucumber.io.ClasspathResourceLoader; import cucumber.runtime.Backend; import cucumber.runtime.Runtime; +import cucumber.runtime.RuntimeOptions; import org.custommonkey.xmlunit.Diff; import org.custommonkey.xmlunit.XMLUnit; import org.junit.Test; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; -import java.io.*; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; import java.util.List; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; -/** - * @author Uladzimir Mihura - * Date: 2/25/12 - * Time: 8:39 PM - */ public class JUnitFormatterTest { @Test public void featureSimpleTest() throws Exception { - runFeaturesWithFormatter(asList("cucumber/formatter/JUnitFormatterTest_1.feature")); - compareXML("cucumber/formatter/JUnitFormatterTest_1.report.xml", "report.xml"); + File report = runFeaturesWithJunitFormatter(asList("cucumber/formatter/JUnitFormatterTest_1.feature")); + assertXmlEqual("cucumber/formatter/JUnitFormatterTest_1.report.xml", report); } @Test public void featureWithBackgroundTest() throws Exception { - runFeaturesWithFormatter(asList("cucumber/formatter/JUnitFormatterTest_2.feature")); - compareXML("cucumber/formatter/JUnitFormatterTest_2.report.xml", "report.xml"); + File report = runFeaturesWithJunitFormatter(asList("cucumber/formatter/JUnitFormatterTest_2.feature")); + assertXmlEqual("cucumber/formatter/JUnitFormatterTest_2.report.xml", report); } @Test public void featureWithOutlineTest() throws Exception { - runFeaturesWithFormatter(asList("cucumber/formatter/JUnitFormatterTest_3.feature")); - compareXML("cucumber/formatter/JUnitFormatterTest_3.report.xml", "report.xml"); + File report = runFeaturesWithJunitFormatter(asList("cucumber/formatter/JUnitFormatterTest_3.feature")); + assertXmlEqual("cucumber/formatter/JUnitFormatterTest_3.report.xml", report); } - private void runFeaturesWithFormatter(final List featurePaths) throws IOException { - File report = new File("report.xml"); -// report.deleteOnExit(); - final JUnitFormatter f = new JUnitFormatter(report); + private File runFeaturesWithJunitFormatter(final List featurePaths) throws IOException { + File report = File.createTempFile("cucumber-jvm-junit", "xml"); final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); final ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader); - final List gluePaths = emptyList(); - final cucumber.runtime.Runtime runtime = new Runtime(resourceLoader, gluePaths, classLoader, asList(mock(Backend.class)), false); - runtime.run(featurePaths, emptyList(), f, f); - f.done(); - f.close(); + + List args = new ArrayList(); + args.add("--format"); + args.add("junit:" + report.getAbsolutePath()); + args.addAll(featurePaths); + + RuntimeOptions runtimeOptions = new RuntimeOptions(args.toArray(new String[args.size()])); + final cucumber.runtime.Runtime runtime = new Runtime(resourceLoader, classLoader, asList(mock(Backend.class)), runtimeOptions); + runtime.run(); + return report; } - private void compareXML(String expected, String received) throws IOException, ParserConfigurationException, SAXException { + private void assertXmlEqual(String expected, File actual) throws IOException, ParserConfigurationException, SAXException { XMLUnit.setIgnoreWhitespace(true); - Diff diff = new Diff(new InputStreamReader(Thread.currentThread().getContextClassLoader().getResourceAsStream(expected)), new FileReader(received)); + Diff diff = new Diff(new InputStreamReader(Thread.currentThread().getContextClassLoader().getResourceAsStream(expected)), new FileReader(actual)); assertTrue("XML files are similar " + diff, diff.identical()); } diff --git a/core/src/test/java/cucumber/formatter/TempDir.java b/core/src/test/java/cucumber/formatter/TempDir.java new file mode 100644 index 0000000000..f033642ecf --- /dev/null +++ b/core/src/test/java/cucumber/formatter/TempDir.java @@ -0,0 +1,26 @@ +package cucumber.formatter; + +import java.io.File; +import java.io.IOException; + +public class TempDir { + public static File createTempDirectory() throws IOException { + File temp = createTempFile(); + + if (!(temp.delete())) { + throw new IOException("Could not delete temp file: " + temp.getAbsolutePath()); + } + + if (!(temp.mkdir())) { + throw new IOException("Could not create temp directory: " + temp.getAbsolutePath()); + } + + temp.deleteOnExit(); + + return temp; + } + + public static File createTempFile() throws IOException { + return File.createTempFile("temp", Long.toString(System.nanoTime())); + } +} diff --git a/core/src/test/java/cucumber/runtime/BackgroundTest.java b/core/src/test/java/cucumber/runtime/BackgroundTest.java index a436d71330..92f713c934 100644 --- a/core/src/test/java/cucumber/runtime/BackgroundTest.java +++ b/core/src/test/java/cucumber/runtime/BackgroundTest.java @@ -6,7 +6,6 @@ import org.junit.Test; import java.io.IOException; -import java.util.ArrayList; import static cucumber.runtime.TestHelper.feature; import static java.util.Arrays.asList; @@ -17,7 +16,8 @@ public class BackgroundTest { @Test public void should_run_background() throws IOException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - Runtime runtime = new Runtime(new ClasspathResourceLoader(classLoader), new ArrayList(), classLoader, asList(mock(Backend.class)), false); + RuntimeOptions runtimeOptions = new RuntimeOptions(); + Runtime runtime = new Runtime(new ClasspathResourceLoader(classLoader), classLoader, asList(mock(Backend.class)), runtimeOptions); CucumberFeature feature = feature("test.feature", "" + "Feature:\n" + " Background:\n" + @@ -27,7 +27,7 @@ public void should_run_background() throws IOException { StringBuilder out = new StringBuilder(); PrettyFormatter pretty = new PrettyFormatter(out, true, true); - runtime.run(feature, pretty, pretty); + feature.run(pretty, pretty, runtime); String expectedOutput = "" + "Feature: \n" + "\n" + diff --git a/core/src/test/java/cucumber/runtime/HookOrderTest.java b/core/src/test/java/cucumber/runtime/HookOrderTest.java index ede57bb358..75906421a9 100644 --- a/core/src/test/java/cucumber/runtime/HookOrderTest.java +++ b/core/src/test/java/cucumber/runtime/HookOrderTest.java @@ -27,7 +27,8 @@ public class HookOrderTest { @Before public void buildMockWorld() { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - runtime = new Runtime(mock(ResourceLoader.class), new ArrayList(), classLoader, asList(mock(Backend.class)), false); + RuntimeOptions runtimeOptions = new RuntimeOptions(); + runtime = new Runtime(mock(ResourceLoader.class), classLoader, asList(mock(Backend.class)), runtimeOptions); glue = runtime.getGlue(); } diff --git a/core/src/test/java/cucumber/runtime/HookTest.java b/core/src/test/java/cucumber/runtime/HookTest.java index 53e022a440..25b524ff4a 100644 --- a/core/src/test/java/cucumber/runtime/HookTest.java +++ b/core/src/test/java/cucumber/runtime/HookTest.java @@ -47,7 +47,8 @@ public void after_hooks_execute_before_objects_are_disposed() throws Throwable { CucumberScenario scenario = new CucumberScenario(feature, null, gherkinScenario); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - Runtime runtime = new Runtime(new ClasspathResourceLoader(classLoader), CODE_PATHS, classLoader, asList(backend), false); + RuntimeOptions runtimeOptions = new RuntimeOptions(); + Runtime runtime = new Runtime(new ClasspathResourceLoader(classLoader), classLoader, asList(backend), runtimeOptions); runtime.getGlue().addAfterHook(hook); scenario.run(mock(Formatter.class), mock(Reporter.class), runtime); diff --git a/core/src/test/java/cucumber/runtime/RuntimeOptionsTest.java b/core/src/test/java/cucumber/runtime/RuntimeOptionsTest.java new file mode 100644 index 0000000000..6a422b4c1a --- /dev/null +++ b/core/src/test/java/cucumber/runtime/RuntimeOptionsTest.java @@ -0,0 +1,35 @@ +package cucumber.runtime; + +import cucumber.formatter.HTMLFormatter; +import org.junit.Test; + +import java.io.File; + +import static java.util.Arrays.asList; +import static junit.framework.Assert.assertEquals; + +public class RuntimeOptionsTest { + @Test + public void assigns_feature_paths() { + RuntimeOptions options = new RuntimeOptions("--glue", "somewhere", "somewhere_else"); + assertEquals(asList("somewhere_else"), options.featurePaths); + } + + @Test + public void assigns_glue() { + RuntimeOptions options = new RuntimeOptions("--glue", "somewhere"); + assertEquals(asList("somewhere"), options.glue); + } + + @Test + public void assigns_dotcucumber() { + RuntimeOptions options = new RuntimeOptions("--dotcucumber", "somewhere", "--glue", "somewhere"); + assertEquals(new File("somewhere"), options.dotCucumber); + } + + @Test + public void creates_formatter() { + RuntimeOptions options = new RuntimeOptions("--format", "html:some/dir", "--glue", "somewhere"); + assertEquals(HTMLFormatter.class, options.formatters.get(0).getClass()); + } +} diff --git a/core/src/test/java/cucumber/runtime/RuntimeTest.java b/core/src/test/java/cucumber/runtime/RuntimeTest.java index 2f67437f94..67384ba97a 100644 --- a/core/src/test/java/cucumber/runtime/RuntimeTest.java +++ b/core/src/test/java/cucumber/runtime/RuntimeTest.java @@ -5,7 +5,6 @@ import gherkin.formatter.JSONPrettyFormatter; import org.junit.Test; -import java.util.Collections; import java.util.List; import static cucumber.runtime.TestHelper.feature; @@ -26,7 +25,9 @@ public void runs_feature_with_json_formatter() throws Exception { JSONPrettyFormatter jsonFormatter = new JSONPrettyFormatter(out); List backends = asList(mock(Backend.class)); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - new Runtime(new ClasspathResourceLoader(classLoader), Collections.emptyList(), classLoader, backends, true).run(feature, jsonFormatter, jsonFormatter); + RuntimeOptions runtimeOptions = new RuntimeOptions(); + Runtime runtime = new Runtime(new ClasspathResourceLoader(classLoader), classLoader, backends, runtimeOptions); + feature.run(jsonFormatter, jsonFormatter, runtime); jsonFormatter.done(); String expected = "" + "[\n" + diff --git a/examples/scala-calculator/src/test/scala/cucumber/examples/scalacalculator/RunCukesTest.scala b/examples/scala-calculator/src/test/scala/cucumber/examples/scalacalculator/RunCukesTest.scala index 47966f25d7..c2472fe246 100644 --- a/examples/scala-calculator/src/test/scala/cucumber/examples/scalacalculator/RunCukesTest.scala +++ b/examples/scala-calculator/src/test/scala/cucumber/examples/scalacalculator/RunCukesTest.scala @@ -1,8 +1,7 @@ package cucumber.examples.scalacalculator import org.junit.runner.RunWith -import cucumber.junit.{Feature, Cucumber} +import cucumber.junit.Cucumber @RunWith(classOf[Cucumber]) -@Feature("basic_arithmetic.feature") class RunCukesTest \ No newline at end of file diff --git a/guice/src/test/java/cucumber/runtime/java/guice/GuiceFactoryTest.java b/guice/src/test/java/cucumber/runtime/java/guice/GuiceFactoryTest.java index 4fa9fdaa86..e1b92aae70 100644 --- a/guice/src/test/java/cucumber/runtime/java/guice/GuiceFactoryTest.java +++ b/guice/src/test/java/cucumber/runtime/java/guice/GuiceFactoryTest.java @@ -5,9 +5,7 @@ import java.io.IOException; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; +import static org.junit.Assert.*; public class GuiceFactoryTest { @Test diff --git a/java/pom.xml b/java/pom.xml index a63e614cbf..0da1b91760 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 @@ -54,7 +55,8 @@ generate-sources - + - + diff --git a/jruby/src/main/java/cucumber/runtime/jruby/JRubyBackend.java b/jruby/src/main/java/cucumber/runtime/jruby/JRubyBackend.java index ddbcfc15c7..c83f2feb86 100644 --- a/jruby/src/main/java/cucumber/runtime/jruby/JRubyBackend.java +++ b/jruby/src/main/java/cucumber/runtime/jruby/JRubyBackend.java @@ -2,11 +2,7 @@ import cucumber.io.Resource; import cucumber.io.ResourceLoader; -import cucumber.runtime.Backend; -import cucumber.runtime.CucumberException; -import cucumber.runtime.Glue; -import cucumber.runtime.PendingException; -import cucumber.runtime.UnreportedStepExecutor; +import cucumber.runtime.*; import cucumber.runtime.snippets.SnippetGenerator; import cucumber.table.DataTable; import gherkin.I18n; @@ -20,11 +16,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; -import java.util.HashSet; -import java.util.List; -import java.util.MissingResourceException; -import java.util.ResourceBundle; -import java.util.Set; +import java.util.*; public class JRubyBackend implements Backend { private static final String DSL = "/cucumber/runtime/jruby/dsl.rb"; diff --git a/jruby/src/test/resources/cucumber/runtime/jruby/test/cukes.feature b/jruby/src/test/resources/cucumber/runtime/jruby/test/cukes.feature index 986cc64882..581a84fb7e 100644 --- a/jruby/src/test/resources/cucumber/runtime/jruby/test/cukes.feature +++ b/jruby/src/test/resources/cucumber/runtime/jruby/test/cukes.feature @@ -42,7 +42,8 @@ Feature: Cukes Given I store the value "" When I grab another value "" Then those values are the same - Examples: - | value | otherValue | - | 1 | 1 | - | awesome | awesome | \ No newline at end of file + + Examples: + | value | otherValue | + | 1 | 1 | + | awesome | awesome | \ No newline at end of file diff --git a/jruby/src/test/resources/cucumber/runtime/jruby/test/example.rb b/jruby/src/test/resources/cucumber/runtime/jruby/test/example.rb index 8f453a060d..7bdb69f2b4 100644 --- a/jruby/src/test/resources/cucumber/runtime/jruby/test/example.rb +++ b/jruby/src/test/resources/cucumber/runtime/jruby/test/example.rb @@ -1,7 +1,7 @@ # JRUBY will barf out warnings when we redefine constants :D DEFINE_A_CONSTANT = 1 -#this will help me ensure that jruby stuff is only loaded one time +# this will help me ensure that jruby stuff is only loaded one time # otherwise tests will bomb and fail, as they should. if @loaded_already raise "OMG PARSED MULTIPLE TIMES!!!!1" diff --git a/junit/pom.xml b/junit/pom.xml index 1b06f630bc..099e43e33d 100644 --- a/junit/pom.xml +++ b/junit/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 diff --git a/junit/src/main/java/cucumber/junit/Cucumber.java b/junit/src/main/java/cucumber/junit/Cucumber.java index e1616cce67..0ccf4466c6 100644 --- a/junit/src/main/java/cucumber/junit/Cucumber.java +++ b/junit/src/main/java/cucumber/junit/Cucumber.java @@ -1,10 +1,10 @@ package cucumber.junit; -import cucumber.formatter.NullReporter; import cucumber.io.ClasspathResourceLoader; import cucumber.io.ResourceLoader; import cucumber.runtime.CucumberException; import cucumber.runtime.Runtime; +import cucumber.runtime.RuntimeOptions; import cucumber.runtime.model.CucumberFeature; import cucumber.runtime.snippets.SummaryPrinter; import org.junit.runner.Description; @@ -13,13 +13,13 @@ import org.junit.runners.model.InitializationError; import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; -import static cucumber.runtime.Utils.packagePath; -import static cucumber.runtime.model.CucumberFeature.load; -import static java.util.Arrays.asList; - /** * Classes annotated with {@code @RunWith(Cucumber.class)} will run a Cucumber Feature. * The class should be empty without any fields or methods. @@ -27,12 +27,11 @@ * Cucumber will look for a {@code .feature} file on the classpath, using the same resource * path as the annotated class ({@code .class} substituted by {@code .feature}). *

- * Additional hints can be given to Cucumber by annotating the class with {@link cucumber.junit.Feature}. + * Additional hints can be given to Cucumber by annotating the class with {@link Options}. * - * @see cucumber.junit.Feature + * @see Options */ public class Cucumber extends ParentRunner { - private final ResourceLoader resourceLoader; private final JUnitReporter jUnitReporter; private final List children = new ArrayList(); private final Runtime runtime; @@ -48,16 +47,16 @@ public class Cucumber extends ParentRunner { public Cucumber(Class clazz) throws InitializationError, IOException { super(clazz); ClassLoader classLoader = clazz.getClassLoader(); - resourceLoader = new ClasspathResourceLoader(classLoader); + ResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader); assertNoDeclaredMethods(clazz); - List featurePaths = featurePaths(clazz); - List gluePaths = gluePaths(clazz); - runtime = new Runtime(resourceLoader, gluePaths, classLoader); + RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(clazz); + RuntimeOptions runtimeOptions = runtimeOptionsFactory.create(); + runtime = new Runtime(resourceLoader, classLoader, runtimeOptions); // TODO: Create formatter(s) based on Annotations. Use same technique as in cli.Main for MultiFormatter - jUnitReporter = new JUnitReporter(new NullReporter(), new NullReporter()); - addChildren(featurePaths, filters(clazz)); + jUnitReporter = new JUnitReporter(runtimeOptions.reporter(classLoader), runtimeOptions.formatter(classLoader)); + addChildren(runtimeOptions.cucumberFeatures(resourceLoader)); } @Override @@ -96,64 +95,37 @@ private void assertNoDeclaredMethods(Class clazz) { } } - /** - * @param clazz the Class used to kick it all off - * @return either a path to a single feature, or to a directory or classpath entry containing them - */ - private List featurePaths(Class clazz) { - cucumber.junit.Feature featureAnnotation = getFeatureAnnotation(clazz); - String featurePath; - if (featureAnnotation != null) { - featurePath = featureAnnotation.value(); - } else { - featurePath = packagePath(clazz); - } - return asList(featurePath); - } - - private List gluePaths(Class clazz) { - List gluePaths = new ArrayList(); - - gluePaths.add(packagePath(clazz)); - - // Add additional ones - cucumber.junit.Feature featureAnnotation = getFeatureAnnotation(clazz); - if (featureAnnotation != null) { - for (String packageName : featureAnnotation.packages()) { - gluePaths.add(packagePath(packageName)); - } - } - return gluePaths; - } - - private List filters(Class clazz) { - cucumber.junit.Feature featureAnnotation = getFeatureAnnotation(clazz); - Object[] filters = new Object[0]; - if (featureAnnotation != null) { - filters = toLong(featureAnnotation.lines()); - if (filters.length == 0) { - filters = featureAnnotation.tags(); - } - } - return asList(filters); - } - - private void addChildren(List featurePaths, final List filters) throws InitializationError { - List cucumberFeatures = load(resourceLoader, featurePaths, filters); + private void addChildren(List cucumberFeatures) throws InitializationError { for (CucumberFeature cucumberFeature : cucumberFeatures) { children.add(new FeatureRunner(cucumberFeature, runtime, jUnitReporter)); } } - private Long[] toLong(long[] primitiveLongs) { - Long[] longs = new Long[primitiveLongs.length]; - for (int i = 0; i < primitiveLongs.length; i++) { - longs[i] = primitiveLongs[i]; - } - return longs; - } - - private Feature getFeatureAnnotation(Class clazz) { - return (Feature) clazz.getAnnotation(Feature.class); + /** + * This annotation can be used to give additional hints to the {@link cucumber.junit.Cucumber} runner + * about what to run. It provides similar options to the Cucumber command line used by {@link cucumber.cli.Main} + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE}) + public static @interface Options { + /** + * @return true if this is a dry run + */ + boolean dryRun() default false; + + /** + * @return the paths to the feature(s) + */ + String[] features() default {}; + + /** + * @return where to look for glue code (stepdefs and hooks) + */ + String[] glue() default {}; + + /** + * @return what tags in the feature should be executed + */ + String[] tags() default {}; } } diff --git a/junit/src/main/java/cucumber/junit/Feature.java b/junit/src/main/java/cucumber/junit/Feature.java deleted file mode 100644 index a59abb5289..0000000000 --- a/junit/src/main/java/cucumber/junit/Feature.java +++ /dev/null @@ -1,34 +0,0 @@ -package cucumber.junit; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * This annotation can be used to give additional hints to the {@link Cucumber} runner - * about what to run. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -public @interface Feature { - /** - * @return the path to the .feature - */ - String value(); - - /** - * @return what lines in the feature should be executed - */ - long[] lines() default {}; - - /** - * @return what tags in the feature should be executed - */ - String[] tags() default {}; - - /** - * @return where to look for glue code (stepdefs and hooks) - */ - String[] packages() default {}; -} diff --git a/junit/src/main/java/cucumber/junit/RuntimeOptionsFactory.java b/junit/src/main/java/cucumber/junit/RuntimeOptionsFactory.java new file mode 100644 index 0000000000..457c253419 --- /dev/null +++ b/junit/src/main/java/cucumber/junit/RuntimeOptionsFactory.java @@ -0,0 +1,72 @@ +package cucumber.junit; + +import cucumber.runtime.RuntimeOptions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static cucumber.runtime.Utils.packagePath; + +public class RuntimeOptionsFactory { + private Class clazz; + + public RuntimeOptionsFactory(Class clazz) { + this.clazz = clazz; + } + + public RuntimeOptions create() { + List args = new ArrayList(); + Cucumber.Options cucumberOptions = getFeatureAnnotation(clazz); + + addDryRun(cucumberOptions, args); + addGlue(cucumberOptions, clazz, args); + addFeatures(cucumberOptions, clazz, args); + addTags(cucumberOptions, args); + + return new RuntimeOptions(args.toArray(new String[args.size()])); + + } + + private Cucumber.Options getFeatureAnnotation(Class clazz) { + return clazz.getAnnotation(Cucumber.Options.class); + } + + private void addDryRun(Cucumber.Options options, List args) { + if (options != null) { + if (options.dryRun()) { + args.add("--dry-run"); + } + } + } + + private void addGlue(Cucumber.Options options, Class clazz, List args) { + if (options != null && options.glue().length != 0) { + for (String glue : options.glue()) { + args.add("--glue"); + args.add(glue); + } + } else { + args.add("--glue"); + args.add(packagePath(clazz)); + } + } + + private void addFeatures(Cucumber.Options options, Class clazz, List args) { + if (options != null && options.features().length != 0) { + Collections.addAll(args, options.features()); + } else { + args.add(packagePath(clazz)); + } + } + + private void addTags(Cucumber.Options options, List args) { + if (options != null) { + for (String tags : options.tags()) { + args.add("--tags"); + args.add(tags); + } + } + } + +} diff --git a/junit/src/test/java/cucumber/junit/CucumberTest.java b/junit/src/test/java/cucumber/junit/CucumberTest.java index 5ce2ed6621..5b39000ae4 100644 --- a/junit/src/test/java/cucumber/junit/CucumberTest.java +++ b/junit/src/test/java/cucumber/junit/CucumberTest.java @@ -52,11 +52,11 @@ public void finds_no_features_when_explicit_package_has_nothnig() throws IOExcep private class ImplicitPackage { } - @Feature("cucumber/junit") + @Cucumber.Options(features = {"cucumber/junit"}) private class ExplicitPackage { } - @Feature("gibber/ish") + @Cucumber.Options(features = {"gibber/ish"}) private class ExplicitPackageWithNoFeatures { } } diff --git a/jython/bin/cucumber-jvm.py b/jython/bin/cucumber-jvm.py index 620e05d629..6e11f04914 100644 --- a/jython/bin/cucumber-jvm.py +++ b/jython/bin/cucumber-jvm.py @@ -18,4 +18,4 @@ def createRuntime(resourceLoader, gluePaths, classLoader, dryRun): jythonBackend = JythonBackend(resourceLoader) return Runtime(resourceLoader, gluePaths, classLoader, [jythonBackend], dryRun) -Main.run(sys.argv[1:], cl, createRuntime) +Main.run(sys.argv[1:], cl) diff --git a/openejb/src/main/java/cucumber/runtime/java/openejb/OpenEJBObjectFactory.java b/openejb/src/main/java/cucumber/runtime/java/openejb/OpenEJBObjectFactory.java index 5e2c16cd68..98e06be2aa 100644 --- a/openejb/src/main/java/cucumber/runtime/java/openejb/OpenEJBObjectFactory.java +++ b/openejb/src/main/java/cucumber/runtime/java/openejb/OpenEJBObjectFactory.java @@ -5,12 +5,7 @@ import org.apache.openejb.OpenEjbContainer; import javax.ejb.embeddable.EJBContainer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.util.*; public class OpenEJBObjectFactory implements ObjectFactory { static { diff --git a/picocontainer/pom.xml b/picocontainer/pom.xml index a60d01e604..c49de2aa19 100644 --- a/picocontainer/pom.xml +++ b/picocontainer/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 @@ -73,15 +74,16 @@ - - - - - - - - - + + + + + + + + + diff --git a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/DatesSteps.java b/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/DatesSteps.java index d8fb452ccb..543c4ada85 100644 --- a/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/DatesSteps.java +++ b/picocontainer/src/test/java/cucumber/runtime/java/picocontainer/DatesSteps.java @@ -24,7 +24,7 @@ public void the_iso_date_is(@DateFormat("yyyy-MM-dd'T'HH:mm:ss") Date date) { this.date = toMidnight(date); } - @Given("^the iso calendar is (\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2})$") + @Given("^the iso calendar is (\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})$") public void the_iso_calendar_is(@DateFormat("yyyy-MM-dd'T'HH:mm:ss") Calendar cal) { this.date = toMidnight(cal); } diff --git a/picocontainer/src/test/resources/.cucumber/stepdefs.json b/picocontainer/src/test/resources/.cucumber/stepdefs.json index 04cc098a97..1b89ca5498 100644 --- a/picocontainer/src/test/resources/.cucumber/stepdefs.json +++ b/picocontainer/src/test/resources/.cucumber/stepdefs.json @@ -7,17 +7,59 @@ { "source": "^I have (\\d+) (.*) in my belly$", "flags": "", - "steps": [] + "steps": [ + { + "name": "I have 12 cukes in my belly", + "args": [ + { + "offset": 7, + "val": "12" + }, + { + "offset": 10, + "val": "cukes" + } + ] + }, + { + "name": "I have 3 cukes in my belly", + "args": [ + { + "offset": 7, + "val": "3" + }, + { + "offset": 9, + "val": "cukes" + } + ] + } + ] }, { "source": "^I have this in my basket:$", "flags": "", - "steps": [] + "steps": [ + { + "name": "I have this in my basket:", + "args": [] + } + ] }, { "source": "^I should be (.*)$", "flags": "", - "steps": [] + "steps": [ + { + "name": "I should be \u003cmood\u003e", + "args": [ + { + "offset": 12, + "val": "\u003cmood\u003e" + } + ] + } + ] }, { "source": "^the (.*) contains (.*)", @@ -45,9 +87,19 @@ ] }, { - "source": "^the iso calendar is (\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2})$", + "source": "^the iso calendar is (\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})$", "flags": "", - "steps": [] + "steps": [ + { + "name": "the iso calendar is 2012-03-01T06:54:14", + "args": [ + { + "offset": 20, + "val": "2012-03-01T06:54:14" + } + ] + } + ] }, { "source": "^the iso date is (.+)$", @@ -57,6 +109,16 @@ { "source": "^there are (\\d+) cukes in my belly", "flags": "", - "steps": [] + "steps": [ + { + "name": "there are 3 cukes in my belly", + "args": [ + { + "offset": 10, + "val": "3" + } + ] + } + ] } ] \ No newline at end of file diff --git a/picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/cukes.featureX b/picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/cukes.feature similarity index 100% rename from picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/cukes.featureX rename to picocontainer/src/test/resources/cucumber/runtime/java/picocontainer/cukes.feature diff --git a/pom.xml b/pom.xml index 8fb23ef6e3..6b605be52b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 info.cukes cucumber-jvm @@ -16,7 +17,7 @@ UTF-8 ${project.build.directory} - 2.9.0 + 2.9.1 2.0.0-beta-2 @@ -79,6 +80,11 @@ gherkin ${gherkin.version} + + com.beust + jcommander + 1.23 + org.codehaus.groovy groovy-all @@ -341,7 +347,8 @@ true full - + cucumber.cli.Main diff --git a/spring/src/main/java/cucumber/runtime/java/spring/hooks/SpringTransactionHooks.java b/spring/src/main/java/cucumber/runtime/java/spring/hooks/SpringTransactionHooks.java index 50bddede33..8f143f4ca1 100644 --- a/spring/src/main/java/cucumber/runtime/java/spring/hooks/SpringTransactionHooks.java +++ b/spring/src/main/java/cucumber/runtime/java/spring/hooks/SpringTransactionHooks.java @@ -13,8 +13,8 @@ * This class defines before and after hooks which provide automatic spring rollback capabilities. * These hooks will apply to any element(s) within a .feature file tagged with @txn. *

- * Clients wishing to leverage these hooks should include this class' package in the packages property of the - * Test class' Feature annotation. + * Clients wishing to leverage these hooks should include this class' package in the glue property of the + * Test class' {@link Cucumber.Options} annotation. *

* The BEFORE and AFTER hooks both rely on being able to obtain a PlatformTransactionManager by type, or * by an optionally specified bean name, from the runtime BeanFactory. diff --git a/spring/src/test/java/cucumber/runtime/java/spring/SpringFactoryTest.java b/spring/src/test/java/cucumber/runtime/java/spring/SpringFactoryTest.java index 179b830f6c..58bee8c53b 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/SpringFactoryTest.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/SpringFactoryTest.java @@ -3,11 +3,7 @@ import cucumber.runtime.java.ObjectFactory; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class SpringFactoryTest { diff --git a/spring/src/test/java/cucumber/runtime/java/spring/hooks/SpringTransactionHooksTest.java b/spring/src/test/java/cucumber/runtime/java/spring/hooks/SpringTransactionHooksTest.java index 4e375f933d..d71a65ba2c 100644 --- a/spring/src/test/java/cucumber/runtime/java/spring/hooks/SpringTransactionHooksTest.java +++ b/spring/src/test/java/cucumber/runtime/java/spring/hooks/SpringTransactionHooksTest.java @@ -11,9 +11,7 @@ import org.springframework.transaction.support.SimpleTransactionStatus; import static org.junit.Assert.assertSame; -import static org.mockito.Mockito.isA; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class SpringTransactionHooksTest { diff --git a/spring/src/test/resources/applicationContext.xml b/spring/src/test/resources/applicationContext.xml index 5fbce27c49..28ebcee442 100644 --- a/spring/src/test/resources/applicationContext.xml +++ b/spring/src/test/resources/applicationContext.xml @@ -1,8 +1,7 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">