package gherkin.formatter; import gherkin.formatter.ansi.AnsiEscapes; import gherkin.formatter.model.*; import org.junit.Test; import static gherkin.formatter.LocationMatcher.hasLocation; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.endsWith; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import gherkin.parser.Parser; import java.io.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class PrettyFormatterTest { private static final List<Comment> NO_COMMENTS = emptyList(); private static final List<Tag> NO_TAGS = Collections.emptyList(); @Test public void prints_nice_colors() throws UnsupportedEncodingException, InterruptedException { PrettyFormatter f = new PrettyFormatter(System.out, false, false); f.scenario(new Scenario(NO_COMMENTS, NO_TAGS, "Scenario", "a scenario", "", 1, "a-scenario")); f.step(new Step(new ArrayList<Comment>(), "Given ", "I have 6 cukes", 1, null, null)); Thread.sleep(1000); f.match(new Match(Arrays.asList(new Argument(7, "6")), "somewhere.brainfuck")); Thread.sleep(1000); f.result(new Result("failed", 55L, "Something\nbad\nhappened")); } @Test public void prints_table() throws UnsupportedEncodingException, InterruptedException { PrettyFormatter f = new PrettyFormatter(System.out, false, false); f.scenario(new Scenario(NO_COMMENTS, Collections.<Tag>emptyList(), "Scenario", "a scenario", "", 1, "a-scenario")); ArrayList<DataTableRow> rows = new ArrayList<DataTableRow>() {{ add(new DataTableRow(NO_COMMENTS, asList("un", "deux"), 1, Row.DiffType.NONE)); add(new DataTableRow(NO_COMMENTS, asList("one", "two"), 1, Row.DiffType.DELETE)); add(new DataTableRow(NO_COMMENTS, asList("en", "to"), 1, Row.DiffType.INSERT)); }}; Step step = new Step(new ArrayList<Comment>(), "Given ", "I have 6 cukes", 1, rows, null); f.step(step); Thread.sleep(1000); f.match(new Match(Arrays.asList(new Argument(7, "6")), "somewhere.brainfuck")); Thread.sleep(1000); f.result(new Result("failed", 55L, "Something\nbad\nhappened")); } @Test public void shouldNotCloseProvidedStreamInDone() { PrintStream out = mock(PrintStream.class); Formatter formatter = new PrettyFormatter(out, true, true); formatter.done(); verify(out, never()).close(); } @Test public void shouldFlushAndCloseProvidedStreamInClose() { PrintStream out = mock(PrintStream.class); Formatter formatter = new PrettyFormatter(out, true, true); formatter.close(); verify(out).flush(); verify(out).close(); } @Test public void shouldFormatAsDesigned() throws IOException { StringBuilder featureBuilder = new StringBuilder(); featureBuilder.append("Feature: PrettyFormatter\n"); featureBuilder.append("Scenario: Formmat beautifully\n"); featureBuilder.append("When I have this table:\n"); featureBuilder.append("\t|name|value|\n"); featureBuilder.append("\t|a|b|\n"); featureBuilder.append("Then should formatt beautifully.\n"); String feature = featureBuilder.toString(); List<String> lines = doFormatter(feature); assertEquals("Formatter produces unexpected quantity of lines. ", 7, lines.size()); assertEquals("Feature: PrettyFormatter", lines.get(0)); assertEquals("", lines.get(1)); assertEquals(" Scenario: Formmat beautifully", lines.get(2)); assertEquals(" When I have this table:", lines.get(3)); assertEquals(" | name | value |", lines.get(4)); assertEquals(" | a | b |", lines.get(5)); assertEquals(" Then should formatt beautifully.", lines.get(6)); } @Test public void shouldAppendOnlyCompleteLinesAndFlushBetween() throws IOException { StringBuilder featureBuilder = new StringBuilder(); featureBuilder.append("Feature: PrettyFormatter\n"); featureBuilder.append("Scenario: Formmat beautifully\n"); featureBuilder.append("When I have this table:\n"); featureBuilder.append("\t|name|value|\n"); featureBuilder.append("\t|a|b|\n"); featureBuilder.append("Then should formatt beautifully.\n"); String feature = featureBuilder.toString(); Formatter formatter = new PrettyFormatter(new CheckingAppendable(), false, false); new Parser(formatter).parse(feature, "", 0); formatter.close(); } class CheckingAppendable implements Appendable, Flushable { boolean flushed = true; void checkAppend(CharSequence csq) { if (!flushed) { fail("No flush before append: " + csq); } assertThat("Must only append complete lines", csq.toString(), endsWith("\n")); flushed = false; } @Override public Appendable append(CharSequence csq) throws IOException { checkAppend(csq); return this; } @Override public Appendable append(CharSequence csq, int start, int end) throws IOException { checkAppend(csq.subSequence(start, end)); return this; } @Override public Appendable append(char c) throws IOException { checkAppend(String.valueOf(c)); return this; } @Override public void flush() throws IOException { flushed = true; } } @Test public void whenIMissSomeCellsInARowShouldFill() throws IOException { StringBuilder featureBuilder = new StringBuilder(); featureBuilder.append("Feature: PrettyFormatter\n"); featureBuilder.append("Scenario: Formmat beautifully\n"); featureBuilder.append("When I have this table:\n"); featureBuilder.append("\t|name|value|\n"); featureBuilder.append("\t|a|\n"); // <--- here is different featureBuilder.append("Then should formatt beautifully.\n"); String feature = featureBuilder.toString(); List<String> lines = doFormatter(feature); assertEquals("Formatter produces unexpected quantity of lines. ", 7, lines.size()); assertEquals(" | a | |", lines.get(5)); } @Test public void whenIAddSomeExtraCellsInARowShouldFillOthersRows() throws IOException { StringBuilder featureBuilder = new StringBuilder(); featureBuilder.append("Feature: PrettyFormatter\n"); featureBuilder.append("Scenario: Formmat beautifully\n"); featureBuilder.append("When I have this table:\n"); featureBuilder.append("\t|name|value|\n"); featureBuilder.append("\t|a|b|c|\n"); // <--- here is different featureBuilder.append("Then should formatt beautifully.\n"); String feature = featureBuilder.toString(); List<String> lines = doFormatter(feature); assertEquals("Formatter produces unexpected quantity of lines. ", 7, lines.size()); assertEquals(" | name | value | |", lines.get(4)); assertEquals(" | a | b | c |", lines.get(5)); } @Test public void shouldHandleAllStepCallsBeforeAnyMatchResultCalls() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrettyFormatter formatter = createMonochromePrettyFormatter(baos); formatter.uri("test/resources/cucumber/examples/java/helloworld/helloworld.feature"); callFeature(formatter, "Hello World", 1); callStartOfScenarioLifecycle(formatter, "Say hello", 3); callScenario(formatter, "Say hello", 3); callStep(formatter, "Given ", "I have a hello app with \"Howdy\"", 4); callStep(formatter, "When ", "I ask it to say hi", 5); callStep(formatter, "Then ", "it should answer with \"Howdy World\"", 6); callMatch(formatter, "HelloStepdefs.I_have_a_hello_app_with(String)", "Howdy", 25); callResult(formatter, "passed"); callMatch(formatter, "HelloStepdefs.I_ask_it_to_say_hi()"); callResult(formatter, "passed"); callMatch(formatter, "HelloStepdefs.it_should_answer_with(String)", "Howdy World", 23); callResult(formatter, "passed"); callEndOfScenarioLifecycle(formatter, "Say hello", 3); formatter.eof(); formatter.done(); formatter.close(); assertEquals( // the hash signs are aligned, its just the \" that give an illusion non-alignment "Feature: Hello World\n" + "\n" + " Scenario: Say hello # test/resources/cucumber/examples/java/helloworld/helloworld.feature:3\n" + " Given I have a hello app with \"Howdy\" # HelloStepdefs.I_have_a_hello_app_with(String)\n" + " When I ask it to say hi # HelloStepdefs.I_ask_it_to_say_hi()\n" + " Then it should answer with \"Howdy World\" # HelloStepdefs.it_should_answer_with(String)\n", baos.toString()); } @Test public void shouldHandleInterleavedStepMatchResultCallsForScenario() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrettyFormatter formatter = createMonochromePrettyFormatter(baos); formatter.uri("test/resources/cucumber/examples/java/helloworld/helloworld.feature"); callFeature(formatter, "Hello World", 1); callStartOfScenarioLifecycle(formatter, "Say hello", 3); callScenario(formatter, "Say hello", 3); callStep(formatter, "Given ", "I have a hello app with \"Howdy\"", 4); callMatch(formatter, "HelloStepdefs.I_have_a_hello_app_with(String)", "Howdy", 25); callResult(formatter, "passed"); callStep(formatter, "When ", "I ask it to say hi", 5); callMatch(formatter, "HelloStepdefs.I_ask_it_to_say_hi()"); callResult(formatter, "passed"); callStep(formatter, "Then ", "it should answer with \"Howdy World\"", 6); callMatch(formatter, "HelloStepdefs.it_should_answer_with(String)", "Howdy World", 23); callResult(formatter, "passed"); callEndOfScenarioLifecycle(formatter, "Say hello", 3); formatter.eof(); formatter.done(); formatter.close(); assertEquals( // the hash signs are aligned, its just the \" that give an illusion non-alignment "Feature: Hello World\n" + "\n" + " Scenario: Say hello # test/resources/cucumber/examples/java/helloworld/helloworld.feature:3\n" + " Given I have a hello app with \"Howdy\" # HelloStepdefs.I_have_a_hello_app_with(String)\n" + " When I ask it to say hi # HelloStepdefs.I_ask_it_to_say_hi()\n" + " Then it should answer with \"Howdy World\" # HelloStepdefs.it_should_answer_with(String)\n", baos.toString()); } @Test public void shouldTreatStepWithMatchCallsWithoutResultAsSkipped() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrettyFormatter formatter = createColorPrettyFormatter(baos); formatter.uri("path/name.feature"); callFeature(formatter, "A Feature", 1); callStartOfScenarioLifecycle(formatter, "A Scenario", 3); callScenario(formatter, "A Scenario", 3); callStep(formatter, "* ", "First step", 4); callMatch(formatter, "Stepdefs.First_step()"); callStep(formatter, "* ", "Second step", 5); callMatch(formatter, "Stepdefs.Second_step()"); callEndOfScenarioLifecycle(formatter, "A Scenario", 3); formatter.eof(); formatter.done(); formatter.close(); List<String> lines = extractLines(baos); assertThat(lines.get(3), containsString("First step")); assertLineHasStatus(lines.get(3), "skipped"); assertThat(lines.get(3), hasLocation()); assertThat(lines.get(4), containsString("Second step")); assertLineHasStatus(lines.get(3), "skipped"); assertThat(lines.get(4), hasLocation()); } @Test public void shouldHandleResultCallsWithoutMatchForScenario() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrettyFormatter formatter = createColorPrettyFormatter(baos); formatter.uri("path/name.feature"); callFeature(formatter, "A Feature", 1); callStartOfScenarioLifecycle(formatter, "A Scenario", 3); callScenario(formatter, "A Scenario", 3); callStep(formatter, "* ", "First step", 4); callResult(formatter, "undefined"); callStep(formatter, "* ", "Second step", 5); callResult(formatter, "skipped"); callEndOfScenarioLifecycle(formatter, "A Scenario", 3); formatter.eof(); formatter.done(); formatter.close(); List<String> lines = extractLines(baos); assertThat(lines.get(3), containsString("First step")); assertLineHasStatus(lines.get(3), "undefined"); assertThat(lines.get(3), not(hasLocation())); assertThat(lines.get(4), containsString("Second step")); assertLineHasStatus(lines.get(4), "skipped"); assertThat(lines.get(4), not(hasLocation())); } @Test public void shouldPrintResultErrorMessagesRegerdlessOfMatchCalls() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrettyFormatter formatter = createMonochromePrettyFormatter(baos); formatter.uri("path/name.feature"); callFeature(formatter, "A Feature", 1); callScenario(formatter, "A Scenario", 3); callStep(formatter, "* ", "First step", 4); callMatch(formatter, "Stepdefs.First_step()"); callResult(formatter, "failed", "Error message 1"); callStep(formatter, "* ", "Second step", 5); callResult(formatter, "skipped", "Error message 2"); formatter.eof(); formatter.done(); formatter.close(); assertEquals( "Feature: A Feature\n" + "\n" + " Scenario: A Scenario # path/name.feature:3\n" + " * First step # Stepdefs.First_step()\n" + " Error message 1\n" + " * Second step\n" + " Error message 2\n", baos.toString()); } /** * Execute a formatting feature for many different scenarios. * * @param feature * @return * @throws IOException */ private List<String> doFormatter(String feature) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream out = new PrintStream(baos); Formatter formatter = new PrettyFormatter(out, true, false); new Parser(formatter).parse(feature, "", 0); formatter.close(); List<String> lines = extractLines(baos); return lines; } private List<String> extractLines(ByteArrayOutputStream baos) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(baos.toByteArray()))); String line; List<String> lines = new ArrayList<String>(); int lineNumber = 0; while ((line = br.readLine()) != null) { System.out.println(lineNumber+":"+line); lineNumber++; lines.add(line); } return lines; } private PrettyFormatter createMonochromePrettyFormatter(ByteArrayOutputStream baos) { PrintStream out = new PrintStream(baos); return new PrettyFormatter(out, true, true); } private PrettyFormatter createColorPrettyFormatter(ByteArrayOutputStream baos) { PrintStream out = new PrintStream(baos); return new PrettyFormatter(out, false, true); } private void callFeature(PrettyFormatter formatter, String name, int line) { formatter.feature(new Feature(Collections.<Comment>emptyList(), Collections.<Tag>emptyList(), "Feature", name, "", line, "")); } private void callStartOfScenarioLifecycle(PrettyFormatter formatter, String name, int line) { formatter.startOfScenarioLifeCycle(new Scenario(Collections.<Comment>emptyList(), Collections.<Tag>emptyList(), "Scenario", name, "", line, "")); } private void callScenario(PrettyFormatter formatter, String name, int line) { formatter.scenario(new Scenario(Collections.<Comment>emptyList(), Collections.<Tag>emptyList(), "Scenario", name, "", line, "")); } private void callStep(PrettyFormatter formatter, String keyword, String name, int line) { formatter.step(new Step(new ArrayList<Comment>(), keyword, name, line, null, null)); } private void callMatch(PrettyFormatter formatter, String location) { formatter.match(new Match(Collections.<Argument>emptyList(), location)); } private void callMatch(PrettyFormatter formatter, String location, String argumentValue, int offset) { Argument arg = new Argument(offset, argumentValue); List<Argument> argList = new ArrayList<Argument>(); argList.add(arg); formatter.match(new Match(argList, location)); } private void callResult(PrettyFormatter formatter, String status) { formatter.result(new Result(status, 0l, null)); } private void callResult(PrettyFormatter formatter, String status, String errorMessage) { formatter.result(new Result(status, 0l, errorMessage)); } private void callEndOfScenarioLifecycle(PrettyFormatter formatter, String name, int line) { formatter.startOfScenarioLifeCycle(new Scenario(Collections.<Comment>emptyList(), Collections.<Tag>emptyList(), "Scenario", name, "", line, "")); } private void assertLineHasStatus(String line, String status) { if ("undefined".equalsIgnoreCase(status)) { assertThat(line, containsString(AnsiEscapes.YELLOW.toString())); } else if ("skipped".equalsIgnoreCase(status)) { assertThat(line, containsString(AnsiEscapes.CYAN.toString())); } else { fail("status not implemented"); } } }