1 | //===-- flang/unittests/Runtime/CharacterTest.cpp ---------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | // Basic sanity tests of CHARACTER API; exhaustive testing will be done |
10 | // in Fortran. |
11 | |
12 | #include "flang/Runtime/character.h" |
13 | #include "gtest/gtest.h" |
14 | #include "flang/Runtime/descriptor.h" |
15 | #include <cstring> |
16 | #include <functional> |
17 | #include <tuple> |
18 | #include <vector> |
19 | |
20 | using namespace Fortran::runtime; |
21 | |
22 | using CharacterTypes = ::testing::Types<char, char16_t, char32_t>; |
23 | |
24 | // Helper for creating, allocating and filling up a descriptor with data from |
25 | // raw character literals, converted to the CHAR type used by the test. |
26 | template <typename CHAR> |
27 | OwningPtr<Descriptor> CreateDescriptor(const std::vector<SubscriptValue> &shape, |
28 | const std::vector<const char *> &raw_strings) { |
29 | std::size_t length{std::strlen(s: raw_strings[0])}; |
30 | |
31 | OwningPtr<Descriptor> descriptor{Descriptor::Create(sizeof(CHAR), length, |
32 | nullptr, shape.size(), nullptr, CFI_attribute_allocatable)}; |
33 | int rank{static_cast<int>(shape.size())}; |
34 | // Use a weird lower bound of 2 to flush out subscripting bugs |
35 | for (int j{0}; j < rank; ++j) { |
36 | descriptor->GetDimension(j).SetBounds(2, shape[j] + 1); |
37 | } |
38 | if (descriptor->Allocate() != 0) { |
39 | return nullptr; |
40 | } |
41 | |
42 | std::size_t offset = 0; |
43 | for (const char *raw : raw_strings) { |
44 | std::basic_string<CHAR> converted{raw, raw + length}; |
45 | std::copy(converted.begin(), converted.end(), |
46 | descriptor->OffsetElement<CHAR>(offset * length * sizeof(CHAR))); |
47 | ++offset; |
48 | } |
49 | |
50 | return descriptor; |
51 | } |
52 | |
53 | TEST(CharacterTests, AppendAndPad) { |
54 | static constexpr int limitMax{8}; |
55 | static char buffer[limitMax]; |
56 | static std::size_t offset{0}; |
57 | for (std::size_t limit{0}; limit < limitMax; ++limit, offset = 0) { |
58 | std::memset(s: buffer, c: 0, n: sizeof buffer); |
59 | |
60 | // Ensure appending characters does not overrun the limit |
61 | offset = RTNAME(CharacterAppend1)(buffer, limit, offset, "abc" , 3); |
62 | offset = RTNAME(CharacterAppend1)(buffer, limit, offset, "DE" , 2); |
63 | ASSERT_LE(offset, limit) << "offset " << offset << ">" << limit; |
64 | |
65 | // Ensure whitespace padding does not overrun limit, the string is still |
66 | // null-terminated, and string matches the expected value up to the limit. |
67 | RTNAME(CharacterPad1)(buffer, limit, offset); |
68 | EXPECT_EQ(buffer[limit], '\0') |
69 | << "buffer[" << limit << "]='" << buffer[limit] << "'" ; |
70 | buffer[limit] = buffer[limit] ? '\0' : buffer[limit]; |
71 | ASSERT_EQ(std::memcmp(buffer, "abcDE " , limit), 0) |
72 | << "buffer = '" << buffer << "'" ; |
73 | } |
74 | } |
75 | |
76 | TEST(CharacterTests, CharacterAppend1Overrun) { |
77 | static constexpr int bufferSize{4}; |
78 | static constexpr std::size_t limit{2}; |
79 | static char buffer[bufferSize]; |
80 | static std::size_t offset{0}; |
81 | std::memset(s: buffer, c: 0, n: sizeof buffer); |
82 | offset = RTNAME(CharacterAppend1)(buffer, limit, offset, "1234" , bufferSize); |
83 | ASSERT_EQ(offset, limit) << "CharacterAppend1 did not halt at limit = " |
84 | << limit << ", but at offset = " << offset; |
85 | } |
86 | |
87 | // Test ADJUSTL() and ADJUSTR() |
88 | template <typename CHAR> struct AdjustLRTests : public ::testing::Test {}; |
89 | TYPED_TEST_SUITE(AdjustLRTests, CharacterTypes, ); |
90 | |
91 | struct AdjustLRTestCase { |
92 | const char *input, *output; |
93 | }; |
94 | |
95 | template <typename CHAR> |
96 | void RunAdjustLRTest(const char *which, |
97 | const std::function<void( |
98 | Descriptor &, const Descriptor &, const char *, int)> &adjust, |
99 | const char *inputRaw, const char *outputRaw) { |
100 | OwningPtr<Descriptor> input{CreateDescriptor<CHAR>({}, {inputRaw})}; |
101 | ASSERT_NE(input, nullptr); |
102 | ASSERT_TRUE(input->IsAllocated()); |
103 | |
104 | StaticDescriptor<1> outputStaticDescriptor; |
105 | Descriptor &output{outputStaticDescriptor.descriptor()}; |
106 | |
107 | adjust(output, *input, /*sourceFile=*/nullptr, /*sourceLine=*/0); |
108 | std::basic_string<CHAR> got{ |
109 | output.OffsetElement<CHAR>(), std::strlen(s: inputRaw)}; |
110 | std::basic_string<CHAR> expect{outputRaw, outputRaw + std::strlen(s: outputRaw)}; |
111 | ASSERT_EQ(got, expect) << which << "('" << inputRaw |
112 | << "') for CHARACTER(kind=" << sizeof(CHAR) << ")" ; |
113 | } |
114 | |
115 | TYPED_TEST(AdjustLRTests, AdjustL) { |
116 | static std::vector<AdjustLRTestCase> testcases{ |
117 | {.input: " where should the spaces be?" , .output: "where should the spaces be? " }, |
118 | {.input: " leading and trailing whitespaces " , |
119 | .output: "leading and trailing whitespaces " }, |
120 | {.input: "shouldn't change" , .output: "shouldn't change" }, |
121 | }; |
122 | |
123 | for (const auto &t : testcases) { |
124 | RunAdjustLRTest<TypeParam>("Adjustl" , RTNAME(Adjustl), t.input, t.output); |
125 | } |
126 | } |
127 | |
128 | TYPED_TEST(AdjustLRTests, AdjustR) { |
129 | static std::vector<AdjustLRTestCase> testcases{ |
130 | {.input: "where should the spaces be? " , .output: " where should the spaces be?" }, |
131 | {.input: " leading and trailing whitespaces " , |
132 | .output: " leading and trailing whitespaces" }, |
133 | {.input: "shouldn't change" , .output: "shouldn't change" }, |
134 | }; |
135 | |
136 | for (const auto &t : testcases) { |
137 | RunAdjustLRTest<TypeParam>("Adjustr" , RTNAME(Adjustr), t.input, t.output); |
138 | } |
139 | } |
140 | |
141 | //------------------------------------------------------------------------------ |
142 | /// Tests and infrastructure for character comparison functions |
143 | //------------------------------------------------------------------------------ |
144 | |
145 | template <typename CHAR> |
146 | using ComparisonFuncTy = |
147 | std::function<int(const CHAR *, const CHAR *, std::size_t, std::size_t)>; |
148 | |
149 | using ComparisonFuncsTy = std::tuple<ComparisonFuncTy<char>, |
150 | ComparisonFuncTy<char16_t>, ComparisonFuncTy<char32_t>>; |
151 | |
152 | // These comparison functions are the systems under test in the |
153 | // CharacterComparisonTests test cases. |
154 | static ComparisonFuncsTy comparisonFuncs{ |
155 | RTNAME(CharacterCompareScalar1), |
156 | RTNAME(CharacterCompareScalar2), |
157 | RTNAME(CharacterCompareScalar4), |
158 | }; |
159 | |
160 | // Types of _values_ over which comparison tests are parameterized |
161 | template <typename CHAR> |
162 | using ComparisonParametersTy = |
163 | std::vector<std::tuple<const CHAR *, const CHAR *, int, int, int>>; |
164 | |
165 | using ComparisonTestCasesTy = std::tuple<ComparisonParametersTy<char>, |
166 | ComparisonParametersTy<char16_t>, ComparisonParametersTy<char32_t>>; |
167 | |
168 | static ComparisonTestCasesTy comparisonTestCases{ |
169 | { |
170 | std::make_tuple(args: "abc" , args: "abc" , args: 3, args: 3, args: 0), |
171 | std::make_tuple(args: "abc" , args: "def" , args: 3, args: 3, args: -1), |
172 | std::make_tuple(args: "ab " , args: "abc" , args: 3, args: 2, args: 0), |
173 | std::make_tuple(args: "abc" , args: "abc" , args: 2, args: 3, args: -1), |
174 | std::make_tuple(args: "ab\xff" , args: "ab " , args: 3, args: 2, args: 1), |
175 | std::make_tuple(args: "ab " , args: "ab\xff" , args: 2, args: 3, args: -1), |
176 | }, |
177 | { |
178 | std::make_tuple(args: u"abc" , args: u"abc" , args: 3, args: 3, args: 0), |
179 | std::make_tuple(args: u"abc" , args: u"def" , args: 3, args: 3, args: -1), |
180 | std::make_tuple(args: u"ab " , args: u"abc" , args: 3, args: 2, args: 0), |
181 | std::make_tuple(args: u"abc" , args: u"abc" , args: 2, args: 3, args: -1), |
182 | }, |
183 | { |
184 | std::make_tuple(args: U"abc" , args: U"abc" , args: 3, args: 3, args: 0), |
185 | std::make_tuple(args: U"abc" , args: U"def" , args: 3, args: 3, args: -1), |
186 | std::make_tuple(args: U"ab " , args: U"abc" , args: 3, args: 2, args: 0), |
187 | std::make_tuple(args: U"abc" , args: U"abc" , args: 2, args: 3, args: -1), |
188 | }}; |
189 | |
190 | template <typename CHAR> |
191 | struct CharacterComparisonTests : public ::testing::Test { |
192 | CharacterComparisonTests() |
193 | : parameters{std::get<ComparisonParametersTy<CHAR>>(comparisonTestCases)}, |
194 | characterComparisonFunc{ |
195 | std::get<ComparisonFuncTy<CHAR>>(comparisonFuncs)} {} |
196 | ComparisonParametersTy<CHAR> parameters; |
197 | ComparisonFuncTy<CHAR> characterComparisonFunc; |
198 | }; |
199 | |
200 | TYPED_TEST_SUITE(CharacterComparisonTests, CharacterTypes, ); |
201 | |
202 | TYPED_TEST(CharacterComparisonTests, CompareCharacters) { |
203 | for (auto &[x, y, xBytes, yBytes, expect] : this->parameters) { |
204 | int cmp{this->characterComparisonFunc(x, y, xBytes, yBytes)}; |
205 | TypeParam buf[2][8]; |
206 | std::memset(s: buf, c: 0, n: sizeof buf); |
207 | std::memcpy(dest: buf[0], src: x, n: xBytes); |
208 | std::memcpy(dest: buf[1], src: y, n: yBytes); |
209 | ASSERT_EQ(cmp, expect) << "compare '" << x << "'(" << xBytes << ") to '" |
210 | << y << "'(" << yBytes << "), got " << cmp |
211 | << ", should be " << expect << '\n'; |
212 | |
213 | // Perform the same test with the parameters reversed and the difference |
214 | // negated |
215 | std::swap(x, y); |
216 | std::swap(xBytes, yBytes); |
217 | expect = -expect; |
218 | |
219 | cmp = this->characterComparisonFunc(x, y, xBytes, yBytes); |
220 | std::memset(s: buf, c: 0, n: sizeof buf); |
221 | std::memcpy(dest: buf[0], src: x, n: xBytes); |
222 | std::memcpy(dest: buf[1], src: y, n: yBytes); |
223 | ASSERT_EQ(cmp, expect) << "compare '" << x << "'(" << xBytes << ") to '" |
224 | << y << "'(" << yBytes << "'), got " << cmp |
225 | << ", should be " << expect << '\n'; |
226 | } |
227 | } |
228 | |
229 | // Test MIN() and MAX() |
230 | struct ExtremumTestCase { |
231 | std::vector<SubscriptValue> shape; // Empty = scalar, non-empty = array. |
232 | std::vector<const char *> x, y, expect; |
233 | }; |
234 | |
235 | template <typename CHAR> |
236 | void RunExtremumTests(const char *which, |
237 | std::function<void(Descriptor &, const Descriptor &, const char *, int)> |
238 | function, |
239 | const std::vector<ExtremumTestCase> &testCases) { |
240 | std::stringstream traceMessage; |
241 | traceMessage << which << " for CHARACTER(kind=" << sizeof(CHAR) << ")" ; |
242 | SCOPED_TRACE(traceMessage.str()); |
243 | |
244 | for (const auto &t : testCases) { |
245 | OwningPtr<Descriptor> x = CreateDescriptor<CHAR>(t.shape, t.x); |
246 | OwningPtr<Descriptor> y = CreateDescriptor<CHAR>(t.shape, t.y); |
247 | |
248 | ASSERT_NE(x, nullptr); |
249 | ASSERT_TRUE(x->IsAllocated()); |
250 | ASSERT_NE(y, nullptr); |
251 | ASSERT_TRUE(y->IsAllocated()); |
252 | function(*x, *y, __FILE__, __LINE__); |
253 | |
254 | std::size_t length = x->ElementBytes() / sizeof(CHAR); |
255 | for (std::size_t i = 0; i < t.x.size(); ++i) { |
256 | std::basic_string<CHAR> got{ |
257 | x->OffsetElement<CHAR>(i * x->ElementBytes()), length}; |
258 | std::basic_string<CHAR> expect{ |
259 | t.expect[i], t.expect[i] + std::strlen(s: t.expect[i])}; |
260 | EXPECT_EQ(expect, got) << "inputs: '" << t.x[i] << "','" << t.y[i] << "'" ; |
261 | } |
262 | } |
263 | } |
264 | |
265 | template <typename CHAR> struct ExtremumTests : public ::testing::Test {}; |
266 | TYPED_TEST_SUITE(ExtremumTests, CharacterTypes, ); |
267 | |
268 | TYPED_TEST(ExtremumTests, MinTests) { |
269 | static std::vector<ExtremumTestCase> tests{{{}, {"a" }, {"z" }, {"a" }}, |
270 | {{1}, {"zaaa" }, {"aa" }, {"aa " }}, |
271 | {{1, 1}, {"aaz" }, {"aaaaa" }, {"aaaaa" }}, |
272 | {{2, 3}, {"a" , "b" , "c" , "d" , "E" , "f" }, |
273 | {"xa" , "ya" , "az" , "dd" , "Sz" , "cc" }, |
274 | {"a " , "b " , "az" , "d " , "E " , "cc" }}}; |
275 | RunExtremumTests<TypeParam>("MIN" , RTNAME(CharacterMin), tests); |
276 | } |
277 | |
278 | TYPED_TEST(ExtremumTests, MaxTests) { |
279 | static std::vector<ExtremumTestCase> tests{ |
280 | {{}, {"a" }, {"z" }, {"z" }}, |
281 | {{1}, {"zaa" }, {"aaaaa" }, {"zaa " }}, |
282 | {{1, 1, 1}, {"aaaaa" }, {"aazaa" }, {"aazaa" }}, |
283 | }; |
284 | RunExtremumTests<TypeParam>("MAX" , RTNAME(CharacterMax), tests); |
285 | } |
286 | |
287 | template <typename CHAR> |
288 | void RunAllocationTest(const char *xRaw, const char *yRaw) { |
289 | OwningPtr<Descriptor> x = CreateDescriptor<CHAR>({}, {xRaw}); |
290 | OwningPtr<Descriptor> y = CreateDescriptor<CHAR>({}, {yRaw}); |
291 | |
292 | ASSERT_NE(x, nullptr); |
293 | ASSERT_TRUE(x->IsAllocated()); |
294 | ASSERT_NE(y, nullptr); |
295 | ASSERT_TRUE(y->IsAllocated()); |
296 | |
297 | void *old = x->raw().base_addr; |
298 | RTNAME(CharacterMin)(*x, *y, __FILE__, __LINE__); |
299 | EXPECT_EQ(old, x->raw().base_addr); |
300 | } |
301 | |
302 | TYPED_TEST(ExtremumTests, NoReallocate) { |
303 | // Test that we don't reallocate if the accumulator is already large enough. |
304 | RunAllocationTest<TypeParam>("loooooong" , "short" ); |
305 | } |
306 | |
307 | // Test search functions INDEX(), SCAN(), and VERIFY() |
308 | |
309 | template <typename CHAR> |
310 | using SearchFunction = std::function<std::size_t( |
311 | const CHAR *, std::size_t, const CHAR *, std::size_t, bool)>; |
312 | template <template <typename> class FUNC> |
313 | using CharTypedFunctions = |
314 | std::tuple<FUNC<char>, FUNC<char16_t>, FUNC<char32_t>>; |
315 | using SearchFunctions = CharTypedFunctions<SearchFunction>; |
316 | struct SearchTestCase { |
317 | const char *x, *y; |
318 | bool back; |
319 | std::size_t expect; |
320 | }; |
321 | |
322 | template <typename CHAR> |
323 | void RunSearchTests(const char *which, |
324 | const std::vector<SearchTestCase> &testCases, |
325 | const SearchFunction<CHAR> &function) { |
326 | for (const auto &t : testCases) { |
327 | // Convert default character to desired kind |
328 | std::size_t xLen{std::strlen(s: t.x)}, yLen{std::strlen(s: t.y)}; |
329 | std::basic_string<CHAR> x{t.x, t.x + xLen}; |
330 | std::basic_string<CHAR> y{t.y, t.y + yLen}; |
331 | auto got{function(x.data(), xLen, y.data(), yLen, t.back)}; |
332 | ASSERT_EQ(got, t.expect) |
333 | << which << "('" << t.x << "','" << t.y << "',back=" << t.back |
334 | << ") for CHARACTER(kind=" << sizeof(CHAR) << "): got " << got |
335 | << ", expected " << t.expect; |
336 | } |
337 | } |
338 | |
339 | template <typename CHAR> struct SearchTests : public ::testing::Test {}; |
340 | TYPED_TEST_SUITE(SearchTests, CharacterTypes, ); |
341 | |
342 | TYPED_TEST(SearchTests, IndexTests) { |
343 | static SearchFunctions functions{ |
344 | RTNAME(Index1), RTNAME(Index2), RTNAME(Index4)}; |
345 | static std::vector<SearchTestCase> tests{ |
346 | {.x: "" , .y: "" , .back: false, .expect: 1}, |
347 | {.x: "" , .y: "" , .back: true, .expect: 1}, |
348 | {.x: "a" , .y: "" , .back: false, .expect: 1}, |
349 | {.x: "a" , .y: "" , .back: true, .expect: 2}, |
350 | {.x: "" , .y: "a" , .back: false, .expect: 0}, |
351 | {.x: "" , .y: "a" , .back: true, .expect: 0}, |
352 | {.x: "aa" , .y: "a" , .back: false, .expect: 1}, |
353 | {.x: "aa" , .y: "a" , .back: true, .expect: 2}, |
354 | {.x: "Fortran that I ran" , .y: "that I ran" , .back: false, .expect: 9}, |
355 | {.x: "Fortran that I ran" , .y: "that I ran" , .back: true, .expect: 9}, |
356 | {.x: "Fortran that you ran" , .y: "that I ran" , .back: false, .expect: 0}, |
357 | {.x: "Fortran that you ran" , .y: "that I ran" , .back: true, .expect: 0}, |
358 | }; |
359 | RunSearchTests( |
360 | "INDEX" , tests, std::get<SearchFunction<TypeParam>>(functions)); |
361 | } |
362 | |
363 | TYPED_TEST(SearchTests, ScanTests) { |
364 | static SearchFunctions functions{RTNAME(Scan1), RTNAME(Scan2), RTNAME(Scan4)}; |
365 | static std::vector<SearchTestCase> tests{ |
366 | {.x: "abc" , .y: "abc" , .back: false, .expect: 1}, |
367 | {.x: "abc" , .y: "abc" , .back: true, .expect: 3}, |
368 | {.x: "abc" , .y: "cde" , .back: false, .expect: 3}, |
369 | {.x: "abc" , .y: "cde" , .back: true, .expect: 3}, |
370 | {.x: "abc" , .y: "x" , .back: false, .expect: 0}, |
371 | {.x: "" , .y: "x" , .back: false, .expect: 0}, |
372 | }; |
373 | RunSearchTests("SCAN" , tests, std::get<SearchFunction<TypeParam>>(functions)); |
374 | } |
375 | |
376 | TYPED_TEST(SearchTests, VerifyTests) { |
377 | static SearchFunctions functions{ |
378 | RTNAME(Verify1), RTNAME(Verify2), RTNAME(Verify4)}; |
379 | static std::vector<SearchTestCase> tests{ |
380 | {.x: "abc" , .y: "abc" , .back: false, .expect: 0}, |
381 | {.x: "abc" , .y: "abc" , .back: true, .expect: 0}, |
382 | {.x: "abc" , .y: "cde" , .back: false, .expect: 1}, |
383 | {.x: "abc" , .y: "cde" , .back: true, .expect: 2}, |
384 | {.x: "abc" , .y: "x" , .back: false, .expect: 1}, |
385 | {.x: "" , .y: "x" , .back: false, .expect: 0}, |
386 | }; |
387 | RunSearchTests( |
388 | "VERIFY" , tests, std::get<SearchFunction<TypeParam>>(functions)); |
389 | } |
390 | |
391 | // Test REPEAT() |
392 | template <typename CHAR> struct RepeatTests : public ::testing::Test {}; |
393 | TYPED_TEST_SUITE(RepeatTests, CharacterTypes, ); |
394 | |
395 | struct RepeatTestCase { |
396 | std::size_t ncopies; |
397 | const char *input, *output; |
398 | }; |
399 | |
400 | template <typename CHAR> |
401 | void RunRepeatTest( |
402 | std::size_t ncopies, const char *inputRaw, const char *outputRaw) { |
403 | OwningPtr<Descriptor> input{CreateDescriptor<CHAR>({}, {inputRaw})}; |
404 | ASSERT_NE(input, nullptr); |
405 | ASSERT_TRUE(input->IsAllocated()); |
406 | |
407 | StaticDescriptor<1> outputStaticDescriptor; |
408 | Descriptor &output{outputStaticDescriptor.descriptor()}; |
409 | |
410 | RTNAME(Repeat)(output, *input, ncopies); |
411 | std::basic_string<CHAR> got{ |
412 | output.OffsetElement<CHAR>(), output.ElementBytes() / sizeof(CHAR)}; |
413 | std::basic_string<CHAR> expect{outputRaw, outputRaw + std::strlen(s: outputRaw)}; |
414 | ASSERT_EQ(got, expect) << "'" << inputRaw << "' * " << ncopies |
415 | << "' for CHARACTER(kind=" << sizeof(CHAR) << ")" ; |
416 | } |
417 | |
418 | TYPED_TEST(RepeatTests, Repeat) { |
419 | static std::vector<RepeatTestCase> testcases{ |
420 | {.ncopies: 1, .input: "just one copy" , .output: "just one copy" }, |
421 | {.ncopies: 5, .input: "copy." , .output: "copy.copy.copy.copy.copy." }, |
422 | {.ncopies: 0, .input: "no copies" , .output: "" }, |
423 | }; |
424 | |
425 | for (const auto &t : testcases) { |
426 | RunRepeatTest<TypeParam>(t.ncopies, t.input, t.output); |
427 | } |
428 | } |
429 | |