1
0
mirror of https://github.com/octoleo/plantuml.git synced 2024-11-19 11:45:09 +00:00

Merge pull request #600 from jmones/bugfix/allow-white-space-after-end-mark

Bugfix to make plantuml pipe play nice when @startuml & @enduml marks are present
This commit is contained in:
arnaudroques 2021-07-29 09:50:39 +02:00 committed by GitHub
commit 19822a5539
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 500 additions and 106 deletions

3
.gitignore vendored
View File

@ -17,3 +17,6 @@ target/
.settings/ .settings/
.classpath .classpath
.project .project
# for jenv users
.java-version

View File

@ -35,11 +35,15 @@
*/ */
package net.sourceforge.plantuml; package net.sourceforge.plantuml;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.List; import java.util.List;
import net.sourceforge.plantuml.core.Diagram; import net.sourceforge.plantuml.core.Diagram;
@ -51,89 +55,97 @@ import net.sourceforge.plantuml.security.SFile;
public class Pipe { public class Pipe {
private final Option option; private final Option option;
private final InputStream is; private final BufferedReader br;
private final PrintStream ps; private final PrintStream ps;
private boolean closed = false;
private final String charset;
private final Stdrpt stdrpt; private final Stdrpt stdrpt;
public Pipe(Option option, PrintStream ps, InputStream is, String charset) { public Pipe(Option option, PrintStream ps, InputStream is, String charset) {
this.option = option; this.option = option;
this.is = is; try {
this.br = new BufferedReader(
new InputStreamReader(is, (charset != null) ? charset : Charset.defaultCharset().name()));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Invalid charset provided", e);
}
this.ps = ps; this.ps = ps;
this.charset = charset;
this.stdrpt = option.getStdrpt(); this.stdrpt = option.getStdrpt();
} }
public void managePipe(ErrorStatus error) throws IOException { public void managePipe(ErrorStatus error) throws IOException {
final boolean noStdErr = option.isPipeNoStdErr(); final boolean noStdErr = option.isPipeNoStdErr();
int nb = 0;
do { for(String source = readFirstDiagram(); source != null; source = readSubsequentDiagram()) {
final String source = readOneDiagram();
if (source == null) {
ps.flush();
if (nb == 0) {
// error.goNoData();
}
return;
}
nb++;
final Defines defines = option.getDefaultDefines(); final Defines defines = option.getDefaultDefines();
final SFile newCurrentDir = option.getFileDir() == null ? null : new SFile(option.getFileDir()); final SFile newCurrentDir = option.getFileDir() == null ? null : new SFile(option.getFileDir());
final SourceStringReader sourceStringReader = new SourceStringReader(defines, source, "UTF-8", final SourceStringReader sourceStringReader = new SourceStringReader(defines, source, "UTF-8",
option.getConfig(), newCurrentDir); option.getConfig(), newCurrentDir);
if (option.isComputeurl()) { if (option.isComputeurl()) {
for (BlockUml s : sourceStringReader.getBlocks()) { computeUrlForDiagram(sourceStringReader);
ps.println(s.getEncodedUrl());
}
} else if (option.isSyntax()) { } else if (option.isSyntax()) {
final Diagram system = sourceStringReader.getBlocks().get(0).getDiagram(); syntaxCheckDiagram(sourceStringReader, error);
if (system instanceof UmlDiagram) {
error.goOk();
ps.println(((UmlDiagram) system).getUmlDiagramType().name());
ps.println(system.getDescription());
} else if (system instanceof PSystemError) {
error.goWithError();
stdrpt.printInfo(ps, (PSystemError) system);
} else {
error.goOk();
ps.println("OTHER");
ps.println(system.getDescription());
}
} else if (option.isPipeMap()) { } else if (option.isPipeMap()) {
final String result = sourceStringReader.getCMapData(option.getImageIndex(), createPipeMapForDiagram(sourceStringReader, error);
option.getFileFormatOption());
// https://forum.plantuml.net/10049/2019-pipemap-diagrams-containing-links-give-zero-exit-code
// We don't check errors
error.goOk();
if (result == null) {
ps.println();
} else {
ps.println(result);
}
} else { } else {
final OutputStream os = noStdErr ? new ByteArrayOutputStream() : ps; generateDiagram(sourceStringReader, error, noStdErr);
final DiagramDescription result = sourceStringReader.outputImage(os, option.getImageIndex(),
option.getFileFormatOption());
printInfo(noStdErr ? ps : System.err, sourceStringReader);
if (result != null && "(error)".equalsIgnoreCase(result.getDescription())) {
error.goWithError();
} else {
error.goOk();
if (noStdErr) {
final ByteArrayOutputStream baos = (ByteArrayOutputStream) os;
baos.close();
ps.write(baos.toByteArray());
}
}
if (option.getPipeDelimitor() != null) {
ps.println(option.getPipeDelimitor());
}
} }
ps.flush(); ps.flush();
} while (closed == false); }
if (nb == 0) { }
// error.goNoData();
private void generateDiagram(SourceStringReader sourceStringReader, ErrorStatus error, boolean noStdErr) throws IOException {
final OutputStream os = noStdErr ? new ByteArrayOutputStream() : ps;
final DiagramDescription result = sourceStringReader.outputImage(os, option.getImageIndex(),
option.getFileFormatOption());
printInfo(noStdErr ? ps : System.err, sourceStringReader);
if (result != null && "(error)".equalsIgnoreCase(result.getDescription())) {
error.goWithError();
} else {
error.goOk();
if (noStdErr) {
final ByteArrayOutputStream baos = (ByteArrayOutputStream) os;
baos.close();
ps.write(baos.toByteArray());
}
}
if (option.getPipeDelimitor() != null) {
ps.println(option.getPipeDelimitor());
}
}
private void createPipeMapForDiagram(SourceStringReader sourceStringReader, ErrorStatus error) throws IOException {
final String result = sourceStringReader.getCMapData(option.getImageIndex(),
option.getFileFormatOption());
// https://forum.plantuml.net/10049/2019-pipemap-diagrams-containing-links-give-zero-exit-code
// We don't check errors
error.goOk();
if (result == null) {
ps.println();
} else {
ps.println(result);
}
}
private void computeUrlForDiagram(SourceStringReader sourceStringReader) throws IOException {
for (BlockUml s : sourceStringReader.getBlocks()) {
ps.println(s.getEncodedUrl());
}
}
private void syntaxCheckDiagram(SourceStringReader sourceStringReader, ErrorStatus error) {
final Diagram system = sourceStringReader.getBlocks().get(0).getDiagram();
if (system instanceof UmlDiagram) {
error.goOk();
ps.println(((UmlDiagram) system).getUmlDiagramType().name());
ps.println(system.getDescription());
} else if (system instanceof PSystemError) {
error.goWithError();
stdrpt.printInfo(ps, system);
} else {
error.goOk();
ps.println("OTHER");
ps.println(system.getDescription());
} }
} }
@ -146,37 +158,58 @@ public class Pipe {
} }
} }
private boolean isFinished(String s) { String readFirstDiagram() throws IOException {
return s == null || s.startsWith("@end"); return readSingleDiagram(true);
} }
private String readOneDiagram() throws IOException { String readSubsequentDiagram() throws IOException {
return readSingleDiagram(false);
}
String readSingleDiagram(boolean unmarkedAllowed) throws IOException {
State state = State.NO_CONTENT;
String expectedEnd = null;
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
while (true) {
final String s = readOneLine(); String line;
if (s == null) { while(state != State.COMPLETE && (line = br.readLine()) != null) {
closed = true;
} else if (s.startsWith("@@@format ")) { if(line.startsWith("@@@format ")) {
manageFormat(s); manageFormat(line);
} else { } else {
sb.append(s); if (state == State.NO_CONTENT && line.trim().length() > 0) {
sb.append(BackSlash.NEWLINE); state = State.START_MARK_NOT_FOUND;
} }
if (isFinished(s)) {
break; if (state == State.START_MARK_NOT_FOUND && line.startsWith("@start")) {
sb.setLength(0); // discard any previous input
state = State.START_MARK_FOUND;
expectedEnd = "@end" + line.substring(6).split("^[A-Za-z]")[0];
} else if (state == State.START_MARK_FOUND && line.startsWith(expectedEnd)) {
state = State.COMPLETE;
}
if (state != State.NO_CONTENT) {
sb.append(line).append("\n");
}
} }
} }
String source = sb.toString().trim();
if (sb.length() == 0) { switch(state) {
return null; case NO_CONTENT:
return null;
case START_MARK_NOT_FOUND:
return (unmarkedAllowed) ? "@startuml\n" + sb.toString() + "@enduml\n" : null;
case START_MARK_FOUND:
return sb.toString() + expectedEnd;
case COMPLETE:
return sb.toString();
default:
throw new IllegalStateException("Unexpected value: " + state);
} }
if (source.startsWith("@start") == false) {
source = "@startuml\n" + source + "\n@enduml";
}
return source;
} }
private void manageFormat(String s) { void manageFormat(String s) {
if (s.contains("png")) { if (s.contains("png")) {
option.setFileFormatOption(new FileFormatOption(FileFormat.PNG)); option.setFileFormatOption(new FileFormatOption(FileFormat.PNG));
} else if (s.contains("svg")) { } else if (s.contains("svg")) {
@ -184,27 +217,10 @@ public class Pipe {
} }
} }
private String readOneLine() throws IOException { enum State {
final ByteArrayOutputStream baos = new ByteArrayOutputStream(); NO_CONTENT,
while (true) { START_MARK_NOT_FOUND,
final int read = is.read(); START_MARK_FOUND,
if (read == -1) { COMPLETE
if (baos.size() == 0) {
return null;
}
break;
}
if (read != '\r' && read != '\n') {
baos.write(read);
}
if (read == '\n') {
break;
}
}
if (charset == null) {
return new String(baos.toByteArray());
}
return new String(baos.toByteArray(), charset);
} }
} }

