blob: e45bc81739b1664bac260bd7fafbbde4a22c7839 [file] [log] [blame]
Asher Mancinelli4abba772021-04-19 07:33:25 -07001//===-- flang/unittests/RuntimeGTest/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/format-implementation.h"
11#include "../runtime/io-error.h"
12#include <string>
13#include <tuple>
14#include <vector>
15
16using namespace Fortran::runtime;
17using namespace Fortran::runtime::io;
18using namespace std::literals::string_literals;
19
20using ResultsTy = std::vector<std::string>;
21
22// A test harness context for testing FormatControl
23class TestFormatContext : public IoErrorHandler {
24public:
25 using CharType = char;
26 TestFormatContext() : IoErrorHandler{"format.cpp", 1} {}
27 bool Emit(const char *, std::size_t);
28 bool Emit(const char16_t *, std::size_t);
29 bool Emit(const char32_t *, std::size_t);
30 bool AdvanceRecord(int = 1);
31 void HandleRelativePosition(std::int64_t);
32 void HandleAbsolutePosition(std::int64_t);
33 void Report(const DataEdit &);
34 ResultsTy results;
35 MutableModes &mutableModes() { return mutableModes_; }
36
37private:
38 MutableModes mutableModes_;
39};
40
41bool TestFormatContext::Emit(const char *s, std::size_t len) {
42 std::string str{s, len};
43 results.push_back("'"s + str + '\'');
44 return true;
45}
46bool TestFormatContext::Emit(const char16_t *, std::size_t) {
47 Crash("TestFormatContext::Emit(const char16_t *) called");
48 return false;
49}
50bool TestFormatContext::Emit(const char32_t *, std::size_t) {
51 Crash("TestFormatContext::Emit(const char32_t *) called");
52 return false;
53}
54
55bool TestFormatContext::AdvanceRecord(int n) {
56 while (n-- > 0) {
57 results.emplace_back("/");
58 }
59 return true;
60}
61
62void TestFormatContext::HandleAbsolutePosition(std::int64_t n) {
63 results.push_back("T"s + std::to_string(n));
64}
65
66void TestFormatContext::HandleRelativePosition(std::int64_t n) {
67 if (n < 0) {
68 results.push_back("TL"s + std::to_string(-n));
69 } else {
70 results.push_back(std::to_string(n) + 'X');
71 }
72}
73
74void TestFormatContext::Report(const DataEdit &edit) {
75 std::string str{edit.descriptor};
76 if (edit.repeat != 1) {
77 str = std::to_string(edit.repeat) + '*' + str;
78 }
79 if (edit.variation) {
80 str += edit.variation;
81 }
82 if (edit.width) {
83 str += std::to_string(*edit.width);
84 }
85 if (edit.digits) {
86 str += "."s + std::to_string(*edit.digits);
87 }
88 if (edit.expoDigits) {
89 str += "E"s + std::to_string(*edit.expoDigits);
90 }
91 // modes?
92 results.push_back(str);
93}
94
95struct FormatTests : public CrashHandlerFixture {};
96
97TEST(FormatTests, FormatStringTraversal) {
98
99 using ParamsTy = std::tuple<int, const char *, ResultsTy, int>;
100
101 static const std::vector<ParamsTy> params{
102 {1, "('PI=',F9.7)", ResultsTy{"'PI='", "F9.7"}, 1},
103 {1, "(3HPI=F9.7)", ResultsTy{"'PI='", "F9.7"}, 1},
104 {1, "(3HPI=/F9.7)", ResultsTy{"'PI='", "/", "F9.7"}, 1},
105 {2, "('PI=',F9.7)", ResultsTy{"'PI='", "F9.7", "/", "'PI='", "F9.7"}, 1},
106 {2, "(2('PI=',F9.7),'done')",
107 ResultsTy{"'PI='", "F9.7", "'PI='", "F9.7", "'done'"}, 1},
108 {2, "(3('PI=',F9.7,:),'tooFar')",
109 ResultsTy{"'PI='", "F9.7", "'PI='", "F9.7"}, 1},
110 {2, "(*('PI=',F9.7,:),'tooFar')",
111 ResultsTy{"'PI='", "F9.7", "'PI='", "F9.7"}, 1},
112 {1, "(3F9.7)", ResultsTy{"2*F9.7"}, 2},
113 };
114
115 for (const auto &[n, format, expect, repeat] : params) {
116 TestFormatContext context;
117 FormatControl<decltype(context)> control{
118 context, format, std::strlen(format)};
119
120 for (auto i{0}; i < n; i++) {
121 context.Report(/*edit=*/control.GetNextDataEdit(context, repeat));
122 }
123 control.Finish(context);
124
125 auto iostat{context.GetIoStat()};
126 ASSERT_EQ(iostat, 0) << "Expected iostat == 0, but GetIoStat() == "
127 << iostat;
128
129 // Create strings of the expected/actual results for printing errors
130 std::string allExpectedResults{""}, allActualResults{""};
131 for (const auto &res : context.results) {
132 allActualResults += " "s + res;
133 }
134 for (const auto &res : expect) {
135 allExpectedResults += " "s + res;
136 }
137
138 const auto &results = context.results;
139 ASSERT_EQ(expect, results) << "Expected '" << allExpectedResults
140 << "' but got '" << allActualResults << "'";
141 }
142}
143
144struct InvalidFormatFailure : CrashHandlerFixture {};
145
146TEST(InvalidFormatFailure, ParenMismatch) {
147 static constexpr const char *format{"("};
148 static constexpr int repeat{1};
149
150 TestFormatContext context;
151 FormatControl<decltype(context)> control{
152 context, format, std::strlen(format)};
153
154 ASSERT_DEATH(
155 context.Report(/*edit=*/control.GetNextDataEdit(context, repeat)),
Michael Kruse0112f6a2021-06-10 11:23:53 -0500156 R"(FORMAT missing at least one '\)')");
Asher Mancinelli4abba772021-04-19 07:33:25 -0700157}
158
159TEST(InvalidFormatFailure, MissingPrecision) {
160 static constexpr const char *format{"(F9.)"};
161 static constexpr int repeat{1};
162
163 TestFormatContext context;
164 FormatControl<decltype(context)> control{
165 context, format, std::strlen(format)};
166
167 ASSERT_DEATH(
168 context.Report(/*edit=*/control.GetNextDataEdit(context, repeat)),
Michael Kruse0112f6a2021-06-10 11:23:53 -0500169 R"(Invalid FORMAT: integer expected at '\)')");
Asher Mancinelli4abba772021-04-19 07:33:25 -0700170}
171
172TEST(InvalidFormatFailure, MissingFormatWidth) {
173 static constexpr const char *format{"(F.9)"};
174 static constexpr int repeat{1};
175
176 TestFormatContext context;
177 FormatControl<decltype(context)> control{
178 context, format, std::strlen(format)};
179
180 ASSERT_DEATH(
181 context.Report(/*edit=*/control.GetNextDataEdit(context, repeat)),
182 "Invalid FORMAT: integer expected at '.'");
183}