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-consts.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(descriptor.raw().base_addr, 0, 30 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(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(false); 54 logicals.push_back(true); 55 logicals.push_back(false); 56 std::vector<std::complex<float>> complexes; 57 complexes.push_back(std::complex<float>{123.0, -0.5}); 58 std::vector<std::string> characters; 59 characters.emplace_back("aBcDeFgHiJkLmNoPqRsTuVwXyZ"); 60 characters.emplace_back("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