xref: /llvm-project/flang/unittests/Runtime/Format.cpp (revision c13f7e17409b6fed29696c2bbe59efc3af3a408b)
1ffc67bb3SDavid Spickett //===-- flang/unittests/Runtime/Format.cpp ----------------------*- C++ -*-===//
2ffc67bb3SDavid Spickett //
3ffc67bb3SDavid Spickett // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4ffc67bb3SDavid Spickett // See https://llvm.org/LICENSE.txt for license information.
5ffc67bb3SDavid Spickett // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6ffc67bb3SDavid Spickett //
7ffc67bb3SDavid Spickett //===----------------------------------------------------------------------===//
8ffc67bb3SDavid Spickett 
9ffc67bb3SDavid Spickett #include "CrashHandlerFixture.h"
10ffc67bb3SDavid Spickett #include "../runtime/connection.h"
11ffc67bb3SDavid Spickett #include "../runtime/format-implementation.h"
12ffc67bb3SDavid Spickett #include "../runtime/io-error.h"
13ffc67bb3SDavid Spickett #include <optional>
14ffc67bb3SDavid Spickett #include <string>
15ffc67bb3SDavid Spickett #include <tuple>
16ffc67bb3SDavid Spickett #include <vector>
17ffc67bb3SDavid Spickett 
18ffc67bb3SDavid Spickett using namespace Fortran::runtime;
19ffc67bb3SDavid Spickett using namespace Fortran::runtime::io;
20ffc67bb3SDavid Spickett using namespace std::literals::string_literals;
21ffc67bb3SDavid Spickett 
22ffc67bb3SDavid Spickett using ResultsTy = std::vector<std::string>;
23ffc67bb3SDavid Spickett 
24ffc67bb3SDavid Spickett // A test harness context for testing FormatControl
25ffc67bb3SDavid Spickett class TestFormatContext : public IoErrorHandler {
26ffc67bb3SDavid Spickett public:
27ffc67bb3SDavid Spickett   using CharType = char;
TestFormatContext()28ffc67bb3SDavid Spickett   TestFormatContext() : IoErrorHandler{"format.cpp", 1} {}
29ffc67bb3SDavid Spickett   bool Emit(const char *, std::size_t, std::size_t = 0);
30ffc67bb3SDavid Spickett   bool AdvanceRecord(int = 1);
31ffc67bb3SDavid Spickett   void HandleRelativePosition(std::int64_t);
32ffc67bb3SDavid Spickett   void HandleAbsolutePosition(std::int64_t);
33ffc67bb3SDavid Spickett   void Report(const std::optional<DataEdit> &);
34ffc67bb3SDavid Spickett   ResultsTy results;
mutableModes()35ffc67bb3SDavid Spickett   MutableModes &mutableModes() { return mutableModes_; }
GetConnectionState()36ffc67bb3SDavid Spickett   ConnectionState &GetConnectionState() { return connectionState_; }
37ffc67bb3SDavid Spickett 
38ffc67bb3SDavid Spickett private:
39ffc67bb3SDavid Spickett   MutableModes mutableModes_;
40ffc67bb3SDavid Spickett   ConnectionState connectionState_;
41ffc67bb3SDavid Spickett };
42ffc67bb3SDavid Spickett 
Emit(const char * s,std::size_t len,std::size_t)43ffc67bb3SDavid Spickett bool TestFormatContext::Emit(const char *s, std::size_t len, std::size_t) {
44ffc67bb3SDavid Spickett   std::string str{s, len};
45ffc67bb3SDavid Spickett   results.push_back("'"s + str + '\'');
46ffc67bb3SDavid Spickett   return true;
47ffc67bb3SDavid Spickett }
48ffc67bb3SDavid Spickett 
AdvanceRecord(int n)49ffc67bb3SDavid Spickett bool TestFormatContext::AdvanceRecord(int n) {
50ffc67bb3SDavid Spickett   while (n-- > 0) {
51ffc67bb3SDavid Spickett     results.emplace_back("/");
52ffc67bb3SDavid Spickett   }
53ffc67bb3SDavid Spickett   return true;
54ffc67bb3SDavid Spickett }
55ffc67bb3SDavid Spickett 
HandleAbsolutePosition(std::int64_t n)56ffc67bb3SDavid Spickett void TestFormatContext::HandleAbsolutePosition(std::int64_t n) {
57ffc67bb3SDavid Spickett   results.push_back("T"s + std::to_string(n));
58ffc67bb3SDavid Spickett }
59ffc67bb3SDavid Spickett 
HandleRelativePosition(std::int64_t n)60ffc67bb3SDavid Spickett void TestFormatContext::HandleRelativePosition(std::int64_t n) {
61ffc67bb3SDavid Spickett   if (n < 0) {
62ffc67bb3SDavid Spickett     results.push_back("TL"s + std::to_string(-n));
63ffc67bb3SDavid Spickett   } else {
64ffc67bb3SDavid Spickett     results.push_back(std::to_string(n) + 'X');
65ffc67bb3SDavid Spickett   }
66ffc67bb3SDavid Spickett }
67ffc67bb3SDavid Spickett 
Report(const std::optional<DataEdit> & edit)68ffc67bb3SDavid Spickett void TestFormatContext::Report(const std::optional<DataEdit> &edit) {
69ffc67bb3SDavid Spickett   if (edit) {
70ffc67bb3SDavid Spickett     std::string str{edit->descriptor};
71ffc67bb3SDavid Spickett     if (edit->repeat != 1) {
72ffc67bb3SDavid Spickett       str = std::to_string(edit->repeat) + '*' + str;
73ffc67bb3SDavid Spickett     }
74ffc67bb3SDavid Spickett     if (edit->variation) {
75ffc67bb3SDavid Spickett       str += edit->variation;
76ffc67bb3SDavid Spickett     }
77ffc67bb3SDavid Spickett     if (edit->width) {
78ffc67bb3SDavid Spickett       str += std::to_string(*edit->width);
79ffc67bb3SDavid Spickett     }
80ffc67bb3SDavid Spickett     if (edit->digits) {
81ffc67bb3SDavid Spickett       str += "."s + std::to_string(*edit->digits);
82ffc67bb3SDavid Spickett     }
83ffc67bb3SDavid Spickett     if (edit->expoDigits) {
84ffc67bb3SDavid Spickett       str += "E"s + std::to_string(*edit->expoDigits);
85ffc67bb3SDavid Spickett     }
86ffc67bb3SDavid Spickett     // modes?
87ffc67bb3SDavid Spickett     results.push_back(str);
88ffc67bb3SDavid Spickett   } else {
89ffc67bb3SDavid Spickett     results.push_back("(nullopt)"s);
90ffc67bb3SDavid Spickett   }
91ffc67bb3SDavid Spickett }
92ffc67bb3SDavid Spickett 
93ffc67bb3SDavid Spickett struct FormatTests : public CrashHandlerFixture {};
94ffc67bb3SDavid Spickett 
TEST(FormatTests,FormatStringTraversal)95ffc67bb3SDavid Spickett TEST(FormatTests, FormatStringTraversal) {
96ffc67bb3SDavid Spickett 
97ffc67bb3SDavid Spickett   using ParamsTy = std::tuple<int, const char *, ResultsTy, int>;
98ffc67bb3SDavid Spickett 
99ffc67bb3SDavid Spickett   static const std::vector<ParamsTy> params{
100ffc67bb3SDavid Spickett       {1, "('PI=',F9.7)", ResultsTy{"'PI='", "F9.7"}, 1},
101ffc67bb3SDavid Spickett       {1, "(3HPI=F9.7)", ResultsTy{"'PI='", "F9.7"}, 1},
102ffc67bb3SDavid Spickett       {1, "(3HPI=/F9.7)", ResultsTy{"'PI='", "/", "F9.7"}, 1},
103ffc67bb3SDavid Spickett       {2, "('PI=',F9.7)", ResultsTy{"'PI='", "F9.7", "/", "'PI='", "F9.7"}, 1},
104ffc67bb3SDavid Spickett       {2, "(2('PI=',F9.7),'done')",
105ffc67bb3SDavid Spickett           ResultsTy{"'PI='", "F9.7", "'PI='", "F9.7", "'done'"}, 1},
106ffc67bb3SDavid Spickett       {2, "(3('PI=',F9.7,:),'tooFar')",
107ffc67bb3SDavid Spickett           ResultsTy{"'PI='", "F9.7", "'PI='", "F9.7"}, 1},
108ffc67bb3SDavid Spickett       {2, "(*('PI=',F9.7,:))", ResultsTy{"'PI='", "F9.7", "'PI='", "F9.7"}, 1},
109ffc67bb3SDavid Spickett       {1, "(3F9.7)", ResultsTy{"2*F9.7"}, 2},
110ec4ba0f5SPeter Klausler       {9, "((I4,2(E10.1)))",
111ec4ba0f5SPeter Klausler           ResultsTy{"I4", "E10.1", "E10.1", "/", "I4", "E10.1", "E10.1", "/",
112ec4ba0f5SPeter Klausler               "I4", "E10.1", "E10.1"},
113ec4ba0f5SPeter Klausler           1},
114*c13f7e17SPeter Klausler       {1, "(F)", ResultsTy{"F"}, 1}, // missing 'w'
115ffc67bb3SDavid Spickett   };
116ffc67bb3SDavid Spickett 
117ffc67bb3SDavid Spickett   for (const auto &[n, format, expect, repeat] : params) {
118ffc67bb3SDavid Spickett     TestFormatContext context;
119ffc67bb3SDavid Spickett     FormatControl<decltype(context)> control{
120ffc67bb3SDavid Spickett         context, format, std::strlen(format)};
121ffc67bb3SDavid Spickett 
122ffc67bb3SDavid Spickett     for (auto i{0}; i < n; i++) {
123ffc67bb3SDavid Spickett       context.Report(/*edit=*/control.GetNextDataEdit(context, repeat));
124ffc67bb3SDavid Spickett     }
125ffc67bb3SDavid Spickett     control.Finish(context);
126ffc67bb3SDavid Spickett 
127ffc67bb3SDavid Spickett     auto iostat{context.GetIoStat()};
128ffc67bb3SDavid Spickett     ASSERT_EQ(iostat, 0) << "Expected iostat == 0, but GetIoStat() == "
129ffc67bb3SDavid Spickett                          << iostat;
130ffc67bb3SDavid Spickett 
131ffc67bb3SDavid Spickett     // Create strings of the expected/actual results for printing errors
132ffc67bb3SDavid Spickett     std::string allExpectedResults{""}, allActualResults{""};
133ffc67bb3SDavid Spickett     for (const auto &res : context.results) {
134ffc67bb3SDavid Spickett       allActualResults += " "s + res;
135ffc67bb3SDavid Spickett     }
136ffc67bb3SDavid Spickett     for (const auto &res : expect) {
137ffc67bb3SDavid Spickett       allExpectedResults += " "s + res;
138ffc67bb3SDavid Spickett     }
139ffc67bb3SDavid Spickett 
140ffc67bb3SDavid Spickett     const auto &results = context.results;
141ffc67bb3SDavid Spickett     ASSERT_EQ(expect, results) << "Expected '" << allExpectedResults
142ffc67bb3SDavid Spickett                                << "' but got '" << allActualResults << "'";
143ffc67bb3SDavid Spickett   }
144ffc67bb3SDavid Spickett }
145ffc67bb3SDavid Spickett 
146ffc67bb3SDavid Spickett struct InvalidFormatFailure : CrashHandlerFixture {};
147ffc67bb3SDavid Spickett 
TEST(InvalidFormatFailure,ParenMismatch)148ffc67bb3SDavid Spickett TEST(InvalidFormatFailure, ParenMismatch) {
149ffc67bb3SDavid Spickett   static constexpr const char *format{"("};
150ffc67bb3SDavid Spickett   static constexpr int repeat{1};
151ffc67bb3SDavid Spickett 
152ffc67bb3SDavid Spickett   TestFormatContext context;
153ffc67bb3SDavid Spickett   FormatControl<decltype(context)> control{
154ffc67bb3SDavid Spickett       context, format, std::strlen(format)};
155ffc67bb3SDavid Spickett 
156ffc67bb3SDavid Spickett   ASSERT_DEATH(
157ffc67bb3SDavid Spickett       context.Report(/*edit=*/control.GetNextDataEdit(context, repeat)),
158ffc67bb3SDavid Spickett       R"(FORMAT missing at least one '\)')");
159ffc67bb3SDavid Spickett }
160ffc67bb3SDavid Spickett 
TEST(InvalidFormatFailure,MissingPrecision)161ffc67bb3SDavid Spickett TEST(InvalidFormatFailure, MissingPrecision) {
162ffc67bb3SDavid Spickett   static constexpr const char *format{"(F9.)"};
163ffc67bb3SDavid Spickett   static constexpr int repeat{1};
164ffc67bb3SDavid Spickett 
165ffc67bb3SDavid Spickett   TestFormatContext context;
166ffc67bb3SDavid Spickett   FormatControl<decltype(context)> control{
167ffc67bb3SDavid Spickett       context, format, std::strlen(format)};
168ffc67bb3SDavid Spickett 
169ffc67bb3SDavid Spickett   ASSERT_DEATH(
170ffc67bb3SDavid Spickett       context.Report(/*edit=*/control.GetNextDataEdit(context, repeat)),
171ffc67bb3SDavid Spickett       R"(Invalid FORMAT: integer expected at '\)')");
172ffc67bb3SDavid Spickett }
173ffc67bb3SDavid Spickett 
TEST(InvalidFormatFailure,MissingFormatWidthWithDigits)174*c13f7e17SPeter Klausler TEST(InvalidFormatFailure, MissingFormatWidthWithDigits) {
175ffc67bb3SDavid Spickett   static constexpr const char *format{"(F.9)"};
176ffc67bb3SDavid Spickett   static constexpr int repeat{1};
177ffc67bb3SDavid Spickett 
178ffc67bb3SDavid Spickett   TestFormatContext context;
179ffc67bb3SDavid Spickett   FormatControl<decltype(context)> control{
180ffc67bb3SDavid Spickett       context, format, std::strlen(format)};
181ffc67bb3SDavid Spickett 
182ffc67bb3SDavid Spickett   ASSERT_DEATH(
183ffc67bb3SDavid Spickett       context.Report(/*edit=*/control.GetNextDataEdit(context, repeat)),
184ffc67bb3SDavid Spickett       "Invalid FORMAT: integer expected at '.'");
185ffc67bb3SDavid Spickett }
186