1 //===-- flang/unittests/Runtime/Format.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 #include "CrashHandlerFixture.h"
10 #include "../runtime/connection.h"
11 #include "../runtime/format-implementation.h"
12 #include "../runtime/io-error.h"
18 using namespace Fortran::runtime
;
19 using namespace Fortran::runtime::io
;
20 using namespace std::literals::string_literals
;
22 using ResultsTy
= std::vector
<std::string
>;
24 // A test harness context for testing FormatControl
25 class TestFormatContext
: public IoErrorHandler
{
27 using CharType
= char;
28 TestFormatContext() : IoErrorHandler
{"format.cpp", 1} {}
29 bool Emit(const char *, std::size_t, std::size_t = 0);
30 bool AdvanceRecord(int = 1);
31 void HandleRelativePosition(std::int64_t);
32 void HandleAbsolutePosition(std::int64_t);
33 void Report(const std::optional
<DataEdit
> &);
35 MutableModes
&mutableModes() { return mutableModes_
; }
36 ConnectionState
&GetConnectionState() { return connectionState_
; }
39 MutableModes mutableModes_
;
40 ConnectionState connectionState_
;
43 bool TestFormatContext::Emit(const char *s
, std::size_t len
, std::size_t) {
44 std::string str
{s
, len
};
45 results
.push_back("'"s
+ str
+ '\'');
49 bool TestFormatContext::AdvanceRecord(int n
) {
51 results
.emplace_back("/");
56 void TestFormatContext::HandleAbsolutePosition(std::int64_t n
) {
57 results
.push_back("T"s
+ std::to_string(n
));
60 void TestFormatContext::HandleRelativePosition(std::int64_t n
) {
62 results
.push_back("TL"s
+ std::to_string(-n
));
64 results
.push_back(std::to_string(n
) + 'X');
68 void TestFormatContext::Report(const std::optional
<DataEdit
> &edit
) {
70 std::string str
{edit
->descriptor
};
71 if (edit
->repeat
!= 1) {
72 str
= std::to_string(edit
->repeat
) + '*' + str
;
74 if (edit
->variation
) {
75 str
+= edit
->variation
;
78 str
+= std::to_string(*edit
->width
);
81 str
+= "."s
+ std::to_string(*edit
->digits
);
83 if (edit
->expoDigits
) {
84 str
+= "E"s
+ std::to_string(*edit
->expoDigits
);
87 results
.push_back(str
);
89 results
.push_back("(nullopt)"s
);
93 struct FormatTests
: public CrashHandlerFixture
{};
95 TEST(FormatTests
, FormatStringTraversal
) {
97 using ParamsTy
= std::tuple
<int, const char *, ResultsTy
, int>;
99 static const std::vector
<ParamsTy
> params
{
100 {1, "('PI=',F9.7)", ResultsTy
{"'PI='", "F9.7"}, 1},
101 {1, "(3HPI=F9.7)", ResultsTy
{"'PI='", "F9.7"}, 1},
102 {1, "(3HPI=/F9.7)", ResultsTy
{"'PI='", "/", "F9.7"}, 1},
103 {2, "('PI=',F9.7)", ResultsTy
{"'PI='", "F9.7", "/", "'PI='", "F9.7"}, 1},
104 {2, "(2('PI=',F9.7),'done')",
105 ResultsTy
{"'PI='", "F9.7", "'PI='", "F9.7", "'done'"}, 1},
106 {2, "(3('PI=',F9.7,:),'tooFar')",
107 ResultsTy
{"'PI='", "F9.7", "'PI='", "F9.7"}, 1},
108 {2, "(*('PI=',F9.7,:))", ResultsTy
{"'PI='", "F9.7", "'PI='", "F9.7"}, 1},
109 {1, "(3F9.7)", ResultsTy
{"2*F9.7"}, 2},
110 {9, "((I4,2(E10.1)))",
111 ResultsTy
{"I4", "E10.1", "E10.1", "/", "I4", "E10.1", "E10.1", "/",
112 "I4", "E10.1", "E10.1"},
114 {1, "(F)", ResultsTy
{"F"}, 1}, // missing 'w'
117 for (const auto &[n
, format
, expect
, repeat
] : params
) {
118 TestFormatContext context
;
119 FormatControl
<decltype(context
)> control
{
120 context
, format
, std::strlen(format
)};
122 for (auto i
{0}; i
< n
; i
++) {
123 context
.Report(/*edit=*/control
.GetNextDataEdit(context
, repeat
));
125 control
.Finish(context
);
127 auto iostat
{context
.GetIoStat()};
128 ASSERT_EQ(iostat
, 0) << "Expected iostat == 0, but GetIoStat() == "
131 // Create strings of the expected/actual results for printing errors
132 std::string allExpectedResults
{""}, allActualResults
{""};
133 for (const auto &res
: context
.results
) {
134 allActualResults
+= " "s
+ res
;
136 for (const auto &res
: expect
) {
137 allExpectedResults
+= " "s
+ res
;
140 const auto &results
= context
.results
;
141 ASSERT_EQ(expect
, results
) << "Expected '" << allExpectedResults
142 << "' but got '" << allActualResults
<< "'";
146 struct InvalidFormatFailure
: CrashHandlerFixture
{};
148 TEST(InvalidFormatFailure
, ParenMismatch
) {
149 static constexpr const char *format
{"("};
150 static constexpr int repeat
{1};
152 TestFormatContext context
;
153 FormatControl
<decltype(context
)> control
{
154 context
, format
, std::strlen(format
)};
157 context
.Report(/*edit=*/control
.GetNextDataEdit(context
, repeat
)),
158 R
"(FORMAT missing at least one '\)')");
161 TEST(InvalidFormatFailure
, MissingPrecision
) {
162 static constexpr const char *format
{"(F9.)"};
163 static constexpr int repeat
{1};
165 TestFormatContext context
;
166 FormatControl
<decltype(context
)> control
{
167 context
, format
, std::strlen(format
)};
170 context
.Report(/*edit=*/control
.GetNextDataEdit(context
, repeat
)),
171 R
"(Invalid FORMAT: integer expected at '\)')");
174 TEST(InvalidFormatFailure
, MissingFormatWidthWithDigits
) {
175 static constexpr const char *format
{"(F.9)"};
176 static constexpr int repeat
{1};
178 TestFormatContext context
;
179 FormatControl
<decltype(context
)> control
{
180 context
, format
, std::strlen(format
)};
183 context
.Report(/*edit=*/control
.GetNextDataEdit(context
, repeat
)),
184 "Invalid FORMAT: integer expected at '.'");