runCliStarter( //
TRACE_COMMAND, this.DOC_DIR.toString(), //
OUTPUT_FILE_PARAMETER, this.outputFile.toString(), //
- WANTED_ARTIFACT_TYPES_PARAMETER, "feat,req"
- //
+ WANTED_ARTIFACT_TYPES_PARAMETER, "feat,req" //
);
assertExitOkWithOutputFileStart(runnable, "ok - 3 total");
}
@@ -381,7 +382,7 @@ private String getOutputFileContent()
}
catch (final IOException exception)
{
- // Need to convert this to an unchecked exception. Otherwise we get
+ // Need to convert this to an unchecked exception. Otherwise, we get
// stuck with the checked exceptions in the assertion lambdas.
throw new RuntimeException(exception);
}
diff --git a/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/AnsiSequence.java b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/AnsiSequence.java
new file mode 100644
index 00000000..682a613e
--- /dev/null
+++ b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/AnsiSequence.java
@@ -0,0 +1,76 @@
+package org.itsallcode.openfasttrace.report.plaintext;
+
+/**
+ * ANSI console font effect sequences
+ */
+// [impl->dsn~reporting.plain-text.ansi-color~1]
+// [impl-> dsn~reporting.plain-text.ansi-font-style~1]
+enum AnsiSequence {
+ /** Reset all font effects */
+ RESET(0),
+ /** Bold font */
+ BOLD(1),
+ /** Italic font */
+ ITALIC(3),
+ /** Underlined */
+ UNDERLINE(4),
+ /** Inverted foreground and background color */
+ INVERSE(7),
+ /** Black */
+ BLACK(30),
+ /** Red */
+ RED(31),
+ /** Green */
+ GREEN(32),
+ /** Yellow */
+ YELLOW(33),
+ /** Blue */
+ BLUE(34),
+ /** Magenta */
+ MAGENTA(35),
+ /** Cyan */
+ CYAN(36),
+ /** White */
+ WHITE(37),
+ /** Bright Red */
+ BRIGHT_RED(91);
+
+ public static final String PREFIX = "\u001B[";
+ public static final String SUFFIX = "m";
+ private final int id;
+
+ private AnsiSequence(final int id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return PREFIX + id + SUFFIX;
+ }
+
+ /**
+ * Get the ID of the ANSI sequence.
+ * @return ANSI sequence ID
+ */
+ public int getId() {
+ return this.id;
+ }
+
+ /**
+ * Combine the given ANSI sequences.
+ *
+ * @param ids IDs of the font effects that should be combined
+ * @return sequence that represents the combined font effect.
+ */
+ public static String combine(AnsiSequence ... ids) {
+ StringBuilder builder = new StringBuilder(PREFIX);
+ for(int i = 0; i < ids.length ; ++i) {
+ if (i > 0) {
+ builder.append(";");
+ }
+ builder.append(ids[i].id);
+ }
+ builder.append(SUFFIX);
+ return builder.toString();
+ }
+}
diff --git a/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/ConsoleColorFormatter.java b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/ConsoleColorFormatter.java
new file mode 100644
index 00000000..61bc3663
--- /dev/null
+++ b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/ConsoleColorFormatter.java
@@ -0,0 +1,31 @@
+package org.itsallcode.openfasttrace.report.plaintext;
+
+import static org.itsallcode.openfasttrace.report.plaintext.AnsiSequence.*;
+
+/**
+ * Formatter that uses ANSI code sequences to color the output.
+ */
+// [impl->dsn~reporting.plain-text.ansi-color~1]
+final class ConsoleColorFormatter implements TextFormatter {
+ /**
+ * Create a new instance of a {@link ConsoleColorFormatter}.
+ */
+ public ConsoleColorFormatter() {
+ // Added for JavaDoc.
+ }
+
+ @Override
+ public String formatOk(final String text) {
+ return GREEN + text + RESET;
+ }
+
+ @Override
+ public String formatNotOk(final String text) {
+ return BRIGHT_RED + text + RESET;
+ }
+
+ @Override
+ public String formatStrong(final String text) {
+ return AnsiSequence.combine(BOLD, CYAN) + text + RESET;
+ }
+}
\ No newline at end of file
diff --git a/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/MonochromeTextFormatter.java b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/MonochromeTextFormatter.java
new file mode 100644
index 00000000..78681223
--- /dev/null
+++ b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/MonochromeTextFormatter.java
@@ -0,0 +1,35 @@
+package org.itsallcode.openfasttrace.report.plaintext;
+
+import static org.itsallcode.openfasttrace.report.plaintext.AnsiSequence.*;
+
+/**
+ * Formatter that only uses font weight and style
+ *
+ * Useful for people who cannot distinguish colors or terminals that don't display colors or if colors are hard to
+ * discern with a console color scheme.
+ *
+ */
+// [impl->dsn~reporting.plain-text.ansi-font-style~1]
+public class MonochromeTextFormatter implements TextFormatter {
+ /**
+ * Create a new instance of a {@link MonochromeTextFormatter}.
+ */
+ public MonochromeTextFormatter() {
+ // Added for JavaDoc.
+ }
+
+ @Override
+ public String formatOk(final String text) {
+ return text;
+ }
+
+ @Override
+ public String formatNotOk(final String text) {
+ return INVERSE + text + RESET;
+ }
+
+ @Override
+ public String formatStrong(final String text) {
+ return BOLD + text + RESET;
+ }
+}
diff --git a/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/NullTextFormatter.java b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/NullTextFormatter.java
new file mode 100644
index 00000000..36771e76
--- /dev/null
+++ b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/NullTextFormatter.java
@@ -0,0 +1,31 @@
+package org.itsallcode.openfasttrace.report.plaintext;
+
+/**
+ * Pseudo text formatter that returns everything exactly as it was given.
+ *
+ * This is useful for reports that are piped into other tools or written to files.
+ *
+ */
+class NullTextFormatter implements TextFormatter {
+ /**
+ * Create a new instance of a {@link NullTextFormatter}.
+ */
+ public NullTextFormatter() {
+ // Added for JavaDoc.
+ }
+
+ @Override
+ public String formatOk(final String text) {
+ return text;
+ }
+
+ @Override
+ public String formatNotOk(final String text) {
+ return text;
+ }
+
+ @Override
+ public String formatStrong(final String text) {
+ return text;
+ }
+}
\ No newline at end of file
diff --git a/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/PlainTextReport.java b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/PlainTextReport.java
index bcc91c6a..16250bc5 100644
--- a/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/PlainTextReport.java
+++ b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/PlainTextReport.java
@@ -2,7 +2,6 @@
import java.io.OutputStream;
import java.io.PrintStream;
-import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
@@ -13,7 +12,6 @@
import org.itsallcode.openfasttrace.api.ReportSettings;
import org.itsallcode.openfasttrace.api.core.*;
-import org.itsallcode.openfasttrace.api.report.ReportException;
import org.itsallcode.openfasttrace.api.report.Reportable;
/**
@@ -27,6 +25,7 @@ public class PlainTextReport implements Reportable
.comparing(LinkedSpecificationItem::getId);
private int nonEmptySections = 0;
private final ReportSettings settings;
+ private final TextFormatter formatter;
/**
* Create a new instance of {@link PlainTextReport}
@@ -41,20 +40,17 @@ public PlainTextReport(final Trace trace, final ReportSettings settings)
{
this.trace = trace;
this.settings = settings;
+ this.formatter = TextFormatterFactory.createFormatter(settings.getColorScheme());
}
@Override
public void renderToStream(final OutputStream outputStream)
{
final Charset charset = StandardCharsets.UTF_8;
- try (final PrintStream report = new PrintStream(outputStream, false, charset.displayName()))
+ try (final PrintStream report = new PrintStream(outputStream, false, charset))
{
renderToPrintStream(report);
}
- catch (final UnsupportedEncodingException e)
- {
- throw new ReportException("Encoding charset '" + charset + "' not supported", e);
- }
}
private void renderToPrintStream(final PrintStream report)
@@ -109,7 +105,7 @@ private void renderResultStatus(final PrintStream report)
private String translateStatus(final boolean ok)
{
- return ok ? "ok" : "not ok";
+ return ok ? this.formatter.formatOk("ok") : this.formatter.formatNotOk("not ok");
}
// [impl->dsn~reporting.plain-text.summary~2]
@@ -149,13 +145,12 @@ private void renderFailureSummaries(final PrintStream report)
private void renderItemSummary(final PrintStream report, final LinkedSpecificationItem item)
{
report.print(translateStatus(!item.isDefect()));
- report.print(" - ");
renderItemLinkCounts(report, item);
- report.print(" - ");
- report.print(item.getId().toString());
+ report.print(this.formatter.formatStrong(item.getId().toString()));
report.print(" ");
renderMaturity(report, item);
report.print(translateArtifactTypeCoverage(item));
+ renderDuplicatesCount(report, item);
report.print(this.settings.getNewline());
}
@@ -164,13 +159,13 @@ private String translateArtifactTypeCoverage(final LinkedSpecificationItem item)
final Comparator byTypeName = Comparator.comparing(a -> a.replaceFirst("[-+]", ""));
final Stream uncoveredStream = item.getUncoveredArtifactTypes().stream()
- .map(x -> "-" + x);
+ .map(x -> this.formatter.formatNotOk("-" + x));
return "(" + Stream.concat( //
Stream.concat( //
uncoveredStream, //
- item.getCoveredArtifactTypes().stream() //
+ item.getCoveredArtifactTypes().stream().map(this.formatter::formatOk) //
), //
- item.getOverCoveredArtifactTypes().stream().map(x -> "+" + x) //
+ item.getOverCoveredArtifactTypes().stream().map(x -> this.formatter.formatNotOk("+" + x)) //
) //
.sorted(byTypeName) //
.collect(Collectors.joining(", ")) + ")";
@@ -178,15 +173,36 @@ private String translateArtifactTypeCoverage(final LinkedSpecificationItem item)
private void renderItemLinkCounts(final PrintStream report, final LinkedSpecificationItem item)
{
- report.print(item.countIncomingBadLinks());
- report.print("/");
- report.print(item.countIncomingLinks());
- report.print(">");
- report.print(item.countDuplicateLinks());
- report.print(">");
- report.print(item.countOutgoingBadLinks());
- report.print("/");
- report.print(item.countOutgoingLinks());
+ final int incomingLinks = item.countIncomingLinks();
+ final int incomingBadLinks = item.countIncomingBadLinks();
+ final int incomingGoodLinks = incomingLinks - incomingBadLinks;
+ final int outgoingLinks = item.countOutgoingLinks();
+ final int outgoingBadLinks = item.countOutgoingBadLinks();
+ final int outgoingGoodLinks = outgoingLinks - outgoingBadLinks;
+ report.print(" [ in: ");
+ report.print(formatCountXofY(incomingGoodLinks, incomingLinks));
+ report.print(" | out: ");
+ report.print(formatCountXofY(outgoingGoodLinks, outgoingLinks));
+ report.print(" ] ");
+ }
+
+ private void renderDuplicatesCount(final PrintStream report, final LinkedSpecificationItem item) {
+ final int duplicateLinks = item.countDuplicateLinks();
+ if(duplicateLinks != 0) {
+ report.print(" [has ");
+ report.print(this.formatter.formatNotOk(duplicateLinks + " duplicate" + (duplicateLinks > 1 ? "s" : "")));
+ report.print("]");
+ }
+ }
+
+ private String formatCountXofY(final int countGood, final int count) {
+ if((countGood == 0) && (count == 0)) {
+ return " 0 / 0 ";
+ } else {
+ return (countGood == count)
+ ? this.formatter.formatOk(String.format("%2d / %2d ✔", countGood, count))
+ : this.formatter.formatNotOk(String.format("%2d / %2d ✘", countGood, count));
+ }
}
private void renderMaturity(final PrintStream report, final LinkedSpecificationItem item)
@@ -239,7 +255,6 @@ private void renderOrigin(final PrintStream report, final Location location)
private void renderEmptyItemDetailsLine(final PrintStream report)
{
- report.print("|");
report.print(this.settings.getNewline());
}
@@ -251,7 +266,7 @@ private void renderDescription(final PrintStream report, final LinkedSpecificati
renderEmptyItemDetailsLine(report);
for (final String line : description.split(Newline.anyNewlineReqEx()))
{
- report.print("| ");
+ report.print(" ");
report.print(line);
report.print(this.settings.getNewline());
}
@@ -284,9 +299,14 @@ private void renderLink(final PrintStream report, final TracedLink link,
final boolean showOrigin)
{
final LinkStatus status = link.getStatus();
- report.print(status.isIncoming() ? "|<-- (" : "|--> (");
- report.print(status.getShortTag());
- report.print(") ");
+ report.print(" [");
+ if(status == LinkStatus.COVERS || (status == LinkStatus.COVERED_SHALLOW)) {
+ report.print(formatter.formatOk(padStatus(status)));
+ } else {
+ report.print(formatter.formatNotOk(padStatus(status)));
+ }
+ report.print("] ");
+ report.print(status.isIncoming() ? "← " : "→ ");
report.print(link.getOtherLinkEnd().getId());
report.print(this.settings.getNewline());
if (showOrigin)
@@ -294,21 +314,25 @@ private void renderLink(final PrintStream report, final TracedLink link,
final Location location = link.getOtherLinkEnd().getLocation();
if (location != null)
{
- report.print("| ");
+ report.print(" ");
renderOrigin(report, location);
report.print(this.settings.getNewline());
}
}
}
+ private static String padStatus(final LinkStatus status) {
+ return String.format("%-17s", status.toString().toLowerCase());
+ }
+
private void renderTags(final PrintStream report, final LinkedSpecificationItem item)
{
final List tags = item.getTags();
if (tags != null && !tags.equals(Collections.emptyList()))
{
renderEmptyItemDetailsLine(report);
- report.print("| #: ");
- report.print(tags.stream().collect(Collectors.joining(", ")));
+ report.print(" #: ");
+ report.print(String.join(", ", tags));
report.print(this.settings.getNewline());
++this.nonEmptySections;
}
@@ -320,7 +344,7 @@ private void renderOrigin(final PrintStream report, final LinkedSpecificationIte
if (location != null)
{
renderEmptyItemDetailsLine(report);
- report.print("| (");
+ report.print(" (");
report.print(location.getPath());
report.print(":");
report.print(location.getLine());
diff --git a/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/TextFormatter.java b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/TextFormatter.java
new file mode 100644
index 00000000..e717b523
--- /dev/null
+++ b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/TextFormatter.java
@@ -0,0 +1,29 @@
+package org.itsallcode.openfasttrace.report.plaintext;
+
+/**
+ * Interface for text formatters.
+ */
+interface TextFormatter {
+ /**
+ * Format a text span that represents a good result.
+ * @param text text span to be formatted
+ * @return formatted text
+ */
+ public String formatOk(final String text);
+
+ /**
+ * Format a text span that represents a bad result.
+ *
+ * @param text text span to be formatted
+ * @return formatted text
+ */
+ public String formatNotOk(final String text);
+
+ /**
+ * Format a text span that represents a strongly emphasized text.
+ *
+ * @param text text span to be formatted
+ * @return formatted text
+ */
+ public String formatStrong(final String text);
+}
\ No newline at end of file
diff --git a/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/TextFormatterFactory.java b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/TextFormatterFactory.java
new file mode 100644
index 00000000..8e93d881
--- /dev/null
+++ b/reporter/plaintext/src/main/java/org/itsallcode/openfasttrace/report/plaintext/TextFormatterFactory.java
@@ -0,0 +1,35 @@
+package org.itsallcode.openfasttrace.report.plaintext;
+
+import org.itsallcode.openfasttrace.api.ColorScheme;
+
+/**
+ * Factory for text formatters
+ */
+final class TextFormatterFactory {
+ private TextFormatterFactory() {
+ // prevent instantiation
+ }
+
+ /**
+ * Create a text formatter
+ * @param colorScheme color scheme that the formatter should apply
+ * @return text formatter
+ */
+ public static TextFormatter createFormatter(ColorScheme colorScheme) {
+ if(colorScheme == null)
+ {
+ return new NullTextFormatter();
+ }
+ switch (colorScheme) {
+ case BLACK_AND_WHITE:
+ return new NullTextFormatter();
+ case MONOCHROME:
+ return new MonochromeTextFormatter();
+ case COLOR:
+ return new ConsoleColorFormatter();
+ default:
+ throw new IllegalArgumentException("Unable to create text formatter for unknown color scheme '"
+ + colorScheme + "'.");
+ }
+ }
+}
diff --git a/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/AnsiSequenceTest.java b/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/AnsiSequenceTest.java
new file mode 100644
index 00000000..6390ba1c
--- /dev/null
+++ b/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/AnsiSequenceTest.java
@@ -0,0 +1,38 @@
+package org.itsallcode.openfasttrace.report.plaintext;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static org.itsallcode.openfasttrace.report.plaintext.AnsiSequence.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+class AnsiSequenceTest {
+ public static Stream getAnsiSequenceIds() {
+ return Stream.of(
+ Arguments.of(RESET, 0),
+ Arguments.of(BOLD, 1),
+ Arguments.of(ITALIC, 3),
+ Arguments.of(UNDERLINE, 4),
+ Arguments.of(INVERSE, 7),
+ Arguments.of(BLACK, 30),
+ Arguments.of(RED, 31),
+ Arguments.of(GREEN, 32),
+ Arguments.of(YELLOW, 33),
+ Arguments.of(BLUE, 34),
+ Arguments.of(MAGENTA, 35),
+ Arguments.of(CYAN, 36),
+ Arguments.of(WHITE, 37),
+ Arguments.of(BRIGHT_RED, 91)
+ );
+ }
+
+ @MethodSource("getAnsiSequenceIds")
+ @ParameterizedTest
+ void testValues(final AnsiSequence sequence, final int id) {
+ assertThat(sequence.getId(), equalTo(id));
+ }
+}
\ No newline at end of file
diff --git a/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/ConsoleColorFormatterTest.java b/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/ConsoleColorFormatterTest.java
new file mode 100644
index 00000000..ceedc1d9
--- /dev/null
+++ b/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/ConsoleColorFormatterTest.java
@@ -0,0 +1,29 @@
+package org.itsallcode.openfasttrace.report.plaintext;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+class ConsoleColorFormatterTest {
+ private static final TextFormatter FORMATTER = new ConsoleColorFormatter();
+
+ // [utest->dsn~reporting.plain-text.ansi-color~1]
+ @Test
+ void testFormatOk() {
+ assertThat(FORMATTER.formatOk("ok"), equalTo("\u001B[32mok\u001B[0m"));
+ }
+
+ // [utest->dsn~reporting.plain-text.ansi-color~1]
+ @Test
+ void testFormatNotOk() {
+ assertThat(FORMATTER.formatNotOk("not ok"), equalTo("\u001B[91mnot ok\u001B[0m"));
+ }
+
+ // [utest->dsn~reporting.plain-text.ansi-color~1]
+ // [utest-> dsn~reporting.plain-text.ansi-font-style~1]
+ @Test
+ void testFormatStrong() {
+ assertThat(FORMATTER.formatStrong("strong"), equalTo("\u001B[1;36mstrong\u001B[0m"));
+ }
+}
\ No newline at end of file
diff --git a/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/TestNullTextFormatter.java b/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/TestNullTextFormatter.java
new file mode 100644
index 00000000..5827b487
--- /dev/null
+++ b/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/TestNullTextFormatter.java
@@ -0,0 +1,25 @@
+package org.itsallcode.openfasttrace.report.plaintext;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+class TestNullTextFormatter {
+ private static final TextFormatter FORMATTER = new NullTextFormatter();
+
+ @Test
+ void testFormatOk() {
+ assertThat(FORMATTER.formatOk("ok"), equalTo("ok"));
+ }
+
+ @Test
+ void testFormatNotOk() {
+ assertThat(FORMATTER.formatNotOk("not ok"), equalTo("not ok"));
+ }
+
+ @Test
+ void testFormatStrong() {
+ assertThat(FORMATTER.formatStrong("strong"), equalTo("strong"));
+ }
+}
\ No newline at end of file
diff --git a/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/TestPlainTextReport.java b/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/TestPlainTextReport.java
index a27b1118..866456fa 100644
--- a/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/TestPlainTextReport.java
+++ b/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/TestPlainTextReport.java
@@ -3,11 +3,11 @@
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.joining;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
import static org.itsallcode.openfasttrace.testutil.core.SampleArtifactTypes.*;
import static org.itsallcode.openfasttrace.testutil.matcher.MultilineTextMatcher.matchesAllLines;
import static org.mockito.Mockito.*;
+import java.nio.charset.StandardCharsets;
import java.io.*;
import java.util.*;
@@ -80,20 +80,19 @@ private String getExpectedReportText(final String... expectedReportLines)
private String getReportOutput(final ReportVerbosity verbosity, final boolean showOrigin)
{
- final Newline newline = NEWLINE_SEPARATOR;
- return getReportOutputWithNewline(verbosity, newline, showOrigin);
+ return getReportOutputWithNewline(verbosity, NEWLINE_SEPARATOR, showOrigin);
}
private String getReportOutputWithNewline(final ReportVerbosity verbosity,
final Newline newline, final boolean showOrigin)
{
- final OutputStream outputStream = new ByteArrayOutputStream();
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final ReportSettings settings = ReportSettings.builder().verbosity(verbosity)
.newline(newline).showOrigin(showOrigin).build();
final PlaintextReporterFactory factory = createFactory(settings);
final Reportable report = factory.createImporter(this.traceMock);
report.renderToStream(outputStream);
- return outputStream.toString();
+ return outputStream.toString(StandardCharsets.UTF_8);
}
private PlaintextReporterFactory createFactory(final ReportSettings settings)
@@ -158,10 +157,10 @@ void testReport_LevelFailureSummaries_NotOK()
prepareFailedItemDetails();
assertReportOutput(ReportVerbosity.FAILURE_SUMMARIES, //
- "not ok - 0/0>0>2/4 - dsn~bar~1 [proposed] (impl, -uman, utest)", //
- "not ok - 0/3>1>0/2 - req~foo~1 (dsn)", //
- "not ok - 3/7>1>2/3 - req~zoo~1 [rejected] (-impl, -utest)", //
- "not ok - 1/6>0>0/0 - req~zoo~2 [draft] (dsn, +utest)", //
+ "not ok [ in: 0 / 0 | out: 2 / 4 ✘ ] dsn~bar~1 [proposed] (impl, -uman, utest)", //
+ "not ok [ in: 3 / 3 ✔ | out: 2 / 2 ✔ ] req~foo~1 (dsn) [has 1 duplicate]", //
+ "not ok [ in: 4 / 7 ✘ | out: 1 / 3 ✘ ] req~zoo~1 [rejected] (-impl, -utest) [has 1 duplicate]", //
+ "not ok [ in: 5 / 6 ✘ | out: 0 / 0 ] req~zoo~2 [draft] (dsn, +utest)", //
"", //
"not ok - 6 total, 4 defect");
}
@@ -179,16 +178,16 @@ private void prepareFailedItemDetails()
final LinkedSpecificationItem itemDMock = createLinkedItemMock("req~zoo~1",
ItemStatus.REJECTED, "desc D1", 3, 7, 1, 2, 3);
- when(itemAMock.getNeedsArtifactTypes()).thenReturn(asList(DSN));
- when(itemAMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(asList(DSN)));
- when(itemBMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(asList(IMPL, UTEST)));
- when(itemBMock.getUncoveredArtifactTypes()).thenReturn(asList(UMAN));
- when(itemCMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(asList(DSN)));
- when(itemCMock.getOverCoveredArtifactTypes()).thenReturn(new HashSet<>(asList(UTEST)));
+ when(itemAMock.getNeedsArtifactTypes()).thenReturn(List.of(DSN));
+ when(itemAMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(List.of(DSN)));
+ when(itemBMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(List.of(IMPL, UTEST)));
+ when(itemBMock.getUncoveredArtifactTypes()).thenReturn(List.of(UMAN));
+ when(itemCMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(List.of(DSN)));
+ when(itemCMock.getOverCoveredArtifactTypes()).thenReturn(new HashSet<>(List.of(UTEST)));
when(itemDMock.getCoveredArtifactTypes()).thenReturn(Collections.emptySet());
- when(itemDMock.getUncoveredArtifactTypes()).thenReturn(asList(IMPL, UTEST));
+ when(itemDMock.getUncoveredArtifactTypes()).thenReturn(List.of(IMPL, UTEST));
when(this.traceMock.getDefectItems())
- .thenReturn(asList(itemAMock, itemBMock, itemCMock, itemDMock));
+ .thenReturn(List.of(itemAMock, itemBMock, itemCMock, itemDMock));
when(itemAMock.getLocation()).thenReturn(Location.create("/tmp/foo.md", 1));
when(itemBMock.getLocation()).thenReturn(Location.create("/tmp/bar.md", 2));
when(itemCMock.getLocation()).thenReturn(Location.create("/tmp/zoo.xml", 13));
@@ -204,17 +203,17 @@ void testReport_LevelFailureDetails()
prepareMixedItemDetails();
assertReportOutput(ReportVerbosity.FAILURE_DETAILS, //
- "not ok - 0/1>3>2/4 - dsn~failure~0 (impl, uman, -utest)", //
- "|", //
- "| This is a failure.", //
- "|", //
- "|<-- ( ) imp~failure~0", //
- "|--> ( ) req~bar~1", //
- "|--> (+) req~baz~1", //
- "|--> ( ) req~foo~1", //
- "|--> (<) req~zoo~1", //
- "|--> (/) req~zoo~2", //
- "|", //
+ "not ok [ in: 1 / 1 ✔ | out: 2 / 4 ✘ ] dsn~failure~0 (impl, uman, -utest) [has 3 duplicates]", //
+ "", //
+ " This is a failure.", //
+ "", //
+ " [covered shallow ] ← imp~failure~0", //
+ " [covers ] → req~bar~1", //
+ " [unwanted ] → req~baz~1", //
+ " [covers ] → req~foo~1", //
+ " [outdated ] → req~zoo~1", //
+ " [orphaned ] → req~zoo~2", //
+ "", //
"", //
"not ok - 2 total, 1 defect");
}
@@ -228,23 +227,23 @@ void testReport_LevelAll()
prepareMixedItemDetails();
assertReportOutput(ReportVerbosity.ALL, //
- "not ok - 0/1>3>2/4 - dsn~failure~0 (impl, uman, -utest)", //
- "|", //
- "| This is a failure.", //
- "|", //
- "|<-- ( ) imp~failure~0", //
- "|--> ( ) req~bar~1", //
- "|--> (+) req~baz~1", //
- "|--> ( ) req~foo~1", //
- "|--> (<) req~zoo~1", //
- "|--> (/) req~zoo~2", //
- "|", //
- "ok - 0/0>0>0/0 - req~success~20170126 (dsn)", //
- "|", //
- "| This is a success.", //
- "|", //
- "| #: tag, another tag", //
- "|", //
+ "not ok [ in: 1 / 1 ✔ | out: 2 / 4 ✘ ] dsn~failure~0 (impl, uman, -utest) [has 3 duplicates]", //
+ "", //
+ " This is a failure.", //
+ "", //
+ " [covered shallow ] ← imp~failure~0", //
+ " [covers ] → req~bar~1", //
+ " [unwanted ] → req~baz~1", //
+ " [covers ] → req~foo~1", //
+ " [outdated ] → req~zoo~1", //
+ " [orphaned ] → req~zoo~2", //
+ "", //
+ "ok [ in: 0 / 0 | out: 0 / 0 ] req~success~20170126 (dsn)", //
+ "", //
+ " This is a success.", //
+ "", //
+ " #: tag, another tag", //
+ "", //
"", //
"not ok - 2 total, 1 defect");
}
@@ -258,14 +257,14 @@ private void prepareMixedItemDetails()
"This is a failure.", //
0, 1, 3, 2, 4);
- when(itemAMock.getNeedsArtifactTypes()).thenReturn(asList(DSN));
- when(itemAMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(asList(DSN)));
- when(itemAMock.getTags()).thenReturn(asList("tag", "another tag"));
- when(itemBMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(asList(IMPL, UMAN)));
- when(itemBMock.getUncoveredArtifactTypes()).thenReturn(asList(UTEST));
+ when(itemAMock.getNeedsArtifactTypes()).thenReturn(List.of(DSN));
+ when(itemAMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(List.of(DSN)));
+ when(itemAMock.getTags()).thenReturn(List.of("tag", "another tag"));
+ when(itemBMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(List.of(IMPL, UMAN)));
+ when(itemBMock.getUncoveredArtifactTypes()).thenReturn(List.of(UTEST));
prepareLinks(itemBMock);
- when(this.traceMock.getItems()).thenReturn(asList(itemAMock, itemBMock));
- when(this.traceMock.getDefectItems()).thenReturn(asList(itemBMock));
+ when(this.traceMock.getItems()).thenReturn(List.of(itemAMock, itemBMock));
+ when(this.traceMock.getDefectItems()).thenReturn(List.of(itemBMock));
}
private void prepareLinks(final LinkedSpecificationItem itemMock)
@@ -336,24 +335,24 @@ void testReportWithDifferentLineSeparator()
final LinkedSpecificationItem itemBMock = createLinkedItemMock("b~b~2", //
"Yet another" + separator + "multiline text", //
0, 0, 0, 0, 0);
- when(itemAMock.getNeedsArtifactTypes()).thenReturn(asList(DSN));
- when(itemAMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(asList(DSN)));
- when(itemBMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(asList(IMPL)));
+ when(itemAMock.getNeedsArtifactTypes()).thenReturn(List.of(DSN));
+ when(itemAMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(List.of(DSN)));
+ when(itemBMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(List.of(IMPL)));
when(this.traceMock.hasNoDefects()).thenReturn(true);
- when(this.traceMock.getItems()).thenReturn(asList(itemAMock, itemBMock));
+ when(this.traceMock.getItems()).thenReturn(List.of(itemAMock, itemBMock));
assertThat(getReportOutputWithNewline(ReportVerbosity.ALL, separator, false), //
- equalTo("ok - 0/0>0>0/0 - a~a~1 (dsn)" + separator//
- + "|" + separator //
- + "| This is" + separator //
- + "| a multiline description" + separator //
- + "|" + separator //
- + "ok - 0/0>0>0/0 - b~b~2 (impl)" + separator //
- + "|" + separator //
- + "| Yet another" + separator //
- + "| multiline text" + separator //
- + "|" + separator //
+ matchesAllLines("ok [ in: 0 / 0 | out: 0 / 0 ] a~a~1 (dsn)" + separator//
+ "" + separator //
+ + " This is" + separator //
+ + " a multiline description" + separator //
+ + separator //
+ + "ok [ in: 0 / 0 | out: 0 / 0 ] b~b~2 (impl)" + separator //
+ + "" + separator //
+ + " Yet another" + separator //
+ + " multiline text" + separator //
+ + separator //
+ + separator //
+ "ok - 2 total" + separator));
}
@@ -365,8 +364,8 @@ void testReportWithOriginDisplayEnabled()
{
final LinkedSpecificationItem itemMock = createLinkedItemMock("req~item.with-source~77",
"Description", 0, 1, 0, 0, 0);
- when(itemMock.getNeedsArtifactTypes()).thenReturn(asList(DSN));
- when(itemMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(asList(DSN)));
+ when(itemMock.getNeedsArtifactTypes()).thenReturn(List.of(DSN));
+ when(itemMock.getCoveredArtifactTypes()).thenReturn(new HashSet<>(List.of(DSN)));
final LinkedSpecificationItem other = createOtherItemMock("dsn~the-other~1");
when(other.getLocation()).thenReturn(Location.create("baz/zoo", 10));
final List links = new ArrayList<>();
@@ -377,17 +376,17 @@ void testReportWithOriginDisplayEnabled()
when(this.traceMock.count()).thenReturn(1);
when(this.traceMock.countDefects()).thenReturn(0);
when(this.traceMock.hasNoDefects()).thenReturn(true);
- when(this.traceMock.getItems()).thenReturn(asList(itemMock));
+ when(this.traceMock.getItems()).thenReturn(List.of(itemMock));
assertReportOutputWithOrigin(ReportVerbosity.ALL, //
- "ok - 0/1>0>0/0 - req~item.with-source~77 (dsn)", //
- "|", //
- "| Description", //
- "|", //
- "| (/foo/bar:42)", //
- "|", //
- "|<-- ( ) dsn~the-other~1", //
- "| (baz/zoo:10)", //
- "|", //
+ "ok [ in: 1 / 1 ✔ | out: 0 / 0 ] req~item.with-source~77 (dsn)", //
+ "", //
+ " Description", //
+ "", //
+ " (/foo/bar:42)", //
+ "", //
+ " [covered shallow ] ← dsn~the-other~1", //
+ " (baz/zoo:10)", //
+ "", //
"", //
"ok - 1 total");
}
diff --git a/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/TestTextFormatterFactory.java b/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/TestTextFormatterFactory.java
new file mode 100644
index 00000000..8f5aea87
--- /dev/null
+++ b/reporter/plaintext/src/test/java/org/itsallcode/openfasttrace/report/plaintext/TestTextFormatterFactory.java
@@ -0,0 +1,32 @@
+package org.itsallcode.openfasttrace.report.plaintext;
+
+import org.itsallcode.openfasttrace.api.ColorScheme;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.itsallcode.openfasttrace.api.ColorScheme.*;
+import static org.itsallcode.openfasttrace.report.plaintext.TextFormatterFactory.createFormatter;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class TestTextFormatterFactory {
+ @Test
+ void testCreateNullTextFormatter() {
+ assertThat(createFormatter(BLACK_AND_WHITE), instanceOf(NullTextFormatter.class));
+ }
+
+ @Test
+ void testCreateMonochromeTextFormatter() {
+ assertThat(createFormatter(MONOCHROME), instanceOf(MonochromeTextFormatter.class));
+ }
+
+ @Test
+ void testCreateConsoleColorFormatter() {
+ assertThat(createFormatter(COLOR), instanceOf(ConsoleColorFormatter.class));
+ }
+
+ @Test
+ void testCreateDefaultFormatter() {
+ assertThat(createFormatter(null), instanceOf(NullTextFormatter.class));
+ }
+}
\ No newline at end of file
diff --git a/testutil/pom.xml b/testutil/pom.xml
index f065cdb4..19fc271d 100644
--- a/testutil/pom.xml
+++ b/testutil/pom.xml
@@ -68,6 +68,13 @@
true
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ false
+
+
\ No newline at end of file
diff --git a/testutil/src/main/java/module-info.java b/testutil/src/main/java/module-info.java
new file mode 100644
index 00000000..84fc9f62
--- /dev/null
+++ b/testutil/src/main/java/module-info.java
@@ -0,0 +1,21 @@
+/**
+ * Shared test utilities.
+ */
+module org.itsallcode.openfasttrace.testutil
+{
+ exports org.itsallcode.openfasttrace.testutil;
+ exports org.itsallcode.openfasttrace.testutil.cli;
+ exports org.itsallcode.openfasttrace.testutil.core;
+ exports org.itsallcode.openfasttrace.testutil.importer;
+ exports org.itsallcode.openfasttrace.testutil.log;
+ exports org.itsallcode.openfasttrace.testutil.matcher;
+ exports org.itsallcode.openfasttrace.testutil.xml;
+
+ requires hamcrest.all;
+ requires transitive org.junit.jupiter.api;
+ requires org.mockito;
+ requires org.mockito.junit.jupiter;
+ requires java.logging;
+ requires transitive java.xml;
+ requires org.itsallcode.openfasttrace.api;
+}