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; 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 37 private: 38 MutableModes mutableModes_; 39 }; 40 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 } 46 bool TestFormatContext::Emit(const char16_t *, std::size_t) { 47 Crash("TestFormatContext::Emit(const char16_t *) called"); 48 return false; 49 } 50 bool TestFormatContext::Emit(const char32_t *, std::size_t) { 51 Crash("TestFormatContext::Emit(const char32_t *) called"); 52 return false; 53 } 54 55 bool TestFormatContext::AdvanceRecord(int n) { 56 while (n-- > 0) { 57 results.emplace_back("/"); 58 } 59 return true; 60 } 61 62 void TestFormatContext::HandleAbsolutePosition(std::int64_t n) { 63 results.push_back("T"s + std::to_string(n)); 64 } 65 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 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 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 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 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 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