1 //===-- flang/unittests/Runtime/CharacterTest.cpp ---------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 // Basic sanity tests of CHARACTER API; exhaustive testing will be done
12 #include "flang/Runtime/character.h"
13 #include "gtest/gtest.h"
14 #include "flang/Runtime/descriptor.h"
20 using namespace Fortran::runtime
;
22 using CharacterTypes
= ::testing::Types
<char, char16_t
, char32_t
>;
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])};
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);
38 if (descriptor
->Allocate() != 0) {
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
)));
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
);
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
;
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
<< "'";
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
;
87 // Test ADJUSTL() and ADJUSTR()
88 template <typename CHAR
> struct AdjustLRTests
: public ::testing::Test
{};
89 TYPED_TEST_SUITE(AdjustLRTests
, CharacterTypes
, );
91 struct AdjustLRTestCase
{
92 const char *input
, *output
;
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());
104 StaticDescriptor
<1> outputStaticDescriptor
;
105 Descriptor
&output
{outputStaticDescriptor
.descriptor()};
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
) << ")";
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"},
123 for (const auto &t
: testcases
) {
124 RunAdjustLRTest
<TypeParam
>("Adjustl", RTNAME(Adjustl
), t
.input
, t
.output
);
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"},
136 for (const auto &t
: testcases
) {
137 RunAdjustLRTest
<TypeParam
>("Adjustr", RTNAME(Adjustr
), t
.input
, t
.output
);
141 //------------------------------------------------------------------------------
142 /// Tests and infrastructure for character comparison functions
143 //------------------------------------------------------------------------------
145 template <typename CHAR
>
146 using ComparisonFuncTy
=
147 std::function
<int(const CHAR
*, const CHAR
*, std::size_t, std::size_t)>;
149 using ComparisonFuncsTy
= std::tuple
<ComparisonFuncTy
<char>,
150 ComparisonFuncTy
<char16_t
>, ComparisonFuncTy
<char32_t
>>;
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
),
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>>;
165 using ComparisonTestCasesTy
= std::tuple
<ComparisonParametersTy
<char>,
166 ComparisonParametersTy
<char16_t
>, ComparisonParametersTy
<char32_t
>>;
168 static ComparisonTestCasesTy comparisonTestCases
{
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),
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),
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),
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
;
200 TYPED_TEST_SUITE(CharacterComparisonTests
, CharacterTypes
, );
202 TYPED_TEST(CharacterComparisonTests
, CompareCharacters
) {
203 for (auto &[x
, y
, xBytes
, yBytes
, expect
] : this->parameters
) {
204 int cmp
{this->characterComparisonFunc(x
, y
, xBytes
, yBytes
)};
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';
213 // Perform the same test with the parameters reversed and the difference
216 std::swap(xBytes
, yBytes
);
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';
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
;
235 template <typename CHAR
>
236 void RunExtremumTests(const char *which
,
237 std::function
<void(Descriptor
&, const Descriptor
&, const char *, int)>
239 const std::vector
<ExtremumTestCase
> &testCases
) {
240 std::stringstream traceMessage
;
241 traceMessage
<< which
<< " for CHARACTER(kind=" << sizeof(CHAR
) << ")";
242 SCOPED_TRACE(traceMessage
.str());
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
);
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__
);
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
] << "'";
265 template <typename CHAR
> struct ExtremumTests
: public ::testing::Test
{};
266 TYPED_TEST_SUITE(ExtremumTests
, CharacterTypes
, );
268 TYPED_TEST(ExtremumTests
, MinTests
) {
269 static std::vector
<ExtremumTestCase
> tests
{{{}, {"a"}, {"z"}, {"a"}},
270 {{1}, {"zaaa"}, {"aa"}, {"aa "}},
271 {{1, 1}, {"aaz"}, {"aaaaa"}, {"aaaaa"}},
272 {{2, 3}, {"a", "b", "c", "d", "E", "f"},
273 {"xa", "ya", "az", "dd", "Sz", "cc"},
274 {"a ", "b ", "az", "d ", "E ", "cc"}}};
275 RunExtremumTests
<TypeParam
>("MIN", RTNAME(CharacterMin
), tests
);
278 TYPED_TEST(ExtremumTests
, MaxTests
) {
279 static std::vector
<ExtremumTestCase
> tests
{
280 {{}, {"a"}, {"z"}, {"z"}},
281 {{1}, {"zaa"}, {"aaaaa"}, {"zaa "}},
282 {{1, 1, 1}, {"aaaaa"}, {"aazaa"}, {"aazaa"}},
284 RunExtremumTests
<TypeParam
>("MAX", RTNAME(CharacterMax
), tests
);
287 template <typename CHAR
>
288 void RunAllocationTest(const char *xRaw
, const char *yRaw
) {
289 OwningPtr
<Descriptor
> x
= CreateDescriptor
<CHAR
>({}, {xRaw
});
290 OwningPtr
<Descriptor
> y
= CreateDescriptor
<CHAR
>({}, {yRaw
});
292 ASSERT_NE(x
, nullptr);
293 ASSERT_TRUE(x
->IsAllocated());
294 ASSERT_NE(y
, nullptr);
295 ASSERT_TRUE(y
->IsAllocated());
297 void *old
= x
->raw().base_addr
;
298 RTNAME(CharacterMin
)(*x
, *y
, __FILE__
, __LINE__
);
299 EXPECT_EQ(old
, x
->raw().base_addr
);
302 TYPED_TEST(ExtremumTests
, NoReallocate
) {
303 // Test that we don't reallocate if the accumulator is already large enough.
304 RunAllocationTest
<TypeParam
>("loooooong", "short");
307 // Test search functions INDEX(), SCAN(), and VERIFY()
309 template <typename CHAR
>
310 using SearchFunction
= std::function
<std::size_t(
311 const CHAR
*, std::size_t, const CHAR
*, std::size_t, bool)>;
312 template <template <typename
> class FUNC
>
313 using CharTypedFunctions
=
314 std::tuple
<FUNC
<char>, FUNC
<char16_t
>, FUNC
<char32_t
>>;
315 using SearchFunctions
= CharTypedFunctions
<SearchFunction
>;
316 struct SearchTestCase
{
322 template <typename CHAR
>
323 void RunSearchTests(const char *which
,
324 const std::vector
<SearchTestCase
> &testCases
,
325 const SearchFunction
<CHAR
> &function
) {
326 for (const auto &t
: testCases
) {
327 // Convert default character to desired kind
328 std::size_t xLen
{std::strlen(t
.x
)}, yLen
{std::strlen(t
.y
)};
329 std::basic_string
<CHAR
> x
{t
.x
, t
.x
+ xLen
};
330 std::basic_string
<CHAR
> y
{t
.y
, t
.y
+ yLen
};
331 auto got
{function(x
.data(), xLen
, y
.data(), yLen
, t
.back
)};
332 ASSERT_EQ(got
, t
.expect
)
333 << which
<< "('" << t
.x
<< "','" << t
.y
<< "',back=" << t
.back
334 << ") for CHARACTER(kind=" << sizeof(CHAR
) << "): got " << got
335 << ", expected " << t
.expect
;
339 template <typename CHAR
> struct SearchTests
: public ::testing::Test
{};
340 TYPED_TEST_SUITE(SearchTests
, CharacterTypes
, );
342 TYPED_TEST(SearchTests
, IndexTests
) {
343 static SearchFunctions functions
{
344 RTNAME(Index1
), RTNAME(Index2
), RTNAME(Index4
)};
345 static std::vector
<SearchTestCase
> tests
{
352 {"aa", "a", false, 1},
353 {"aa", "a", true, 2},
354 {"Fortran that I ran", "that I ran", false, 9},
355 {"Fortran that I ran", "that I ran", true, 9},
356 {"Fortran that you ran", "that I ran", false, 0},
357 {"Fortran that you ran", "that I ran", true, 0},
360 "INDEX", tests
, std::get
<SearchFunction
<TypeParam
>>(functions
));
363 TYPED_TEST(SearchTests
, ScanTests
) {
364 static SearchFunctions functions
{RTNAME(Scan1
), RTNAME(Scan2
), RTNAME(Scan4
)};
365 static std::vector
<SearchTestCase
> tests
{
366 {"abc", "abc", false, 1},
367 {"abc", "abc", true, 3},
368 {"abc", "cde", false, 3},
369 {"abc", "cde", true, 3},
370 {"abc", "x", false, 0},
373 RunSearchTests("SCAN", tests
, std::get
<SearchFunction
<TypeParam
>>(functions
));
376 TYPED_TEST(SearchTests
, VerifyTests
) {
377 static SearchFunctions functions
{
378 RTNAME(Verify1
), RTNAME(Verify2
), RTNAME(Verify4
)};
379 static std::vector
<SearchTestCase
> tests
{
380 {"abc", "abc", false, 0},
381 {"abc", "abc", true, 0},
382 {"abc", "cde", false, 1},
383 {"abc", "cde", true, 2},
384 {"abc", "x", false, 1},
388 "VERIFY", tests
, std::get
<SearchFunction
<TypeParam
>>(functions
));
392 template <typename CHAR
> struct RepeatTests
: public ::testing::Test
{};
393 TYPED_TEST_SUITE(RepeatTests
, CharacterTypes
, );
395 struct RepeatTestCase
{
397 const char *input
, *output
;
400 template <typename CHAR
>
402 std::size_t ncopies
, const char *inputRaw
, const char *outputRaw
) {
403 OwningPtr
<Descriptor
> input
{CreateDescriptor
<CHAR
>({}, {inputRaw
})};
404 ASSERT_NE(input
, nullptr);
405 ASSERT_TRUE(input
->IsAllocated());
407 StaticDescriptor
<1> outputStaticDescriptor
;
408 Descriptor
&output
{outputStaticDescriptor
.descriptor()};
410 RTNAME(Repeat
)(output
, *input
, ncopies
);
411 std::basic_string
<CHAR
> got
{
412 output
.OffsetElement
<CHAR
>(), output
.ElementBytes() / sizeof(CHAR
)};
413 std::basic_string
<CHAR
> expect
{outputRaw
, outputRaw
+ std::strlen(outputRaw
)};
414 ASSERT_EQ(got
, expect
) << "'" << inputRaw
<< "' * " << ncopies
415 << "' for CHARACTER(kind=" << sizeof(CHAR
) << ")";
418 TYPED_TEST(RepeatTests
, Repeat
) {
419 static std::vector
<RepeatTestCase
> testcases
{
420 {1, "just one copy", "just one copy"},
421 {5, "copy.", "copy.copy.copy.copy.copy."},
422 {0, "no copies", ""},
425 for (const auto &t
: testcases
) {
426 RunRepeatTest
<TypeParam
>(t
.ncopies
, t
.input
, t
.output
);