1//===-- 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-rt/runtime/descriptor.h"
15#include <cstring>
16#include <functional>
17#include <tuple>
18#include <vector>
19
20using namespace Fortran::runtime;
21
22using 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.
26template <typename CHAR>
27OwningPtr<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(kNoAsyncObject) != 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
53TEST(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
76TEST(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()
88template <typename CHAR> struct AdjustLRTests : public ::testing::Test {};
89TYPED_TEST_SUITE(AdjustLRTests, CharacterTypes, );
90
91struct AdjustLRTestCase {
92 const char *input, *output;
93};
94
95template <typename CHAR>
96void 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
115TYPED_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
128TYPED_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
145template <typename CHAR>
146using ComparisonFuncTy =
147 std::function<int(const CHAR *, const CHAR *, std::size_t, std::size_t)>;
148
149using 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.
154static ComparisonFuncsTy comparisonFuncs{
155 RTNAME(CharacterCompareScalar1),
156 RTNAME(CharacterCompareScalar2),
157 RTNAME(CharacterCompareScalar4),
158};
159
160// Types of _values_ over which comparison tests are parameterized
161template <typename CHAR>
162using ComparisonParametersTy =
163 std::vector<std::tuple<const CHAR *, const CHAR *, int, int, int>>;
164
165using ComparisonTestCasesTy = std::tuple<ComparisonParametersTy<char>,
166 ComparisonParametersTy<char16_t>, ComparisonParametersTy<char32_t>>;
167
168static 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
190template <typename CHAR>
191struct 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
200TYPED_TEST_SUITE(CharacterComparisonTests, CharacterTypes, );
201
202TYPED_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()
230struct ExtremumTestCase {
231 std::vector<SubscriptValue> shape; // Empty = scalar, non-empty = array.
232 std::vector<const char *> x, y, expect;
233};
234
235template <typename CHAR>
236void 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 x->Deallocate();
264 y->Deallocate();
265 }
266}
267
268template <typename CHAR> struct ExtremumTests : public ::testing::Test {};
269TYPED_TEST_SUITE(ExtremumTests, CharacterTypes, );
270
271TYPED_TEST(ExtremumTests, MinTests) {
272 static std::vector<ExtremumTestCase> tests{{{}, {"a"}, {"z"}, {"a"}},
273 {{1}, {"zaaa"}, {"aa"}, {"aa "}},
274 {{1, 1}, {"aaz"}, {"aaaaa"}, {"aaaaa"}},
275 {{2, 3}, {"a", "b", "c", "d", "E", "f"},
276 {"xa", "ya", "az", "dd", "Sz", "cc"},
277 {"a ", "b ", "az", "d ", "E ", "cc"}}};
278 RunExtremumTests<TypeParam>("MIN", RTNAME(CharacterMin), tests);
279}
280
281TYPED_TEST(ExtremumTests, MaxTests) {
282 static std::vector<ExtremumTestCase> tests{
283 {{}, {"a"}, {"z"}, {"z"}},
284 {{1}, {"zaa"}, {"aaaaa"}, {"zaa "}},
285 {{1, 1, 1}, {"aaaaa"}, {"aazaa"}, {"aazaa"}},
286 };
287 RunExtremumTests<TypeParam>("MAX", RTNAME(CharacterMax), tests);
288}
289
290template <typename CHAR>
291void RunAllocationTest(const char *xRaw, const char *yRaw) {
292 OwningPtr<Descriptor> x = CreateDescriptor<CHAR>({}, {xRaw});
293 OwningPtr<Descriptor> y = CreateDescriptor<CHAR>({}, {yRaw});
294
295 ASSERT_NE(x, nullptr);
296 ASSERT_TRUE(x->IsAllocated());
297 ASSERT_NE(y, nullptr);
298 ASSERT_TRUE(y->IsAllocated());
299
300 void *old = x->raw().base_addr;
301 RTNAME(CharacterMin)(*x, *y, __FILE__, __LINE__);
302 EXPECT_EQ(old, x->raw().base_addr);
303}
304
305TYPED_TEST(ExtremumTests, NoReallocate) {
306 // Test that we don't reallocate if the accumulator is already large enough.
307 RunAllocationTest<TypeParam>("loooooong", "short");
308}
309
310// Test search functions INDEX(), SCAN(), and VERIFY()
311
312template <typename CHAR>
313using SearchFunction = std::function<std::size_t(
314 const CHAR *, std::size_t, const CHAR *, std::size_t, bool)>;
315template <template <typename> class FUNC>
316using CharTypedFunctions =
317 std::tuple<FUNC<char>, FUNC<char16_t>, FUNC<char32_t>>;
318using SearchFunctions = CharTypedFunctions<SearchFunction>;
319struct SearchTestCase {
320 const char *x, *y;
321 bool back;
322 std::size_t expect;
323};
324
325template <typename CHAR>
326void RunSearchTests(const char *which,
327 const std::vector<SearchTestCase> &testCases,
328 const SearchFunction<CHAR> &function) {
329 for (const auto &t : testCases) {
330 // Convert default character to desired kind
331 std::size_t xLen{std::strlen(s: t.x)}, yLen{std::strlen(s: t.y)};
332 std::basic_string<CHAR> x{t.x, t.x + xLen};
333 std::basic_string<CHAR> y{t.y, t.y + yLen};
334 auto got{function(x.data(), xLen, y.data(), yLen, t.back)};
335 ASSERT_EQ(got, t.expect)
336 << which << "('" << t.x << "','" << t.y << "',back=" << t.back
337 << ") for CHARACTER(kind=" << sizeof(CHAR) << "): got " << got
338 << ", expected " << t.expect;
339 }
340}
341
342template <typename CHAR> struct SearchTests : public ::testing::Test {};
343TYPED_TEST_SUITE(SearchTests, CharacterTypes, );
344
345TYPED_TEST(SearchTests, IndexTests) {
346 static SearchFunctions functions{
347 RTNAME(Index1), RTNAME(Index2), RTNAME(Index4)};
348 static std::vector<SearchTestCase> tests{
349 {.x: "", .y: "", .back: false, .expect: 1},
350 {.x: "", .y: "", .back: true, .expect: 1},
351 {.x: "a", .y: "", .back: false, .expect: 1},
352 {.x: "a", .y: "", .back: true, .expect: 2},
353 {.x: "", .y: "a", .back: false, .expect: 0},
354 {.x: "", .y: "a", .back: true, .expect: 0},
355 {.x: "aa", .y: "a", .back: false, .expect: 1},
356 {.x: "aa", .y: "a", .back: true, .expect: 2},
357 {.x: "aAA", .y: "A", .back: false, .expect: 2},
358 {.x: "Fortran that I ran", .y: "that I ran", .back: false, .expect: 9},
359 {.x: "Fortran that I ran", .y: "that I ran", .back: true, .expect: 9},
360 {.x: "Fortran that you ran", .y: "that I ran", .back: false, .expect: 0},
361 {.x: "Fortran that you ran", .y: "that I ran", .back: true, .expect: 0},
362 };
363 RunSearchTests(
364 "INDEX", tests, std::get<SearchFunction<TypeParam>>(functions));
365}
366
367TYPED_TEST(SearchTests, ScanTests) {
368 static SearchFunctions functions{RTNAME(Scan1), RTNAME(Scan2), RTNAME(Scan4)};
369 static std::vector<SearchTestCase> tests{
370 {.x: "abc", .y: "abc", .back: false, .expect: 1},
371 {.x: "abc", .y: "abc", .back: true, .expect: 3},
372 {.x: "abc", .y: "cde", .back: false, .expect: 3},
373 {.x: "abc", .y: "cde", .back: true, .expect: 3},
374 {.x: "abc", .y: "x", .back: false, .expect: 0},
375 {.x: "", .y: "x", .back: false, .expect: 0},
376 };
377 RunSearchTests("SCAN", tests, std::get<SearchFunction<TypeParam>>(functions));
378}
379
380TYPED_TEST(SearchTests, VerifyTests) {
381 static SearchFunctions functions{
382 RTNAME(Verify1), RTNAME(Verify2), RTNAME(Verify4)};
383 static std::vector<SearchTestCase> tests{
384 {.x: "abc", .y: "abc", .back: false, .expect: 0},
385 {.x: "abc", .y: "abc", .back: true, .expect: 0},
386 {.x: "abc", .y: "cde", .back: false, .expect: 1},
387 {.x: "abc", .y: "cde", .back: true, .expect: 2},
388 {.x: "abc", .y: "x", .back: false, .expect: 1},
389 {.x: "", .y: "x", .back: false, .expect: 0},
390 };
391 RunSearchTests(
392 "VERIFY", tests, std::get<SearchFunction<TypeParam>>(functions));
393}
394
395// Test REPEAT()
396template <typename CHAR> struct RepeatTests : public ::testing::Test {};
397TYPED_TEST_SUITE(RepeatTests, CharacterTypes, );
398
399struct RepeatTestCase {
400 std::size_t ncopies;
401 const char *input, *output;
402};
403
404template <typename CHAR>
405void RunRepeatTest(
406 std::size_t ncopies, const char *inputRaw, const char *outputRaw) {
407 OwningPtr<Descriptor> input{CreateDescriptor<CHAR>({}, {inputRaw})};
408 ASSERT_NE(input, nullptr);
409 ASSERT_TRUE(input->IsAllocated());
410
411 StaticDescriptor<1> outputStaticDescriptor;
412 Descriptor &output{outputStaticDescriptor.descriptor()};
413
414 RTNAME(Repeat)(output, *input, ncopies);
415 std::basic_string<CHAR> got{
416 output.OffsetElement<CHAR>(), output.ElementBytes() / sizeof(CHAR)};
417 std::basic_string<CHAR> expect{outputRaw, outputRaw + std::strlen(s: outputRaw)};
418 ASSERT_EQ(got, expect) << "'" << inputRaw << "' * " << ncopies
419 << "' for CHARACTER(kind=" << sizeof(CHAR) << ")";
420}
421
422TYPED_TEST(RepeatTests, Repeat) {
423 static std::vector<RepeatTestCase> testcases{
424 {.ncopies: 1, .input: "just one copy", .output: "just one copy"},
425 {.ncopies: 5, .input: "copy.", .output: "copy.copy.copy.copy.copy."},
426 {.ncopies: 0, .input: "no copies", .output: ""},
427 };
428
429 for (const auto &t : testcases) {
430 RunRepeatTest<TypeParam>(t.ncopies, t.input, t.output);
431 }
432}
433

source code of flang-rt/unittests/Runtime/CharacterTest.cpp