xref: /llvm-project/flang/unittests/Runtime/Format.cpp (revision c13f7e17409b6fed29696c2bbe59efc3af3a408b)
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