//===-- flang/unittests/Runtime/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/execute.h" #include "flang/Runtime/extensions.h" #include "flang/Runtime/main.h" #include #include #if _REENTRANT || _POSIX_C_SOURCE >= 199506L #include // LOGIN_NAME_MAX used in getlog test #endif 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; } template static OwningPtr EmptyIntDescriptor() { OwningPtr descriptor{Descriptor::Create(TypeCategory::Integer, kind, nullptr, 0, nullptr, CFI_attribute_allocatable)}; if (descriptor->Allocate() != 0) { return nullptr; } return descriptor; } template static OwningPtr IntDescriptor(const int &value) { OwningPtr descriptor{Descriptor::Create(TypeCategory::Integer, kind, nullptr, 0, nullptr, CFI_attribute_allocatable)}; if (descriptor->Allocate() != 0) { return nullptr; } std::memcpy(descriptor->OffsetElement(), &value, sizeof(int)); 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 CheckCharEqStr(const char *value, const std::string &expected) const { ASSERT_NE(value, nullptr); EXPECT_EQ(std::strncmp(value, expected.c_str(), expected.size()), 0) << "expected: " << expected << "\n" << "value: " << value; } void CheckDescriptorEqStr( const Descriptor *value, const std::string &expected) const { ASSERT_NE(value, nullptr); EXPECT_EQ(std::strncmp(value->OffsetElement(), expected.c_str(), value->ElementBytes()), 0) << "expected: " << expected << "\n" << "value: " << std::string{value->OffsetElement(), value->ElementBytes()}; } template void CheckDescriptorEqInt( const Descriptor *value, const INT_T expected) const { if (expected != -1) { ASSERT_NE(value, nullptr); EXPECT_EQ(*value->OffsetElement(), expected); } } template void CheckValue(RuntimeCall F, const char *expectedValue, std::int64_t expectedLength = -1, std::int32_t expectedStatus = 0, const char *expectedErrMsg = "shouldn't change") const { OwningPtr value{CreateEmptyCharDescriptor()}; ASSERT_NE(value, nullptr); OwningPtr length{ expectedLength == -1 ? nullptr : EmptyIntDescriptor()}; OwningPtr errmsg{CharDescriptor(expectedErrMsg)}; ASSERT_NE(errmsg, nullptr); std::string expectedValueStr{ GetPaddedStr(expectedValue, value->ElementBytes())}; EXPECT_EQ(F(value.get(), length.get(), errmsg.get()), expectedStatus); CheckDescriptorEqStr(value.get(), expectedValueStr); CheckDescriptorEqInt(length.get(), expectedLength); 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 *length, const Descriptor *errmsg) { return RTNAME(GetCommandArgument)(n, value, length, errmsg); }, expectedValue, std::strlen(expectedValue)); } void CheckCommandValue(const char *args[], int n) const { SCOPED_TRACE("Checking command:"); ASSERT_GE(n, 1); std::string expectedValue{args[0]}; for (int i = 1; i < n; i++) { expectedValue += " " + std::string{args[i]}; } CheckValue( [&](const Descriptor *value, const Descriptor *length, const Descriptor *errmsg) { return RTNAME(GetCommand)(value, length, errmsg); }, expectedValue.c_str(), expectedValue.size()); } 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 *length, const Descriptor *errmsg) { return RTNAME(GetEnvVariable)( *CharDescriptor(name), value, length, trimName, errmsg); }, expectedValue, std::strlen(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"; CheckValue( [&](const Descriptor *value, const Descriptor *length, const Descriptor *errmsg) { return RTNAME(GetEnvVariable)( *CharDescriptor(name), value, length, trimName, errmsg); }, "", 0, 1, "Missing environment variable"); } void CheckMissingArgumentValue(int n, const char *errStr = nullptr) const { OwningPtr value{CreateEmptyCharDescriptor()}; ASSERT_NE(value, nullptr); OwningPtr length{EmptyIntDescriptor()}; ASSERT_NE(length, nullptr); OwningPtr err{errStr ? CreateEmptyCharDescriptor() : nullptr}; EXPECT_GT( RTNAME(GetCommandArgument)(n, value.get(), length.get(), err.get()), 0); std::string spaces(value->ElementBytes(), ' '); CheckDescriptorEqStr(value.get(), spaces); CheckDescriptorEqInt(length.get(), 0); if (errStr) { std::string paddedErrStr(GetPaddedStr(errStr, err->ElementBytes())); CheckDescriptorEqStr(err.get(), paddedErrStr); } } void CheckMissingCommandValue(const char *errStr = nullptr) const { OwningPtr value{CreateEmptyCharDescriptor()}; ASSERT_NE(value, nullptr); OwningPtr length{EmptyIntDescriptor()}; ASSERT_NE(length, nullptr); OwningPtr err{errStr ? CreateEmptyCharDescriptor() : nullptr}; EXPECT_GT(RTNAME(GetCommand)(value.get(), length.get(), err.get()), 0); std::string spaces(value->ElementBytes(), ' '); CheckDescriptorEqStr(value.get(), spaces); CheckDescriptorEqInt(length.get(), 0); if (errStr) { std::string paddedErrStr(GetPaddedStr(errStr, err->ElementBytes())); CheckDescriptorEqStr(err.get(), paddedErrStr); } } }; class NoArgv : public CommandFixture { protected: NoArgv() : CommandFixture(0, nullptr) {} }; #if _WIN32 || _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || \ _SVID_SOURCE || defined(_POSIX_SOURCE) TEST_F(NoArgv, FdateGetDate) { char input[]{"24LengthCharIsJustRight"}; const std::size_t charLen = sizeof(input); FORTRAN_PROCEDURE_NAME(fdate)(input, charLen); // Tue May 26 21:51:03 2015\n\0 // index at 3, 7, 10, 19 should be space // when date is less than two digit, index 8 would be space // Tue May 6 21:51:03 2015\n\0 for (std::size_t i{0}; i < charLen; i++) { if (i == 8) continue; if (i == 3 || i == 7 || i == 10 || i == 19) { EXPECT_EQ(input[i], ' '); continue; } EXPECT_NE(input[i], ' '); } } TEST_F(NoArgv, FdateGetDateTooShort) { char input[]{"TooShortAllPadSpace"}; const std::size_t charLen = sizeof(input); FORTRAN_PROCEDURE_NAME(fdate)(input, charLen); for (std::size_t i{0}; i < charLen; i++) { EXPECT_EQ(input[i], ' '); } } TEST_F(NoArgv, FdateGetDatePadSpace) { char input[]{"All char after 23 pad spaces"}; const std::size_t charLen = sizeof(input); FORTRAN_PROCEDURE_NAME(fdate)(input, charLen); for (std::size_t i{24}; i < charLen; i++) { EXPECT_EQ(input[i], ' '); } } #else TEST_F(NoArgv, FdateNotSupported) { char input[]{"No change due to crash"}; EXPECT_DEATH(FORTRAN_PROCEDURE_NAME(fdate)(input, sizeof(input)), "fdate is not supported."); CheckCharEqStr(input, "No change due to crash"); } #endif // TODO: Test other intrinsics with this fixture. TEST_F(NoArgv, GetCommand) { CheckMissingCommandValue(); } 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, GetCommandArgument) { CheckMissingArgumentValue(-1); CheckArgumentValue(commandOnlyArgv[0], 0); CheckMissingArgumentValue(1); } TEST_F(ZeroArguments, GetCommand) { CheckCommandValue(commandOnlyArgv, 1); } TEST_F(ZeroArguments, ECLValidCommandAndPadSync) { OwningPtr command{CharDescriptor("echo hi")}; bool wait{true}; OwningPtr exitStat{IntDescriptor(404)}; OwningPtr cmdStat{IntDescriptor(202)}; OwningPtr cmdMsg{CharDescriptor("No change")}; RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); std::string spaces(cmdMsg->ElementBytes(), ' '); CheckDescriptorEqInt(exitStat.get(), 0); CheckDescriptorEqInt(cmdStat.get(), 0); CheckDescriptorEqStr(cmdMsg.get(), "No change"); } TEST_F(ZeroArguments, ECLValidCommandStatusSetSync) { OwningPtr command{CharDescriptor("echo hi")}; bool wait{true}; OwningPtr exitStat{IntDescriptor(404)}; OwningPtr cmdStat{IntDescriptor(202)}; OwningPtr cmdMsg{CharDescriptor("No change")}; RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); CheckDescriptorEqInt(exitStat.get(), 0); CheckDescriptorEqInt(cmdStat.get(), 0); CheckDescriptorEqStr(cmdMsg.get(), "No change"); } TEST_F(ZeroArguments, ECLGeneralErrorCommandErrorSync) { OwningPtr command{CharDescriptor(NOT_EXE)}; bool wait{true}; OwningPtr exitStat{IntDescriptor(404)}; OwningPtr cmdStat{IntDescriptor(202)}; OwningPtr cmdMsg{CharDescriptor("cmd msg buffer XXXXXXXXXXXXXX")}; RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); CheckDescriptorEqInt(exitStat.get(), 1); #if defined(_WIN32) CheckDescriptorEqInt(cmdStat.get(), 6); CheckDescriptorEqStr(cmdMsg.get(), "Invalid command lineXXXXXXXXX"); #else CheckDescriptorEqInt(cmdStat.get(), 3); CheckDescriptorEqStr(cmdMsg.get(), "Command line execution failed"); #endif } TEST_F(ZeroArguments, ECLNotExecutedCommandErrorSync) { OwningPtr command{CharDescriptor( "touch NotExecutedCommandFile && chmod -x NotExecutedCommandFile && " "./NotExecutedCommandFile")}; bool wait{true}; OwningPtr exitStat{IntDescriptor(404)}; OwningPtr cmdStat{IntDescriptor(202)}; OwningPtr cmdMsg{CharDescriptor("cmd msg buffer XXXXXXXX")}; RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); #ifdef _WIN32 CheckDescriptorEqInt(exitStat.get(), 1); CheckDescriptorEqInt(cmdStat.get(), 6); CheckDescriptorEqStr(cmdMsg.get(), "Invalid command lineXXX"); #else CheckDescriptorEqInt(exitStat.get(), 126); CheckDescriptorEqInt(cmdStat.get(), 4); CheckDescriptorEqStr(cmdMsg.get(), "Command cannot be execu"); // removing the file only on Linux (file is not created on Win) OwningPtr commandClean{ CharDescriptor("rm -f NotExecutedCommandFile")}; OwningPtr cmdMsgNoErr{CharDescriptor("No Error")}; RTNAME(ExecuteCommandLine) (*commandClean.get(), wait, exitStat.get(), cmdStat.get(), cmdMsgNoErr.get()); CheckDescriptorEqInt(exitStat.get(), 0); CheckDescriptorEqStr(cmdMsgNoErr.get(), "No Error"); CheckDescriptorEqInt(cmdStat.get(), 0); #endif } TEST_F(ZeroArguments, ECLNotFoundCommandErrorSync) { OwningPtr command{CharDescriptor("NotFoundCommand")}; bool wait{true}; OwningPtr exitStat{IntDescriptor(404)}; OwningPtr cmdStat{IntDescriptor(202)}; OwningPtr cmdMsg{CharDescriptor("unmodified buffer XXXXXXXXX")}; RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); #ifdef _WIN32 CheckDescriptorEqInt(exitStat.get(), 1); CheckDescriptorEqInt(cmdStat.get(), 6); CheckDescriptorEqStr(cmdMsg.get(), "Invalid command lineXXXXXXX"); #else CheckDescriptorEqInt(exitStat.get(), 127); CheckDescriptorEqInt(cmdStat.get(), 5); CheckDescriptorEqStr(cmdMsg.get(), "Command not found with exit"); #endif } TEST_F(ZeroArguments, ECLInvalidCommandTerminatedSync) { OwningPtr command{CharDescriptor("InvalidCommand")}; bool wait{true}; OwningPtr cmdMsg{CharDescriptor("No Change")}; #ifdef _WIN32 EXPECT_DEATH(RTNAME(ExecuteCommandLine)( *command.get(), wait, nullptr, nullptr, cmdMsg.get()), "Invalid command quit with exit status code: 1"); #else EXPECT_DEATH(RTNAME(ExecuteCommandLine)( *command.get(), wait, nullptr, nullptr, cmdMsg.get()), "Command not found with exit code: 127."); #endif CheckDescriptorEqStr(cmdMsg.get(), "No Change"); } TEST_F(ZeroArguments, ECLValidCommandAndExitStatNoChangeAndCMDStatusSetAsync) { OwningPtr command{CharDescriptor("echo hi")}; bool wait{false}; OwningPtr exitStat{IntDescriptor(404)}; OwningPtr cmdStat{IntDescriptor(202)}; OwningPtr cmdMsg{CharDescriptor("No change")}; RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); CheckDescriptorEqInt(exitStat.get(), 404); CheckDescriptorEqInt(cmdStat.get(), 0); CheckDescriptorEqStr(cmdMsg.get(), "No change"); } TEST_F(ZeroArguments, ECLInvalidCommandParentNotTerminatedAsync) { OwningPtr command{CharDescriptor("InvalidCommand")}; bool wait{false}; OwningPtr cmdMsg{CharDescriptor("No change")}; EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( *command.get(), wait, nullptr, nullptr, cmdMsg.get())); CheckDescriptorEqStr(cmdMsg.get(), "No change"); } TEST_F(ZeroArguments, ECLInvalidCommandAsyncDontAffectSync) { OwningPtr command{CharDescriptor("echo hi")}; EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( *command.get(), false, nullptr, nullptr, nullptr)); EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( *command.get(), true, nullptr, nullptr, nullptr)); } TEST_F(ZeroArguments, ECLInvalidCommandAsyncDontAffectAsync) { OwningPtr command{CharDescriptor("echo hi")}; EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( *command.get(), false, nullptr, nullptr, nullptr)); EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( *command.get(), false, nullptr, nullptr, nullptr)); } TEST_F(ZeroArguments, SystemValidCommandExitStat) { // envrionment setup for SYSTEM from EXECUTE_COMMAND_LINE runtime OwningPtr cmdStat{IntDescriptor(202)}; bool wait{true}; // setup finished OwningPtr command{CharDescriptor("echo hi")}; OwningPtr exitStat{EmptyIntDescriptor()}; RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), nullptr); CheckDescriptorEqInt(exitStat.get(), 0); } TEST_F(ZeroArguments, SystemInvalidCommandExitStat) { // envrionment setup for SYSTEM from EXECUTE_COMMAND_LINE runtime OwningPtr cmdStat{IntDescriptor(202)}; bool wait{true}; // setup finished OwningPtr command{CharDescriptor("InvalidCommand")}; OwningPtr exitStat{EmptyIntDescriptor()}; RTNAME(ExecuteCommandLine) (*command.get(), wait, exitStat.get(), cmdStat.get(), nullptr); #ifdef _WIN32 CheckDescriptorEqInt(exitStat.get(), 1); #else CheckDescriptorEqInt(exitStat.get(), 127); #endif } TEST_F(ZeroArguments, SystemValidCommandOptionalExitStat) { // envrionment setup for SYSTEM from EXECUTE_COMMAND_LINE runtime OwningPtr cmdStat{IntDescriptor(202)}; bool wait{true}; // setup finished OwningPtr command{CharDescriptor("echo hi")}; EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( *command.get(), wait, nullptr, cmdStat.get(), nullptr)); } TEST_F(ZeroArguments, SystemInvalidCommandOptionalExitStat) { // envrionment setup for SYSTEM from EXECUTE_COMMAND_LINE runtime OwningPtr cmdStat{IntDescriptor(202)}; bool wait{true}; // setup finished OwningPtr command{CharDescriptor("InvalidCommand")}; EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( *command.get(), wait, nullptr, cmdStat.get(), nullptr);); } 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, GetCommandArgument) { CheckMissingArgumentValue(-1); CheckArgumentValue(oneArgArgv[0], 0); CheckArgumentValue(oneArgArgv[1], 1); CheckMissingArgumentValue(2); } TEST_F(OneArgument, GetCommand) { CheckCommandValue(oneArgArgv, 2); } 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, GetCommandArgument) { 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', 'length' and 'error' parameters // aren't passed. EXPECT_GT(RTNAME(GetCommandArgument)(2), 0); EXPECT_EQ(RTNAME(GetCommandArgument)(1), 0); EXPECT_GT(RTNAME(GetCommandArgument)(-1), 0); } TEST_F(SeveralArguments, MissingArguments) { CheckMissingArgumentValue(-1, "Invalid argument number"); CheckMissingArgumentValue(2, "Missing argument"); CheckMissingArgumentValue(5, "Invalid argument number"); CheckMissingArgumentValue(5); } TEST_F(SeveralArguments, ArgValueTooShort) { OwningPtr tooShort{CreateEmptyCharDescriptor<15>()}; ASSERT_NE(tooShort, nullptr); EXPECT_EQ(RTNAME(GetCommandArgument)(1, tooShort.get()), -1); CheckDescriptorEqStr(tooShort.get(), severalArgsArgv[1]); OwningPtr length{EmptyIntDescriptor()}; ASSERT_NE(length, nullptr); OwningPtr errMsg{CreateEmptyCharDescriptor()}; ASSERT_NE(errMsg, nullptr); EXPECT_EQ( RTNAME(GetCommandArgument)(1, tooShort.get(), length.get(), errMsg.get()), -1); CheckDescriptorEqInt(length.get(), 16); std::string expectedErrMsg{ GetPaddedStr("Value too short", errMsg->ElementBytes())}; CheckDescriptorEqStr(errMsg.get(), expectedErrMsg); } TEST_F(SeveralArguments, ArgErrMsgTooShort) { OwningPtr errMsg{CreateEmptyCharDescriptor<3>()}; EXPECT_GT(RTNAME(GetCommandArgument)(-1, nullptr, nullptr, errMsg.get()), 0); CheckDescriptorEqStr(errMsg.get(), "Inv"); } TEST_F(SeveralArguments, GetCommand) { CheckMissingCommandValue(); CheckMissingCommandValue("Missing argument"); } TEST_F(SeveralArguments, CommandErrMsgTooShort) { OwningPtr value{CreateEmptyCharDescriptor()}; OwningPtr length{EmptyIntDescriptor()}; OwningPtr errMsg{CreateEmptyCharDescriptor<3>()}; EXPECT_GT(RTNAME(GetCommand)(value.get(), length.get(), errMsg.get()), 0); std::string spaces(value->ElementBytes(), ' '); CheckDescriptorEqStr(value.get(), spaces); CheckDescriptorEqInt(length.get(), 0); CheckDescriptorEqStr(errMsg.get(), "Mis"); } TEST_F(SeveralArguments, GetCommandCanTakeNull) { EXPECT_GT(RTNAME(GetCommand)(nullptr, nullptr, nullptr), 0); } static const char *onlyValidArgsArgv[]{ "aProgram", "-f", "has/a/few/slashes", "has\\a\\few\\backslashes"}; class OnlyValidArguments : public CommandFixture { protected: OnlyValidArguments() : CommandFixture(sizeof(onlyValidArgsArgv) / sizeof(*onlyValidArgsArgv), onlyValidArgsArgv) {} }; TEST_F(OnlyValidArguments, GetCommand) { CheckCommandValue(onlyValidArgsArgv, 4); } TEST_F(OnlyValidArguments, CommandValueTooShort) { OwningPtr tooShort{CreateEmptyCharDescriptor<50>()}; ASSERT_NE(tooShort, nullptr); OwningPtr length{EmptyIntDescriptor()}; ASSERT_NE(length, nullptr); EXPECT_EQ(RTNAME(GetCommand)(tooShort.get(), length.get(), nullptr), -1); CheckDescriptorEqStr( tooShort.get(), "aProgram -f has/a/few/slashes has\\a\\few\\backslashe"); CheckDescriptorEqInt(length.get(), 51); OwningPtr errMsg{CreateEmptyCharDescriptor()}; ASSERT_NE(errMsg, nullptr); EXPECT_EQ(-1, RTNAME(GetCommand)(tooShort.get(), nullptr, errMsg.get())); std::string expectedErrMsg{ GetPaddedStr("Value too short", errMsg->ElementBytes())}; CheckDescriptorEqStr(errMsg.get(), expectedErrMsg); } TEST_F(OnlyValidArguments, GetCommandCanTakeNull) { EXPECT_EQ(0, RTNAME(GetCommand)(nullptr, nullptr, nullptr)); OwningPtr value{CreateEmptyCharDescriptor()}; ASSERT_NE(value, nullptr); OwningPtr length{EmptyIntDescriptor()}; ASSERT_NE(length, nullptr); EXPECT_EQ(0, RTNAME(GetCommand)(value.get(), nullptr, nullptr)); CheckDescriptorEqStr(value.get(), GetPaddedStr("aProgram -f has/a/few/slashes has\\a\\few\\backslashes", value->ElementBytes())); EXPECT_EQ(0, RTNAME(GetCommand)(nullptr, length.get(), nullptr)); CheckDescriptorEqInt(length.get(), 51); } TEST_F(OnlyValidArguments, GetCommandShortLength) { OwningPtr length{EmptyIntDescriptor()}; ASSERT_NE(length, nullptr); EXPECT_EQ(0, RTNAME(GetCommand)(nullptr, length.get(), nullptr)); CheckDescriptorEqInt(length.get(), 51); } TEST_F(ZeroArguments, GetPID) { // pid should always greater than 0, in both linux and windows EXPECT_GT(RTNAME(GetPID)(), 0); } class EnvironmentVariables : public CommandFixture { protected: EnvironmentVariables() : CommandFixture(0, nullptr) { SetEnv("NAME", "VALUE"); #ifdef _WIN32 SetEnv("USERNAME", "loginName"); #else SetEnv("LOGNAME", "loginName"); #endif 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))}; OwningPtr length{EmptyIntDescriptor()}; EXPECT_EQ(0, RTNAME(GetEnvVariable)(*CharDescriptor("PATH"), /*value=*/nullptr, length.get())); CheckDescriptorEqInt(length.get(), expectedLen); } TEST_F(EnvironmentVariables, Trim) { if (EnableFineGrainedTests()) { CheckEnvVarValue("VALUE", "NAME "); } } TEST_F(EnvironmentVariables, NoTrim) { if (EnableFineGrainedTests()) { CheckMissingEnvVarValue("NAME ", /*trim_name=*/false); } } TEST_F(EnvironmentVariables, Empty) { if (EnableFineGrainedTests()) { CheckEnvVarValue("", "EMPTY"); } } TEST_F(EnvironmentVariables, NoValueOrErrmsg) { ASSERT_EQ(std::getenv("DOESNT_EXIST"), nullptr) << "Environment variable DOESNT_EXIST actually exists"; EXPECT_EQ(RTNAME(GetEnvVariable)(*CharDescriptor("DOESNT_EXIST")), 1); if (EnableFineGrainedTests()) { EXPECT_EQ(RTNAME(GetEnvVariable)(*CharDescriptor("NAME")), 0); } } TEST_F(EnvironmentVariables, ValueTooShort) { if (EnableFineGrainedTests()) { OwningPtr tooShort{CreateEmptyCharDescriptor<2>()}; ASSERT_NE(tooShort, nullptr); EXPECT_EQ(RTNAME(GetEnvVariable)(*CharDescriptor("NAME"), tooShort.get(), /*length=*/nullptr, /*trim_name=*/true, nullptr), -1); CheckDescriptorEqStr(tooShort.get(), "VALUE"); OwningPtr errMsg{CreateEmptyCharDescriptor()}; ASSERT_NE(errMsg, nullptr); EXPECT_EQ(RTNAME(GetEnvVariable)(*CharDescriptor("NAME"), tooShort.get(), /*length=*/nullptr, /*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(GetEnvVariable)(*CharDescriptor("DOESNT_EXIST"), nullptr, /*length=*/nullptr, /*trim_name=*/true, errMsg.get()), 1); CheckDescriptorEqStr(errMsg.get(), "Mis"); } // username first char must not be null TEST_F(EnvironmentVariables, GetlogGetName) { const int charLen{3}; char input[charLen]{"\0\0"}; FORTRAN_PROCEDURE_NAME(getlog)(input, charLen); EXPECT_NE(input[0], '\0'); } #if _REENTRANT || _POSIX_C_SOURCE >= 199506L TEST_F(EnvironmentVariables, GetlogPadSpace) { // guarantee 1 char longer than max, last char should be pad space int charLen; #ifdef LOGIN_NAME_MAX charLen = LOGIN_NAME_MAX + 2; #else charLen = sysconf(_SC_LOGIN_NAME_MAX) + 2; if (charLen == -1) charLen = _POSIX_LOGIN_NAME_MAX + 2; #endif std::vector input(charLen); FORTRAN_PROCEDURE_NAME(getlog)(input.data(), charLen); EXPECT_EQ(input[charLen - 1], ' '); } #endif #ifdef _WIN32 // Test ability to get name from environment variable TEST_F(EnvironmentVariables, GetlogEnvGetName) { if (EnableFineGrainedTests()) { ASSERT_NE(std::getenv("USERNAME"), nullptr) << "Environment variable USERNAME does not exist"; char input[]{"XXXXXXXXX"}; FORTRAN_PROCEDURE_NAME(getlog)(input, sizeof(input)); CheckCharEqStr(input, "loginName"); } } TEST_F(EnvironmentVariables, GetlogEnvBufferShort) { if (EnableFineGrainedTests()) { ASSERT_NE(std::getenv("USERNAME"), nullptr) << "Environment variable USERNAME does not exist"; char input[]{"XXXXXX"}; FORTRAN_PROCEDURE_NAME(getlog)(input, sizeof(input)); CheckCharEqStr(input, "loginN"); } } TEST_F(EnvironmentVariables, GetlogEnvPadSpace) { if (EnableFineGrainedTests()) { ASSERT_NE(std::getenv("USERNAME"), nullptr) << "Environment variable USERNAME does not exist"; char input[]{"XXXXXXXXXX"}; FORTRAN_PROCEDURE_NAME(getlog)(input, sizeof(input)); CheckCharEqStr(input, "loginName "); } } #endif