From 62876306859b90009e2cbbab6f3e7b1744e109c7 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Wed, 28 May 2025 21:23:21 +0200 Subject: [PATCH 01/52] Remove univocity-parsers license --- .../LICENSE-univocity-parsers.md | 168 ------------------ .../junit-jupiter-params.gradle.kts | 4 - ...nit-platform-console-standalone.gradle.kts | 4 - 3 files changed, 176 deletions(-) delete mode 100644 junit-jupiter-params/LICENSE-univocity-parsers.md diff --git a/junit-jupiter-params/LICENSE-univocity-parsers.md b/junit-jupiter-params/LICENSE-univocity-parsers.md deleted file mode 100644 index f58ac2e9c2cf..000000000000 --- a/junit-jupiter-params/LICENSE-univocity-parsers.md +++ /dev/null @@ -1,168 +0,0 @@ -Apache License -============== - -_Version 2.0, January 2004_ -_<>_ - -### Terms and Conditions for use, reproduction, and distribution - -#### 1. Definitions - -“License” shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -“Licensor” shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -“Legal Entity” shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, “control” means **(i)** the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the -outstanding shares, or **(iii)** beneficial ownership of such entity. - -“You” (or “Your”) shall mean an individual or Legal Entity exercising -permissions granted by this License. - -“Source” form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -“Object” form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -“Work” shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -“Derivative Works” shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -“Contribution” shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -“submitted” means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as “Not a Contribution.” - -“Contributor” shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -#### 2. Grant of Copyright License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -#### 3. Grant of Patent License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -#### 4. Redistribution - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -* **(a)** You must give any other recipients of the Work or Derivative Works a copy of -this License; and -* **(b)** You must cause any modified files to carry prominent notices stating that You -changed the files; and -* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. - -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -#### 5. Submission of Contributions - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -#### 6. Trademarks - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -#### 7. Disclaimer of Warranty - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -#### 8. Limitation of Liability - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -#### 9. Accepting Warranty or Additional Liability - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 45b9b56127bb..8cf5811fe8e9 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -47,10 +47,6 @@ tasks { } shadowJar { relocate("com.univocity", "org.junit.jupiter.params.shadow.com.univocity") - from(projectDir) { - include("LICENSE-univocity-parsers.md") - into("META-INF") - } } compileJava { options.compilerArgs.addAll(listOf( diff --git a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts index 1ef341f586ff..a79c45ec8386 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -44,10 +44,6 @@ tasks { include("LICENSE-picocli.md") into("META-INF") } - from(dependencyProject(project.projects.junitJupiterParams).projectDir) { - include("LICENSE-univocity-parsers.md") - into("META-INF") - } from(shadowedArtifactsFile) { into("META-INF") } From 7d98380c394a79c744eb67a3afa2b47c32c6f858 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Wed, 28 May 2025 21:29:24 +0200 Subject: [PATCH 02/52] Do not create a shadow jar from com.univocity --- junit-jupiter-params/junit-jupiter-params.gradle.kts | 3 --- 1 file changed, 3 deletions(-) diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 8cf5811fe8e9..cf634abe09ec 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -45,9 +45,6 @@ tasks { """) } } - shadowJar { - relocate("com.univocity", "org.junit.jupiter.params.shadow.com.univocity") - } compileJava { options.compilerArgs.addAll(listOf( "--add-modules", "univocity.parsers", From aabcd600402eb92c478b385eff8ed99f16c7d029 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Wed, 28 May 2025 21:52:45 +0200 Subject: [PATCH 03/52] Rework arguments providers to use FastCSV --- gradle/libs.versions.toml | 2 +- .../junit-jupiter-params.gradle.kts | 11 +- .../params/provider/CsvArgumentsProvider.java | 119 +++++-------- .../provider/CsvFileArgumentsProvider.java | 116 +++++-------- .../params/provider/CsvParserFactory.java | 90 ---------- .../params/provider/CsvReaderFactory.java | 156 ++++++++++++++++++ 6 files changed, 242 insertions(+), 252 deletions(-) delete mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9adce7a6ba81..a22c1b0a32a8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,7 +71,7 @@ slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.17" } snapshotTests-junit5 = { module = "de.skuzzle.test:snapshot-tests-junit5", version.ref = "snapshotTests" } snapshotTests-xml = { module = "de.skuzzle.test:snapshot-tests-xml", version.ref = "snapshotTests" } spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" } -univocity-parsers = { module = "com.sonofab1rd:univocity-parsers", version = "2.10.2" } +fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0-SNAPSHOT" } xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlunit" } xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } testingAnnotations = { module = "com.gradle:develocity-testing-annotations", version = "2.0.1" } diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index cf634abe09ec..d8ebd114e2ed 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -20,7 +20,7 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnly(libs.jspecify) - shadowed(libs.univocity.parsers) + implementation(libs.fastcsv) compileOnly(kotlin("stdlib")) @@ -29,7 +29,6 @@ dependencies { } extraJavaModuleInfo { - automaticModule(libs.univocity.parsers, "univocity.parsers") failOnMissingModuleInfo = false } @@ -47,8 +46,8 @@ tasks { } compileJava { options.compilerArgs.addAll(listOf( - "--add-modules", "univocity.parsers", - "--add-reads", "${javaModuleName}=univocity.parsers" + "--add-modules", "de.siegmar.fastcsv", + "--add-reads", "${javaModuleName}=de.siegmar.fastcsv" )) } compileJmhJava { @@ -61,8 +60,8 @@ tasks { } javadoc { (options as StandardJavadocDocletOptions).apply { - addStringOption("-add-modules", "univocity.parsers") - addStringOption("-add-reads", "${javaModuleName}=univocity.parsers") + addStringOption("-add-modules", "de.siegmar.fastcsv") + addStringOption("-add-reads", "${javaModuleName}=de.siegmar.fastcsv") } } } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 9ec3aa038417..721f81c72ad7 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -10,19 +10,14 @@ package org.junit.jupiter.params.provider; -import static java.util.Objects.requireNonNull; -import static org.junit.jupiter.params.provider.CsvParserFactory.createParserFor; - -import java.io.StringReader; import java.lang.annotation.Annotation; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; -import com.univocity.parsers.csv.CsvParser; +import de.siegmar.fastcsv.reader.CsvReader; +import de.siegmar.fastcsv.reader.CsvRecord; +import de.siegmar.fastcsv.reader.NamedCsvRecord; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Named; @@ -42,72 +37,37 @@ class CsvArgumentsProvider extends AnnotationBasedArgumentsProvider { @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, CsvSource csvSource) { - Set nullValues = Set.of(csvSource.nullValues()); - CsvParser csvParser = createParserFor(csvSource); - final boolean textBlockDeclared = !csvSource.textBlock().isEmpty(); - Preconditions.condition(csvSource.value().length > 0 ^ textBlockDeclared, - () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); - - return textBlockDeclared ? parseTextBlock(csvSource, csvParser, nullValues) - : parseValueArray(csvSource, csvParser, nullValues); - } - private Stream parseTextBlock(CsvSource csvSource, CsvParser csvParser, Set nullValues) { - String textBlock = csvSource.textBlock(); - boolean useHeadersInDisplayName = csvSource.useHeadersInDisplayName(); - List argumentsList = new ArrayList<>(); - - try { - List<@Nullable String[]> csvRecords = csvParser.parseAll(new StringReader(textBlock)); - String[] headers = useHeadersInDisplayName ? getHeaders(csvParser) : null; - - AtomicInteger index = new AtomicInteger(0); - for (var csvRecord : csvRecords) { - index.incrementAndGet(); - Preconditions.notNull(csvRecord, - () -> "Record at index " + index + " contains invalid CSV: \"\"\"\n" + textBlock + "\n\"\"\""); - argumentsList.add(processCsvRecord(csvRecord, nullValues, useHeadersInDisplayName, headers)); + Preconditions.condition(csvSource.value().length > 0 ^ !csvSource.textBlock().isEmpty(), + () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); + + List arguments = new ArrayList<>(); + + try (CsvReader reader = CsvReaderFactory.createReaderFor(csvSource, getData(csvSource))) { + for (CsvRecord record : reader) { + arguments.add(processCsvRecord(record, csvSource.useHeadersInDisplayName())); } } catch (Throwable throwable) { throw handleCsvException(throwable, csvSource); } - return argumentsList.stream(); + return arguments.stream(); } - private Stream parseValueArray(CsvSource csvSource, CsvParser csvParser, Set nullValues) { - boolean useHeadersInDisplayName = csvSource.useHeadersInDisplayName(); - List argumentsList = new ArrayList<>(); - - try { - String[] headers = null; - AtomicInteger index = new AtomicInteger(0); - for (String input : csvSource.value()) { - index.incrementAndGet(); - String[] csvRecord = csvParser.parseLine(input + LINE_SEPARATOR); - // Lazily retrieve headers if necessary. - if (useHeadersInDisplayName && headers == null) { - headers = getHeaders(csvParser); - continue; + private static String getData(CsvSource csvSource) { + if (!csvSource.textBlock().isEmpty()) { + return csvSource.textBlock(); + } + else { + for (int i = 0; i < csvSource.value().length; i++) { + if (csvSource.value()[i].isEmpty()) { + String message = String.format("Record at index %d contains empty CSV", i + 1); + throw new PreconditionViolationException(message); } - Preconditions.notNull(csvRecord, - () -> "Record at index " + index + " contains invalid CSV: \"" + input + "\""); - argumentsList.add(processCsvRecord(csvRecord, nullValues, useHeadersInDisplayName, headers)); } + return String.join(LINE_SEPARATOR, csvSource.value()); } - catch (Throwable throwable) { - throw handleCsvException(throwable, csvSource); - } - - return argumentsList.stream(); - } - - // Cannot get parsed headers until after parsing has started. - static String[] getHeaders(CsvParser csvParser) { - return Arrays.stream(csvParser.getContext().parsedHeaders())// - .map(String::trim)// - .toArray(String[]::new); } /** @@ -115,30 +75,23 @@ static String[] getHeaders(CsvParser csvParser) { * {@link Named} if necessary (for CSV header support), and returns the * CSV record wrapped in an {@link Arguments} instance. */ - static Arguments processCsvRecord(@Nullable String[] csvRecord, Set nullValues, - boolean useHeadersInDisplayName, String @Nullable [] headers) { - - // Nothing to process? - if (nullValues.isEmpty() && !useHeadersInDisplayName) { - return Arguments.of((Object[]) csvRecord); - } + static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayName) { + Preconditions.condition(!useHeadersInDisplayName || record.getFieldCount() <= getHeaders(record).size(), + () -> String.format( // + "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s", // + record.getFieldCount(), getHeaders(record).size(), record.getFields())); // - Preconditions.condition(!useHeadersInDisplayName || (csvRecord.length <= requireNonNull(headers).length), - () -> "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s".formatted( - csvRecord.length, requireNonNull(headers).length, Arrays.toString(csvRecord))); + @Nullable Object[] arguments = new Object[record.getFields().size()]; - @Nullable - Object[] arguments = new Object[csvRecord.length]; - for (int i = 0; i < csvRecord.length; i++) { - Object column = csvRecord[i]; - if (column != null && nullValues.contains(column)) { - column = null; - } + for (int i = 0; i < record.getFields().size(); i++) { + String field = record.getFields().get(i); + Object argument = CsvReaderFactory.DefaultFieldModifier.NULL_MARKER.equals(field) ? null : field; if (useHeadersInDisplayName) { - column = asNamed(requireNonNull(headers)[i] + " = " + column, column); + argument = asNamed(getHeaders(record).get(i) + " = " + argument, argument); } - arguments[i] = column; + arguments[i] = argument; } + return Arguments.of(arguments); } @@ -147,6 +100,10 @@ static Arguments processCsvRecord(@Nullable String[] csvRecord, Set null return Named.of(name, column); } + private static List getHeaders(CsvRecord record) { + return ((NamedCsvRecord) record).getHeader(); + } + /** * @return this method always throws an exception and therefore never * returns anything; the return type is merely present to allow this diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java index fe5bc1583084..b05a95623e60 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java @@ -10,29 +10,21 @@ package org.junit.jupiter.params.provider; -import static java.util.Objects.requireNonNull; -import static java.util.Spliterators.spliteratorUnknownSize; -import static java.util.stream.StreamSupport.stream; -import static org.junit.jupiter.params.provider.CsvArgumentsProvider.getHeaders; -import static org.junit.jupiter.params.provider.CsvArgumentsProvider.handleCsvException; -import static org.junit.jupiter.params.provider.CsvArgumentsProvider.processCsvRecord; -import static org.junit.jupiter.params.provider.CsvParserFactory.createParserFor; - import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; -import java.util.Iterator; import java.util.List; -import java.util.Set; import java.util.Spliterator; +import java.util.function.Consumer; import java.util.stream.Stream; +import java.util.stream.StreamSupport; -import com.univocity.parsers.csv.CsvParser; +import de.siegmar.fastcsv.reader.CsvReader; +import de.siegmar.fastcsv.reader.CsvRecord; -import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; @@ -57,9 +49,8 @@ class CsvFileArgumentsProvider extends AnnotationBasedArgumentsProvider provideArguments(ParameterDeclarations parameters, ExtensionContext context, CsvFileSource csvFileSource) { - Charset charset = getCharsetFrom(csvFileSource); - CsvParser csvParser = createParserFor(csvFileSource); + Charset charset = getCharsetFrom(csvFileSource); Stream resources = Arrays.stream(csvFileSource.resources()).map(inputStreamProvider::classpathResource); Stream files = Arrays.stream(csvFileSource.files()).map(inputStreamProvider::file); List sources = Stream.concat(resources, files).toList(); @@ -68,12 +59,12 @@ protected Stream provideArguments(ParameterDeclarations par return Preconditions.notEmpty(sources, "Resources or files must not be empty") .stream() .map(source -> source.open(context)) - .map(inputStream -> beginParsing(inputStream, csvFileSource, csvParser, charset)) - .flatMap(parser -> toStream(parser, csvFileSource)); + .map(inputStream -> CsvReaderFactory.createReaderFor(csvFileSource, inputStream, charset)) + .flatMap(reader -> toStream(reader, csvFileSource)); // @formatter:on } - private Charset getCharsetFrom(CsvFileSource csvFileSource) { + private static Charset getCharsetFrom(CsvFileSource csvFileSource) { try { return Charset.forName(csvFileSource.encoding()); } @@ -82,81 +73,58 @@ private Charset getCharsetFrom(CsvFileSource csvFileSource) { } } - private CsvParser beginParsing(InputStream inputStream, CsvFileSource csvFileSource, CsvParser csvParser, - Charset charset) { - try { - csvParser.beginParsing(inputStream, charset); - } - catch (Throwable throwable) { - throw handleCsvException(throwable, csvFileSource); - } - return csvParser; - } - - private Stream toStream(CsvParser csvParser, CsvFileSource csvFileSource) { - CsvParserIterator iterator = new CsvParserIterator(csvParser, csvFileSource); - return stream(spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) // - .skip(csvFileSource.numLinesToSkip()) // + private static Stream toStream(CsvReader reader, CsvFileSource csvFileSource) { + // @formatter:off + Stream stream = StreamSupport.stream( + CsvExceptionHandlingSpliterator.fromDelegate(reader.spliterator(), csvFileSource), false + ); + return stream + .skip(csvFileSource.numLinesToSkip()) + .map(record -> CsvArgumentsProvider.processCsvRecord( + record, csvFileSource.useHeadersInDisplayName()) + ) .onClose(() -> { try { - csvParser.stopParsing(); + reader.close(); } catch (Throwable throwable) { - throw handleCsvException(throwable, csvFileSource); + throw CsvArgumentsProvider.handleCsvException(throwable, csvFileSource); } }); + // @formatter:on } - private static class CsvParserIterator implements Iterator { - - private final CsvParser csvParser; - private final CsvFileSource csvFileSource; - private final boolean useHeadersInDisplayName; - private final Set nullValues; - - @Nullable - private Arguments nextArguments; + private record CsvExceptionHandlingSpliterator(Spliterator delegate, CsvFileSource csvFileSource) + implements Spliterator { - private String @Nullable [] headers; + static CsvExceptionHandlingSpliterator fromDelegate(Spliterator delegate, + CsvFileSource csvFileSource) { + return new CsvExceptionHandlingSpliterator<>(delegate, csvFileSource); + } - CsvParserIterator(CsvParser csvParser, CsvFileSource csvFileSource) { - this.csvParser = csvParser; - this.csvFileSource = csvFileSource; - this.useHeadersInDisplayName = csvFileSource.useHeadersInDisplayName(); - this.nullValues = Set.of(csvFileSource.nullValues()); - advance(); + @Override + public boolean tryAdvance(final Consumer action) { + try { + return delegate.tryAdvance(action); + } + catch (Throwable throwable) { + throw CsvArgumentsProvider.handleCsvException(throwable, csvFileSource); + } } @Override - public boolean hasNext() { - return this.nextArguments != null; + public Spliterator trySplit() { + return delegate.trySplit(); } @Override - public Arguments next() { - Arguments result = this.nextArguments; - advance(); - return requireNonNull(result); + public long estimateSize() { + return delegate.estimateSize(); } - private void advance() { - try { - String[] csvRecord = this.csvParser.parseNext(); - if (csvRecord != null) { - // Lazily retrieve headers if necessary. - if (this.useHeadersInDisplayName && this.headers == null) { - this.headers = getHeaders(this.csvParser); - } - this.nextArguments = processCsvRecord(csvRecord, this.nullValues, this.useHeadersInDisplayName, - this.headers); - } - else { - this.nextArguments = null; - } - } - catch (Throwable throwable) { - throw handleCsvException(throwable, this.csvFileSource); - } + @Override + public int characteristics() { + return delegate.characteristics(); } } 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 deleted file mode 100644 index ba1b1c0c34ac..000000000000 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2015-2025 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.params.provider; - -import java.lang.annotation.Annotation; - -import com.univocity.parsers.csv.CsvParser; -import com.univocity.parsers.csv.CsvParserSettings; - -import org.junit.platform.commons.util.Preconditions; - -/** - * @since 5.6 - */ -class CsvParserFactory { - - private static final String DEFAULT_DELIMITER = ","; - private static final String LINE_SEPARATOR = "\n"; - private static final char EMPTY_CHAR = '\0'; - private static final boolean COMMENT_PROCESSING_FOR_CSV_FILE_SOURCE = true; - - static CsvParser createParserFor(CsvSource annotation) { - String delimiter = selectDelimiter(annotation, annotation.delimiter(), annotation.delimiterString()); - boolean commentProcessingEnabled = !annotation.textBlock().isEmpty(); - return createParser(delimiter, LINE_SEPARATOR, annotation.quoteCharacter(), annotation.emptyValue(), - annotation.maxCharsPerColumn(), commentProcessingEnabled, annotation.useHeadersInDisplayName(), - annotation.ignoreLeadingAndTrailingWhitespace()); - } - - static CsvParser createParserFor(CsvFileSource annotation) { - String delimiter = selectDelimiter(annotation, annotation.delimiter(), annotation.delimiterString()); - return createParser(delimiter, annotation.lineSeparator(), annotation.quoteCharacter(), annotation.emptyValue(), - annotation.maxCharsPerColumn(), COMMENT_PROCESSING_FOR_CSV_FILE_SOURCE, - annotation.useHeadersInDisplayName(), annotation.ignoreLeadingAndTrailingWhitespace()); - } - - private static String selectDelimiter(Annotation annotation, char delimiter, String delimiterString) { - Preconditions.condition(delimiter == EMPTY_CHAR || delimiterString.isEmpty(), - () -> "The delimiter and delimiterString attributes cannot be set simultaneously in " + annotation); - - if (delimiter != EMPTY_CHAR) { - return String.valueOf(delimiter); - } - if (!delimiterString.isEmpty()) { - return delimiterString; - } - return DEFAULT_DELIMITER; - } - - private static CsvParser createParser(String delimiter, String lineSeparator, char quote, String emptyValue, - int maxCharsPerColumn, boolean commentProcessingEnabled, boolean headerExtractionEnabled, - boolean ignoreLeadingAndTrailingWhitespace) { - return new CsvParser(createParserSettings(delimiter, lineSeparator, quote, emptyValue, maxCharsPerColumn, - commentProcessingEnabled, headerExtractionEnabled, ignoreLeadingAndTrailingWhitespace)); - } - - private static CsvParserSettings createParserSettings(String delimiter, String lineSeparator, char quote, - String emptyValue, int maxCharsPerColumn, boolean commentProcessingEnabled, boolean headerExtractionEnabled, - boolean ignoreLeadingAndTrailingWhitespace) { - - CsvParserSettings settings = new CsvParserSettings(); - settings.setHeaderExtractionEnabled(headerExtractionEnabled); - settings.getFormat().setDelimiter(delimiter); - settings.getFormat().setLineSeparator(lineSeparator); - settings.getFormat().setQuote(quote); - settings.getFormat().setQuoteEscape(quote); - settings.setEmptyValue(emptyValue); - settings.setCommentProcessingEnabled(commentProcessingEnabled); - settings.setAutoConfigurationEnabled(false); - settings.setIgnoreLeadingWhitespaces(ignoreLeadingAndTrailingWhitespace); - settings.setIgnoreTrailingWhitespaces(ignoreLeadingAndTrailingWhitespace); - Preconditions.condition(maxCharsPerColumn > 0 || maxCharsPerColumn == -1, - () -> "maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); - settings.setMaxCharsPerColumn(maxCharsPerColumn); - // Do not use the built-in support for skipping rows/lines since it will - // throw an IllegalArgumentException if the file does not contain at least - // the number of specified lines to skip. - // settings.setNumberOfRowsToSkip(annotation.numLinesToSkip()); - return settings; - } - -} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java new file mode 100644 index 000000000000..8a2612cb8b86 --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.provider; + +import static de.siegmar.fastcsv.reader.CommentStrategy.NONE; +import static de.siegmar.fastcsv.reader.CommentStrategy.SKIP; + +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.nio.charset.Charset; +import java.util.Set; +import java.util.UUID; + +import de.siegmar.fastcsv.reader.CsvCallbackHandler; +import de.siegmar.fastcsv.reader.CsvReader; +import de.siegmar.fastcsv.reader.CsvRecord; +import de.siegmar.fastcsv.reader.CsvRecordHandler; +import de.siegmar.fastcsv.reader.FieldModifier; +import de.siegmar.fastcsv.reader.NamedCsvRecordHandler; + +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 5.13 + */ +class CsvReaderFactory { + + private static final String DEFAULT_DELIMITER = ","; + private static final char EMPTY_CHAR = '\0'; + private static final int MAX_FIELDS = 512; + + static CsvReader createReaderFor(CsvSource csvSource, String data) { + validateMaxCharsPerColumn(csvSource.maxCharsPerColumn()); + + String delimiter = selectDelimiter(csvSource, csvSource.delimiter(), csvSource.delimiterString()); + // @formatter:off + CsvReader.CsvReaderBuilder builder = CsvReader.builder() + .fieldSeparator(delimiter) + .lenientWhitespacesAroundQuotes(true) + .quoteCharacter(csvSource.quoteCharacter()) + .commentStrategy(csvSource.textBlock().isEmpty() ? NONE : SKIP); + + CsvCallbackHandler callbackHandler = createCallbackHandler( + csvSource.emptyValue(), + Set.of(csvSource.nullValues()), + csvSource.ignoreLeadingAndTrailingWhitespace(), + csvSource.maxCharsPerColumn(), + csvSource.useHeadersInDisplayName() + ); + // @formatter:on + return builder.build(callbackHandler, data); + } + + static CsvReader createReaderFor(CsvFileSource csvFileSource, InputStream inputStream, + Charset charset) { + + validateMaxCharsPerColumn(csvFileSource.maxCharsPerColumn()); + + String delimiter = selectDelimiter(csvFileSource, csvFileSource.delimiter(), csvFileSource.delimiterString()); + // @formatter:off + CsvReader.CsvReaderBuilder builder = CsvReader.builder() + .fieldSeparator(delimiter) + .lenientWhitespacesAroundQuotes(true) + .quoteCharacter(csvFileSource.quoteCharacter()) + .commentStrategy(SKIP); + + CsvCallbackHandler callbackHandler = createCallbackHandler( + csvFileSource.emptyValue(), + Set.of(csvFileSource.nullValues()), + csvFileSource.ignoreLeadingAndTrailingWhitespace(), + csvFileSource.maxCharsPerColumn(), + csvFileSource.useHeadersInDisplayName() + ); + // @formatter:on + return builder.build(callbackHandler, inputStream, charset); + } + + private static void validateMaxCharsPerColumn(int maxCharsPerColumn) { + Preconditions.condition(maxCharsPerColumn > 0 || maxCharsPerColumn == -1, + () -> "maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); + } + + private static String selectDelimiter(Annotation annotation, char delimiter, String delimiterString) { + Preconditions.condition(delimiter == EMPTY_CHAR || delimiterString.isEmpty(), + () -> "The delimiter and delimiterString attributes cannot be set simultaneously in " + annotation); + + if (delimiter != EMPTY_CHAR) { + return String.valueOf(delimiter); + } + if (!delimiterString.isEmpty()) { + return delimiterString; + } + return DEFAULT_DELIMITER; + } + + private static CsvCallbackHandler createCallbackHandler(String emptyValue, + Set nullValues, boolean ignoreLeadingAndTrailingWhitespaces, int maxCharsPerColumn, + boolean useHeadersInDisplayName) { + + int maxFieldSize = maxCharsPerColumn == -1 ? Integer.MAX_VALUE : maxCharsPerColumn; + FieldModifier modifier = new DefaultFieldModifier(emptyValue, nullValues, ignoreLeadingAndTrailingWhitespaces); + + // @formatter:off + if (useHeadersInDisplayName) { + return NamedCsvRecordHandler.builder() + .maxFields(MAX_FIELDS) + .maxFieldSize(maxFieldSize) + .maxRecordSize(Integer.MAX_VALUE) + .fieldModifier(modifier) + .build(); + } + return CsvRecordHandler.builder() + .maxFields(MAX_FIELDS) + .maxFieldSize(maxFieldSize) + .maxRecordSize(Integer.MAX_VALUE) + .fieldModifier(modifier) + .build(); + // @formatter:on + } + + record DefaultFieldModifier(String emptyValue, Set nullValues, boolean ignoreLeadingAndTrailingWhitespaces) + implements FieldModifier { + /** + * Represents a {@code null} value and serves as a workaround + * since FastCSV does not allow the modified field value to be {@code null}. + *

