From 2c1f8e9878cd17f3a966ec5084a53d90d271207b Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:41:21 +0100 Subject: [PATCH 01/15] Enclose scenarios with AsciiDoc tags These will be used prepare a hierarchical tag overview. Issue: #54 Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../jgiven/report/ReportBlockConverter.java | 45 +++--- .../asciidoc/AsciiDocBlockConverter.java | 22 ++- .../asciidoc/AsciiDocReportModelVisitor.java | 6 +- .../AsciiDocReportModelVisitorTest.java | 4 +- .../AsciiDocScenarioBlockConverterTest.java | 144 +++++++++++------- 5 files changed, 133 insertions(+), 88 deletions(-) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java index 88bb8b0fcfd..011ced84e4d 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java @@ -39,13 +39,13 @@ String convertStatisticsBlock(ListMultimap featureStat *

* * @param name the name of the scenario - * @param executionStatus was the scenario successful - * @param duration how long did the scenario run - * @param tagNames names of the tags if scenario is tagged + * @param executionStatus was the scenario successful? + * @param duration how long did the scenario run? + * @param tags tags the scenario is tagged with * @param extendedDescription detailed description of the scenario, may be {@code null} */ String convertScenarioHeaderBlock(String name, ExecutionStatus executionStatus, long duration, - List tagNames, String extendedDescription); + List tags, String extendedDescription); /** * Convert scenario case number and parameters into case header. @@ -54,7 +54,7 @@ String convertScenarioHeaderBlock(String name, ExecutionStatus executionStatus, * * @param caseNr the number of the case, starting from 1 * @param executionStatus whether the case was successful - * @param duration how long did the scenario case run + * @param duration how long did the scenario case run? * @param description a short description of this case, may be {@code null} */ String convertCaseHeaderBlock(int caseNr, ExecutionStatus executionStatus, final long duration, String description); @@ -62,29 +62,29 @@ String convertScenarioHeaderBlock(String name, ExecutionStatus executionStatus, /** * Convert the words that make up the first step into a block. * - * @param depth the depth of the step - * @param words the words to be converted - * @param status was the step executed successfully - * @param durationInNanos how long did the step take - * @param extendedDescription detailed description of the step, may be {@code null} - * @param caseIsUnsuccessful was the scenario case executed successfully - * @param currentSectionTitle the current section's title, may be {@code null} + * @param depth the depth of the step + * @param words the words to be converted + * @param status was the step executed successfully + * @param durationInNanos how long did the step take? + * @param extendedDescription detailed description of the step, may be {@code null} + * @param caseIsUnsuccessful was the scenario case executed successfully? + * @param currentSectionTitle the current section's title, may be {@code null} */ String convertFirstStepBlock(int depth, List words, StepStatus status, long durationInNanos, - String extendedDescription, boolean caseIsUnsuccessful, String currentSectionTitle); + String extendedDescription, boolean caseIsUnsuccessful, String currentSectionTitle); /** * Convert the words that make up a step into a block. * - * @param depth the depth of the step - * @param words the words to be converted - * @param status was the step executed successfully - * @param durationInNanos how long did the step take - * @param extendedDescription detailed description of the step, may be {@code null} - * @param caseIsUnsuccessful was the scenario case executed successfully + * @param depth the depth of the step + * @param words the words to be converted + * @param status was the step executed successfully + * @param durationInNanos how long did the step take? + * @param extendedDescription detailed description of the step, may be {@code null} + * @param caseIsUnsuccessful was the scenario case executed successfully? */ String convertStepBlock(int depth, List words, StepStatus status, long durationInNanos, - String extendedDescription, boolean caseIsUnsuccessful); + String extendedDescription, boolean caseIsUnsuccessful); /** * Is invoked at the end of a scenario, when the scenario has multiple case and a data table. @@ -104,8 +104,9 @@ String convertStepBlock(int depth, List words, StepStatus status, long dur /** * Is invoked at the end of a scenario. * - * @param executionStatus was the scenario successful + * @param executionStatus was the scenario successful? + * @param tags tags the scenario is tagged with */ - String convertScenarioFooterBlock(ExecutionStatus executionStatus); + String convertScenarioFooterBlock(ExecutionStatus executionStatus, List tags); } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java index 6b9bcac8f1c..d19a4205650 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java @@ -4,6 +4,7 @@ import static java.util.stream.Stream.generate; import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; import com.tngtech.jgiven.impl.util.WordUtil; import com.tngtech.jgiven.report.ReportBlockConverter; import com.tngtech.jgiven.report.model.CasesTable; @@ -87,7 +88,10 @@ public String convertScenarioHeaderBlock(final String name, final ExecutionStatu final String extendedDescription) { StringBuilder blockContent = new StringBuilder(); - blockContent.append(MetadataMapper.toAsciiDocTagStart(executionStatus)).append(LINE_BREAK); + blockContent.append(MetadataMapper.toAsciiDocStartTag(executionStatus)).append(LINE_BREAK); + + tags.forEach(tag -> blockContent.append(TagMapper.toAsciiDocStartTag(tag)).append(LINE_BREAK)); + blockContent.append(LINE_BREAK); blockContent.append("==== ").append(WordUtil.capitalize(name)).append(LINE_BREAK); @@ -107,7 +111,7 @@ public String convertScenarioHeaderBlock(final String name, final ExecutionStatu blockContent.append(LINE_BREAK); blockContent.append(LINE_BREAK); blockContent.append("Tags: "); - blockContent.append(tags.stream().map(tag -> "_" + TagMapper.mapTag(tag) + "_").collect(joining(", "))); + blockContent.append(tags.stream().map(tag -> "_" + TagMapper.toHumanReadableLabel(tag) + "_").collect(joining(", "))); } return blockContent.toString(); @@ -231,8 +235,14 @@ public String convertCaseFooterBlock(final String errorMessage, final List tags) { + StringBuilder blockContent = new StringBuilder(); + + Lists.reverse(tags).forEach(tag -> blockContent.append(TagMapper.toAsciiDocEndTag(tag)).append(LINE_BREAK)); + + blockContent.append(MetadataMapper.toAsciiDocEndTag(executionStatus)); + + return blockContent.toString(); } private static boolean appendWordFragments(final StringBuilder blockContent, final List words, @@ -285,8 +295,8 @@ private static boolean appendFragment(final StringBuilder blockContent, final bo final String fragment) { final String lineContinuation = lastFragmentWasBlockFragment ? "" : " "; if (fragment.contains(LINE_BREAK)) { - if (!statusAlreadyAppended && !statusFragment.isBlank()){ - blockContent.append(" ").append(statusFragment); + if (!statusAlreadyAppended && !statusFragment.isBlank()) { + blockContent.append(" ").append(statusFragment); } blockContent.append(fragment); return true; diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java index 32b444952c7..351e2a5bdcb 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java @@ -131,7 +131,11 @@ public void visitEnd(final ScenarioModel scenarioModel) { asciiDocBlocks.add(casesTableBlock); } - String scenarioFooter = blockConverter.convertScenarioFooterBlock(scenarioModel.getExecutionStatus()); + final List tags = scenarioModel.getTagIds().stream() + .map(this.featureTagMap::get) + .collect(Collectors.toList()); + + String scenarioFooter = blockConverter.convertScenarioFooterBlock(scenarioModel.getExecutionStatus(), tags); asciiDocBlocks.add(scenarioFooter); } diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java index 24f85e26422..374cb70534d 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java @@ -256,7 +256,7 @@ public String convertFeatureHeaderBlock(String featureName, ReportStatistics sta @Override public String convertScenarioHeaderBlock(String name, ExecutionStatus executionStatus, long duration, - List tagNames, String extendedDescription) { + List tags, String extendedDescription) { return "ScenarioHeaderBlock"; } @@ -290,7 +290,7 @@ public String convertCaseFooterBlock(final String errorMessage, final List tags) { return "ScenarioFooterBlock"; } } diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocScenarioBlockConverterTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocScenarioBlockConverterTest.java index 7496c052914..21a80e5152d 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocScenarioBlockConverterTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocScenarioBlockConverterTest.java @@ -6,7 +6,6 @@ import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.jgiven.report.model.ExecutionStatus; import com.tngtech.jgiven.report.model.Tag; -import java.util.ArrayList; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -18,134 +17,165 @@ public class AsciiDocScenarioBlockConverterTest { @Test @DataProvider({"SUCCESS, successful, icon:check-square[role=green]", - "FAILED, failed, icon:exclamation-circle[role=red]", - "SCENARIO_PENDING, pending, icon:ban[role=silver]", - "SOME_STEPS_PENDING, pending, icon:ban[role=silver]"}) + "FAILED, failed, icon:exclamation-circle[role=red]", + "SCENARIO_PENDING, pending, icon:ban[role=silver]", + "SOME_STEPS_PENDING, pending, icon:ban[role=silver]"}) public void convert_scenario_header_without_tags_or_description(final ExecutionStatus status, final String scenarioTag, final String humanStatus) { // given - List tagNames = new ArrayList<>(); - final long oneSecond = 1_000_000_000L; + List tags = List.of(); + long oneSecond = 1_000_000_000L; // when - String block = converter.convertScenarioHeaderBlock("my first scenario", status, oneSecond, tagNames, null); + String block = converter.convertScenarioHeaderBlock("my first scenario", status, oneSecond, tags, null); // then assertThatBlockContainsLines(block, - "// tag::scenario-" + scenarioTag + "[]", - "", - "==== My first scenario", - "", - humanStatus + " (1s 0ms)"); + "// tag::scenario-" + scenarioTag + "[]", + "", + "==== My first scenario", + "", + humanStatus + " (1s 0ms)"); } @Test public void convert_scenario_header_with_a_tag_and_no_description() { // given - List tagNames = new ArrayList<>(); - tagNames.add(mkTag("Best Tag")); - final long nineMilliseconds = 10_000_000L; + List tags = List.of(mkTag("BestTag")); + long nineMilliseconds = 10_000_000L; // when String block = converter.convertScenarioHeaderBlock("my first scenario", - ExecutionStatus.SCENARIO_PENDING, nineMilliseconds, tagNames, ""); + ExecutionStatus.SCENARIO_PENDING, nineMilliseconds, tags, ""); // then assertThatBlockContainsLines(block, - "// tag::scenario-pending[]", - "", - "==== My first scenario", - "", - "icon:ban[role=silver] (10ms)", - "", - "Tags: _[.jg-tag-ArbitraryTag]#Best Tag#_"); + "// tag::scenario-pending[]", + "// tag::com.jgiven.ArbitraryTag-BestTag[]", + "", + "==== My first scenario", + "", + "icon:ban[role=silver] (10ms)", + "", + "Tags: _[.jg-tag-ArbitraryTag]#BestTag#_"); } @Test public void convert_scenario_header_with_description_and_no_tags() { // given - List tagNames = new ArrayList<>(); - final long halfMillisecond = 500_000L; + List tags = List.of(); + long halfMillisecond = 500_000L; // when String block = converter.convertScenarioHeaderBlock("my first scenario", - ExecutionStatus.SOME_STEPS_PENDING, halfMillisecond, tagNames, "Best scenario ever!!!"); + ExecutionStatus.SOME_STEPS_PENDING, halfMillisecond, tags, "Best scenario ever!!!"); // then assertThatBlockContainsLines(block, - "// tag::scenario-pending[]", - "", - "==== My first scenario", - "", - "icon:ban[role=silver] (0ms)", - "", - "+++Best scenario ever!!!+++"); + "// tag::scenario-pending[]", + "", + "==== My first scenario", + "", + "icon:ban[role=silver] (0ms)", + "", + "+++Best scenario ever!!!+++"); } @Test public void convert_scenario_header_with_a_tag_and_description() { // given - List tagNames = new ArrayList<>(); - tagNames.add(mkTag("Best Tag")); - final long threeSeconds = 3_000_000_000L; + List tags = List.of(mkTag("BestTag")); + long threeSeconds = 3_000_000_000L; // when String block = - converter.convertScenarioHeaderBlock("my first scenario", ExecutionStatus.SUCCESS, threeSeconds, tagNames, - "Best scenario ever!!!"); + converter.convertScenarioHeaderBlock("my first scenario", ExecutionStatus.SUCCESS, threeSeconds, tags, + "Best scenario ever!!!"); // then assertThatBlockContainsLines(block, - "// tag::scenario-successful[]", - "", - "==== My first scenario", - "", - "icon:check-square[role=green] (3s 0ms)", - "", - "+++Best scenario ever!!!+++", - "", - "Tags: _[.jg-tag-ArbitraryTag]#Best Tag#_"); + "// tag::scenario-successful[]", + "// tag::com.jgiven.ArbitraryTag-BestTag[]", + "", + "==== My first scenario", + "", + "icon:check-square[role=green] (3s 0ms)", + "", + "+++Best scenario ever!!!+++", + "", + "Tags: _[.jg-tag-ArbitraryTag]#BestTag#_"); } @Test public void convert_scenario_header_with_multiple_tags() { // given - List tagNames = new ArrayList<>(); - tagNames.add(mkTag("Best Tag")); - tagNames.add(mkTag("Other Tag")); - tagNames.add(mkTag("Nicest Tag")); - final long threeSeconds = 3_000_000_000L; + List tags = List.of(mkTag("BestTag"), mkTag("OtherTag"), mkTag("NicestTag")); + long threeSeconds = 3_000_000_000L; // when String block = converter.convertScenarioHeaderBlock("my first scenario", ExecutionStatus.SUCCESS, threeSeconds, - tagNames, ""); + tags, ""); // then assertThatBlockContainsLines(block, "// tag::scenario-successful[]", + "// tag::com.jgiven.ArbitraryTag-BestTag[]", + "// tag::com.jgiven.ArbitraryTag-OtherTag[]", + "// tag::com.jgiven.ArbitraryTag-NicestTag[]", "", "==== My first scenario", "", "icon:check-square[role=green] (3s 0ms)", "", - "Tags: _[.jg-tag-ArbitraryTag]#Best Tag#_, _[.jg-tag-ArbitraryTag]#Other Tag#_, " - + "_[.jg-tag-ArbitraryTag]#Nicest Tag#_"); + "Tags: _[.jg-tag-ArbitraryTag]#BestTag#_, _[.jg-tag-ArbitraryTag]#OtherTag#_, " + + "_[.jg-tag-ArbitraryTag]#NicestTag#_"); } @Test @DataProvider({"SUCCESS, successful", "FAILED, failed", "SCENARIO_PENDING, pending", "SOME_STEPS_PENDING, pending"}) - public void convert_scenario_footer(final ExecutionStatus status, final String scenarioTag) { + public void convert_scenario_footer_without_tags(final ExecutionStatus status, final String scenarioTag) { // given + List tags = List.of(); // when - String block = converter.convertScenarioFooterBlock(status); + String block = converter.convertScenarioFooterBlock(status, tags); // then assertThat(block).isEqualTo("// end::scenario-" + scenarioTag + "[]"); } + @Test + public void convert_scenario_footer_with_a_tag() { + // given + List tags = List.of(mkTag("BestTag")); + + // when + String block = converter.convertScenarioFooterBlock(ExecutionStatus.FAILED, tags); + + // then + assertThatBlockContainsLines(block, + "// end::com.jgiven.ArbitraryTag-BestTag[]", + "// end::scenario-failed[]"); + } + + @Test + public void convert_scenario_footer_with_multiple_tags() { + // given + List tags = List.of(mkTag("BestTag"), mkTag("OtherTag"), mkTag("NicestTag")); + + // when + String block = converter.convertScenarioFooterBlock(ExecutionStatus.FAILED, tags); + + // then + assertThatBlockContainsLines(block, + "// end::com.jgiven.ArbitraryTag-NicestTag[]", + "// end::com.jgiven.ArbitraryTag-OtherTag[]", + "// end::com.jgiven.ArbitraryTag-BestTag[]", + "// end::scenario-failed[]"); + } + private static Tag mkTag(final String value) { final Tag tag = new Tag("com.jgiven.ArbitraryTag", value); tag.setType("ArbitraryTag"); From dcc2273206c3057cf5d03609f92aef75307db66d Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:44:19 +0100 Subject: [PATCH 02/15] Simplify test to ensure both snippets are compatible Issue: #54 Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../report/asciidoc/MetadataMapper.java | 8 +++--- .../report/asciidoc/MetadataMapperTest.java | 25 ++++++------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/MetadataMapper.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/MetadataMapper.java index 2ce10917916..41f35fa143f 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/MetadataMapper.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/MetadataMapper.java @@ -12,18 +12,18 @@ final class MetadataMapper { private static final int NANOSECONDS_PER_MILLISECOND = 1000000; private MetadataMapper() { - // static helper class not intended to be instantiated + // static helper class isn't intended to be instantiated } - static String toAsciiDocTagStart(ExecutionStatus executionStatus) { + static String toAsciiDocStartTag(ExecutionStatus executionStatus) { return "// tag::" + toAsciiDocTagName(executionStatus) + "[]"; } - static String toAsciiDocTagEnd(ExecutionStatus executionStatus) { + static String toAsciiDocEndTag(ExecutionStatus executionStatus) { return "// end::" + toAsciiDocTagName(executionStatus) + "[]"; } - static String toAsciiDocTagName(final ExecutionStatus executionStatus) { + private static String toAsciiDocTagName(final ExecutionStatus executionStatus) { switch (executionStatus) { case SCENARIO_PENDING: case SOME_STEPS_PENDING: diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/MetadataMapperTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/MetadataMapperTest.java index 92fe07804bb..a7aaf01b326 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/MetadataMapperTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/MetadataMapperTest.java @@ -14,21 +14,6 @@ */ @RunWith(DataProviderRunner.class) public class MetadataMapperTest { - - @Test - public void toAsciiDocTagStart() { - final String actualName = MetadataMapper.toAsciiDocTagStart(ExecutionStatus.SUCCESS); - - assertThat(actualName).isEqualTo("// tag::scenario-successful[]"); - } - - @Test - public void toAsciiDocTagEnd() { - final String actualName = MetadataMapper.toAsciiDocTagEnd(ExecutionStatus.SUCCESS); - - assertThat(actualName).isEqualTo("// end::scenario-successful[]"); - } - @Test @DataProvider({ "SUCCESS, scenario-successful", @@ -36,9 +21,13 @@ public void toAsciiDocTagEnd() { "SCENARIO_PENDING, scenario-pending", "SOME_STEPS_PENDING, scenario-pending"}) public void toAsciiDocTagName(final ExecutionStatus executionStatus, final String expectedName) { - final String actualName = MetadataMapper.toAsciiDocTagName(executionStatus); + // when + final String startSnippet = MetadataMapper.toAsciiDocStartTag(executionStatus); + final String endSnippet = MetadataMapper.toAsciiDocEndTag(executionStatus); - assertThat(actualName).isEqualTo(expectedName); + // then + assertThat(startSnippet).isEqualTo("// tag::" + expectedName + "[]"); + assertThat(endSnippet).isEqualTo("// end::" + expectedName + "[]"); } @Test @@ -86,7 +75,7 @@ public void toScenarioDurationForDurationOver1ms(final long nanoseconds, final S } @Test - public void toStepDurationBelow1ms() { + public void toStepDurationBelow10ms() { final String actualDuration = MetadataMapper.toHumanReadableStepDuration(9_999_999); assertThat(actualDuration).isEmpty(); From 240daf572b5dc696f9f5fc2988e73e4e8a294621 Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:09:17 +0100 Subject: [PATCH 03/15] Add mapping JGiven tags to AsciiDoc tags Issue: #54 Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../jgiven/report/asciidoc/TagMapper.java | 18 ++- .../jgiven/report/asciidoc/TagMapperTest.java | 122 +++++++++++++----- 2 files changed, 107 insertions(+), 33 deletions(-) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java index 38e36cf411f..09f0a51d98c 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java @@ -5,11 +5,23 @@ final class TagMapper { private TagMapper() { - // static helper class not intended to be instantiated + // static helper class isn't intended to be instantiated } - static String mapTag(final Tag tag) { + static String toHumanReadableLabel(final Tag tag) { final String cssClassOrType = tag.getCssClass() == null ? "jg-tag-" + tag.getType() : tag.getCssClass(); - return "[." + cssClassOrType + "]#" + tag.toString() + "#"; + return "[." + cssClassOrType + "]#" + tag + "#"; + } + + static String toAsciiDocStartTag(final Tag tag) { + return "// tag::" + toAsciiDocTagName(tag) + "[]"; + } + + static String toAsciiDocEndTag(final Tag tag) { + return "// end::" + toAsciiDocTagName(tag) + "[]"; + } + + private static String toAsciiDocTagName(final Tag tag) { + return tag.toIdString(); } } diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java index dc14e53cd74..11db56952ea 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java @@ -1,88 +1,150 @@ package com.tngtech.jgiven.report.asciidoc; +import static org.assertj.core.api.Assertions.assertThat; + import com.tngtech.jgiven.report.model.Tag; -import java.util.List; -import org.assertj.core.api.Assertions; import org.junit.Test; +import java.util.List; + public class TagMapperTest { @Test - public void map_simple_tag() { + public void simple_tag_to_label() { // given final Tag tag = new Tag("com.tngtech.jgiven.tags.Feature"); tag.setType("Feature"); // when - final String snippet = TagMapper.mapTag(tag); + final String snippet = TagMapper.toHumanReadableLabel(tag); // then - Assertions.assertThat(snippet).isEqualTo("[.jg-tag-Feature]#Feature#"); + assertThat(snippet).isEqualTo("[.jg-tag-Feature]#Feature#"); } @Test - public void map_simple_tag_with_name() { - // given - final Tag tag = new Tag("com.tngtech.jgiven.tags.Priority", "Core Features", null); - tag.setType("FeatureCore"); - - // when - final String snippet = TagMapper.mapTag(tag); - - // then - Assertions.assertThat(snippet).isEqualTo("[.jg-tag-FeatureCore]#Core Features#"); - } - - @Test - public void map_simple_tag_with_single_value() { + public void single_value_tag_to_label() { // given final Tag tag = new Tag("com.tngtech.jgiven.tags.Story", "ACME-1337"); tag.setType("Story"); // when - final String snippet = TagMapper.mapTag(tag); + final String snippet = TagMapper.toHumanReadableLabel(tag); // then - Assertions.assertThat(snippet).isEqualTo("[.jg-tag-Story]#ACME-1337#"); + assertThat(snippet).isEqualTo("[.jg-tag-Story]#ACME-1337#"); } @Test - public void map_combined_tag_with_single_value() { + public void single_value_tag_with_type_to_label() { // given final Tag tag = new Tag("com.tngtech.jgiven.tags.Issue", "#1337"); tag.setType("Issue"); tag.setPrependType(true); // when - final String snippet = TagMapper.mapTag(tag); + final String snippet = TagMapper.toHumanReadableLabel(tag); // then - Assertions.assertThat(snippet).isEqualTo("[.jg-tag-Issue]#Issue-#1337#"); + assertThat(snippet).isEqualTo("[.jg-tag-Issue]#Issue-#1337#"); } @Test - public void map_simple_tag_with_multiple_values() { + public void multiple_value_tag_to_label() { // given final Tag tag = new Tag("com.tngtech.jgiven.tags.Story", List.of("ACME-1337", "ACME-4221")); tag.setType("Story"); // when - final String snippet = TagMapper.mapTag(tag); + final String snippet = TagMapper.toHumanReadableLabel(tag); // then - Assertions.assertThat(snippet).isEqualTo("[.jg-tag-Story]#ACME-1337, ACME-4221#"); + assertThat(snippet).isEqualTo("[.jg-tag-Story]#ACME-1337, ACME-4221#"); } @Test - public void map_simple_tag_with_css_class() { + public void tag_with_css_class_to_label() { // given final Tag tag = new Tag("com.tngtech.jgiven.tags.Priority", "1"); tag.setType("Priority"); tag.setCssClass("hidden"); // when - final String snippet = TagMapper.mapTag(tag); + final String snippet = TagMapper.toHumanReadableLabel(tag); + + // then + assertThat(snippet).isEqualTo("[.hidden]#1#"); + } + + @Test + public void tag_with_name_to_label() { + // given + final Tag tag = new Tag("com.tngtech.jgiven.tags.FeatureCore", "Core Features", null); + tag.setType("FeatureCore"); + + // when + final String snippet = TagMapper.toHumanReadableLabel(tag); + + // then + assertThat(snippet).isEqualTo("[.jg-tag-FeatureCore]#Core Features#"); + } + + @Test + public void simple_tag_to_AsciiDoc_tag() { + // given + final Tag tag = new Tag("com.tngtech.jgiven.tags.Feature"); + tag.setType("Feature"); + + // when + final String startSnippet = TagMapper.toAsciiDocStartTag(tag); + final String endSnippet = TagMapper.toAsciiDocEndTag(tag); + + // then + assertThat(startSnippet).isEqualTo("// tag::com.tngtech.jgiven.tags.Feature[]"); + assertThat(endSnippet).isEqualTo("// end::com.tngtech.jgiven.tags.Feature[]"); + } + + @Test + public void single_value_tag_to_AsciiDoc_tag() { + // given + final Tag tag = new Tag("com.tngtech.jgiven.tags.Feature", "AsciiDoc"); + tag.setType("Feature"); + + // when + final String startSnippet = TagMapper.toAsciiDocStartTag(tag); + final String endSnippet = TagMapper.toAsciiDocEndTag(tag); + + // then + assertThat(startSnippet).isEqualTo("// tag::com.tngtech.jgiven.tags.Feature-AsciiDoc[]"); + assertThat(endSnippet).isEqualTo("// end::com.tngtech.jgiven.tags.Feature-AsciiDoc[]"); + } + + @Test + public void single_value_tag_with_type_to_AsciiDoc_tag() { + // given + final Tag tag = new Tag("com.tngtech.jgiven.tags.Issue", "#1337"); + tag.setType("Issue"); + tag.setPrependType(true); + + // when + final String startSnippet = TagMapper.toAsciiDocStartTag(tag); + final String endSnippet = TagMapper.toAsciiDocEndTag(tag); + + // then + assertThat(startSnippet).isEqualTo("// tag::com.tngtech.jgiven.tags.Issue-#1337[]"); + assertThat(endSnippet).isEqualTo("// end::com.tngtech.jgiven.tags.Issue-#1337[]"); + } + + @Test + public void multiple_value_tag_to_AsciiDoc_tag() { + // given + final Tag tag = new Tag("com.tngtech.jgiven.tags.Feature", List.of("AsciiDoc", "Markdown")); + + // when + final String startSnippet = TagMapper.toAsciiDocStartTag(tag); + final String endSnippet = TagMapper.toAsciiDocEndTag(tag); // then - Assertions.assertThat(snippet).isEqualTo("[.hidden]#1#"); + assertThat(startSnippet).isEqualTo("// tag::com.tngtech.jgiven.tags.Feature-AsciiDoc, Markdown[]"); + assertThat(endSnippet).isEqualTo("// end::com.tngtech.jgiven.tags.Feature-AsciiDoc, Markdown[]"); } } From b48c5936689abf1928ae81f117de95cf628c4ac6 Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:14:10 +0100 Subject: [PATCH 04/15] Collect tagged feature files and scenario counts Issue: #54 Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../asciidoc/AsciiDocReportModelVisitor.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java index 351e2a5bdcb..393b85b20a0 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java @@ -13,6 +13,7 @@ import com.tngtech.jgiven.report.model.Tag; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -21,23 +22,26 @@ class AsciiDocReportModelVisitor extends ReportModelVisitor { private final ReportBlockConverter blockConverter; - private final List asciiDocBlocks; private final ReportStatistics featureStatistics; private final CasesTableCalculator tableCalculator; + private final List asciiDocBlocks; + private final Map featureTagIds; private Map featureTagMap; + private List tagList; private boolean scenarioHasDataTable; + private boolean scenarioHasMultipleCases; private boolean skipCurrentCase; private boolean caseIsUnsuccessful; - private String currentSectionTitle; - private boolean scenarioHasMultipleCases; private boolean isFirstStepInCase; + private String currentSectionTitle; public AsciiDocReportModelVisitor(final ReportBlockConverter blockConverter, final ReportStatistics featureStatistics) { this.blockConverter = blockConverter; - this.asciiDocBlocks = new ArrayList<>(); this.featureStatistics = featureStatistics; this.tableCalculator = new CasesTableCalculator(); + this.asciiDocBlocks = new ArrayList<>(); + this.featureTagIds = new HashMap<>(); } @Override @@ -55,12 +59,14 @@ public void visit(final ReportModel reportModel) { @Override public void visit(final ScenarioModel scenarioModel) { - final List tags = scenarioModel.getTagIds().stream() + scenarioModel.getTagIds().forEach(tagId -> featureTagIds.merge(tagId, 1, Integer::sum)); + + tagList = scenarioModel.getTagIds().stream() .map(this.featureTagMap::get) .collect(Collectors.toList()); String scenarioHeader = blockConverter.convertScenarioHeaderBlock(scenarioModel.getDescription(), - scenarioModel.getExecutionStatus(), scenarioModel.getDurationInNanos(), tags, + scenarioModel.getExecutionStatus(), scenarioModel.getDurationInNanos(), tagList, scenarioModel.getExtendedDescription()); asciiDocBlocks.add(scenarioHeader); @@ -131,15 +137,17 @@ public void visitEnd(final ScenarioModel scenarioModel) { asciiDocBlocks.add(casesTableBlock); } - final List tags = scenarioModel.getTagIds().stream() - .map(this.featureTagMap::get) - .collect(Collectors.toList()); - - String scenarioFooter = blockConverter.convertScenarioFooterBlock(scenarioModel.getExecutionStatus(), tags); + String scenarioFooter = blockConverter.convertScenarioFooterBlock(scenarioModel.getExecutionStatus(), tagList); asciiDocBlocks.add(scenarioFooter); } public List getResult() { return Collections.unmodifiableList(asciiDocBlocks); } + + + public Map getUsedTags() { + return Collections.unmodifiableMap(featureTagIds); + } + } From 7659507701548f42f8ab723d39ab3c2b7fbcbbb1 Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:14:40 +0100 Subject: [PATCH 05/15] Write index files only for used tags Issue: #54 Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../asciidoc/AsciiDocReportGenerator.java | 95 ++++++++++++++----- .../asciidoc/AsciiDocSnippetGenerator.java | 21 ++-- .../jgiven/report/asciidoc/TagMapper.java | 2 +- .../AsciiDocSnippetGeneratorTest.java | 4 +- 4 files changed, 87 insertions(+), 35 deletions(-) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java index d6981927c06..3b102346cb7 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java @@ -8,17 +8,21 @@ import com.tngtech.jgiven.impl.util.PrintWriterUtil; import com.tngtech.jgiven.report.AbstractReportConfig; import com.tngtech.jgiven.report.AbstractReportGenerator; +import com.tngtech.jgiven.report.model.ReportModel; import com.tngtech.jgiven.report.model.ReportModelFile; import com.tngtech.jgiven.report.model.ReportStatistics; +import com.tngtech.jgiven.report.model.Tag; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.net.URL; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,14 +41,20 @@ */ public class AsciiDocReportGenerator extends AbstractReportGenerator { + private static final String FEATURE_PATH = "features"; private static final Logger log = LoggerFactory.getLogger(AsciiDocReportGenerator.class); private final AsciiDocBlockConverter blockConverter = new AsciiDocBlockConverter(); + private final HashMap allTags = new HashMap<>(); private final List featureFiles = new ArrayList<>(); private final List failedScenarioFiles = new ArrayList<>(); private final List pendingScenarioFiles = new ArrayList<>(); + private final HashMap> taggedScenarioFiles = new HashMap<>(); + private final Map taggedScenarioCounts = new HashMap<>(); + private File targetDir; private File featuresDir; + private File tagsDir; @Override public AsciiDocReportConfig createReportConfig(String... args) { @@ -73,6 +83,8 @@ public void generate() { writeIndexFileForPendingScenarios(); + taggedScenarioFiles.forEach(this::writeIndexFileForTaggedScenarios); + writeTotalStatisticsFile(); writeIndexFileForFullReport(config.getTitle()); @@ -90,6 +102,11 @@ private boolean prepareDirectories(final File targetDir) { return false; } + tagsDir = new File(this.targetDir.getPath() + "/tags"); + if (!ensureDirectoryExists(this.tagsDir)) { + return false; + } + featuresDir = new File(this.targetDir.getPath() + "/features"); return ensureDirectoryExists(featuresDir); } @@ -98,17 +115,21 @@ private void writeFeatureFiles() { completeReportModel.getAllReportModels().stream() .sorted(Comparator.comparing(AsciiDocReportGenerator::byFeatureName)) .forEach(reportModelFile -> { - final String featureFileName = Files.getNameWithoutExtension( + final ReportStatistics statistics = completeReportModel.getStatistics(reportModelFile); + final String fileName = Files.getNameWithoutExtension( reportModelFile.file().getName()) + ".asciidoc"; - writeAsciiDocBlocksToFile(new File(featuresDir, featureFileName), - collectReportBlocks(reportModelFile, featureFileName)); + final List asciiDocBlocks = collectReportBlocks(fileName, statistics, reportModelFile.model()); + writeAsciiDocBlocksToFile(featuresDir, fileName, asciiDocBlocks); }); } - private List collectReportBlocks(final ReportModelFile reportModelFile, final String featureFileName) { - featureFiles.add(featureFileName); + private List collectReportBlocks( + final String featureFileName, + final ReportStatistics statistics, + final ReportModel model) { + allTags.putAll(model.getTagMap()); - final ReportStatistics statistics = completeReportModel.getStatistics(reportModelFile); + featureFiles.add(featureFileName); if (statistics.numFailedScenarios > 0) { failedScenarioFiles.add(featureFileName); } @@ -117,42 +138,62 @@ private List collectReportBlocks(final ReportModelFile reportModelFile, } final AsciiDocReportModelVisitor visitor = new AsciiDocReportModelVisitor(blockConverter, statistics); - reportModelFile.model().accept(visitor); + model.accept(visitor); + + visitor.getUsedTags().forEach((tagId, count) -> { + taggedScenarioCounts.merge(tagId, count, Integer::sum); + taggedScenarioFiles.computeIfAbsent(tagId, key -> new ArrayList<>()).add(featureFileName); + }); return visitor.getResult(); } private void writeIndexFileForAllScenarios() { + final int numScenarios = this.completeReportModel.getTotalStatistics().numScenarios; final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "All Scenarios", "scenarios in total", this.featureFiles, "", - this.completeReportModel.getTotalStatistics().numScenarios); + "All Scenarios", "scenarios in total", numScenarios, "", + FEATURE_PATH, this.featureFiles + ); - writeAsciiDocBlocksToFile(new File(targetDir, "allScenarios.asciidoc"), - snippetGenerator.generateIndexSnippet()); + writeAsciiDocBlocksToFile(targetDir, "allScenarios.asciidoc", snippetGenerator.generateIndexSnippet()); } private void writeIndexFileForFailedScenarios() { final String scenarioKind = "failed"; + final int numFailedScenarios = this.completeReportModel.getTotalStatistics().numFailedScenarios; final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "Failed Scenarios", "failed scenarios", this.failedScenarioFiles, scenarioKind, - this.completeReportModel.getTotalStatistics().numFailedScenarios); + "Failed Scenarios", "failed scenarios", numFailedScenarios, "scenario-" + scenarioKind, + FEATURE_PATH, this.failedScenarioFiles + ); - writeAsciiDocBlocksToFile(new File(targetDir, scenarioKind + "Scenarios.asciidoc"), + writeAsciiDocBlocksToFile(targetDir, scenarioKind + "Scenarios.asciidoc", snippetGenerator.generateIndexSnippet()); } private void writeIndexFileForPendingScenarios() { final String scenarioKind = "pending"; + final int numPendingScenarios = this.completeReportModel.getTotalStatistics().numPendingScenarios; final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "Pending Scenarios", "pending scenarios", this.pendingScenarioFiles, scenarioKind, - this.completeReportModel.getTotalStatistics().numPendingScenarios); + "Pending Scenarios", "pending scenarios", numPendingScenarios, "scenario-" + scenarioKind, + FEATURE_PATH, this.pendingScenarioFiles + ); - writeAsciiDocBlocksToFile(new File(targetDir, scenarioKind + "Scenarios.asciidoc"), + writeAsciiDocBlocksToFile(targetDir, scenarioKind + "Scenarios.asciidoc", snippetGenerator.generateIndexSnippet()); } - private void writeTotalStatisticsFile() { + private void writeIndexFileForTaggedScenarios(final String tagId, final List files) { + final Tag tag = allTags.get(tagId); + final String tagName = TagMapper.toAsciiDocTagName(tag); + final int numTaggedScenarios = taggedScenarioCounts.get(tagId); + final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( + tag.getName(), "tagged scenarios", numTaggedScenarios, tagName, + "../" + FEATURE_PATH, files); + writeAsciiDocBlocksToFile(tagsDir, tagName + ".asciidoc", snippetGenerator.generateIndexSnippet()); + } + + private void writeTotalStatisticsFile() { final ListMultimap featureStatistics = completeReportModel.getAllReportModels() .stream() .collect(Multimaps.toMultimap( @@ -163,16 +204,16 @@ private void writeTotalStatisticsFile() { final String statisticsBlock = blockConverter.convertStatisticsBlock( featureStatistics, completeReportModel.getTotalStatistics()); - writeAsciiDocBlocksToFile(new File(targetDir, "totalStatistics.asciidoc"), - Collections.singletonList(statisticsBlock)); + writeAsciiDocBlocksToFile(targetDir, "totalStatistics.asciidoc", Collections.singletonList(statisticsBlock)); } private void writeIndexFileForFullReport(final String reportTitle) { final URL resourceUrl = Resources.getResource(this.getClass(), "index.asciidoc"); try { - final List indexLines = Resources.readLines(resourceUrl, Charset.defaultCharset()); + final List indexLines = Resources.readLines(resourceUrl, StandardCharsets.UTF_8); - try (PrintWriter writer = PrintWriterUtil.getPrintWriter(new File(targetDir, "index.asciidoc"))) { + final File indexFile = new File(targetDir, "index.asciidoc"); + try (PrintWriter writer = PrintWriterUtil.getPrintWriter(indexFile)) { writer.println("= " + reportTitle); indexLines.forEach(writer::println); } @@ -189,13 +230,17 @@ private static boolean ensureDirectoryExists(final File directory) { return true; } - private static String byFeatureName(ReportModelFile modelFile) { + private static String byFeatureName(final ReportModelFile modelFile) { return (null != modelFile.model().getName()) ? modelFile.model().getName() : modelFile.model().getClassName(); } - private static void writeAsciiDocBlocksToFile(final File file, final List asciiDocBlocks) { + private static void writeAsciiDocBlocksToFile( + final File directory, + final String fileName, + final List asciiDocBlocks) { + final File file = new File(directory, fileName); try (final PrintWriter writer = PrintWriterUtil.getPrintWriter(file)) { for (final String block : asciiDocBlocks) { writer.println(block); diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java index f075c368f20..d30316a9072 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java @@ -13,14 +13,21 @@ final class AsciiDocSnippetGenerator { private final List featureFiles; private final int numScenarios; private final String tagSelector; + private final String featurePath; - AsciiDocSnippetGenerator(final String title, final String scenarioQualifier, - final List featureFiles, final String tags, final int numScenarios) { + AsciiDocSnippetGenerator( + final String title, + final String scenarioQualifier, + final int numScenarios, + final String tags, + final String featurePath, + final List featureFiles) { this.title = title; this.scenarioQualifier = scenarioQualifier; - this.featureFiles = featureFiles; - this.tagSelector = Strings.isNullOrEmpty(tags) ? "" : "tag=scenario-" + tags; this.numScenarios = numScenarios; + this.tagSelector = Strings.isNullOrEmpty(tags) ? "" : "tag=" + tags; + this.featurePath = featurePath; + this.featureFiles = featureFiles; } List generateIndexSnippet() { @@ -45,7 +52,7 @@ private List generateIndexSnippet(final String intro, final String tags) result.add(":leveloffset: -1"); } - featureFiles.forEach(fileName -> result.add(includeMacroFor(fileName, tags))); + featureFiles.forEach(fileName -> result.add(includeMacroFor(featurePath, fileName, tags))); if (!Strings.isNullOrEmpty(tags)) { result.add(":leveloffset: +1"); @@ -54,7 +61,7 @@ private List generateIndexSnippet(final String intro, final String tags) } - private static String includeMacroFor(final String fileName, final String tags) { - return "include::features/" + fileName + "[" + tags + "]"; + private static String includeMacroFor(final String featurePath, final String fileName, final String tags) { + return "include::" + featurePath + "/" + fileName + "[" + tags + "]"; } } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java index 09f0a51d98c..50ec64758c4 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java @@ -21,7 +21,7 @@ static String toAsciiDocEndTag(final Tag tag) { return "// end::" + toAsciiDocTagName(tag) + "[]"; } - private static String toAsciiDocTagName(final Tag tag) { + static String toAsciiDocTagName(final Tag tag) { return tag.toIdString(); } } diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java index a66b7e44ec9..cd22db7b152 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java @@ -17,7 +17,7 @@ public void writeIndexFileForFailedScenarios() { // when AsciiDocSnippetGenerator asciiDocSnippetGenerator = new AsciiDocSnippetGenerator( - "Failed Scenarios", "failed scenarios", featureFileNames, "failed", 3); + "Failed Scenarios", "failed scenarios", 3, "scenario-failed", "features", featureFileNames); final List blocks = asciiDocSnippetGenerator.generateIndexSnippet(); // then @@ -41,7 +41,7 @@ public void writeIndexFileForAllScenarios() { // when AsciiDocSnippetGenerator asciiDocSnippetGenerator = new AsciiDocSnippetGenerator( - "All Scenarios", "scenarios in total", featureFileNames, "", 40); + "All Scenarios", "scenarios in total", 40, "", "features", featureFileNames); final List blocks = asciiDocSnippetGenerator.generateIndexSnippet(); // then From 2a3dfaf5febaf11f35e6192738fee1a5fe15f5ce Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:20:23 +0100 Subject: [PATCH 06/15] Replace spaces in tag names Issue: #54 Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../report/asciidoc/AsciiDocBlockConverter.java | 3 ++- .../report/asciidoc/AsciiDocReportGenerator.java | 2 +- .../report/asciidoc/AsciiDocReportModelVisitor.java | 3 +-- .../tngtech/jgiven/report/asciidoc/TagMapper.java | 2 +- .../tngtech/jgiven/report/asciidoc/index.asciidoc | 2 +- .../asciidoc/AsciiDocReportModelVisitorTest.java | 12 ++++++------ .../jgiven/report/asciidoc/TagMapperTest.java | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java index d19a4205650..ff6cdbfec82 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java @@ -46,7 +46,8 @@ public String convertStatisticsBlock(final ListMultimap appendStatisticsRowFragment(statisticsTable, entry.getKey(), entry.getValue())); appendStatisticsRowFragment(statisticsTable, "sum", totalStatistics); diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java index 3b102346cb7..f412a4a5352 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java @@ -145,7 +145,7 @@ private List collectReportBlocks( taggedScenarioFiles.computeIfAbsent(tagId, key -> new ArrayList<>()).add(featureFileName); }); - return visitor.getResult(); + return visitor.getAsciiDocBlocks(); } private void writeIndexFileForAllScenarios() { diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java index 393b85b20a0..8f419f4b9dd 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java @@ -141,11 +141,10 @@ public void visitEnd(final ScenarioModel scenarioModel) { asciiDocBlocks.add(scenarioFooter); } - public List getResult() { + public List getAsciiDocBlocks() { return Collections.unmodifiableList(asciiDocBlocks); } - public Map getUsedTags() { return Collections.unmodifiableMap(featureTagIds); } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java index 50ec64758c4..bdd2d1014f0 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java @@ -22,6 +22,6 @@ static String toAsciiDocEndTag(final Tag tag) { } static String toAsciiDocTagName(final Tag tag) { - return tag.toIdString(); + return tag.toIdString().replace(' ', '_'); } } diff --git a/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc b/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc index d8cdbcc293c..75ec86dcd6a 100644 --- a/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc +++ b/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc @@ -1,6 +1,6 @@ // Report title is provided via report config. :toc: left -:toclevels: 3 +:toclevels: 2 :icons: font include::totalStatistics.asciidoc[] diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java index 374cb70534d..62091b4bb8d 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java @@ -42,7 +42,7 @@ public void visits_a_simple_report() { report.accept(reportModelVisitor); // then - assertThat(reportModelVisitor.getResult()) + assertThat(reportModelVisitor.getAsciiDocBlocks()) .isEqualTo(ImmutableList.of( "FeatureHeaderBlock", "ScenarioHeaderBlock", @@ -70,7 +70,7 @@ public void visits_a_report_with_two_scenarios() { report.accept(reportModelVisitor); // then - assertThat(reportModelVisitor.getResult()) + assertThat(reportModelVisitor.getAsciiDocBlocks()) .isEqualTo(ImmutableList.of( "FeatureHeaderBlock", "ScenarioHeaderBlock", @@ -102,7 +102,7 @@ public void visits_a_scenario_with_two_standalone_cases() { report.accept(reportModelVisitor); // then - assertThat(reportModelVisitor.getResult()) + assertThat(reportModelVisitor.getAsciiDocBlocks()) .isEqualTo(ImmutableList.of( "FeatureHeaderBlock", "ScenarioHeaderBlock", @@ -134,7 +134,7 @@ public void visits_a_scenario_with_two_cases_as_table() { report.accept(reportModelVisitor); // then - assertThat(reportModelVisitor.getResult()) + assertThat(reportModelVisitor.getAsciiDocBlocks()) .isEqualTo(ImmutableList.of( "FeatureHeaderBlock", "ScenarioHeaderBlock", @@ -158,7 +158,7 @@ public void visits_a_scenario_with_a_section() { report.accept(reportModelVisitor); // then - assertThat(reportModelVisitor.getResult()) + assertThat(reportModelVisitor.getAsciiDocBlocks()) .isEqualTo(ImmutableList.of( "FeatureHeaderBlock", "ScenarioHeaderBlock", @@ -185,7 +185,7 @@ public void visits_a_scenario_with_two_sections() { report.accept(reportModelVisitor); // then - assertThat(reportModelVisitor.getResult()) + assertThat(reportModelVisitor.getAsciiDocBlocks()) .isEqualTo(ImmutableList.of( "FeatureHeaderBlock", "ScenarioHeaderBlock", diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java index 11db56952ea..1c04f96ab5b 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java @@ -144,7 +144,7 @@ public void multiple_value_tag_to_AsciiDoc_tag() { final String endSnippet = TagMapper.toAsciiDocEndTag(tag); // then - assertThat(startSnippet).isEqualTo("// tag::com.tngtech.jgiven.tags.Feature-AsciiDoc, Markdown[]"); - assertThat(endSnippet).isEqualTo("// end::com.tngtech.jgiven.tags.Feature-AsciiDoc, Markdown[]"); + assertThat(startSnippet).isEqualTo("// tag::com.tngtech.jgiven.tags.Feature-AsciiDoc,_Markdown[]"); + assertThat(endSnippet).isEqualTo("// end::com.tngtech.jgiven.tags.Feature-AsciiDoc,_Markdown[]"); } } From af5badc0e03f6e0d953f9d87ac78641d42e38f6f Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:28:50 +0100 Subject: [PATCH 07/15] Group scenarios by tags Issue: #54 Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../asciidoc/AsciiDocReportGenerator.java | 173 +++++++++--------- .../asciidoc/AsciiDocSnippetGenerator.java | 81 +++++--- .../AsciiDocSnippetGeneratorTest.java | 20 +- 3 files changed, 154 insertions(+), 120 deletions(-) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java index f412a4a5352..bc0c8a61d47 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java @@ -42,19 +42,21 @@ public class AsciiDocReportGenerator extends AbstractReportGenerator { private static final String FEATURE_PATH = "features"; + private static final String ASCIIDOC_FILETYPE = ".asciidoc"; + private static final String SCENARIO_TAG = "scenario-"; private static final Logger log = LoggerFactory.getLogger(AsciiDocReportGenerator.class); private final AsciiDocBlockConverter blockConverter = new AsciiDocBlockConverter(); - private final HashMap allTags = new HashMap<>(); - private final List featureFiles = new ArrayList<>(); - private final List failedScenarioFiles = new ArrayList<>(); - private final List pendingScenarioFiles = new ArrayList<>(); - private final HashMap> taggedScenarioFiles = new HashMap<>(); + private final Map allTags = new HashMap<>(); + private final List allFeatures = new ArrayList<>(); + private final List failedScenarioFeatures = new ArrayList<>(); + private final List pendingScenarioFeatures = new ArrayList<>(); + private final Map> taggedScenarioFeatures = new HashMap<>(); private final Map taggedScenarioCounts = new HashMap<>(); private File targetDir; private File featuresDir; - private File tagsDir; + @Override public AsciiDocReportConfig createReportConfig(String... args) { @@ -63,18 +65,10 @@ public AsciiDocReportConfig createReportConfig(String... args) { @Override public void generate() { - if (config == null) { - throw new IllegalStateException("AsciiDocReporter must be configured before generating a report."); - } - - if (!prepareDirectories(config.getTargetDir())) { + if (!loadConfigAndModel()) { return; } - if (completeReportModel == null) { - loadReportModel(); - } - writeFeatureFiles(); writeIndexFileForAllScenarios(); @@ -83,32 +77,27 @@ public void generate() { writeIndexFileForPendingScenarios(); - taggedScenarioFiles.forEach(this::writeIndexFileForTaggedScenarios); - writeTotalStatisticsFile(); writeIndexFileForFullReport(config.getTitle()); } - private boolean prepareDirectories(final File targetDir) { - this.targetDir = targetDir; - if (this.targetDir == null) { - log.error("Target directory was not configured"); - return false; + + private boolean loadConfigAndModel() { + if (config == null) { + throw new IllegalStateException("AsciiDocReporter must be configured before generating a report."); } - if (!ensureDirectoryExists(this.targetDir)) { + if (!prepareDirectories(config.getTargetDir())) { return false; } - tagsDir = new File(this.targetDir.getPath() + "/tags"); - if (!ensureDirectoryExists(this.tagsDir)) { - return false; + if (completeReportModel == null) { + loadReportModel(); } - featuresDir = new File(this.targetDir.getPath() + "/features"); - return ensureDirectoryExists(featuresDir); + return true; } private void writeFeatureFiles() { @@ -116,81 +105,49 @@ private void writeFeatureFiles() { .sorted(Comparator.comparing(AsciiDocReportGenerator::byFeatureName)) .forEach(reportModelFile -> { final ReportStatistics statistics = completeReportModel.getStatistics(reportModelFile); - final String fileName = Files.getNameWithoutExtension( - reportModelFile.file().getName()) + ".asciidoc"; - final List asciiDocBlocks = collectReportBlocks(fileName, statistics, reportModelFile.model()); - writeAsciiDocBlocksToFile(featuresDir, fileName, asciiDocBlocks); - }); - } - - private List collectReportBlocks( - final String featureFileName, - final ReportStatistics statistics, - final ReportModel model) { - allTags.putAll(model.getTagMap()); - - featureFiles.add(featureFileName); - if (statistics.numFailedScenarios > 0) { - failedScenarioFiles.add(featureFileName); - } - if (statistics.numPendingScenarios > 0) { - pendingScenarioFiles.add(featureFileName); - } - - final AsciiDocReportModelVisitor visitor = new AsciiDocReportModelVisitor(blockConverter, statistics); - model.accept(visitor); - - visitor.getUsedTags().forEach((tagId, count) -> { - taggedScenarioCounts.merge(tagId, count, Integer::sum); - taggedScenarioFiles.computeIfAbsent(tagId, key -> new ArrayList<>()).add(featureFileName); - }); + final String featureName = Files.getNameWithoutExtension(reportModelFile.file().getName()); + final List asciiDocBlocks = collectReportBlocks(featureName, statistics, reportModelFile.model()); - return visitor.getAsciiDocBlocks(); + writeAsciiDocBlocksToFile(featuresDir, featureName, asciiDocBlocks); + }); } private void writeIndexFileForAllScenarios() { final int numScenarios = this.completeReportModel.getTotalStatistics().numScenarios; + final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "All Scenarios", "scenarios in total", numScenarios, "", - FEATURE_PATH, this.featureFiles - ); + "All Scenarios", "scenarios in total", numScenarios); + + final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); + asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet(FEATURE_PATH, this.allFeatures, "", 0)); - writeAsciiDocBlocksToFile(targetDir, "allScenarios.asciidoc", snippetGenerator.generateIndexSnippet()); + writeAsciiDocBlocksToFile(targetDir, "allScenarios", asciiDocBlocks); } private void writeIndexFileForFailedScenarios() { - final String scenarioKind = "failed"; final int numFailedScenarios = this.completeReportModel.getTotalStatistics().numFailedScenarios; + final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "Failed Scenarios", "failed scenarios", numFailedScenarios, "scenario-" + scenarioKind, - FEATURE_PATH, this.failedScenarioFiles - ); + "Failed Scenarios", "failed scenarios", numFailedScenarios); + + final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); + asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet( + FEATURE_PATH, this.failedScenarioFeatures, SCENARIO_TAG + "failed", -1)); - writeAsciiDocBlocksToFile(targetDir, scenarioKind + "Scenarios.asciidoc", - snippetGenerator.generateIndexSnippet()); + writeAsciiDocBlocksToFile(targetDir, "failedScenarios", asciiDocBlocks); } private void writeIndexFileForPendingScenarios() { - final String scenarioKind = "pending"; final int numPendingScenarios = this.completeReportModel.getTotalStatistics().numPendingScenarios; - final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "Pending Scenarios", "pending scenarios", numPendingScenarios, "scenario-" + scenarioKind, - FEATURE_PATH, this.pendingScenarioFiles - ); - writeAsciiDocBlocksToFile(targetDir, scenarioKind + "Scenarios.asciidoc", - snippetGenerator.generateIndexSnippet()); - } - - private void writeIndexFileForTaggedScenarios(final String tagId, final List files) { - final Tag tag = allTags.get(tagId); - final String tagName = TagMapper.toAsciiDocTagName(tag); - final int numTaggedScenarios = taggedScenarioCounts.get(tagId); final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - tag.getName(), "tagged scenarios", numTaggedScenarios, tagName, - "../" + FEATURE_PATH, files); + "Pending Scenarios", "pending scenarios", numPendingScenarios); + + final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); + asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet( + FEATURE_PATH, this.pendingScenarioFeatures, SCENARIO_TAG + "pending", -1)); - writeAsciiDocBlocksToFile(tagsDir, tagName + ".asciidoc", snippetGenerator.generateIndexSnippet()); + writeAsciiDocBlocksToFile(targetDir, "pendingScenarios", asciiDocBlocks); } private void writeTotalStatisticsFile() { @@ -204,7 +161,7 @@ private void writeTotalStatisticsFile() { final String statisticsBlock = blockConverter.convertStatisticsBlock( featureStatistics, completeReportModel.getTotalStatistics()); - writeAsciiDocBlocksToFile(targetDir, "totalStatistics.asciidoc", Collections.singletonList(statisticsBlock)); + writeAsciiDocBlocksToFile(targetDir, "totalStatistics", Collections.singletonList(statisticsBlock)); } private void writeIndexFileForFullReport(final String reportTitle) { @@ -222,6 +179,52 @@ private void writeIndexFileForFullReport(final String reportTitle) { } } + private boolean prepareDirectories(final File targetDir) { + File tagsDir; + this.targetDir = targetDir; + if (this.targetDir == null) { + log.error("Target directory was not configured"); + return false; + } + + if (!ensureDirectoryExists(this.targetDir)) { + return false; + } + + tagsDir = new File(this.targetDir.getPath() + "/tags"); + if (!ensureDirectoryExists(tagsDir)) { + return false; + } + + featuresDir = new File(this.targetDir.getPath() + "/features"); + return ensureDirectoryExists(featuresDir); + } + + private List collectReportBlocks( + final String featureName, + final ReportStatistics statistics, + final ReportModel model) { + allTags.putAll(model.getTagMap()); + + allFeatures.add(featureName); + if (statistics.numFailedScenarios > 0) { + failedScenarioFeatures.add(featureName); + } + if (statistics.numPendingScenarios > 0) { + pendingScenarioFeatures.add(featureName); + } + + final AsciiDocReportModelVisitor visitor = new AsciiDocReportModelVisitor(blockConverter, statistics); + model.accept(visitor); + + visitor.getUsedTags().forEach((tagId, count) -> { + taggedScenarioCounts.merge(tagId, count, Integer::sum); + taggedScenarioFeatures.computeIfAbsent(tagId, key -> new ArrayList<>()).add(featureName); + }); + + return visitor.getAsciiDocBlocks(); + } + private static boolean ensureDirectoryExists(final File directory) { if (!directory.exists() && !directory.mkdirs()) { log.error("Could not ensure directory exists {}", directory); @@ -240,7 +243,7 @@ private static void writeAsciiDocBlocksToFile( final File directory, final String fileName, final List asciiDocBlocks) { - final File file = new File(directory, fileName); + final File file = new File(directory, fileName + ASCIIDOC_FILETYPE); try (final PrintWriter writer = PrintWriterUtil.getPrintWriter(file)) { for (final String block : asciiDocBlocks) { writer.println(block); diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java index d30316a9072..ed69b4b9234 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java @@ -1,6 +1,7 @@ package com.tngtech.jgiven.report.asciidoc; import com.google.common.base.Strings; +import com.tngtech.jgiven.report.model.Tag; import java.util.ArrayList; import java.util.List; @@ -8,60 +9,94 @@ * Generate snippets for including feature files via AsciiDoc include macro. */ final class AsciiDocSnippetGenerator { + private static final String LINE_BREAK = System.lineSeparator(); private final String title; private final String scenarioQualifier; - private final List featureFiles; private final int numScenarios; - private final String tagSelector; - private final String featurePath; AsciiDocSnippetGenerator( final String title, final String scenarioQualifier, - final int numScenarios, - final String tags, - final String featurePath, - final List featureFiles) { + final int numScenarios) { this.title = title; this.scenarioQualifier = scenarioQualifier; this.numScenarios = numScenarios; - this.tagSelector = Strings.isNullOrEmpty(tags) ? "" : "tag=" + tags; - this.featurePath = featurePath; - this.featureFiles = featureFiles; } - List generateIndexSnippet() { + List generateIntroSnippet(final String description) { final ArrayList result = new ArrayList<>(); + result.add("== " + this.title); - if (featureFiles.isEmpty()) { + if (!description.isEmpty()) { + result.add("+++" + LINE_BREAK + description + LINE_BREAK + "+++"); + } + + if (numScenarios == 0) { result.add("There are no " + scenarioQualifier + ". Keep rocking!"); } else { - final String intro = "There are " + numScenarios + " " + scenarioQualifier + "."; - result.addAll(generateIndexSnippet(intro, this.tagSelector)); + result.add("There are " + numScenarios + " " + scenarioQualifier + "."); } return result; } - private List generateIndexSnippet(final String intro, final String tags) { + List generateIndexSnippet(final String featurePath, final List featureFiles, final String tags, final int leveloffset) { + final String tagSelector = Strings.isNullOrEmpty(tags) ? "" : "tag=" + tags; final ArrayList result = new ArrayList<>(); - result.add(intro); - if (!Strings.isNullOrEmpty(tags)) { - result.add(":leveloffset: -1"); + if (!featureFiles.isEmpty()) { + result.addAll(generateIncludeSnippet("", leveloffset, featurePath, featureFiles, tagSelector)); } - featureFiles.forEach(fileName -> result.add(includeMacroFor(featurePath, fileName, tags))); + return result; + } + + List generateTagSnippet(final Tag tag, int scenarioCount, final List features, final int leveloffset) { + final ArrayList result = new ArrayList<>(); - if (!Strings.isNullOrEmpty(tags)) { - result.add(":leveloffset: +1"); + result.add("=== " + tag.toString()); + + if (features.isEmpty()) { + result.add("There are no tagged scenarios. Keep rocking!"); + } else { + final String intro = "There are " + scenarioCount + " " + scenarioQualifier + "."; + final String tagSelector = TagMapper.toAsciiDocTagName(tag); + result.addAll(generateIncludeSnippet(intro, leveloffset, "../features", features, tagSelector)); } + return result; } + private List generateIncludeSnippet( + final String intro, + final int leveloffset, + final String featurePath, + final List featureFiles, + final String tags) { + final ArrayList result = new ArrayList<>(); + + if (!intro.isBlank()) { + result.add(intro); + } + + if (leveloffset > 0) { + result.add(":leveloffset: +" + leveloffset); + } else if (leveloffset < 0) { + result.add(":leveloffset: -" + Math.abs(leveloffset)); + } + + featureFiles.forEach(fileName -> result.add(includeMacroFor(featurePath, fileName, tags))); + + if (leveloffset > 0) { + result.add(":leveloffset: -" + leveloffset); + } else if (leveloffset < 0) { + result.add(":leveloffset: +" + Math.abs(leveloffset)); + } + return result; + } - private static String includeMacroFor(final String featurePath, final String fileName, final String tags) { - return "include::" + featurePath + "/" + fileName + "[" + tags + "]"; + private static String includeMacroFor(final String featurePath, final String featureName, final String tags) { + return "include::" + featurePath + "/" + featureName + ".asciidoc[" + tags + "]"; } } diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java index cd22db7b152..51c4d513c94 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java @@ -12,18 +12,16 @@ public class AsciiDocSnippetGeneratorTest { public void writeIndexFileForFailedScenarios() { // given final List featureFileNames = new ArrayList<>(); - featureFileNames.add("com.example.application.FailedScenarioOne.asciidoc"); - featureFileNames.add("com.example.application.FailedScenarioTwo.asciidoc"); + featureFileNames.add("com.example.application.FailedScenarioOne"); + featureFileNames.add("com.example.application.FailedScenarioTwo"); // when AsciiDocSnippetGenerator asciiDocSnippetGenerator = new AsciiDocSnippetGenerator( - "Failed Scenarios", "failed scenarios", 3, "scenario-failed", "features", featureFileNames); - final List blocks = asciiDocSnippetGenerator.generateIndexSnippet(); + "Failed Scenarios", "failed scenarios", 3); + final List blocks = asciiDocSnippetGenerator.generateIndexSnippet("features", featureFileNames, "scenario-failed", -1); // then assertThat(blocks).containsExactly( - "== Failed Scenarios", - "There are 3 failed scenarios.", ":leveloffset: -1", "include::features/com.example.application.FailedScenarioOne.asciidoc[tag=scenario-failed]", "include::features/com.example.application.FailedScenarioTwo.asciidoc[tag=scenario-failed]", @@ -35,19 +33,17 @@ public void writeIndexFileForFailedScenarios() { public void writeIndexFileForAllScenarios() { // given final List featureFileNames = new ArrayList<>(); - featureFileNames.add("com.example.application.BigFeature.asciidoc"); - featureFileNames.add("com.example.application.OtherFeature.asciidoc"); + featureFileNames.add("com.example.application.BigFeature"); + featureFileNames.add("com.example.application.OtherFeature"); // when AsciiDocSnippetGenerator asciiDocSnippetGenerator = new AsciiDocSnippetGenerator( - "All Scenarios", "scenarios in total", 40, "", "features", featureFileNames); - final List blocks = asciiDocSnippetGenerator.generateIndexSnippet(); + "All Scenarios", "scenarios in total", 40); + final List blocks = asciiDocSnippetGenerator.generateIndexSnippet("features", featureFileNames, "", 0); // then assertThat(blocks).containsExactly( - "== All Scenarios", - "There are 40 scenarios in total.", "include::features/com.example.application.BigFeature.asciidoc[]", "include::features/com.example.application.OtherFeature.asciidoc[]"); } From f1a3f43a571cc6d5d7b72c7f918c093c17ad63d7 Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Sun, 11 Feb 2024 14:24:04 +0100 Subject: [PATCH 08/15] Restructure report generator Issue: #54 Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../asciidoc/AsciiDocReportGenerator.java | 83 ++++++++++++++++++- .../report/asciidoc/HierarchyCalculator.java | 29 +++++++ .../jgiven/report/asciidoc/index.asciidoc | 2 + 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/HierarchyCalculator.java diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java index bc0c8a61d47..a49c86d3a88 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java @@ -23,6 +23,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +59,7 @@ public class AsciiDocReportGenerator extends AbstractReportGenerator { private File targetDir; private File featuresDir; + private File tagsDir; @Override @@ -77,6 +81,14 @@ public void generate() { writeIndexFileForPendingScenarios(); + final HierarchyCalculator hierarchyCalculator = new HierarchyCalculator(allTags, taggedScenarioFeatures); + + final Map>> groupedTags = hierarchyCalculator.computeGroupedTag(); + + groupedTags.forEach(this::writeIndexFileForTaggedScenarios); + + writeIndexFileForAllTags(groupedTags); + writeTotalStatisticsFile(); writeIndexFileForFullReport(config.getTitle()); @@ -150,6 +162,76 @@ private void writeIndexFileForPendingScenarios() { writeAsciiDocBlocksToFile(targetDir, "pendingScenarios", asciiDocBlocks); } + private void writeIndexFileForTaggedScenarios(final String tagType, final Map> taggedScenarios) { + final Optional firstTag = taggedScenarios.keySet().stream() + .findFirst() + .map(allTags::get); + + if (firstTag.isEmpty()) { + return; + } + + final int numTaggedScenarios = taggedScenarios.keySet().stream().mapToInt(taggedScenarioCounts::get).sum(); + + final List asciiDocBlocks = taggedScenarios.keySet().size() == 1 + ? singleValuedTag(taggedScenarios, firstTag.get(), numTaggedScenarios) + : multiValuedTag(taggedScenarios, firstTag.get(), numTaggedScenarios); + + writeAsciiDocBlocksToFile(tagsDir, tagType, asciiDocBlocks); + } + + private List multiValuedTag(final Map> taggedScenarios, final Tag tag, final int numTaggedScenarios) { + final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( + tag.getName(), "tagged scenarios", numTaggedScenarios); + + final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(tag.getDescription()); + taggedScenarios.forEach((tagId, features) -> { + final List snippet = snippetGenerator.generateTagSnippet( + allTags.get(tagId), taggedScenarioCounts.get(tagId), features, 0); + asciiDocBlocks.addAll(snippet); + }); + return asciiDocBlocks; + } + + private List singleValuedTag(final Map> taggedScenarios, final Tag tag, final int numTaggedScenarios) { + final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( + tag.toString(), "tagged scenarios", numTaggedScenarios); + + final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(tag.getDescription()); + taggedScenarios.forEach((tagId, features) -> { + final Tag valueTag = allTags.get(tagId); + final String tagName = TagMapper.toAsciiDocTagName(valueTag); + asciiDocBlocks.add("=== Scenarios"); + final List snippet = snippetGenerator.generateIndexSnippet("../" + FEATURE_PATH, features, tagName, 0); + asciiDocBlocks.addAll(snippet); + }); + return asciiDocBlocks; + } + + + private void writeIndexFileForAllTags(final Map>> strings) { + + final List tagFiles = strings.entrySet().stream() + .sorted((o1, o2) -> { + final Tag tag1 = allTags.get(o1.getValue().keySet().stream().findFirst().orElse("")); + final Tag tag2 = allTags.get(o2.getValue().keySet().stream().findFirst().orElse("")); + return Objects.compare(tag1, tag2, Comparator.comparing(Tag::getName)); + + }) + .map(entry -> entry.getKey().replace(' ', '_')) + .collect(Collectors.toList()); + final Integer total = taggedScenarioCounts.values().stream().reduce(Integer::sum).orElse(999); + final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( + "Tags", "all tags are beautiful", total + ); + + final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); + asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet("tags", tagFiles, "", 1)); + writeAsciiDocBlocksToFile(targetDir, "allTags", + asciiDocBlocks); + + } + private void writeTotalStatisticsFile() { final ListMultimap featureStatistics = completeReportModel.getAllReportModels() .stream() @@ -180,7 +262,6 @@ private void writeIndexFileForFullReport(final String reportTitle) { } private boolean prepareDirectories(final File targetDir) { - File tagsDir; this.targetDir = targetDir; if (this.targetDir == null) { log.error("Target directory was not configured"); diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/HierarchyCalculator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/HierarchyCalculator.java new file mode 100644 index 00000000000..f3bd986423f --- /dev/null +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/HierarchyCalculator.java @@ -0,0 +1,29 @@ +package com.tngtech.jgiven.report.asciidoc; + +import com.tngtech.jgiven.report.model.Tag; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class HierarchyCalculator { + private final Map> taggedScenarioFiles; + private final Map allTags; + + public HierarchyCalculator(final Map allTags, final Map> taggedScenarioFiles) { + this.taggedScenarioFiles = taggedScenarioFiles; + this.allTags = allTags; + } + + Map>> computeGroupedTag() { + return taggedScenarioFiles.entrySet().stream() + .filter(entry -> allTags.get(entry.getKey()).getShownInNavigation()) + .collect(Collectors.groupingBy(this::fullType, Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + } + + private String fullType(final Map.Entry> entry) { + final Tag tag = allTags.get(entry.getKey()); + + return tag.getFullType(); + + } +} diff --git a/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc b/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc index 75ec86dcd6a..54a5b779925 100644 --- a/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc +++ b/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc @@ -10,3 +10,5 @@ include::allScenarios.asciidoc[] include::failedScenarios.asciidoc[] include::pendingScenarios.asciidoc[] + +include::allTags.asciidoc[] From e1d9b698c8e847337f4ca6bede3aee2a3ccef5ba Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Sun, 18 May 2025 20:18:26 +0200 Subject: [PATCH 09/15] Take the number of failed/pending scenarios into account Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../asciidoc/AsciiDocReportGenerator.java | 40 +++++------ .../asciidoc/AsciiDocSnippetGenerator.java | 45 +++++++----- .../AsciiDocIntroSnippetGeneratorTest.java | 70 +++++++++++++++++++ .../AsciiDocSnippetGeneratorTest.java | 13 ++-- 4 files changed, 124 insertions(+), 44 deletions(-) create mode 100644 jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocIntroSnippetGeneratorTest.java diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java index 885ced25f14..ebaf6239876 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java @@ -25,7 +25,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -95,7 +94,6 @@ public void generate() { writeTotalStatisticsFile(); writeIndexFileForFullReport(config.getTitle()); - } @@ -131,10 +129,11 @@ private void writeIndexFileForAllScenarios() { final int numScenarios = this.completeReportModel.getTotalStatistics().numScenarios; final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "All Scenarios", "scenarios in total", numScenarios); + "All Scenarios", "", numScenarios); final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); - asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet(FEATURE_PATH, this.allFeatures, "", 0)); + asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet( + FEATURE_PATH, this.allFeatures, "", 0)); writeAsciiDocBlocksToFile(targetDir, "allScenarios", asciiDocBlocks); } @@ -143,7 +142,7 @@ private void writeIndexFileForFailedScenarios() { final int numFailedScenarios = this.completeReportModel.getTotalStatistics().numFailedScenarios; final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "Failed Scenarios", "failed scenarios", numFailedScenarios); + "Failed Scenarios", "failed", numFailedScenarios); final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet( @@ -156,7 +155,7 @@ private void writeIndexFileForPendingScenarios() { final int numPendingScenarios = this.completeReportModel.getTotalStatistics().numPendingScenarios; final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "Pending Scenarios", "pending scenarios", numPendingScenarios); + "Pending Scenarios", "pending", numPendingScenarios); final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet( @@ -167,6 +166,7 @@ private void writeIndexFileForPendingScenarios() { private void writeIndexFileForAbortedScenarios() { final int numAbortedScenarios = this.completeReportModel.getTotalStatistics().numAbortedScenarios; + final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( "Aborted Scenarios", "aborted scenarios", numAbortedScenarios); @@ -188,36 +188,37 @@ private void writeIndexFileForTaggedScenarios(final String tagType, final Map asciiDocBlocks = taggedScenarios.keySet().size() == 1 + final List asciiDocBlocks = taggedScenarios.size() == 1 ? singleValuedTag(taggedScenarios, firstTag.get(), numTaggedScenarios) : multiValuedTag(taggedScenarios, firstTag.get(), numTaggedScenarios); writeAsciiDocBlocksToFile(tagsDir, tagType, asciiDocBlocks); } - private List multiValuedTag(final Map> taggedScenarios, final Tag tag, final int numTaggedScenarios) { + private List singleValuedTag(final Map> taggedScenarios, final Tag tag, final int numTaggedScenarios) { final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - tag.getName(), "tagged scenarios", numTaggedScenarios); + tag.toString(), "tagged", numTaggedScenarios); final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(tag.getDescription()); + taggedScenarios.forEach((tagId, features) -> { - final List snippet = snippetGenerator.generateTagSnippet( - allTags.get(tagId), taggedScenarioCounts.get(tagId), features, 0); + final Tag valueTag = allTags.get(tagId); + final String tagName = TagMapper.toAsciiDocTagName(valueTag); + asciiDocBlocks.add("=== Scenarios"); + final List snippet = snippetGenerator.generateIndexSnippet("../" + FEATURE_PATH, features, tagName, 0); asciiDocBlocks.addAll(snippet); }); return asciiDocBlocks; } - private List singleValuedTag(final Map> taggedScenarios, final Tag tag, final int numTaggedScenarios) { + private List multiValuedTag(final Map> taggedScenarios, final Tag tag, final int numTaggedScenarios) { final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - tag.toString(), "tagged scenarios", numTaggedScenarios); + tag.getName(), "tagged", numTaggedScenarios); final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(tag.getDescription()); taggedScenarios.forEach((tagId, features) -> { - final Tag valueTag = allTags.get(tagId); - final String tagName = TagMapper.toAsciiDocTagName(valueTag); - asciiDocBlocks.add("=== Scenarios"); - final List snippet = snippetGenerator.generateIndexSnippet("../" + FEATURE_PATH, features, tagName, 0); + final List snippet = snippetGenerator.generateTagSnippet( + allTags.get(tagId), taggedScenarioCounts.get(tagId), features); asciiDocBlocks.addAll(snippet); }); return asciiDocBlocks; @@ -232,7 +233,7 @@ private void writeIndexFileForAllTags(final Map }) .map(entry -> entry.getKey().replace(' ', '_')) - .collect(Collectors.toList()); + .toList(); final Integer total = taggedScenarioCounts.values().stream().reduce(Integer::sum).orElse(999); final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( "Tags", "all tags are beautiful", total @@ -240,8 +241,7 @@ private void writeIndexFileForAllTags(final Map final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet("tags", tagFiles, "", 1)); - writeAsciiDocBlocksToFile(targetDir, "allTags", - asciiDocBlocks); + writeAsciiDocBlocksToFile(targetDir, "allTags", asciiDocBlocks); } private void writeTotalStatisticsFile() { diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java index ed69b4b9234..66c439f4ec5 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java @@ -10,6 +10,7 @@ */ final class AsciiDocSnippetGenerator { private static final String LINE_BREAK = System.lineSeparator(); + private static final String LEVEL_OFFSET = ":leveloffset:"; private final String title; private final String scenarioQualifier; private final int numScenarios; @@ -24,7 +25,7 @@ final class AsciiDocSnippetGenerator { } List generateIntroSnippet(final String description) { - final ArrayList result = new ArrayList<>(); + final List result = new ArrayList<>(); result.add("== " + this.title); @@ -32,37 +33,47 @@ List generateIntroSnippet(final String description) { result.add("+++" + LINE_BREAK + description + LINE_BREAK + "+++"); } - if (numScenarios == 0) { - result.add("There are no " + scenarioQualifier + ". Keep rocking!"); + final String qualifiedScenario = scenarioQualifier.isBlank() ? "scenario" : scenarioQualifier + " scenario"; + + if (numScenarios <= 0) { + result.add("There are no " + qualifiedScenario + "s. Keep rocking!"); + } else if (numScenarios == 1) { + result.add("There is " + numScenarios + " " + qualifiedScenario + "."); } else { - result.add("There are " + numScenarios + " " + scenarioQualifier + "."); + result.add("There are " + numScenarios + " " + qualifiedScenario + "s."); } return result; } - List generateIndexSnippet(final String featurePath, final List featureFiles, final String tags, final int leveloffset) { + List generateIndexSnippet(final String featurePath, final List features, final String tags, final int levelOffset) { + final List result = new ArrayList<>(); + final String tagSelector = Strings.isNullOrEmpty(tags) ? "" : "tag=" + tags; - final ArrayList result = new ArrayList<>(); - if (!featureFiles.isEmpty()) { - result.addAll(generateIncludeSnippet("", leveloffset, featurePath, featureFiles, tagSelector)); + if (!features.isEmpty()) { + result.addAll(generateIncludeSnippet("", levelOffset, featurePath, features, tagSelector)); } return result; } - List generateTagSnippet(final Tag tag, int scenarioCount, final List features, final int leveloffset) { + List generateTagSnippet(final Tag tag, int scenarioCount, final List features) { final ArrayList result = new ArrayList<>(); result.add("=== " + tag.toString()); - if (features.isEmpty()) { - result.add("There are no tagged scenarios. Keep rocking!"); + if (numScenarios <= 0) { + result.add("There are no " + scenarioQualifier + " scenarios. Keep rocking!"); } else { - final String intro = "There are " + scenarioCount + " " + scenarioQualifier + "."; + final String intro; + if (numScenarios == 1) { + intro = "There is " + scenarioCount + " " + scenarioQualifier + " scenario."; + } else { + intro = "There are " + scenarioCount + " " + scenarioQualifier + " scenarios."; + } final String tagSelector = TagMapper.toAsciiDocTagName(tag); - result.addAll(generateIncludeSnippet(intro, leveloffset, "../features", features, tagSelector)); + result.addAll(generateIncludeSnippet(intro, 0, "../features", features, tagSelector)); } return result; @@ -81,17 +92,17 @@ private List generateIncludeSnippet( } if (leveloffset > 0) { - result.add(":leveloffset: +" + leveloffset); + result.add(LEVEL_OFFSET + " +" + leveloffset); } else if (leveloffset < 0) { - result.add(":leveloffset: -" + Math.abs(leveloffset)); + result.add(LEVEL_OFFSET + " -" + Math.abs(leveloffset)); } featureFiles.forEach(fileName -> result.add(includeMacroFor(featurePath, fileName, tags))); if (leveloffset > 0) { - result.add(":leveloffset: -" + leveloffset); + result.add(LEVEL_OFFSET + " -" + leveloffset); } else if (leveloffset < 0) { - result.add(":leveloffset: +" + Math.abs(leveloffset)); + result.add(LEVEL_OFFSET + " +" + Math.abs(leveloffset)); } return result; } diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocIntroSnippetGeneratorTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocIntroSnippetGeneratorTest.java new file mode 100644 index 00000000000..b28b43c64ae --- /dev/null +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocIntroSnippetGeneratorTest.java @@ -0,0 +1,70 @@ +package com.tngtech.jgiven.report.asciidoc; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class AsciiDocIntroSnippetGeneratorTest { + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][]{ + {"All Scenarios", "", 0, "There are no scenarios. Keep rocking!"}, + {"All Scenarios", "", 1, "There is 1 scenario."}, + {"All Scenarios", "", 2, "There are 2 scenarios."}, + {"Failed Scenarios", "failed", 0, "There are no failed scenarios. Keep rocking!"}, + {"Failed Scenarios", "failed", 1, "There is 1 failed scenario."}, + {"Failed Scenarios", "failed", 2, "There are 2 failed scenarios."}, + {"Pending Scenarios", "pending", 0, "There are no pending scenarios. Keep rocking!"}, + {"Pending Scenarios", "pending", 1, "There is 1 pending scenario."}, + {"Pending Scenarios", "pending", 2, "There are 2 pending scenarios."}, + {"Aborted Scenarios", "aborted", 0, "There are no aborted scenarios. Keep rocking!"}, + {"Aborted Scenarios", "aborted", 1, "There is 1 aborted scenario."}, + {"Aborted Scenarios", "aborted", 2, "There are 2 aborted scenarios."}, + }); + } + + private final String title; + private final String qualifier; + private final int numScenarios; + private final String expectedMessage; + + public AsciiDocIntroSnippetGeneratorTest(final String title, final String qualifier, int numScenarios, String expectedMessage) { + this.title = title; + this.qualifier = qualifier; + this.numScenarios = numScenarios; + this.expectedMessage = expectedMessage; + } + + @Test + public void generateIntroSnippetWithoutDescriptionForQualifiedScenarios() { + // given + AsciiDocSnippetGenerator generator = new AsciiDocSnippetGenerator(title, qualifier, numScenarios); + + // when + List blocks = generator.generateIntroSnippet(""); + + assertThat(blocks).containsExactly("== " + title, expectedMessage); + } + + @Test + public void generateIntroSnippetWithDescriptionForQualifiedScenarios() { + // given + AsciiDocSnippetGenerator generator = new AsciiDocSnippetGenerator(title, qualifier, numScenarios); + + // when + final String description = """ + As a tester
+ I want to set some nice description
+ so that the report speaks for itself."""; + List blocks = generator.generateIntroSnippet(description); + + assertThat(blocks).containsExactly("== " + title, "+++\n" + description + "\n+++", expectedMessage); + } +} diff --git a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java index 51c4d513c94..a467e52b476 100644 --- a/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java +++ b/jgiven-core/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java @@ -2,14 +2,16 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.ArrayList; import java.util.List; + import org.junit.Test; +import java.util.ArrayList; + public class AsciiDocSnippetGeneratorTest { @Test - public void writeIndexFileForFailedScenarios() { + public void generateIndexForFailedScenarios() { // given final List featureFileNames = new ArrayList<>(); featureFileNames.add("com.example.application.FailedScenarioOne"); @@ -17,7 +19,7 @@ public void writeIndexFileForFailedScenarios() { // when AsciiDocSnippetGenerator asciiDocSnippetGenerator = new AsciiDocSnippetGenerator( - "Failed Scenarios", "failed scenarios", 3); + "Failed Scenarios", "failed", 3); final List blocks = asciiDocSnippetGenerator.generateIndexSnippet("features", featureFileNames, "scenario-failed", -1); // then @@ -28,15 +30,13 @@ public void writeIndexFileForFailedScenarios() { ":leveloffset: +1"); } - @Test - public void writeIndexFileForAllScenarios() { + public void generateIndexForAllScenarios() { // given final List featureFileNames = new ArrayList<>(); featureFileNames.add("com.example.application.BigFeature"); featureFileNames.add("com.example.application.OtherFeature"); - // when AsciiDocSnippetGenerator asciiDocSnippetGenerator = new AsciiDocSnippetGenerator( "All Scenarios", "scenarios in total", 40); @@ -47,5 +47,4 @@ public void writeIndexFileForAllScenarios() { "include::features/com.example.application.BigFeature.asciidoc[]", "include::features/com.example.application.OtherFeature.asciidoc[]"); } - } From 4a373d1c7ebf5425419b4c362c73e6ca47f5dfe3 Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Sun, 18 May 2025 21:50:00 +0200 Subject: [PATCH 10/15] Fix intro for aborted scenarios and add them to report Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java | 2 +- .../resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java index ebaf6239876..0ea8e2de535 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java @@ -168,7 +168,7 @@ private void writeIndexFileForAbortedScenarios() { final int numAbortedScenarios = this.completeReportModel.getTotalStatistics().numAbortedScenarios; final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "Aborted Scenarios", "aborted scenarios", numAbortedScenarios); + "Aborted Scenarios", "aborted", numAbortedScenarios); final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet( diff --git a/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc b/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc index 54a5b779925..ae5d7f01722 100644 --- a/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc +++ b/jgiven-core/src/main/resources/com/tngtech/jgiven/report/asciidoc/index.asciidoc @@ -11,4 +11,6 @@ include::failedScenarios.asciidoc[] include::pendingScenarios.asciidoc[] +include::abortedScenarios.asciidoc[] + include::allTags.asciidoc[] From b85acdd96bb42a87f1dff9edcfeedfcca56d4e86 Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Fri, 11 Jul 2025 22:43:55 +0200 Subject: [PATCH 11/15] Fix section intros Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../asciidoc/AsciiDocReportGenerator.java | 8 ++-- .../asciidoc/AsciiDocSnippetGenerator.java | 37 ++++++++----------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java index 0ea8e2de535..3cd881b03bd 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java @@ -46,6 +46,7 @@ public class AsciiDocReportGenerator extends AbstractReportGenerator { private static final String FEATURE_PATH = "features"; private static final String ASCIIDOC_FILETYPE = ".asciidoc"; private static final String SCENARIO_TAG = "scenario-"; + private static final String TAGGED_SCENARIO_QUALIFIER = "tagged"; private static final Logger log = LoggerFactory.getLogger(AsciiDocReportGenerator.class); private final AsciiDocBlockConverter blockConverter = new AsciiDocBlockConverter(); @@ -197,7 +198,7 @@ private void writeIndexFileForTaggedScenarios(final String tagType, final Map singleValuedTag(final Map> taggedScenarios, final Tag tag, final int numTaggedScenarios) { final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - tag.toString(), "tagged", numTaggedScenarios); + tag.toString(), TAGGED_SCENARIO_QUALIFIER, numTaggedScenarios); final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(tag.getDescription()); @@ -213,9 +214,10 @@ private List singleValuedTag(final Map> taggedScena private List multiValuedTag(final Map> taggedScenarios, final Tag tag, final int numTaggedScenarios) { final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - tag.getName(), "tagged", numTaggedScenarios); + tag.getName(), TAGGED_SCENARIO_QUALIFIER, numTaggedScenarios); final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(tag.getDescription()); + taggedScenarios.forEach((tagId, features) -> { final List snippet = snippetGenerator.generateTagSnippet( allTags.get(tagId), taggedScenarioCounts.get(tagId), features); @@ -236,7 +238,7 @@ private void writeIndexFileForAllTags(final Map .toList(); final Integer total = taggedScenarioCounts.values().stream().reduce(Integer::sum).orElse(999); final AsciiDocSnippetGenerator snippetGenerator = new AsciiDocSnippetGenerator( - "Tags", "all tags are beautiful", total + "Tags", TAGGED_SCENARIO_QUALIFIER, total ); final List asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java index 66c439f4ec5..c57dcd90554 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGenerator.java @@ -33,15 +33,7 @@ List generateIntroSnippet(final String description) { result.add("+++" + LINE_BREAK + description + LINE_BREAK + "+++"); } - final String qualifiedScenario = scenarioQualifier.isBlank() ? "scenario" : scenarioQualifier + " scenario"; - - if (numScenarios <= 0) { - result.add("There are no " + qualifiedScenario + "s. Keep rocking!"); - } else if (numScenarios == 1) { - result.add("There is " + numScenarios + " " + qualifiedScenario + "."); - } else { - result.add("There are " + numScenarios + " " + qualifiedScenario + "s."); - } + result.add(createIntroSentence(numScenarios, scenarioQualifier)); return result; } @@ -63,18 +55,9 @@ List generateTagSnippet(final Tag tag, int scenarioCount, final List generateIncludeSnippet( return result; } + private String createIntroSentence(final int scenarioCount, final String scenarioQualifier) { + final String qualifiedScenario = scenarioQualifier.isBlank() ? "scenario" : scenarioQualifier + " scenario"; + + if (scenarioCount <= 0) { + return "There are no " + qualifiedScenario + "s. Keep rocking!"; + } else if (scenarioCount == 1) { + return "There is " + scenarioCount + " " + qualifiedScenario + "."; + } else { + return "There are " + scenarioCount + " " + qualifiedScenario + "s."; + } + } + private static String includeMacroFor(final String featurePath, final String featureName, final String tags) { return "include::" + featurePath + "/" + featureName + ".asciidoc[" + tags + "]"; } From 833e2f17218154e9b4faf720a2a4cbced09e3791 Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Sun, 5 Oct 2025 22:29:34 +0200 Subject: [PATCH 12/15] Cleanup import statements Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../jgiven/report/asciidoc/AsciiDocReportGenerator.java | 6 +----- .../report/asciidoc/AsciiDocSnippetGeneratorTest.java | 4 +--- .../com/tngtech/jgiven/report/asciidoc/TagMapperTest.java | 3 +-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java index 5b029b368f6..5204a979e8d 100644 --- a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java +++ b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java @@ -1,6 +1,5 @@ package com.tngtech.jgiven.report.asciidoc; -import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Multimaps; import com.google.common.io.Files; @@ -14,10 +13,7 @@ import com.tngtech.jgiven.report.model.Tag; import java.io.File; import java.io.IOException; -import java.io.PrintWriter; -import java.net.URL; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -25,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -250,6 +245,7 @@ private void writeIndexFileForAllTags(final Map private void writeTotalStatisticsFile() { final var featureStatistics = completeReportModel.getAllReportModels() .stream() + .filter(modelFile -> modelFile.model() != null) .collect(Multimaps.toMultimap( modelFile -> modelFile.model().getName(), completeReportModel::getStatistics, diff --git a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java index a467e52b476..86c60dd7b20 100644 --- a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java +++ b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocSnippetGeneratorTest.java @@ -2,12 +2,10 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.ArrayList; import java.util.List; - import org.junit.Test; -import java.util.ArrayList; - public class AsciiDocSnippetGeneratorTest { @Test diff --git a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java index 1c04f96ab5b..fbd803467db 100644 --- a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java +++ b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java @@ -3,9 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import com.tngtech.jgiven.report.model.Tag; -import org.junit.Test; - import java.util.List; +import org.junit.Test; public class TagMapperTest { @Test From 1731e183ec30d5daed7d5346b66489024a0f81df Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Sun, 5 Oct 2025 22:35:03 +0200 Subject: [PATCH 13/15] Fix some more grammar errors Also reformat Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../jgiven/report/ReportBlockConverter.java | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java index 011ced84e4d..a8bb1cda3ae 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java @@ -20,11 +20,12 @@ public interface ReportBlockConverter { * @param featureStatistics a map from feature names to statistics * @param totalStatistics the total statistics for all features combined */ - String convertStatisticsBlock(ListMultimap featureStatistics, - ReportStatistics totalStatistics); + String convertStatisticsBlock( + ListMultimap featureStatistics, + ReportStatistics totalStatistics); /** - * Convert feature name and execution statistics into feature header. + * Convert feature name and execution statistics into the feature header. * * @param featureName the feature's name * @param statistics the execution statistics for the feature @@ -33,7 +34,7 @@ String convertStatisticsBlock(ListMultimap featureStat String convertFeatureHeaderBlock(String featureName, ReportStatistics statistics, String description); /** - * Convert scenario name and more meta information into scenario header. + * Convert the scenario name and more meta information into the scenario header. *

* The name corresponds to the test method name *

@@ -44,8 +45,12 @@ String convertStatisticsBlock(ListMultimap featureStat * @param tags tags the scenario is tagged with * @param extendedDescription detailed description of the scenario, may be {@code null} */ - String convertScenarioHeaderBlock(String name, ExecutionStatus executionStatus, long duration, - List tags, String extendedDescription); + String convertScenarioHeaderBlock( + String name, + ExecutionStatus executionStatus, + long duration, + List tags, + String extendedDescription); /** * Convert scenario case number and parameters into case header. @@ -64,27 +69,38 @@ String convertScenarioHeaderBlock(String name, ExecutionStatus executionStatus, * * @param depth the depth of the step * @param words the words to be converted - * @param status was the step executed successfully + * @param status was the step executed successfully? * @param durationInNanos how long did the step take? * @param extendedDescription detailed description of the step, may be {@code null} * @param caseIsUnsuccessful was the scenario case executed successfully? * @param currentSectionTitle the current section's title, may be {@code null} */ - String convertFirstStepBlock(int depth, List words, StepStatus status, long durationInNanos, - String extendedDescription, boolean caseIsUnsuccessful, String currentSectionTitle); + String convertFirstStepBlock( + int depth, + List words, + StepStatus status, + long durationInNanos, + String extendedDescription, + boolean caseIsUnsuccessful, + String currentSectionTitle); /** * Convert the words that make up a step into a block. * * @param depth the depth of the step * @param words the words to be converted - * @param status was the step executed successfully + * @param status was the step executed successfully? * @param durationInNanos how long did the step take? * @param extendedDescription detailed description of the step, may be {@code null} * @param caseIsUnsuccessful was the scenario case executed successfully? */ - String convertStepBlock(int depth, List words, StepStatus status, long durationInNanos, - String extendedDescription, boolean caseIsUnsuccessful); + String convertStepBlock( + int depth, + List words, + StepStatus status, + long durationInNanos, + String extendedDescription, + boolean caseIsUnsuccessful); /** * Is invoked at the end of a scenario, when the scenario has multiple case and a data table. From d5b8bef937bf992f8791fa9c4d69e7bca7ea5968 Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Sun, 5 Oct 2025 22:41:09 +0200 Subject: [PATCH 14/15] Reformat Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../asciidoc/AsciiDocBlockConverter.java | 91 +++++++++++++------ 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java index d6691a9a6c4..9255e3c79f0 100644 --- a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java +++ b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java @@ -27,8 +27,9 @@ class AsciiDocBlockConverter implements ReportBlockConverter { private static final Pattern MULTILINE_PATTERN = Pattern.compile("\\R"); @Override - public String convertStatisticsBlock(final ListMultimap featureStatistics, - final ReportStatistics totalStatistics) { + public String convertStatisticsBlock( + final ListMultimap featureStatistics, + final ReportStatistics totalStatistics) { final StringBuilder statisticsTable = new StringBuilder(); statisticsTable.append(".Total Statistics").append(LINE_BREAK); @@ -58,8 +59,10 @@ public String convertStatisticsBlock(final ListMultimap tags, - final String extendedDescription) { + public String convertScenarioHeaderBlock( + final String name, + final ExecutionStatus executionStatus, + final long duration, + final List tags, + final String extendedDescription) { StringBuilder blockContent = new StringBuilder(); blockContent.append(MetadataMapper.toAsciiDocStartTag(executionStatus)).append(LINE_BREAK); @@ -122,8 +128,11 @@ public String convertScenarioHeaderBlock(final String name, final ExecutionStatu } @Override - public String convertCaseHeaderBlock(final int caseNr, final ExecutionStatus executionStatus, - final long duration, final String description) { + public String convertCaseHeaderBlock( + final int caseNr, + final ExecutionStatus executionStatus, + final long duration, + final String description) { StringBuilder blockContent = new StringBuilder(); blockContent.append("===== Case ").append(caseNr); @@ -140,10 +149,14 @@ public String convertCaseHeaderBlock(final int caseNr, final ExecutionStatus exe } @Override - public String convertFirstStepBlock(final int depth, final List words, final StepStatus status, - final long durationInNanos, - final String extendedDescription, final boolean caseIsUnsuccessful, - final String currentSectionTitle) { + public String convertFirstStepBlock( + final int depth, + final List words, + final StepStatus status, + final long durationInNanos, + final String extendedDescription, + final boolean caseIsUnsuccessful, + final String currentSectionTitle) { StringBuilder blockContent = new StringBuilder(); @@ -159,9 +172,13 @@ public String convertFirstStepBlock(final int depth, final List words, fin } @Override - public String convertStepBlock(final int depth, final List words, final StepStatus status, - final long durationInNanos, - final String extendedDescription, final boolean caseIsUnsuccessful) { + public String convertStepBlock( + final int depth, + final List words, + final StepStatus status, + final long durationInNanos, + final String extendedDescription, + final boolean caseIsUnsuccessful) { StringBuilder blockContent = new StringBuilder(); @@ -205,8 +222,10 @@ public String convertCasesTableBlock(final CasesTable casesTable) { return blockContent.toString(); } - private static void convertCaseRow(final StringBuilder blockContent, final int columnCount, - final CasesTable.CaseRow caseRow) { + private static void convertCaseRow( + final StringBuilder blockContent, + final int columnCount, + final CasesTable.CaseRow caseRow) { Optional errorMessage = caseRow.errorMessage(); blockContent.append(errorMessage.isPresent() ? ".2+| " : "| ").append(caseRow.rowNumber()); @@ -249,8 +268,10 @@ public String convertScenarioFooterBlock(final ExecutionStatus executionStatus, return blockContent.toString(); } - private static boolean appendWordFragments(final StringBuilder blockContent, final List words, - final String statusFragment) { + private static boolean appendWordFragments( + final StringBuilder blockContent, + final List words, + final String statusFragment) { boolean statusAppended = false; boolean lastFragmentWasBlockFragment = false; for (Word word : words) { @@ -294,9 +315,12 @@ private static boolean argumentContainsLineBreaks(String argumentValue) { return MULTILINE_PATTERN.matcher(argumentValue).find(); } - private static boolean appendFragment(final StringBuilder blockContent, final boolean lastFragmentWasBlockFragment, - final String statusFragment, final boolean statusAlreadyAppended, - final String fragment) { + private static boolean appendFragment( + final StringBuilder blockContent, + final boolean lastFragmentWasBlockFragment, + final String statusFragment, + final boolean statusAlreadyAppended, + final String fragment) { final String lineContinuation = lastFragmentWasBlockFragment ? "" : " "; if (fragment.contains(LINE_BREAK)) { if (!statusAlreadyAppended && !statusFragment.isBlank()) { @@ -311,7 +335,9 @@ private static boolean appendFragment(final StringBuilder blockContent, final bo } private static void appendErrorFragment( - final StringBuilder blockContent, final String errorMessage, final List stackTraceLines) { + final StringBuilder blockContent, + final String errorMessage, + final List stackTraceLines) { blockContent.append("[.jg-exception]").append(LINE_BREAK); blockContent.append("====").append(LINE_BREAK); blockContent.append("[%hardbreaks]").append(LINE_BREAK); @@ -333,8 +359,10 @@ private static void appendErrorFragment( blockContent.append("====").append(LINE_BREAK); } - private static void appendStatisticsRowFragment(final StringBuilder builder, final String name, - final ReportStatistics statistics) { + private static void appendStatisticsRowFragment( + final StringBuilder builder, + final String name, + final ReportStatistics statistics) { builder.append("| ").append(name); builder.append(" | ").append(statistics.numClasses); builder.append(" | ").append(statistics.numSuccessfulScenarios); @@ -349,8 +377,10 @@ private static void appendStatisticsRowFragment(final StringBuilder builder, fin .append(LINE_BREAK); } - private static String buildStepStatusFragment(final boolean caseIsUnsuccessful, final StepStatus status, - final long duration) { + private static String buildStepStatusFragment( + final boolean caseIsUnsuccessful, + final StepStatus status, + final long duration) { final String humanReadableStatus = caseIsUnsuccessful ? MetadataMapper.toHumanReadableStatus(status) : ""; final String humanReadableStepDuration = MetadataMapper.toHumanReadableStepDuration(duration); @@ -422,8 +452,9 @@ private static String buildOtherWordFragment(final String word, final boolean di } } - private static String buildExtendedDescriptionFragment(final boolean lastFragmentIsBlock, - final String extendedDescription) { + private static String buildExtendedDescriptionFragment( + final boolean lastFragmentIsBlock, + final String extendedDescription) { String fragment = ""; if (extendedDescription != null && !extendedDescription.isEmpty()) { From 1866f6adf420faab5ffa688ccfba53a70eceac31 Mon Sep 17 00:00:00 2001 From: Johannes Thorn <2544827+johthor@users.noreply.github.com> Date: Mon, 6 Oct 2025 00:07:53 +0200 Subject: [PATCH 15/15] Add testMethodName as another AsciiDoc tag around the scenario Also optimize some tag prefixes to improve the understanding. Issue: #54 Signed-off-by: Johannes Thorn <2544827+johthor@users.noreply.github.com> --- .../asciidoc/AsciiDocBlockConverter.java | 8 +- .../asciidoc/AsciiDocReportGenerator.java | 11 ++- .../asciidoc/AsciiDocReportModelVisitor.java | 9 ++- .../report/asciidoc/MetadataMapper.java | 77 +++++++++---------- .../jgiven/report/asciidoc/TagMapper.java | 2 +- .../AsciiDocReportModelVisitorTest.java | 49 ++++++++---- .../AsciiDocScenarioBlockConverterTest.java | 59 ++++++++------ .../report/asciidoc/MetadataMapperTest.java | 46 +++++------ .../jgiven/report/asciidoc/TagMapperTest.java | 16 ++-- .../jgiven/report/ReportBlockConverter.java | 19 ++--- 10 files changed, 166 insertions(+), 130 deletions(-) diff --git a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java index 9255e3c79f0..d02b0497b35 100644 --- a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java +++ b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocBlockConverter.java @@ -50,6 +50,7 @@ public String convertStatisticsBlock( featureStatistics.entries().stream() .sorted(Map.Entry.comparingByKey()) + .filter(entry -> entry.getValue() != null) .forEach(entry -> appendStatisticsRowFragment(statisticsTable, entry.getKey(), entry.getValue())); appendStatisticsRowFragment(statisticsTable, "sum", totalStatistics); @@ -91,6 +92,7 @@ public String convertFeatureHeaderBlock( @Override public String convertScenarioHeaderBlock( + final String identifier, final String name, final ExecutionStatus executionStatus, final long duration, @@ -98,6 +100,7 @@ public String convertScenarioHeaderBlock( final String extendedDescription) { StringBuilder blockContent = new StringBuilder(); + blockContent.append(MetadataMapper.toAsciiDocStartTag(identifier)).append(LINE_BREAK); blockContent.append(MetadataMapper.toAsciiDocStartTag(executionStatus)).append(LINE_BREAK); tags.forEach(tag -> blockContent.append(TagMapper.toAsciiDocStartTag(tag)).append(LINE_BREAK)); @@ -258,12 +261,13 @@ public String convertCaseFooterBlock(final String errorMessage, final List tags) { + public String convertScenarioFooterBlock(final String identifier, final ExecutionStatus executionStatus, final List tags) { StringBuilder blockContent = new StringBuilder(); Lists.reverse(tags).forEach(tag -> blockContent.append(TagMapper.toAsciiDocEndTag(tag)).append(LINE_BREAK)); - blockContent.append(MetadataMapper.toAsciiDocEndTag(executionStatus)); + blockContent.append(MetadataMapper.toAsciiDocEndTag(executionStatus)).append(LINE_BREAK); + blockContent.append(MetadataMapper.toAsciiDocEndTag(identifier)); return blockContent.toString(); } diff --git a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java index 5204a979e8d..04c16ad2926 100644 --- a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java +++ b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportGenerator.java @@ -7,6 +7,7 @@ import com.tngtech.jgiven.impl.util.PrintWriterUtil; import com.tngtech.jgiven.report.AbstractReportConfig; import com.tngtech.jgiven.report.AbstractReportGenerator; +import com.tngtech.jgiven.report.model.ExecutionStatus; import com.tngtech.jgiven.report.model.ReportModel; import com.tngtech.jgiven.report.model.ReportModelFile; import com.tngtech.jgiven.report.model.ReportStatistics; @@ -41,7 +42,6 @@ public class AsciiDocReportGenerator extends AbstractReportGenerator { private static final String FEATURE_PATH = "features"; private static final String ASCIIDOC_FILETYPE = ".asciidoc"; - private static final String SCENARIO_TAG = "scenario-"; private static final String TAGGED_SCENARIO_QUALIFIER = "tagged"; private static final Logger log = LoggerFactory.getLogger(AsciiDocReportGenerator.class); @@ -143,7 +143,8 @@ private void writeIndexFileForFailedScenarios() { final var asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet( - FEATURE_PATH, this.failedScenarioFeatures, SCENARIO_TAG + "failed", -1)); + FEATURE_PATH, this.failedScenarioFeatures, + MetadataMapper.toAsciiDocTagName(ExecutionStatus.FAILED), -1)); writeAsciiDocBlocksToFile(targetDir, "failedScenarios", asciiDocBlocks); } @@ -156,7 +157,8 @@ private void writeIndexFileForPendingScenarios() { final var asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet( - FEATURE_PATH, this.pendingScenarioFeatures, SCENARIO_TAG + "pending", -1)); + FEATURE_PATH, this.pendingScenarioFeatures, + MetadataMapper.toAsciiDocTagName(ExecutionStatus.SCENARIO_PENDING), -1)); writeAsciiDocBlocksToFile(targetDir, "pendingScenarios", asciiDocBlocks); } @@ -169,7 +171,8 @@ private void writeIndexFileForAbortedScenarios() { final var asciiDocBlocks = snippetGenerator.generateIntroSnippet(""); asciiDocBlocks.addAll(snippetGenerator.generateIndexSnippet( - FEATURE_PATH, this.abortedScenarioFeatures, SCENARIO_TAG + "aborted", -1)); + FEATURE_PATH, this.abortedScenarioFeatures, + MetadataMapper.toAsciiDocTagName(ExecutionStatus.ABORTED), -1)); writeAsciiDocBlocksToFile(targetDir, "abortedScenarios", asciiDocBlocks); } diff --git a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java index 8f419f4b9dd..8f611b5b137 100644 --- a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java +++ b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitor.java @@ -65,9 +65,9 @@ public void visit(final ScenarioModel scenarioModel) { .map(this.featureTagMap::get) .collect(Collectors.toList()); - String scenarioHeader = blockConverter.convertScenarioHeaderBlock(scenarioModel.getDescription(), - scenarioModel.getExecutionStatus(), scenarioModel.getDurationInNanos(), tagList, - scenarioModel.getExtendedDescription()); + String scenarioHeader = blockConverter.convertScenarioHeaderBlock(scenarioModel.getTestMethodName(), + scenarioModel.getDescription(), scenarioModel.getExecutionStatus(), scenarioModel.getDurationInNanos(), + tagList, scenarioModel.getExtendedDescription()); asciiDocBlocks.add(scenarioHeader); scenarioHasDataTable = scenarioModel.isCasesAsTable(); @@ -137,7 +137,8 @@ public void visitEnd(final ScenarioModel scenarioModel) { asciiDocBlocks.add(casesTableBlock); } - String scenarioFooter = blockConverter.convertScenarioFooterBlock(scenarioModel.getExecutionStatus(), tagList); + String scenarioFooter = blockConverter.convertScenarioFooterBlock( + scenarioModel.getTestMethodName(), scenarioModel.getExecutionStatus(), tagList); asciiDocBlocks.add(scenarioFooter); } diff --git a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/MetadataMapper.java b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/MetadataMapper.java index ad53ecb0925..55c945fe2b1 100644 --- a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/MetadataMapper.java +++ b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/MetadataMapper.java @@ -16,6 +16,18 @@ private MetadataMapper() { // static helper class isn't intended to be instantiated } + public static String toAsciiDocStartTag(final String scenarioName) { + return "// tag::" + toAsciiDocTagName(scenarioName) + "[]"; + } + + static String toAsciiDocEndTag(final String scenarioName) { + return "// end::" + toAsciiDocTagName(scenarioName) + "[]"; + } + + static String toAsciiDocTagName(final String scenarioName) { + return "scenario-" + scenarioName; + } + static String toAsciiDocStartTag(ExecutionStatus executionStatus) { return "// tag::" + toAsciiDocTagName(executionStatus) + "[]"; } @@ -24,49 +36,32 @@ static String toAsciiDocEndTag(ExecutionStatus executionStatus) { return "// end::" + toAsciiDocTagName(executionStatus) + "[]"; } - private static String toAsciiDocTagName(final ExecutionStatus executionStatus) { - switch (executionStatus) { - case SCENARIO_PENDING: - case SOME_STEPS_PENDING: - return "scenario-pending"; - case SUCCESS: - return "scenario-successful"; - case FAILED: - return "scenario-failed"; - default: - return "scenario-" + executionStatus.toString().toLowerCase(); - } + static String toAsciiDocTagName(final ExecutionStatus executionStatus) { + return switch (executionStatus) { + case SUCCESS -> "status-is-successful"; + case SCENARIO_PENDING, SOME_STEPS_PENDING -> "status-is-pending"; + case ABORTED -> "status-is-aborted"; + case FAILED -> "status-is-failed"; + }; } static String toHumanReadableStatus(final ExecutionStatus executionStatus) { - switch (executionStatus) { - case SCENARIO_PENDING: - case SOME_STEPS_PENDING: - return ICON_BANNED; - case SUCCESS: - return ICON_CHECK_MARK; - case FAILED: - return ICON_EXCLAMATION_MARK; - case ABORTED: - return ICON_TIMES_CIRCLE; - default: - return executionStatus.toString(); - } + return switch (executionStatus) { + case SUCCESS -> ICON_CHECK_MARK; + case SCENARIO_PENDING, SOME_STEPS_PENDING -> ICON_BANNED; + case ABORTED -> ICON_TIMES_CIRCLE; + case FAILED -> ICON_EXCLAMATION_MARK; + }; } static String toHumanReadableStatus(final StepStatus stepStatus) { - switch (stepStatus) { - case PASSED: - return ICON_CHECK_MARK; - case FAILED: - return ICON_EXCLAMATION_MARK; - case SKIPPED: - return ICON_STEP_FORWARD; - case PENDING: - return ICON_BANNED; - default: - return stepStatus.toString(); - } + return switch (stepStatus) { + case PASSED -> ICON_CHECK_MARK; + case SKIPPED -> ICON_STEP_FORWARD; + case PENDING -> ICON_BANNED; + case ABORTED -> ICON_TIMES_CIRCLE; + case FAILED -> ICON_EXCLAMATION_MARK; + }; } static String toHumanReadableScenarioDuration(final long durationInNanos) { @@ -86,11 +81,11 @@ static String toHumanReadableStepDuration(final long durationInNanos) { } private static String toHumanReadableDuration(final long nanos) { - final Duration duration = Duration.ofNanos(nanos); - final String millisFragment = duration.getNano() / NANOSECONDS_PER_MILLISECOND + "ms"; + final var duration = Duration.ofNanos(nanos); + final var millisFragment = duration.getNano() / NANOSECONDS_PER_MILLISECOND + "ms"; - final long seconds = duration.getSeconds(); - final String secondsFragment = seconds > 0 ? seconds + "s " : ""; + final var seconds = duration.getSeconds(); + final var secondsFragment = seconds > 0 ? seconds + "s " : ""; return secondsFragment + millisFragment; } diff --git a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java index bdd2d1014f0..faa325e8089 100644 --- a/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java +++ b/jgiven-asciidoc-report/src/main/java/com/tngtech/jgiven/report/asciidoc/TagMapper.java @@ -22,6 +22,6 @@ static String toAsciiDocEndTag(final Tag tag) { } static String toAsciiDocTagName(final Tag tag) { - return tag.toIdString().replace(' ', '_'); + return "tag-" + tag.toIdString().replace(' ', '_'); } } diff --git a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java index 62091b4bb8d..f5471701ef7 100644 --- a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java +++ b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocReportModelVisitorTest.java @@ -177,9 +177,9 @@ public void visits_a_scenario_with_two_sections() { mkStep("When", "action"), mkStep("Then", "outcome"), mkSectionTitle("Second Section"), - mkStep("Given", "other state"), + mkStep("Given", "another state"), mkStep("When", "other action"), - mkStep("Then", "other outcome")))); + mkStep("Then", "another outcome")))); // when report.accept(reportModelVisitor); @@ -243,39 +243,60 @@ private static StepModel mkStep(final String introWord, final String object) { private static class MyFakeReportBlockConverter implements ReportBlockConverter { @Override - public String convertStatisticsBlock(final ListMultimap featureStatistics, + public String convertStatisticsBlock( + final ListMultimap featureStatistics, final ReportStatistics totalStatistics) { return "StatisticsBlock"; } @Override - public String convertFeatureHeaderBlock(String featureName, ReportStatistics statistics, + public String convertFeatureHeaderBlock( + String featureName, + ReportStatistics statistics, String description) { return "FeatureHeaderBlock"; } @Override - public String convertScenarioHeaderBlock(String name, ExecutionStatus executionStatus, long duration, - List tags, String extendedDescription) { + public String convertScenarioHeaderBlock( + final String identifier, + String name, + ExecutionStatus executionStatus, + long duration, + List tags, + String extendedDescription) { return "ScenarioHeaderBlock"; } @Override - public String convertCaseHeaderBlock(final int caseNr, final ExecutionStatus executionStatus, - final long duration, final String description) { + public String convertCaseHeaderBlock( + final int caseNr, + final ExecutionStatus executionStatus, + final long duration, + final String description) { return "CaseHeaderBlock"; } @Override - public String convertFirstStepBlock(final int depth, final List words, final StepStatus status, - final long durationInNanos, final String extendedDescription, - final boolean caseIsUnsuccessful, final String currentSectionTitle) { + public String convertFirstStepBlock( + final int depth, + final List words, + final StepStatus status, + final long durationInNanos, + final String extendedDescription, + final boolean caseIsUnsuccessful, + final String currentSectionTitle) { return "FirstStepBlock"; } @Override - public String convertStepBlock(int depth, List words, StepStatus status, long durationInNanos, - String extendedDescription, boolean caseIsUnsuccessful) { + public String convertStepBlock( + int depth, + List words, + StepStatus status, + long durationInNanos, + String extendedDescription, + boolean caseIsUnsuccessful) { return "StepBlock"; } @@ -290,7 +311,7 @@ public String convertCaseFooterBlock(final String errorMessage, final List tags) { + public String convertScenarioFooterBlock(final String identifier, ExecutionStatus executionStatus, final List tags) { return "ScenarioFooterBlock"; } } diff --git a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocScenarioBlockConverterTest.java b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocScenarioBlockConverterTest.java index 21a80e5152d..41244322970 100644 --- a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocScenarioBlockConverterTest.java +++ b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/AsciiDocScenarioBlockConverterTest.java @@ -28,11 +28,12 @@ public void convert_scenario_header_without_tags_or_description(final ExecutionS long oneSecond = 1_000_000_000L; // when - String block = converter.convertScenarioHeaderBlock("my first scenario", status, oneSecond, tags, null); + String block = converter.convertScenarioHeaderBlock("method_1", "my first scenario", status, oneSecond, tags, null); // then assertThatBlockContainsLines(block, - "// tag::scenario-" + scenarioTag + "[]", + "// tag::scenario-method_1[]", + "// tag::status-is-" + scenarioTag + "[]", "", "==== My first scenario", "", @@ -46,13 +47,14 @@ public void convert_scenario_header_with_a_tag_and_no_description() { long nineMilliseconds = 10_000_000L; // when - String block = converter.convertScenarioHeaderBlock("my first scenario", + String block = converter.convertScenarioHeaderBlock("method_1", "my first scenario", ExecutionStatus.SCENARIO_PENDING, nineMilliseconds, tags, ""); // then assertThatBlockContainsLines(block, - "// tag::scenario-pending[]", - "// tag::com.jgiven.ArbitraryTag-BestTag[]", + "// tag::scenario-method_1[]", + "// tag::status-is-pending[]", + "// tag::tag-com.jgiven.ArbitraryTag-BestTag[]", "", "==== My first scenario", "", @@ -68,12 +70,13 @@ public void convert_scenario_header_with_description_and_no_tags() { long halfMillisecond = 500_000L; // when - String block = converter.convertScenarioHeaderBlock("my first scenario", + String block = converter.convertScenarioHeaderBlock("method_1", "my first scenario", ExecutionStatus.SOME_STEPS_PENDING, halfMillisecond, tags, "Best scenario ever!!!"); // then assertThatBlockContainsLines(block, - "// tag::scenario-pending[]", + "// tag::scenario-method_1[]", + "// tag::status-is-pending[]", "", "==== My first scenario", "", @@ -90,13 +93,14 @@ public void convert_scenario_header_with_a_tag_and_description() { // when String block = - converter.convertScenarioHeaderBlock("my first scenario", ExecutionStatus.SUCCESS, threeSeconds, tags, + converter.convertScenarioHeaderBlock("method_1", "my first scenario", ExecutionStatus.SUCCESS, threeSeconds, tags, "Best scenario ever!!!"); // then assertThatBlockContainsLines(block, - "// tag::scenario-successful[]", - "// tag::com.jgiven.ArbitraryTag-BestTag[]", + "// tag::scenario-method_1[]", + "// tag::status-is-successful[]", + "// tag::tag-com.jgiven.ArbitraryTag-BestTag[]", "", "==== My first scenario", "", @@ -115,15 +119,16 @@ public void convert_scenario_header_with_multiple_tags() { // when String block = - converter.convertScenarioHeaderBlock("my first scenario", ExecutionStatus.SUCCESS, threeSeconds, + converter.convertScenarioHeaderBlock("method_1", "my first scenario", ExecutionStatus.SUCCESS, threeSeconds, tags, ""); // then assertThatBlockContainsLines(block, - "// tag::scenario-successful[]", - "// tag::com.jgiven.ArbitraryTag-BestTag[]", - "// tag::com.jgiven.ArbitraryTag-OtherTag[]", - "// tag::com.jgiven.ArbitraryTag-NicestTag[]", + "// tag::scenario-method_1[]", + "// tag::status-is-successful[]", + "// tag::tag-com.jgiven.ArbitraryTag-BestTag[]", + "// tag::tag-com.jgiven.ArbitraryTag-OtherTag[]", + "// tag::tag-com.jgiven.ArbitraryTag-NicestTag[]", "", "==== My first scenario", "", @@ -140,10 +145,12 @@ public void convert_scenario_footer_without_tags(final ExecutionStatus status, f List tags = List.of(); // when - String block = converter.convertScenarioFooterBlock(status, tags); + String block = converter.convertScenarioFooterBlock("method_3", status, tags); // then - assertThat(block).isEqualTo("// end::scenario-" + scenarioTag + "[]"); + assertThatBlockContainsLines(block, + "// end::status-is-" + scenarioTag + "[]", + "// end::scenario-method_3[]"); } @Test @@ -152,12 +159,13 @@ public void convert_scenario_footer_with_a_tag() { List tags = List.of(mkTag("BestTag")); // when - String block = converter.convertScenarioFooterBlock(ExecutionStatus.FAILED, tags); + String block = converter.convertScenarioFooterBlock("method_1", ExecutionStatus.FAILED, tags); // then assertThatBlockContainsLines(block, - "// end::com.jgiven.ArbitraryTag-BestTag[]", - "// end::scenario-failed[]"); + "// end::tag-com.jgiven.ArbitraryTag-BestTag[]", + "// end::status-is-failed[]", + "// end::scenario-method_1[]"); } @Test @@ -166,14 +174,15 @@ public void convert_scenario_footer_with_multiple_tags() { List tags = List.of(mkTag("BestTag"), mkTag("OtherTag"), mkTag("NicestTag")); // when - String block = converter.convertScenarioFooterBlock(ExecutionStatus.FAILED, tags); + String block = converter.convertScenarioFooterBlock("method_2", ExecutionStatus.FAILED, tags); // then assertThatBlockContainsLines(block, - "// end::com.jgiven.ArbitraryTag-NicestTag[]", - "// end::com.jgiven.ArbitraryTag-OtherTag[]", - "// end::com.jgiven.ArbitraryTag-BestTag[]", - "// end::scenario-failed[]"); + "// end::tag-com.jgiven.ArbitraryTag-NicestTag[]", + "// end::tag-com.jgiven.ArbitraryTag-OtherTag[]", + "// end::tag-com.jgiven.ArbitraryTag-BestTag[]", + "// end::status-is-failed[]", + "// end::scenario-method_2[]"); } private static Tag mkTag(final String value) { diff --git a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/MetadataMapperTest.java b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/MetadataMapperTest.java index a7aaf01b326..0354e3e1cc9 100644 --- a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/MetadataMapperTest.java +++ b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/MetadataMapperTest.java @@ -16,10 +16,12 @@ public class MetadataMapperTest { @Test @DataProvider({ - "SUCCESS, scenario-successful", - "FAILED, scenario-failed", - "SCENARIO_PENDING, scenario-pending", - "SOME_STEPS_PENDING, scenario-pending"}) + "SUCCESS, status-is-successful", + "SCENARIO_PENDING, status-is-pending", + "SOME_STEPS_PENDING, status-is-pending", + "ABORTED, status-is-aborted", + "FAILED, status-is-failed", + }) public void toAsciiDocTagName(final ExecutionStatus executionStatus, final String expectedName) { // when final String startSnippet = MetadataMapper.toAsciiDocStartTag(executionStatus); @@ -32,10 +34,10 @@ public void toAsciiDocTagName(final ExecutionStatus executionStatus, final Strin @Test @DataProvider({ - "SUCCESS, icon:check-square[role=green]", - "FAILED, icon:exclamation-circle[role=red]", - "SCENARIO_PENDING, icon:ban[role=silver]", - "SOME_STEPS_PENDING, icon:ban[role=silver]"}) + "SUCCESS, icon:check-square[role=green]", + "FAILED, icon:exclamation-circle[role=red]", + "SCENARIO_PENDING, icon:ban[role=silver]", + "SOME_STEPS_PENDING, icon:ban[role=silver]"}) public void toHumanReadableExecutionStatus(final ExecutionStatus executionStatus, final String expectedStatus) { final String actualStatus = MetadataMapper.toHumanReadableStatus(executionStatus); @@ -44,10 +46,10 @@ public void toHumanReadableExecutionStatus(final ExecutionStatus executionStatus @Test @DataProvider({ - "PASSED, icon:check-square[role=green]", - "FAILED, icon:exclamation-circle[role=red]", - "SKIPPED, icon:step-forward[role=silver]", - "PENDING, icon:ban[role=silver]"}) + "PASSED, icon:check-square[role=green]", + "FAILED, icon:exclamation-circle[role=red]", + "SKIPPED, icon:step-forward[role=silver]", + "PENDING, icon:ban[role=silver]"}) public void toHumanReadableStepStatus(final StepStatus stepStatus, final String expectedStatus) { final String actualStatus = MetadataMapper.toHumanReadableStatus(stepStatus); @@ -63,11 +65,11 @@ public void toScenarioDurationBelow1ms() { @Test @DataProvider({ - " 1000000, 1ms", - " 999999999, 999ms", - "1000000000, 1s 0ms", - "1000999999, 1s 0ms", - "1001000000, 1s 1ms"}) + " 1000000, 1ms", + " 999999999, 999ms", + "1000000000, 1s 0ms", + "1000999999, 1s 0ms", + "1001000000, 1s 1ms"}) public void toScenarioDurationForDurationOver1ms(final long nanoseconds, final String expectedDuration) { final String actualDuration = MetadataMapper.toHumanReadableScenarioDuration(nanoseconds); @@ -83,11 +85,11 @@ public void toStepDurationBelow10ms() { @Test @DataProvider({ - " 10000000, (10ms)", - " 999999999, (999ms)", - "1000000000, (1s 0ms)", - "1000999999, (1s 0ms)", - "1001000000, (1s 1ms)"}) + " 10000000, (10ms)", + " 999999999, (999ms)", + "1000000000, (1s 0ms)", + "1000999999, (1s 0ms)", + "1001000000, (1s 1ms)"}) public void toStepDurationForDurationOver1ms(final long nanoseconds, final String expectedDuration) { final String actualDuration = MetadataMapper.toHumanReadableStepDuration(nanoseconds); diff --git a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java index fbd803467db..88ac5573a49 100644 --- a/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java +++ b/jgiven-asciidoc-report/src/test/java/com/tngtech/jgiven/report/asciidoc/TagMapperTest.java @@ -98,8 +98,8 @@ public void simple_tag_to_AsciiDoc_tag() { final String endSnippet = TagMapper.toAsciiDocEndTag(tag); // then - assertThat(startSnippet).isEqualTo("// tag::com.tngtech.jgiven.tags.Feature[]"); - assertThat(endSnippet).isEqualTo("// end::com.tngtech.jgiven.tags.Feature[]"); + assertThat(startSnippet).isEqualTo("// tag::tag-com.tngtech.jgiven.tags.Feature[]"); + assertThat(endSnippet).isEqualTo("// end::tag-com.tngtech.jgiven.tags.Feature[]"); } @Test @@ -113,8 +113,8 @@ public void single_value_tag_to_AsciiDoc_tag() { final String endSnippet = TagMapper.toAsciiDocEndTag(tag); // then - assertThat(startSnippet).isEqualTo("// tag::com.tngtech.jgiven.tags.Feature-AsciiDoc[]"); - assertThat(endSnippet).isEqualTo("// end::com.tngtech.jgiven.tags.Feature-AsciiDoc[]"); + assertThat(startSnippet).isEqualTo("// tag::tag-com.tngtech.jgiven.tags.Feature-AsciiDoc[]"); + assertThat(endSnippet).isEqualTo("// end::tag-com.tngtech.jgiven.tags.Feature-AsciiDoc[]"); } @Test @@ -129,8 +129,8 @@ public void single_value_tag_with_type_to_AsciiDoc_tag() { final String endSnippet = TagMapper.toAsciiDocEndTag(tag); // then - assertThat(startSnippet).isEqualTo("// tag::com.tngtech.jgiven.tags.Issue-#1337[]"); - assertThat(endSnippet).isEqualTo("// end::com.tngtech.jgiven.tags.Issue-#1337[]"); + assertThat(startSnippet).isEqualTo("// tag::tag-com.tngtech.jgiven.tags.Issue-#1337[]"); + assertThat(endSnippet).isEqualTo("// end::tag-com.tngtech.jgiven.tags.Issue-#1337[]"); } @Test @@ -143,7 +143,7 @@ public void multiple_value_tag_to_AsciiDoc_tag() { final String endSnippet = TagMapper.toAsciiDocEndTag(tag); // then - assertThat(startSnippet).isEqualTo("// tag::com.tngtech.jgiven.tags.Feature-AsciiDoc,_Markdown[]"); - assertThat(endSnippet).isEqualTo("// end::com.tngtech.jgiven.tags.Feature-AsciiDoc,_Markdown[]"); + assertThat(startSnippet).isEqualTo("// tag::tag-com.tngtech.jgiven.tags.Feature-AsciiDoc,_Markdown[]"); + assertThat(endSnippet).isEqualTo("// end::tag-com.tngtech.jgiven.tags.Feature-AsciiDoc,_Markdown[]"); } } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java index a8bb1cda3ae..1884a803a09 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/ReportBlockConverter.java @@ -34,18 +34,18 @@ String convertStatisticsBlock( String convertFeatureHeaderBlock(String featureName, ReportStatistics statistics, String description); /** - * Convert the scenario name and more meta information into the scenario header. - *

- * The name corresponds to the test method name - *

+ * Converts a scenario's header information into a formatted string block. * + * @param identifier the unique identifier for the scenario * @param name the name of the scenario - * @param executionStatus was the scenario successful? - * @param duration how long did the scenario run? - * @param tags tags the scenario is tagged with - * @param extendedDescription detailed description of the scenario, may be {@code null} + * @param executionStatus the execution status of the scenario, such as SUCCESS or FAILED + * @param duration the execution duration of the scenario in milliseconds + * @param tags a list of tags associated with the scenario + * @param extendedDescription an extended description for the scenario; may be null + * @return a formatted string block representing the scenario header */ String convertScenarioHeaderBlock( + String identifier, String name, ExecutionStatus executionStatus, long duration, @@ -120,9 +120,10 @@ String convertStepBlock( /** * Is invoked at the end of a scenario. * + * @param identifier the unique identifier for the scenario * @param executionStatus was the scenario successful? * @param tags tags the scenario is tagged with */ - String convertScenarioFooterBlock(ExecutionStatus executionStatus, List tags); + String convertScenarioFooterBlock(final String identifier, ExecutionStatus executionStatus, List tags); }