1 //===-- flang/unittests/RuntimeGTest/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 "../../runtime/io-error.h"
11 #include "flang/Runtime/descriptor.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
SetCharacter(char * to,std::size_t n,const char * from)18 void SetCharacter(char *to, std::size_t n, const char *from) {
19   auto len{std::strlen(from)};
20   std::memcpy(to, from, std::min(len, n));
21   if (len < n) {
22     std::memset(to + len, ' ', n - len);
23   }
24 }
25 
26 struct InputTest : CrashHandlerFixture {};
27 
TEST(InputTest,TestListInputAlphabet)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(inputBuffers[j++], maxInputBufferLength,
39       "3*'abcdefghijklmnopqrstuvwxyzABC");
40   SetCharacter(
41       inputBuffers[j++], maxInputBufferLength, "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 
TEST(InputTest,TestListInputIntegerList)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(buffer[j++], maxBufferLength, "1 2 2*3  ,");
76   SetCharacter(buffer[j++], maxBufferLength, ",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 
TEST(InputTest,TestListInputInvalidFormatWithSingleSuccess)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.
TEST(InputTest,TestListInputInvalidFormat)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 
TEST_P(SimpleListInputTest,TestListInput)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