diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f193a..0734518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. Back to [Readme](README.md). -## [1.5.3] - UNRELEASED +## [1.6.0] - 2019-12-10 + +### Changed + +* Cucable is now more resilient when trying to deal with unparsable features - these are skipped instead of stopping the overall execution. + +### Fixed + +* Utf-8 encoding error on linux (#150) +* Scenarios had too many tags when there were also feature tags (#145) + +## [1.5.3] - 2019-09-09 ### Added @@ -271,6 +282,7 @@ Back to [Readme](README.md). Initial project version on GitHub and Maven Central. +[1.6.0]: https://github.com/trivago/cucable-plugin/compare/1.5.3...1.6.0 [1.5.3]: https://github.com/trivago/cucable-plugin/compare/1.5.2...1.5.3 [1.5.2]: https://github.com/trivago/cucable-plugin/compare/1.5.1...1.5.2 [1.5.1]: https://github.com/trivago/cucable-plugin/compare/1.5.0...1.5.1 diff --git a/README.md b/README.md index 3a35b4b..a7b9bca 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,17 @@ [![codecov](https://codecov.io/gh/trivago/cucable-plugin/branch/master/graph/badge.svg)](https://codecov.io/gh/trivago/cucable-plugin) [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/bischoffdev) +--- + +### Note: + +This project is feature-complete. Expect only bug fixes at this time. +For new projects, you should consider using Cucumber's native parallelization feature instead. + +Thanks to everyone using, testing and improving Cucable over the last years! + +--- + ![Cucumber compatible](documentation/img/cucumber-compatible-black-64.png) @@ -16,6 +27,7 @@ - [Cucable Maven Plugin](#cucable-maven-plugin) - [Cucumber 4](#cucumber-4) + - [Cucumber 5](#cucumber-5) - [Repository Structure](#repository-structure) - [Changelog](#changelog) - [Maven dependency](#maven-dependency) @@ -84,6 +96,10 @@ Even though Cucumber 4 supports basic parallel runs, Cucable has more options th * You don't need any test framework changes because Cucable runs before the framework invocations * You have full control over your runners because of template variables and custom placeholders +## Cucumber 5 + +* Cucumber 5 (using testng or junit 5) can natively run features and scenarios in parallel. Cucable __can__ be used but does not __have__ to be. + ## Repository Structure * [plugin-code](plugin-code) contains the full plugin source code. diff --git a/example-project/pom.xml b/example-project/pom.xml index d163801..d2a04c0 100644 --- a/example-project/pom.xml +++ b/example-project/pom.xml @@ -7,7 +7,7 @@ com.trivago.rta cucable-test-project - 1.5.2 + 1.6.0 jar diff --git a/example-project/src/test/resources/features/testfeature2/MyTest1InTestFeature2.feature b/example-project/src/test/resources/features/testfeature2/MyTest1InTestFeature2.feature index 264ee09..2edefef 100644 --- a/example-project/src/test/resources/features/testfeature2/MyTest1InTestFeature2.feature +++ b/example-project/src/test/resources/features/testfeature2/MyTest1InTestFeature2.feature @@ -4,4 +4,9 @@ Feature: MyTest1InTestFeature2 @scenario1Tag1 @scenario1Tag2 Scenario: Scenario 1 in MyTest1InTestFeature2 - Given I do something \ No newline at end of file + Given I do something + + @Login + Scenario: Login + Given this is a given step + Given I am on a page with text 'İ' \ No newline at end of file diff --git a/plugin-code/pom.xml b/plugin-code/pom.xml index 625363e..29bddf6 100644 --- a/plugin-code/pom.xml +++ b/plugin-code/pom.xml @@ -6,7 +6,7 @@ com.trivago.rta cucable-plugin - 1.5.3 + 1.6.0 https://github.com/trivago/cucable-plugin Cucable Maven Plugin @@ -80,9 +80,9 @@ 2.0.2 3.9 - 3.0.0 - 5.5.1 - 0.8.12 + 3.2.0 + 5.6.0-M1 + 0.8.13 diff --git a/plugin-code/src/main/java/com/trivago/features/FeatureFileContentRenderer.java b/plugin-code/src/main/java/com/trivago/features/FeatureFileContentRenderer.java index 4d9160c..c12bc95 100644 --- a/plugin-code/src/main/java/com/trivago/features/FeatureFileContentRenderer.java +++ b/plugin-code/src/main/java/com/trivago/features/FeatureFileContentRenderer.java @@ -49,7 +49,11 @@ private String getRenderedFeatureFileContent(List singleScenario for (SingleScenario singleScenario : singleScenarios) { renderedContent.append(LINE_SEPARATOR); - addTags(renderedContent, singleScenario.getScenarioTags()); + List scenarioTags = singleScenario.getScenarioTags(); + if (scenarioTags != null && firstScenario.getFeatureTags() != null) { + scenarioTags.removeAll(firstScenario.getFeatureTags()); + } + addTags(renderedContent, scenarioTags); addTags(renderedContent, singleScenario.getExampleTags()); addNameAndDescription( diff --git a/plugin-code/src/main/java/com/trivago/features/FeatureFileConverter.java b/plugin-code/src/main/java/com/trivago/features/FeatureFileConverter.java index e4267c0..8f4aaea 100644 --- a/plugin-code/src/main/java/com/trivago/features/FeatureFileConverter.java +++ b/plugin-code/src/main/java/com/trivago/features/FeatureFileConverter.java @@ -405,7 +405,6 @@ private int generateRunnerClassesWithDesiredNumberOfFeatures( * @throws CucablePluginException see {@link CucablePluginException}. */ private void generateRunnerClass(final List 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; diff --git a/plugin-code/src/main/java/com/trivago/files/FileIO.java b/plugin-code/src/main/java/com/trivago/files/FileIO.java index 581badd..8dc0c5f 100644 --- a/plugin-code/src/main/java/com/trivago/files/FileIO.java +++ b/plugin-code/src/main/java/com/trivago/files/FileIO.java @@ -38,7 +38,7 @@ public class FileIO { */ public void writeContentToFile(String content, String filePath) throws FileCreationException { try { - FileUtils.fileWrite(filePath, content); + FileUtils.fileWrite(filePath, "UTF-8", content); } catch (IOException e) { throw new FileCreationException(filePath); } @@ -53,7 +53,7 @@ public void writeContentToFile(String content, String filePath) throws FileCreat */ public String readContentFromFile(String filePath) throws MissingFileException { try { - return FileUtils.fileRead(filePath); + return FileUtils.fileRead(filePath, "UTF-8"); } catch (IOException e) { throw new MissingFileException(filePath); } diff --git a/plugin-code/src/main/java/com/trivago/gherkin/GherkinDocumentParser.java b/plugin-code/src/main/java/com/trivago/gherkin/GherkinDocumentParser.java index 8f533f1..60fcedc 100644 --- a/plugin-code/src/main/java/com/trivago/gherkin/GherkinDocumentParser.java +++ b/plugin-code/src/main/java/com/trivago/gherkin/GherkinDocumentParser.java @@ -17,6 +17,7 @@ package com.trivago.gherkin; import com.trivago.exceptions.CucablePluginException; +import com.trivago.logging.CucableLogger; import com.trivago.properties.PropertyManager; import com.trivago.vo.DataTable; import com.trivago.vo.SingleScenario; @@ -24,13 +25,7 @@ import gherkin.AstBuilder; import gherkin.Parser; import gherkin.ParserException; -import gherkin.ast.Background; -import gherkin.ast.Examples; -import gherkin.ast.Feature; -import gherkin.ast.GherkinDocument; -import gherkin.ast.Scenario; -import gherkin.ast.ScenarioDefinition; -import gherkin.ast.ScenarioOutline; +import gherkin.ast.*; import io.cucumber.tagexpressions.Expression; import io.cucumber.tagexpressions.TagExpressionException; import io.cucumber.tagexpressions.TagExpressionParser; @@ -38,6 +33,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.regex.Matcher; @@ -52,16 +48,18 @@ public class GherkinDocumentParser { private final GherkinToCucableConverter gherkinToCucableConverter; private final GherkinTranslations gherkinTranslations; private final PropertyManager propertyManager; + private final CucableLogger cucableLogger; @Inject GherkinDocumentParser( final GherkinToCucableConverter gherkinToCucableConverter, final GherkinTranslations gherkinTranslations, - final PropertyManager propertyManager - ) { + final PropertyManager propertyManager, + final CucableLogger logger) { this.gherkinToCucableConverter = gherkinToCucableConverter; this.gherkinTranslations = gherkinTranslations; this.propertyManager = propertyManager; + this.cucableLogger = logger; } /** @@ -81,6 +79,10 @@ public List getSingleScenariosFromFeature( GherkinDocument gherkinDocument = getGherkinDocumentFromFeatureFileContent(escapedFeatureContent); Feature feature = gherkinDocument.getFeature(); + if (feature == null) { + return Collections.emptyList(); + } + String featureName = feature.getKeyword() + ": " + feature.getName(); String featureLanguage = feature.getLanguage(); String featureDescription = feature.getDescription(); @@ -161,7 +163,6 @@ public List getSingleScenariosFromFeature( * @param featureLanguage The feature language this scenario outline belongs to. * @param featureTags The feature tags of the parent feature. * @param backgroundSteps Return a Cucable {@link SingleScenario} list. - * @throws CucablePluginException Thrown when the scenario outline does not contain an example table. */ private List getSingleScenariosFromOutline( final ScenarioOutline scenarioOutline, @@ -171,7 +172,7 @@ private List getSingleScenariosFromOutline( final String featureDescription, final List featureTags, final List backgroundSteps - ) throws CucablePluginException { + ) { // Retrieve the translation of "Scenario" in the target language and add it to the scenario String translatedScenarioKeyword = gherkinTranslations.getScenarioKeyword(featureLanguage); @@ -185,7 +186,8 @@ private List getSingleScenariosFromOutline( List steps = gherkinToCucableConverter.convertGherkinStepsToCucableSteps(scenarioOutline.getSteps()); if (scenarioOutline.getExamples().isEmpty()) { - throw new CucablePluginException("Scenario outline without examples table!"); + cucableLogger.warn("Scenario outline without example table!"); + return outlineScenarios; } for (Examples exampleTable : scenarioOutline.getExamples()) { @@ -314,7 +316,7 @@ private GherkinDocument getGherkinDocumentFromFeatureFileContent(final String fe } if (gherkinDocument == null || gherkinDocument.getFeature() == null) { - throw new CucablePluginException("Could not parse features from gherkin document!"); + cucableLogger.warn("No parsable gherkin."); } return gherkinDocument; @@ -326,7 +328,7 @@ private GherkinDocument getGherkinDocumentFromFeatureFileContent(final String fe * * @param singleScenario a single scenario object. * @return true if an include tag and no exclude tags are included in the source tag list and scenario name - * (if specified) matches. + * (if specified) matches. */ private boolean scenarioShouldBeIncluded(SingleScenario singleScenario) throws CucablePluginException { @@ -354,10 +356,10 @@ private boolean scenarioShouldBeIncluded(SingleScenario singleScenario) throws C /** * Checks if a scenarioName value matches with the scenario name. * - * @param language Feature file language ("en", "ro" etc). + * @param language Feature file language ("en", "ro" etc). * @param stringToMatch the string that will be matched with the scenarioName value. * @return index of the scenarioName value in the scenarioNames list if a match exists. - * -1 if no match exists. + * -1 if no match exists. */ public int matchScenarioWithScenarioNames(String language, String stringToMatch) { List scenarioNames = propertyManager.getScenarioNames(); diff --git a/plugin-code/src/test/java/com/trivago/gherkin/GherkinDocumentParserTest.java b/plugin-code/src/test/java/com/trivago/gherkin/GherkinDocumentParserTest.java index 42ea097..584f6ff 100644 --- a/plugin-code/src/test/java/com/trivago/gherkin/GherkinDocumentParserTest.java +++ b/plugin-code/src/test/java/com/trivago/gherkin/GherkinDocumentParserTest.java @@ -1,6 +1,7 @@ package com.trivago.gherkin; import com.trivago.exceptions.CucablePluginException; +import com.trivago.logging.CucableLogger; import com.trivago.properties.PropertyManager; import com.trivago.vo.DataTable; import com.trivago.vo.SingleScenario; @@ -15,25 +16,28 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; public class GherkinDocumentParserTest { private GherkinDocumentParser gherkinDocumentParser; private PropertyManager propertyManager; + private CucableLogger mockedLogger; @Before public void setup() { GherkinToCucableConverter gherkinToCucableConverter = new GherkinToCucableConverter(); GherkinTranslations gherkinTranslations = new GherkinTranslations(); propertyManager = mock(PropertyManager.class); - gherkinDocumentParser = new GherkinDocumentParser(gherkinToCucableConverter, gherkinTranslations, propertyManager); + mockedLogger = mock(CucableLogger.class); + gherkinDocumentParser = new GherkinDocumentParser(gherkinToCucableConverter, gherkinTranslations, propertyManager, mockedLogger); } - @Test(expected = CucablePluginException.class) + @Test public void invalidFeatureTest() throws Exception { gherkinDocumentParser.getSingleScenariosFromFeature("", "", null); + verify(mockedLogger, times(1)).warn("No parsable gherkin."); } @Test