master
cpp 191 lines 5.51 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2
3 /*++
4
5 Module Name:
6
7 WSLCCsvSharedUnitTests.cpp
8
9 Abstract:
10
11 Unit tests for the shared CSV helpers in wsl::shared::string (SplitCsvFields, CsvEscapeField,
12 JoinCsvFields). These implement the single-record RFC 4180 grammar that docker buildx uses via
13 go-csvvalue / encoding/csv, and back the --output/--secret spec parsers. The tests pin the grammar
14 (quoting, "" escaping, embedded commas, malformed-record rejection) and the escape/join round-trip.
15
16 --*/
17
18 #include "precomp.h"
19 #include "windows/Common.h"
20 #include "WSLCCLITestHelpers.h"
21 #include <optional>
22 #include <string>
23 #include <vector>
24
25 namespace WSLCCsvSharedUnitTests {
26
27 using WStrings = std::vector<std::wstring>;
28
29 class WSLCCsvSharedUnitTests
30 {
31 WSLC_TEST_CLASS(WSLCCsvSharedUnitTests)
32
33 static void VerifySplit(const std::wstring& record, const WStrings& expected)
34 {
35 const auto fields = wsl::shared::string::SplitCsvFields(record);
36 VERIFY_IS_TRUE(fields.has_value());
37 if (!fields.has_value())
38 {
39 return;
40 }
41
42 VERIFY_ARE_EQUAL(expected.size(), fields->size());
43 for (size_t i = 0; i < expected.size() && i < fields->size(); ++i)
44 {
45 VERIFY_ARE_EQUAL(expected[i], (*fields)[i]);
46 }
47 }
48
49 static void VerifyMalformed(const std::wstring& record)
50 {
51 VERIFY_IS_FALSE(wsl::shared::string::SplitCsvFields(record).has_value());
52 }
53
54 // --- SplitCsvFields ---
55
56 TEST_METHOD(Split_SimpleFields)
57 {
58 VerifySplit(L"a,b,c", WStrings{L"a", L"b", L"c"});
59 }
60
61 TEST_METHOD(Split_SingleField)
62 {
63 VerifySplit(L"abc", WStrings{L"abc"});
64 }
65
66 TEST_METHOD(Split_EmptyInputIsOneEmptyField)
67 {
68 VerifySplit(L"", WStrings{L""});
69 }
70
71 TEST_METHOD(Split_EmptyFieldsPreserved)
72 {
73 // Unlike wsl::shared::string::Split, empty fields are preserved (they are meaningful in a spec).
74 VerifySplit(L"a,,c", WStrings{L"a", L"", L"c"});
75 }
76
77 TEST_METHOD(Split_LeadingAndTrailingCommas)
78 {
79 VerifySplit(L",a,", WStrings{L"", L"a", L""});
80 }
81
82 TEST_METHOD(Split_QuotedFieldWithComma)
83 {
84 VerifySplit(L"\"a,b\",c", WStrings{L"a,b", L"c"});
85 }
86
87 TEST_METHOD(Split_QuotedFieldWithEscapedQuote)
88 {
89 // A doubled quote inside a quoted field is a single literal quote.
90 VerifySplit(L"\"a\"\"b\"", WStrings{L"a\"b"});
91 }
92
93 TEST_METHOD(Split_QuotedEmptyField)
94 {
95 VerifySplit(L"\"\",x", WStrings{L"", L"x"});
96 }
97
98 TEST_METHOD(Split_QuotedThenPlainField)
99 {
100 VerifySplit(L"\"a\",b", WStrings{L"a", L"b"});
101 }
102
103 TEST_METHOD(Split_UnterminatedQuoteIsMalformed)
104 {
105 VerifyMalformed(L"\"abc");
106 }
107
108 TEST_METHOD(Split_TextAfterClosingQuoteIsMalformed)
109 {
110 VerifyMalformed(L"\"a\"b");
111 }
112
113 TEST_METHOD(Split_BareQuoteInUnquotedFieldIsMalformed)
114 {
115 // Go's encoding/csv (non-lazy) rejects a bare double quote in an unquoted field (ErrBareQuote).
116 VerifyMalformed(L"a\"b,c");
117 }
118
119 // --- CsvEscapeField ---
120
121 TEST_METHOD(Escape_PlainFieldUnchanged)
122 {
123 VERIFY_ARE_EQUAL(std::wstring(L"abc"), wsl::shared::string::CsvEscapeField(std::wstring(L"abc")));
124 }
125
126 TEST_METHOD(Escape_EmptyFieldUnchanged)
127 {
128 VERIFY_ARE_EQUAL(std::wstring(L""), wsl::shared::string::CsvEscapeField(std::wstring(L"")));
129 }
130
131 TEST_METHOD(Escape_CommaQuoted)
132 {
133 VERIFY_ARE_EQUAL(std::wstring(L"\"a,b\""), wsl::shared::string::CsvEscapeField(std::wstring(L"a,b")));
134 }
135
136 TEST_METHOD(Escape_QuoteDoubledAndQuoted)
137 {
138 VERIFY_ARE_EQUAL(std::wstring(L"\"a\"\"b\""), wsl::shared::string::CsvEscapeField(std::wstring(L"a\"b")));
139 }
140
141 TEST_METHOD(Escape_NewlineQuoted)
142 {
143 VERIFY_ARE_EQUAL(std::wstring(L"\"a\nb\""), wsl::shared::string::CsvEscapeField(std::wstring(L"a\nb")));
144 }
145
146 TEST_METHOD(Escape_LeadingOrTrailingSpaceQuoted)
147 {
148 VERIFY_ARE_EQUAL(std::wstring(L"\" a\""), wsl::shared::string::CsvEscapeField(std::wstring(L" a")));
149 VERIFY_ARE_EQUAL(std::wstring(L"\"a \""), wsl::shared::string::CsvEscapeField(std::wstring(L"a ")));
150 }
151
152 // --- JoinCsvFields ---
153
154 TEST_METHOD(Join_PlainFields)
155 {
156 VERIFY_ARE_EQUAL(std::wstring(L"a,b,c"), wsl::shared::string::JoinCsvFields(WStrings{L"a", L"b", L"c"}));
157 }
158
159 TEST_METHOD(Join_EscapesFieldContainingComma)
160 {
161 VERIFY_ARE_EQUAL(std::wstring(L"\"a,b\",c"), wsl::shared::string::JoinCsvFields(WStrings{L"a,b", L"c"}));
162 }
163
164 // --- Round-trip: SplitCsvFields(JoinCsvFields(x)) == x ---
165
166 TEST_METHOD(RoundTrip_JoinThenSplitPreservesFields)
167 {
168 const WStrings inputs{L"type=image", L"annotation.foo=a,b,c", L"name=x", L"quote=a\"b", L""};
169 const auto joined = wsl::shared::string::JoinCsvFields(inputs);
170 VerifySplit(joined, inputs);
171 }
172
173 // --- Template works for narrow (char) strings too ---
174
175 TEST_METHOD(Narrow_SplitAndJoinRoundTrip)
176 {
177 const std::vector<std::string> inputs{"a,b", "c", "d\"e"};
178 const auto joined = wsl::shared::string::JoinCsvFields(inputs);
179 VERIFY_ARE_EQUAL(std::string("\"a,b\",c,\"d\"\"e\""), joined);
180
181 const auto fields = wsl::shared::string::SplitCsvFields(joined);
182 VERIFY_IS_TRUE(fields.has_value());
183 VERIFY_ARE_EQUAL(inputs.size(), fields->size());
184 for (size_t i = 0; i < inputs.size() && i < fields->size(); ++i)
185 {
186 VERIFY_ARE_EQUAL(inputs[i], (*fields)[i]);
187 }
188 }
189 };
190
191 } // namespace WSLCCsvSharedUnitTests