| 1 | //===-- 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 "flang-rt/runtime/namelist.h" |
| 10 | #include "CrashHandlerFixture.h" |
| 11 | #include "tools.h" |
| 12 | #include "flang-rt/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{"group1" , items, 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 | // Tests REAL-looking input to integers |
| 309 | TEST(NamelistTests, RealValueForInt) { |
| 310 | OwningPtr<Descriptor> scDesc{ |
| 311 | MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>( |
| 312 | std::vector<int>{}, std::vector<int>{{}})}; |
| 313 | const NamelistGroup::Item items[]{{"j" , *scDesc}}; |
| 314 | const NamelistGroup group{"nml" , 1, items}; |
| 315 | static char t1[]{"&nml j=123.456/" }; |
| 316 | StaticDescriptor<1, true> statDesc; |
| 317 | Descriptor &internalDesc{statDesc.descriptor()}; |
| 318 | internalDesc.Establish(TypeCode{CFI_type_char}, |
| 319 | /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); |
| 320 | auto inCookie{IONAME(BeginInternalArrayListInput)( |
| 321 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
| 322 | ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); |
| 323 | ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk) |
| 324 | << "namelist real input for integer" ; |
| 325 | char out[16]; |
| 326 | internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, |
| 327 | out, 0, nullptr, CFI_attribute_pointer); |
| 328 | auto outCookie{IONAME(BeginInternalArrayListOutput)( |
| 329 | internalDesc, nullptr, 0, __FILE__, __LINE__)}; |
| 330 | ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); |
| 331 | ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output" ; |
| 332 | std::string got{out, sizeof out}; |
| 333 | static const std::string expect{" &NML J= 123/ " }; |
| 334 | EXPECT_EQ(got, expect); |
| 335 | } |
| 336 | |
| 337 | // TODO: Internal NAMELIST error tests |
| 338 | |