|
15 | 15 |
|
16 | 16 | import java.io.StringReader;
|
17 | 17 | import java.lang.annotation.Annotation;
|
| 18 | +import java.util.ArrayList; |
18 | 19 | import java.util.Arrays;
|
19 | 20 | import java.util.List;
|
20 | 21 | import java.util.Set;
|
|
23 | 24 |
|
24 | 25 | import com.univocity.parsers.csv.CsvParser;
|
25 | 26 |
|
| 27 | +import org.junit.jupiter.api.Named; |
26 | 28 | import org.junit.jupiter.api.extension.ExtensionContext;
|
27 | 29 | import org.junit.jupiter.params.support.AnnotationConsumer;
|
28 | 30 | import org.junit.platform.commons.PreconditionViolationException;
|
@@ -53,56 +55,96 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
53 | 55 | Preconditions.condition(this.annotation.value().length > 0 ^ textBlockDeclared,
|
54 | 56 | () -> "@CsvSource must be declared with either `value` or `textBlock` but not both");
|
55 | 57 |
|
56 |
| - if (textBlockDeclared) { |
57 |
| - return parseTextBlock(this.annotation.textBlock()).stream().map(Arguments::of); |
58 |
| - } |
59 |
| - |
60 |
| - AtomicInteger index = new AtomicInteger(0); |
61 |
| - // @formatter:off |
62 |
| - return Arrays.stream(this.annotation.value()) |
63 |
| - .map(line -> parseLine(line, index.incrementAndGet())) |
64 |
| - .map(Arguments::of); |
65 |
| - // @formatter:on |
| 58 | + return textBlockDeclared ? parseTextBlock() : parseValueArray(); |
66 | 59 | }
|
67 | 60 |
|
68 |
| - private List<String[]> parseTextBlock(String textBlock) { |
| 61 | + private Stream<Arguments> parseTextBlock() { |
| 62 | + String textBlock = this.annotation.textBlock(); |
| 63 | + boolean useHeadersInDisplayName = this.annotation.useHeadersInDisplayName(); |
| 64 | + List<Arguments> argumentsList = new ArrayList<>(); |
| 65 | + |
69 | 66 | try {
|
70 |
| - AtomicInteger index = new AtomicInteger(0); |
71 | 67 | List<String[]> csvRecords = this.csvParser.parseAll(new StringReader(textBlock));
|
| 68 | + String[] headers = useHeadersInDisplayName ? getHeaders(this.csvParser) : null; |
| 69 | + |
| 70 | + AtomicInteger index = new AtomicInteger(0); |
72 | 71 | for (String[] csvRecord : csvRecords) {
|
73 | 72 | index.incrementAndGet();
|
74 | 73 | Preconditions.notNull(csvRecord,
|
75 |
| - () -> "Line at index " + index.get() + " contains invalid CSV: \"\"\"\n" + textBlock + "\n\"\"\""); |
76 |
| - processNullValues(csvRecord, this.nullValues); |
| 74 | + () -> "Record at index " + index + " contains invalid CSV: \"\"\"\n" + textBlock + "\n\"\"\""); |
| 75 | + argumentsList.add(processCsvRecord(csvRecord, this.nullValues, useHeadersInDisplayName, headers)); |
77 | 76 | }
|
78 |
| - return csvRecords; |
79 | 77 | }
|
80 | 78 | catch (Throwable throwable) {
|
81 | 79 | throw handleCsvException(throwable, this.annotation);
|
82 | 80 | }
|
| 81 | + |
| 82 | + return argumentsList.stream(); |
83 | 83 | }
|
84 | 84 |
|
85 |
| - private String[] parseLine(String line, int index) { |
| 85 | + private Stream<Arguments> parseValueArray() { |
| 86 | + boolean useHeadersInDisplayName = this.annotation.useHeadersInDisplayName(); |
| 87 | + List<Arguments> argumentsList = new ArrayList<>(); |
| 88 | + |
86 | 89 | try {
|
87 |
| - String[] csvRecord = this.csvParser.parseLine(line + LINE_SEPARATOR); |
88 |
| - Preconditions.notNull(csvRecord, |
89 |
| - () -> "Line at index " + index + " contains invalid CSV: \"" + line + "\""); |
90 |
| - processNullValues(csvRecord, this.nullValues); |
91 |
| - return csvRecord; |
| 90 | + String[] headers = null; |
| 91 | + AtomicInteger index = new AtomicInteger(0); |
| 92 | + for (String input : this.annotation.value()) { |
| 93 | + index.incrementAndGet(); |
| 94 | + String[] csvRecord = this.csvParser.parseLine(input + LINE_SEPARATOR); |
| 95 | + // Lazily retrieve headers if necessary. |
| 96 | + if (useHeadersInDisplayName && headers == null) { |
| 97 | + headers = getHeaders(this.csvParser); |
| 98 | + } |
| 99 | + Preconditions.notNull(csvRecord, |
| 100 | + () -> "Record at index " + index + " contains invalid CSV: \"" + input + "\""); |
| 101 | + argumentsList.add(processCsvRecord(csvRecord, this.nullValues, useHeadersInDisplayName, headers)); |
| 102 | + } |
92 | 103 | }
|
93 | 104 | catch (Throwable throwable) {
|
94 | 105 | throw handleCsvException(throwable, this.annotation);
|
95 | 106 | }
|
| 107 | + |
| 108 | + return argumentsList.stream(); |
96 | 109 | }
|
97 | 110 |
|
98 |
| - static void processNullValues(String[] csvRecord, Set<String> nullValues) { |
99 |
| - if (!nullValues.isEmpty()) { |
100 |
| - for (int i = 0; i < csvRecord.length; i++) { |
101 |
| - if (nullValues.contains(csvRecord[i])) { |
102 |
| - csvRecord[i] = null; |
103 |
| - } |
| 111 | + // Cannot get parsed headers until after parsing has started. |
| 112 | + static String[] getHeaders(CsvParser csvParser) { |
| 113 | + return Arrays.stream(csvParser.getContext().parsedHeaders())// |
| 114 | + .map(String::trim)// |
| 115 | + .toArray(String[]::new); |
| 116 | + } |
| 117 | + |
| 118 | + /** |
| 119 | + * Processes custom null values, supports wrapping of column values in |
| 120 | + * {@link Named} if necessary (for CSV header support), and returns the |
| 121 | + * CSV record wrapped in an {@link Arguments} instance. |
| 122 | + */ |
| 123 | + static Arguments processCsvRecord(Object[] csvRecord, Set<String> nullValues, boolean useHeadersInDisplayName, |
| 124 | + String[] headers) { |
| 125 | + |
| 126 | + // Nothing to process? |
| 127 | + if (nullValues.isEmpty() && !useHeadersInDisplayName) { |
| 128 | + return Arguments.of(csvRecord); |
| 129 | + } |
| 130 | + |
| 131 | + Preconditions.condition(!useHeadersInDisplayName || (csvRecord.length <= headers.length), |
| 132 | + () -> String.format( |
| 133 | + "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s", |
| 134 | + csvRecord.length, headers.length, Arrays.toString(csvRecord))); |
| 135 | + |
| 136 | + Object[] arguments = new Object[csvRecord.length]; |
| 137 | + for (int i = 0; i < csvRecord.length; i++) { |
| 138 | + Object column = csvRecord[i]; |
| 139 | + if (nullValues.contains(column)) { |
| 140 | + column = null; |
| 141 | + } |
| 142 | + if (useHeadersInDisplayName) { |
| 143 | + column = Named.of(headers[i] + " = " + column, column); |
104 | 144 | }
|
| 145 | + arguments[i] = column; |
105 | 146 | }
|
| 147 | + return Arguments.of(arguments); |
106 | 148 | }
|
107 | 149 |
|
108 | 150 | /**
|
|
0 commit comments