1 | //===- unittest/Support/ProgramTest.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 "llvm/Support/Program.h" |
10 | #include "llvm/Config/llvm-config.h" |
11 | #include "llvm/Support/CommandLine.h" |
12 | #include "llvm/Support/ConvertUTF.h" |
13 | #include "llvm/Support/FileSystem.h" |
14 | #include "llvm/Support/Path.h" |
15 | #include "llvm/Support/Signals.h" |
16 | #include "gtest/gtest.h" |
17 | #include <stdlib.h> |
18 | #include <thread> |
19 | #if defined(__APPLE__) |
20 | # include <crt_externs.h> |
21 | #elif !defined(_MSC_VER) |
22 | // Forward declare environ in case it's not provided by stdlib.h. |
23 | extern char **environ; |
24 | #endif |
25 | |
26 | #if defined(LLVM_ON_UNIX) |
27 | #include <unistd.h> |
28 | void sleep_for(unsigned int seconds) { |
29 | sleep(seconds: seconds); |
30 | } |
31 | #elif defined(_WIN32) |
32 | #include <windows.h> |
33 | void sleep_for(unsigned int seconds) { |
34 | Sleep(seconds * 1000); |
35 | } |
36 | #else |
37 | #error sleep_for is not implemented on your platform. |
38 | #endif |
39 | |
40 | #define ASSERT_NO_ERROR(x) \ |
41 | if (std::error_code ASSERT_NO_ERROR_ec = x) { \ |
42 | SmallString<128> MessageStorage; \ |
43 | raw_svector_ostream Message(MessageStorage); \ |
44 | Message << #x ": did not return errc::success.\n" \ |
45 | << "error number: " << ASSERT_NO_ERROR_ec.value() << "\n" \ |
46 | << "error message: " << ASSERT_NO_ERROR_ec.message() << "\n"; \ |
47 | GTEST_FATAL_FAILURE_(MessageStorage.c_str()); \ |
48 | } else { \ |
49 | } |
50 | // From TestMain.cpp. |
51 | extern const char *TestMainArgv0; |
52 | |
53 | namespace { |
54 | |
55 | using namespace llvm; |
56 | using namespace sys; |
57 | |
58 | static cl::opt<std::string> |
59 | ProgramTestStringArg1("program-test-string-arg1" ); |
60 | static cl::opt<std::string> |
61 | ProgramTestStringArg2("program-test-string-arg2" ); |
62 | |
63 | class ProgramEnvTest : public testing::Test { |
64 | std::vector<StringRef> EnvTable; |
65 | std::vector<std::string> EnvStorage; |
66 | |
67 | protected: |
68 | void SetUp() override { |
69 | auto EnvP = [] { |
70 | #if defined(_WIN32) |
71 | _wgetenv(L"TMP" ); // Populate _wenviron, initially is null |
72 | return _wenviron; |
73 | #elif defined(__APPLE__) |
74 | return *_NSGetEnviron(); |
75 | #else |
76 | return environ; |
77 | #endif |
78 | }(); |
79 | ASSERT_TRUE(EnvP); |
80 | |
81 | auto prepareEnvVar = [this](decltype(*EnvP) Var) -> StringRef { |
82 | #if defined(_WIN32) |
83 | // On Windows convert UTF16 encoded variable to UTF8 |
84 | auto Len = wcslen(Var); |
85 | ArrayRef<char> Ref{reinterpret_cast<char const *>(Var), |
86 | Len * sizeof(*Var)}; |
87 | EnvStorage.emplace_back(); |
88 | auto convStatus = convertUTF16ToUTF8String(Ref, EnvStorage.back()); |
89 | EXPECT_TRUE(convStatus); |
90 | return EnvStorage.back(); |
91 | #else |
92 | (void)this; |
93 | return StringRef(Var); |
94 | #endif |
95 | }; |
96 | |
97 | while (*EnvP != nullptr) { |
98 | auto S = prepareEnvVar(*EnvP); |
99 | if (!StringRef(S).starts_with(Prefix: "GTEST_" )) |
100 | EnvTable.emplace_back(args&: S); |
101 | ++EnvP; |
102 | } |
103 | } |
104 | |
105 | void TearDown() override { |
106 | EnvTable.clear(); |
107 | EnvStorage.clear(); |
108 | } |
109 | |
110 | void addEnvVar(StringRef Var) { EnvTable.emplace_back(args&: Var); } |
111 | |
112 | ArrayRef<StringRef> getEnviron() const { return EnvTable; } |
113 | }; |
114 | |
115 | #ifdef _WIN32 |
116 | void checkSeparators(StringRef Path) { |
117 | char UndesiredSeparator = sys::path::get_separator()[0] == '/' ? '\\' : '/'; |
118 | ASSERT_EQ(Path.find(UndesiredSeparator), StringRef::npos); |
119 | } |
120 | |
121 | TEST_F(ProgramEnvTest, CreateProcessLongPath) { |
122 | if (getenv("LLVM_PROGRAM_TEST_LONG_PATH" )) |
123 | exit(0); |
124 | |
125 | // getMainExecutable returns an absolute path; prepend the long-path prefix. |
126 | SmallString<128> MyAbsExe( |
127 | sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1)); |
128 | checkSeparators(MyAbsExe); |
129 | // Force a path with backslashes, when we are going to prepend the \\?\ |
130 | // prefix. |
131 | sys::path::native(MyAbsExe, sys::path::Style::windows_backslash); |
132 | std::string MyExe; |
133 | if (!StringRef(MyAbsExe).starts_with("\\\\?\\" )) |
134 | MyExe.append("\\\\?\\" ); |
135 | MyExe.append(std::string(MyAbsExe.begin(), MyAbsExe.end())); |
136 | |
137 | StringRef ArgV[] = {MyExe, |
138 | "--gtest_filter=ProgramEnvTest.CreateProcessLongPath" }; |
139 | |
140 | // Add LLVM_PROGRAM_TEST_LONG_PATH to the environment of the child. |
141 | addEnvVar("LLVM_PROGRAM_TEST_LONG_PATH=1" ); |
142 | |
143 | // Redirect stdout to a long path. |
144 | SmallString<128> TestDirectory; |
145 | ASSERT_NO_ERROR( |
146 | fs::createUniqueDirectory("program-redirect-test" , TestDirectory)); |
147 | SmallString<256> LongPath(TestDirectory); |
148 | LongPath.push_back('\\'); |
149 | // MAX_PATH = 260 |
150 | LongPath.append(260 - TestDirectory.size(), 'a'); |
151 | |
152 | std::string Error; |
153 | bool ExecutionFailed; |
154 | std::optional<StringRef> Redirects[] = {std::nullopt, LongPath.str(), |
155 | std::nullopt}; |
156 | int RC = ExecuteAndWait(MyExe, ArgV, getEnviron(), Redirects, |
157 | /*secondsToWait=*/ 10, /*memoryLimit=*/ 0, &Error, |
158 | &ExecutionFailed); |
159 | EXPECT_FALSE(ExecutionFailed) << Error; |
160 | EXPECT_EQ(0, RC); |
161 | |
162 | // Remove the long stdout. |
163 | ASSERT_NO_ERROR(fs::remove(Twine(LongPath))); |
164 | ASSERT_NO_ERROR(fs::remove(Twine(TestDirectory))); |
165 | } |
166 | #endif |
167 | |
168 | TEST_F(ProgramEnvTest, CreateProcessTrailingSlash) { |
169 | if (getenv(name: "LLVM_PROGRAM_TEST_CHILD" )) { |
170 | if (ProgramTestStringArg1 == "has\\\\ trailing\\" && |
171 | ProgramTestStringArg2 == "has\\\\ trailing\\" ) { |
172 | exit(status: 0); // Success! The arguments were passed and parsed. |
173 | } |
174 | exit(status: 1); |
175 | } |
176 | |
177 | std::string my_exe = |
178 | sys::fs::getMainExecutable(argv0: TestMainArgv0, MainExecAddr: &ProgramTestStringArg1); |
179 | StringRef argv[] = { |
180 | my_exe, |
181 | "--gtest_filter=ProgramEnvTest.CreateProcessTrailingSlash" , |
182 | "-program-test-string-arg1" , |
183 | "has\\\\ trailing\\" , |
184 | "-program-test-string-arg2" , |
185 | "has\\\\ trailing\\" }; |
186 | |
187 | // Add LLVM_PROGRAM_TEST_CHILD to the environment of the child. |
188 | addEnvVar(Var: "LLVM_PROGRAM_TEST_CHILD=1" ); |
189 | |
190 | std::string error; |
191 | bool ExecutionFailed; |
192 | // Redirect stdout and stdin to NUL, but let stderr through. |
193 | #ifdef _WIN32 |
194 | StringRef nul("NUL" ); |
195 | #else |
196 | StringRef nul("/dev/null" ); |
197 | #endif |
198 | std::optional<StringRef> redirects[] = {nul, nul, std::nullopt}; |
199 | int rc = ExecuteAndWait(Program: my_exe, Args: argv, Env: getEnviron(), Redirects: redirects, |
200 | /*secondsToWait=*/ SecondsToWait: 10, /*memoryLimit=*/ MemoryLimit: 0, ErrMsg: &error, |
201 | ExecutionFailed: &ExecutionFailed); |
202 | EXPECT_FALSE(ExecutionFailed) << error; |
203 | EXPECT_EQ(0, rc); |
204 | } |
205 | |
206 | TEST_F(ProgramEnvTest, TestExecuteNoWait) { |
207 | using namespace llvm::sys; |
208 | |
209 | if (getenv(name: "LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT" )) { |
210 | sleep_for(/*seconds*/ 1); |
211 | exit(status: 0); |
212 | } |
213 | |
214 | std::string Executable = |
215 | sys::fs::getMainExecutable(argv0: TestMainArgv0, MainExecAddr: &ProgramTestStringArg1); |
216 | StringRef argv[] = {Executable, |
217 | "--gtest_filter=ProgramEnvTest.TestExecuteNoWait" }; |
218 | |
219 | // Add LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT to the environment of the child. |
220 | addEnvVar(Var: "LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT=1" ); |
221 | |
222 | std::string Error; |
223 | bool ExecutionFailed; |
224 | ProcessInfo PI1 = ExecuteNoWait(Program: Executable, Args: argv, Env: getEnviron(), Redirects: {}, MemoryLimit: 0, ErrMsg: &Error, |
225 | ExecutionFailed: &ExecutionFailed); |
226 | ASSERT_FALSE(ExecutionFailed) << Error; |
227 | ASSERT_NE(PI1.Pid, ProcessInfo::InvalidPid) << "Invalid process id" ; |
228 | |
229 | unsigned LoopCount = 0; |
230 | |
231 | // Test that Wait() with SecondsToWait=std::nullopt works. In this case, |
232 | // LoopCount should only be incremented once. |
233 | while (true) { |
234 | ++LoopCount; |
235 | ProcessInfo WaitResult = |
236 | llvm::sys::Wait(PI: PI1, /*SecondsToWait=*/std::nullopt, ErrMsg: &Error); |
237 | ASSERT_TRUE(Error.empty()); |
238 | if (WaitResult.Pid == PI1.Pid) |
239 | break; |
240 | } |
241 | |
242 | EXPECT_EQ(LoopCount, 1u) << "LoopCount should be 1" ; |
243 | |
244 | ProcessInfo PI2 = ExecuteNoWait(Program: Executable, Args: argv, Env: getEnviron(), |
245 | /*Redirects*/ {}, /*MemoryLimit*/ 0, ErrMsg: &Error, |
246 | ExecutionFailed: &ExecutionFailed); |
247 | ASSERT_FALSE(ExecutionFailed) << Error; |
248 | ASSERT_NE(PI2.Pid, ProcessInfo::InvalidPid) << "Invalid process id" ; |
249 | |
250 | // Test that Wait() with SecondsToWait=0 performs a non-blocking wait. In this |
251 | // case, LoopCount should be greater than 1 (more than one increment occurs). |
252 | while (true) { |
253 | ++LoopCount; |
254 | ProcessInfo WaitResult = llvm::sys::Wait(PI: PI2, /*SecondsToWait=*/0, ErrMsg: &Error); |
255 | ASSERT_TRUE(Error.empty()); |
256 | if (WaitResult.Pid == PI2.Pid) |
257 | break; |
258 | } |
259 | |
260 | ASSERT_GT(LoopCount, 1u) << "LoopCount should be >1" ; |
261 | } |
262 | |
263 | TEST_F(ProgramEnvTest, TestExecuteNoWaitDetached) { |
264 | using namespace llvm::sys; |
265 | |
266 | if (getenv(name: "LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT_DETACHED" )) { |
267 | sleep_for(/*seconds=*/5); |
268 | char *Detached = getenv(name: "LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT_DETACHED_TRUE" ); |
269 | #if _WIN32 |
270 | HANDLE StdHandle = GetStdHandle(STD_OUTPUT_HANDLE); |
271 | |
272 | if (Detached && (StdHandle == INVALID_HANDLE_VALUE || StdHandle == NULL)) |
273 | exit(100); |
274 | if (!Detached && (StdHandle != INVALID_HANDLE_VALUE && StdHandle != NULL)) |
275 | exit(200); |
276 | #else |
277 | int ParentSID = std::stoi( |
278 | str: std::string(getenv(name: "LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT_DETACHED_SID" ))); |
279 | |
280 | pid_t ChildSID = ::getsid(pid: 0); |
281 | if (ChildSID == -1) { |
282 | llvm::errs() << "Could not get process SID: " << strerror(errno) << '\n'; |
283 | exit(status: 1); |
284 | } |
285 | |
286 | if (Detached && (ChildSID != ParentSID)) |
287 | exit(status: 100); |
288 | if (!Detached && (ChildSID == ParentSID)) |
289 | exit(status: 200); |
290 | #endif |
291 | exit(status: 0); |
292 | } |
293 | |
294 | std::string Executable = |
295 | sys::fs::getMainExecutable(argv0: TestMainArgv0, MainExecAddr: &ProgramTestStringArg1); |
296 | StringRef argv[] = { |
297 | Executable, "--gtest_filter=ProgramEnvTest.TestExecuteNoWaitDetached" }; |
298 | addEnvVar(Var: "LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT_DETACHED=1" ); |
299 | |
300 | #if _WIN32 |
301 | // Depending on how the test is run it may already be detached from a |
302 | // console. Temporarily allocate a new console. If a console already |
303 | // exists AllocConsole will harmlessly fail and return false |
304 | BOOL AllocConsoleSuccess = AllocConsole(); |
305 | |
306 | // Confirm existence of console |
307 | HANDLE StdHandle = GetStdHandle(STD_OUTPUT_HANDLE); |
308 | ASSERT_TRUE(StdHandle != INVALID_HANDLE_VALUE && StdHandle != NULL); |
309 | #else |
310 | pid_t SID = ::getsid(pid: 0); |
311 | ASSERT_NE(SID, -1); |
312 | std::string SIDEnvVar = |
313 | "LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT_DETACHED_SID=" + std::to_string(val: SID); |
314 | addEnvVar(Var: SIDEnvVar); |
315 | #endif |
316 | |
317 | // DetachProcess = true |
318 | { |
319 | std::string Error; |
320 | bool ExecutionFailed; |
321 | std::vector<llvm::StringRef> Env = getEnviron(); |
322 | Env.emplace_back(args: "LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT_DETACHED_TRUE=1" ); |
323 | ProcessInfo PI1 = |
324 | ExecuteNoWait(Program: Executable, Args: argv, Env, Redirects: {}, MemoryLimit: 0, ErrMsg: &Error, ExecutionFailed: &ExecutionFailed, |
325 | AffinityMask: nullptr, /*DetachProcess=*/true); |
326 | ASSERT_FALSE(ExecutionFailed) << Error; |
327 | ASSERT_NE(PI1.Pid, ProcessInfo::InvalidPid) << "Invalid process id" ; |
328 | ProcessInfo WaitResult = Wait(PI: PI1, SecondsToWait: std::nullopt, ErrMsg: &Error); |
329 | ASSERT_EQ(WaitResult.ReturnCode, 100); |
330 | } |
331 | |
332 | // DetachProcess = false |
333 | { |
334 | std::string Error; |
335 | bool ExecutionFailed; |
336 | ProcessInfo PI2 = |
337 | ExecuteNoWait(Program: Executable, Args: argv, Env: getEnviron(), Redirects: {}, MemoryLimit: 0, ErrMsg: &Error, |
338 | ExecutionFailed: &ExecutionFailed, AffinityMask: nullptr, /*DetachProcess=*/false); |
339 | ASSERT_FALSE(ExecutionFailed) << Error; |
340 | ASSERT_NE(PI2.Pid, ProcessInfo::InvalidPid) << "Invalid process id" ; |
341 | ProcessInfo WaitResult = Wait(PI: PI2, SecondsToWait: std::nullopt, ErrMsg: &Error); |
342 | ASSERT_EQ(WaitResult.ReturnCode, 200); |
343 | } |
344 | #if _WIN32 |
345 | // If console was allocated then free the console |
346 | if (AllocConsoleSuccess) { |
347 | BOOL FreeConsoleSuccess = FreeConsole(); |
348 | ASSERT_NE(FreeConsoleSuccess, 0); |
349 | } |
350 | #endif |
351 | } |
352 | |
353 | TEST_F(ProgramEnvTest, TestExecuteAndWaitTimeout) { |
354 | using namespace llvm::sys; |
355 | |
356 | if (getenv(name: "LLVM_PROGRAM_TEST_TIMEOUT" )) { |
357 | sleep_for(/*seconds*/ 10); |
358 | exit(status: 0); |
359 | } |
360 | |
361 | std::string Executable = |
362 | sys::fs::getMainExecutable(argv0: TestMainArgv0, MainExecAddr: &ProgramTestStringArg1); |
363 | StringRef argv[] = { |
364 | Executable, "--gtest_filter=ProgramEnvTest.TestExecuteAndWaitTimeout" }; |
365 | |
366 | // Add LLVM_PROGRAM_TEST_TIMEOUT to the environment of the child. |
367 | addEnvVar(Var: "LLVM_PROGRAM_TEST_TIMEOUT=1" ); |
368 | |
369 | std::string Error; |
370 | bool ExecutionFailed; |
371 | int RetCode = |
372 | ExecuteAndWait(Program: Executable, Args: argv, Env: getEnviron(), Redirects: {}, /*SecondsToWait=*/1, |
373 | /*MemoryLimit*/ 0, ErrMsg: &Error, ExecutionFailed: &ExecutionFailed); |
374 | ASSERT_EQ(-2, RetCode); |
375 | } |
376 | |
377 | TEST_F(ProgramEnvTest, TestExecuteNoWaitTimeoutPolling) { |
378 | using namespace llvm::sys; |
379 | |
380 | if (getenv(name: "LLVM_PROGRAM_TEST_TIMEOUT" )) { |
381 | sleep_for(/*seconds*/ 5); |
382 | exit(status: 0); |
383 | } |
384 | |
385 | std::string Executable = |
386 | sys::fs::getMainExecutable(argv0: TestMainArgv0, MainExecAddr: &ProgramTestStringArg1); |
387 | StringRef argv[] = { |
388 | Executable, |
389 | "--gtest_filter=ProgramEnvTest.TestExecuteNoWaitTimeoutPolling" }; |
390 | |
391 | // Add LLVM_PROGRAM_TEST_TIMEOUT to the environment of the child. |
392 | addEnvVar(Var: "LLVM_PROGRAM_TEST_TIMEOUT=1" ); |
393 | |
394 | std::string Error; |
395 | bool ExecutionFailed; |
396 | ProcessInfo PI0 = ExecuteNoWait(Program: Executable, Args: argv, Env: getEnviron(), |
397 | /*Redirects=*/{}, /*MemoryLimit=*/0, ErrMsg: &Error, |
398 | ExecutionFailed: &ExecutionFailed); |
399 | ASSERT_FALSE(ExecutionFailed) << Error; |
400 | ASSERT_NE(PI0.Pid, ProcessInfo::InvalidPid) << "Invalid process id" ; |
401 | |
402 | // Check that we don't kill the process with a non-0 SecondsToWait if Polling. |
403 | unsigned LoopCount = 0; |
404 | ProcessInfo WaitResult; |
405 | do { |
406 | ++LoopCount; |
407 | WaitResult = llvm::sys::Wait(PI: PI0, /*SecondsToWait=*/1, ErrMsg: &Error, |
408 | /*ProcStats=*/ProcStat: nullptr, |
409 | /*Polling=*/true); |
410 | ASSERT_TRUE(Error.empty()) << Error; |
411 | } while (WaitResult.Pid != PI0.Pid); |
412 | |
413 | ASSERT_GT(LoopCount, 1u) << "LoopCount should be >1" ; |
414 | } |
415 | |
416 | TEST(ProgramTest, TestExecuteNegative) { |
417 | std::string Executable = "i_dont_exist" ; |
418 | StringRef argv[] = {Executable}; |
419 | |
420 | { |
421 | std::string Error; |
422 | bool ExecutionFailed; |
423 | int RetCode = ExecuteAndWait(Program: Executable, Args: argv, Env: std::nullopt, Redirects: {}, SecondsToWait: 0, MemoryLimit: 0, |
424 | ErrMsg: &Error, ExecutionFailed: &ExecutionFailed); |
425 | ASSERT_LT(RetCode, 0) << "On error ExecuteAndWait should return 0 or " |
426 | "positive value indicating the result code" ; |
427 | ASSERT_TRUE(ExecutionFailed); |
428 | ASSERT_FALSE(Error.empty()); |
429 | } |
430 | |
431 | { |
432 | std::string Error; |
433 | bool ExecutionFailed; |
434 | ProcessInfo PI = ExecuteNoWait(Program: Executable, Args: argv, Env: std::nullopt, Redirects: {}, MemoryLimit: 0, |
435 | ErrMsg: &Error, ExecutionFailed: &ExecutionFailed); |
436 | ASSERT_EQ(PI.Pid, ProcessInfo::InvalidPid) |
437 | << "On error ExecuteNoWait should return an invalid ProcessInfo" ; |
438 | ASSERT_TRUE(ExecutionFailed); |
439 | ASSERT_FALSE(Error.empty()); |
440 | } |
441 | |
442 | } |
443 | |
444 | #ifdef _WIN32 |
445 | const char utf16le_text[] = |
446 | "\x6c\x00\x69\x00\x6e\x00\x67\x00\xfc\x00\x69\x00\xe7\x00\x61\x00" ; |
447 | const char utf16be_text[] = |
448 | "\x00\x6c\x00\x69\x00\x6e\x00\x67\x00\xfc\x00\x69\x00\xe7\x00\x61" ; |
449 | #endif |
450 | const char utf8_text[] = "\x6c\x69\x6e\x67\xc3\xbc\x69\xc3\xa7\x61" ; |
451 | |
452 | TEST(ProgramTest, TestWriteWithSystemEncoding) { |
453 | SmallString<128> TestDirectory; |
454 | ASSERT_NO_ERROR(fs::createUniqueDirectory("program-test" , TestDirectory)); |
455 | errs() << "Test Directory: " << TestDirectory << '\n'; |
456 | errs().flush(); |
457 | SmallString<128> file_pathname(TestDirectory); |
458 | path::append(path&: file_pathname, a: "international-file.txt" ); |
459 | // Only on Windows we should encode in UTF16. For other systems, use UTF8 |
460 | ASSERT_NO_ERROR(sys::writeFileWithEncoding(file_pathname.c_str(), utf8_text, |
461 | sys::WEM_UTF16)); |
462 | int fd = 0; |
463 | ASSERT_NO_ERROR(fs::openFileForRead(file_pathname.c_str(), fd)); |
464 | #if defined(_WIN32) |
465 | char buf[18]; |
466 | ASSERT_EQ(::read(fd, buf, 18), 18); |
467 | const char *utf16_text; |
468 | if (strncmp(buf, "\xfe\xff" , 2) == 0) { // UTF16-BE |
469 | utf16_text = utf16be_text; |
470 | } else if (strncmp(buf, "\xff\xfe" , 2) == 0) { // UTF16-LE |
471 | utf16_text = utf16le_text; |
472 | } else { |
473 | FAIL() << "Invalid BOM in UTF-16 file" ; |
474 | } |
475 | ASSERT_EQ(strncmp(&buf[2], utf16_text, 16), 0); |
476 | #else |
477 | char buf[10]; |
478 | ASSERT_EQ(::read(fd, buf, 10), 10); |
479 | ASSERT_EQ(strncmp(buf, utf8_text, 10), 0); |
480 | #endif |
481 | ::close(fd: fd); |
482 | ASSERT_NO_ERROR(fs::remove(file_pathname.str())); |
483 | ASSERT_NO_ERROR(fs::remove(TestDirectory.str())); |
484 | } |
485 | |
486 | TEST_F(ProgramEnvTest, TestExecuteAndWaitStatistics) { |
487 | using namespace llvm::sys; |
488 | |
489 | if (getenv(name: "LLVM_PROGRAM_TEST_STATISTICS" )) |
490 | exit(status: 0); |
491 | |
492 | std::string Executable = |
493 | sys::fs::getMainExecutable(argv0: TestMainArgv0, MainExecAddr: &ProgramTestStringArg1); |
494 | StringRef argv[] = { |
495 | Executable, "--gtest_filter=ProgramEnvTest.TestExecuteAndWaitStatistics" }; |
496 | |
497 | // Add LLVM_PROGRAM_TEST_STATISTICS to the environment of the child. |
498 | addEnvVar(Var: "LLVM_PROGRAM_TEST_STATISTICS=1" ); |
499 | |
500 | std::string Error; |
501 | bool ExecutionFailed; |
502 | std::optional<ProcessStatistics> ProcStat; |
503 | int RetCode = ExecuteAndWait(Program: Executable, Args: argv, Env: getEnviron(), Redirects: {}, SecondsToWait: 0, MemoryLimit: 0, ErrMsg: &Error, |
504 | ExecutionFailed: &ExecutionFailed, ProcStat: &ProcStat); |
505 | ASSERT_EQ(0, RetCode); |
506 | ASSERT_TRUE(ProcStat); |
507 | ASSERT_GE(ProcStat->UserTime, std::chrono::microseconds(0)); |
508 | ASSERT_GE(ProcStat->TotalTime, ProcStat->UserTime); |
509 | } |
510 | |
511 | TEST_F(ProgramEnvTest, TestLockFile) { |
512 | using namespace llvm::sys; |
513 | |
514 | if (const char *LockedFile = getenv(name: "LLVM_PROGRAM_TEST_LOCKED_FILE" )) { |
515 | // Child process. |
516 | int FD2; |
517 | ASSERT_NO_ERROR(fs::openFileForReadWrite(LockedFile, FD2, |
518 | fs::CD_OpenExisting, fs::OF_None)); |
519 | |
520 | std::error_code ErrC = fs::tryLockFile(FD: FD2, Timeout: std::chrono::seconds(5)); |
521 | ASSERT_NO_ERROR(ErrC); |
522 | ASSERT_NO_ERROR(fs::unlockFile(FD2)); |
523 | close(fd: FD2); |
524 | exit(status: 0); |
525 | } |
526 | |
527 | // Create file that will be locked. |
528 | SmallString<64> LockedFile; |
529 | int FD1; |
530 | ASSERT_NO_ERROR( |
531 | fs::createTemporaryFile("TestLockFile" , "temp" , FD1, LockedFile)); |
532 | |
533 | std::string Executable = |
534 | sys::fs::getMainExecutable(argv0: TestMainArgv0, MainExecAddr: &ProgramTestStringArg1); |
535 | StringRef argv[] = {Executable, "--gtest_filter=ProgramEnvTest.TestLockFile" }; |
536 | |
537 | // Add LLVM_PROGRAM_TEST_LOCKED_FILE to the environment of the child. |
538 | std::string EnvVar = "LLVM_PROGRAM_TEST_LOCKED_FILE=" ; |
539 | EnvVar += LockedFile.str(); |
540 | addEnvVar(Var: EnvVar); |
541 | |
542 | // Lock the file. |
543 | ASSERT_NO_ERROR(fs::tryLockFile(FD1)); |
544 | |
545 | std::string Error; |
546 | bool ExecutionFailed; |
547 | ProcessInfo PI2 = ExecuteNoWait(Program: Executable, Args: argv, Env: getEnviron(), Redirects: {}, MemoryLimit: 0, ErrMsg: &Error, |
548 | ExecutionFailed: &ExecutionFailed); |
549 | ASSERT_FALSE(ExecutionFailed) << Error; |
550 | ASSERT_TRUE(Error.empty()); |
551 | ASSERT_NE(PI2.Pid, ProcessInfo::InvalidPid) << "Invalid process id" ; |
552 | |
553 | // Wait some time to give the child process a chance to start. |
554 | std::this_thread::sleep_for(rtime: std::chrono::milliseconds(100)); |
555 | |
556 | ASSERT_NO_ERROR(fs::unlockFile(FD1)); |
557 | ProcessInfo WaitResult = llvm::sys::Wait(PI: PI2, /*SecondsToWait=*/5, ErrMsg: &Error); |
558 | ASSERT_TRUE(Error.empty()); |
559 | ASSERT_EQ(0, WaitResult.ReturnCode); |
560 | ASSERT_EQ(WaitResult.Pid, PI2.Pid); |
561 | sys::fs::remove(path: LockedFile); |
562 | } |
563 | |
564 | TEST_F(ProgramEnvTest, TestExecuteWithNoStacktraceHandler) { |
565 | using namespace llvm::sys; |
566 | |
567 | if (getenv(name: "LLVM_PROGRAM_TEST_NO_STACKTRACE_HANDLER" )) { |
568 | sys::PrintStackTrace(OS&: errs()); |
569 | exit(status: 0); |
570 | } |
571 | |
572 | std::string Executable = |
573 | sys::fs::getMainExecutable(argv0: TestMainArgv0, MainExecAddr: &ProgramTestStringArg1); |
574 | StringRef argv[] = { |
575 | Executable, |
576 | "--gtest_filter=ProgramEnvTest.TestExecuteWithNoStacktraceHandler" }; |
577 | |
578 | addEnvVar(Var: "LLVM_PROGRAM_TEST_NO_STACKTRACE_HANDLER=1" ); |
579 | |
580 | std::string Error; |
581 | bool ExecutionFailed; |
582 | int RetCode = ExecuteAndWait(Program: Executable, Args: argv, Env: getEnviron(), Redirects: {}, SecondsToWait: 0, MemoryLimit: 0, ErrMsg: &Error, |
583 | ExecutionFailed: &ExecutionFailed); |
584 | EXPECT_FALSE(ExecutionFailed) << Error; |
585 | ASSERT_EQ(0, RetCode); |
586 | } |
587 | |
588 | } // end anonymous namespace |
589 | |