1 //===-- flang/unittests/Runtime/Format.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-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "CrashHandlerFixture.h"
10 #include "../runtime/connection.h"
11 #include "../runtime/format-implementation.h"
12 #include "../runtime/io-error.h"
13 #include <optional>
14 #include <string>
15 #include <tuple>
16 #include <vector>
17
18 using namespace Fortran::runtime;
19 using namespace Fortran::runtime::io;
20 using namespace std::literals::string_literals;
21
22 using ResultsTy = std::vector<std::string>;
23
24 // A test harness context for testing FormatControl
25 class TestFormatContext : public IoErrorHandler {
26 public:
27 using CharType = char;
TestFormatContext()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> &);
34 ResultsTy results;
mutableModes()35 MutableModes &mutableModes() { return mutableModes_; }
GetConnectionState()36 ConnectionState &GetConnectionState() { return connectionState_; }
37
38 private:
39 MutableModes mutableModes_;
40 ConnectionState connectionState_;
41 };
42
Emit(const char * s,std::size_t len,std::size_t)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 + '\'');
46 return true;
47 }
48
AdvanceRecord(int n)49 bool TestFormatContext::AdvanceRecord(int n) {
50 while (n-- > 0) {
51 results.emplace_back("/");
52 }
53 return true;
54 }
55
HandleAbsolutePosition(std::int64_t n)56 void TestFormatContext::HandleAbsolutePosition(std::int64_t n) {
57 results.push_back("T"s + std::to_string(n));
58 }
59
HandleRelativePosition(std::int64_t n)60 void TestFormatContext::HandleRelativePosition(std::int64_t n) {
61 if (n < 0) {
62 results.push_back("TL"s + std::to_string(-n));
63 } else {
64 results.push_back(std::to_string(n) + 'X');
65 }
66 }
67
Report(const std::optional<DataEdit> & edit)68 void TestFormatContext::Report(const std::optional<DataEdit> &edit) {
69 if (edit) {
70 std::string str{edit->descriptor};
71 if (edit->repeat != 1) {
72 str = std::to_string(edit->repeat) + '*' + str;
73 }
74 if (edit->variation) {
75 str += edit->variation;
76 }
77 if (edit->width) {
78 str += std::to_string(*edit->width);
79 }
80 if (edit->digits) {
81 str += "."s + std::to_string(*edit->digits);
82 }
83 if (edit->expoDigits) {
84 str += "E"s + std::to_string(*edit->expoDigits);
85 }
86 // modes?
87 results.push_back(str);
88 } else {
89 results.push_back("(nullopt)"s);
90 }
91 }
92
93 struct FormatTests : public CrashHandlerFixture {};
94
TEST(FormatTests,FormatStringTraversal)95 TEST(FormatTests, FormatStringTraversal) {
96
97 using ParamsTy = std::tuple<int, const char *, ResultsTy, int>;
98
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"},
113 1},
114 {1, "(F)", ResultsTy{"F"}, 1}, // missing 'w'
115 };
116
117 for (const auto &[n, format, expect, repeat] : params) {
118 TestFormatContext context;
119 FormatControl<decltype(context)> control{
120 context, format, std::strlen(format)};
121
122 for (auto i{0}; i < n; i++) {
123 context.Report(/*edit=*/control.GetNextDataEdit(context, repeat));
124 }
125 control.Finish(context);
126
127 auto iostat{context.GetIoStat()};
128 ASSERT_EQ(iostat, 0) << "Expected iostat == 0, but GetIoStat() == "
129 << iostat;
130
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;
135 }
136 for (const auto &res : expect) {
137 allExpectedResults += " "s + res;
138 }
139
140 const auto &results = context.results;
141 ASSERT_EQ(expect, results) << "Expected '" << allExpectedResults
142 << "' but got '" << allActualResults << "'";
143 }
144 }
145
146 struct InvalidFormatFailure : CrashHandlerFixture {};
147
TEST(InvalidFormatFailure,ParenMismatch)148 TEST(InvalidFormatFailure, ParenMismatch) {
149 static constexpr const char *format{"("};
150 static constexpr int repeat{1};
151
152 TestFormatContext context;
153 FormatControl<decltype(context)> control{
154 context, format, std::strlen(format)};
155
156 ASSERT_DEATH(
157 context.Report(/*edit=*/control.GetNextDataEdit(context, repeat)),
158 R"(FORMAT missing at least one '\)')");
159 }
160
TEST(InvalidFormatFailure,MissingPrecision)161 TEST(InvalidFormatFailure, MissingPrecision) {
162 static constexpr const char *format{"(F9.)"};
163 static constexpr int repeat{1};
164
165 TestFormatContext context;
166 FormatControl<decltype(context)> control{
167 context, format, std::strlen(format)};
168
169 ASSERT_DEATH(
170 context.Report(/*edit=*/control.GetNextDataEdit(context, repeat)),
171 R"(Invalid FORMAT: integer expected at '\)')");
172 }
173
TEST(InvalidFormatFailure,MissingFormatWidthWithDigits)174 TEST(InvalidFormatFailure, MissingFormatWidthWithDigits) {
175 static constexpr const char *format{"(F.9)"};
176 static constexpr int repeat{1};
177
178 TestFormatContext context;
179 FormatControl<decltype(context)> control{
180 context, format, std::strlen(format)};
181
182 ASSERT_DEATH(
183 context.Report(/*edit=*/control.GetNextDataEdit(context, repeat)),
184 "Invalid FORMAT: integer expected at '.'");
185 }
186