| 1 | //===-- unittests/Runtime/ListInputTest.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 "flang-rt/runtime/descriptor.h" |
| 11 | #include "flang-rt/runtime/io-error.h" |
| 12 | #include "flang/Runtime/io-api.h" |
| 13 | |
| 14 | using namespace Fortran::runtime; |
| 15 | using namespace Fortran::runtime::io; |
| 16 | |
| 17 | // Pads characters with whitespace when needed |
| 18 | void SetCharacter(char *to, std::size_t n, const char *from) { |
| 19 | auto len{std::strlen(s: from)}; |
| 20 | std::memcpy(dest: to, src: from, n: std::min(a: len, b: n)); |
| 21 | if (len < n) { |
| 22 | std::memset(s: to + len, c: ' ', n: n - len); |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | struct InputTest : CrashHandlerFixture {}; |
| 27 | |
| 28 | TEST(InputTest, TestListInputAlphabet) { |
| 29 | constexpr int numInputBuffers{2}; |
| 30 | constexpr int maxInputBufferLength{32}; |
| 31 | char inputBuffers[numInputBuffers][maxInputBufferLength]; |
| 32 | const char expectedOutput[]{ |
| 33 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ " }; |
| 34 | int j{0}; |
| 35 | |
| 36 | // Use _two_ input buffers and _three_ output buffers. Note the `3*` in the |
| 37 | // _inputBuffers_. |
| 38 | SetCharacter(to: inputBuffers[j++], n: maxInputBufferLength, |
| 39 | from: "3*'abcdefghijklmnopqrstuvwxyzABC" ); |
| 40 | SetCharacter( |
| 41 | to: inputBuffers[j++], n: maxInputBufferLength, from: "DEFGHIJKLMNOPQRSTUVWXYZ'" ); |
| 42 | |
| 43 | StaticDescriptor<1> staticDescriptor; |
| 44 | Descriptor &whole{staticDescriptor.descriptor()}; |
| 45 | SubscriptValue extent[]{numInputBuffers}; |
| 46 | whole.Establish(TypeCode{CFI_type_char}, maxInputBufferLength, &inputBuffers, |
| 47 | 1, extent, CFI_attribute_pointer); |
| 48 | whole.Check(); |
| 49 | auto *cookie{IONAME(BeginInternalArrayListInput)(whole)}; |
| 50 | |
| 51 | constexpr int numOutputBuffers{3}; |
| 52 | constexpr int outputBufferLength{54}; |
| 53 | char outputBuffers[numOutputBuffers][outputBufferLength]{}; |
| 54 | for (j = 0; j < numOutputBuffers; ++j) { |
| 55 | IONAME(InputAscii)(cookie, outputBuffers[j], outputBufferLength - 1); |
| 56 | } |
| 57 | |
| 58 | const auto status{IONAME(EndIoStatement)(cookie)}; |
| 59 | ASSERT_EQ(status, 0) << "list-directed input failed, status " |
| 60 | << static_cast<int>(status) << '\n'; |
| 61 | |
| 62 | // Verify results that the _two_ ascii inputs result in _three_ alphabets |
| 63 | for (j = 0; j < numOutputBuffers; ++j) { |
| 64 | ASSERT_EQ(std::strcmp(outputBuffers[j], expectedOutput), 0) |
| 65 | << "wanted outputBuffers[" << j << "]=" << expectedOutput << ", got '" |
| 66 | << outputBuffers[j] << "'\n" ; |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | TEST(InputTest, TestListInputIntegerList) { |
| 71 | constexpr int numBuffers{2}; |
| 72 | constexpr int maxBufferLength{32}; |
| 73 | char buffer[numBuffers][maxBufferLength]; |
| 74 | int j{0}; |
| 75 | SetCharacter(to: buffer[j++], n: maxBufferLength, from: "1 2 2*3 ," ); |
| 76 | SetCharacter(to: buffer[j++], n: maxBufferLength, from: ",6,,8,2*" ); |
| 77 | |
| 78 | StaticDescriptor<1> staticDescriptor; |
| 79 | Descriptor &whole{staticDescriptor.descriptor()}; |
| 80 | SubscriptValue extent[]{numBuffers}; |
| 81 | whole.Establish(TypeCode{CFI_type_char}, maxBufferLength, &buffer, 1, extent, |
| 82 | CFI_attribute_pointer); |
| 83 | whole.Check(); |
| 84 | auto *cookie{IONAME(BeginInternalArrayListInput)(whole)}; |
| 85 | |
| 86 | constexpr int listInputLength{10}; |
| 87 | |
| 88 | // Negative numbers will be overwritten by _expectedOutput_, and positive |
| 89 | // numbers will not be as their indices are "Null values" of the Fortran 2018 |
| 90 | // standard 13.10.3.2 in the format strings _buffer_ |
| 91 | std::int64_t actualOutput[listInputLength]{ |
| 92 | -1, -2, -3, -4, 5, -6, 7, -8, 9, 10}; |
| 93 | const std::int64_t expectedOutput[listInputLength]{ |
| 94 | 1, 2, 3, 3, 5, 6, 7, 8, 9, 10}; |
| 95 | for (j = 0; j < listInputLength; ++j) { |
| 96 | IONAME(InputInteger)(cookie, actualOutput[j]); |
| 97 | } |
| 98 | |
| 99 | const auto status{IONAME(EndIoStatement)(cookie)}; |
| 100 | ASSERT_EQ(status, 0) << "list-directed input failed, status " |
| 101 | << static_cast<int>(status) << '\n'; |
| 102 | |
| 103 | // Verify the calls to _InputInteger_ resulted in _expectedOutput_ |
| 104 | for (j = 0; j < listInputLength; ++j) { |
| 105 | ASSERT_EQ(actualOutput[j], expectedOutput[j]) |
| 106 | << "wanted actualOutput[" << j << "]==" << expectedOutput[j] << ", got " |
| 107 | << actualOutput[j] << '\n'; |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | TEST(InputTest, TestListInputInvalidFormatWithSingleSuccess) { |
| 112 | std::string formatBuffer{"1, g" }; |
| 113 | constexpr int numBuffers{1}; |
| 114 | |
| 115 | StaticDescriptor<1> staticDescriptor; |
| 116 | Descriptor &whole{staticDescriptor.descriptor()}; |
| 117 | SubscriptValue extent[]{numBuffers}; |
| 118 | whole.Establish(TypeCode{CFI_type_char}, formatBuffer.size(), |
| 119 | formatBuffer.data(), 1, extent, CFI_attribute_pointer); |
| 120 | whole.Check(); |
| 121 | |
| 122 | auto *cookie{IONAME(BeginInternalArrayListInput)(whole)}; |
| 123 | std::int64_t dummy; |
| 124 | |
| 125 | // Perform _InputInteger_ once successfully |
| 126 | IONAME(InputInteger)(cookie, dummy); |
| 127 | |
| 128 | // Perform failing InputInteger |
| 129 | ASSERT_DEATH(IONAME(InputInteger)(cookie, dummy), |
| 130 | "Bad character 'g' in INTEGER input field" ); |
| 131 | } |
| 132 | |
| 133 | // Same test as _TestListInputInvalidFormatWithSingleSuccess_, however no |
| 134 | // successful call to _InputInteger_ is performed first. |
| 135 | TEST(InputTest, TestListInputInvalidFormat) { |
| 136 | std::string formatBuffer{"g" }; |
| 137 | constexpr int numBuffers{1}; |
| 138 | |
| 139 | StaticDescriptor<1> staticDescriptor; |
| 140 | Descriptor &whole{staticDescriptor.descriptor()}; |
| 141 | SubscriptValue extent[]{numBuffers}; |
| 142 | whole.Establish(TypeCode{CFI_type_char}, formatBuffer.size(), |
| 143 | formatBuffer.data(), 1, extent, CFI_attribute_pointer); |
| 144 | whole.Check(); |
| 145 | |
| 146 | auto *cookie{IONAME(BeginInternalArrayListInput)(whole)}; |
| 147 | std::int64_t dummy; |
| 148 | |
| 149 | // Perform failing InputInteger |
| 150 | ASSERT_DEATH(IONAME(InputInteger)(cookie, dummy), |
| 151 | "Bad character 'g' in INTEGER input field" ); |
| 152 | } |
| 153 | |
| 154 | using ParamTy = std::tuple<std::string, std::vector<int>>; |
| 155 | |
| 156 | struct SimpleListInputTest : testing::TestWithParam<ParamTy> {}; |
| 157 | |
| 158 | TEST_P(SimpleListInputTest, TestListInput) { |
| 159 | auto [formatBuffer, expectedOutput] = GetParam(); |
| 160 | constexpr int numBuffers{1}; |
| 161 | |
| 162 | StaticDescriptor<1> staticDescriptor; |
| 163 | Descriptor &whole{staticDescriptor.descriptor()}; |
| 164 | SubscriptValue extent[]{numBuffers}; |
| 165 | whole.Establish(TypeCode{CFI_type_char}, formatBuffer.size(), |
| 166 | formatBuffer.data(), 1, extent, CFI_attribute_pointer); |
| 167 | whole.Check(); |
| 168 | auto *cookie{IONAME(BeginInternalArrayListInput)(whole)}; |
| 169 | |
| 170 | const auto listInputLength{expectedOutput.size()}; |
| 171 | std::vector<std::int64_t> actualOutput(listInputLength); |
| 172 | for (std::size_t j = 0; j < listInputLength; ++j) { |
| 173 | IONAME(InputInteger)(cookie, actualOutput[j]); |
| 174 | } |
| 175 | |
| 176 | const auto status{IONAME(EndIoStatement)(cookie)}; |
| 177 | ASSERT_EQ(status, 0) << "list-directed input failed, status " |
| 178 | << static_cast<int>(status) << '\n'; |
| 179 | |
| 180 | // Verify the calls to _InputInteger_ resulted in _expectedOutput_ |
| 181 | for (std::size_t j = 0; j < listInputLength; ++j) { |
| 182 | ASSERT_EQ(actualOutput[j], expectedOutput[j]) |
| 183 | << "wanted actualOutput[" << j << "]==" << expectedOutput[j] << ", got " |
| 184 | << actualOutput[j] << '\n'; |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | INSTANTIATE_TEST_SUITE_P(SimpleListInputTestInstantiation, SimpleListInputTest, |
| 189 | testing::Values(std::make_tuple("" , std::vector<int>{}), |
| 190 | std::make_tuple("0" , std::vector<int>{}), |
| 191 | std::make_tuple("1" , std::vector<int>{1}), |
| 192 | std::make_tuple("1, 2" , std::vector<int>{1, 2}), |
| 193 | std::make_tuple("3*2" , std::vector<int>{2, 2, 2}))); |
| 194 | |