//===-- flang/unittests/Runtime/CharacterTest.cpp ---------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // Basic sanity tests of CHARACTER API; exhaustive testing will be done // in Fortran. #include "flang/Runtime/character.h" #include "gtest/gtest.h" #include "flang/Runtime/descriptor.h" #include #include #include #include using namespace Fortran::runtime; using CharacterTypes = ::testing::Types; // Helper for creating, allocating and filling up a descriptor with data from // raw character literals, converted to the CHAR type used by the test. template OwningPtr CreateDescriptor(const std::vector &shape, const std::vector &raw_strings) { std::size_t length{std::strlen(raw_strings[0])}; OwningPtr descriptor{Descriptor::Create(sizeof(CHAR), length, nullptr, shape.size(), nullptr, CFI_attribute_allocatable)}; int rank{static_cast(shape.size())}; // Use a weird lower bound of 2 to flush out subscripting bugs for (int j{0}; j < rank; ++j) { descriptor->GetDimension(j).SetBounds(2, shape[j] + 1); } if (descriptor->Allocate() != 0) { return nullptr; } std::size_t offset = 0; for (const char *raw : raw_strings) { std::basic_string converted{raw, raw + length}; std::copy(converted.begin(), converted.end(), descriptor->OffsetElement(offset * length * sizeof(CHAR))); ++offset; } return descriptor; } TEST(CharacterTests, AppendAndPad) { static constexpr int limitMax{8}; static char buffer[limitMax]; static std::size_t offset{0}; for (std::size_t limit{0}; limit < limitMax; ++limit, offset = 0) { std::memset(buffer, 0, sizeof buffer); // Ensure appending characters does not overrun the limit offset = RTNAME(CharacterAppend1)(buffer, limit, offset, "abc", 3); offset = RTNAME(CharacterAppend1)(buffer, limit, offset, "DE", 2); ASSERT_LE(offset, limit) << "offset " << offset << ">" << limit; // Ensure whitespace padding does not overrun limit, the string is still // null-terminated, and string matches the expected value up to the limit. RTNAME(CharacterPad1)(buffer, limit, offset); EXPECT_EQ(buffer[limit], '\0') << "buffer[" << limit << "]='" << buffer[limit] << "'"; buffer[limit] = buffer[limit] ? '\0' : buffer[limit]; ASSERT_EQ(std::memcmp(buffer, "abcDE ", limit), 0) << "buffer = '" << buffer << "'"; } } TEST(CharacterTests, CharacterAppend1Overrun) { static constexpr int bufferSize{4}; static constexpr std::size_t limit{2}; static char buffer[bufferSize]; static std::size_t offset{0}; std::memset(buffer, 0, sizeof buffer); offset = RTNAME(CharacterAppend1)(buffer, limit, offset, "1234", bufferSize); ASSERT_EQ(offset, limit) << "CharacterAppend1 did not halt at limit = " << limit << ", but at offset = " << offset; } // Test ADJUSTL() and ADJUSTR() template struct AdjustLRTests : public ::testing::Test {}; TYPED_TEST_SUITE(AdjustLRTests, CharacterTypes, ); struct AdjustLRTestCase { const char *input, *output; }; template void RunAdjustLRTest(const char *which, const std::function &adjust, const char *inputRaw, const char *outputRaw) { OwningPtr input{CreateDescriptor({}, {inputRaw})}; ASSERT_NE(input, nullptr); ASSERT_TRUE(input->IsAllocated()); StaticDescriptor<1> outputStaticDescriptor; Descriptor &output{outputStaticDescriptor.descriptor()}; adjust(output, *input, /*sourceFile=*/nullptr, /*sourceLine=*/0); std::basic_string got{ output.OffsetElement(), std::strlen(inputRaw)}; std::basic_string expect{outputRaw, outputRaw + std::strlen(outputRaw)}; ASSERT_EQ(got, expect) << which << "('" << inputRaw << "') for CHARACTER(kind=" << sizeof(CHAR) << ")"; } TYPED_TEST(AdjustLRTests, AdjustL) { static std::vector testcases{ {" where should the spaces be?", "where should the spaces be? "}, {" leading and trailing whitespaces ", "leading and trailing whitespaces "}, {"shouldn't change", "shouldn't change"}, }; for (const auto &t : testcases) { RunAdjustLRTest("Adjustl", RTNAME(Adjustl), t.input, t.output); } } TYPED_TEST(AdjustLRTests, AdjustR) { static std::vector testcases{ {"where should the spaces be? ", " where should the spaces be?"}, {" leading and trailing whitespaces ", " leading and trailing whitespaces"}, {"shouldn't change", "shouldn't change"}, }; for (const auto &t : testcases) { RunAdjustLRTest("Adjustr", RTNAME(Adjustr), t.input, t.output); } } //------------------------------------------------------------------------------ /// Tests and infrastructure for character comparison functions //------------------------------------------------------------------------------ template using ComparisonFuncTy = std::function; using ComparisonFuncsTy = std::tuple, ComparisonFuncTy, ComparisonFuncTy>; // These comparison functions are the systems under test in the // CharacterComparisonTests test cases. static ComparisonFuncsTy comparisonFuncs{ RTNAME(CharacterCompareScalar1), RTNAME(CharacterCompareScalar2), RTNAME(CharacterCompareScalar4), }; // Types of _values_ over which comparison tests are parameterized template using ComparisonParametersTy = std::vector>; using ComparisonTestCasesTy = std::tuple, ComparisonParametersTy, ComparisonParametersTy>; static ComparisonTestCasesTy comparisonTestCases{ { std::make_tuple("abc", "abc", 3, 3, 0), std::make_tuple("abc", "def", 3, 3, -1), std::make_tuple("ab ", "abc", 3, 2, 0), std::make_tuple("abc", "abc", 2, 3, -1), }, { std::make_tuple(u"abc", u"abc", 3, 3, 0), std::make_tuple(u"abc", u"def", 3, 3, -1), std::make_tuple(u"ab ", u"abc", 3, 2, 0), std::make_tuple(u"abc", u"abc", 2, 3, -1), }, { std::make_tuple(U"abc", U"abc", 3, 3, 0), std::make_tuple(U"abc", U"def", 3, 3, -1), std::make_tuple(U"ab ", U"abc", 3, 2, 0), std::make_tuple(U"abc", U"abc", 2, 3, -1), }}; template struct CharacterComparisonTests : public ::testing::Test { CharacterComparisonTests() : parameters{std::get>(comparisonTestCases)}, characterComparisonFunc{ std::get>(comparisonFuncs)} {} ComparisonParametersTy parameters; ComparisonFuncTy characterComparisonFunc; }; TYPED_TEST_SUITE(CharacterComparisonTests, CharacterTypes, ); TYPED_TEST(CharacterComparisonTests, CompareCharacters) { for (auto &[x, y, xBytes, yBytes, expect] : this->parameters) { int cmp{this->characterComparisonFunc(x, y, xBytes, yBytes)}; TypeParam buf[2][8]; std::memset(buf, 0, sizeof buf); std::memcpy(buf[0], x, xBytes); std::memcpy(buf[1], y, yBytes); ASSERT_EQ(cmp, expect) << "compare '" << x << "'(" << xBytes << ") to '" << y << "'(" << yBytes << "), got " << cmp << ", should be " << expect << '\n'; // Perform the same test with the parameters reversed and the difference // negated std::swap(x, y); std::swap(xBytes, yBytes); expect = -expect; cmp = this->characterComparisonFunc(x, y, xBytes, yBytes); std::memset(buf, 0, sizeof buf); std::memcpy(buf[0], x, xBytes); std::memcpy(buf[1], y, yBytes); ASSERT_EQ(cmp, expect) << "compare '" << x << "'(" << xBytes << ") to '" << y << "'(" << yBytes << "'), got " << cmp << ", should be " << expect << '\n'; } } // Test MIN() and MAX() struct ExtremumTestCase { std::vector shape; // Empty = scalar, non-empty = array. std::vector x, y, expect; }; template void RunExtremumTests(const char *which, std::function function, const std::vector &testCases) { std::stringstream traceMessage; traceMessage << which << " for CHARACTER(kind=" << sizeof(CHAR) << ")"; SCOPED_TRACE(traceMessage.str()); for (const auto &t : testCases) { OwningPtr x = CreateDescriptor(t.shape, t.x); OwningPtr y = CreateDescriptor(t.shape, t.y); ASSERT_NE(x, nullptr); ASSERT_TRUE(x->IsAllocated()); ASSERT_NE(y, nullptr); ASSERT_TRUE(y->IsAllocated()); function(*x, *y, __FILE__, __LINE__); std::size_t length = x->ElementBytes() / sizeof(CHAR); for (std::size_t i = 0; i < t.x.size(); ++i) { std::basic_string got{ x->OffsetElement(i * x->ElementBytes()), length}; std::basic_string expect{ t.expect[i], t.expect[i] + std::strlen(t.expect[i])}; EXPECT_EQ(expect, got) << "inputs: '" << t.x[i] << "','" << t.y[i] << "'"; } } } template struct ExtremumTests : public ::testing::Test {}; TYPED_TEST_SUITE(ExtremumTests, CharacterTypes, ); TYPED_TEST(ExtremumTests, MinTests) { static std::vector tests{{{}, {"a"}, {"z"}, {"a"}}, {{1}, {"zaaa"}, {"aa"}, {"aa "}}, {{1, 1}, {"aaz"}, {"aaaaa"}, {"aaaaa"}}, {{2, 3}, {"a", "b", "c", "d", "E", "f"}, {"xa", "ya", "az", "dd", "Sz", "cc"}, {"a ", "b ", "az", "d ", "E ", "cc"}}}; RunExtremumTests("MIN", RTNAME(CharacterMin), tests); } TYPED_TEST(ExtremumTests, MaxTests) { static std::vector tests{ {{}, {"a"}, {"z"}, {"z"}}, {{1}, {"zaa"}, {"aaaaa"}, {"zaa "}}, {{1, 1, 1}, {"aaaaa"}, {"aazaa"}, {"aazaa"}}, }; RunExtremumTests("MAX", RTNAME(CharacterMax), tests); } template void RunAllocationTest(const char *xRaw, const char *yRaw) { OwningPtr x = CreateDescriptor({}, {xRaw}); OwningPtr y = CreateDescriptor({}, {yRaw}); ASSERT_NE(x, nullptr); ASSERT_TRUE(x->IsAllocated()); ASSERT_NE(y, nullptr); ASSERT_TRUE(y->IsAllocated()); void *old = x->raw().base_addr; RTNAME(CharacterMin)(*x, *y, __FILE__, __LINE__); EXPECT_EQ(old, x->raw().base_addr); } TYPED_TEST(ExtremumTests, NoReallocate) { // Test that we don't reallocate if the accumulator is already large enough. RunAllocationTest("loooooong", "short"); } // Test search functions INDEX(), SCAN(), and VERIFY() template using SearchFunction = std::function; template