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>
CreateDescriptor(const std::vector<SubscriptValue> & shape,const std::vector<const char * > & raw_strings)27 OwningPtr<Descriptor> CreateDescriptor(const std::vector<SubscriptValue> &shape,
28     const std::vector<const char *> &raw_strings) {
29   std::size_t length{std::strlen(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 
TEST(CharacterTests,AppendAndPad)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(buffer, 0, 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 
TEST(CharacterTests,CharacterAppend1Overrun)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(buffer, 0, 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>
RunAdjustLRTest(const char * which,const std::function<void (Descriptor &,const Descriptor &,const char *,int)> & adjust,const char * inputRaw,const char * outputRaw)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(inputRaw)};
110   std::basic_string<CHAR> expect{outputRaw, outputRaw + std::strlen(outputRaw)};
111   ASSERT_EQ(got, expect) << which << "('" << inputRaw
112                          << "') for CHARACTER(kind=" << sizeof(CHAR) << ")";
113 }
114 
TYPED_TEST(AdjustLRTests,AdjustL)115 TYPED_TEST(AdjustLRTests, AdjustL) {
116   static std::vector<AdjustLRTestCase> testcases{
117       {"     where should the spaces be?", "where should the spaces be?     "},
118       {"   leading and trailing whitespaces   ",
119           "leading and trailing whitespaces      "},
120       {"shouldn't change", "shouldn't change"},
121   };
122 
123   for (const auto &t : testcases) {
124     RunAdjustLRTest<TypeParam>("Adjustl", RTNAME(Adjustl), t.input, t.output);
125   }
126 }
127 
TYPED_TEST(AdjustLRTests,AdjustR)128 TYPED_TEST(AdjustLRTests, AdjustR) {
129   static std::vector<AdjustLRTestCase> testcases{
130       {"where should the spaces be?   ", "   where should the spaces be?"},
131       {" leading and trailing whitespaces ",
132           "  leading and trailing whitespaces"},
133       {"shouldn't change", "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("abc", "abc", 3, 3, 0),
171         std::make_tuple("abc", "def", 3, 3, -1),
172         std::make_tuple("ab ", "abc", 3, 2, 0),
173         std::make_tuple("abc", "abc", 2, 3, -1),
174     },
175     {
176         std::make_tuple(u"abc", u"abc", 3, 3, 0),
177         std::make_tuple(u"abc", u"def", 3, 3, -1),
178         std::make_tuple(u"ab ", u"abc", 3, 2, 0),
179         std::make_tuple(u"abc", u"abc", 2, 3, -1),
180     },
181     {
182         std::make_tuple(U"abc", U"abc", 3, 3, 0),
183         std::make_tuple(U"abc", U"def", 3, 3, -1),
184         std::make_tuple(U"ab ", U"abc", 3, 2, 0),
185         std::make_tuple(U"abc", U"abc", 2, 3, -1),
186     }};
187 
188 template <typename CHAR>
189 struct CharacterComparisonTests : public ::testing::Test {
CharacterComparisonTestsCharacterComparisonTests190   CharacterComparisonTests()
191       : parameters{std::get<ComparisonParametersTy<CHAR>>(comparisonTestCases)},
192         characterComparisonFunc{
193             std::get<ComparisonFuncTy<CHAR>>(comparisonFuncs)} {}
194   ComparisonParametersTy<CHAR> parameters;
195   ComparisonFuncTy<CHAR> characterComparisonFunc;
196 };
197 
198 TYPED_TEST_SUITE(CharacterComparisonTests, CharacterTypes, );
199 
TYPED_TEST(CharacterComparisonTests,CompareCharacters)200 TYPED_TEST(CharacterComparisonTests, CompareCharacters) {
201   for (auto &[x, y, xBytes, yBytes, expect] : this->parameters) {
202     int cmp{this->characterComparisonFunc(x, y, xBytes, yBytes)};
203     TypeParam buf[2][8];
204     std::memset(buf, 0, sizeof buf);
205     std::memcpy(buf[0], x, xBytes);
206     std::memcpy(buf[1], y, yBytes);
207     ASSERT_EQ(cmp, expect) << "compare '" << x << "'(" << xBytes << ") to '"
208                            << y << "'(" << yBytes << "), got " << cmp
209                            << ", should be " << expect << '\n';
210 
211     // Perform the same test with the parameters reversed and the difference
212     // negated
213     std::swap(x, y);
214     std::swap(xBytes, yBytes);
215     expect = -expect;
216 
217     cmp = this->characterComparisonFunc(x, y, xBytes, yBytes);
218     std::memset(buf, 0, sizeof buf);
219     std::memcpy(buf[0], x, xBytes);
220     std::memcpy(buf[1], y, yBytes);
221     ASSERT_EQ(cmp, expect) << "compare '" << x << "'(" << xBytes << ") to '"
222                            << y << "'(" << yBytes << "'), got " << cmp
223                            << ", should be " << expect << '\n';
224   }
225 }
226 
227 // Test MIN() and MAX()
228 struct ExtremumTestCase {
229   std::vector<SubscriptValue> shape; // Empty = scalar, non-empty = array.
230   std::vector<const char *> x, y, expect;
231 };
232 
233 template <typename CHAR>
RunExtremumTests(const char * which,std::function<void (Descriptor &,const Descriptor &,const char *,int)> function,const std::vector<ExtremumTestCase> & testCases)234 void RunExtremumTests(const char *which,
235     std::function<void(Descriptor &, const Descriptor &, const char *, int)>
236         function,
237     const std::vector<ExtremumTestCase> &testCases) {
238   std::stringstream traceMessage;
239   traceMessage << which << " for CHARACTER(kind=" << sizeof(CHAR) << ")";
240   SCOPED_TRACE(traceMessage.str());
241 
242   for (const auto &t : testCases) {
243     OwningPtr<Descriptor> x = CreateDescriptor<CHAR>(t.shape, t.x);
244     OwningPtr<Descriptor> y = CreateDescriptor<CHAR>(t.shape, t.y);
245 
246     ASSERT_NE(x, nullptr);
247     ASSERT_TRUE(x->IsAllocated());
248     ASSERT_NE(y, nullptr);
249     ASSERT_TRUE(y->IsAllocated());
250     function(*x, *y, __FILE__, __LINE__);
251 
252     std::size_t length = x->ElementBytes() / sizeof(CHAR);
253     for (std::size_t i = 0; i < t.x.size(); ++i) {
254       std::basic_string<CHAR> got{
255           x->OffsetElement<CHAR>(i * x->ElementBytes()), length};
256       std::basic_string<CHAR> expect{
257           t.expect[i], t.expect[i] + std::strlen(t.expect[i])};
258       EXPECT_EQ(expect, got) << "inputs: '" << t.x[i] << "','" << t.y[i] << "'";
259     }
260   }
261 }
262 
263 template <typename CHAR> struct ExtremumTests : public ::testing::Test {};
264 TYPED_TEST_SUITE(ExtremumTests, CharacterTypes, );
265 
TYPED_TEST(ExtremumTests,MinTests)266 TYPED_TEST(ExtremumTests, MinTests) {
267   static std::vector<ExtremumTestCase> tests{{{}, {"a"}, {"z"}, {"a"}},
268       {{1}, {"zaaa"}, {"aa"}, {"aa  "}},
269       {{1, 1}, {"aaz"}, {"aaaaa"}, {"aaaaa"}},
270       {{2, 3}, {"a", "b", "c", "d", "E", "f"},
271           {"xa", "ya", "az", "dd", "Sz", "cc"},
272           {"a ", "b ", "az", "d ", "E ", "cc"}}};
273   RunExtremumTests<TypeParam>("MIN", RTNAME(CharacterMin), tests);
274 }
275 
TYPED_TEST(ExtremumTests,MaxTests)276 TYPED_TEST(ExtremumTests, MaxTests) {
277   static std::vector<ExtremumTestCase> tests{
278       {{}, {"a"}, {"z"}, {"z"}},
279       {{1}, {"zaa"}, {"aaaaa"}, {"zaa  "}},
280       {{1, 1, 1}, {"aaaaa"}, {"aazaa"}, {"aazaa"}},
281   };
282   RunExtremumTests<TypeParam>("MAX", RTNAME(CharacterMax), tests);
283 }
284 
285 template <typename CHAR>
RunAllocationTest(const char * xRaw,const char * yRaw)286 void RunAllocationTest(const char *xRaw, const char *yRaw) {
287   OwningPtr<Descriptor> x = CreateDescriptor<CHAR>({}, {xRaw});
288   OwningPtr<Descriptor> y = CreateDescriptor<CHAR>({}, {yRaw});
289 
290   ASSERT_NE(x, nullptr);
291   ASSERT_TRUE(x->IsAllocated());
292   ASSERT_NE(y, nullptr);
293   ASSERT_TRUE(y->IsAllocated());
294 
295   void *old = x->raw().base_addr;
296   RTNAME(CharacterMin)(*x, *y, __FILE__, __LINE__);
297   EXPECT_EQ(old, x->raw().base_addr);
298 }
299 
TYPED_TEST(ExtremumTests,NoReallocate)300 TYPED_TEST(ExtremumTests, NoReallocate) {
301   // Test that we don't reallocate if the accumulator is already large enough.
302   RunAllocationTest<TypeParam>("loooooong", "short");
303 }
304 
305 // Test search functions INDEX(), SCAN(), and VERIFY()
306 
307 template <typename CHAR>
308 using SearchFunction = std::function<std::size_t(
309     const CHAR *, std::size_t, const CHAR *, std::size_t, bool)>;
310 template <template <typename> class FUNC>
311 using CharTypedFunctions =
312     std::tuple<FUNC<char>, FUNC<char16_t>, FUNC<char32_t>>;
313 using SearchFunctions = CharTypedFunctions<SearchFunction>;
314 struct SearchTestCase {
315   const char *x, *y;
316   bool back;
317   std::size_t expect;
318 };
319 
320 template <typename CHAR>
RunSearchTests(const char * which,const std::vector<SearchTestCase> & testCases,const SearchFunction<CHAR> & function)321 void RunSearchTests(const char *which,
322     const std::vector<SearchTestCase> &testCases,
323     const SearchFunction<CHAR> &function) {
324   for (const auto &t : testCases) {
325     // Convert default character to desired kind
326     std::size_t xLen{std::strlen(t.x)}, yLen{std::strlen(t.y)};
327     std::basic_string<CHAR> x{t.x, t.x + xLen};
328     std::basic_string<CHAR> y{t.y, t.y + yLen};
329     auto got{function(x.data(), xLen, y.data(), yLen, t.back)};
330     ASSERT_EQ(got, t.expect)
331         << which << "('" << t.x << "','" << t.y << "',back=" << t.back
332         << ") for CHARACTER(kind=" << sizeof(CHAR) << "): got " << got
333         << ", expected " << t.expect;
334   }
335 }
336 
337 template <typename CHAR> struct SearchTests : public ::testing::Test {};
338 TYPED_TEST_SUITE(SearchTests, CharacterTypes, );
339 
TYPED_TEST(SearchTests,IndexTests)340 TYPED_TEST(SearchTests, IndexTests) {
341   static SearchFunctions functions{
342       RTNAME(Index1), RTNAME(Index2), RTNAME(Index4)};
343   static std::vector<SearchTestCase> tests{
344       {"", "", false, 1},
345       {"", "", true, 1},
346       {"a", "", false, 1},
347       {"a", "", true, 2},
348       {"", "a", false, 0},
349       {"", "a", true, 0},
350       {"aa", "a", false, 1},
351       {"aa", "a", true, 2},
352       {"Fortran that I ran", "that I ran", false, 9},
353       {"Fortran that I ran", "that I ran", true, 9},
354       {"Fortran that you ran", "that I ran", false, 0},
355       {"Fortran that you ran", "that I ran", true, 0},
356   };
357   RunSearchTests(
358       "INDEX", tests, std::get<SearchFunction<TypeParam>>(functions));
359 }
360 
TYPED_TEST(SearchTests,ScanTests)361 TYPED_TEST(SearchTests, ScanTests) {
362   static SearchFunctions functions{RTNAME(Scan1), RTNAME(Scan2), RTNAME(Scan4)};
363   static std::vector<SearchTestCase> tests{
364       {"abc", "abc", false, 1},
365       {"abc", "abc", true, 3},
366       {"abc", "cde", false, 3},
367       {"abc", "cde", true, 3},
368       {"abc", "x", false, 0},
369       {"", "x", false, 0},
370   };
371   RunSearchTests("SCAN", tests, std::get<SearchFunction<TypeParam>>(functions));
372 }
373 
TYPED_TEST(SearchTests,VerifyTests)374 TYPED_TEST(SearchTests, VerifyTests) {
375   static SearchFunctions functions{
376       RTNAME(Verify1), RTNAME(Verify2), RTNAME(Verify4)};
377   static std::vector<SearchTestCase> tests{
378       {"abc", "abc", false, 0},
379       {"abc", "abc", true, 0},
380       {"abc", "cde", false, 1},
381       {"abc", "cde", true, 2},
382       {"abc", "x", false, 1},
383       {"", "x", false, 0},
384   };
385   RunSearchTests(
386       "VERIFY", tests, std::get<SearchFunction<TypeParam>>(functions));
387 }
388 
389 // Test REPEAT()
390 template <typename CHAR> struct RepeatTests : public ::testing::Test {};
391 TYPED_TEST_SUITE(RepeatTests, CharacterTypes, );
392 
393 struct RepeatTestCase {
394   std::size_t ncopies;
395   const char *input, *output;
396 };
397 
398 template <typename CHAR>
RunRepeatTest(std::size_t ncopies,const char * inputRaw,const char * outputRaw)399 void RunRepeatTest(
400     std::size_t ncopies, const char *inputRaw, const char *outputRaw) {
401   OwningPtr<Descriptor> input{CreateDescriptor<CHAR>({}, {inputRaw})};
402   ASSERT_NE(input, nullptr);
403   ASSERT_TRUE(input->IsAllocated());
404 
405   StaticDescriptor<1> outputStaticDescriptor;
406   Descriptor &output{outputStaticDescriptor.descriptor()};
407 
408   RTNAME(Repeat)(output, *input, ncopies);
409   std::basic_string<CHAR> got{
410       output.OffsetElement<CHAR>(), output.ElementBytes() / sizeof(CHAR)};
411   std::basic_string<CHAR> expect{outputRaw, outputRaw + std::strlen(outputRaw)};
412   ASSERT_EQ(got, expect) << "'" << inputRaw << "' * " << ncopies
413                          << "' for CHARACTER(kind=" << sizeof(CHAR) << ")";
414 }
415 
TYPED_TEST(RepeatTests,Repeat)416 TYPED_TEST(RepeatTests, Repeat) {
417   static std::vector<RepeatTestCase> testcases{
418       {1, "just one copy", "just one copy"},
419       {5, "copy.", "copy.copy.copy.copy.copy."},
420       {0, "no copies", ""},
421   };
422 
423   for (const auto &t : testcases) {
424     RunRepeatTest<TypeParam>(t.ncopies, t.input, t.output);
425   }
426 }
427