//===-- flang/unittests/RuntimeGTest/CommandTest.cpp ----------------------===// // // 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 "flang/Runtime/command.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "flang/Runtime/descriptor.h" #include "flang/Runtime/main.h" #include using namespace Fortran::runtime; template static OwningPtr CreateEmptyCharDescriptor() { OwningPtr descriptor{Descriptor::Create( sizeof(char), n, nullptr, 0, nullptr, CFI_attribute_allocatable)}; if (descriptor->Allocate() != 0) { return nullptr; } return descriptor; } static OwningPtr CharDescriptor(const char *value) { std::size_t n{std::strlen(value)}; OwningPtr descriptor{Descriptor::Create( sizeof(char), n, nullptr, 0, nullptr, CFI_attribute_allocatable)}; if (descriptor->Allocate() != 0) { return nullptr; } std::memcpy(descriptor->OffsetElement(), value, n); return descriptor; } class CommandFixture : public ::testing::Test { protected: CommandFixture(int argc, const char *argv[]) { RTNAME(ProgramStart)(argc, argv, {}); } std::string GetPaddedStr(const char *text, std::size_t len) const { std::string res{text}; assert(res.length() <= len && "No room to pad"); res.append(len - res.length(), ' '); return res; } void CheckDescriptorEqStr( const Descriptor *value, const std::string &expected) const { EXPECT_EQ(std::strncmp(value->OffsetElement(), expected.c_str(), value->ElementBytes()), 0) << "expected: " << expected << "\n" << "value: " << std::string{value->OffsetElement(), value->ElementBytes()}; } template void CheckValue(RuntimeCall F, const char *expectedValue, std::int32_t expectedStatus = 0, const char *expectedErrMsg = "shouldn't change") const { OwningPtr value{CreateEmptyCharDescriptor()}; ASSERT_NE(value, nullptr); OwningPtr errmsg{CharDescriptor(expectedErrMsg)}; std::string expectedValueStr{ GetPaddedStr(expectedValue, value->ElementBytes())}; EXPECT_EQ(F(value.get(), errmsg.get()), expectedStatus); CheckDescriptorEqStr(value.get(), expectedValueStr); CheckDescriptorEqStr(errmsg.get(), expectedErrMsg); } void CheckArgumentValue(const char *expectedValue, int n) const { SCOPED_TRACE(n); SCOPED_TRACE("Checking argument:"); CheckValue( [&](const Descriptor *value, const Descriptor *errmsg) { return RTNAME(ArgumentValue)(n, value, errmsg); }, expectedValue); } void CheckEnvVarValue( const char *expectedValue, const char *name, bool trimName = true) const { SCOPED_TRACE(name); SCOPED_TRACE("Checking environment variable"); CheckValue( [&](const Descriptor *value, const Descriptor *errmsg) { return RTNAME(EnvVariableValue)(*CharDescriptor(name), value, trimName, errmsg, /*sourceFile=*/nullptr, /*line=*/0); }, expectedValue); } void CheckMissingEnvVarValue(const char *name, bool trimName = true) const { SCOPED_TRACE(name); SCOPED_TRACE("Checking missing environment variable"); ASSERT_EQ(nullptr, std::getenv(name)) << "Environment variable " << name << " not expected to exist"; OwningPtr nameDescriptor{CharDescriptor(name)}; EXPECT_EQ(0, RTNAME(EnvVariableLength)(*nameDescriptor, trimName)); CheckValue( [&](const Descriptor *value, const Descriptor *errmsg) { return RTNAME(EnvVariableValue)(*nameDescriptor, value, trimName, errmsg, /*sourceFile=*/nullptr, /*line=*/0); }, "", 1, "Missing environment variable"); } void CheckMissingArgumentValue(int n, const char *errStr = nullptr) const { OwningPtr value{CreateEmptyCharDescriptor()}; ASSERT_NE(value, nullptr); OwningPtr err{errStr ? CreateEmptyCharDescriptor() : nullptr}; EXPECT_GT(RTNAME(ArgumentValue)(n, value.get(), err.get()), 0); std::string spaces(value->ElementBytes(), ' '); CheckDescriptorEqStr(value.get(), spaces); if (errStr) { std::string paddedErrStr(GetPaddedStr(errStr, err->ElementBytes())); CheckDescriptorEqStr(err.get(), paddedErrStr); } } }; static const char *commandOnlyArgv[]{"aProgram"}; class ZeroArguments : public CommandFixture { protected: ZeroArguments() : CommandFixture(1, commandOnlyArgv) {} }; TEST_F(ZeroArguments, ArgumentCount) { EXPECT_EQ(0, RTNAME(ArgumentCount)()); } TEST_F(ZeroArguments, ArgumentLength) { EXPECT_EQ(0, RTNAME(ArgumentLength)(-1)); EXPECT_EQ(8, RTNAME(ArgumentLength)(0)); EXPECT_EQ(0, RTNAME(ArgumentLength)(1)); } TEST_F(ZeroArguments, ArgumentValue) { CheckArgumentValue(commandOnlyArgv[0], 0); } static const char *oneArgArgv[]{"aProgram", "anArgumentOfLength20"}; class OneArgument : public CommandFixture { protected: OneArgument() : CommandFixture(2, oneArgArgv) {} }; TEST_F(OneArgument, ArgumentCount) { EXPECT_EQ(1, RTNAME(ArgumentCount)()); } TEST_F(OneArgument, ArgumentLength) { EXPECT_EQ(0, RTNAME(ArgumentLength)(-1)); EXPECT_EQ(8, RTNAME(ArgumentLength)(0)); EXPECT_EQ(20, RTNAME(ArgumentLength)(1)); EXPECT_EQ(0, RTNAME(ArgumentLength)(2)); } TEST_F(OneArgument, ArgumentValue) { CheckArgumentValue(oneArgArgv[0], 0); CheckArgumentValue(oneArgArgv[1], 1); } static const char *severalArgsArgv[]{ "aProgram", "16-char-long-arg", "", "-22-character-long-arg", "o"}; class SeveralArguments : public CommandFixture { protected: SeveralArguments() : CommandFixture(sizeof(severalArgsArgv) / sizeof(*severalArgsArgv), severalArgsArgv) {} }; TEST_F(SeveralArguments, ArgumentCount) { EXPECT_EQ(4, RTNAME(ArgumentCount)()); } TEST_F(SeveralArguments, ArgumentLength) { EXPECT_EQ(0, RTNAME(ArgumentLength)(-1)); EXPECT_EQ(8, RTNAME(ArgumentLength)(0)); EXPECT_EQ(16, RTNAME(ArgumentLength)(1)); EXPECT_EQ(0, RTNAME(ArgumentLength)(2)); EXPECT_EQ(22, RTNAME(ArgumentLength)(3)); EXPECT_EQ(1, RTNAME(ArgumentLength)(4)); EXPECT_EQ(0, RTNAME(ArgumentLength)(5)); } TEST_F(SeveralArguments, ArgumentValue) { CheckArgumentValue(severalArgsArgv[0], 0); CheckArgumentValue(severalArgsArgv[1], 1); CheckArgumentValue(severalArgsArgv[3], 3); CheckArgumentValue(severalArgsArgv[4], 4); } TEST_F(SeveralArguments, NoArgumentValue) { // Make sure we don't crash if the 'value' and 'error' parameters aren't // passed. EXPECT_EQ(RTNAME(ArgumentValue)(2, nullptr, nullptr), 0); EXPECT_GT(RTNAME(ArgumentValue)(-1, nullptr, nullptr), 0); } TEST_F(SeveralArguments, MissingArguments) { CheckMissingArgumentValue(-1, "Invalid argument number"); CheckMissingArgumentValue(2, "Missing argument"); CheckMissingArgumentValue(5, "Invalid argument number"); CheckMissingArgumentValue(5); } TEST_F(SeveralArguments, ValueTooShort) { OwningPtr tooShort{CreateEmptyCharDescriptor<15>()}; ASSERT_NE(tooShort, nullptr); EXPECT_EQ(RTNAME(ArgumentValue)(1, tooShort.get(), nullptr), -1); CheckDescriptorEqStr(tooShort.get(), severalArgsArgv[1]); OwningPtr errMsg{CreateEmptyCharDescriptor()}; ASSERT_NE(errMsg, nullptr); EXPECT_EQ(RTNAME(ArgumentValue)(1, tooShort.get(), errMsg.get()), -1); std::string expectedErrMsg{ GetPaddedStr("Value too short", errMsg->ElementBytes())}; CheckDescriptorEqStr(errMsg.get(), expectedErrMsg); } TEST_F(SeveralArguments, ErrMsgTooShort) { OwningPtr errMsg{CreateEmptyCharDescriptor<3>()}; EXPECT_GT(RTNAME(ArgumentValue)(-1, nullptr, errMsg.get()), 0); CheckDescriptorEqStr(errMsg.get(), "Inv"); } class EnvironmentVariables : public CommandFixture { protected: EnvironmentVariables() : CommandFixture(0, nullptr) { SetEnv("NAME", "VALUE"); SetEnv("EMPTY", ""); } // If we have access to setenv, we can run some more fine-grained tests. template void SetEnv(const ParamType *name, const ParamType *value, decltype(setenv(name, value, 1)) *Enabled = nullptr) { ASSERT_EQ(0, setenv(name, value, /*overwrite=*/1)); canSetEnv = true; } // Fallback method if setenv is not available. template void SetEnv(const void *, const void *) {} bool EnableFineGrainedTests() const { return canSetEnv; } private: bool canSetEnv{false}; }; TEST_F(EnvironmentVariables, Nonexistent) { CheckMissingEnvVarValue("DOESNT_EXIST"); CheckMissingEnvVarValue(" "); CheckMissingEnvVarValue(""); } TEST_F(EnvironmentVariables, Basic) { // Test a variable that's expected to exist in the environment. char *path{std::getenv("PATH")}; auto expectedLen{static_cast(std::strlen(path))}; EXPECT_EQ(expectedLen, RTNAME(EnvVariableLength)(*CharDescriptor("PATH"))); } TEST_F(EnvironmentVariables, Trim) { if (EnableFineGrainedTests()) { EXPECT_EQ(5, RTNAME(EnvVariableLength)(*CharDescriptor("NAME "))); CheckEnvVarValue("VALUE", "NAME "); } } TEST_F(EnvironmentVariables, NoTrim) { if (EnableFineGrainedTests()) { CheckMissingEnvVarValue("NAME ", /*trim_name=*/false); } } TEST_F(EnvironmentVariables, Empty) { if (EnableFineGrainedTests()) { EXPECT_EQ(0, RTNAME(EnvVariableLength)(*CharDescriptor("EMPTY"))); CheckEnvVarValue("", "EMPTY"); } } TEST_F(EnvironmentVariables, NoValueOrErrmsg) { ASSERT_EQ(std::getenv("DOESNT_EXIST"), nullptr) << "Environment variable DOESNT_EXIST actually exists"; EXPECT_EQ(RTNAME(EnvVariableValue)(*CharDescriptor("DOESNT_EXIST")), 1); if (EnableFineGrainedTests()) { EXPECT_EQ(RTNAME(EnvVariableValue)(*CharDescriptor("NAME")), 0); } } TEST_F(EnvironmentVariables, ValueTooShort) { if (EnableFineGrainedTests()) { OwningPtr tooShort{CreateEmptyCharDescriptor<2>()}; ASSERT_NE(tooShort, nullptr); EXPECT_EQ(RTNAME(EnvVariableValue)(*CharDescriptor("NAME"), tooShort.get(), /*trim_name=*/true, nullptr), -1); CheckDescriptorEqStr(tooShort.get(), "VALUE"); OwningPtr errMsg{CreateEmptyCharDescriptor()}; ASSERT_NE(errMsg, nullptr); EXPECT_EQ(RTNAME(EnvVariableValue)(*CharDescriptor("NAME"), tooShort.get(), /*trim_name=*/true, errMsg.get()), -1); std::string expectedErrMsg{ GetPaddedStr("Value too short", errMsg->ElementBytes())}; CheckDescriptorEqStr(errMsg.get(), expectedErrMsg); } } TEST_F(EnvironmentVariables, ErrMsgTooShort) { ASSERT_EQ(std::getenv("DOESNT_EXIST"), nullptr) << "Environment variable DOESNT_EXIST actually exists"; OwningPtr errMsg{CreateEmptyCharDescriptor<3>()}; EXPECT_EQ(RTNAME(EnvVariableValue)(*CharDescriptor("DOESNT_EXIST"), nullptr, /*trim_name=*/true, errMsg.get()), 1); CheckDescriptorEqStr(errMsg.get(), "Mis"); }