Skip to content

Commit

Permalink
Merge pull request #69 from trivago/multiple-feature-runner
Browse files Browse the repository at this point in the history
Multiple feature runner
  • Loading branch information
Benjamin Bischoff authored May 18, 2018
2 parents 56dbc2b + 5be6045 commit 51de9e2
Show file tree
Hide file tree
Showing 31 changed files with 638 additions and 296 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

Back to [Readme](README.md).

## [1.0.0] - 2018-05-18

### Added

- Support a fixed number of runners running multiple scenarios in sequence
- New `[CUCABLE:RUNNER]` template placeholder that is substituted with the current runner name
- New `[CUCABLE:FEATURE]` template placeholder that is substituted with the one or multiple features in the generated runner

### Changed

- Feature and runner generation is now separated in order to support more features in the future.

### Fixed

- Better logging for missing example table placeholders in scenario outlines (contributed by [@daczczcz1](https://github.com/daczczcz1))

### Removed

- Template placeholder `[FEATURE_FILE_NAME]` is not supported anymore, please use `[CUCABLE:FEATURE]` and `[CUCABLE:RUNNER]` instead

## [0.1.11] - 2018-05-08

- Fix for wrong unicode detection of source feature path in runner comments
Expand Down Expand Up @@ -139,6 +159,7 @@ Back to [Readme](README.md).

Initial project version on GitHub and Maven Central.

[1.0.0]: https://github.com/trivago/cucable-plugin/compare/0.1.11...1.0.0
[0.1.11]: https://github.com/trivago/cucable-plugin/compare/0.1.10...0.1.11
[0.1.10]: https://github.com/trivago/cucable-plugin/compare/0.1.9...0.1.10
[0.1.9]: https://github.com/trivago/cucable-plugin/compare/0.1.8...0.1.9
Expand Down
162 changes: 114 additions & 48 deletions README.md

Large diffs are not rendered by default.

Binary file modified documentation/img/browserstack.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions documentation/img/cucable_flow.gliffy

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions documentation/img/cucable_flow_multi_runner.gliffy

Large diffs are not rendered by default.

Binary file added documentation/img/cucable_flow_multi_runner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions documentation/img/cucable_flow_single_runner.gliffy

Large diffs are not rendered by default.

Binary file added documentation/img/cucable_flow_single_runner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 17 additions & 9 deletions example-project/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>de.benjamin-bischoff</groupId>
<artifactId>cucable-test-project</artifactId>
<version>0.1.11</version>
<version>1.0.0</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down Expand Up @@ -37,18 +37,22 @@
</executions>
<configuration>
<!-- This can be either a Java class file or a text based template -->
<sourceRunnerTemplateFile>src/test/java/some/template/CucableJavaTemplate.java</sourceRunnerTemplateFile>
<sourceRunnerTemplateFile>src/test/java/some/template/CucableJavaTemplate.java
</sourceRunnerTemplateFile>
<!--<sourceRunnerTemplateFile>src/test/resources/cucable.template</sourceRunnerTemplateFile>-->

<!-- process all features in the given directory -->
<sourceFeatures>src/test/resources/features</sourceFeatures>

<!-- process a specific file in the given directory -->
<!-- process a specific feature file in the given directory -->
<!--<sourceFeatures>src/test/resources/features/testfeature/MyTest1.feature</sourceFeatures>-->

<!-- process a specific file and specific line numbers in the given directory -->
<!-- process a specific feature file and specific line numbers in the given directory -->
<!--<sourceFeatures>src/test/resources/features/testfeature/MyTest1.feature:8:19</sourceFeatures>-->

<generatedFeatureDirectory>${generated.feature.directory}</generatedFeatureDirectory>
<generatedRunnerDirectory>${generated.runner.directory}</generatedRunnerDirectory>

<!-- include scenarios with certain tags -->
<!--<includeScenarioTags>-->
<!--<param>@scenarioTag1</param>-->
Expand All @@ -60,12 +64,16 @@
<!--<param>@skipme</param>-->
<!--</excludeScenarioTags>-->

<generatedFeatureDirectory>${generated.feature.directory}</generatedFeatureDirectory>
<generatedRunnerDirectory>${generated.runner.directory}</generatedRunnerDirectory>
<numberOfTestRuns>1</numberOfTestRuns>
<!-- this optional number of test runs will create runners and features multiple times
if set to a number greater than 1 -->
<!--<numberOfTestRuns>1</numberOfTestRuns>-->

<!-- if this is set, a fixed number of runners is generated and
all features will be distributed round-robin to the generated runners. -->
<!--<desiredNumberOfRunners>2</desiredNumberOfRunners>-->

<!-- Logging -->
<logLevel>default</logLevel>
<!-- optional log level -->
<!--<logLevel>default</logLevel>-->
<!--<logLevel>compact</logLevel>-->
<!--<logLevel>minimal</logLevel>-->
<!--<logLevel>off</logLevel>-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import cucumber.api.CucumberOptions;

@CucumberOptions(
features = {"target/parallel/features/[FEATURE_FILE_NAME].feature"},
plugin = {"json:target/cucumber-report/[FEATURE_FILE_NAME].json"}
features = {"target/parallel/features/[CUCABLE:FEATURE].feature"},
plugin = {"json:target/cucumber-report/[CUCABLE:RUNNER].json"}
)
public class CucableJavaTemplate {

Expand Down
6 changes: 3 additions & 3 deletions example-project/src/test/resources/cucable.template
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import cucumber.api.CucumberOptions;

@CucumberOptions(
features = {"target/parallel/features/[FEATURE_FILE_NAME].feature"},
plugin = {"json:target/cucumber-report/[FEATURE_FILE_NAME].json"}
features = {"target/parallel/features/[CUCABLE:FEATURE].feature"},
plugin = {"json:target/cucumber-report/[CUCABLE:RUNNER].json"}
)
public class [FEATURE_FILE_NAME] {
public class [CUCABLE:RUNNER] {
}
2 changes: 1 addition & 1 deletion plugin-code/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.trivago.rta</groupId>
<artifactId>cucable-plugin</artifactId>
<version>0.1.11</version>
<version>1.0.0</version>
<url>https://github.com/trivago/cucable-plugin</url>

<name>Cucable Maven Plugin</name>
Expand Down
17 changes: 16 additions & 1 deletion plugin-code/src/main/java/com/trivago/rta/CucablePlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ final class CucablePlugin extends AbstractMojo {
@Parameter(property = "parallel.excludeScenarioTags")
private List<String> excludeScenarioTags;

/**
* Optional desired number of test runners that each run multiple features in sequence.
*/
@Parameter(property = "parallel.desiredNumberOfRunners", defaultValue = "0")
private int desiredNumberOfRunners = 0;

/**
* Optional log level to control what information is logged in the console.
* Allowed values: default, compact, minimal, off
Expand All @@ -109,6 +115,7 @@ public CucablePlugin(
* @throws CucablePluginException When thrown, the plugin execution is stopped.
*/
public void execute() throws CucablePluginException {

// Initialize logger to be available outside the AbstractMojo class
logger.initialize(getLog(), logLevel);

Expand All @@ -120,15 +127,23 @@ public void execute() throws CucablePluginException {
propertyManager.setNumberOfTestRuns(numberOfTestRuns);
propertyManager.setExcludeScenarioTags(excludeScenarioTags);
propertyManager.setIncludeScenarioTags(includeScenarioTags);
propertyManager.setDesiredNumberOfRunners(desiredNumberOfRunners);
propertyManager.validateSettings();

// Logging
logHeader();
propertyManager.logProperties();

// Create the necessary directories if missing.
fileManager.prepareGeneratedFeatureAndRunnerDirs();
featureFileConverter.convertToSingleScenariosAndRunners(fileManager.getFeatureFilePaths());

// Conversion of scenarios into single scenarios and runners.
featureFileConverter.generateSingleScenarioFeatures(fileManager.getFeatureFilePaths());
}

/**
* Log the plugin name and version.
*/
private void logHeader() {
CucableLogger.CucableLogLevel[] cucableLogLevels =
new CucableLogger.CucableLogLevel[]{CucableLogger.CucableLogLevel.DEFAULT, CucableLogger.CucableLogLevel.COMPACT};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@
import com.trivago.rta.logging.CucableLogger;
import com.trivago.rta.properties.PropertyManager;
import com.trivago.rta.runners.RunnerFileContentRenderer;
import com.trivago.rta.vo.FeatureRunner;
import com.trivago.rta.vo.SingleScenario;
import com.trivago.rta.vo.SingleScenarioRunner;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static com.trivago.rta.logging.CucableLogger.CucableLogLevel.COMPACT;
import static com.trivago.rta.logging.CucableLogger.CucableLogLevel.DEFAULT;
Expand Down Expand Up @@ -86,31 +88,35 @@ public FeatureFileConverter(
* @param featureFilePaths feature files to process
* @throws CucablePluginException see {@link CucablePluginException}
*/
public void convertToSingleScenariosAndRunners(
public void generateSingleScenarioFeatures(
final List<Path> featureFilePaths) throws CucablePluginException {
int scenarioCounter = 0;
int featureFileCounter = 0;
List<String> allGeneratedFeaturePaths = new ArrayList<>();

for (Path featureFilePath : featureFilePaths) {
scenarioCounter += convertToSingleScenariosAndRunners(featureFilePath);
List<String> generatedFeatureFilePaths = generateSingleScenarioFeatures(featureFilePath);
allGeneratedFeaturePaths.addAll(generatedFeatureFilePaths);
featureFileCounter += generatedFeatureFilePaths.size();
}
int runnerFileCounter = generateRunnerClasses(allGeneratedFeaturePaths, propertyManager.getDesiredNumberOfRunners());
logger.info("-------------------------------------", DEFAULT);
logger.info(
String.format("Cucable created %d separate scenario(s) from the provided feature(s).", scenarioCounter),
DEFAULT, COMPACT, MINIMAL
String.format("Cucable created %d separate feature file(s) and %d runner(s) from the provided feature(s).",
featureFileCounter, runnerFileCounter), DEFAULT, COMPACT, MINIMAL
);
}

/**
* Converts all scenarios in the given feature file to single
* scenario feature files and their respective runners.
*
* @param featureFilePath feature file to process.
* @param sourceFeatureFilePath feature file to process.
* @return Number of created scenarios.
* @throws CucablePluginException see {@link CucablePluginException}
*/
private int convertToSingleScenariosAndRunners(final Path featureFilePath)
private List<String> generateSingleScenarioFeatures(final Path sourceFeatureFilePath)
throws CucablePluginException {

String featureFilePathString = featureFilePath.toString();
String featureFilePathString = sourceFeatureFilePath.toString();

if (featureFilePathString == null || featureFilePathString.equals("")) {
throw new MissingFileException(featureFilePathString);
Expand Down Expand Up @@ -141,9 +147,12 @@ private int convertToSingleScenariosAndRunners(final Path featureFilePath)
throw new CucablePluginException("There is no parsable scenario or scenario outline at line " + lineNumbers);
}

// Stores all generated feature file names and associated source feature paths for later runner creation
List<String> generatedFeaturePaths = new ArrayList<>();

for (SingleScenario singleScenario : singleScenarios) {
String renderedFeatureFileContent = featureFileContentRenderer.getRenderedFeatureFileContent(singleScenario);
String featureFileName = getFeatureFileNameFromPath(featureFilePath);
String featureFileName = getFeatureFileNameFromPath(sourceFeatureFilePath);
Integer featureCounter = singleFeatureCounters.getOrDefault(featureFileName, 0);
featureCounter++;
String scenarioCounterFilenamePart = String.format(SCENARIO_COUNTER_FORMAT, featureCounter);
Expand All @@ -169,26 +178,87 @@ private int convertToSingleScenariosAndRunners(final Path featureFilePath)
// Save scenario information to new feature file
fileIO.writeContentToFile(renderedFeatureFileContent, generatedFeatureFilePath);

// Generate runner for the newly generated single scenario feature file
SingleScenarioRunner singleScenarioRunner =
new SingleScenarioRunner(
propertyManager.getSourceRunnerTemplateFile(), generatedFileName);
generatedFeaturePaths.add(generatedFileName);
}
}
logFeatureFileConversionMessage(featureFilePathString, singleScenarios.size());
return generatedFeaturePaths;
}

String renderedRunnerFileContent =
runnerFileContentRenderer.getRenderedRunnerFileContent(singleScenarioRunner, singleScenario);
/**
* Generate runner classes for a list of feature file paths.
*
* @param generatedFeatureNames The list of generated feature file names.
* @param numberOfDesiredRunners The number of desired runners (if set to 0, a runner is generated for each feature file path).
* @return The number of generated runners.
* @throws CucablePluginException see {@link CucablePluginException}.
*/
private int generateRunnerClasses(final List<String> generatedFeatureNames, final int numberOfDesiredRunners) throws CucablePluginException {

String generatedRunnerFilePath =
propertyManager.getGeneratedRunnerDirectory()
.concat(PATH_SEPARATOR)
.concat(generatedFileName)
.concat(RUNNER_FILE_EXTENSION);
int targetRunnerNumber = numberOfDesiredRunners;
if (targetRunnerNumber == 0) {
targetRunnerNumber = generatedFeatureNames.size();
}

fileIO.writeContentToFile(renderedRunnerFileContent, generatedRunnerFilePath);
List<List<String>> generatedFeatureNamesPerRunner = new ArrayList<>(targetRunnerNumber);
for (int i = 0; i < targetRunnerNumber; i++) {
generatedFeatureNamesPerRunner.add(new ArrayList<>());
}

int currentRunnerIndex = 0;
for (String generatedFeatureName : generatedFeatureNames) {
generatedFeatureNamesPerRunner.get(currentRunnerIndex).add(generatedFeatureName);
currentRunnerIndex++;
if (currentRunnerIndex >= targetRunnerNumber) {
currentRunnerIndex = 0;
}
}
int createdScenarios = singleScenarios.size();
logProcessCompleteMessage(featureFilePathString, createdScenarios);
return createdScenarios;

int runnerFileCounter = 0;
for (List<String> generatedFeatureNamesForSingleRunner : generatedFeatureNamesPerRunner) {
if (generatedFeatureNamesForSingleRunner.size() > 0) {
generateRunnerClass(generatedFeatureNamesForSingleRunner);
runnerFileCounter++;
}
}

return runnerFileCounter;
}

/**
* Generate a single runner class from a list of feature files.
*
* @param generatedFeatureFileNames The list of generated generated feature file names.
* @throws CucablePluginException see {@link CucablePluginException}.
*/
private void generateRunnerClass(final List<String> generatedFeatureFileNames) throws CucablePluginException {

// The runner class name will be equal to the feature name if there is only one feature to run.
// Otherwise, a generated runner class name is used.
String runnerClassName;
if (generatedFeatureFileNames.size() == 1) {
runnerClassName = generatedFeatureFileNames.get(0);
} else {
runnerClassName = "CucableMultiRunner_"
.concat(UUID.randomUUID().toString().replace("-", "_"))
.concat(INTEGRATION_TEST_POSTFIX);
}

// Generate runner for the newly generated single scenario feature file
FeatureRunner featureRunner =
new FeatureRunner(
propertyManager.getSourceRunnerTemplateFile(), runnerClassName, generatedFeatureFileNames);

String renderedRunnerClassContent =
runnerFileContentRenderer.getRenderedRunnerFileContent(featureRunner);

String generatedRunnerClassFilePath =
propertyManager.getGeneratedRunnerDirectory()
.concat(PATH_SEPARATOR)
.concat(runnerClassName)
.concat(RUNNER_FILE_EXTENSION);

fileIO.writeContentToFile(renderedRunnerClassContent, generatedRunnerClassFilePath);
}

/**
Expand All @@ -197,12 +267,12 @@ private int convertToSingleScenariosAndRunners(final Path featureFilePath)
* @param featureFileName The name of the processed feature file.
* @param createdScenarios The number of created scenarios for the feature file.
*/
private void logProcessCompleteMessage(String featureFileName, final int createdScenarios) {
private void logFeatureFileConversionMessage(String featureFileName, final int createdScenarios) {
String logPostfix = ".";
if (propertyManager.hasValidScenarioLineNumbers()) {
logPostfix = String.format(" with line number(s) %s.", propertyManager.getScenarioLineNumbers());
}
logger.info(String.format("- %3d scenario(s) from %s%s", createdScenarios, featureFileName, logPostfix),
logger.info(String.format("- %3d feature(s) <= %s%s", createdScenarios, featureFileName, logPostfix),
DEFAULT);
}

Expand Down
Loading

0 comments on commit 51de9e2

Please sign in to comment.