From 4181b9c05d5ac8ea056e3c06d35503f99403157a Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 5 Nov 2021 11:38:31 +0100 Subject: [PATCH] Make quote character in @CsvFileSource configurable Prior to this commit, the quote character for quoted strings in @CsvFileSource was hard coded to a double quote (") and could not be changed. Commit f1cbfbe14a introduced a new quoteCharacter attribute in @CsvSource that allows the user to change the quote character. For consistency between the two features, this commit introduces a new quoteCharacter attribute in @CsvFileSource. The quoteCharacter defaults to a double quote for backward compatibility. Closes #2735 --- .../release-notes/release-notes-5.8.2.adoc | 15 ++++++----- .../asciidoc/user-guide/writing-tests.adoc | 15 +++++------ .../src/test/resources/two-column.csv | 2 +- .../params/provider/CsvFileSource.java | 25 ++++++++++++++----- .../params/provider/CsvParserFactory.java | 3 +-- .../CsvFileArgumentsProviderTests.java | 13 ++++++++++ .../provider/MockCsvAnnotationBuilder.java | 23 +++++++++++------ 7 files changed, 65 insertions(+), 31 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc index b16c00a2c1c1..6e97eae8b162 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.2.adoc @@ -1,12 +1,12 @@ [[release-notes-5.8.2]] == 5.8.2 -*Date of Release:* October ❓, 2021 +*Date of Release:* November ❓, 2021 *Scope:* -* Text blocks in `@CsvSource` are treated as CSV files -* Custom quote character support in `@CsvSource` +* Text blocks in `@CsvSource` are treated like CSV files +* Custom quote character support in `@CsvSource` and `@CsvFileSource` For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit5-repo}+/milestone/60?closed=1+[5.8.2] milestone page in the JUnit repository on @@ -24,14 +24,13 @@ No changes. ==== New Features and Improvements -* Text blocks in `@CsvSource` are now treated as complete CSV files, including support for - comments beginning with a `+++#+++` symbol as well as support for new lines within +* Text blocks in `@CsvSource` are now treated like complete CSV files, including support + for comments beginning with a `+++#+++` symbol as well as support for new lines within quoted strings. See the <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-CsvSource, User Guide>> for details and examples. -* The quote character for _quoted strings_ in `@CsvSource` is now configurable via the new - `quoteCharacter` attribute, which defaults to a single quote (`'`) for backward - compatibility. +* The quote character for _quoted strings_ in `@CsvSource` and `@CsvFileSource` is now + configurable via new `quoteCharacter` attributes in each annotation. [[release-notes-5.8.2-junit-vintage]] diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 0bc94d85b421..efa460277ba1 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -1344,9 +1344,9 @@ The default delimiter is a comma (`,`), but you can use another character by set `String` delimiter instead of a single character. However, both delimiter attributes cannot be set simultaneously. -By default, `@CsvSource` uses a single quote `'` as its quote character, but this can be +By default, `@CsvSource` uses a single quote (`'`) as its quote character, but this can be changed via the `quoteCharacter` attribute. See the `'lemon, lime'` value in the example -above and in the table below. An empty, quoted value `''` results in an empty `String` +above and in the table below. An empty, quoted value (`''`) results in an empty `String` unless the `emptyValue` attribute is set; whereas, an entirely _empty_ value is interpreted as a `null` reference. By specifying one or more `nullValues`, a custom value can be interpreted as a `null` reference (see the `NIL` example in the table below). An @@ -1395,9 +1395,9 @@ In contrast to CSV records supplied via the `value` attribute, a text block can comments. Any line beginning with a `+++#+++` symbol will be treated as a comment and ignored. Note, however, that the `+++#+++` symbol must be the first character on the line without any leading whitespace. It is therefore recommended that the closing text block -delimiter `"""` be placed either at the end of the last line of input or on the following -line, left aligned with the rest of the input (as can be seen in the example below which -demonstrates formatting similar to a table). +delimiter (`"""`) be placed either at the end of the last line of input or on the +following line, left aligned with the rest of the input (as can be seen in the example +below which demonstrates formatting similar to a table). [source,java,indent=0] ---- @@ -1458,8 +1458,9 @@ include::{testResourcesDir}/two-column.csv[] ---- In contrast to the default syntax used in `@CsvSource`, `@CsvFileSource` uses a double -quote `"` as the quote character. See the `"United States of America"` value in the -example above. An empty, quoted value `""` results in an empty `String` unless the +quote (`+++"+++`) as the quote character by default, but this can be changed via the +`quoteCharacter` attribute. See the `"United States of America"` value in the example +above. An empty, quoted value (`+++""+++`) results in an empty `String` unless the `emptyValue` attribute is set; whereas, an entirely _empty_ value is interpreted as a `null` reference. By specifying one or more `nullValues`, a custom value can be interpreted as a `null` reference. An `ArgumentConversionException` is thrown if the diff --git a/documentation/src/test/resources/two-column.csv b/documentation/src/test/resources/two-column.csv index 917fba3ff5b6..011550be3707 100644 --- a/documentation/src/test/resources/two-column.csv +++ b/documentation/src/test/resources/two-column.csv @@ -1,4 +1,4 @@ -Country, reference +Country, Reference Sweden, 1 Poland, 2 "United States of America", 3 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java index 1c582b2ea45b..a2c9f400b285 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java @@ -36,12 +36,12 @@ * via either {@link #delimiter} or {@link #delimiterString}. * *

In contrast to the default syntax used in {@code @CsvSource}, {@code @CsvFileSource} - * uses a double quote ({@code "}) as its quote character (see the User Guide for - * examples). An empty, quoted value ({@code ""}) results in an empty {@link String} - * unless the {@link #emptyValue} attribute is set; whereas, an entirely empty - * value is interpreted as a {@code null} reference. By specifying one or more - * {@link #nullValues} a custom value can be interpreted as a {@code null} reference - * (see the User Guide for an example). An + * uses a double quote ({@code "}) as its quote character by default, but this can + * be changed via {@link #quoteCharacter}. An empty, quoted value ({@code ""}) + * results in an empty {@link String} unless the {@link #emptyValue} attribute is + * set; whereas, an entirely empty value is interpreted as a {@code null} + * reference. By specifying one or more {@link #nullValues} a custom value can be + * interpreted as a {@code null} reference (see the User Guide for an example). An * {@link org.junit.jupiter.params.converter.ArgumentConversionException * ArgumentConversionException} is thrown if the target type of a {@code null} * reference is a primitive type. @@ -95,6 +95,19 @@ */ String lineSeparator() default "\n"; + /** + * The quote character to use for quoted strings. + * + *

Defaults to a double quote ({@code "}). + * + *

You may change the quote character to anything that makes sense for + * your use case. + * + * @since 5.8.2 + */ + @API(status = EXPERIMENTAL, since = "5.8.2") + char quoteCharacter() default '"'; + /** * The column delimiter character to use when reading the CSV files. * diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java index c55a88e1fca4..7c7a4fd2f9ad 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java @@ -24,7 +24,6 @@ class CsvParserFactory { private static final String DEFAULT_DELIMITER = ","; private static final String LINE_SEPARATOR = "\n"; - private static final char DOUBLE_QUOTE = '"'; private static final char EMPTY_CHAR = '\0'; private static final boolean COMMENT_PROCESSING_FOR_CSV_FILE_SOURCE = true; @@ -37,7 +36,7 @@ static CsvParser createParserFor(CsvSource annotation) { static CsvParser createParserFor(CsvFileSource annotation) { String delimiter = selectDelimiter(annotation, annotation.delimiter(), annotation.delimiterString()); - return createParser(delimiter, annotation.lineSeparator(), DOUBLE_QUOTE, annotation.emptyValue(), + return createParser(delimiter, annotation.lineSeparator(), annotation.quoteCharacter(), annotation.emptyValue(), annotation.maxCharsPerColumn(), COMMENT_PROCESSING_FOR_CSV_FILE_SOURCE, annotation.ignoreLeadingAndTrailingWhitespace()); } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java index 7016d86d614e..a820ab5a7ca2 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java @@ -65,6 +65,19 @@ void providesArgumentsForCarriageReturnAndSemicolon() { assertThat(arguments).containsExactly(array("foo", "bar"), array("baz", "qux")); } + @Test + void providesArgumentsWithCustomQuoteCharacter() { + var annotation = csvFileSource()// + .resources("test.csv")// + .quoteCharacter('\'')// + .build(); + + var arguments = provideArguments(annotation, "foo, 'bar \"and\" baz', qux \n 'lemon lime', banana, apple"); + + assertThat(arguments).containsExactly(array("foo", "bar \"and\" baz", "qux"), + array("lemon lime", "banana", "apple")); + } + @Test void providesArgumentsWithStringDelimiter() { var annotation = csvFileSource()// diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java index 47099b70c7d3..aa8a1d1e8fc9 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java @@ -34,6 +34,7 @@ static MockCsvFileSourceBuilder csvFileSource() { // ------------------------------------------------------------------------- + private char quoteCharacter = '\0'; protected char delimiter = '\0'; protected String delimiterString = ""; protected String emptyValue = ""; @@ -46,6 +47,11 @@ private MockCsvAnnotationBuilder() { protected abstract B getSelf(); + B quoteCharacter(char quoteCharacter) { + this.quoteCharacter = quoteCharacter; + return getSelf(); + } + B delimiter(char delimiter) { this.delimiter = delimiter; return getSelf(); @@ -84,7 +90,10 @@ static class MockCsvSourceBuilder extends MockCsvAnnotationBuilder