diff --git a/flang/unittests/CMakeLists.txt b/flang/unittests/CMakeLists.txt --- a/flang/unittests/CMakeLists.txt +++ b/flang/unittests/CMakeLists.txt @@ -40,6 +40,7 @@ add_subdirectory(Decimal) add_subdirectory(Evaluate) add_subdirectory(Runtime) +add_subdirectory(RuntimeGTest) if (FLANG_BUILD_NEW_DRIVER) add_subdirectory(Frontend) diff --git a/flang/unittests/Runtime/CMakeLists.txt b/flang/unittests/Runtime/CMakeLists.txt --- a/flang/unittests/Runtime/CMakeLists.txt +++ b/flang/unittests/Runtime/CMakeLists.txt @@ -46,8 +46,3 @@ RuntimeTesting FortranRuntime ) - -add_flang_nongtest_unittest(character - RuntimeTesting - FortranRuntime -) diff --git a/flang/unittests/Runtime/character.cpp b/flang/unittests/Runtime/character.cpp deleted file mode 100644 --- a/flang/unittests/Runtime/character.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Basic sanity tests of CHARACTER API; exhaustive testing will be done -// in Fortran. - -#include "../../runtime/character.h" -#include "testing.h" -#include - -using namespace Fortran::runtime; - -static void AppendAndPad(std::size_t limit) { - char x[8]; - std::size_t xLen{0}; - std::memset(x, 0, sizeof x); - xLen = RTNAME(CharacterAppend1)(x, limit, xLen, "abc", 3); - xLen = RTNAME(CharacterAppend1)(x, limit, xLen, "DE", 2); - RTNAME(CharacterPad1)(x, limit, xLen); - if (xLen > limit) { - Fail() << "xLen " << xLen << ">" << limit << '\n'; - } - if (x[limit]) { - Fail() << "x[" << limit << "]='" << x[limit] << "'\n"; - x[limit] = '\0'; - } - if (std::memcmp(x, "abcDE ", limit)) { - Fail() << "x = '" << x << "'\n"; - } -} - -static void TestCharCompare(const char *x, const char *y, std::size_t xBytes, - std::size_t yBytes, int expect) { - int cmp{RTNAME(CharacterCompareScalar1)(x, y, xBytes, yBytes)}; - if (cmp != expect) { - char buf[2][8]; - std::memset(buf, 0, sizeof buf); - std::memcpy(buf[0], x, xBytes); - std::memcpy(buf[1], y, yBytes); - Fail() << "compare '" << buf[0] << "'(" << xBytes << ") to '" << buf[1] - << "'(" << yBytes << "), got " << cmp << ", should be " << expect - << '\n'; - } -} - -static void Compare(const char *x, const char *y, std::size_t xBytes, - std::size_t yBytes, int expect) { - TestCharCompare(x, y, xBytes, yBytes, expect); - TestCharCompare(y, x, yBytes, xBytes, -expect); -} - -int main() { - StartTests(); - for (std::size_t j{0}; j < 8; ++j) { - AppendAndPad(j); - } - Compare("abc", "abc", 3, 3, 0); - Compare("abc", "def", 3, 3, -1); - Compare("ab ", "abc", 3, 2, 0); - Compare("abc", "abc", 2, 3, -1); - return EndTests(); -} diff --git a/flang/unittests/RuntimeGTest/CMakeLists.txt b/flang/unittests/RuntimeGTest/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/CMakeLists.txt @@ -0,0 +1,8 @@ +add_flang_unittest(FlangRuntimeTests + CharacterTest.cpp +) + +target_link_libraries(FlangRuntimeTests + PRIVATE + FortranRuntime +) diff --git a/flang/unittests/RuntimeGTest/CharacterTest.cpp b/flang/unittests/RuntimeGTest/CharacterTest.cpp new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/CharacterTest.cpp @@ -0,0 +1,145 @@ +//===-- flang/unittests/RuntimeGTest/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 +// +//===----------------------------------------------------------------------===// + +#include "../../runtime/character.h" +#include "gtest/gtest.h" +#include +#include +#include +#include + +using namespace Fortran::runtime; + +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; +} + +//------------------------------------------------------------------------------ +/// Tests and infrastructure for character comparison functions +//------------------------------------------------------------------------------ + +template +using CHARCOMP_FUNC = + std::function; + +// Maps character types to character comparison functions +template CHARCOMP_FUNC CreatecharacterComparisonFunc(); + +template <> CHARCOMP_FUNC CreatecharacterComparisonFunc() { + return RTNAME(CharacterCompareScalar1); +} + +template <> CHARCOMP_FUNC CreatecharacterComparisonFunc() { + return RTNAME(CharacterCompareScalar2); +} + +template <> CHARCOMP_FUNC CreatecharacterComparisonFunc() { + return RTNAME(CharacterCompareScalar4); +} + +// Types of _values_ over which tests are parameterized +template +using PARAMETER = std::tuple; + +template std::vector> CreateParameters(); + +template <> std::vector> CreateParameters() { + return { + 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), + }; +} + +template <> std::vector> CreateParameters() { + return { + 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 <> std::vector> CreateParameters() { + return { + 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(CreateParameters()), + characterComparisonFunc(CreatecharacterComparisonFunc()) {} + std::vector> parameters; + CHARCOMP_FUNC characterComparisonFunc; +}; + +using CHARACTERS = ::testing::Types; +TYPED_TEST_CASE(CharacterComparisonTests, CHARACTERS); + +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'; + } +}