//===-- flang/unittests/Runtime/Namelist.cpp --------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "../../runtime/namelist.h" #include "CrashHandlerFixture.h" #include "tools.h" #include "flang/Runtime/descriptor.h" #include "flang/Runtime/io-api.h" #include #include #include #include #include #include #include #include using namespace Fortran::runtime; using namespace Fortran::runtime::io; struct NamelistTests : CrashHandlerFixture {}; static void ClearDescriptorStorage(const Descriptor &descriptor) { std::memset(descriptor.raw().base_addr, 0, descriptor.Elements() * descriptor.ElementBytes()); } TEST(NamelistTests, BasicSanity) { static constexpr int numLines{12}; static constexpr int lineLength{32}; static char buffer[numLines][lineLength]; StaticDescriptor<1, true> statDescs[1]; Descriptor &internalDesc{statDescs[0].descriptor()}; SubscriptValue extent[]{numLines}; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/lineLength, &buffer, 1, extent, CFI_attribute_pointer); // Set up data arrays std::vector ints; for (int j{0}; j < 20; ++j) { ints.push_back(j % 2 == 0 ? (1 << j) : -(1 << j)); } std::vector reals{0.0, -0.0, std::numeric_limits::infinity(), -std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN(), std::numeric_limits::max(), std::numeric_limits::lowest(), std::numeric_limits::epsilon()}; std::vector logicals; logicals.push_back(false); logicals.push_back(true); logicals.push_back(false); std::vector> complexes; complexes.push_back(std::complex{123.0, -0.5}); std::vector characters; characters.emplace_back("aBcDeFgHiJkLmNoPqRsTuVwXyZ"); characters.emplace_back("0123456789'\".............."); // Copy the data into new descriptors OwningPtr intDesc{ MakeArray(sizeof(int))>( std::vector{5, 4}, std::move(ints))}; OwningPtr realDesc{ MakeArray(sizeof(double))>( std::vector{4, 2}, std::move(reals))}; OwningPtr logicalDesc{ MakeArray(sizeof(std::uint8_t))>( std::vector{3}, std::move(logicals))}; OwningPtr complexDesc{ MakeArray(sizeof(float))>( std::vector{}, std::move(complexes))}; OwningPtr characterDesc{MakeArray( std::vector{2}, std::move(characters), characters[0].size())}; // Create a NAMELIST group static constexpr int items{5}; const NamelistGroup::Item itemArray[items]{{"ints", *intDesc}, {"reals", *realDesc}, {"logicals", *logicalDesc}, {"complexes", *complexDesc}, {"characters", *characterDesc}}; const NamelistGroup group{"group1", items, itemArray}; // Do an internal NAMELIST write and check results auto outCookie1{IONAME(BeginInternalArrayListOutput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(SetDelim)(outCookie1, "APOSTROPHE", 10)); ASSERT_TRUE(IONAME(OutputNamelist)(outCookie1, group)); auto outStatus1{IONAME(EndIoStatement)(outCookie1)}; ASSERT_EQ(outStatus1, 0) << "Failed namelist output sanity, status " << static_cast(outStatus1); static const std::string expect{" &GROUP1 INTS= 1 -2 4 -8 16 -32 " " 64 -128 256 -512 1024 -2048 " " 4096 -8192 16384 -32768 65536 " " -131072 262144 -524288,REALS= " " 0. -0. Inf -Inf NaN " " 1.7976931348623157E+308 " " -1.7976931348623157E+308 " " 2.220446049250313E-16,LOGICALS=" "F T F,COMPLEXES= (123.,-.5), " " CHARACTERS= 'aBcDeFgHiJkLmNoPqR" "sTuVwXyZ' '0123456789''\"........" "......'/ "}; std::string got{buffer[0], sizeof buffer}; EXPECT_EQ(got, expect); // Clear the arrays, read them back, write out again, and compare ClearDescriptorStorage(*intDesc); ClearDescriptorStorage(*realDesc); ClearDescriptorStorage(*logicalDesc); ClearDescriptorStorage(*complexDesc); ClearDescriptorStorage(*characterDesc); auto inCookie{IONAME(BeginInternalArrayListInput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); auto inStatus{IONAME(EndIoStatement)(inCookie)}; ASSERT_EQ(inStatus, 0) << "Failed namelist input sanity, status " << static_cast(inStatus); auto outCookie2{IONAME(BeginInternalArrayListOutput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(SetDelim)(outCookie2, "APOSTROPHE", 10)); ASSERT_TRUE(IONAME(OutputNamelist)(outCookie2, group)); auto outStatus2{IONAME(EndIoStatement)(outCookie2)}; ASSERT_EQ(outStatus2, 0) << "Failed namelist output sanity rewrite, status " << static_cast(outStatus2); std::string got2{buffer[0], sizeof buffer}; EXPECT_EQ(got2, expect); } TEST(NamelistTests, Subscripts) { // INTEGER :: A(-1:0, -1:1) OwningPtr aDesc{ MakeArray(sizeof(int))>( std::vector{2, 3}, std::vector(6, 0))}; aDesc->GetDimension(0).SetBounds(-1, 0); aDesc->GetDimension(1).SetBounds(-1, 1); const NamelistGroup::Item items[]{{"a", *aDesc}}; const NamelistGroup group{"justa", 1, items}; static char t1[]{"&justa A(0,+1:-1:-2)=1 2/"}; StaticDescriptor<1, true> statDesc; Descriptor &internalDesc{statDesc.descriptor()}; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); auto inCookie{IONAME(BeginInternalArrayListInput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); auto inStatus{IONAME(EndIoStatement)(inCookie)}; ASSERT_EQ(inStatus, 0) << "Failed namelist input subscripts, status " << static_cast(inStatus); char out[40]; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, out, 0, nullptr, CFI_attribute_pointer); auto outCookie{IONAME(BeginInternalArrayListOutput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); auto outStatus{IONAME(EndIoStatement)(outCookie)}; ASSERT_EQ(outStatus, 0) << "Failed namelist output subscripts rewrite, status " << static_cast(outStatus); std::string got{out, sizeof out}; static const std::string expect{" &JUSTA A= 0 2 0 0 0 1/ "}; EXPECT_EQ(got, expect); } TEST(NamelistTests, ShortArrayInput) { OwningPtr aDesc{ MakeArray(sizeof(int))>( std::vector{2}, std::vector(2, -1))}; OwningPtr bDesc{ MakeArray(sizeof(int))>( std::vector{2}, std::vector(2, -2))}; const NamelistGroup::Item items[]{{"a", *aDesc}, {"b", *bDesc}}; const NamelistGroup group{"nl", 2, items}; // Two 12-character lines of internal input static char t1[]{"&nl a = 1 b " " = 2 / "}; StaticDescriptor<1, true> statDesc; Descriptor &internalDesc{statDesc.descriptor()}; SubscriptValue shape{2}; internalDesc.Establish(1, 12, t1, 1, &shape, CFI_attribute_pointer); auto inCookie{IONAME(BeginInternalArrayListInput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); auto inStatus{IONAME(EndIoStatement)(inCookie)}; ASSERT_EQ(inStatus, 0) << "Failed namelist input subscripts, status " << static_cast(inStatus); EXPECT_EQ(*aDesc->ZeroBasedIndexedElement(0), 1); EXPECT_EQ(*aDesc->ZeroBasedIndexedElement(1), -1); EXPECT_EQ(*bDesc->ZeroBasedIndexedElement(0), 2); EXPECT_EQ(*bDesc->ZeroBasedIndexedElement(1), -2); } TEST(NamelistTests, ScalarSubstring) { OwningPtr scDesc{MakeArray( std::vector{}, std::vector{"abcdefgh"}, 8)}; const NamelistGroup::Item items[]{{"a", *scDesc}}; const NamelistGroup group{"justa", 1, items}; static char t1[]{"&justa A(2:5)='BCDE'/"}; StaticDescriptor<1, true> statDesc; Descriptor &internalDesc{statDesc.descriptor()}; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); auto inCookie{IONAME(BeginInternalArrayListInput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk) << "namelist scalar substring input"; char out[32]; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, out, 0, nullptr, CFI_attribute_pointer); auto outCookie{IONAME(BeginInternalArrayListOutput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(SetDelim)(outCookie, "apostrophe", 10)); ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output"; std::string got{out, sizeof out}; static const std::string expect{" &JUSTA A= 'aBCDEfgh'/ "}; EXPECT_EQ(got, expect); } TEST(NamelistTests, ArraySubstring) { OwningPtr scDesc{ MakeArray(std::vector{2}, std::vector{"abcdefgh", "ijklmnop"}, 8)}; const NamelistGroup::Item items[]{{"a", *scDesc}}; const NamelistGroup group{"justa", 1, items}; static char t1[]{"&justa A(:)(2:+5)='BCDE' 'JKLM'/"}; StaticDescriptor<1, true> statDesc; Descriptor &internalDesc{statDesc.descriptor()}; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); auto inCookie{IONAME(BeginInternalArrayListInput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk) << "namelist scalar substring input"; char out[40]; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, out, 0, nullptr, CFI_attribute_pointer); auto outCookie{IONAME(BeginInternalArrayListOutput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(SetDelim)(outCookie, "apostrophe", 10)); ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output"; std::string got{out, sizeof out}; static const std::string expect{" &JUSTA A= 'aBCDEfgh' 'iJKLMnop'/ "}; EXPECT_EQ(got, expect); } TEST(NamelistTests, Skip) { OwningPtr scDesc{ MakeArray(sizeof(int))>( std::vector{}, std::vector{-1})}; const NamelistGroup::Item items[]{{"j", *scDesc}}; const NamelistGroup group{"nml", 1, items}; static char t1[]{"&skip a='str''ing'/&nml j=123/"}; StaticDescriptor<1, true> statDesc; Descriptor &internalDesc{statDesc.descriptor()}; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); auto inCookie{IONAME(BeginInternalArrayListInput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk) << "namelist input with skipping"; char out[20]; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, out, 0, nullptr, CFI_attribute_pointer); auto outCookie{IONAME(BeginInternalArrayListOutput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output"; std::string got{out, sizeof out}; static const std::string expect{" &NML J= 123/ "}; EXPECT_EQ(got, expect); } // Tests DECIMAL=COMMA mode TEST(NamelistTests, Comma) { OwningPtr scDesc{ MakeArray(sizeof(float))>( std::vector{2}, std::vector>{{}, {}})}; const NamelistGroup::Item items[]{{"z", *scDesc}}; const NamelistGroup group{"nml", 1, items}; static char t1[]{"&nml z=(-1,0;2,0);(-3,0;0,5)/"}; StaticDescriptor<1, true> statDesc; Descriptor &internalDesc{statDesc.descriptor()}; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer); auto inCookie{IONAME(BeginInternalArrayListInput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(SetDecimal)(inCookie, "COMMA", 5)); ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group)); ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk) << "namelist input with skipping"; char out[30]; internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out, out, 0, nullptr, CFI_attribute_pointer); auto outCookie{IONAME(BeginInternalArrayListOutput)( internalDesc, nullptr, 0, __FILE__, __LINE__)}; ASSERT_TRUE(IONAME(SetDecimal)(outCookie, "COMMA", 5)); ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group)); ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output"; std::string got{out, sizeof out}; static const std::string expect{" &NML Z= (-1,;2,) (-3,;,5)/ "}; EXPECT_EQ(got, expect); } // TODO: Internal NAMELIST error tests