1 //===-- flang/unittests/RuntimeGTest/CommandTest.cpp ----------------------===//
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/Runtime/command.h"
10 #include "gmock/gmock.h"
11 #include "gtest/gtest.h"
12 #include "flang/Runtime/descriptor.h"
13 #include "flang/Runtime/main.h"
14 #include <cstdlib>
15 
16 using namespace Fortran::runtime;
17 
18 template <std::size_t n = 64>
19 static OwningPtr<Descriptor> CreateEmptyCharDescriptor() {
20   OwningPtr<Descriptor> descriptor{Descriptor::Create(
21       sizeof(char), n, nullptr, 0, nullptr, CFI_attribute_allocatable)};
22   if (descriptor->Allocate() != 0) {
23     return nullptr;
24   }
25   return descriptor;
26 }
27 
28 static OwningPtr<Descriptor> CharDescriptor(const char *value) {
29   std::size_t n{std::strlen(value)};
30   OwningPtr<Descriptor> descriptor{Descriptor::Create(
31       sizeof(char), n, nullptr, 0, nullptr, CFI_attribute_allocatable)};
32   if (descriptor->Allocate() != 0) {
33     return nullptr;
34   }
35   std::memcpy(descriptor->OffsetElement(), value, n);
36   return descriptor;
37 }
38 
39 class CommandFixture : public ::testing::Test {
40 protected:
41   CommandFixture(int argc, const char *argv[]) {
42     RTNAME(ProgramStart)(argc, argv, {});
43   }
44 
45   std::string GetPaddedStr(const char *text, std::size_t len) const {
46     std::string res{text};
47     assert(res.length() <= len && "No room to pad");
48     res.append(len - res.length(), ' ');
49     return res;
50   }
51 
52   void CheckDescriptorEqStr(
53       const Descriptor *value, const std::string &expected) const {
54     EXPECT_EQ(std::strncmp(value->OffsetElement(), expected.c_str(),
55                   value->ElementBytes()),
56         0);
57   }
58 
59   void CheckArgumentValue(int n, const char *argv) const {
60     OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()};
61     ASSERT_NE(value, nullptr);
62 
63     std::string expected{GetPaddedStr(argv, value->ElementBytes())};
64 
65     EXPECT_EQ(RTNAME(ArgumentValue)(n, value.get(), nullptr), 0);
66     CheckDescriptorEqStr(value.get(), expected);
67   }
68 
69   void CheckMissingArgumentValue(int n, const char *errStr = nullptr) const {
70     OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()};
71     ASSERT_NE(value, nullptr);
72 
73     OwningPtr<Descriptor> err{errStr ? CreateEmptyCharDescriptor() : nullptr};
74 
75     EXPECT_GT(RTNAME(ArgumentValue)(n, value.get(), err.get()), 0);
76 
77     std::string spaces(value->ElementBytes(), ' ');
78     CheckDescriptorEqStr(value.get(), spaces);
79 
80     if (errStr) {
81       std::string paddedErrStr(GetPaddedStr(errStr, err->ElementBytes()));
82       CheckDescriptorEqStr(err.get(), paddedErrStr);
83     }
84   }
85 };
86 
87 static const char *commandOnlyArgv[]{"aProgram"};
88 class ZeroArguments : public CommandFixture {
89 protected:
90   ZeroArguments() : CommandFixture(1, commandOnlyArgv) {}
91 };
92 
93 TEST_F(ZeroArguments, ArgumentCount) { EXPECT_EQ(0, RTNAME(ArgumentCount)()); }
94 
95 TEST_F(ZeroArguments, ArgumentLength) {
96   EXPECT_EQ(0, RTNAME(ArgumentLength)(-1));
97   EXPECT_EQ(8, RTNAME(ArgumentLength)(0));
98   EXPECT_EQ(0, RTNAME(ArgumentLength)(1));
99 }
100 
101 TEST_F(ZeroArguments, ArgumentValue) {
102   CheckArgumentValue(0, commandOnlyArgv[0]);
103 }
104 
105 static const char *oneArgArgv[]{"aProgram", "anArgumentOfLength20"};
106 class OneArgument : public CommandFixture {
107 protected:
108   OneArgument() : CommandFixture(2, oneArgArgv) {}
109 };
110 
111 TEST_F(OneArgument, ArgumentCount) { EXPECT_EQ(1, RTNAME(ArgumentCount)()); }
112 
113 TEST_F(OneArgument, ArgumentLength) {
114   EXPECT_EQ(0, RTNAME(ArgumentLength)(-1));
115   EXPECT_EQ(8, RTNAME(ArgumentLength)(0));
116   EXPECT_EQ(20, RTNAME(ArgumentLength)(1));
117   EXPECT_EQ(0, RTNAME(ArgumentLength)(2));
118 }
119 
120 TEST_F(OneArgument, ArgumentValue) {
121   CheckArgumentValue(0, oneArgArgv[0]);
122   CheckArgumentValue(1, oneArgArgv[1]);
123 }
124 
125 static const char *severalArgsArgv[]{
126     "aProgram", "16-char-long-arg", "", "-22-character-long-arg", "o"};
127 class SeveralArguments : public CommandFixture {
128 protected:
129   SeveralArguments()
130       : CommandFixture(sizeof(severalArgsArgv) / sizeof(*severalArgsArgv),
131             severalArgsArgv) {}
132 };
133 
134 TEST_F(SeveralArguments, ArgumentCount) {
135   EXPECT_EQ(4, RTNAME(ArgumentCount)());
136 }
137 
138 TEST_F(SeveralArguments, ArgumentLength) {
139   EXPECT_EQ(0, RTNAME(ArgumentLength)(-1));
140   EXPECT_EQ(8, RTNAME(ArgumentLength)(0));
141   EXPECT_EQ(16, RTNAME(ArgumentLength)(1));
142   EXPECT_EQ(0, RTNAME(ArgumentLength)(2));
143   EXPECT_EQ(22, RTNAME(ArgumentLength)(3));
144   EXPECT_EQ(1, RTNAME(ArgumentLength)(4));
145   EXPECT_EQ(0, RTNAME(ArgumentLength)(5));
146 }
147 
148 TEST_F(SeveralArguments, ArgumentValue) {
149   CheckArgumentValue(0, severalArgsArgv[0]);
150   CheckArgumentValue(1, severalArgsArgv[1]);
151   CheckArgumentValue(3, severalArgsArgv[3]);
152   CheckArgumentValue(4, severalArgsArgv[4]);
153 }
154 
155 TEST_F(SeveralArguments, NoArgumentValue) {
156   // Make sure we don't crash if the 'value' and 'error' parameters aren't
157   // passed.
158   EXPECT_EQ(RTNAME(ArgumentValue)(2, nullptr, nullptr), 0);
159   EXPECT_GT(RTNAME(ArgumentValue)(-1, nullptr, nullptr), 0);
160 }
161 
162 TEST_F(SeveralArguments, MissingArguments) {
163   CheckMissingArgumentValue(-1, "Invalid argument number");
164   CheckMissingArgumentValue(2, "Missing argument");
165   CheckMissingArgumentValue(5, "Invalid argument number");
166   CheckMissingArgumentValue(5);
167 }
168 
169 TEST_F(SeveralArguments, ValueTooShort) {
170   OwningPtr<Descriptor> tooShort{CreateEmptyCharDescriptor<15>()};
171   ASSERT_NE(tooShort, nullptr);
172   EXPECT_EQ(RTNAME(ArgumentValue)(1, tooShort.get(), nullptr), -1);
173   CheckDescriptorEqStr(tooShort.get(), severalArgsArgv[1]);
174 
175   OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor()};
176   ASSERT_NE(errMsg, nullptr);
177 
178   EXPECT_EQ(RTNAME(ArgumentValue)(1, tooShort.get(), errMsg.get()), -1);
179 
180   std::string expectedErrMsg{
181       GetPaddedStr("Value too short", errMsg->ElementBytes())};
182   CheckDescriptorEqStr(errMsg.get(), expectedErrMsg);
183 }
184 
185 TEST_F(SeveralArguments, ErrMsgTooShort) {
186   OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor<3>()};
187   EXPECT_GT(RTNAME(ArgumentValue)(-1, nullptr, errMsg.get()), 0);
188   CheckDescriptorEqStr(errMsg.get(), "Inv");
189 }
190 
191 class EnvironmentVariables : public CommandFixture {
192 protected:
193   EnvironmentVariables() : CommandFixture(0, nullptr) {
194     SetEnv("NAME", "VALUE");
195   }
196 
197   // If we have access to setenv, we can run some more fine-grained tests.
198   template <typename ParamType = char>
199   void SetEnv(const ParamType *name, const ParamType *value,
200       decltype(setenv(name, value, 1)) *Enabled = nullptr) {
201     ASSERT_EQ(0, setenv(name, value, /*overwrite=*/1));
202     canSetEnv = true;
203   }
204 
205   // Fallback method if setenv is not available.
206   template <typename Unused = void> void SetEnv(const void *, const void *) {}
207 
208   bool EnableFineGrainedTests() const { return canSetEnv; }
209 
210 private:
211   bool canSetEnv{false};
212 };
213 
214 TEST_F(EnvironmentVariables, Length) {
215   EXPECT_EQ(0, RTNAME(EnvVariableLength)(*CharDescriptor("DOESNT_EXIST")));
216 
217   EXPECT_EQ(0, RTNAME(EnvVariableLength)(*CharDescriptor("     ")));
218   EXPECT_EQ(0, RTNAME(EnvVariableLength)(*CharDescriptor("")));
219 
220   // Test a variable that's expected to exist in the environment.
221   char *path{std::getenv("PATH")};
222   auto expectedLen{static_cast<int64_t>(std::strlen(path))};
223   EXPECT_EQ(expectedLen, RTNAME(EnvVariableLength)(*CharDescriptor("PATH")));
224 
225   if (EnableFineGrainedTests()) {
226     EXPECT_EQ(5, RTNAME(EnvVariableLength)(*CharDescriptor("NAME")));
227 
228     EXPECT_EQ(5, RTNAME(EnvVariableLength)(*CharDescriptor("NAME  ")));
229     EXPECT_EQ(0,
230         RTNAME(EnvVariableLength)(
231             *CharDescriptor("NAME "), /*trim_name=*/false));
232   }
233 }
234