View File

@ -0,0 +1,375 @@
package net.sourceforge.plantuml;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import org.assertj.core.api.AutoCloseableSoftAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
class PipeTest {
ByteArrayOutputStream baos;
ErrorStatus errorStatus;
Option option;
Pipe pipe;
PrintStream ps;
@BeforeEach
void setup() {
errorStatus = ErrorStatus.init();
baos = new ByteArrayOutputStream();
option = new Option();
ps = new PrintStream(baos);
pipe = new Pipe(option, ps, new ByteArrayInputStream(new byte[0]), UTF_8.toString());
}
@Test
void should_managePipe_set_as_no_error_for_empty_input() throws IOException {
pipe = new Pipe(option, ps, new ByteArrayInputStream(new byte[0]), UTF_8.name());
pipe.managePipe(errorStatus);
try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) {
softly.assertThat(errorStatus.hasError()).isFalse();
softly.assertThat(errorStatus.isNoData()).isTrue();
softly.assertThat(baos.toByteArray()).isEmpty();
}
}
static List<TestCase> managePipeTestCases() {
LinkedList<TestCase> l = new LinkedList<>();
l.add(TestCase.of("", "", "", Verification.EXACT, false, true));
// ok render
l.add(TestCase.of("", "a->b", "(?s).*PNG.*Generated by http://plantuml.com.*", Verification.REGEX, false, false));
l.add(TestCase.of("", "@startuml\na->b\n@enduml", "(?s).*PNG.*Generated by http://plantuml.com.*", Verification.REGEX, false, false));
l.add(TestCase.of("", "@startuml\na->b\n@enduml\n@startuml\na->b\nb->c\n@enduml\n", "(?s).*(PNG.*Generated by http://plantuml.com.*){2}", Verification.REGEX, false, false));
// bad data
l.add(TestCase.of("", "a", "(?s).*PNG.*Generated by http://plantuml.com.*", Verification.REGEX, true, false));
l.add(TestCase.of("", "@startuml\na\n@enduml\n", "(?s).*PNG.*Generated by http://plantuml.com.*", Verification.REGEX, true, false));
l.add(TestCase.of("", "@startuml\na\n@enduml\n@startuml\na\n@enduml\n", "(?s).*PNG.*Generated by http://plantuml.com.*", Verification.REGEX, true, false));
// ignore garbage before; after; before&between&after diagrams
l.add(TestCase.of("", "this-is-garbage\n@startuml\na->b\n@enduml", "(?s).*(PNG.*Generated by http://plantuml.com.*){1}", Verification.REGEX, false, false));
l.add(TestCase.of("", "@startuml\na->b\n@enduml\nthis-is-garbage", "(?s).*(PNG.*Generated by http://plantuml.com.*){1}", Verification.REGEX, false, false));
l.add(TestCase.of("", "this-is-garbage\n@startuml\na->b\n@enduml\nthis-is-garbage\n@startuml\na->b\n@enduml\nthis-is-garbage", "(?s).*(PNG.*Generated by http://plantuml.com.*){2}", Verification.REGEX, false, false));
// ignore other diagram start tags when still not closed (but fails to generate)
l.add(TestCase.of("", "@startuml\na->b\n@startgantt\n@enduml\n", "(?s).*(PNG.*Generated by http://plantuml.com.*){1}", Verification.REGEX, true, false));
// manage @@@format svg
l.add(TestCase.of("", "@startuml\n@@@format svg\na->b\n@enduml", "(?s).*<\\?xml.*<svg.*</svg>", Verification.REGEX, false, false));
l.add(TestCase.of("", "@startuml\n@@@format svg\na->b\n@enduml\n@startuml\n@@@format svg\na->b\n@enduml", "(?s).*(<\\?xml.*<svg.*</svg>.*){2}", Verification.REGEX, false, false));
// mixed formats
l.add(TestCase.of("", "@startuml\n@@@format png\na->b\n@enduml\n@startuml\n@@@format svg\na->b\n@enduml", "(?s).*PNG.*Generated by http://plantuml.com.*<\\?xml.*<svg.*</svg>", Verification.REGEX, false, false));
l.add(TestCase.of("", "@startuml\n@@@format svg\na->b\n@enduml\n@startuml\n@@@format png\na->b\n@enduml", "(?s).*<\\?xml.*<svg.*</svg>.*PNG.*Generated by http://plantuml.com.*", Verification.REGEX, false, false));
// pipe delimitor
l.add(TestCase.of("-pipedelimitor PIPE-DELIMITOR", "@startuml\na->b\n@enduml\n@startuml\na->b\nb->c\n@enduml\n", "(?s).*(PNG.*Generated by http://plantuml.com.*PIPE-DELIMITOR.*){2}", Verification.REGEX, false, false));
// if format is set in first diagram and not in the second, it will be used in both (Possibly incorrect: preseved from old behaviour)
l.add(TestCase.of("", "@startuml\n@@@format svg\na->b\n@enduml\n@startuml\na->b\n@enduml", "(?s).*(<\\?xml.*<svg.*</svg>.*){2}", Verification.REGEX, false, false));
// ok computeurl
l.add(TestCase.of("-computeurl", "@startuml\na->b\n@enduml", "IzIrIm80\n", Verification.EXACT, false, true));
l.add(TestCase.of("-computeurl", "@startuml\na->b\n@enduml\n@startuml\na->b\nb->c\n@enduml\n", "IzIrIm80\nIzIrI-9AqhLB1W00\n", Verification.EXACT,false, true));
// ok encodeurl
l.add(TestCase.of("-encodeurl", "@startuml\na->b\n@enduml", "IzIrIm80\n", Verification.EXACT,false, true));
l.add(TestCase.of("-encodeurl", "@startuml\na->b\n@enduml\n@startuml\na->b\nb->c\n@enduml\n", "IzIrIm80\nIzIrI-9AqhLB1W00\n", Verification.EXACT, false, true));
// valid syntax
l.add(TestCase.of("-syntax", "@startuml\na->b\n@enduml", "SEQUENCE\n(2 participants)\n", Verification.EXACT, false, false));
l.add(TestCase.of("-syntax", "@startuml\na->b\n@enduml\n@startuml\na->b\nb->c\n@enduml\n", "SEQUENCE\n(2 participants)\nSEQUENCE\n(3 participants)\n", Verification.EXACT, false, false));
l.add(TestCase.of("-syntax", "@startgantt\n[a] lasts 1 day\n@endgantt", "OTHER\n(Project)\n", Verification.EXACT, false, false));
// invalid syntax
l.add(TestCase.of("-syntax", "@startuml\na\n@enduml", "ERROR\n1\nSyntax Error?\n", Verification.EXACT, true, false));
l.add(TestCase.of("-syntax", "@startuml\na\n@enduml\n@startuml\na\n@enduml", "ERROR\n1\nSyntax Error?\nERROR\n1\nSyntax Error?\n", Verification.EXACT, true, false));
l.add(TestCase.of("-syntax", "@startuml\na->b\n@enduml\n@startuml\na\n@enduml", "SEQUENCE\n(2 participants)\nERROR\n1\nSyntax Error?\n", Verification.EXACT, true, false));
l.add(TestCase.of("-syntax", "@startuml\na\n@enduml\n@startuml\na->b\n@enduml", "ERROR\n1\nSyntax Error?\nSEQUENCE\n(2 participants)\n", Verification.EXACT, true, false));
// pipemap (using regexp to allow any coords so that it doesn't fail on different systems)
l.add(TestCase.of("-pipemap", "@startuml\na->b: [[http://a.com]] c\n@enduml",
"<map id=\"plantuml_map\" name=\"plantuml_map\">\n" +
"<area shape=\"rect\" id=\"id1\" href=\"http://a.com\" title=\"http://a.com\" alt=\"\" coords=\"[0-9]+,[0-9]+,[0-9]+,[0-9]+\"/>\n" +
"</map>\n\n",
Verification.REGEX, false, false));
l.add(TestCase.of("-pipemap", "@startuml\na->b: [[http://a.com]] c\n@enduml\n@startuml\nc->d: [[http://c.com]] e\n@enduml",
"<map id=\"plantuml_map\" name=\"plantuml_map\">\n" +
"<area shape=\"rect\" id=\"id1\" href=\"http://a.com\" title=\"http://a.com\" alt=\"\" coords=\"[0-9]+,[0-9]+,[0-9]+,[0-9]+\"/>\n" +
"</map>\n\n" +
"<map id=\"plantuml_map\" name=\"plantuml_map\">\n" +
"<area shape=\"rect\" id=\"id1\" href=\"http://c.com\" title=\"http://c.com\" alt=\"\" coords=\"[0-9]+,[0-9]+,[0-9]+,[0-9]+\"/>\n" +
"</map>\n\n",
Verification.REGEX, false, false));
// no links/invalid input => no pipemap to output (no error as of https://forum.plantuml.net/10049/2019-pipemap-diagrams-containing-links-give-zero-exit-code )
l.add(TestCase.of("-pipemap", "@startuml\na->b\n@enduml", "\n", Verification.EXACT, false, false));
l.add(TestCase.of("-pipemap", "@startuml\na\n@enduml", "\n", Verification.EXACT, false, false));
return l;
}
@ParameterizedTest
@MethodSource("managePipeTestCases")
void should_managePipe_manage_success_cases_correctly(TestCase testCase) throws IOException, InterruptedException {
option = new Option(testCase.getOptions().split(" "));
pipe = new Pipe(option, ps, new ByteArrayInputStream(testCase.getInput().getBytes(UTF_8)), UTF_8.name());
pipe.managePipe(errorStatus);
try (AutoCloseableSoftAssertions softly = new AutoCloseableSoftAssertions()) {
softly.assertThat(errorStatus.hasError()).isEqualTo(testCase.isExpectedHasErrors());
softly.assertThat(errorStatus.isNoData()).isEqualTo(testCase.isExpectedIsNoData());
switch(testCase.getExpectedOutVerification()) {
case EXACT:
softly.assertThat(new String(baos.toByteArray(), UTF_8)).isEqualTo(testCase.getExpectedOut());
break;
case REGEX:
softly.assertThat(new String(baos.toByteArray(), UTF_8)).matches(testCase.getExpectedOut());
break;
}
}
}
@Test
void should_readFirstDiagram_return_null_for_empty_input() throws IOException {
pipe = new Pipe(option, null, new ByteArrayInputStream(new byte[0]), UTF_8.name());
String actual = pipe.readFirstDiagram();
assertThat(actual).isNull();
}
@Test
void should_readFirstDiagram_decode_a_special_unicode_character_when_provided_charset_is_utf8() throws IOException {
pipe = new Pipe(option, null, new ByteArrayInputStream("\u2620\n".getBytes(UTF_8)), UTF_8.name());
String actual = pipe.readFirstDiagram();
assertThat(actual).isEqualTo("@startuml\n\u2620\n@enduml\n");
}
// The testing is relevant only if the testing VM is configured in non-UTF-8 defaultCharset()
@Test
void should_readFirstDiagram_uses_current_charset_if_not_provided() throws IOException {
String input = "\u00C1"; // A acute accented. (HTML &Aacute;). Multibyte in UTF-8.
if(Charset.defaultCharset().newEncoder().canEncode(input)) {
pipe = new Pipe(option, null, new ByteArrayInputStream(input.getBytes(Charset.defaultCharset())), null);
String actual = pipe.readFirstDiagram();
assertThat(actual).isEqualTo("@startuml\n\u00C1\n@enduml\n");
} else {
// default charset can't encode &Aacute;. Ignore the test.
assertTrue(true);
}
}
@ParameterizedTest
@ValueSource(strings = {
"ab\nc", // *nix, macOsX
"ab\rc", // pre-macOsX macs
"ab\r\nc", // Windows
// the case \n\r is handled as 2 new lines, thus not added
"ab\nc\n",
"ab\nc\r",
"ab\nc\r\n"
})
void should_readFirstDiagram_decode_correctly_different_line_endings(String input) throws IOException {
pipe = new Pipe(option, null, new ByteArrayInputStream(input.getBytes(UTF_8)), UTF_8.name());
String first = pipe.readFirstDiagram();
String second = pipe.readFirstDiagram();
assertThat(first).isEqualTo("@startuml\nab\nc\n@enduml\n");
assertThat(second).isEqualTo(null); // no spurious diagram afterwards
}
static List<InputExpected> firstStartAndEndMarks() {
List<InputExpected> l = new LinkedList<>();
l.add(InputExpected.of("\nab\r\ncde", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("\nab\r\ncde\n", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("\nab\r\ncde\n@enduml", "@startuml\nab\ncde\n@enduml\n@enduml\n"));
l.add(InputExpected.of("\nab\r\ncde\n@endwhatever", "@startuml\nab\ncde\n@endwhatever\n@enduml\n"));
l.add(InputExpected.of("\nab\r\ncde\n@enduml\n", "@startuml\nab\ncde\n@enduml\n@enduml\n"));
l.add(InputExpected.of("@startuml\nab\r\ncde\n@enduml", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("@startuml\nab\r\ncde\n@enduml\n", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("@startuml\nab\r\ncde\n@enduml\n\n", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("@startuml\nab\r\ncde\n@enduml\r\n\r\n", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("this-is-garbage\n@startuml\nab\rcde\n@enduml\nthis-is-garbage\n", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("@startwhatever\nab\rcde\n@endwhatever", "@startwhatever\nab\ncde\n@endwhatever\n"));
return l;
}
@ParameterizedTest
@MethodSource("firstStartAndEndMarks")
void should_readFirstDiagram_handle_correctly_start_and_end_marks(InputExpected inputExpected) throws IOException {
pipe = new Pipe(option, null, new ByteArrayInputStream(inputExpected.getInput().getBytes(UTF_8)), UTF_8.name());
String actual = pipe.readFirstDiagram();
assertThat(actual).isEqualTo(inputExpected.getExpected());
}
static List<InputExpected> subsequentStartAndEndMarks() {
List<InputExpected> l = new LinkedList<>();
l.add(InputExpected.of("\nab\r\ncde", null));
l.add(InputExpected.of("\nab\r\ncde\n", null));
l.add(InputExpected.of("\nab\r\ncde\n@enduml", null));
l.add(InputExpected.of("\nab\r\ncde\n@endwhatever", null));
l.add(InputExpected.of("\nab\r\ncde\n@enduml\n", null));
l.add(InputExpected.of("@startuml\nab\r\ncde\n@enduml", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("@startuml\nab\r\ncde\n@enduml\n", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("@startuml\nab\r\ncde\n@enduml\n\n", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("@startuml\nab\r\ncde\n@enduml\r\n\r\n", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("this-is-garbage\n@startuml\nab\rcde\n@enduml\nthis-is-garbage\n", "@startuml\nab\ncde\n@enduml\n"));
l.add(InputExpected.of("@startwhatever\nab\rcde\n@endwhatever", "@startwhatever\nab\ncde\n@endwhatever\n"));
return l;
}
@ParameterizedTest
@MethodSource("subsequentStartAndEndMarks")
void should_readSubsequentDiagram_handle_correctly_start_and_end_marks(InputExpected inputExpected) throws IOException {
pipe = new Pipe(option, null, new ByteArrayInputStream(inputExpected.getInput().getBytes(UTF_8)), UTF_8.name());
String actual = pipe.readSubsequentDiagram();
assertThat(actual).isEqualTo(inputExpected.getExpected());
}
@ParameterizedTest
@ValueSource(strings = {"@@@format png", "png", "@@@format png <deadbeef>"})
void should_manageFormat_handle_png(String valid) {
pipe.manageFormat(valid);
assertThat(option.getFileFormatOption().getFileFormat()).isEqualTo(FileFormat.PNG);
}
@ParameterizedTest
@ValueSource(strings = {"@@@format svg", "svg", "@@@format svg <deadbeef>"})
void should_manageFormat_handle_svg(String valid) {
pipe.manageFormat(valid);
assertThat(option.getFileFormatOption().getFileFormat()).isEqualTo(FileFormat.SVG);
}
@ParameterizedTest
@ValueSource(strings = {"@@@format invalid", "invalid", "@@@format invalid <deadbeef>"})
void should_manageFormat_ignore_any_other_value_and_keep_default_value_as_file_format_option(String valid) {
pipe.manageFormat(valid);
assertThat(option.getFileFormatOption().getFileFormat()).isEqualTo(FileFormat.PNG);
}
static class TestCase {
private final String options;
private final String input;
private final String expectedOut;
private final Verification expectedOutVerification;
private final boolean expectedHasErrors;
private final boolean expectedIsNoData;
public TestCase(String options, String input, String expectedOut, Verification expectedOutVerification, boolean expectedHasErrors, boolean expectedIsNoData) {
this.options = options;
this.input = input;
this.expectedOut = expectedOut;
this.expectedOutVerification = expectedOutVerification;
this.expectedHasErrors = expectedHasErrors;
this.expectedIsNoData = expectedIsNoData;
}
public static TestCase of(String option, String input, String expectedOut, Verification expectedOutVerification, boolean expectedHasErrors, boolean expectedIsNoData) {
return new TestCase(option, input, expectedOut, expectedOutVerification, expectedHasErrors, expectedIsNoData);
}
public String getOptions() {
return options;
}
public String getInput() {
return input;
}
public String getExpectedOut() {
return expectedOut;
}
public Verification getExpectedOutVerification() {
return expectedOutVerification;
}
public boolean isExpectedHasErrors() {
return expectedHasErrors;
}
public boolean isExpectedIsNoData() {
return expectedIsNoData;
}
@Override
public String toString() {
return "o:'" + options + "', i:'" + input + "', e-out='" + expectedOut + "', e-err='" + expectedHasErrors + "', e-nodata='" + expectedIsNoData + "'";
}
}
static class InputExpected {
private final String input;
private final String expected;
public InputExpected(String input, String expected) {
this.input = input;
this.expected = expected;
}
public static InputExpected of(String input, String expected) {
return new InputExpected(input, expected);
}
public String getInput() {
return input;
}
public String getExpected() {
return expected;
}
@Override
public String toString() {
return "i:'" + input + "', e='" + expected + "'";
}
}
enum Verification {
EXACT,
REGEX
}
}