1 //===-- 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 
16 using namespace Fortran::runtime;
17 using namespace Fortran::runtime::io;
18 using namespace std::literals::string_literals;
19 
20 using ResultsTy = std::vector<std::string>;
21 
22 // A test harness context for testing FormatControl
23 class TestFormatContext : public IoErrorHandler {
24 public:
25   using CharType = char;
TestFormatContext()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;
mutableModes()35   MutableModes &mutableModes() { return mutableModes_; }
36 
37 private:
38   MutableModes mutableModes_;
39 };
40 
Emit(const char * s,std::size_t len)41 bool 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 }
Emit(const char16_t *,std::size_t)46 bool TestFormatContext::Emit(const char16_t *, std::size_t) {
47   Crash("TestFormatContext::Emit(const char16_t *) called");
48   return false;
49 }
Emit(const char32_t *,std::size_t)50 bool TestFormatContext::Emit(const char32_t *, std::size_t) {
51   Crash("TestFormatContext::Emit(const char32_t *) called");
52   return false;
53 }
54 
AdvanceRecord(int n)55 bool TestFormatContext::AdvanceRecord(int n) {
56   while (n-- > 0) {
57     results.emplace_back("/");
58   }
59   return true;
60 }
61 
HandleAbsolutePosition(std::int64_t n)62 void TestFormatContext::HandleAbsolutePosition(std::int64_t n) {
63   results.push_back("T"s + std::to_string(n));
64 }
65 
HandleRelativePosition(std::int64_t n)66 void 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 
Report(const DataEdit & edit)74 void 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 
95 struct FormatTests : public CrashHandlerFixture {};
96 
TEST(FormatTests,FormatStringTraversal)97 TEST(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 
144 struct InvalidFormatFailure : CrashHandlerFixture {};
145 
TEST(InvalidFormatFailure,ParenMismatch)146 TEST(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)),
156       R"(FORMAT missing at least one '\)')");
157 }
158 
TEST(InvalidFormatFailure,MissingPrecision)159 TEST(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)),
169       R"(Invalid FORMAT: integer expected at '\)')");
170 }
171 
TEST(InvalidFormatFailure,MissingFormatWidth)172 TEST(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 }
184