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(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 std::make_tuple("ab\xff", "ab ", 3, 2, 1), 175 std::make_tuple("ab ", "ab\xff", 2, 3, -1), 176 }, 177 { 178 std::make_tuple(u"abc", u"abc", 3, 3, 0), 179 std::make_tuple(u"abc", u"def", 3, 3, -1), 180 std::make_tuple(u"ab ", u"abc", 3, 2, 0), 181 std::make_tuple(u"abc", u"abc", 2, 3, -1), 182 }, 183 { 184 std::make_tuple(U"abc", U"abc", 3, 3, 0), 185 std::make_tuple(U"abc", U"def", 3, 3, -1), 186 std::make_tuple(U"ab ", U"abc", 3, 2, 0), 187 std::make_tuple(U"abc", U"abc", 2, 3, -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(buf, 0, sizeof buf); 207 std::memcpy(buf[0], x, xBytes); 208 std::memcpy(buf[1], y, 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(buf, 0, sizeof buf); 221 std::memcpy(buf[0], x, xBytes); 222 std::memcpy(buf[1], y, 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(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 268 template <typename CHAR> struct ExtremumTests : public ::testing::Test {}; 269 TYPED_TEST_SUITE(ExtremumTests, CharacterTypes, ); 270 271 TYPED_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 281 TYPED_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 290 template <typename CHAR> 291 void 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 305 TYPED_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 312 template <typename CHAR> 313 using SearchFunction = std::function<std::size_t( 314 const CHAR *, std::size_t, const CHAR *, std::size_t, bool)>; 315 template <template <typename> class FUNC> 316 using CharTypedFunctions = 317 std::tuple<FUNC<char>, FUNC<char16_t>, FUNC<char32_t>>; 318 using SearchFunctions = CharTypedFunctions<SearchFunction>; 319 struct SearchTestCase { 320 const char *x, *y; 321 bool back; 322 std::size_t expect; 323 }; 324 325 template <typename CHAR> 326 void 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(t.x)}, yLen{std::strlen(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 342 template <typename CHAR> struct SearchTests : public ::testing::Test {}; 343 TYPED_TEST_SUITE(SearchTests, CharacterTypes, ); 344 345 TYPED_TEST(SearchTests, IndexTests) { 346 static SearchFunctions functions{ 347 RTNAME(Index1), RTNAME(Index2), RTNAME(Index4)}; 348 static std::vector<SearchTestCase> tests{ 349 {"", "", false, 1}, 350 {"", "", true, 1}, 351 {"a", "", false, 1}, 352 {"a", "", true, 2}, 353 {"", "a", false, 0}, 354 {"", "a", true, 0}, 355 {"aa", "a", false, 1}, 356 {"aa", "a", true, 2}, 357 {"Fortran that I ran", "that I ran", false, 9}, 358 {"Fortran that I ran", "that I ran", true, 9}, 359 {"Fortran that you ran", "that I ran", false, 0}, 360 {"Fortran that you ran", "that I ran", true, 0}, 361 }; 362 RunSearchTests( 363 "INDEX", tests, std::get<SearchFunction<TypeParam>>(functions)); 364 } 365 366 TYPED_TEST(SearchTests, ScanTests) { 367 static SearchFunctions functions{RTNAME(Scan1), RTNAME(Scan2), RTNAME(Scan4)}; 368 static std::vector<SearchTestCase> tests{ 369 {"abc", "abc", false, 1}, 370 {"abc", "abc", true, 3}, 371 {"abc", "cde", false, 3}, 372 {"abc", "cde", true, 3}, 373 {"abc", "x", false, 0}, 374 {"", "x", false, 0}, 375 }; 376 RunSearchTests("SCAN", tests, std::get<SearchFunction<TypeParam>>(functions)); 377 } 378 379 TYPED_TEST(SearchTests, VerifyTests) { 380 static SearchFunctions functions{ 381 RTNAME(Verify1), RTNAME(Verify2), RTNAME(Verify4)}; 382 static std::vector<SearchTestCase> tests{ 383 {"abc", "abc", false, 0}, 384 {"abc", "abc", true, 0}, 385 {"abc", "cde", false, 1}, 386 {"abc", "cde", true, 2}, 387 {"abc", "x", false, 1}, 388 {"", "x", false, 0}, 389 }; 390 RunSearchTests( 391 "VERIFY", tests, std::get<SearchFunction<TypeParam>>(functions)); 392 } 393 394 // Test REPEAT() 395 template <typename CHAR> struct RepeatTests : public ::testing::Test {}; 396 TYPED_TEST_SUITE(RepeatTests, CharacterTypes, ); 397 398 struct RepeatTestCase { 399 std::size_t ncopies; 400 const char *input, *output; 401 }; 402 403 template <typename CHAR> 404 void RunRepeatTest( 405 std::size_t ncopies, const char *inputRaw, const char *outputRaw) { 406 OwningPtr<Descriptor> input{CreateDescriptor<CHAR>({}, {inputRaw})}; 407 ASSERT_NE(input, nullptr); 408 ASSERT_TRUE(input->IsAllocated()); 409 410 StaticDescriptor<1> outputStaticDescriptor; 411 Descriptor &output{outputStaticDescriptor.descriptor()}; 412 413 RTNAME(Repeat)(output, *input, ncopies); 414 std::basic_string<CHAR> got{ 415 output.OffsetElement<CHAR>(), output.ElementBytes() / sizeof(CHAR)}; 416 std::basic_string<CHAR> expect{outputRaw, outputRaw + std::strlen(outputRaw)}; 417 ASSERT_EQ(got, expect) << "'" << inputRaw << "' * " << ncopies 418 << "' for CHARACTER(kind=" << sizeof(CHAR) << ")"; 419 } 420 421 TYPED_TEST(RepeatTests, Repeat) { 422 static std::vector<RepeatTestCase> testcases{ 423 {1, "just one copy", "just one copy"}, 424 {5, "copy.", "copy.copy.copy.copy.copy."}, 425 {0, "no copies", ""}, 426 }; 427 428 for (const auto &t : testcases) { 429 RunRepeatTest<TypeParam>(t.ncopies, t.input, t.output); 430 } 431 } 432