From aacfb46878f4207fa6690ed2446f587f4f1a1a37 Mon Sep 17 00:00:00 2001 From: Andrew Sweet Date: Tue, 12 May 2020 14:47:19 +0100 Subject: [PATCH] #154 - distinguish between feature file formatting and description block formatting --- cukedoctor-converter/pom.xml | 17 +++ .../renderer/CukedoctorFeatureRenderer.java | 2 +- .../github/cukedoctor/util/StringUtil.java | 103 ++++++++++++--- .../bdd/cukedoctor/EnrichmentSteps.java | 44 +++++-- .../com/github/cukedoctor/util/MetaCuke.java | 75 +++++++++++ .../cukedoctor/util/StringUtilTests.java | 118 ++++++++++++++++++ .../bdd/cukedoctor/enrichment.feature | 92 ++++++++++++++ cukedoctor-section-layout/pom.xml | 7 ++ .../sectionlayout/bdd/StepDefs.java | 75 +++-------- .../example/CustomRendererTest.java | 14 +-- 10 files changed, 452 insertions(+), 95 deletions(-) create mode 100644 cukedoctor-converter/src/test/java/com/github/cukedoctor/util/MetaCuke.java create mode 100644 cukedoctor-converter/src/test/java/com/github/cukedoctor/util/StringUtilTests.java diff --git a/cukedoctor-converter/pom.xml b/cukedoctor-converter/pom.xml index 41497c78..8f8c3b9a 100644 --- a/cukedoctor-converter/pom.xml +++ b/cukedoctor-converter/pom.xml @@ -67,6 +67,23 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + test-jar + + + + + + + perf diff --git a/cukedoctor-converter/src/main/java/com/github/cukedoctor/renderer/CukedoctorFeatureRenderer.java b/cukedoctor-converter/src/main/java/com/github/cukedoctor/renderer/CukedoctorFeatureRenderer.java index 4ab5f6b7..e29107ae 100644 --- a/cukedoctor-converter/src/main/java/com/github/cukedoctor/renderer/CukedoctorFeatureRenderer.java +++ b/cukedoctor-converter/src/main/java/com/github/cukedoctor/renderer/CukedoctorFeatureRenderer.java @@ -61,7 +61,7 @@ public String renderFeature(Feature feature) { } if (hasText(feature.getDescription())) { - final String description = trimAllLines(feature.getDescription()).replaceAll("\\\\", "").replaceAll("\\n", newLine()); + final String description = trimAllLines(feature.getDescription()).replaceAll("\\\\", ""); renderDescription(builder, description); } diff --git a/cukedoctor-converter/src/main/java/com/github/cukedoctor/util/StringUtil.java b/cukedoctor-converter/src/main/java/com/github/cukedoctor/util/StringUtil.java index 36a05501..b4b14967 100644 --- a/cukedoctor-converter/src/main/java/com/github/cukedoctor/util/StringUtil.java +++ b/cukedoctor-converter/src/main/java/com/github/cukedoctor/util/StringUtil.java @@ -5,31 +5,96 @@ public class StringUtil { + private StringUtil() {} + public static String trimAllLines(String text) { - if (hasText(text)) { - StringBuilder trimmedDescription = new StringBuilder(); - String[] lines = text.split(newLine()); - boolean shouldTrim = true; - for (int i = 0; i < lines.length; i++) { - if (lines[i].trim().startsWith(Constants.Markup.listing())) { - if (shouldTrim) { - shouldTrim = false;//remove trimming on start listing - } else { - shouldTrim = true; //enable trimming on end listing - } + if (!hasText(text)) { + return text; + } + + StringBuilder trimmedDescription = new StringBuilder(); + String[] lines = normaliseLineEndings(text).split(newLine()); + boolean shouldTrim = true; + boolean foundFirstNotEmptyLine = false; + int leadingWhitespaceCharsToTrim = 0; + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + + if (!foundFirstNotEmptyLine) { + if (line != null && !line.isEmpty()) { + leadingWhitespaceCharsToTrim = countLeadingWhitespace(line); + foundFirstNotEmptyLine = true; } + } + + line = trimStart(line, leadingWhitespaceCharsToTrim); + + if (line.trim().startsWith(Constants.Markup.listing())) { if (shouldTrim) { - trimmedDescription.append(lines[i].trim()); + shouldTrim = false;//remove trimming on start listing } else { - trimmedDescription.append(lines[i]); - } - if (i < lines.length - 1) { - trimmedDescription.append(newLine()); + shouldTrim = true; //enable trimming on end listing } } - return trimmedDescription.toString(); + + if (shouldTrim) { + trimmedDescription.append(trimEnd(line)); + } else { + trimmedDescription.append(line); + } + + if (i < lines.length - 1) { + trimmedDescription.append(newLine()); + } } - return text; + + return trimmedDescription.toString(); } -} + static int countLeadingWhitespace(String text) { + if (text == null) return 0; + + int i = 0; + while (i < text.length()) { + char c = text.charAt(i); + if (c != ' ' && c != '\t') { + break; + } + + i += Character.charCount(c); + } + + return i; + } + + static String trimStart(String text, int count) { + if (count < 1) return text; + + if (text == null) return null; + + int i = 0; + while (i < text.length() && i < count) { + char c = text.charAt(i); + if (c != ' ' && c != '\t') { + break; + } + + i += Character.charCount(c); + } + + return text.substring(i); + } + + static String trimEnd(String text) { + if (text == null) return null; + + // https://stackoverflow.com/a/48053234 + return text.replaceFirst("\\s++$", ""); + } + + public static String normaliseLineEndings(String s) { + if (s == null) return null; + + return s.replaceAll("\\r\\n|\\r|\\n", System.lineSeparator()); + } +} \ No newline at end of file diff --git a/cukedoctor-converter/src/test/java/com/github/cukedoctor/bdd/cukedoctor/EnrichmentSteps.java b/cukedoctor-converter/src/test/java/com/github/cukedoctor/bdd/cukedoctor/EnrichmentSteps.java index 4287eddc..23175103 100644 --- a/cukedoctor-converter/src/test/java/com/github/cukedoctor/bdd/cukedoctor/EnrichmentSteps.java +++ b/cukedoctor-converter/src/test/java/com/github/cukedoctor/bdd/cukedoctor/EnrichmentSteps.java @@ -5,9 +5,14 @@ import com.github.cukedoctor.builder.CukedoctorDocumentBuilderImpl; import com.github.cukedoctor.parser.FeatureParser; import com.github.cukedoctor.renderer.CukedoctorFeatureRenderer; +import com.github.cukedoctor.util.MetaCuke; +import io.cucumber.java.After; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; +import java.io.IOException; import java.net.URL; import java.util.List; @@ -17,9 +22,27 @@ * Created by pestano on 11/03/16. */ public class EnrichmentSteps { - + private final MetaCuke metaCuke = new MetaCuke(); String documentation; + public EnrichmentSteps() throws IOException { + } + + @Before + public void before() throws IOException { + metaCuke.setUp(); + } + + @After + public void after() throws IOException { + metaCuke.tearDown(); + } + + @Given("^the feature:$") + public void the_feature(String featureText) throws Throwable { + assertThat(featureText).isNotNull(); + metaCuke.addFeature(featureText); + } @When("^I convert docstring enriched json output activated with a step comment using cukedoctor converter$") public void I_convert_docstring_enriched_json_output_activated_with_a_step_comment_using_cukedoctor_converter() throws Throwable { @@ -41,16 +64,23 @@ public void I_convert_docstring_enriched_json_output_activiated_with_a_scenario_ getFeatureFixture("/json-output/enrichment/table-and-source-scenario-tag.json"); } - @Then("^DocString asciidoc output must be rendered in my documentation$") - public void DocString_asciidoc_output_must_be_rendered_in_my_documentation(String expected) throws Throwable { - assertThat(documentation.replaceAll("\r","")).contains((expected.replaceAll("\r",""))); - } - @When("^I convert enriched feature json output using cukedoctor$") public void I_convert_enriched_feature_json_output_using_cukedoctor() throws Throwable { getFeatureFixture("/json-output/enrichment/calc.json"); } + @When("^I convert it$") + public void I_convert_it() { + metaCuke.runCucumber("com.care.dont"); + List features = FeatureParser.parse(metaCuke.getReport().getAbsolutePath()); + documentation = new CukedoctorFeatureRenderer((DocumentAttributes) null).renderFeatures(features, new CukedoctorDocumentBuilderImpl().createNestedBuilder()); + } + + @Then("^DocString asciidoc output must be rendered in my documentation$") + @Then("^it should be rendered in AsciiDoc as$") + public void DocString_asciidoc_output_must_be_rendered_in_my_documentation(String expected) throws Throwable { + assertThat(documentation.replaceAll("\r","")).contains((expected.replaceAll("\r",""))); + } private void getFeatureFixture(String fixturePath) { URL featureFile = getClass().getResource(fixturePath); @@ -59,4 +89,4 @@ private void getFeatureFixture(String fixturePath) { assertThat(features).isNotNull().hasSize(1); documentation = new CukedoctorFeatureRenderer((DocumentAttributes) null).renderFeatures(features, new CukedoctorDocumentBuilderImpl().createNestedBuilder()); } -} +} \ No newline at end of file diff --git a/cukedoctor-converter/src/test/java/com/github/cukedoctor/util/MetaCuke.java b/cukedoctor-converter/src/test/java/com/github/cukedoctor/util/MetaCuke.java new file mode 100644 index 00000000..0a4dd066 --- /dev/null +++ b/cukedoctor-converter/src/test/java/com/github/cukedoctor/util/MetaCuke.java @@ -0,0 +1,75 @@ +package com.github.cukedoctor.util; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.LinkedList; + +public class MetaCuke { + private final LinkedList featureFiles = new LinkedList<>(); + private File featureDirectory; + private File reportFile; + + public MetaCuke() throws IOException { + featureDirectory = Files.createTempDirectory("Features").toFile(); + featureDirectory.deleteOnExit(); + } + + public void setUp() throws IOException { + featureFiles.clear(); + reportFile = createTempFile("Report", ".json"); + } + + public void addFeature(String featureText) throws IOException { + File featureFile = createTempFile("Scenario", ".feature"); + writeFeatureToFile(featureText, featureFile); + featureFiles.add(featureFile); + } + + public void runCucumber(Class stepDefs) { + runCucumber(stepDefs.getPackage().getName()); + } + + public void runCucumber(String stepDefinitionPath) { + io.cucumber.core.cli.Main.run( + new String[]{ + "--glue", + stepDefinitionPath, + // The below lines are helpful for debugging, but severely confuse general test output + // "--plugin", + // "pretty", + "--plugin", + "json:" + reportFile.getAbsolutePath(), + featureDirectory.getAbsolutePath(), + }, + Thread.currentThread().getContextClassLoader()); + } + + public File getReport() { + return reportFile; + } + + public void tearDown() throws IOException { + for (File feature : featureFiles) { + Files.deleteIfExists(feature.toPath()); + } + + Files.deleteIfExists(reportFile.toPath()); + } + + private File createTempFile(String prefix, String suffix) throws IOException { + File tempFile = File.createTempFile(prefix, suffix, featureDirectory); + tempFile.deleteOnExit(); + return tempFile; + } + + private static void writeFeatureToFile(String featureText, File featureFile) throws IOException { + FileWriter fileWriter = new FileWriter(featureFile); + BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); + bufferedWriter.write(featureText); + bufferedWriter.close(); + fileWriter.close(); + } +} diff --git a/cukedoctor-converter/src/test/java/com/github/cukedoctor/util/StringUtilTests.java b/cukedoctor-converter/src/test/java/com/github/cukedoctor/util/StringUtilTests.java new file mode 100644 index 00000000..50718935 --- /dev/null +++ b/cukedoctor-converter/src/test/java/com/github/cukedoctor/util/StringUtilTests.java @@ -0,0 +1,118 @@ +package com.github.cukedoctor.util; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static java.lang.System.lineSeparator; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(JUnit4.class) +public class StringUtilTests { + + @Test + public void shouldTrimEndOfNullString() { + assertNull(StringUtil.trimEnd(null)); + } + + @Test + public void shouldTrimEndOfEmptyString() { + assertEquals("", StringUtil.trimEnd("")); + } + + @Test + public void shouldTrimEndOfStringWithNoTrailingWhitespace() { + assertEquals(" \t my string", StringUtil.trimEnd(" \t my string")); + } + + @Test + public void shouldTrimEndOfStringWithTrailingWhitespace() { + assertEquals("my string", StringUtil.trimEnd("my string \t \t" + lineSeparator())); + } + + + @Test + public void shouldCountLeadingWhitespaceOfNullString() { + assertEquals(0, StringUtil.countLeadingWhitespace(null)); + } + + @Test + public void shouldCountLeadingWhitespaceOfEmptyString() { + assertEquals(0, StringUtil.countLeadingWhitespace("")); + } + + @Test + public void shouldCountLeadingWhitespaceOfStringWithNoLeadingWhitespace() { + assertEquals(0, StringUtil.countLeadingWhitespace("my string \t \t")); + } + + @Test + public void shouldCountLeadingWhitespace() { + assertEquals(3, StringUtil.countLeadingWhitespace("\t my string")); + } + + + @Test + public void shouldTrimStartOfNullString() { + assertNull(StringUtil.trimStart(null, 42)); + } + + @Test + public void shouldTrimStartOfEmptyString() { + assertEquals("", StringUtil.trimStart("", 42)); + } + + @Test + public void shouldTrimStartOfStringWhereThereIsNoLeadingWhitepace() { + assertEquals("my string \t \t", StringUtil.trimStart("my string \t \t", 3)); + } + + @Test + public void shouldTrimStartOfStringWhereCountMatchesLeadingWhitespace() { + assertEquals("my string", StringUtil.trimStart("\t my string", 3)); + } + + @Test + public void shouldTrimStartOfStringWhereCounIsLessThanLeadingWhitespace() { + assertEquals(" my string", StringUtil.trimStart("\t my string", 2)); + } + + @Test + public void shouldTrimStartOfStringWhereCountIsGreaterThanLeadingWhitespace() { + assertEquals("my string", StringUtil.trimStart("\t my string", 4)); + } + + + + @Test + public void shouldTrimAllLines() { + final String fixture = " \t \tThis is the description for Feature One. The first non-blank line of this description in the feature file began with four whitespace characters." + lineSeparator() + + " \t Therefore, cukedoctor will ignore up to the first four" + lineSeparator() + + "whitespace characters" + lineSeparator() + + " in all other lines in the same description," + lineSeparator() + + " if any are present." + lineSeparator() + + "" + lineSeparator() + + "" + lineSeparator() + + "This includes" + lineSeparator() + + " further lines" + lineSeparator() + + " in a different" + lineSeparator() + + " paragraph" + lineSeparator() + + " \tin the same description."; + + final String expected = "This is the description for Feature One. The first non-blank line of this description in the feature file began with four whitespace characters." + lineSeparator() + + "\t Therefore, cukedoctor will ignore up to the first four" + lineSeparator() + + "whitespace characters" + lineSeparator() + + "in all other lines in the same description," + lineSeparator() + + "if any are present." + lineSeparator() + + "" + lineSeparator() + + "" + lineSeparator() + + "This includes" + lineSeparator() + + "further lines" + lineSeparator() + + "in a different" + lineSeparator() + + " paragraph" + lineSeparator() + + " \tin the same description."; + + assertEquals(expected, StringUtil.trimAllLines(fixture)); + } +} \ No newline at end of file diff --git a/cukedoctor-converter/src/test/resources/com/github/cukedoctor/bdd/cukedoctor/enrichment.feature b/cukedoctor-converter/src/test/resources/com/github/cukedoctor/bdd/cukedoctor/enrichment.feature index eca81fd6..0b4d20f0 100644 --- a/cukedoctor-converter/src/test/resources/com/github/cukedoctor/bdd/cukedoctor/enrichment.feature +++ b/cukedoctor-converter/src/test/resources/com/github/cukedoctor/bdd/cukedoctor/enrichment.feature @@ -287,3 +287,95 @@ the following table icon:thumbs-up[role="green",title="Passed"] [small right]#(0 """ + + + Scenario: Whitespace in descriptions + Features and Scenarios can have multi-line descriptions. In a feature file, these may be indented. + Cukedoctor uses the indentation of the first line non-blank line of the description to determine the difference + between the indentation _of_ the description in a feature file and your desired indentation _within_ the the + description itself. + + Given the feature: +""" + Feature: Feature One + + + This is the description for Feature One. The first non-blank line of this description in the feature file began with four whitespace characters. + Therefore, cukedoctor will ignore up to the first four +whitespace characters + in all other lines in the same description, + if any are present. + + +This includes + further lines + in a different + paragraph + in the same description. + + + + Scenario: Scenario One + + This is the description for Scenario One. The first non-blank line of this description in the feature file began with four whitespace characters. + Therefore, cukedoctor will ignore up to the first four +whitespace characters + in all other lines in the same description, + if any are present. + + +This includes + further lines + in a different + paragraph + in the same description. + + + + Scenario: Scenario Two +This scenario has no indentation. You don't have to use it, after all. + Indentation in subsequent lines is therefore fully preserved. + +""" + When I convert it + Then it should be rendered in AsciiDoc as +"""asciidoc +== *Features* + +[[Feature-One, Feature One]] +=== *Feature One* + +**** +This is the description for Feature One. The first non-blank line of this description in the feature file began with four whitespace characters. + Therefore, cukedoctor will ignore up to the first four +whitespace characters +in all other lines in the same description, +if any are present. + + +This includes +further lines +in a different + paragraph + in the same description. +**** + +==== Scenario: Scenario One +This is the description for Scenario One. The first non-blank line of this description in the feature file began with four whitespace characters. + Therefore, cukedoctor will ignore up to the first four +whitespace characters +in all other lines in the same description, +if any are present. + + +This includes +further lines +in a different + paragraph + in the same description. + +==== Scenario: Scenario Two +This scenario has no indentation. You don't have to use it, after all. + Indentation in subsequent lines is therefore fully preserved. + +""" \ No newline at end of file diff --git a/cukedoctor-section-layout/pom.xml b/cukedoctor-section-layout/pom.xml index ba152cf2..57aa313a 100644 --- a/cukedoctor-section-layout/pom.xml +++ b/cukedoctor-section-layout/pom.xml @@ -15,6 +15,13 @@ cukedoctor-converter ${project.parent.version} + + com.github.cukedoctor + cukedoctor-converter + ${project.parent.version} + test-jar + test + com.github.cukedoctor cukedoctor-main diff --git a/cukedoctor-section-layout/src/test/java/com/github/cukedoctor/sectionlayout/bdd/StepDefs.java b/cukedoctor-section-layout/src/test/java/com/github/cukedoctor/sectionlayout/bdd/StepDefs.java index c4535253..89da7f24 100644 --- a/cukedoctor-section-layout/src/test/java/com/github/cukedoctor/sectionlayout/bdd/StepDefs.java +++ b/cukedoctor-section-layout/src/test/java/com/github/cukedoctor/sectionlayout/bdd/StepDefs.java @@ -9,59 +9,47 @@ import com.github.cukedoctor.config.CukedoctorConfig; import com.github.cukedoctor.parser.FeatureParser; import com.github.cukedoctor.sectionlayout.SectionFeatureRenderer; +import com.github.cukedoctor.sectionlayout.bdd.inception.InceptionStepDefs; +import com.github.cukedoctor.util.MetaCuke; +import com.github.cukedoctor.util.StringUtil; import io.cucumber.java.Before; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import org.junit.After; -import java.io.BufferedWriter; import java.io.File; -import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; -import java.util.LinkedList; import java.util.List; import static org.junit.Assert.assertEquals; public class StepDefs { + private final MetaCuke metaCuke = new MetaCuke(); private final File introDirectory; - private final File tempDirectory; - private final LinkedList featureFilePaths = new LinkedList<>(); - private Path reportFilePath; private String renderedDocument; public StepDefs() throws IOException { introDirectory = Files.createTempDirectory("Intro").toFile(); introDirectory.deleteOnExit(); - - tempDirectory = Files.createTempDirectory("Features").toFile(); - tempDirectory.deleteOnExit(); } @Before public void before() throws IOException { - featureFilePaths.clear(); - reportFilePath = createTempFile("Report", ".json").toPath(); + metaCuke.setUp(); renderedDocument = null; } @After public void after() throws IOException { - for (Path path : featureFilePaths) { - Files.deleteIfExists(path); - } - - Files.deleteIfExists(reportFilePath); + metaCuke.tearDown(); } - @Given("I have the Feature") public void i_have_the_feature(String featureText) throws IOException { - featureFilePaths.add(createFeatureFile(featureText)); + metaCuke.addFeature(featureText); } @Given("I am hiding the Features Section") @@ -124,52 +112,16 @@ public void they_will_be_rendered_as(String expectedDocument) { assetDocumentEquals(expectedDocument); } - - private File createTempFile(String prefix, String suffix) throws IOException { - File tempFile = File.createTempFile(prefix, suffix, tempDirectory); - tempFile.deleteOnExit(); - return tempFile; - } - - private Path createFeatureFile(String featureText) throws IOException { - File featureFile = createTempFile("Scenario", ".feature"); - writeFeatureToFile(featureText, featureFile); - return featureFile.toPath(); - } - - private static void writeFeatureToFile(String featureText, File featureFile) throws IOException { - FileWriter fileWriter = new FileWriter(featureFile); - BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); - bufferedWriter.write(featureText); - bufferedWriter.close(); - fileWriter.close(); - } - private void runCucumber() { - io.cucumber.core.cli.Main.run( - new String[]{ - "--glue", - "com/github/cukedoctor/sectionlayout/bdd/inception", - // The below lines are helpful for debugging, but severely confuse general test output - // "--plugin", - // "pretty", - "--plugin", - "json:" + reportFilePath, - tempDirectory.getAbsolutePath(), - }, - Thread.currentThread().getContextClassLoader()); + metaCuke.runCucumber(InceptionStepDefs.class); } public void assetDocumentEquals(String expectedDocument) { - assertEquals(normaliseLineEndings(expectedDocument), normaliseLineEndings(renderedDocument)); - } - - private static String normaliseLineEndings(String s) { - return s.replaceAll("\r\n|\r|\n", System.lineSeparator()); + assertEquals(StringUtil.normaliseLineEndings(expectedDocument), StringUtil.normaliseLineEndings(renderedDocument)); } public List parseFeatures() { - return FeatureParser.parse(reportFilePath.toAbsolutePath().toString()); + return FeatureParser.parse(metaCuke.getReport().getAbsolutePath()); } public void renderFeatures(List features) { @@ -193,7 +145,8 @@ private void setAllStepDurationsToZero(List features) { private CukedoctorConfig getConfig() { return new CukedoctorConfig().setHideSummarySection(true) - .setIntroChapterDir(introDirectory.getAbsolutePath())// Avoid picking up intro docs in source tree - .setIntroChapterRelativePath(introDirectory.getAbsolutePath()); + .setIntroChapterDir(introDirectory.getAbsolutePath())// Avoid picking up intro docs in source tree + .setIntroChapterRelativePath(introDirectory.getAbsolutePath()); } -} \ No newline at end of file +} + diff --git a/cukedoctor-spi-example/src/test/java/com/github/cukedoctor/example/CustomRendererTest.java b/cukedoctor-spi-example/src/test/java/com/github/cukedoctor/example/CustomRendererTest.java index d32ecb15..32b5c8b7 100644 --- a/cukedoctor-spi-example/src/test/java/com/github/cukedoctor/example/CustomRendererTest.java +++ b/cukedoctor-spi-example/src/test/java/com/github/cukedoctor/example/CustomRendererTest.java @@ -121,13 +121,13 @@ public void shouldRenderDocumentationUsingCustomRenderers(){ "I should have 0 as result icon:thumbs-down[role=\"red\",title=\"Failed\"] [small right]#(000ms)#"+newLine()+ ""+newLine()+ "IMPORTANT: java.lang.AssertionError: expected:<0> but was:<1>"+newLine()+ - "at org.junit.Assert.fail(Assert.java:88)"+newLine()+ - "at org.junit.Assert.failNotEquals(Assert.java:743)"+newLine()+ - "at org.junit.Assert.assertEquals(Assert.java:118)"+newLine()+ - "at org.junit.Assert.assertEquals(Assert.java:555)"+newLine()+ - "at org.junit.Assert.assertEquals(Assert.java:542)"+newLine()+ - "at com.github.cukedoctor.example.bdd.CalcStepDef.I_should_have_result(CalcStepDef.java:37)"+newLine()+ - "at ✽.Then I should have 0 as result(src/test/resources/features/calc.feature:14)"+newLine()+ + "\tat org.junit.Assert.fail(Assert.java:88)"+newLine()+ + "\tat org.junit.Assert.failNotEquals(Assert.java:743)"+newLine()+ + "\tat org.junit.Assert.assertEquals(Assert.java:118)"+newLine()+ + "\tat org.junit.Assert.assertEquals(Assert.java:555)"+newLine()+ + "\tat org.junit.Assert.assertEquals(Assert.java:542)"+newLine()+ + "\tat com.github.cukedoctor.example.bdd.CalcStepDef.I_should_have_result(CalcStepDef.java:37)"+newLine()+ + "\tat ✽.Then I should have 0 as result(src/test/resources/features/calc.feature:14)"+newLine()+ ""+newLine()+ "=========="+newLine()+ ""+newLine()+