1 //===-- flang/unittests/RuntimeGTest/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(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(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 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> 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 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 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 { 190 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 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> 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 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 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> 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 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> 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 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 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 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> 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 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