+ * The marker is generated with a unique ID to ensure it cannot conflict with actual CSV content. + */ + static final String NULL_MARKER = String.format("", UUID.randomUUID()); + + @Override + public String modify(long unusedStartingLineNumber, int unusedFieldIdx, boolean quoted, String field) { + if (field.isEmpty() && quoted && !emptyValue.isEmpty()) { + return emptyValue; + } + if (field.isBlank() && !quoted) { + return NULL_MARKER; + } + String modifiedField = (ignoreLeadingAndTrailingWhitespaces && !quoted) ? field.strip() : field; + if (nullValues.contains(modifiedField)) { + return NULL_MARKER; + } + return modifiedField; + } + + } + +} From fd33e6f91808aa6809be022cecdf5c8ab2fe0979 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Wed, 28 May 2025 22:32:29 +0200 Subject: [PATCH 04/52] test: Update expected root cause exceptions --- jupiter-tests/jupiter-tests.gradle.kts | 1 + .../jupiter/params/provider/CsvArgumentsProviderTests.java | 4 ++-- .../params/provider/CsvFileArgumentsProviderTests.java | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/jupiter-tests/jupiter-tests.gradle.kts b/jupiter-tests/jupiter-tests.gradle.kts index 9b1baaf781a7..e924ecb834e2 100644 --- a/jupiter-tests/jupiter-tests.gradle.kts +++ b/jupiter-tests/jupiter-tests.gradle.kts @@ -24,6 +24,7 @@ dependencies { testImplementation(libs.kotlinx.coroutines) testImplementation(libs.groovy4) testImplementation(libs.memoryfilesystem) + testImplementation(libs.fastcsv) testImplementation(testFixtures(projects.junitJupiterApi)) testImplementation(testFixtures(projects.junitJupiterEngine)) testImplementation(testFixtures(projects.junitPlatformLauncher)) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index 6cc030cf447f..d18917c00934 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -275,7 +275,7 @@ void throwsExceptionIfSourceExceedsMaxCharsPerColumnConfig() { assertThatExceptionOfType(CsvParsingException.class)// .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// - .withRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); + .withRootCauseInstanceOf(de.siegmar.fastcsv.reader.CsvParseException.class); } @Test @@ -294,7 +294,7 @@ void throwsExceptionWhenSourceExceedsDefaultMaxCharsPerColumnConfig() { assertThatExceptionOfType(CsvParsingException.class)// .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// - .withRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); + .withRootCauseInstanceOf(de.siegmar.fastcsv.reader.CsvParseException.class); } @Test diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java index 06d27ddf5ae7..f9f54a737172 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java @@ -379,7 +379,7 @@ void throwsExceptionForInvalidCsvFormat() { assertThat(exception)// .hasMessageStartingWith("Failed to parse CSV input configured via Mock for CsvFileSource")// - .hasRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); + .hasRootCauseInstanceOf(de.siegmar.fastcsv.reader.CsvParseException.class); } @Test @@ -483,7 +483,7 @@ void throwsExceptionForExceedsMaxCharsFileWithDefaultConfig(@TempDir Path tempDi assertThat(exception)// .hasMessageStartingWith("Failed to parse CSV input configured via Mock for CsvFileSource")// - .hasRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); + .hasRootCauseInstanceOf(de.siegmar.fastcsv.reader.CsvParseException.class); } @Test From 1dc063fc1ad675514dc6db0b5cbc70900dfb180a Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Wed, 28 May 2025 22:38:29 +0200 Subject: [PATCH 05/52] test: Update expected message on empty CSV --- .../jupiter/params/provider/CsvArgumentsProvider.java | 2 +- .../params/provider/CsvArgumentsProviderTests.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 721f81c72ad7..c069eeef0a3f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -62,7 +62,7 @@ private static String getData(CsvSource csvSource) { else { for (int i = 0; i < csvSource.value().length; i++) { if (csvSource.value()[i].isEmpty()) { - String message = String.format("Record at index %d contains empty CSV", i + 1); + String message = String.format("CSV record at index %d is empty", i + 1); throw new PreconditionViolationException(message); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index d18917c00934..700d113a0a16 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -30,12 +30,12 @@ class CsvArgumentsProviderTests { @Test - void throwsExceptionForInvalidCsv() { + void throwsExceptionForEmptyLine() { var annotation = csvSource("foo", "bar", ""); assertThatExceptionOfType(JUnitException.class)// .isThrownBy(() -> provideArguments(annotation).toArray())// - .withMessage("Record at index 3 contains invalid CSV: \"\""); + .withMessage("CSV record at index 3 is empty"); } @Test @@ -133,7 +133,7 @@ void ignoresLeadingAndTrailingSpaces() { var arguments = provideArguments(annotation); assertThat(arguments).containsExactly( - new Object[][] { { "1", "a" }, { "2", " b" }, { "3", "c " }, { "4", " d " } }); + new Object[][] { { "1", "a" }, { "2", " b" }, { "3", "c " }, { "4", " d " } }); } @Test @@ -364,7 +364,7 @@ private void supportsCsvHeaders(CsvSource csvSource) { }); assertThat(argumentsAsStrings).containsExactly(array("FRUIT = apple", "RANK = 1"), - array("FRUIT = banana", "RANK = 2")); + array("FRUIT = banana", "RANK = 2")); } @Test @@ -378,7 +378,7 @@ void throwsExceptionIfColumnCountExceedsHeaderCount() { assertThatExceptionOfType(PreconditionViolationException.class)// .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessage( - "The number of columns (3) exceeds the number of supplied headers (2) in CSV record: [banana, 2, BOOM!]"); + "The number of columns (3) exceeds the number of supplied headers (2) in CSV record: [banana, 2, BOOM!]"); } private Stream provideArguments(CsvSource annotation) { From b8efe077d69b0e001e1104d12cb8755ec473a2ee Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Wed, 28 May 2025 22:44:12 +0200 Subject: [PATCH 06/52] test: Cover additional cases for empty values --- .../params/provider/CsvArgumentsProviderTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index 700d113a0a16..e58245bff85c 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -233,20 +233,20 @@ void throwsExceptionIfBothDelimitersAreSimultaneouslySet() { @Test void defaultEmptyValueAndDefaultNullValue() { - var annotation = csvSource("'', null, , apple"); + var annotation = csvSource("'', null, ,, apple"); var arguments = provideArguments(annotation); - assertThat(arguments).containsExactly(array("", "null", null, "apple")); + assertThat(arguments).containsExactly(array("", "null", null, null, "apple")); } @Test void customEmptyValueAndDefaultNullValue() { - var annotation = csvSource().emptyValue("EMPTY").lines("'', null, , apple").build(); + var annotation = csvSource().emptyValue("EMPTY").lines("'', null, ,, apple").build(); var arguments = provideArguments(annotation); - assertThat(arguments).containsExactly(array("EMPTY", "null", null, "apple")); + assertThat(arguments).containsExactly(array("EMPTY", "null", null, null, "apple")); } @Test From f62814c7011a5894d3d5b7eb84148e1f25823587 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 1 Jun 2025 12:01:01 +0200 Subject: [PATCH 07/52] Move "either value or textBlock" validation to getData(CsvSource) --- .../junit/jupiter/params/provider/CsvArgumentsProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index c069eeef0a3f..825ec24e2700 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -38,9 +38,6 @@ class CsvArgumentsProvider extends AnnotationBasedArgumentsProvider { protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, CsvSource csvSource) { - Preconditions.condition(csvSource.value().length > 0 ^ !csvSource.textBlock().isEmpty(), - () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); - List arguments = new ArrayList<>(); try (CsvReader reader = CsvReaderFactory.createReaderFor(csvSource, getData(csvSource))) { @@ -56,6 +53,9 @@ protected Stream provideArguments(ParameterDeclarations par } private static String getData(CsvSource csvSource) { + Preconditions.condition(csvSource.value().length > 0 ^ !csvSource.textBlock().isEmpty(), + () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); + if (!csvSource.textBlock().isEmpty()) { return csvSource.textBlock(); } From e768e7a76d4646d91487c6f8437bb154af071004 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 1 Jun 2025 13:12:18 +0200 Subject: [PATCH 08/52] Deprecate CsvFileSource.lineSeparator as it's now detected automatically --- .../junit/jupiter/params/provider/CsvArgumentsProvider.java | 4 +--- .../org/junit/jupiter/params/provider/CsvFileSource.java | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 825ec24e2700..990cf4aeee4f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -32,8 +32,6 @@ */ class CsvArgumentsProvider extends AnnotationBasedArgumentsProvider { - private static final String LINE_SEPARATOR = "\n"; - @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, CsvSource csvSource) { @@ -66,7 +64,7 @@ private static String getData(CsvSource csvSource) { throw new PreconditionViolationException(message); } } - return String.join(LINE_SEPARATOR, csvSource.value()); + return String.join("\n", csvSource.value()); } } 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 3f06e2ff62f0..9af729c8c74c 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 @@ -10,6 +10,7 @@ package org.junit.jupiter.params.provider; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; @@ -106,7 +107,12 @@ * or 2 characters, typically {@code "\r"}, {@code "\n"}, or {@code "\r\n"}. * *

Defaults to {@code "\n"}. + * + * @deprecated Since JUnit 6.0, line separators are detected automatically during CSV parsing. + * This setting is no longer required and will be ignored. */ + @API(status = DEPRECATED, since = "6.0") // + @Deprecated(since = "6.0", forRemoval = true) String lineSeparator() default "\n"; /** From 72ae17219c2f6f462d659cdec9ba7a730a47b50a Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 1 Jun 2025 13:16:43 +0200 Subject: [PATCH 09/52] test: Remove CsvFileSource.lineSeparator() usages --- .../params/provider/CsvFileArgumentsProviderTests.java | 3 --- .../jupiter/params/provider/MockCsvAnnotationBuilder.java | 7 ------- 2 files changed, 10 deletions(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java index f9f54a737172..796ba143f740 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java @@ -44,7 +44,6 @@ class CsvFileArgumentsProviderTests { void providesArgumentsForNewlineAndComma() { var annotation = csvFileSource()// .resources("test.csv")// - .lineSeparator("\n")// .delimiter(',')// .build(); @@ -57,7 +56,6 @@ void providesArgumentsForNewlineAndComma() { void providesArgumentsForCarriageReturnAndSemicolon() { var annotation = csvFileSource()// .resources("test.csv")// - .lineSeparator("\r")// .delimiter(';')// .build(); @@ -386,7 +384,6 @@ void throwsExceptionForInvalidCsvFormat() { void emptyValueIsAnEmptyWithCustomNullValueString() { var annotation = csvFileSource()// .resources("test.csv")// - .lineSeparator("\n")// .delimiter(',')// .nullValues("NIL")// .build(); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java index 22434b0f243f..7386fed94278 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java @@ -144,7 +144,6 @@ static class MockCsvFileSourceBuilder extends MockCsvAnnotationBuilder Date: Sun, 1 Jun 2025 13:17:23 +0200 Subject: [PATCH 10/52] CsvReaderFactory: set "since" to 6.0 --- .../org/junit/jupiter/params/provider/CsvReaderFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index 8a2612cb8b86..dc1a9d75145b 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -29,7 +29,7 @@ import org.junit.platform.commons.util.Preconditions; /** - * @since 5.13 + * @since 6.0 */ class CsvReaderFactory { From 3d3cadfbd72db7f07af65c7dc4023e29856ff3cc Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 1 Jun 2025 13:54:05 +0200 Subject: [PATCH 11/52] Preserve the original validation order --- .../params/provider/CsvArgumentsProvider.java | 2 ++ .../provider/CsvFileArgumentsProvider.java | 3 ++ .../params/provider/CsvReaderFactory.java | 36 +++++++++++-------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 990cf4aeee4f..34e376ed77ad 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -36,6 +36,8 @@ class CsvArgumentsProvider extends AnnotationBasedArgumentsProvider { protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, CsvSource csvSource) { + CsvReaderFactory.validate(csvSource); + List arguments = new ArrayList<>(); try (CsvReader reader = CsvReaderFactory.createReaderFor(csvSource, getData(csvSource))) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java index b05a95623e60..72cf260e4f35 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java @@ -51,6 +51,9 @@ protected Stream provideArguments(ParameterDeclarations par CsvFileSource csvFileSource) { Charset charset = getCharsetFrom(csvFileSource); + + CsvReaderFactory.validate(csvFileSource); + Stream resources = Arrays.stream(csvFileSource.resources()).map(inputStreamProvider::classpathResource); Stream files = Arrays.stream(csvFileSource.files()).map(inputStreamProvider::file); List sources = Stream.concat(resources, files).toList(); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index dc1a9d75145b..a3d8c64e618f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -37,10 +37,28 @@ class CsvReaderFactory { private static final char EMPTY_CHAR = '\0'; private static final int MAX_FIELDS = 512; - static CsvReader createReaderFor(CsvSource csvSource, String data) { + static void validate(CsvSource csvSource) { validateMaxCharsPerColumn(csvSource.maxCharsPerColumn()); + validateDelimiter(csvSource.delimiter(), csvSource.delimiterString(), csvSource); + } - String delimiter = selectDelimiter(csvSource, csvSource.delimiter(), csvSource.delimiterString()); + static void validate(CsvFileSource csvFileSource) { + validateMaxCharsPerColumn(csvFileSource.maxCharsPerColumn()); + validateDelimiter(csvFileSource.delimiter(), csvFileSource.delimiterString(), csvFileSource); + } + + private static void validateMaxCharsPerColumn(int maxCharsPerColumn) { + Preconditions.condition(maxCharsPerColumn > 0 || maxCharsPerColumn == -1, + () -> "maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); + } + + private static void validateDelimiter(char delimiter, String delimiterString, Annotation annotation) { + Preconditions.condition(delimiter == EMPTY_CHAR || delimiterString.isEmpty(), + () -> "The delimiter and delimiterString attributes cannot be set simultaneously in " + annotation); + } + + static CsvReader createReaderFor(CsvSource csvSource, String data) { + String delimiter = selectDelimiter(csvSource.delimiter(), csvSource.delimiterString()); // @formatter:off CsvReader.CsvReaderBuilder builder = CsvReader.builder() .fieldSeparator(delimiter) @@ -62,9 +80,7 @@ static CsvReader createReaderFor(CsvSource csvSource, Strin static CsvReader createReaderFor(CsvFileSource csvFileSource, InputStream inputStream, Charset charset) { - validateMaxCharsPerColumn(csvFileSource.maxCharsPerColumn()); - - String delimiter = selectDelimiter(csvFileSource, csvFileSource.delimiter(), csvFileSource.delimiterString()); + String delimiter = selectDelimiter(csvFileSource.delimiter(), csvFileSource.delimiterString()); // @formatter:off CsvReader.CsvReaderBuilder builder = CsvReader.builder() .fieldSeparator(delimiter) @@ -83,15 +99,7 @@ static CsvReader createReaderFor(CsvFileSource csvFileSourc return builder.build(callbackHandler, inputStream, charset); } - private static void validateMaxCharsPerColumn(int maxCharsPerColumn) { - Preconditions.condition(maxCharsPerColumn > 0 || maxCharsPerColumn == -1, - () -> "maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); - } - - private static String selectDelimiter(Annotation annotation, char delimiter, String delimiterString) { - Preconditions.condition(delimiter == EMPTY_CHAR || delimiterString.isEmpty(), - () -> "The delimiter and delimiterString attributes cannot be set simultaneously in " + annotation); - + private static String selectDelimiter(char delimiter, String delimiterString) { if (delimiter != EMPTY_CHAR) { return String.valueOf(delimiter); } From b88e12d8e1b0f1addbebcde885efe18a0d637a9e Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 1 Jun 2025 14:27:42 +0200 Subject: [PATCH 12/52] Formatting --- .../jupiter/params/provider/CsvArgumentsProvider.java | 11 ++++++----- .../jupiter/params/provider/CsvReaderFactory.java | 4 ++-- .../params/provider/CsvArgumentsProviderTests.java | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 34e376ed77ad..3f5d0387d158 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -54,7 +54,7 @@ protected Stream provideArguments(ParameterDeclarations par private static String getData(CsvSource csvSource) { Preconditions.condition(csvSource.value().length > 0 ^ !csvSource.textBlock().isEmpty(), - () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); + () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); if (!csvSource.textBlock().isEmpty()) { return csvSource.textBlock(); @@ -77,11 +77,12 @@ private static String getData(CsvSource csvSource) { */ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayName) { Preconditions.condition(!useHeadersInDisplayName || record.getFieldCount() <= getHeaders(record).size(), - () -> String.format( // - "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s", // - record.getFieldCount(), getHeaders(record).size(), record.getFields())); // + () -> String.format( // + "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s", // + record.getFieldCount(), getHeaders(record).size(), record.getFields())); // - @Nullable Object[] arguments = new Object[record.getFields().size()]; + @Nullable + Object[] arguments = new Object[record.getFields().size()]; for (int i = 0; i < record.getFields().size(); i++) { String field = record.getFields().get(i); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index a3d8c64e618f..b437a093c1e6 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -49,12 +49,12 @@ static void validate(CsvFileSource csvFileSource) { private static void validateMaxCharsPerColumn(int maxCharsPerColumn) { Preconditions.condition(maxCharsPerColumn > 0 || maxCharsPerColumn == -1, - () -> "maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); + () -> "maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); } private static void validateDelimiter(char delimiter, String delimiterString, Annotation annotation) { Preconditions.condition(delimiter == EMPTY_CHAR || delimiterString.isEmpty(), - () -> "The delimiter and delimiterString attributes cannot be set simultaneously in " + annotation); + () -> "The delimiter and delimiterString attributes cannot be set simultaneously in " + annotation); } static CsvReader createReaderFor(CsvSource csvSource, String data) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index e58245bff85c..bd73404357ea 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -133,7 +133,7 @@ void ignoresLeadingAndTrailingSpaces() { var arguments = provideArguments(annotation); assertThat(arguments).containsExactly( - new Object[][] { { "1", "a" }, { "2", " b" }, { "3", "c " }, { "4", " d " } }); + new Object[][] { { "1", "a" }, { "2", " b" }, { "3", "c " }, { "4", " d " } }); } @Test @@ -364,7 +364,7 @@ private void supportsCsvHeaders(CsvSource csvSource) { }); assertThat(argumentsAsStrings).containsExactly(array("FRUIT = apple", "RANK = 1"), - array("FRUIT = banana", "RANK = 2")); + array("FRUIT = banana", "RANK = 2")); } @Test @@ -378,7 +378,7 @@ void throwsExceptionIfColumnCountExceedsHeaderCount() { assertThatExceptionOfType(PreconditionViolationException.class)// .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessage( - "The number of columns (3) exceeds the number of supplied headers (2) in CSV record: [banana, 2, BOOM!]"); + "The number of columns (3) exceeds the number of supplied headers (2) in CSV record: [banana, 2, BOOM!]"); } private Stream provideArguments(CsvSource annotation) { From cc7fd8c3e562e053aa2718a535e3831ab85f7aa1 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 1 Jun 2025 14:32:27 +0200 Subject: [PATCH 13/52] ModularUserGuideTests: require de.siegmar.fastcsv module --- .../tooling/support/tests/ModularUserGuideTests.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java index 7882fc19847b..c6c44f8db164 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java @@ -59,6 +59,8 @@ class ModularUserGuideTests { // Byte Buddy is used by AssertJ's soft assertions which are used by the Engine Test Kit requires net.bytebuddy; + requires de.siegmar.fastcsv; + requires java.desktop; requires java.logging; requires java.scripting; @@ -94,6 +96,7 @@ private static List compile(Path temp, Writer out, Writer err) throws Ex ThirdPartyJars.copy(lib, "org.opentest4j.reporting", "open-test-reporting-tooling-spi"); ThirdPartyJars.copy(lib, "com.google.jimfs", "jimfs"); ThirdPartyJars.copy(lib, "com.google.guava", "guava"); + ThirdPartyJars.copy(lib, "siegmar", "fastcsv"); loadAllJUnitModules(lib); args.add("--module-path"); args.add(lib.toString()); @@ -135,6 +138,7 @@ private static void junit(Path temp, OutputFiles outputFiles) throws Exception { temp.resolve("lib").toString() // )) // .addArguments("--add-modules", "documentation") // + .addArguments("--add-reads", "org.junit.jupiter.params=de.siegmar.fastcsv") // .addArguments("--patch-module", "documentation=" + projectDir.resolve("src/test/resources")) // .addArguments("--module", "org.junit.platform.console") // .addArguments("execute") // @@ -170,6 +174,7 @@ void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp, "lib/apiguardian-api-.+\\.jar", // "lib/assertj-core-.+\\.jar", // "lib/byte-buddy-.+", // + "lib/fastcsv-.+\\.jar", // "lib/guava-.+\\.jar", // "lib/hamcrest-.+\\.jar", // "lib/jimfs-.+\\.jar", // From ff2691c61da8122f4d88245c004bdb8dabb340ae Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 1 Jun 2025 16:38:01 +0200 Subject: [PATCH 14/52] platform-tooling-support-tests: add FastCSV dependency --- .../platform-tooling-support-tests.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts index b321d09be1da..2c621a52ac83 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -64,6 +64,7 @@ dependencies { thirdPartyJars(libs.opentest4j) thirdPartyJars(libs.openTestReporting.tooling.spi) thirdPartyJars(libs.jimfs) + thirdPartyJars(libs.fastcsv) antJars(platform(projects.junitBom)) antJars(libs.bundles.ant) From cd3cc0ecc4b6d62d591730ba0bdfa301a3d3cfa2 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 1 Jun 2025 16:59:16 +0200 Subject: [PATCH 15/52] Add release notes --- .../release-notes/release-notes-6.0.0-M1.adoc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc index 626753edd77e..9fa797f799ab 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc @@ -110,6 +110,15 @@ repository on GitHub. * The contracts for the `Executable` parameters of Kotlin-specific `assertTimeout` functions were changed from `callsInPlace(executable, EXACTLY_ONCE)` to `callsInPlace(executable, AT_MOST_ONCE)` which might result in compilation errors. +* The `CsvFileSource.lineSeparator()` parameter is deprecated because line separators + are now detected automatically during CSV parsing. This setting is no longer required + and will be ignored. +* As a result of migrating from + https://github.com/uniVocity/univocity-parsers[univocity-parsers] to + https://fastcsv.org/[FastCSV] for CSV input handling, the root causes and messages + of exceptions thrown when parsing malformed CSV input may differ in rare cases. + While overall parsing behavior remains consistent, this may affect + custom error handling that relies on specific exception types or messages. [[release-notes-6.0.0-M1-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements @@ -117,6 +126,9 @@ repository on GitHub. * Kotlin's `suspend` modifier may now be applied to test and lifecycle methods. * The `Arguments` interface for parameterized tests is now officially a `@FunctionalInterface`. +* CSV parsing for parameterized tests now uses the https://fastcsv.org/[FastCSV] + library instead of the no longer maintained + https://github.com/uniVocity/univocity-parsers[univocity-parsers]. [[release-notes-6.0.0-M1-junit-vintage]] From 4eaba0913dc7f1282f1556341b4c48040d504e8a Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Mon, 2 Jun 2025 20:06:18 +0200 Subject: [PATCH 16/52] test: use CsvParseException import instead of a fully qualified name --- .../jupiter/params/provider/CsvArgumentsProviderTests.java | 6 ++++-- .../params/provider/CsvFileArgumentsProviderTests.java | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index bd73404357ea..6e094c2c6f57 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -17,6 +17,8 @@ import java.util.stream.Stream; +import de.siegmar.fastcsv.reader.CsvParseException; + import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; @@ -275,7 +277,7 @@ void throwsExceptionIfSourceExceedsMaxCharsPerColumnConfig() { assertThatExceptionOfType(CsvParsingException.class)// .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// - .withRootCauseInstanceOf(de.siegmar.fastcsv.reader.CsvParseException.class); + .withRootCauseInstanceOf(CsvParseException.class); } @Test @@ -294,7 +296,7 @@ void throwsExceptionWhenSourceExceedsDefaultMaxCharsPerColumnConfig() { assertThatExceptionOfType(CsvParsingException.class)// .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// - .withRootCauseInstanceOf(de.siegmar.fastcsv.reader.CsvParseException.class); + .withRootCauseInstanceOf(CsvParseException.class); } @Test diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java index 796ba143f740..457acd556c68 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java @@ -26,6 +26,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; +import de.siegmar.fastcsv.reader.CsvParseException; + import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; @@ -377,7 +379,7 @@ void throwsExceptionForInvalidCsvFormat() { assertThat(exception)// .hasMessageStartingWith("Failed to parse CSV input configured via Mock for CsvFileSource")// - .hasRootCauseInstanceOf(de.siegmar.fastcsv.reader.CsvParseException.class); + .hasRootCauseInstanceOf(CsvParseException.class); } @Test @@ -480,7 +482,7 @@ void throwsExceptionForExceedsMaxCharsFileWithDefaultConfig(@TempDir Path tempDi assertThat(exception)// .hasMessageStartingWith("Failed to parse CSV input configured via Mock for CsvFileSource")// - .hasRootCauseInstanceOf(de.siegmar.fastcsv.reader.CsvParseException.class); + .hasRootCauseInstanceOf(CsvParseException.class); } @Test From 7661c96adeb16aba34793416a487790cd5ee5f70 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Mon, 2 Jun 2025 22:53:41 +0200 Subject: [PATCH 17/52] Use condition() instead of creating PreconditionViolationException --- .../junit/jupiter/params/provider/CsvArgumentsProvider.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 3f5d0387d158..e8e79846b22d 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -62,8 +62,10 @@ private static String getData(CsvSource csvSource) { else { for (int i = 0; i < csvSource.value().length; i++) { if (csvSource.value()[i].isEmpty()) { - String message = String.format("CSV record at index %d is empty", i + 1); - throw new PreconditionViolationException(message); + int finalI = i; + Preconditions.condition(!csvSource.value()[i].isEmpty(), // + () -> "CSV record at index %d is empty".formatted(finalI + 1) // + ); } } return String.join("\n", csvSource.value()); From 59effbbe1432d9268baecfac2efcd51d84947c2e Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Tue, 3 Jun 2025 19:20:36 +0200 Subject: [PATCH 18/52] Respect alphabetical order in libs.versions.toml --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a22c1b0a32a8..1e6c53390232 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,6 +36,7 @@ checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checks classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.179" } commons-io = { module = "commons-io:commons-io", version = "2.19.0" } errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.38.0" } +fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0-SNAPSHOT" } groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.27" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } @@ -71,7 +72,6 @@ slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.17" } snapshotTests-junit5 = { module = "de.skuzzle.test:snapshot-tests-junit5", version.ref = "snapshotTests" } snapshotTests-xml = { module = "de.skuzzle.test:snapshot-tests-xml", version.ref = "snapshotTests" } spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" } -fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0-SNAPSHOT" } xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlunit" } xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } testingAnnotations = { module = "com.gradle:develocity-testing-annotations", version = "2.0.1" } From bfe368cb6d563d47a7ff044f91c28ac2c310d7cd Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Tue, 3 Jun 2025 19:57:05 +0200 Subject: [PATCH 19/52] Shadow FastCSV --- junit-jupiter-params/LICENSE-fastcsv.md | 21 +++++++++++++++++++ .../junit-jupiter-params.gradle.kts | 9 +++++++- ...nit-platform-console-standalone.gradle.kts | 4 ++++ .../provider/CsvArgumentsProviderTests.java | 3 +-- .../CsvFileArgumentsProviderTests.java | 3 +-- .../platform-tooling-support-tests.gradle.kts | 1 - .../support/tests/ModularUserGuideTests.java | 5 ----- 7 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 junit-jupiter-params/LICENSE-fastcsv.md diff --git a/junit-jupiter-params/LICENSE-fastcsv.md b/junit-jupiter-params/LICENSE-fastcsv.md new file mode 100644 index 000000000000..9b5b1683182c --- /dev/null +++ b/junit-jupiter-params/LICENSE-fastcsv.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Oliver Siegmar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index d8ebd114e2ed..75a1d08e5a76 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -20,7 +20,7 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnly(libs.jspecify) - implementation(libs.fastcsv) + shadowed(libs.fastcsv) compileOnly(kotlin("stdlib")) @@ -44,6 +44,13 @@ tasks { """) } } + shadowJar { + relocate("de.siegmar.fastcsv", "org.junit.jupiter.params.shadow.de.siegmar.fastcsv") + from(projectDir) { + include("LICENSE-fastcsv.md") + into("META-INF") + } + } compileJava { options.compilerArgs.addAll(listOf( "--add-modules", "de.siegmar.fastcsv", diff --git a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts index a79c45ec8386..6d4da5d5ceeb 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -44,6 +44,10 @@ tasks { include("LICENSE-picocli.md") into("META-INF") } + from(dependencyProject(project.projects.junitJupiterParams).projectDir) { + include("LICENSE-fastcsv.md") + into("META-INF") + } from(shadowedArtifactsFile) { into("META-INF") } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index 6e094c2c6f57..6abd355c180c 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -17,12 +17,11 @@ import java.util.stream.Stream; -import de.siegmar.fastcsv.reader.CsvParseException; - import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.shadow.de.siegmar.fastcsv.reader.CsvParseException; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java index 457acd556c68..62c1d75fa7d6 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java @@ -26,14 +26,13 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; -import de.siegmar.fastcsv.reader.CsvParseException; - import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvFileArgumentsProvider.InputStreamProvider; +import org.junit.jupiter.params.shadow.de.siegmar.fastcsv.reader.CsvParseException; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; diff --git a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts index 2c621a52ac83..b321d09be1da 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -64,7 +64,6 @@ dependencies { thirdPartyJars(libs.opentest4j) thirdPartyJars(libs.openTestReporting.tooling.spi) thirdPartyJars(libs.jimfs) - thirdPartyJars(libs.fastcsv) antJars(platform(projects.junitBom)) antJars(libs.bundles.ant) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java index c6c44f8db164..7882fc19847b 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java @@ -59,8 +59,6 @@ class ModularUserGuideTests { // Byte Buddy is used by AssertJ's soft assertions which are used by the Engine Test Kit requires net.bytebuddy; - requires de.siegmar.fastcsv; - requires java.desktop; requires java.logging; requires java.scripting; @@ -96,7 +94,6 @@ private static List compile(Path temp, Writer out, Writer err) throws Ex ThirdPartyJars.copy(lib, "org.opentest4j.reporting", "open-test-reporting-tooling-spi"); ThirdPartyJars.copy(lib, "com.google.jimfs", "jimfs"); ThirdPartyJars.copy(lib, "com.google.guava", "guava"); - ThirdPartyJars.copy(lib, "siegmar", "fastcsv"); loadAllJUnitModules(lib); args.add("--module-path"); args.add(lib.toString()); @@ -138,7 +135,6 @@ private static void junit(Path temp, OutputFiles outputFiles) throws Exception { temp.resolve("lib").toString() // )) // .addArguments("--add-modules", "documentation") // - .addArguments("--add-reads", "org.junit.jupiter.params=de.siegmar.fastcsv") // .addArguments("--patch-module", "documentation=" + projectDir.resolve("src/test/resources")) // .addArguments("--module", "org.junit.platform.console") // .addArguments("execute") // @@ -174,7 +170,6 @@ void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp, "lib/apiguardian-api-.+\\.jar", // "lib/assertj-core-.+\\.jar", // "lib/byte-buddy-.+", // - "lib/fastcsv-.+\\.jar", // "lib/guava-.+\\.jar", // "lib/hamcrest-.+\\.jar", // "lib/jimfs-.+\\.jar", // From b913ee3095cefe91093f6c29ff54368423da66a7 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Tue, 3 Jun 2025 22:27:00 +0200 Subject: [PATCH 20/52] Remove the no longer used extraJavaModuleInfo plugin --- gradle/libs.versions.toml | 1 - junit-jupiter-params/junit-jupiter-params.gradle.kts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1e6c53390232..f9d0145b875d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -99,7 +99,6 @@ buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.4" } commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.2.1" } develocity = { id = "com.gradle.develocity", version = "4.0.2" } errorProne = { id = "net.ltgt.errorprone", version = "4.2.0" } -extraJavaModuleInfo = { id = "org.gradlex.extra-java-module-info", version = "1.12" } foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "1.0.0" } gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.1" } jmh = { id = "me.champeau.jmh", version = "0.7.3" } diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 75a1d08e5a76..a05b689d1011 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -8,7 +8,6 @@ plugins { id("junitbuild.shadow-conventions") id("junitbuild.jmh-conventions") `java-test-fixtures` - alias(libs.plugins.extraJavaModuleInfo) } description = "JUnit Jupiter Params" @@ -28,10 +27,6 @@ dependencies { osgiVerification(projects.junitPlatformLauncher) } -extraJavaModuleInfo { - failOnMissingModuleInfo = false -} - tasks { jar { bundle { From 61350e428c729301b424de7133f8facb17d7b99b Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Tue, 3 Jun 2025 23:07:59 +0200 Subject: [PATCH 21/52] Updates according to the recent changes in FastCSV snapshot --- .../junit/jupiter/params/provider/CsvReaderFactory.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index b437a093c1e6..4254a23a6604 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -62,7 +62,9 @@ static CsvReader createReaderFor(CsvSource csvSource, Strin // @formatter:off CsvReader.CsvReaderBuilder builder = CsvReader.builder() .fieldSeparator(delimiter) - .lenientWhitespacesAroundQuotes(true) + .trimWhitespacesAroundQuotes(true) + .allowExtraFields(true) + .allowMissingFields(true) .quoteCharacter(csvSource.quoteCharacter()) .commentStrategy(csvSource.textBlock().isEmpty() ? NONE : SKIP); @@ -84,7 +86,9 @@ static CsvReader createReaderFor(CsvFileSource csvFileSourc // @formatter:off CsvReader.CsvReaderBuilder builder = CsvReader.builder() .fieldSeparator(delimiter) - .lenientWhitespacesAroundQuotes(true) + .trimWhitespacesAroundQuotes(true) + .allowExtraFields(true) + .allowMissingFields(true) .quoteCharacter(csvFileSource.quoteCharacter()) .commentStrategy(SKIP); From 43cd5c60c0df6f281d3cad70e8cbdaa4e79cc050 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sat, 7 Jun 2025 16:19:12 +0200 Subject: [PATCH 22/52] Avoid calling getFields() to prevent unnecessary object creation --- .../junit/jupiter/params/provider/CsvArgumentsProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index bdbd3b3afce4..62b2f0824ffa 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -84,10 +84,10 @@ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayN record.getFieldCount(), getHeaders(record).size(), record.getFields())); // @Nullable - Object[] arguments = new Object[record.getFields().size()]; + Object[] arguments = new Object[record.getFieldCount()]; - for (int i = 0; i < record.getFields().size(); i++) { - String field = record.getFields().get(i); + for (int i = 0; i < record.getFieldCount(); i++) { + String field = record.getField(i); Object argument = CsvReaderFactory.DefaultFieldModifier.NULL_MARKER.equals(field) ? null : field; if (useHeadersInDisplayName) { argument = asNamed(getHeaders(record).get(i) + " = " + argument, argument); From 50acf31b15aca569b875feb0f3c5e098519be5cb Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sat, 7 Jun 2025 16:22:06 +0200 Subject: [PATCH 23/52] Remove univocity.parsers module option from documentation.gradle.kts --- documentation/documentation.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index f7bdaa0c2c50..8ce3e441514d 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -451,7 +451,6 @@ tasks { addOption(ModuleSpecificJavadocFileOption("-add-reads", mapOf( "org.junit.platform.console" to provider { "info.picocli" }, "org.junit.platform.reporting" to provider { "org.opentest4j.reporting.events" }, - "org.junit.jupiter.params" to provider { "univocity.parsers" } ))) } From e2ac4f4b32045f98fde9cd39bd6bf7c79e44fd2c Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sat, 7 Jun 2025 22:03:07 +0200 Subject: [PATCH 24/52] Set allowDuplicateHeaderFields explicitly to avoid relying on defaults --- .../java/org/junit/jupiter/params/provider/CsvReaderFactory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index 4254a23a6604..1dc814c6f859 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -123,6 +123,7 @@ private static CsvCallbackHandler createCallbackHandler(Str // @formatter:off if (useHeadersInDisplayName) { return NamedCsvRecordHandler.builder() + .allowDuplicateHeaderFields(true) .maxFields(MAX_FIELDS) .maxFieldSize(maxFieldSize) .maxRecordSize(Integer.MAX_VALUE) From 62bddca7661174ed7c47a79900da72c5494ffbfe Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sat, 7 Jun 2025 22:05:11 +0200 Subject: [PATCH 25/52] Set skipEmptyLines explicitly to avoid relying on defaults --- .../org/junit/jupiter/params/provider/CsvReaderFactory.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index 1dc814c6f859..5c8ab5f6585c 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -61,6 +61,7 @@ static CsvReader createReaderFor(CsvSource csvSource, Strin String delimiter = selectDelimiter(csvSource.delimiter(), csvSource.delimiterString()); // @formatter:off CsvReader.CsvReaderBuilder builder = CsvReader.builder() + .skipEmptyLines(true) .fieldSeparator(delimiter) .trimWhitespacesAroundQuotes(true) .allowExtraFields(true) @@ -85,6 +86,7 @@ static CsvReader createReaderFor(CsvFileSource csvFileSourc String delimiter = selectDelimiter(csvFileSource.delimiter(), csvFileSource.delimiterString()); // @formatter:off CsvReader.CsvReaderBuilder builder = CsvReader.builder() + .skipEmptyLines(true) .fieldSeparator(delimiter) .trimWhitespacesAroundQuotes(true) .allowExtraFields(true) From 5884015d60829ff718938a78a48b52a1f517169a Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sat, 7 Jun 2025 22:37:44 +0200 Subject: [PATCH 26/52] Move common reader settings to constants --- .../params/provider/CsvReaderFactory.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index 5c8ab5f6585c..af9b3a22c4f9 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -35,7 +35,13 @@ class CsvReaderFactory { private static final String DEFAULT_DELIMITER = ","; private static final char EMPTY_CHAR = '\0'; + private static final boolean SKIP_EMPTY_LINES = true; + private static final boolean TRIM_WHITESPACES_AROUND_QUOTES = true; + private static final boolean ALLOW_EXTRA_FIELDS = true; + private static final boolean ALLOW_MISSING_FIELDS = true; + private static final boolean ALLOW_DUPLICATE_HEADER_FIELDS = true; private static final int MAX_FIELDS = 512; + private static final int MAX_RECORD_SIZE = Integer.MAX_VALUE; static void validate(CsvSource csvSource) { validateMaxCharsPerColumn(csvSource.maxCharsPerColumn()); @@ -61,11 +67,11 @@ static CsvReader createReaderFor(CsvSource csvSource, Strin String delimiter = selectDelimiter(csvSource.delimiter(), csvSource.delimiterString()); // @formatter:off CsvReader.CsvReaderBuilder builder = CsvReader.builder() - .skipEmptyLines(true) + .skipEmptyLines(SKIP_EMPTY_LINES) + .trimWhitespacesAroundQuotes(TRIM_WHITESPACES_AROUND_QUOTES) + .allowExtraFields(ALLOW_EXTRA_FIELDS) + .allowMissingFields(ALLOW_MISSING_FIELDS) .fieldSeparator(delimiter) - .trimWhitespacesAroundQuotes(true) - .allowExtraFields(true) - .allowMissingFields(true) .quoteCharacter(csvSource.quoteCharacter()) .commentStrategy(csvSource.textBlock().isEmpty() ? NONE : SKIP); @@ -86,11 +92,11 @@ static CsvReader createReaderFor(CsvFileSource csvFileSourc String delimiter = selectDelimiter(csvFileSource.delimiter(), csvFileSource.delimiterString()); // @formatter:off CsvReader.CsvReaderBuilder builder = CsvReader.builder() - .skipEmptyLines(true) + .skipEmptyLines(SKIP_EMPTY_LINES) + .trimWhitespacesAroundQuotes(TRIM_WHITESPACES_AROUND_QUOTES) + .allowExtraFields(ALLOW_EXTRA_FIELDS) + .allowMissingFields(ALLOW_MISSING_FIELDS) .fieldSeparator(delimiter) - .trimWhitespacesAroundQuotes(true) - .allowExtraFields(true) - .allowMissingFields(true) .quoteCharacter(csvFileSource.quoteCharacter()) .commentStrategy(SKIP); @@ -125,17 +131,17 @@ private static CsvCallbackHandler createCallbackHandler(Str // @formatter:off if (useHeadersInDisplayName) { return NamedCsvRecordHandler.builder() - .allowDuplicateHeaderFields(true) + .allowDuplicateHeaderFields(ALLOW_DUPLICATE_HEADER_FIELDS) .maxFields(MAX_FIELDS) + .maxRecordSize(MAX_RECORD_SIZE) .maxFieldSize(maxFieldSize) - .maxRecordSize(Integer.MAX_VALUE) .fieldModifier(modifier) .build(); } return CsvRecordHandler.builder() .maxFields(MAX_FIELDS) + .maxRecordSize(MAX_RECORD_SIZE) .maxFieldSize(maxFieldSize) - .maxRecordSize(Integer.MAX_VALUE) .fieldModifier(modifier) .build(); // @formatter:on From 9cfea349b93889883bec22cf092bf0dc87b71bed Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sat, 7 Jun 2025 22:43:35 +0200 Subject: [PATCH 27/52] Remove no longer necessary testImplementation(libs.fastcsv) dependency --- jupiter-tests/jupiter-tests.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/jupiter-tests/jupiter-tests.gradle.kts b/jupiter-tests/jupiter-tests.gradle.kts index e924ecb834e2..9b1baaf781a7 100644 --- a/jupiter-tests/jupiter-tests.gradle.kts +++ b/jupiter-tests/jupiter-tests.gradle.kts @@ -24,7 +24,6 @@ dependencies { testImplementation(libs.kotlinx.coroutines) testImplementation(libs.groovy4) testImplementation(libs.memoryfilesystem) - testImplementation(libs.fastcsv) testImplementation(testFixtures(projects.junitJupiterApi)) testImplementation(testFixtures(projects.junitJupiterEngine)) testImplementation(testFixtures(projects.junitPlatformLauncher)) From 1eea9e66371fad5066a64a82ddfd568f2c964c25 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sat, 7 Jun 2025 23:29:34 +0200 Subject: [PATCH 28/52] Resolve NULL_MARKER for headers --- .../release-notes/release-notes-6.0.0-M1.adoc | 2 + .../params/provider/CsvArgumentsProvider.java | 9 ++++- .../provider/CsvArgumentsProviderTests.java | 37 ++++++++++++++----- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc index 92038056c5ea..1607246c2ae1 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc @@ -124,6 +124,8 @@ repository on GitHub. of exceptions thrown when parsing malformed CSV input may differ in rare cases. While overall parsing behavior remains consistent, this may affect custom error handling that relies on specific exception types or messages. +* The `nullValues()` parameter in `CsvSource` and `CsvFileSource` is now applied + to header fields as well as to regular fields. * The `junit-jupiter-migrationsupport` artifact and its contained classes are now deprecated and will be removed in the next major version. * The type bounds of the following methods have been changed to be more flexible and allow diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 62b2f0824ffa..fbd289d70df2 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -88,9 +88,10 @@ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayN for (int i = 0; i < record.getFieldCount(); i++) { String field = record.getField(i); - Object argument = CsvReaderFactory.DefaultFieldModifier.NULL_MARKER.equals(field) ? null : field; + Object argument = resolveNullMarker(field); if (useHeadersInDisplayName) { - argument = asNamed(getHeaders(record).get(i) + " = " + argument, argument); + String header = resolveNullMarker(getHeaders(record).get(i)); + argument = asNamed(header + " = " + argument, argument); } arguments[i] = argument; } @@ -98,6 +99,10 @@ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayN return Arguments.of(arguments); } + private static @Nullable String resolveNullMarker(String record) { + return CsvReaderFactory.DefaultFieldModifier.NULL_MARKER.equals(record) ? null : record; + } + private static Named<@Nullable Object> asNamed(String name, @Nullable Object column) { return Named.<@Nullable Object> of(name, column); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index 6abd355c180c..9d4be0046ff2 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -260,6 +260,18 @@ void customNullValues() { assertThat(arguments).containsExactly(array("apple", null, null, "", null, "banana", null)); } + @Test + void customNullValueInHeader() { + var annotation = csvSource().useHeadersInDisplayName(true).nullValues("NIL").textBlock(""" + FRUIT, NIL + apple, 1 + """).build(); + + assertThat(headersToValues(annotation)).containsExactly(// + array("FRUIT = apple", "null = 1")// + ); + } + @Test void convertsEmptyValuesToNullInLinesAfterFirstLine() { var annotation = csvSource("'', ''", " , "); @@ -341,31 +353,38 @@ void honorsCommentCharacterWhenUsingTextBlockAttribute() { @Test void supportsCsvHeadersWhenUsingTextBlockAttribute() { - supportsCsvHeaders(csvSource().useHeadersInDisplayName(true).textBlock(""" + var annotation = csvSource().useHeadersInDisplayName(true).textBlock(""" FRUIT, RANK apple, 1 banana, 2 - """).build()); + """).build(); + + assertThat(headersToValues(annotation)).containsExactly(// + array("FRUIT = apple", "RANK = 1"),// + array("FRUIT = banana", "RANK = 2")// + ); } @Test void supportsCsvHeadersWhenUsingValueAttribute() { - supportsCsvHeaders(csvSource().useHeadersInDisplayName(true)// - .lines("FRUIT, RANK", "apple, 1", "banana, 2").build()); + var annotation = csvSource().useHeadersInDisplayName(true)// + .lines("FRUIT, RANK", "apple, 1", "banana, 2").build(); + + assertThat(headersToValues(annotation)).containsExactly(// + array("FRUIT = apple", "RANK = 1"),// + array("FRUIT = banana", "RANK = 2")// + ); } - private void supportsCsvHeaders(CsvSource csvSource) { + private Stream headersToValues(CsvSource csvSource) { var arguments = provideArguments(csvSource); - Stream argumentsAsStrings = arguments.map(array -> { + return arguments.map(array -> { String[] strings = new String[array.length]; for (int i = 0; i < array.length; i++) { strings[i] = String.valueOf(array[i]); } return strings; }); - - assertThat(argumentsAsStrings).containsExactly(array("FRUIT = apple", "RANK = 1"), - array("FRUIT = banana", "RANK = 2")); } @Test From 24450301b1d122e58d35622f662c62600a12a1c3 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sat, 7 Jun 2025 23:41:54 +0200 Subject: [PATCH 29/52] Mention in release notes that annotation parameters now apply to headers --- .../docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc index 1607246c2ae1..11ea65a60531 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc @@ -124,8 +124,9 @@ repository on GitHub. of exceptions thrown when parsing malformed CSV input may differ in rare cases. While overall parsing behavior remains consistent, this may affect custom error handling that relies on specific exception types or messages. -* The `nullValues()` parameter in `CsvSource` and `CsvFileSource` is now applied - to header fields as well as to regular fields. +* Parameters such as `ignoreLeadingAndTrailingWhitespace()`, `nullValues()`, + and others in `CsvSource` and `CsvFileSource` now apply to header fields as well + as to regular fields. * The `junit-jupiter-migrationsupport` artifact and its contained classes are now deprecated and will be removed in the next major version. * The type bounds of the following methods have been changed to be more flexible and allow From ed6e8d426badd28898bd28f062036c96feec3d87 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 8 Jun 2025 00:22:49 +0200 Subject: [PATCH 30/52] Apply spotless --- .../params/provider/CsvArgumentsProviderTests.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index 9d4be0046ff2..683da575dbdf 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -268,7 +268,7 @@ void customNullValueInHeader() { """).build(); assertThat(headersToValues(annotation)).containsExactly(// - array("FRUIT = apple", "null = 1")// + array("FRUIT = apple", "null = 1")// ); } @@ -360,8 +360,8 @@ void supportsCsvHeadersWhenUsingTextBlockAttribute() { """).build(); assertThat(headersToValues(annotation)).containsExactly(// - array("FRUIT = apple", "RANK = 1"),// - array("FRUIT = banana", "RANK = 2")// + array("FRUIT = apple", "RANK = 1"), // + array("FRUIT = banana", "RANK = 2")// ); } @@ -371,8 +371,8 @@ void supportsCsvHeadersWhenUsingValueAttribute() { .lines("FRUIT, RANK", "apple, 1", "banana, 2").build(); assertThat(headersToValues(annotation)).containsExactly(// - array("FRUIT = apple", "RANK = 1"),// - array("FRUIT = banana", "RANK = 2")// + array("FRUIT = apple", "RANK = 1"), // + array("FRUIT = banana", "RANK = 2")// ); } From 954f5483cdf322300ffce83e5e288fa490230ccb Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 8 Jun 2025 00:40:45 +0200 Subject: [PATCH 31/52] Ignore shadowed classes in avoidAccessingStandardStreams() arch test --- .../java/platform/tooling/support/tests/ArchUnitTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java index 47dad8ed873e..bfb4882b58d6 100644 --- a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java +++ b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java @@ -138,7 +138,7 @@ void avoidAccessingStandardStreams(JavaClasses classes) { .that(are(not(name("org.junit.platform.testkit.engine.Executions")))) // //The PreInterruptThreadDumpPrinter writes to StdOut by contract to dump threads .that(are(not(name("org.junit.jupiter.engine.extension.PreInterruptThreadDumpPrinter")))) // - .that(are(not(resideInAPackage("org.junit.platform.console.shadow.picocli")))); + .that(are(not(nameContaining(".shadow.")))); GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS.check(subset); } From 2d62d8a86cd932be5f13ff32ff57a5913a2be094 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 8 Jun 2025 09:27:14 +0200 Subject: [PATCH 32/52] Avoid calling getHeader() to prevent unnecessary object creation --- .../params/provider/CsvArgumentsProvider.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index fbd289d70df2..174419a37d0d 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -86,11 +86,13 @@ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayN @Nullable Object[] arguments = new Object[record.getFieldCount()]; + List headers = useHeadersInDisplayName ? getHeaders(record) : List.of(); + for (int i = 0; i < record.getFieldCount(); i++) { String field = record.getField(i); Object argument = resolveNullMarker(field); - if (useHeadersInDisplayName) { - String header = resolveNullMarker(getHeaders(record).get(i)); + if (!headers.isEmpty()) { + String header = resolveNullMarker(headers.get(i)); argument = asNamed(header + " = " + argument, argument); } arguments[i] = argument; @@ -99,6 +101,10 @@ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayN return Arguments.of(arguments); } + private static List getHeaders(CsvRecord record) { + return ((NamedCsvRecord) record).getHeader(); + } + private static @Nullable String resolveNullMarker(String record) { return CsvReaderFactory.DefaultFieldModifier.NULL_MARKER.equals(record) ? null : record; } @@ -107,10 +113,6 @@ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayN return Named.<@Nullable Object> of(name, column); } - private static List getHeaders(CsvRecord record) { - return ((NamedCsvRecord) record).getHeader(); - } - /** * @return this method always throws an exception and therefore never * returns anything; the return type is merely present to allow this From cf704b360686be3b20d2623e860581818ab42823 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 8 Jun 2025 13:38:40 +0200 Subject: [PATCH 33/52] Replace explicit variable types with var where appropriate --- .../jupiter/params/provider/CsvArgumentsProvider.java | 3 +-- .../jupiter/params/provider/CsvFileArgumentsProvider.java | 6 ++---- .../junit/jupiter/params/provider/CsvReaderFactory.java | 8 ++++---- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 174419a37d0d..810bd9a82af8 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -15,7 +15,6 @@ import java.util.List; import java.util.stream.Stream; -import de.siegmar.fastcsv.reader.CsvReader; import de.siegmar.fastcsv.reader.CsvRecord; import de.siegmar.fastcsv.reader.NamedCsvRecord; @@ -40,7 +39,7 @@ protected Stream provideArguments(ParameterDeclarations par List arguments = new ArrayList<>(); - try (CsvReader reader = CsvReaderFactory.createReaderFor(csvSource, getData(csvSource))) { + try (var reader = CsvReaderFactory.createReaderFor(csvSource, getData(csvSource))) { for (CsvRecord record : reader) { arguments.add(processCsvRecord(record, csvSource.useHeadersInDisplayName())); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java index 72cf260e4f35..b539d8aee95b 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java @@ -77,11 +77,9 @@ private static Charset getCharsetFrom(CsvFileSource csvFileSource) { } private static Stream toStream(CsvReader reader, CsvFileSource csvFileSource) { + var spliterator = CsvExceptionHandlingSpliterator.fromDelegate(reader.spliterator(), csvFileSource); // @formatter:off - Stream stream = StreamSupport.stream( - CsvExceptionHandlingSpliterator.fromDelegate(reader.spliterator(), csvFileSource), false - ); - return stream + return StreamSupport.stream(spliterator, false) .skip(csvFileSource.numLinesToSkip()) .map(record -> CsvArgumentsProvider.processCsvRecord( record, csvFileSource.useHeadersInDisplayName()) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index af9b3a22c4f9..f80b93920b38 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -66,7 +66,7 @@ private static void validateDelimiter(char delimiter, String delimiterString, An static CsvReader createReaderFor(CsvSource csvSource, String data) { String delimiter = selectDelimiter(csvSource.delimiter(), csvSource.delimiterString()); // @formatter:off - CsvReader.CsvReaderBuilder builder = CsvReader.builder() + var builder = CsvReader.builder() .skipEmptyLines(SKIP_EMPTY_LINES) .trimWhitespacesAroundQuotes(TRIM_WHITESPACES_AROUND_QUOTES) .allowExtraFields(ALLOW_EXTRA_FIELDS) @@ -75,7 +75,7 @@ static CsvReader createReaderFor(CsvSource csvSource, Strin .quoteCharacter(csvSource.quoteCharacter()) .commentStrategy(csvSource.textBlock().isEmpty() ? NONE : SKIP); - CsvCallbackHandler callbackHandler = createCallbackHandler( + var callbackHandler = createCallbackHandler( csvSource.emptyValue(), Set.of(csvSource.nullValues()), csvSource.ignoreLeadingAndTrailingWhitespace(), @@ -91,7 +91,7 @@ static CsvReader createReaderFor(CsvFileSource csvFileSourc String delimiter = selectDelimiter(csvFileSource.delimiter(), csvFileSource.delimiterString()); // @formatter:off - CsvReader.CsvReaderBuilder builder = CsvReader.builder() + var builder = CsvReader.builder() .skipEmptyLines(SKIP_EMPTY_LINES) .trimWhitespacesAroundQuotes(TRIM_WHITESPACES_AROUND_QUOTES) .allowExtraFields(ALLOW_EXTRA_FIELDS) @@ -100,7 +100,7 @@ static CsvReader createReaderFor(CsvFileSource csvFileSourc .quoteCharacter(csvFileSource.quoteCharacter()) .commentStrategy(SKIP); - CsvCallbackHandler callbackHandler = createCallbackHandler( + var callbackHandler = createCallbackHandler( csvFileSource.emptyValue(), Set.of(csvFileSource.nullValues()), csvFileSource.ignoreLeadingAndTrailingWhitespace(), From 70cfdbe0b3fc7ca2c7ca2cf07fbe498afb4f27ed Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 8 Jun 2025 13:40:07 +0200 Subject: [PATCH 34/52] Rename fromDelegate() factory method to delegatingTo() --- .../jupiter/params/provider/CsvFileArgumentsProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java index b539d8aee95b..3097b784c175 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java @@ -77,7 +77,7 @@ private static Charset getCharsetFrom(CsvFileSource csvFileSource) { } private static Stream toStream(CsvReader reader, CsvFileSource csvFileSource) { - var spliterator = CsvExceptionHandlingSpliterator.fromDelegate(reader.spliterator(), csvFileSource); + var spliterator = CsvExceptionHandlingSpliterator.delegatingTo(reader.spliterator(), csvFileSource); // @formatter:off return StreamSupport.stream(spliterator, false) .skip(csvFileSource.numLinesToSkip()) @@ -98,7 +98,7 @@ private static Stream toStream(CsvReader reader, private record CsvExceptionHandlingSpliterator(Spliterator delegate, CsvFileSource csvFileSource) implements Spliterator { - static CsvExceptionHandlingSpliterator fromDelegate(Spliterator delegate, + static CsvExceptionHandlingSpliterator delegatingTo(Spliterator delegate, CsvFileSource csvFileSource) { return new CsvExceptionHandlingSpliterator<>(delegate, csvFileSource); } From 0872858f29dd0eb5cc85abf9f1c5185ad82b3c31 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 8 Jun 2025 17:22:47 +0200 Subject: [PATCH 35/52] Ignore empty lines in CsvSource.value() --- .../release-notes/release-notes-6.0.0-M1.adoc | 3 +++ .../params/provider/CsvArgumentsProvider.java | 17 ++--------------- .../provider/CsvArgumentsProviderTests.java | 11 +++++------ 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc index 11ea65a60531..b582852c1f44 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc @@ -127,6 +127,9 @@ repository on GitHub. * Parameters such as `ignoreLeadingAndTrailingWhitespace()`, `nullValues()`, and others in `CsvSource` and `CsvFileSource` now apply to header fields as well as to regular fields. +* Empty lines in `CsvSource.value()` no longer cause a + `PreconditionViolationException`; such lines are now ignored, consistent with + the behavior of `CsvSource.textBlock()`. * The `junit-jupiter-migrationsupport` artifact and its contained classes are now deprecated and will be removed in the next major version. * The type bounds of the following methods have been changed to be more flexible and allow diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 810bd9a82af8..a5ca265e4f74 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -55,20 +55,7 @@ private static String getData(CsvSource csvSource) { Preconditions.condition(csvSource.value().length > 0 ^ !csvSource.textBlock().isEmpty(), () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); - if (!csvSource.textBlock().isEmpty()) { - return csvSource.textBlock(); - } - else { - for (int i = 0; i < csvSource.value().length; i++) { - if (csvSource.value()[i].isEmpty()) { - int finalI = i; - Preconditions.condition(!csvSource.value()[i].isEmpty(), // - () -> "CSV record at index %d is empty".formatted(finalI + 1) // - ); - } - } - return String.join("\n", csvSource.value()); - } + return csvSource.value().length > 0 ? String.join("\n", csvSource.value()) : csvSource.textBlock(); } /** @@ -85,7 +72,7 @@ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayN @Nullable Object[] arguments = new Object[record.getFieldCount()]; - List headers = useHeadersInDisplayName ? getHeaders(record) : List.of(); + List headers = useHeadersInDisplayName ? getHeaders(record) : List.of(); for (int i = 0; i < record.getFieldCount(); i++) { String field = record.getField(i); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index 683da575dbdf..085c3cf19471 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -22,7 +22,6 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.shadow.de.siegmar.fastcsv.reader.CsvParseException; -import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; /** @@ -31,12 +30,12 @@ class CsvArgumentsProviderTests { @Test - void throwsExceptionForEmptyLine() { - var annotation = csvSource("foo", "bar", ""); + void skipsEmptyLines() { + var annotation = csvSource("", "foo", "", "bar", ""); - assertThatExceptionOfType(JUnitException.class)// - .isThrownBy(() -> provideArguments(annotation).toArray())// - .withMessage("CSV record at index 3 is empty"); + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo"), array("bar")); } @Test From d5139e2129d8df0d1dff72f8d3ea3cba83dd36fc Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 8 Jun 2025 22:20:43 +0200 Subject: [PATCH 36/52] Copy FastCSV LICENSE file from dependency JAR --- junit-jupiter-params/LICENSE-fastcsv.md | 21 ------------ .../junit-jupiter-params.gradle.kts | 34 ++++++++++++++++++- 2 files changed, 33 insertions(+), 22 deletions(-) delete mode 100644 junit-jupiter-params/LICENSE-fastcsv.md diff --git a/junit-jupiter-params/LICENSE-fastcsv.md b/junit-jupiter-params/LICENSE-fastcsv.md deleted file mode 100644 index 9b5b1683182c..000000000000 --- a/junit-jupiter-params/LICENSE-fastcsv.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Oliver Siegmar - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index a05b689d1011..6505b0a0ebb2 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -1,6 +1,7 @@ import junitbuild.extensions.javaModuleName import net.ltgt.gradle.errorprone.errorprone import net.ltgt.gradle.nullaway.nullaway +import java.util.zip.ZipFile plugins { id("junitbuild.java-nullability-conventions") @@ -39,12 +40,43 @@ tasks { """) } } + fun copyFastCsvLicenseTo(file: File, configurations: List) { + val jarFile = configurations + .flatMap { it.files } + .firstOrNull { it.name.contains("fastcsv") } + ?: throw GradleException("Could not find FastCSV dependency JAR") + + ZipFile(jarFile).use { zipFile -> + val licenseEntry = zipFile.entries().toList() + .firstOrNull { it.name == "META-INF/LICENSE" } + ?: throw GradleException("Could not find META-INF/LICENSE in FastCSV dependency JAR") + + zipFile.getInputStream(licenseEntry).use { input -> + file.outputStream().use { output -> + input.copyTo(output) + } + } + } + } shadowJar { + val tempLicenseFile = projectDir.resolve("LICENSE-fastcsv") + + doFirst { + copyFastCsvLicenseTo(tempLicenseFile, configurations) + } + relocate("de.siegmar.fastcsv", "org.junit.jupiter.params.shadow.de.siegmar.fastcsv") + from(projectDir) { - include("LICENSE-fastcsv.md") + include(tempLicenseFile.name) into("META-INF") } + + doLast { + if (tempLicenseFile.exists()) { + tempLicenseFile.delete() + } + } } compileJava { options.compilerArgs.addAll(listOf( From 375cd33c9ecdbf69c67d16640c8a756c0610d2fc Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Mon, 16 Jun 2025 04:04:30 +0200 Subject: [PATCH 37/52] Move variable declarations outside loops to improve performance --- .../junit/jupiter/params/provider/CsvArgumentsProvider.java | 3 ++- .../jupiter/params/provider/CsvFileArgumentsProvider.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index a5ca265e4f74..72bb5ad9ba1a 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -40,8 +40,9 @@ protected Stream provideArguments(ParameterDeclarations par List arguments = new ArrayList<>(); try (var reader = CsvReaderFactory.createReaderFor(csvSource, getData(csvSource))) { + boolean useHeadersInDisplayName = csvSource.useHeadersInDisplayName(); for (CsvRecord record : reader) { - arguments.add(processCsvRecord(record, csvSource.useHeadersInDisplayName())); + arguments.add(processCsvRecord(record, useHeadersInDisplayName)); } } catch (Throwable throwable) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java index 3097b784c175..128416d91bd7 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java @@ -78,11 +78,12 @@ private static Charset getCharsetFrom(CsvFileSource csvFileSource) { private static Stream toStream(CsvReader reader, CsvFileSource csvFileSource) { var spliterator = CsvExceptionHandlingSpliterator.delegatingTo(reader.spliterator(), csvFileSource); + boolean useHeadersInDisplayName = csvFileSource.useHeadersInDisplayName(); // @formatter:off return StreamSupport.stream(spliterator, false) .skip(csvFileSource.numLinesToSkip()) .map(record -> CsvArgumentsProvider.processCsvRecord( - record, csvFileSource.useHeadersInDisplayName()) + record, useHeadersInDisplayName) ) .onClose(() -> { try { From 1e7e1ea50908fedc68a22d200de7d4a2bc0c3519 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Mon, 16 Jun 2025 04:06:45 +0200 Subject: [PATCH 38/52] Retrieve headers and fields only once --- .../params/provider/CsvArgumentsProvider.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 72bb5ad9ba1a..29149cd80a27 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -65,20 +65,20 @@ private static String getData(CsvSource csvSource) { * CSV record wrapped in an {@link Arguments} instance. */ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayName) { - Preconditions.condition(!useHeadersInDisplayName || record.getFieldCount() <= getHeaders(record).size(), + List fields = record.getFields(); + List headers = useHeadersInDisplayName ? getHeaders(record) : List.of(); + + Preconditions.condition(!useHeadersInDisplayName || fields.size() <= headers.size(), () -> String.format( // "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s", // - record.getFieldCount(), getHeaders(record).size(), record.getFields())); // + fields.size(), headers.size(), fields)); // @Nullable - Object[] arguments = new Object[record.getFieldCount()]; - - List headers = useHeadersInDisplayName ? getHeaders(record) : List.of(); + Object[] arguments = new Object[fields.size()]; - for (int i = 0; i < record.getFieldCount(); i++) { - String field = record.getField(i); - Object argument = resolveNullMarker(field); - if (!headers.isEmpty()) { + for (int i = 0; i < fields.size(); i++) { + Object argument = resolveNullMarker(fields.get(i)); + if (useHeadersInDisplayName) { String header = resolveNullMarker(headers.get(i)); argument = asNamed(header + " = " + argument, argument); } From 3f74e339a08d37ada247f85ba65b54483437497b Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Mon, 16 Jun 2025 04:07:05 +0200 Subject: [PATCH 39/52] Minor polishing --- .../junit/jupiter/params/provider/CsvArgumentsProvider.java | 4 ++-- .../org/junit/jupiter/params/provider/CsvReaderFactory.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 29149cd80a27..4de211dd14ff 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -68,7 +68,7 @@ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayN List fields = record.getFields(); List headers = useHeadersInDisplayName ? getHeaders(record) : List.of(); - Preconditions.condition(!useHeadersInDisplayName || fields.size() <= headers.size(), + Preconditions.condition(!useHeadersInDisplayName || fields.size() <= headers.size(), // () -> String.format( // "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s", // fields.size(), headers.size(), fields)); // @@ -93,7 +93,7 @@ private static List getHeaders(CsvRecord record) { } private static @Nullable String resolveNullMarker(String record) { - return CsvReaderFactory.DefaultFieldModifier.NULL_MARKER.equals(record) ? null : record; + return record == CsvReaderFactory.DefaultFieldModifier.NULL_MARKER ? null : record; } private static Named<@Nullable Object> asNamed(String name, @Nullable Object column) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index f80b93920b38..24420b9eef51 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -159,13 +159,13 @@ record DefaultFieldModifier(String emptyValue, Set nullValues, boolean i @Override public String modify(long unusedStartingLineNumber, int unusedFieldIdx, boolean quoted, String field) { - if (field.isEmpty() && quoted && !emptyValue.isEmpty()) { + if (quoted && field.isEmpty() && !emptyValue.isEmpty()) { return emptyValue; } - if (field.isBlank() && !quoted) { + if (!quoted && field.isBlank()) { return NULL_MARKER; } - String modifiedField = (ignoreLeadingAndTrailingWhitespaces && !quoted) ? field.strip() : field; + String modifiedField = (!quoted && ignoreLeadingAndTrailingWhitespaces) ? field.strip() : field; if (nullValues.contains(modifiedField)) { return NULL_MARKER; } From ea070551ac21c6ad29e9f459169111d3d62baed7 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 22 Jun 2025 17:42:05 +0200 Subject: [PATCH 40/52] Remove lineSeparator() since FastCSV detects it automatically --- .../release-notes/release-notes-6.0.0-M1.adoc | 6 +++--- .../jupiter/params/provider/CsvFileSource.java | 17 +++-------------- .../provider/CsvFileArgumentsProviderTests.java | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc index b582852c1f44..2d842842bd37 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc @@ -115,9 +115,9 @@ repository on GitHub. * The contracts for the `Executable` parameters of Kotlin-specific `assertTimeout` functions were changed from `callsInPlace(executable, EXACTLY_ONCE)` to `callsInPlace(executable, AT_MOST_ONCE)` which might result in compilation errors. -* The `CsvFileSource.lineSeparator()` parameter is deprecated because line separators - are now detected automatically during CSV parsing. This setting is no longer required - and will be ignored. +* The `CsvFileSource.lineSeparator()` parameter has been removed. The line separator + is now automatically detected, meaning that any of `\r`, `\n`, or `\r\n` is + treated as a line separator. * As a result of migrating from https://github.com/uniVocity/univocity-parsers[univocity-parsers] to https://fastcsv.org/[FastCSV] for CSV input handling, the root causes and messages 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 9af729c8c74c..b51f27b61637 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 @@ -10,7 +10,6 @@ package org.junit.jupiter.params.provider; -import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; @@ -42,6 +41,9 @@ *

The column delimiter (which defaults to a comma ({@code ,})) can be customized * via either {@link #delimiter} or {@link #delimiterString}. * + *

The line separator is detected automatically, meaning that any of + * {@code "\r"}, {@code "\n"}, or {@code "\r\n"} is treated as a line separator. + * *

In contrast to the default syntax used in {@code @CsvSource}, {@code @CsvFileSource} * uses a double quote ({@code "}) as its quote character by default, but this can * be changed via {@link #quoteCharacter}. An empty, quoted value ({@code ""}) @@ -102,19 +104,6 @@ */ String encoding() default "UTF-8"; - /** - * The line separator to use when reading the CSV files; must consist of 1 - * or 2 characters, typically {@code "\r"}, {@code "\n"}, or {@code "\r\n"}. - * - *

Defaults to {@code "\n"}. - * - * @deprecated Since JUnit 6.0, line separators are detected automatically during CSV parsing. - * This setting is no longer required and will be ignored. - */ - @API(status = DEPRECATED, since = "6.0") // - @Deprecated(since = "6.0", forRemoval = true) - String lineSeparator() default "\n"; - /** * Configures whether the first CSV record should be treated as header names * for columns. diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java index 62c1d75fa7d6..34f03a19d0e1 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java @@ -41,6 +41,22 @@ */ class CsvFileArgumentsProviderTests { + @Test + void providesArgumentsForEachSupportedLineSeparator() { + var annotation = csvFileSource()// + .resources("test.csv")// + .build(); + + var arguments = provideArguments(annotation, "foo, bar \n baz, qux \r quux, corge \r\n grault, garply"); + + assertThat(arguments).containsExactly(// + array("foo", "bar"), // + array("baz", "qux"), // + array("quux", "corge"), // + array("grault", "garply")// + ); + } + @Test void providesArgumentsForNewlineAndComma() { var annotation = csvFileSource()// From 28df2ee42cb69a6359529c8f4d7bbbeee5f7ca25 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 22 Jun 2025 17:45:06 +0200 Subject: [PATCH 41/52] Use Gradle task to extract FastCSV license --- .../junit-jupiter-params.gradle.kts | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 6505b0a0ebb2..eef1e757e992 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -1,7 +1,6 @@ import junitbuild.extensions.javaModuleName import net.ltgt.gradle.errorprone.errorprone import net.ltgt.gradle.nullaway.nullaway -import java.util.zip.ZipFile plugins { id("junitbuild.java-nullability-conventions") @@ -40,31 +39,15 @@ tasks { """) } } - fun copyFastCsvLicenseTo(file: File, configurations: List) { - val jarFile = configurations - .flatMap { it.files } - .firstOrNull { it.name.contains("fastcsv") } - ?: throw GradleException("Could not find FastCSV dependency JAR") - - ZipFile(jarFile).use { zipFile -> - val licenseEntry = zipFile.entries().toList() - .firstOrNull { it.name == "META-INF/LICENSE" } - ?: throw GradleException("Could not find META-INF/LICENSE in FastCSV dependency JAR") - - zipFile.getInputStream(licenseEntry).use { input -> - file.outputStream().use { output -> - input.copyTo(output) - } - } + val extractFastCSVLicense by registering(Sync::class) { + from(zipTree(project.configurations.shadowedClasspath.map { it.files.single { file -> file.name.contains("fastcsv") } })) { + include("META-INF/LICENSE") } + into(layout.buildDirectory.dir("fastcsv")) } shadowJar { val tempLicenseFile = projectDir.resolve("LICENSE-fastcsv") - doFirst { - copyFastCsvLicenseTo(tempLicenseFile, configurations) - } - relocate("de.siegmar.fastcsv", "org.junit.jupiter.params.shadow.de.siegmar.fastcsv") from(projectDir) { @@ -72,10 +55,8 @@ tasks { into("META-INF") } - doLast { - if (tempLicenseFile.exists()) { - tempLicenseFile.delete() - } + from(extractFastCSVLicense.map { it.destinationDir }) { + rename { "LICENSE-fastcsv" } } } compileJava { From e4bcdd074229a5f5b767b006d0d631312af3bc8f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 22 Jun 2025 17:52:13 +0200 Subject: [PATCH 42/52] Only include FastCSV license once --- junit-jupiter-params/junit-jupiter-params.gradle.kts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index eef1e757e992..13e110b48449 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -46,17 +46,11 @@ tasks { into(layout.buildDirectory.dir("fastcsv")) } shadowJar { - val tempLicenseFile = projectDir.resolve("LICENSE-fastcsv") - relocate("de.siegmar.fastcsv", "org.junit.jupiter.params.shadow.de.siegmar.fastcsv") - - from(projectDir) { - include(tempLicenseFile.name) - into("META-INF") - } - - from(extractFastCSVLicense.map { it.destinationDir }) { + exclude("META-INF/LICENSE") + from(extractFastCSVLicense.map { "${it.destinationDir}/META-INF" }) { rename { "LICENSE-fastcsv" } + into("META-INF") } } compileJava { From 696bde641753dfd42a0f4db8d38f649cd10262df Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 22 Jun 2025 18:03:56 +0200 Subject: [PATCH 43/52] Replace locally built FastCSV snapshot with officially released version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a9428fd6e9af..6199dc006cee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,7 +36,7 @@ checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checks classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.179" } commons-io = { module = "commons-io:commons-io", version = "2.19.0" } errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.38.0" } -fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0-SNAPSHOT" } +fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0" } groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.27" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } From 63211eb735910970f910eff04f3a27539ff45e55 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 22 Jun 2025 18:21:46 +0200 Subject: [PATCH 44/52] Polish release notes --- .../asciidoc/release-notes/release-notes-6.0.0-M1.adoc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc index 2d842842bd37..c5606316b9a5 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc @@ -144,9 +144,11 @@ repository on GitHub. * Kotlin's `suspend` modifier may now be applied to test and lifecycle methods. * The `Arguments` interface for parameterized tests is now officially a `@FunctionalInterface`. -* CSV parsing for parameterized tests now uses the https://fastcsv.org/[FastCSV] - library instead of the no longer maintained - https://github.com/uniVocity/univocity-parsers[univocity-parsers]. +* CSV parsing for parameterized was migrated from the the no longer maintained + https://github.com/uniVocity/univocity-parsers[univocity-parsers] + to https://fastcsv.org/[FastCSV]. + This improves the consistency of CSV input handling, including for malformed entries, + and provides better error reporting and overall performance. [[release-notes-6.0.0-M1-junit-vintage]] From ac81c55d9614c8492e220e49574b8c75bfc475f6 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 22 Jun 2025 18:25:08 +0200 Subject: [PATCH 45/52] documentation: add reads for de.siegmar.fastcsv module --- documentation/documentation.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index e8e38da83b5c..faf143b10fcb 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -450,6 +450,7 @@ tasks { addOption(ModuleSpecificJavadocFileOption("-add-reads", mapOf( "org.junit.platform.console" to provider { "info.picocli" }, "org.junit.platform.reporting" to provider { "org.opentest4j.reporting.events" }, + "org.junit.jupiter.params" to provider { "de.siegmar.fastcsv" } ))) } From d78689481ec43d7f899320e4faddffa088ed0466 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Sun, 22 Jun 2025 20:31:37 +0200 Subject: [PATCH 46/52] Add module declaration --- documentation/documentation.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index faf143b10fcb..08cb319460ea 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -446,7 +446,7 @@ tasks { ).asPath } })) - addStringOption("-add-modules", "info.picocli,org.opentest4j.reporting.events") + addStringOption("-add-modules", "info.picocli,org.opentest4j.reporting.events,de.siegmar.fastcsv") addOption(ModuleSpecificJavadocFileOption("-add-reads", mapOf( "org.junit.platform.console" to provider { "info.picocli" }, "org.junit.platform.reporting" to provider { "org.opentest4j.reporting.events" }, From 328706a1f6ec962d553dfe17c0a8fcc84c133d53 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 23 Jun 2025 09:43:28 +0200 Subject: [PATCH 47/52] Polish license handling --- NOTICE.md | 2 +- junit-jupiter-params/junit-jupiter-params.gradle.kts | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index 520713de1c3b..47f2ae4ed1a9 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -4,5 +4,5 @@ Open Source Licenses This product may include a number of subcomponents with separate copyright notices and license terms. Your use of the source code for these subcomponents is subject to the terms and conditions of the -subcomponent's license, as noted in the LICENSE-.md +subcomponent's license, as noted in the `LICENSE-[.md]` files. diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 13e110b48449..767ae0733b54 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -40,18 +40,16 @@ tasks { } } val extractFastCSVLicense by registering(Sync::class) { - from(zipTree(project.configurations.shadowedClasspath.map { it.files.single { file -> file.name.contains("fastcsv") } })) { + from(zipTree(configurations.shadowedClasspath.map { it.files.single { file -> file.name.contains("fastcsv") } })) { include("META-INF/LICENSE") + rename { "LICENSE-fastcsv" } } into(layout.buildDirectory.dir("fastcsv")) } shadowJar { relocate("de.siegmar.fastcsv", "org.junit.jupiter.params.shadow.de.siegmar.fastcsv") exclude("META-INF/LICENSE") - from(extractFastCSVLicense.map { "${it.destinationDir}/META-INF" }) { - rename { "LICENSE-fastcsv" } - into("META-INF") - } + from(extractFastCSVLicense) } compileJava { options.compilerArgs.addAll(listOf( From 4044fd9a9250bec74a030435720ab2568ac1c932 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 23 Jun 2025 10:28:46 +0200 Subject: [PATCH 48/52] Include fastcsv license in junit-platform-console-standalone jar --- .../junit-jupiter-params.gradle.kts | 2 +- ...nit-platform-console-standalone.gradle.kts | 29 ++++++++++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 767ae0733b54..1fd48f258033 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -40,7 +40,7 @@ tasks { } } val extractFastCSVLicense by registering(Sync::class) { - from(zipTree(configurations.shadowedClasspath.map { it.files.single { file -> file.name.contains("fastcsv") } })) { + from(configurations.shadowedClasspath.map { it.elements.map { files -> files.map(project::zipTree) } }) { include("META-INF/LICENSE") rename { "LICENSE-fastcsv" } } diff --git a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts index 6d4da5d5ceeb..a49fc7756ab8 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -1,4 +1,3 @@ -import junitbuild.extensions.dependencyProject import junitbuild.java.WriteArtifactsFile plugins { @@ -33,19 +32,33 @@ tasks { from(configurations.shadowedClasspath) outputFile = layout.buildDirectory.file("shadowed-artifacts") } + val extractThirdPartyLicenses by registering(Sync::class) { + from(configurations.shadowedClasspath.map { it.elements.map { files -> files.map(project::zipTree) } }) + into(layout.buildDirectory.dir("thirdPartyLicenses")) + include("LICENSE.txt") + include("LICENSE-junit.txt") + include("META-INF/LICENSE-*") + exclude("META-INF/LICENSE-notice.md") + eachFile { + val fileName = relativePath.lastName + relativePath = RelativePath(true, when (fileName) { + "LICENSE.txt" -> "LICENSE-hamcrest" + "LICENSE-junit.txt" -> "LICENSE-junit4" + else -> fileName + }) + } + includeEmptyDirs = false + } shadowJar { // https://github.com/junit-team/junit5/issues/2557 // exclude compiled module declarations from any source (e.g. /*, /META-INF/versions/N/*) exclude("**/module-info.class") // https://github.com/junit-team/junit5/issues/761 // prevent duplicates, add 3rd-party licenses explicitly - exclude("META-INF/LICENSE*.md") - from(dependencyProject(project.projects.junitPlatformConsole).projectDir) { - include("LICENSE-picocli.md") - into("META-INF") - } - from(dependencyProject(project.projects.junitJupiterParams).projectDir) { - include("LICENSE-fastcsv.md") + exclude("**/COPYRIGHT*") + exclude("META-INF/LICENSE*") + exclude("LICENSE*.txt") // JUnit 4 and Hamcrest + from(extractThirdPartyLicenses) { into("META-INF") } from(shadowedArtifactsFile) { From bb7aaa811bc017f0b8a8cc1edf00b2733471abcd Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 23 Jun 2025 11:05:34 +0200 Subject: [PATCH 49/52] Restore configuration cache compatibility --- .../kotlin/junitbuild/extensions/TaskExtensions.kt | 14 ++++++++++++++ .../junit-jupiter-params.gradle.kts | 2 +- .../junit-platform-console-standalone.gradle.kts | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt index 9cd596dfbfda..ec9a6bc492c9 100644 --- a/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt @@ -1,7 +1,21 @@ package junitbuild.extensions import org.gradle.api.Task +import org.gradle.api.file.ArchiveOperations import org.gradle.internal.os.OperatingSystem +import org.gradle.kotlin.dsl.newInstance +import javax.inject.Inject fun Task.trackOperationSystemAsInput() = inputs.property("os", OperatingSystem.current().familyName) + +fun Task.withArchiveOperations(action: (ArchiveOperations) -> T): T = + archiveOperations.run { action(this) } + +private val Task.archiveOperations: ArchiveOperations + get() = project.objects.newInstance(DummyObject::class).archiveOperations + +private abstract class DummyObject { + @get:Inject + abstract val archiveOperations: ArchiveOperations +} diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 1fd48f258033..6cb6fde4aa2a 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -40,7 +40,7 @@ tasks { } } val extractFastCSVLicense by registering(Sync::class) { - from(configurations.shadowedClasspath.map { it.elements.map { files -> files.map(project::zipTree) } }) { + from(zipTree(configurations.shadowedClasspath.flatMap { it.elements }.map { it.single { file -> file.asFile.name.contains("fastcsv") } })) { include("META-INF/LICENSE") rename { "LICENSE-fastcsv" } } diff --git a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts index a49fc7756ab8..1faade97a723 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -1,3 +1,4 @@ +import junitbuild.extensions.withArchiveOperations import junitbuild.java.WriteArtifactsFile plugins { @@ -33,7 +34,7 @@ tasks { outputFile = layout.buildDirectory.file("shadowed-artifacts") } val extractThirdPartyLicenses by registering(Sync::class) { - from(configurations.shadowedClasspath.map { it.elements.map { files -> files.map(project::zipTree) } }) + from(withArchiveOperations { ops -> configurations.shadowedClasspath.flatMap { it.elements }.map { it.map(ops::zipTree) } }) into(layout.buildDirectory.dir("thirdPartyLicenses")) include("LICENSE.txt") include("LICENSE-junit.txt") From 053b031b5205f45b417b02db6f656443ec6a7221 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 23 Jun 2025 11:12:28 +0200 Subject: [PATCH 50/52] Restore validation of non-blankness of strings in `@CsvSource(value)` --- .../release-notes/release-notes-6.0.0-M1.adoc | 3 --- .../params/provider/CsvArgumentsProvider.java | 15 +++++++++++++-- .../junit/jupiter/params/provider/CsvSource.java | 3 ++- .../provider/CsvArgumentsProviderTests.java | 11 ++++++----- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc index c5606316b9a5..dbc52fa25609 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc @@ -127,9 +127,6 @@ repository on GitHub. * Parameters such as `ignoreLeadingAndTrailingWhitespace()`, `nullValues()`, and others in `CsvSource` and `CsvFileSource` now apply to header fields as well as to regular fields. -* Empty lines in `CsvSource.value()` no longer cause a - `PreconditionViolationException`; such lines are now ignored, consistent with - the behavior of `CsvSource.textBlock()`. * The `junit-jupiter-migrationsupport` artifact and its contained classes are now deprecated and will be removed in the next major version. * The type bounds of the following methods have been changed to be more flexible and allow diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 4de211dd14ff..9a7c4ee47184 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -53,10 +53,21 @@ protected Stream provideArguments(ParameterDeclarations par } private static String getData(CsvSource csvSource) { - Preconditions.condition(csvSource.value().length > 0 ^ !csvSource.textBlock().isEmpty(), + var values = csvSource.value(); + Preconditions.condition(values.length > 0 ^ !csvSource.textBlock().isEmpty(), () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); - return csvSource.value().length > 0 ? String.join("\n", csvSource.value()) : csvSource.textBlock(); + if (!csvSource.textBlock().isEmpty()) { + return csvSource.textBlock(); + } + else { + for (int i = 0; i < values.length; i++) { + int finalI = i; + Preconditions.notBlank(values[i], + () -> "CSV record at index %d must not be blank".formatted(finalI + 1)); + } + return String.join("\n", values); + } } /** diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java index a06ef984acdd..295add81b394 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java @@ -91,7 +91,8 @@ *

Each value corresponds to a record in a CSV file and will be split using * the specified {@link #delimiter} or {@link #delimiterString}. Note that * the first value may optionally be used to supply CSV headers (see - * {@link #useHeadersInDisplayName}). + * {@link #useHeadersInDisplayName}). Moreover, each specified value must + * not be blank. * *

If text block syntax is supported by your programming language, * you may find it more convenient to declare your CSV content via the diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index 085c3cf19471..5e230fb965e8 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.shadow.de.siegmar.fastcsv.reader.CsvParseException; +import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; /** @@ -30,12 +31,12 @@ class CsvArgumentsProviderTests { @Test - void skipsEmptyLines() { - var annotation = csvSource("", "foo", "", "bar", ""); + void throwsExceptionForBlankLines() { + var annotation = csvSource("foo", "bar", " "); - var arguments = provideArguments(annotation); - - assertThat(arguments).containsExactly(array("foo"), array("bar")); + assertThatExceptionOfType(JUnitException.class)// + .isThrownBy(() -> provideArguments(annotation).toArray())// + .withMessage("CSV record at index 3 must not be blank"); } @Test From c1858d7aa4e3be4fcac47b16c233646112172591 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 23 Jun 2025 11:32:51 +0200 Subject: [PATCH 51/52] Polish release notes --- .../release-notes/release-notes-6.0.0-M1.adoc | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc index dbc52fa25609..41c50e8168d9 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc @@ -115,18 +115,18 @@ repository on GitHub. * The contracts for the `Executable` parameters of Kotlin-specific `assertTimeout` functions were changed from `callsInPlace(executable, EXACTLY_ONCE)` to `callsInPlace(executable, AT_MOST_ONCE)` which might result in compilation errors. -* The `CsvFileSource.lineSeparator()` parameter has been removed. The line separator - is now automatically detected, meaning that any of `\r`, `\n`, or `\r\n` is - treated as a line separator. * As a result of migrating from https://github.com/uniVocity/univocity-parsers[univocity-parsers] to - https://fastcsv.org/[FastCSV] for CSV input handling, the root causes and messages - of exceptions thrown when parsing malformed CSV input may differ in rare cases. - While overall parsing behavior remains consistent, this may affect - custom error handling that relies on specific exception types or messages. -* Parameters such as `ignoreLeadingAndTrailingWhitespace()`, `nullValues()`, - and others in `CsvSource` and `CsvFileSource` now apply to header fields as well - as to regular fields. + https://fastcsv.org/[FastCSV] for `@CsvSource` and `@CsvFileSource`, root causes and + messages of exceptions thrown for malformed CSV input may differ in some cases. While + the overall parsing behavior remains consistent, this may affect custom error handling + that relies on specific exception types or messages. +* The `CsvFileSource.lineSeparator()` parameter has been removed. The line separator is + now automatically detected, meaning that any of `\r`, `\n`, or `\r\n` is treated as a + line separator. +* Parameters such as `ignoreLeadingAndTrailingWhitespace()`, `nullValues()`, and others in + `@CsvSource` and `@CsvFileSource` now apply to header fields as well as to regular + fields. * The `junit-jupiter-migrationsupport` artifact and its contained classes are now deprecated and will be removed in the next major version. * The type bounds of the following methods have been changed to be more flexible and allow @@ -141,9 +141,9 @@ repository on GitHub. * Kotlin's `suspend` modifier may now be applied to test and lifecycle methods. * The `Arguments` interface for parameterized tests is now officially a `@FunctionalInterface`. -* CSV parsing for parameterized was migrated from the the no longer maintained - https://github.com/uniVocity/univocity-parsers[univocity-parsers] - to https://fastcsv.org/[FastCSV]. +* The implementation of `@CsvSource` and `@CsvFileSource` was migrated from the no longer + maintained https://github.com/uniVocity/univocity-parsers[univocity-parsers] to + https://fastcsv.org/[FastCSV]. This improves the consistency of CSV input handling, including for malformed entries, and provides better error reporting and overall performance. From 9aab4b50379d880f8774cc36a81f15efa772f588 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 23 Jun 2025 11:41:05 +0200 Subject: [PATCH 52/52] Polishing --- .../docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc index 41c50e8168d9..8e50b81851a2 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc @@ -118,14 +118,14 @@ repository on GitHub. * As a result of migrating from https://github.com/uniVocity/univocity-parsers[univocity-parsers] to https://fastcsv.org/[FastCSV] for `@CsvSource` and `@CsvFileSource`, root causes and - messages of exceptions thrown for malformed CSV input may differ in some cases. While + messages of exceptions thrown for malformed CSV input may differ in some cases. While the overall parsing behavior remains consistent, this may affect custom error handling that relies on specific exception types or messages. * The `CsvFileSource.lineSeparator()` parameter has been removed. The line separator is now automatically detected, meaning that any of `\r`, `\n`, or `\r\n` is treated as a line separator. * Parameters such as `ignoreLeadingAndTrailingWhitespace()`, `nullValues()`, and others in - `@CsvSource` and `@CsvFileSource` now apply to header fields as well as to regular + `@CsvSource` and `@CsvFileSource` now apply to header fields as well as to regular fields. * The `junit-jupiter-migrationsupport` artifact and its contained classes are now deprecated and will be removed in the next major version.