Skip to content

Make the Summary Printer into a plugin #828

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 18, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/src/main/java/cucumber/api/SummaryPrinter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package cucumber.api;

public interface SummaryPrinter {
public void print(cucumber.runtime.Runtime runtime);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package cucumber.runtime;

import cucumber.api.SummaryPrinter;

import java.io.PrintStream;
import java.util.List;

public class SummaryPrinter {
public class DefaultSummaryPrinter implements SummaryPrinter {
private final PrintStream out;

public SummaryPrinter(PrintStream out) {
this.out = out;
public DefaultSummaryPrinter() {
this.out = System.out;
}

@Override
public void print(cucumber.runtime.Runtime runtime) {
out.println();
printStats(runtime);
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/cucumber/runtime/NullSummaryPrinter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package cucumber.runtime;

import cucumber.api.SummaryPrinter;

public class NullSummaryPrinter implements SummaryPrinter {

@Override
public void print(Runtime runtime) {
// Do nothing
}

}
11 changes: 6 additions & 5 deletions core/src/main/java/cucumber/runtime/Runtime.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import cucumber.api.Pending;
import cucumber.api.StepDefinitionReporter;
import cucumber.api.SummaryPrinter;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.model.CucumberFeature;
import cucumber.runtime.xstream.LocalizedXStreams;
Expand Down Expand Up @@ -126,12 +127,12 @@ public void run() throws IOException {
}

public void printSummary() {
// TODO: inject a SummaryPrinter in the ctor
new SummaryPrinter(System.out).print(this);
SummaryPrinter summaryPrinter = runtimeOptions.summaryPrinter(classLoader);
summaryPrinter.print(this);
}

void printStats(PrintStream out) {
stats.printStats(out);
stats.printStats(out, runtimeOptions.isStrict());
}

public void buildBackendWorlds(Reporter reporter, Set<Tag> tags, Scenario gherkinScenario) {
Expand All @@ -144,8 +145,8 @@ public void buildBackendWorlds(Reporter reporter, Set<Tag> tags, Scenario gherki
scenarioResult = new ScenarioImpl(reporter, tags, gherkinScenario);
}

public void disposeBackendWorlds() {
stats.addScenario(scenarioResult.getStatus());
public void disposeBackendWorlds(String scenarioDesignation) {
stats.addScenario(scenarioResult.getStatus(), scenarioDesignation);
for (Backend backend : backends) {
backend.disposeWorld();
}
Expand Down
44 changes: 37 additions & 7 deletions core/src/main/java/cucumber/runtime/RuntimeOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import cucumber.api.SnippetType;
import cucumber.api.StepDefinitionReporter;
import cucumber.api.SummaryPrinter;
import cucumber.runtime.formatter.ColorAware;
import cucumber.runtime.formatter.PluginFactory;
import cucumber.runtime.formatter.StrictAware;
import cucumber.runtime.io.ResourceLoader;
import cucumber.runtime.model.CucumberFeature;
import gherkin.I18n;
import cucumber.runtime.model.PathWithLines;
import gherkin.I18n;
import gherkin.formatter.Formatter;
import gherkin.formatter.Reporter;
import gherkin.util.FixJava;
Expand All @@ -31,7 +32,9 @@ public class RuntimeOptions {
private final List<String> glue = new ArrayList<String>();
private final List<Object> filters = new ArrayList<Object>();
private final List<String> featurePaths = new ArrayList<String>();
private final List<String> pluginNames = new ArrayList<String>();
private final List<String> pluginFormatterNames = new ArrayList<String>();
private final List<String> pluginStepDefinitionReporterNames = new ArrayList<String>();
private final List<String> pluginSummaryPrinterNames = new ArrayList<String>();
private final PluginFactory pluginFactory;
private final List<Object> plugins = new ArrayList<Object>();
private boolean dryRun;
Expand Down Expand Up @@ -81,8 +84,11 @@ public RuntimeOptions(Env env, PluginFactory pluginFactory, List<String> argv) {
parse(Shellwords.parse(cucumberOptionsFromEnv));
}

if (pluginNames.isEmpty()) {
pluginNames.add("progress");
if (pluginFormatterNames.isEmpty()) {
pluginFormatterNames.add("progress");
}
if (pluginSummaryPrinterNames.isEmpty()) {
pluginSummaryPrinterNames.add("default_summary");
}
}

Expand All @@ -109,10 +115,10 @@ private void parse(List<String> args) {
} else if (arg.equals("--tags") || arg.equals("-t")) {
parsedFilters.add(args.remove(0));
} else if (arg.equals("--plugin") || arg.equals("-p")) {
pluginNames.add(args.remove(0));
addPluginName(args.remove(0));
} else if (arg.equals("--format") || arg.equals("-f")) {
System.err.println("WARNING: Cucumber-JVM's --format option is deprecated. Please use --plugin instead.");
pluginNames.add(args.remove(0));
addPluginName(args.remove(0));
} else if (arg.equals("--no-dry-run") || arg.equals("--dry-run") || arg.equals("-d")) {
dryRun = !arg.startsWith("--no-");
} else if (arg.equals("--no-strict") || arg.equals("--strict") || arg.equals("-s")) {
Expand Down Expand Up @@ -150,6 +156,18 @@ private void parse(List<String> args) {
}
}

private void addPluginName(String name) {
if (PluginFactory.isFormatterName(name)) {
pluginFormatterNames.add(name);
} else if (PluginFactory.isStepDefinitionResporterName(name)) {
pluginStepDefinitionReporterNames.add(name);
} else if (PluginFactory.isSummaryPrinterName(name)) {
pluginSummaryPrinterNames.add(name);
} else {
throw new CucumberException("Unrecognized plugin: " + name);
}
}

private boolean haveLineFilters(List<String> parsedFeaturePaths) {
for (String pathName : parsedFeaturePaths) {
if (pathName.startsWith("@") || PathWithLines.hasLineFilters(pathName)) {
Expand Down Expand Up @@ -203,12 +221,20 @@ public List<CucumberFeature> cucumberFeatures(ResourceLoader resourceLoader) {

List<Object> getPlugins() {
if (!pluginNamesInstantiated) {
for (String pluginName : pluginNames) {
for (String pluginName : pluginFormatterNames) {
Object plugin = pluginFactory.create(pluginName);
plugins.add(plugin);
setMonochromeOnColorAwarePlugins(plugin);
setStrictOnStrictAwarePlugins(plugin);
}
for (String pluginName : pluginStepDefinitionReporterNames) {
Object plugin = pluginFactory.create(pluginName);
plugins.add(plugin);
}
for (String pluginName : pluginSummaryPrinterNames) {
Object plugin = pluginFactory.create(pluginName);
plugins.add(plugin);
}
pluginNamesInstantiated = true;
}
return plugins;
Expand All @@ -226,6 +252,10 @@ public StepDefinitionReporter stepDefinitionReporter(ClassLoader classLoader) {
return pluginProxy(classLoader, StepDefinitionReporter.class);
}

public SummaryPrinter summaryPrinter(ClassLoader classLoader) {
return pluginProxy(classLoader, SummaryPrinter.class);
}

/**
* Creates a dynamic proxy that multiplexes method invocations to all plugins of the same type.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cucumber.runtime;

import cucumber.api.CucumberOptions;
import cucumber.runtime.formatter.PluginFactory;
import cucumber.runtime.io.MultiLoader;

import java.util.ArrayList;
Expand Down Expand Up @@ -85,7 +86,9 @@ private void addPlugins(CucumberOptions options, List<String> args) {
for (String plugin : plugins) {
args.add("--plugin");
args.add(plugin);
pluginSpecified = true;
if (PluginFactory.isFormatterName(plugin)) {
pluginSpecified = true;
}
}
}

Expand Down
51 changes: 50 additions & 1 deletion core/src/main/java/cucumber/runtime/Stats.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

class Stats {
Expand All @@ -20,6 +22,10 @@ class Stats {
private long totalDuration = 0;
private Formats formats;
private Locale locale;
private List<String> failedScenarios = new ArrayList<String>();
private List<String> pendingScenarios = new ArrayList<String>();
private List<String> undefinedScenarios = new ArrayList<String>();
private List<String> passedScenarios = new ArrayList<String>();

public Stats(boolean monochrome) {
this(monochrome, Locale.getDefault());
Expand All @@ -34,7 +40,8 @@ public Stats(boolean monochrome, Locale locale) {
}
}

public void printStats(PrintStream out) {
public void printStats(PrintStream out, boolean isStrict) {
printNonZeroResultScenarios(out, isStrict);
if (stepSubCounts.getTotal() == 0) {
out.println("0 Scenarios");
out.println("0 Steps");
Expand Down Expand Up @@ -86,6 +93,35 @@ private void printDuration(PrintStream out) {
out.println(format.format(((double) (totalDuration % ONE_MINUTE)) / ONE_SECOND) + "s");
}

private void printNonZeroResultScenarios(PrintStream out, boolean isStrict) {
printScenarios(out, failedScenarios, Result.FAILED);
if (isStrict) {
printScenarios(out, pendingScenarios, PENDING);
printScenarios(out, undefinedScenarios, Result.UNDEFINED.getStatus());
}
}

private void printScenarios(PrintStream out, List<String> scenarios, String type) {
Format format = formats.get(type);
if (!scenarios.isEmpty()) {
out.println(format.text(capitalizeFirstLetter(type) + " scenarios:"));
}
for (String scenario : scenarios) {
String[] parts = scenario.split("#");
out.print(format.text(parts[0]));
for (int i = 1; i < parts.length; ++i) {
out.println("#" + parts[i]);
}
}
if (!scenarios.isEmpty()) {
out.println();
}
}

private String capitalizeFirstLetter(String type) {
return type.substring(0, 1).toUpperCase(locale) + type.substring(1);
}

public void addStep(Result result) {
addResultToSubCount(stepSubCounts, result.getStatus());
addTime(result.getDuration());
Expand Down Expand Up @@ -117,6 +153,19 @@ private void addResultToSubCount(SubCounts subCounts, String resultStatus) {
}
}

public void addScenario(String resultStatus, String scenarioDesignation) {
addResultToSubCount(scenarioSubCounts, resultStatus);
if (resultStatus.equals(Result.FAILED)) {
failedScenarios.add(scenarioDesignation);
} else if (resultStatus.equals(PENDING)) {
pendingScenarios.add(scenarioDesignation);
} else if (resultStatus.equals(Result.UNDEFINED.getStatus())) {
undefinedScenarios.add(scenarioDesignation);
} else if (resultStatus.equals(Result.PASSED)) {
passedScenarios.add(scenarioDesignation);
}
}

class SubCounts {
public int passed = 0;
public int failed = 0;
Expand Down
47 changes: 45 additions & 2 deletions core/src/main/java/cucumber/runtime/formatter/PluginFactory.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package cucumber.runtime.formatter;

import cucumber.api.StepDefinitionReporter;
import cucumber.api.SummaryPrinter;
import cucumber.runtime.CucumberException;
import cucumber.runtime.DefaultSummaryPrinter;
import cucumber.runtime.NullSummaryPrinter;
import cucumber.runtime.io.URLOutputStream;
import cucumber.runtime.io.UTF8OutputStreamWriter;
import gherkin.formatter.Formatter;
import gherkin.formatter.Reporter;

import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -52,6 +58,8 @@ public class PluginFactory {
put("json", CucumberJSONFormatter.class);
put("usage", UsageFormatter.class);
put("rerun", RerunFormatter.class);
put("default_summary", DefaultSummaryPrinter.class);
put("null_summary", NullSummaryPrinter.class);
}};
private static final Pattern PLUGIN_WITH_FILE_PATTERN = Pattern.compile("([^:]+):(.*)");
private String defaultOutFormatter = null;
Expand Down Expand Up @@ -150,7 +158,7 @@ private <T> Constructor<T> findConstructor(Class<T> pluginClass, Class<?> ctorAr
}
}

private <T> Class<T> pluginClass(String pluginName) {
private static <T> Class<T> pluginClass(String pluginName) {
Class<T> pluginClass = (Class<T>) PLUGIN_CLASSES.get(pluginName);
if (pluginClass == null) {
pluginClass = loadClass(pluginName);
Expand All @@ -159,7 +167,7 @@ private <T> Class<T> pluginClass(String pluginName) {
}

@SuppressWarnings("unchecked")
private <T> Class<T> loadClass(String className) {
private static <T> Class<T> loadClass(String className) {
try {
return (Class<T>) Thread.currentThread().getContextClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
Expand All @@ -181,4 +189,39 @@ private Appendable defaultOutOrFailIfAlreadyUsed(String formatterString) {
defaultOut = null;
}
}

public static boolean isFormatterName(String name) {
Class pluginClass = getPluginClass(name);
if (Formatter.class.isAssignableFrom(pluginClass) || Reporter.class.isAssignableFrom(pluginClass)) {
return true;
}
return false;
}

public static boolean isStepDefinitionResporterName(String name) {
Class pluginClass = getPluginClass(name);
if (StepDefinitionReporter.class.isAssignableFrom(pluginClass)) {
return true;
}
return false;
}

public static boolean isSummaryPrinterName(String name) {
Class pluginClass = getPluginClass(name);
if (SummaryPrinter.class.isAssignableFrom(pluginClass)) {
return true;
}
return false;
}

private static Class getPluginClass(String name) {
Matcher pluginWithFile = PLUGIN_WITH_FILE_PATTERN.matcher(name);
String pluginName;
if (pluginWithFile.matches()) {
pluginName = pluginWithFile.group(1);
} else {
pluginName = name;
}
return pluginClass(pluginName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ public void run(Formatter formatter, Reporter reporter, Runtime runtime) {
} catch (Throwable ignore) {
// IntelliJ has its own formatter which doesn't yet implement this.
}
runtime.disposeBackendWorlds();
runtime.disposeBackendWorlds(createScenarioDesignation());
}

private String createScenarioDesignation() {
return cucumberFeature.getPath() + ":" + Integer.toString(scenario.getLine()) + " # " +
scenario.getKeyword() + ": " + scenario.getName();
}

private void runBackground(Formatter formatter, Reporter reporter, Runtime runtime) {
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/resources/cucumber/api/cli/USAGE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ Options:

-g, --glue PATH Where glue code (step definitions and hooks) is loaded from.
-p, --plugin PLUGIN[:PATH_OR_URL] Register a plugin.
Built-in PLUGIN types: junit, html, pretty, progress, json, usage,
rerun. PLUGIN can also be a fully qualified class name, allowing
Built-in formatter PLUGIN types: junit, html, pretty, progress,
json, usage, rerun. Built-in summary PLUGIN types: default_summary,
null_summary. PLUGIN can also be a fully qualified class name, allowing
registration of 3rd party plugins.
-f, --format FORMAT[:PATH_OR_URL] Deprecated. Use --plugin instead.
-t, --tags TAG_EXPRESSION Only run scenarios tagged with tags matching TAG_EXPRESSION.
Expand Down
Loading