1 | //===-- unittests/Runtime/CommandTest.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 "flang/Runtime/command.h" |
10 | #include "gmock/gmock.h" |
11 | #include "gtest/gtest.h" |
12 | #include "flang-rt/runtime/descriptor.h" |
13 | #include "flang/Runtime/execute.h" |
14 | #include "flang/Runtime/extensions.h" |
15 | #include "flang/Runtime/main.h" |
16 | #include <cstddef> |
17 | #include <cstdlib> |
18 | |
19 | #if _REENTRANT || _POSIX_C_SOURCE >= 199506L |
20 | #include <limits.h> // LOGIN_NAME_MAX used in getlog test |
21 | #endif |
22 | |
23 | using namespace Fortran::runtime; |
24 | |
25 | template <std::size_t n = 64> |
26 | static OwningPtr<Descriptor> CreateEmptyCharDescriptor() { |
27 | OwningPtr<Descriptor> descriptor{Descriptor::Create( |
28 | sizeof(char), n, nullptr, 0, nullptr, CFI_attribute_allocatable)}; |
29 | if (descriptor->Allocate(kNoAsyncObject) != 0) { |
30 | return nullptr; |
31 | } |
32 | return descriptor; |
33 | } |
34 | |
35 | static OwningPtr<Descriptor> CharDescriptor(const char *value) { |
36 | std::size_t n{std::strlen(s: value)}; |
37 | OwningPtr<Descriptor> descriptor{Descriptor::Create( |
38 | sizeof(char), n, nullptr, 0, nullptr, CFI_attribute_allocatable)}; |
39 | if (descriptor->Allocate(kNoAsyncObject) != 0) { |
40 | return nullptr; |
41 | } |
42 | std::memcpy(dest: descriptor->OffsetElement(), src: value, n: n); |
43 | return descriptor; |
44 | } |
45 | |
46 | template <int kind = sizeof(std::int64_t)> |
47 | static OwningPtr<Descriptor> EmptyIntDescriptor() { |
48 | OwningPtr<Descriptor> descriptor{Descriptor::Create(TypeCategory::Integer, |
49 | kind, nullptr, 0, nullptr, CFI_attribute_allocatable)}; |
50 | if (descriptor->Allocate(kNoAsyncObject) != 0) { |
51 | return nullptr; |
52 | } |
53 | return descriptor; |
54 | } |
55 | |
56 | template <int kind = sizeof(std::int64_t)> |
57 | static OwningPtr<Descriptor> IntDescriptor(const int &value) { |
58 | OwningPtr<Descriptor> descriptor{Descriptor::Create(TypeCategory::Integer, |
59 | kind, nullptr, 0, nullptr, CFI_attribute_allocatable)}; |
60 | if (descriptor->Allocate(kNoAsyncObject) != 0) { |
61 | return nullptr; |
62 | } |
63 | std::memcpy(dest: descriptor->OffsetElement<int>(), src: &value, n: sizeof(int)); |
64 | return descriptor; |
65 | } |
66 | |
67 | class CommandFixture : public ::testing::Test { |
68 | protected: |
69 | CommandFixture(int argc, const char *argv[]) { |
70 | RTNAME(ProgramStart)(argc, argv, {}, {}); |
71 | } |
72 | |
73 | std::string GetPaddedStr(const char *text, std::size_t len) const { |
74 | std::string res{text}; |
75 | assert(res.length() <= len && "No room to pad" ); |
76 | res.append(n: len - res.length(), c: ' '); |
77 | return res; |
78 | } |
79 | |
80 | void CheckCharEqStr(const char *value, const std::string &expected) const { |
81 | ASSERT_NE(value, nullptr); |
82 | EXPECT_EQ(std::strncmp(value, expected.c_str(), expected.size()), 0) |
83 | << "expected: " << expected << "\n" |
84 | << "value: " << value; |
85 | } |
86 | |
87 | void CheckDescriptorEqStr( |
88 | const Descriptor *value, const std::string &expected) const { |
89 | ASSERT_NE(value, nullptr); |
90 | EXPECT_EQ(std::strncmp(value->OffsetElement(), expected.c_str(), |
91 | value->ElementBytes()), |
92 | 0) |
93 | << "expected: " << expected << "\n" |
94 | << "value: " |
95 | << std::string{value->OffsetElement(), value->ElementBytes()}; |
96 | } |
97 | |
98 | template <typename INT_T = std::int64_t> |
99 | void CheckDescriptorEqInt( |
100 | const Descriptor *value, const INT_T expected) const { |
101 | if (expected != -1) { |
102 | ASSERT_NE(value, nullptr); |
103 | EXPECT_EQ(*value->OffsetElement<INT_T>(), expected); |
104 | } |
105 | } |
106 | |
107 | template <typename RuntimeCall> |
108 | void CheckValue(RuntimeCall F, const char *expectedValue, |
109 | std::int64_t expectedLength = -1, std::int32_t expectedStatus = 0, |
110 | const char *expectedErrMsg = "shouldn't change" ) const { |
111 | OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()}; |
112 | ASSERT_NE(value, nullptr); |
113 | |
114 | OwningPtr<Descriptor> length{ |
115 | expectedLength == -1 ? nullptr : EmptyIntDescriptor()}; |
116 | |
117 | OwningPtr<Descriptor> errmsg{CharDescriptor(expectedErrMsg)}; |
118 | ASSERT_NE(errmsg, nullptr); |
119 | |
120 | std::string expectedValueStr{ |
121 | GetPaddedStr(text: expectedValue, len: value->ElementBytes())}; |
122 | |
123 | EXPECT_EQ(F(value.get(), length.get(), errmsg.get()), expectedStatus); |
124 | CheckDescriptorEqStr(value.get(), expectedValueStr); |
125 | CheckDescriptorEqInt(length.get(), expectedLength); |
126 | CheckDescriptorEqStr(errmsg.get(), expectedErrMsg); |
127 | } |
128 | |
129 | void CheckArgumentValue(const char *expectedValue, int n) const { |
130 | SCOPED_TRACE(n); |
131 | SCOPED_TRACE("Checking argument:" ); |
132 | CheckValue( |
133 | F: [&](const Descriptor *value, const Descriptor *length, |
134 | const Descriptor *errmsg) { |
135 | return RTNAME(GetCommandArgument)(n, value, length, errmsg); |
136 | }, |
137 | expectedValue, expectedLength: std::strlen(s: expectedValue)); |
138 | } |
139 | |
140 | void CheckCommandValue(const char *args[], int n) const { |
141 | SCOPED_TRACE("Checking command:" ); |
142 | ASSERT_GE(n, 1); |
143 | std::string expectedValue{args[0]}; |
144 | for (int i = 1; i < n; i++) { |
145 | expectedValue += " " + std::string{args[i]}; |
146 | } |
147 | CheckValue( |
148 | F: [&](const Descriptor *value, const Descriptor *length, |
149 | const Descriptor *errmsg) { |
150 | return RTNAME(GetCommand)(value, length, errmsg); |
151 | }, |
152 | expectedValue: expectedValue.c_str(), expectedLength: expectedValue.size()); |
153 | } |
154 | |
155 | void CheckEnvVarValue( |
156 | const char *expectedValue, const char *name, bool trimName = true) const { |
157 | SCOPED_TRACE(name); |
158 | SCOPED_TRACE("Checking environment variable" ); |
159 | CheckValue( |
160 | F: [&](const Descriptor *value, const Descriptor *length, |
161 | const Descriptor *errmsg) { |
162 | return RTNAME(GetEnvVariable)( |
163 | *CharDescriptor(name), value, length, trimName, errmsg); |
164 | }, |
165 | expectedValue, expectedLength: std::strlen(s: expectedValue)); |
166 | } |
167 | |
168 | void CheckMissingEnvVarValue(const char *name, bool trimName = true) const { |
169 | SCOPED_TRACE(name); |
170 | SCOPED_TRACE("Checking missing environment variable" ); |
171 | |
172 | ASSERT_EQ(nullptr, std::getenv(name)) |
173 | << "Environment variable " << name << " not expected to exist" ; |
174 | |
175 | CheckValue( |
176 | F: [&](const Descriptor *value, const Descriptor *length, |
177 | const Descriptor *errmsg) { |
178 | return RTNAME(GetEnvVariable)( |
179 | *CharDescriptor(name), value, length, trimName, errmsg); |
180 | }, |
181 | expectedValue: "" , expectedLength: 0, expectedStatus: 1, expectedErrMsg: "Missing environment variable" ); |
182 | } |
183 | |
184 | void CheckMissingArgumentValue(int n, const char *errStr = nullptr) const { |
185 | OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()}; |
186 | ASSERT_NE(value, nullptr); |
187 | |
188 | OwningPtr<Descriptor> length{EmptyIntDescriptor()}; |
189 | ASSERT_NE(length, nullptr); |
190 | |
191 | OwningPtr<Descriptor> err{errStr ? CreateEmptyCharDescriptor() : nullptr}; |
192 | |
193 | EXPECT_GT( |
194 | RTNAME(GetCommandArgument)(n, value.get(), length.get(), err.get()), 0); |
195 | |
196 | std::string spaces(value->ElementBytes(), ' '); |
197 | CheckDescriptorEqStr(value.get(), spaces); |
198 | |
199 | CheckDescriptorEqInt<std::int64_t>(length.get(), 0); |
200 | |
201 | if (errStr) { |
202 | std::string paddedErrStr(GetPaddedStr(text: errStr, len: err->ElementBytes())); |
203 | CheckDescriptorEqStr(err.get(), paddedErrStr); |
204 | } |
205 | } |
206 | |
207 | void CheckMissingCommandValue(const char *errStr = nullptr) const { |
208 | OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()}; |
209 | ASSERT_NE(value, nullptr); |
210 | |
211 | OwningPtr<Descriptor> length{EmptyIntDescriptor()}; |
212 | ASSERT_NE(length, nullptr); |
213 | |
214 | OwningPtr<Descriptor> err{errStr ? CreateEmptyCharDescriptor() : nullptr}; |
215 | |
216 | EXPECT_GT(RTNAME(GetCommand)(value.get(), length.get(), err.get()), 0); |
217 | |
218 | std::string spaces(value->ElementBytes(), ' '); |
219 | CheckDescriptorEqStr(value.get(), spaces); |
220 | |
221 | CheckDescriptorEqInt<std::int64_t>(length.get(), 0); |
222 | |
223 | if (errStr) { |
224 | std::string paddedErrStr(GetPaddedStr(text: errStr, len: err->ElementBytes())); |
225 | CheckDescriptorEqStr(err.get(), paddedErrStr); |
226 | } |
227 | } |
228 | }; |
229 | |
230 | class NoArgv : public CommandFixture { |
231 | protected: |
232 | NoArgv() : CommandFixture(0, nullptr) {} |
233 | }; |
234 | |
235 | #if _WIN32 || _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || \ |
236 | _SVID_SOURCE || defined(_POSIX_SOURCE) |
237 | TEST_F(NoArgv, FdateGetDate) { |
238 | char input[]{"24LengthCharIsJustRight" }; |
239 | const std::size_t charLen = sizeof(input); |
240 | |
241 | FORTRAN_PROCEDURE_NAME(fdate)(input, charLen); |
242 | |
243 | // Tue May 26 21:51:03 2015\n\0 |
244 | // index at 3, 7, 10, 19 should be space |
245 | // when date is less than two digit, index 8 would be space |
246 | // Tue May 6 21:51:03 2015\n\0 |
247 | for (std::size_t i{0}; i < charLen; i++) { |
248 | if (i == 8) |
249 | continue; |
250 | if (i == 3 || i == 7 || i == 10 || i == 19) { |
251 | EXPECT_EQ(input[i], ' '); |
252 | continue; |
253 | } |
254 | EXPECT_NE(input[i], ' '); |
255 | } |
256 | } |
257 | |
258 | TEST_F(NoArgv, FdateGetDateTooShort) { |
259 | char input[]{"TooShortAllPadSpace" }; |
260 | const std::size_t charLen = sizeof(input); |
261 | |
262 | FORTRAN_PROCEDURE_NAME(fdate)(input, charLen); |
263 | |
264 | for (std::size_t i{0}; i < charLen; i++) { |
265 | EXPECT_EQ(input[i], ' '); |
266 | } |
267 | } |
268 | |
269 | TEST_F(NoArgv, FdateGetDatePadSpace) { |
270 | char input[]{"All char after 23 pad spaces" }; |
271 | const std::size_t charLen = sizeof(input); |
272 | |
273 | FORTRAN_PROCEDURE_NAME(fdate)(input, charLen); |
274 | |
275 | for (std::size_t i{24}; i < charLen; i++) { |
276 | EXPECT_EQ(input[i], ' '); |
277 | } |
278 | } |
279 | |
280 | #else |
281 | TEST_F(NoArgv, FdateNotSupported) { |
282 | char input[]{"No change due to crash" }; |
283 | |
284 | EXPECT_DEATH(FORTRAN_PROCEDURE_NAME(fdate)(input, sizeof(input)), |
285 | "fdate is not supported." ); |
286 | |
287 | CheckCharEqStr(input, "No change due to crash" ); |
288 | } |
289 | #endif |
290 | |
291 | // TODO: Test other intrinsics with this fixture. |
292 | |
293 | TEST_F(NoArgv, GetCommand) { CheckMissingCommandValue(); } |
294 | |
295 | static const char *commandOnlyArgv[]{"aProgram" }; |
296 | class ZeroArguments : public CommandFixture { |
297 | protected: |
298 | ZeroArguments() : CommandFixture(1, commandOnlyArgv) {} |
299 | }; |
300 | |
301 | TEST_F(ZeroArguments, ArgumentCount) { EXPECT_EQ(0, RTNAME(ArgumentCount)()); } |
302 | |
303 | TEST_F(ZeroArguments, GetCommandArgument) { |
304 | CheckMissingArgumentValue(n: -1); |
305 | CheckArgumentValue(expectedValue: commandOnlyArgv[0], n: 0); |
306 | CheckMissingArgumentValue(n: 1); |
307 | } |
308 | |
309 | TEST_F(ZeroArguments, GetCommand) { CheckCommandValue(args: commandOnlyArgv, n: 1); } |
310 | |
311 | TEST_F(ZeroArguments, ECLValidCommandAndPadSync) { |
312 | OwningPtr<Descriptor> command{CharDescriptor("echo hi" )}; |
313 | bool wait{true}; |
314 | OwningPtr<Descriptor> exitStat{IntDescriptor(404)}; |
315 | OwningPtr<Descriptor> cmdStat{IntDescriptor(202)}; |
316 | OwningPtr<Descriptor> cmdMsg{CharDescriptor("No change" )}; |
317 | |
318 | RTNAME(ExecuteCommandLine) |
319 | (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); |
320 | |
321 | std::string spaces(cmdMsg->ElementBytes(), ' '); |
322 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 0); |
323 | CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 0); |
324 | CheckDescriptorEqStr(cmdMsg.get(), "No change" ); |
325 | } |
326 | |
327 | TEST_F(ZeroArguments, ECLValidCommandStatusSetSync) { |
328 | OwningPtr<Descriptor> command{CharDescriptor("echo hi" )}; |
329 | bool wait{true}; |
330 | OwningPtr<Descriptor> exitStat{IntDescriptor(404)}; |
331 | OwningPtr<Descriptor> cmdStat{IntDescriptor(202)}; |
332 | OwningPtr<Descriptor> cmdMsg{CharDescriptor("No change" )}; |
333 | |
334 | RTNAME(ExecuteCommandLine) |
335 | (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); |
336 | |
337 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 0); |
338 | CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 0); |
339 | CheckDescriptorEqStr(cmdMsg.get(), "No change" ); |
340 | } |
341 | |
342 | TEST_F(ZeroArguments, ECLGeneralErrorCommandErrorSync) { |
343 | OwningPtr<Descriptor> command{CharDescriptor(NOT_EXE)}; |
344 | bool wait{true}; |
345 | OwningPtr<Descriptor> exitStat{IntDescriptor(404)}; |
346 | OwningPtr<Descriptor> cmdStat{IntDescriptor(202)}; |
347 | OwningPtr<Descriptor> cmdMsg{CharDescriptor("cmd msg buffer XXXXXXXXXXXXXX" )}; |
348 | |
349 | RTNAME(ExecuteCommandLine) |
350 | (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); |
351 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 1); |
352 | #if defined(_WIN32) |
353 | CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 6); |
354 | CheckDescriptorEqStr(cmdMsg.get(), "Invalid command lineXXXXXXXXX" ); |
355 | #else |
356 | CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 3); |
357 | CheckDescriptorEqStr(cmdMsg.get(), "Command line execution failed" ); |
358 | #endif |
359 | } |
360 | |
361 | TEST_F(ZeroArguments, ECLNotExecutedCommandErrorSync) { |
362 | OwningPtr<Descriptor> command{CharDescriptor( |
363 | "touch NotExecutedCommandFile && chmod -x NotExecutedCommandFile && " |
364 | "./NotExecutedCommandFile" )}; |
365 | bool wait{true}; |
366 | OwningPtr<Descriptor> exitStat{IntDescriptor(404)}; |
367 | OwningPtr<Descriptor> cmdStat{IntDescriptor(202)}; |
368 | OwningPtr<Descriptor> cmdMsg{CharDescriptor("cmd msg buffer XXXXXXXX" )}; |
369 | |
370 | RTNAME(ExecuteCommandLine) |
371 | (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); |
372 | #ifdef _WIN32 |
373 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 1); |
374 | CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 6); |
375 | CheckDescriptorEqStr(cmdMsg.get(), "Invalid command lineXXX" ); |
376 | #else |
377 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 126); |
378 | CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 4); |
379 | CheckDescriptorEqStr(cmdMsg.get(), "Command cannot be execu" ); |
380 | // removing the file only on Linux (file is not created on Win) |
381 | OwningPtr<Descriptor> commandClean{ |
382 | CharDescriptor("rm -f NotExecutedCommandFile" )}; |
383 | OwningPtr<Descriptor> cmdMsgNoErr{CharDescriptor("No Error" )}; |
384 | RTNAME(ExecuteCommandLine) |
385 | (*commandClean.get(), wait, exitStat.get(), cmdStat.get(), cmdMsgNoErr.get()); |
386 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 0); |
387 | CheckDescriptorEqStr(cmdMsgNoErr.get(), "No Error" ); |
388 | CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 0); |
389 | #endif |
390 | } |
391 | |
392 | TEST_F(ZeroArguments, ECLNotFoundCommandErrorSync) { |
393 | OwningPtr<Descriptor> command{CharDescriptor("NotFoundCommand" )}; |
394 | bool wait{true}; |
395 | OwningPtr<Descriptor> exitStat{IntDescriptor(404)}; |
396 | OwningPtr<Descriptor> cmdStat{IntDescriptor(202)}; |
397 | OwningPtr<Descriptor> cmdMsg{CharDescriptor("unmodified buffer XXXXXXXXX" )}; |
398 | |
399 | RTNAME(ExecuteCommandLine) |
400 | (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); |
401 | #ifdef _WIN32 |
402 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 1); |
403 | CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 6); |
404 | CheckDescriptorEqStr(cmdMsg.get(), "Invalid command lineXXXXXXX" ); |
405 | #else |
406 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 127); |
407 | CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 5); |
408 | CheckDescriptorEqStr(cmdMsg.get(), "Command not found with exit" ); |
409 | #endif |
410 | } |
411 | |
412 | TEST_F(ZeroArguments, ECLInvalidCommandTerminatedSync) { |
413 | OwningPtr<Descriptor> command{CharDescriptor("InvalidCommand" )}; |
414 | bool wait{true}; |
415 | OwningPtr<Descriptor> cmdMsg{CharDescriptor("No Change" )}; |
416 | |
417 | #ifdef _WIN32 |
418 | EXPECT_DEATH(RTNAME(ExecuteCommandLine)( |
419 | *command.get(), wait, nullptr, nullptr, cmdMsg.get()), |
420 | "Invalid command quit with exit status code: 1" ); |
421 | #else |
422 | EXPECT_DEATH(RTNAME(ExecuteCommandLine)( |
423 | *command.get(), wait, nullptr, nullptr, cmdMsg.get()), |
424 | "Command not found with exit code: 127." ); |
425 | #endif |
426 | CheckDescriptorEqStr(cmdMsg.get(), "No Change" ); |
427 | } |
428 | |
429 | TEST_F(ZeroArguments, ECLValidCommandAndExitStatNoChangeAndCMDStatusSetAsync) { |
430 | OwningPtr<Descriptor> command{CharDescriptor("echo hi" )}; |
431 | bool wait{false}; |
432 | OwningPtr<Descriptor> exitStat{IntDescriptor(404)}; |
433 | OwningPtr<Descriptor> cmdStat{IntDescriptor(202)}; |
434 | OwningPtr<Descriptor> cmdMsg{CharDescriptor("No change" )}; |
435 | |
436 | RTNAME(ExecuteCommandLine) |
437 | (*command.get(), wait, exitStat.get(), cmdStat.get(), cmdMsg.get()); |
438 | |
439 | CheckDescriptorEqInt(exitStat.get(), 404); |
440 | CheckDescriptorEqInt<std::int64_t>(cmdStat.get(), 0); |
441 | CheckDescriptorEqStr(cmdMsg.get(), "No change" ); |
442 | } |
443 | |
444 | TEST_F(ZeroArguments, ECLInvalidCommandParentNotTerminatedAsync) { |
445 | OwningPtr<Descriptor> command{CharDescriptor("InvalidCommand" )}; |
446 | bool wait{false}; |
447 | OwningPtr<Descriptor> cmdMsg{CharDescriptor("No change" )}; |
448 | |
449 | EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( |
450 | *command.get(), wait, nullptr, nullptr, cmdMsg.get())); |
451 | CheckDescriptorEqStr(cmdMsg.get(), "No change" ); |
452 | } |
453 | |
454 | TEST_F(ZeroArguments, ECLInvalidCommandAsyncDontAffectSync) { |
455 | OwningPtr<Descriptor> command{CharDescriptor("echo hi" )}; |
456 | |
457 | EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( |
458 | *command.get(), false, nullptr, nullptr, nullptr)); |
459 | EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( |
460 | *command.get(), true, nullptr, nullptr, nullptr)); |
461 | } |
462 | |
463 | TEST_F(ZeroArguments, ECLInvalidCommandAsyncDontAffectAsync) { |
464 | OwningPtr<Descriptor> command{CharDescriptor("echo hi" )}; |
465 | |
466 | EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( |
467 | *command.get(), false, nullptr, nullptr, nullptr)); |
468 | EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( |
469 | *command.get(), false, nullptr, nullptr, nullptr)); |
470 | } |
471 | |
472 | TEST_F(ZeroArguments, SystemValidCommandExitStat) { |
473 | // envrionment setup for SYSTEM from EXECUTE_COMMAND_LINE runtime |
474 | OwningPtr<Descriptor> cmdStat{IntDescriptor(202)}; |
475 | bool wait{true}; |
476 | // setup finished |
477 | |
478 | OwningPtr<Descriptor> command{CharDescriptor("echo hi" )}; |
479 | OwningPtr<Descriptor> exitStat{EmptyIntDescriptor()}; |
480 | |
481 | RTNAME(ExecuteCommandLine) |
482 | (*command.get(), wait, exitStat.get(), cmdStat.get(), nullptr); |
483 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 0); |
484 | } |
485 | |
486 | TEST_F(ZeroArguments, SystemInvalidCommandExitStat) { |
487 | // envrionment setup for SYSTEM from EXECUTE_COMMAND_LINE runtime |
488 | OwningPtr<Descriptor> cmdStat{IntDescriptor(202)}; |
489 | bool wait{true}; |
490 | // setup finished |
491 | |
492 | OwningPtr<Descriptor> command{CharDescriptor("InvalidCommand" )}; |
493 | OwningPtr<Descriptor> exitStat{EmptyIntDescriptor()}; |
494 | |
495 | RTNAME(ExecuteCommandLine) |
496 | (*command.get(), wait, exitStat.get(), cmdStat.get(), nullptr); |
497 | #ifdef _WIN32 |
498 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 1); |
499 | #else |
500 | CheckDescriptorEqInt<std::int64_t>(exitStat.get(), 127); |
501 | #endif |
502 | } |
503 | |
504 | TEST_F(ZeroArguments, SystemValidCommandOptionalExitStat) { |
505 | // envrionment setup for SYSTEM from EXECUTE_COMMAND_LINE runtime |
506 | OwningPtr<Descriptor> cmdStat{IntDescriptor(202)}; |
507 | bool wait{true}; |
508 | // setup finished |
509 | |
510 | OwningPtr<Descriptor> command{CharDescriptor("echo hi" )}; |
511 | EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( |
512 | *command.get(), wait, nullptr, cmdStat.get(), nullptr)); |
513 | } |
514 | |
515 | TEST_F(ZeroArguments, SystemInvalidCommandOptionalExitStat) { |
516 | // envrionment setup for SYSTEM from EXECUTE_COMMAND_LINE runtime |
517 | OwningPtr<Descriptor> cmdStat{IntDescriptor(202)}; |
518 | bool wait{true}; |
519 | // setup finished |
520 | |
521 | OwningPtr<Descriptor> command{CharDescriptor("InvalidCommand" )}; |
522 | EXPECT_NO_FATAL_FAILURE(RTNAME(ExecuteCommandLine)( |
523 | *command.get(), wait, nullptr, cmdStat.get(), nullptr);); |
524 | } |
525 | |
526 | static const char *oneArgArgv[]{"aProgram" , "anArgumentOfLength20" }; |
527 | class OneArgument : public CommandFixture { |
528 | protected: |
529 | OneArgument() : CommandFixture(2, oneArgArgv) {} |
530 | }; |
531 | |
532 | TEST_F(OneArgument, ArgumentCount) { EXPECT_EQ(1, RTNAME(ArgumentCount)()); } |
533 | |
534 | TEST_F(OneArgument, GetCommandArgument) { |
535 | CheckMissingArgumentValue(n: -1); |
536 | CheckArgumentValue(expectedValue: oneArgArgv[0], n: 0); |
537 | CheckArgumentValue(expectedValue: oneArgArgv[1], n: 1); |
538 | CheckMissingArgumentValue(n: 2); |
539 | } |
540 | |
541 | TEST_F(OneArgument, GetCommand) { CheckCommandValue(args: oneArgArgv, n: 2); } |
542 | |
543 | static const char *severalArgsArgv[]{ |
544 | "aProgram" , "16-char-long-arg" , "" , "-22-character-long-arg" , "o" }; |
545 | class SeveralArguments : public CommandFixture { |
546 | protected: |
547 | SeveralArguments() |
548 | : CommandFixture(sizeof(severalArgsArgv) / sizeof(*severalArgsArgv), |
549 | severalArgsArgv) {} |
550 | }; |
551 | |
552 | TEST_F(SeveralArguments, ArgumentCount) { |
553 | EXPECT_EQ(4, RTNAME(ArgumentCount)()); |
554 | } |
555 | |
556 | TEST_F(SeveralArguments, GetCommandArgument) { |
557 | CheckArgumentValue(expectedValue: severalArgsArgv[0], n: 0); |
558 | CheckArgumentValue(expectedValue: severalArgsArgv[1], n: 1); |
559 | CheckArgumentValue(expectedValue: severalArgsArgv[3], n: 3); |
560 | CheckArgumentValue(expectedValue: severalArgsArgv[4], n: 4); |
561 | } |
562 | |
563 | TEST_F(SeveralArguments, NoArgumentValue) { |
564 | // Make sure we don't crash if the 'value', 'length' and 'error' parameters |
565 | // aren't passed. |
566 | EXPECT_GT(RTNAME(GetCommandArgument)(2), 0); |
567 | EXPECT_EQ(RTNAME(GetCommandArgument)(1), 0); |
568 | EXPECT_GT(RTNAME(GetCommandArgument)(-1), 0); |
569 | } |
570 | |
571 | TEST_F(SeveralArguments, MissingArguments) { |
572 | CheckMissingArgumentValue(n: -1, errStr: "Invalid argument number" ); |
573 | CheckMissingArgumentValue(n: 2, errStr: "Missing argument" ); |
574 | CheckMissingArgumentValue(n: 5, errStr: "Invalid argument number" ); |
575 | CheckMissingArgumentValue(n: 5); |
576 | } |
577 | |
578 | TEST_F(SeveralArguments, ArgValueTooShort) { |
579 | OwningPtr<Descriptor> tooShort{CreateEmptyCharDescriptor<15>()}; |
580 | ASSERT_NE(tooShort, nullptr); |
581 | EXPECT_EQ(RTNAME(GetCommandArgument)(1, tooShort.get()), -1); |
582 | CheckDescriptorEqStr(tooShort.get(), severalArgsArgv[1]); |
583 | |
584 | OwningPtr<Descriptor> length{EmptyIntDescriptor()}; |
585 | ASSERT_NE(length, nullptr); |
586 | OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor()}; |
587 | ASSERT_NE(errMsg, nullptr); |
588 | |
589 | EXPECT_EQ( |
590 | RTNAME(GetCommandArgument)(1, tooShort.get(), length.get(), errMsg.get()), |
591 | -1); |
592 | |
593 | CheckDescriptorEqInt<std::int64_t>(length.get(), 16); |
594 | std::string expectedErrMsg{ |
595 | GetPaddedStr(text: "Value too short" , len: errMsg->ElementBytes())}; |
596 | CheckDescriptorEqStr(errMsg.get(), expectedErrMsg); |
597 | } |
598 | |
599 | TEST_F(SeveralArguments, ArgErrMsgTooShort) { |
600 | OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor<3>()}; |
601 | EXPECT_GT(RTNAME(GetCommandArgument)(-1, nullptr, nullptr, errMsg.get()), 0); |
602 | CheckDescriptorEqStr(errMsg.get(), "Inv" ); |
603 | } |
604 | |
605 | TEST_F(SeveralArguments, GetCommand) { |
606 | CheckMissingCommandValue(); |
607 | CheckMissingCommandValue(errStr: "Missing argument" ); |
608 | } |
609 | |
610 | TEST_F(SeveralArguments, CommandErrMsgTooShort) { |
611 | OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()}; |
612 | OwningPtr<Descriptor> length{EmptyIntDescriptor()}; |
613 | OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor<3>()}; |
614 | |
615 | EXPECT_GT(RTNAME(GetCommand)(value.get(), length.get(), errMsg.get()), 0); |
616 | |
617 | std::string spaces(value->ElementBytes(), ' '); |
618 | CheckDescriptorEqStr(value.get(), spaces); |
619 | CheckDescriptorEqInt<std::int64_t>(length.get(), 0); |
620 | CheckDescriptorEqStr(errMsg.get(), "Mis" ); |
621 | } |
622 | |
623 | TEST_F(SeveralArguments, GetCommandCanTakeNull) { |
624 | EXPECT_GT(RTNAME(GetCommand)(nullptr, nullptr, nullptr), 0); |
625 | } |
626 | |
627 | static const char *onlyValidArgsArgv[]{ |
628 | "aProgram" , "-f" , "has/a/few/slashes" , "has\\a\\few\\backslashes" }; |
629 | class OnlyValidArguments : public CommandFixture { |
630 | protected: |
631 | OnlyValidArguments() |
632 | : CommandFixture(sizeof(onlyValidArgsArgv) / sizeof(*onlyValidArgsArgv), |
633 | onlyValidArgsArgv) {} |
634 | }; |
635 | |
636 | TEST_F(OnlyValidArguments, GetCommand) { |
637 | CheckCommandValue(args: onlyValidArgsArgv, n: 4); |
638 | } |
639 | |
640 | TEST_F(OnlyValidArguments, CommandValueTooShort) { |
641 | OwningPtr<Descriptor> tooShort{CreateEmptyCharDescriptor<50>()}; |
642 | ASSERT_NE(tooShort, nullptr); |
643 | OwningPtr<Descriptor> length{EmptyIntDescriptor()}; |
644 | ASSERT_NE(length, nullptr); |
645 | |
646 | EXPECT_EQ(RTNAME(GetCommand)(tooShort.get(), length.get(), nullptr), -1); |
647 | |
648 | CheckDescriptorEqStr( |
649 | tooShort.get(), "aProgram -f has/a/few/slashes has\\a\\few\\backslashe" ); |
650 | CheckDescriptorEqInt<std::int64_t>(length.get(), 51); |
651 | |
652 | OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor()}; |
653 | ASSERT_NE(errMsg, nullptr); |
654 | |
655 | EXPECT_EQ(-1, RTNAME(GetCommand)(tooShort.get(), nullptr, errMsg.get())); |
656 | |
657 | std::string expectedErrMsg{ |
658 | GetPaddedStr(text: "Value too short" , len: errMsg->ElementBytes())}; |
659 | CheckDescriptorEqStr(errMsg.get(), expectedErrMsg); |
660 | } |
661 | |
662 | TEST_F(OnlyValidArguments, GetCommandCanTakeNull) { |
663 | EXPECT_EQ(0, RTNAME(GetCommand)(nullptr, nullptr, nullptr)); |
664 | |
665 | OwningPtr<Descriptor> value{CreateEmptyCharDescriptor()}; |
666 | ASSERT_NE(value, nullptr); |
667 | OwningPtr<Descriptor> length{EmptyIntDescriptor()}; |
668 | ASSERT_NE(length, nullptr); |
669 | |
670 | EXPECT_EQ(0, RTNAME(GetCommand)(value.get(), nullptr, nullptr)); |
671 | CheckDescriptorEqStr(value.get(), |
672 | GetPaddedStr("aProgram -f has/a/few/slashes has\\a\\few\\backslashes" , |
673 | value->ElementBytes())); |
674 | |
675 | EXPECT_EQ(0, RTNAME(GetCommand)(nullptr, length.get(), nullptr)); |
676 | CheckDescriptorEqInt<std::int64_t>(length.get(), 51); |
677 | } |
678 | |
679 | TEST_F(OnlyValidArguments, GetCommandShortLength) { |
680 | OwningPtr<Descriptor> length{EmptyIntDescriptor<sizeof(short)>()}; |
681 | ASSERT_NE(length, nullptr); |
682 | |
683 | EXPECT_EQ(0, RTNAME(GetCommand)(nullptr, length.get(), nullptr)); |
684 | CheckDescriptorEqInt<short>(length.get(), 51); |
685 | } |
686 | |
687 | TEST_F(ZeroArguments, GetPID) { |
688 | // pid should always greater than 0, in both linux and windows |
689 | EXPECT_GT(RTNAME(GetPID)(), 0); |
690 | } |
691 | |
692 | class EnvironmentVariables : public CommandFixture { |
693 | protected: |
694 | EnvironmentVariables() : CommandFixture(0, nullptr) { |
695 | SetEnv(name: "NAME" , value: "VALUE" ); |
696 | #ifdef _WIN32 |
697 | SetEnv("USERNAME" , "loginName" ); |
698 | #else |
699 | SetEnv(name: "LOGNAME" , value: "loginName" ); |
700 | #endif |
701 | SetEnv(name: "EMPTY" , value: "" ); |
702 | } |
703 | |
704 | // If we have access to setenv, we can run some more fine-grained tests. |
705 | template <typename ParamType = char> |
706 | void SetEnv(const ParamType *name, const ParamType *value, |
707 | decltype(setenv(name, value, 1)) *Enabled = nullptr) { |
708 | ASSERT_EQ(0, setenv(name, value, /*overwrite=*/1)); |
709 | canSetEnv = true; |
710 | } |
711 | |
712 | // Fallback method if setenv is not available. |
713 | template <typename Unused = void> void SetEnv(const void *, const void *) {} |
714 | |
715 | bool EnableFineGrainedTests() const { return canSetEnv; } |
716 | |
717 | private: |
718 | bool canSetEnv{false}; |
719 | }; |
720 | |
721 | TEST_F(EnvironmentVariables, Nonexistent) { |
722 | CheckMissingEnvVarValue(name: "DOESNT_EXIST" ); |
723 | CheckMissingEnvVarValue(name: " " ); |
724 | CheckMissingEnvVarValue(name: "" ); |
725 | } |
726 | |
727 | TEST_F(EnvironmentVariables, Basic) { |
728 | // Test a variable that's expected to exist in the environment. |
729 | char *path{std::getenv(name: "PATH" )}; |
730 | auto expectedLen{static_cast<int64_t>(std::strlen(s: path))}; |
731 | OwningPtr<Descriptor> length{EmptyIntDescriptor()}; |
732 | EXPECT_EQ(0, |
733 | RTNAME(GetEnvVariable)(*CharDescriptor("PATH" ), |
734 | /*value=*/nullptr, length.get())); |
735 | CheckDescriptorEqInt(length.get(), expectedLen); |
736 | } |
737 | |
738 | TEST_F(EnvironmentVariables, Trim) { |
739 | if (EnableFineGrainedTests()) { |
740 | CheckEnvVarValue(expectedValue: "VALUE" , name: "NAME " ); |
741 | } |
742 | } |
743 | |
744 | TEST_F(EnvironmentVariables, NoTrim) { |
745 | if (EnableFineGrainedTests()) { |
746 | CheckMissingEnvVarValue(name: "NAME " , /*trim_name=*/trimName: false); |
747 | } |
748 | } |
749 | |
750 | TEST_F(EnvironmentVariables, Empty) { |
751 | if (EnableFineGrainedTests()) { |
752 | CheckEnvVarValue(expectedValue: "" , name: "EMPTY" ); |
753 | } |
754 | } |
755 | |
756 | TEST_F(EnvironmentVariables, NoValueOrErrmsg) { |
757 | ASSERT_EQ(std::getenv("DOESNT_EXIST" ), nullptr) |
758 | << "Environment variable DOESNT_EXIST actually exists" ; |
759 | EXPECT_EQ(RTNAME(GetEnvVariable)(*CharDescriptor("DOESNT_EXIST" )), 1); |
760 | |
761 | if (EnableFineGrainedTests()) { |
762 | EXPECT_EQ(RTNAME(GetEnvVariable)(*CharDescriptor("NAME" )), 0); |
763 | } |
764 | } |
765 | |
766 | TEST_F(EnvironmentVariables, ValueTooShort) { |
767 | if (EnableFineGrainedTests()) { |
768 | OwningPtr<Descriptor> tooShort{CreateEmptyCharDescriptor<2>()}; |
769 | ASSERT_NE(tooShort, nullptr); |
770 | EXPECT_EQ(RTNAME(GetEnvVariable)(*CharDescriptor("NAME" ), tooShort.get(), |
771 | /*length=*/nullptr, /*trim_name=*/true, nullptr), |
772 | -1); |
773 | CheckDescriptorEqStr(tooShort.get(), "VALUE" ); |
774 | |
775 | OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor()}; |
776 | ASSERT_NE(errMsg, nullptr); |
777 | |
778 | EXPECT_EQ(RTNAME(GetEnvVariable)(*CharDescriptor("NAME" ), tooShort.get(), |
779 | /*length=*/nullptr, /*trim_name=*/true, errMsg.get()), |
780 | -1); |
781 | |
782 | std::string expectedErrMsg{ |
783 | GetPaddedStr(text: "Value too short" , len: errMsg->ElementBytes())}; |
784 | CheckDescriptorEqStr(errMsg.get(), expectedErrMsg); |
785 | } |
786 | } |
787 | |
788 | TEST_F(EnvironmentVariables, ErrMsgTooShort) { |
789 | ASSERT_EQ(std::getenv("DOESNT_EXIST" ), nullptr) |
790 | << "Environment variable DOESNT_EXIST actually exists" ; |
791 | |
792 | OwningPtr<Descriptor> errMsg{CreateEmptyCharDescriptor<3>()}; |
793 | EXPECT_EQ(RTNAME(GetEnvVariable)(*CharDescriptor("DOESNT_EXIST" ), nullptr, |
794 | /*length=*/nullptr, /*trim_name=*/true, errMsg.get()), |
795 | 1); |
796 | CheckDescriptorEqStr(errMsg.get(), "Mis" ); |
797 | } |
798 | |
799 | // username first char must not be null |
800 | TEST_F(EnvironmentVariables, GetlogGetName) { |
801 | const int charLen{3}; |
802 | char input[charLen]{"\0\0" }; |
803 | FORTRAN_PROCEDURE_NAME(getlog)(input, charLen); |
804 | EXPECT_NE(input[0], '\0'); |
805 | } |
806 | |
807 | #if _REENTRANT || _POSIX_C_SOURCE >= 199506L |
808 | TEST_F(EnvironmentVariables, GetlogPadSpace) { |
809 | // guarantee 1 char longer than max, last char should be pad space |
810 | int charLen; |
811 | #ifdef LOGIN_NAME_MAX |
812 | charLen = LOGIN_NAME_MAX + 2; |
813 | #else |
814 | charLen = sysconf(_SC_LOGIN_NAME_MAX) + 2; |
815 | if (charLen == -1) |
816 | charLen = _POSIX_LOGIN_NAME_MAX + 2; |
817 | #endif |
818 | std::vector<char> input(charLen); |
819 | FORTRAN_PROCEDURE_NAME(getlog)(input.data(), charLen); |
820 | EXPECT_EQ(input[charLen - 1], ' '); |
821 | } |
822 | #endif |
823 | |
824 | #ifdef _WIN32 // Test ability to get name from environment variable |
825 | TEST_F(EnvironmentVariables, GetlogEnvGetName) { |
826 | if (EnableFineGrainedTests()) { |
827 | ASSERT_NE(std::getenv("USERNAME" ), nullptr) |
828 | << "Environment variable USERNAME does not exist" ; |
829 | |
830 | char input[]{"XXXXXXXXX" }; |
831 | FORTRAN_PROCEDURE_NAME(getlog)(input, sizeof(input)); |
832 | |
833 | CheckCharEqStr(input, "loginName" ); |
834 | } |
835 | } |
836 | |
837 | TEST_F(EnvironmentVariables, GetlogEnvBufferShort) { |
838 | if (EnableFineGrainedTests()) { |
839 | ASSERT_NE(std::getenv("USERNAME" ), nullptr) |
840 | << "Environment variable USERNAME does not exist" ; |
841 | |
842 | char input[]{"XXXXXX" }; |
843 | FORTRAN_PROCEDURE_NAME(getlog)(input, sizeof(input)); |
844 | |
845 | CheckCharEqStr(input, "loginN" ); |
846 | } |
847 | } |
848 | |
849 | TEST_F(EnvironmentVariables, GetlogEnvPadSpace) { |
850 | if (EnableFineGrainedTests()) { |
851 | ASSERT_NE(std::getenv("USERNAME" ), nullptr) |
852 | << "Environment variable USERNAME does not exist" ; |
853 | |
854 | char input[]{"XXXXXXXXXX" }; |
855 | FORTRAN_PROCEDURE_NAME(getlog)(input, sizeof(input)); |
856 | |
857 | CheckCharEqStr(input, "loginName " ); |
858 | } |
859 | } |
860 | #endif |
861 | |