1 | //===-- flang/unittests/Runtime/Namelist.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 "../../runtime/namelist.h" |
10 | #include "CrashHandlerFixture.h" |
11 | #include "tools.h" |
12 | #include "flang/Runtime/descriptor.h" |
13 | #include "flang/Runtime/io-api.h" |
14 | #include <algorithm> |
15 | #include <cinttypes> |
16 | #include <complex> |
17 | #include <cstring> |
18 | #include <gtest/gtest.h> |
19 | #include <limits> |
20 | #include <string> |
21 | #include <vector> |
22 | |
23 | using namespace Fortran::runtime; |
24 | using namespace Fortran::runtime::io; |
25 | |
26 | struct NamelistTests : CrashHandlerFixture {}; |
27 | |
28 | static void ClearDescriptorStorage(const Descriptor &descriptor) { |
29 | std::memset(s: descriptor.raw().base_addr, c: 0, |
30 | n: descriptor.Elements() * descriptor.ElementBytes()); |
31 | } |
32 | |
33 | TEST(NamelistTests, BasicSanity) { |
34 | static constexpr int numLines{12}; |
35 | static constexpr int lineLength{32}; |
36 | static char buffer[numLines][lineLength]; |
37 | StaticDescriptor<1, true> statDescs[1]; |
38 | Descriptor &internalDesc{statDescs[0].descriptor()}; |
39 | SubscriptValue extent[]{numLines}; |
40 | internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/lineLength, |
41 | &buffer, 1, extent, CFI_attribute_pointer); |
42 | // Set up data arrays |
43 | std::vector<int> ints; |
44 | for (int j{0}; j < 20; ++j) { |
45 | ints.push_back(x: j % 2 == 0 ? (1 << j) : -(1 << j)); |
46 | } |
47 | std::vector<double> reals{0.0, -0.0, std::numeric_limits<double>::infinity(), |
48 | -std::numeric_limits<double>::infinity(), |
49 | std::numeric_limits<double>::quiet_NaN(), |
50 | std::numeric_limits<double>::max(), std::numeric_limits<double>::lowest(), |
51 | std::numeric_limits<double>::epsilon()}; |
52 | std::vector<std::uint8_t> logicals; |
53 | logicals.push_back(x: false); |
54 | logicals.push_back(x: true); |
55 | logicals.push_back(x: false); |
56 | std::vector<std::complex<float>> complexes; |
57 | complexes.push_back(x: std::complex<float>{123.0, -0.5}); |
58 | std::vector<std::string> characters; |
59 | characters.emplace_back(args: "aBcDeFgHiJkLmNoPqRsTuVwXyZ" ); |
60 | characters.emplace_back(args: "0123456789'\".............." ); |
61 | // Copy the data into new descriptors |
62 | OwningPtr<Descriptor> intDesc{ |
63 | MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>( |
64 | std::vector<int>{5, 4}, std::move(ints))}; |
65 | OwningPtr<Descriptor> realDesc{ |
66 | MakeArray<TypeCategory::Real, static_cast<int>(sizeof(double))>( |
67 | std::vector<int>{4, 2}, std::move(reals))}; |
68 | OwningPtr<Descriptor> logicalDesc{ |
69 | MakeArray<TypeCategory::Logical, static_cast<int>(sizeof(std::uint8_t))>( |
70 | std::vector<int>{3}, std::move(logicals))}; |
71 | OwningPtr<Descriptor> complexDesc{ |
72 | MakeArray<TypeCategory::Complex, static_cast<int>(sizeof(float))>( |
73 | std::vector<int>{}, std::move(complexes))}; |
74 | OwningPtr<Descriptor> characterDesc{MakeArray<TypeCategory::Character, 1>( |
75 | std::vector<int>{2}, std::move(characters), characters[0].size())}; |
76 | // Create a NAMELIST group |
77 | static constexpr int items{5}; |
78 | const NamelistGroup::Item itemArray[items]{{"ints" , *intDesc}, |
79 | {"reals" , *realDesc}, {"logicals" , *logicalDesc}, |
80 | {"complexes" , *complexDesc}, {"characters" , *characterDesc}}; |
81 | const NamelistGroup group{.groupName: "group1" , .items: items, .item: itemArray}; |
82 | // Do an internal NAMELIST write and check results |
83 | auto outCookie1{IONAME(BeginInternalArrayListOutput)( |
84 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
85 | ASSERT_TRUE(IONAME(SetDelim)(outCookie1, "APOSTROPHE" , 10)); |
86 | ASSERT_TRUE(IONAME(OutputNamelist)(outCookie1, group)); |
87 | auto outStatus1{IONAME(EndIoStatement)(outCookie1)}; |
88 | ASSERT_EQ(outStatus1, 0) << "Failed namelist output sanity, status " |
89 | << static_cast<int>(outStatus1); |
90 | |
91 | static const std::string expect{" &GROUP1 INTS= 1 -2 4 -8 16 -32 " |
92 | " 64 -128 256 -512 1024 -2048 " |
93 | " 4096 -8192 16384 -32768 65536 " |
94 | " -131072 262144 -524288,REALS= " |
95 | " 0. -0. Inf -Inf NaN " |
96 | " 1.7976931348623157E+308 " |
97 | " -1.7976931348623157E+308 " |
98 | " 2.220446049250313E-16,LOGICALS=" |
99 | "F T F,COMPLEXES= (123.,-.5), " |
100 | " CHARACTERS= 'aBcDeFgHiJkLmNoPqR" |
101 | "sTuVwXyZ' '0123456789''\"........" |
102 | "......'/ " }; |
103 | std::string got{buffer[0], sizeof buffer}; |
104 | EXPECT_EQ(got, expect); |
105 | |
106 | // Clear the arrays, read them back, write out again, and compare |
107 | ClearDescriptorStorage(*intDesc); |
108 | ClearDescriptorStorage(*realDesc); |
109 | ClearDescriptorStorage(*logicalDesc); |
110 | ClearDescriptorStorage(*complexDesc); |
111 | ClearDescriptorStorage(*characterDesc); |
112 | auto inCookie{IONAME(BeginInternalArrayListInput)( |
113 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
114 | ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); |
115 | auto inStatus{IONAME(EndIoStatement)(inCookie)}; |
116 | ASSERT_EQ(inStatus, 0) << "Failed namelist input sanity, status " |
117 | << static_cast<int>(inStatus); |
118 | auto outCookie2{IONAME(BeginInternalArrayListOutput)( |
119 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
120 | ASSERT_TRUE(IONAME(SetDelim)(outCookie2, "APOSTROPHE" , 10)); |
121 | ASSERT_TRUE(IONAME(OutputNamelist)(outCookie2, group)); |
122 | auto outStatus2{IONAME(EndIoStatement)(outCookie2)}; |
123 | ASSERT_EQ(outStatus2, 0) << "Failed namelist output sanity rewrite, status " |
124 | << static_cast<int>(outStatus2); |
125 | std::string got2{buffer[0], sizeof buffer}; |
126 | EXPECT_EQ(got2, expect); |
127 | } |
128 | |
129 | TEST(NamelistTests, Subscripts) { |
130 | // INTEGER :: A(-1:0, -1:1) |
131 | OwningPtr<Descriptor> aDesc{ |
132 | MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>( |
133 | std::vector<int>{2, 3}, std::vector<int>(6, 0))}; |
134 | aDesc->GetDimension(0).SetBounds(-1, 0); |
135 | aDesc->GetDimension(1).SetBounds(-1, 1); |
136 | const NamelistGroup::Item items[]{{"a" , *aDesc}}; |
137 | const NamelistGroup group{"justa" , 1, items}; |
138 | static char t1[]{"&justa A(0,+1:-1:-2)=1 2/" }; |
139 | StaticDescriptor<1, true> statDesc; |
140 | Descriptor &internalDesc{statDesc.descriptor()}; |
141 | internalDesc.Establish(TypeCode{CFI_type_char}, |
142 | /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); |
143 | auto inCookie{IONAME(BeginInternalArrayListInput)( |
144 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
145 | ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); |
146 | auto inStatus{IONAME(EndIoStatement)(inCookie)}; |
147 | ASSERT_EQ(inStatus, 0) << "Failed namelist input subscripts, status " |
148 | << static_cast<int>(inStatus); |
149 | char out[40]; |
150 | internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, |
151 | out, 0, nullptr, CFI_attribute_pointer); |
152 | auto outCookie{IONAME(BeginInternalArrayListOutput)( |
153 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
154 | ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); |
155 | auto outStatus{IONAME(EndIoStatement)(outCookie)}; |
156 | ASSERT_EQ(outStatus, 0) |
157 | << "Failed namelist output subscripts rewrite, status " |
158 | << static_cast<int>(outStatus); |
159 | std::string got{out, sizeof out}; |
160 | static const std::string expect{" &JUSTA A= 0 2 0 0 0 1/ " }; |
161 | EXPECT_EQ(got, expect); |
162 | } |
163 | |
164 | TEST(NamelistTests, ShortArrayInput) { |
165 | OwningPtr<Descriptor> aDesc{ |
166 | MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>( |
167 | std::vector<int>{2}, std::vector<int>(2, -1))}; |
168 | OwningPtr<Descriptor> bDesc{ |
169 | MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>( |
170 | std::vector<int>{2}, std::vector<int>(2, -2))}; |
171 | const NamelistGroup::Item items[]{{"a" , *aDesc}, {"b" , *bDesc}}; |
172 | const NamelistGroup group{"nl" , 2, items}; |
173 | // Two 12-character lines of internal input |
174 | static char t1[]{"&nl a = 1 b " |
175 | " = 2 / " }; |
176 | StaticDescriptor<1, true> statDesc; |
177 | Descriptor &internalDesc{statDesc.descriptor()}; |
178 | SubscriptValue shape{2}; |
179 | internalDesc.Establish(1, 12, t1, 1, &shape, CFI_attribute_pointer); |
180 | auto inCookie{IONAME(BeginInternalArrayListInput)( |
181 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
182 | ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); |
183 | auto inStatus{IONAME(EndIoStatement)(inCookie)}; |
184 | ASSERT_EQ(inStatus, 0) << "Failed namelist input subscripts, status " |
185 | << static_cast<int>(inStatus); |
186 | EXPECT_EQ(*aDesc->ZeroBasedIndexedElement<int>(0), 1); |
187 | EXPECT_EQ(*aDesc->ZeroBasedIndexedElement<int>(1), -1); |
188 | EXPECT_EQ(*bDesc->ZeroBasedIndexedElement<int>(0), 2); |
189 | EXPECT_EQ(*bDesc->ZeroBasedIndexedElement<int>(1), -2); |
190 | } |
191 | |
192 | TEST(NamelistTests, ScalarSubstring) { |
193 | OwningPtr<Descriptor> scDesc{MakeArray<TypeCategory::Character, 1>( |
194 | std::vector<int>{}, std::vector<std::string>{"abcdefgh" }, 8)}; |
195 | const NamelistGroup::Item items[]{{"a" , *scDesc}}; |
196 | const NamelistGroup group{"justa" , 1, items}; |
197 | static char t1[]{"&justa A(2:5)='BCDE'/" }; |
198 | StaticDescriptor<1, true> statDesc; |
199 | Descriptor &internalDesc{statDesc.descriptor()}; |
200 | internalDesc.Establish(TypeCode{CFI_type_char}, |
201 | /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); |
202 | auto inCookie{IONAME(BeginInternalArrayListInput)( |
203 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
204 | ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); |
205 | ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk) |
206 | << "namelist scalar substring input" ; |
207 | char out[32]; |
208 | internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, |
209 | out, 0, nullptr, CFI_attribute_pointer); |
210 | auto outCookie{IONAME(BeginInternalArrayListOutput)( |
211 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
212 | ASSERT_TRUE(IONAME(SetDelim)(outCookie, "apostrophe" , 10)); |
213 | ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); |
214 | ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output" ; |
215 | std::string got{out, sizeof out}; |
216 | static const std::string expect{" &JUSTA A= 'aBCDEfgh'/ " }; |
217 | EXPECT_EQ(got, expect); |
218 | } |
219 | |
220 | TEST(NamelistTests, ArraySubstring) { |
221 | OwningPtr<Descriptor> scDesc{ |
222 | MakeArray<TypeCategory::Character, 1>(std::vector<int>{2}, |
223 | std::vector<std::string>{"abcdefgh" , "ijklmnop" }, 8)}; |
224 | const NamelistGroup::Item items[]{{"a" , *scDesc}}; |
225 | const NamelistGroup group{"justa" , 1, items}; |
226 | static char t1[]{"&justa A(:)(2:+5)='BCDE' 'JKLM'/" }; |
227 | StaticDescriptor<1, true> statDesc; |
228 | Descriptor &internalDesc{statDesc.descriptor()}; |
229 | internalDesc.Establish(TypeCode{CFI_type_char}, |
230 | /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); |
231 | auto inCookie{IONAME(BeginInternalArrayListInput)( |
232 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
233 | ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); |
234 | ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk) |
235 | << "namelist scalar substring input" ; |
236 | char out[40]; |
237 | internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, |
238 | out, 0, nullptr, CFI_attribute_pointer); |
239 | auto outCookie{IONAME(BeginInternalArrayListOutput)( |
240 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
241 | ASSERT_TRUE(IONAME(SetDelim)(outCookie, "apostrophe" , 10)); |
242 | ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); |
243 | ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output" ; |
244 | std::string got{out, sizeof out}; |
245 | static const std::string expect{" &JUSTA A= 'aBCDEfgh' 'iJKLMnop'/ " }; |
246 | EXPECT_EQ(got, expect); |
247 | } |
248 | |
249 | TEST(NamelistTests, Skip) { |
250 | OwningPtr<Descriptor> scDesc{ |
251 | MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>( |
252 | std::vector<int>{}, std::vector<int>{-1})}; |
253 | const NamelistGroup::Item items[]{{"j" , *scDesc}}; |
254 | const NamelistGroup group{"nml" , 1, items}; |
255 | static char t1[]{"&skip a='str''ing'/&nml j=123/" }; |
256 | StaticDescriptor<1, true> statDesc; |
257 | Descriptor &internalDesc{statDesc.descriptor()}; |
258 | internalDesc.Establish(TypeCode{CFI_type_char}, |
259 | /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); |
260 | auto inCookie{IONAME(BeginInternalArrayListInput)( |
261 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
262 | ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); |
263 | ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk) |
264 | << "namelist input with skipping" ; |
265 | char out[20]; |
266 | internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, |
267 | out, 0, nullptr, CFI_attribute_pointer); |
268 | auto outCookie{IONAME(BeginInternalArrayListOutput)( |
269 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
270 | ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); |
271 | ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output" ; |
272 | std::string got{out, sizeof out}; |
273 | static const std::string expect{" &NML J= 123/ " }; |
274 | EXPECT_EQ(got, expect); |
275 | } |
276 | |
277 | // Tests DECIMAL=COMMA mode |
278 | TEST(NamelistTests, Comma) { |
279 | OwningPtr<Descriptor> scDesc{ |
280 | MakeArray<TypeCategory::Complex, static_cast<int>(sizeof(float))>( |
281 | std::vector<int>{2}, std::vector<std::complex<float>>{{}, {}})}; |
282 | const NamelistGroup::Item items[]{{"z" , *scDesc}}; |
283 | const NamelistGroup group{"nml" , 1, items}; |
284 | static char t1[]{"&nml z=(-1,0;2,0);(-3,0;0,5)/" }; |
285 | StaticDescriptor<1, true> statDesc; |
286 | Descriptor &internalDesc{statDesc.descriptor()}; |
287 | internalDesc.Establish(TypeCode{CFI_type_char}, |
288 | /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); |
289 | auto inCookie{IONAME(BeginInternalArrayListInput)( |
290 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
291 | ASSERT_TRUE(IONAME(SetDecimal)(inCookie, "COMMA" , 5)); |
292 | ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); |
293 | ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk) |
294 | << "namelist input with skipping" ; |
295 | char out[30]; |
296 | internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, |
297 | out, 0, nullptr, CFI_attribute_pointer); |
298 | auto outCookie{IONAME(BeginInternalArrayListOutput)( |
299 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
300 | ASSERT_TRUE(IONAME(SetDecimal)(outCookie, "COMMA" , 5)); |
301 | ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); |
302 | ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output" ; |
303 | std::string got{out, sizeof out}; |
304 | static const std::string expect{" &NML Z= (-1,;2,) (-3,;,5)/ " }; |
305 | EXPECT_EQ(got, expect); |
306 | } |
307 | |
308 | // TODO: Internal NAMELIST error tests |
309 | |