433 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9// Basic sanity tests of CHARACTER API; exhaustive testing will be done10// 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 from25// 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(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 bugs35 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(buffer, 0, sizeof buffer);59 60 // Ensure appending characters does not overrun the limit61 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 still66 // 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(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()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(inputRaw)};110 std::basic_string<CHAR> expect{outputRaw, outputRaw + std::strlen(outputRaw)};111 ASSERT_EQ(got, expect) << which << "('" << inputRaw112 << "') for CHARACTER(kind=" << sizeof(CHAR) << ")";113}114 115TYPED_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 128TYPED_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 functions143//------------------------------------------------------------------------------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 the153// 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 parameterized161template <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("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 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(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 " << cmp211 << ", should be " << expect << '\n';212 213 // Perform the same test with the parameters reversed and the difference214 // negated215 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 " << cmp225 << ", 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(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 kind331 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.back337 << ") for CHARACTER(kind=" << sizeof(CHAR) << "): got " << got338 << ", 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 {"", "", 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 {"aAA", "A", false, 2},358 {"Fortran that I ran", "that I ran", false, 9},359 {"Fortran that I ran", "that I ran", true, 9},360 {"Fortran that you ran", "that I ran", false, 0},361 {"Fortran that you ran", "that I ran", true, 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 {"abc", "abc", false, 1},371 {"abc", "abc", true, 3},372 {"abc", "cde", false, 3},373 {"abc", "cde", true, 3},374 {"abc", "x", false, 0},375 {"", "x", false, 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 {"abc", "abc", false, 0},385 {"abc", "abc", true, 0},386 {"abc", "cde", false, 1},387 {"abc", "cde", true, 2},388 {"abc", "x", false, 1},389 {"", "x", false, 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(outputRaw)};418 ASSERT_EQ(got, expect) << "'" << inputRaw << "' * " << ncopies419 << "' for CHARACTER(kind=" << sizeof(CHAR) << ")";420}421 422TYPED_TEST(RepeatTests, Repeat) {423 static std::vector<RepeatTestCase> testcases{424 {1, "just one copy", "just one copy"},425 {5, "copy.", "copy.copy.copy.copy.copy."},426 {0, "no copies", ""},427 };428 429 for (const auto &t : testcases) {430 RunRepeatTest<TypeParam>(t.ncopies, t.input, t.output);431 }432}433