1 //===- unittest/Support/ProgramTest.cpp -----------------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "llvm/Support/ConvertUTF.h" 11 #include "llvm/Support/CommandLine.h" 12 #include "llvm/Support/FileSystem.h" 13 #include "llvm/Support/Path.h" 14 #include "llvm/Support/Program.h" 15 #include "gtest/gtest.h" 16 #include <stdlib.h> 17 #if defined(__APPLE__) 18 # include <crt_externs.h> 19 #elif !defined(_MSC_VER) 20 // Forward declare environ in case it's not provided by stdlib.h. 21 extern char **environ; 22 #endif 23 24 #if defined(LLVM_ON_UNIX) 25 #include <unistd.h> 26 void sleep_for(unsigned int seconds) { 27 sleep(seconds); 28 } 29 #elif defined(LLVM_ON_WIN32) 30 #include <windows.h> 31 void sleep_for(unsigned int seconds) { 32 Sleep(seconds * 1000); 33 } 34 #else 35 #error sleep_for is not implemented on your platform. 36 #endif 37 38 #define ASSERT_NO_ERROR(x) \ 39 if (std::error_code ASSERT_NO_ERROR_ec = x) { \ 40 SmallString<128> MessageStorage; \ 41 raw_svector_ostream Message(MessageStorage); \ 42 Message << #x ": did not return errc::success.\n" \ 43 << "error number: " << ASSERT_NO_ERROR_ec.value() << "\n" \ 44 << "error message: " << ASSERT_NO_ERROR_ec.message() << "\n"; \ 45 GTEST_FATAL_FAILURE_(MessageStorage.c_str()); \ 46 } else { \ 47 } 48 // From TestMain.cpp. 49 extern const char *TestMainArgv0; 50 51 namespace { 52 53 using namespace llvm; 54 using namespace sys; 55 56 static cl::opt<std::string> 57 ProgramTestStringArg1("program-test-string-arg1"); 58 static cl::opt<std::string> 59 ProgramTestStringArg2("program-test-string-arg2"); 60 61 class ProgramEnvTest : public testing::Test { 62 std::vector<const char *> EnvTable; 63 std::vector<std::string> EnvStorage; 64 65 protected: 66 void SetUp() override { 67 auto EnvP = [] { 68 #if defined(LLVM_ON_WIN32) 69 _wgetenv(L"TMP"); // Populate _wenviron, initially is null 70 return _wenviron; 71 #elif defined(__APPLE__) 72 return *_NSGetEnviron(); 73 #else 74 return environ; 75 #endif 76 }(); 77 ASSERT_TRUE(EnvP); 78 79 auto prepareEnvVar = [this](decltype(*EnvP) Var) { 80 #if defined(LLVM_ON_WIN32) 81 // On Windows convert UTF16 encoded variable to UTF8 82 auto Len = wcslen(Var); 83 ArrayRef<char> Ref{reinterpret_cast<char const *>(Var), 84 Len * sizeof(*Var)}; 85 EnvStorage.emplace_back(); 86 auto convStatus = convertUTF16ToUTF8String(Ref, EnvStorage.back()); 87 EXPECT_TRUE(convStatus); 88 return EnvStorage.back().c_str(); 89 #else 90 return Var; 91 #endif 92 }; 93 94 while (*EnvP != nullptr) { 95 EnvTable.emplace_back(prepareEnvVar(*EnvP)); 96 ++EnvP; 97 } 98 } 99 100 void TearDown() override { 101 EnvTable.clear(); 102 EnvStorage.clear(); 103 } 104 105 void addEnvVar(const char *Var) { 106 ASSERT_TRUE(EnvTable.empty() || EnvTable.back()) << "Env table sealed"; 107 EnvTable.emplace_back(Var); 108 } 109 110 const char **getEnviron() { 111 if (EnvTable.back() != nullptr) 112 EnvTable.emplace_back(nullptr); // Seal table. 113 return &EnvTable[0]; 114 } 115 }; 116 117 #ifdef LLVM_ON_WIN32 118 TEST_F(ProgramEnvTest, CreateProcessLongPath) { 119 if (getenv("LLVM_PROGRAM_TEST_LONG_PATH")) 120 exit(0); 121 122 // getMainExecutable returns an absolute path; prepend the long-path prefix. 123 std::string MyAbsExe = 124 sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); 125 std::string MyExe; 126 if (!StringRef(MyAbsExe).startswith("\\\\?\\")) 127 MyExe.append("\\\\?\\"); 128 MyExe.append(MyAbsExe); 129 130 const char *ArgV[] = { 131 MyExe.c_str(), 132 "--gtest_filter=ProgramEnvTest.CreateProcessLongPath", 133 nullptr 134 }; 135 136 // Add LLVM_PROGRAM_TEST_LONG_PATH to the environment of the child. 137 addEnvVar("LLVM_PROGRAM_TEST_LONG_PATH=1"); 138 139 // Redirect stdout to a long path. 140 SmallString<128> TestDirectory; 141 ASSERT_NO_ERROR( 142 fs::createUniqueDirectory("program-redirect-test", TestDirectory)); 143 SmallString<256> LongPath(TestDirectory); 144 LongPath.push_back('\\'); 145 // MAX_PATH = 260 146 LongPath.append(260 - TestDirectory.size(), 'a'); 147 StringRef LongPathRef(LongPath); 148 149 std::string Error; 150 bool ExecutionFailed; 151 const StringRef *Redirects[] = { nullptr, &LongPathRef, nullptr }; 152 int RC = ExecuteAndWait(MyExe, ArgV, getEnviron(), Redirects, 153 /*secondsToWait=*/ 10, /*memoryLimit=*/ 0, &Error, 154 &ExecutionFailed); 155 EXPECT_FALSE(ExecutionFailed) << Error; 156 EXPECT_EQ(0, RC); 157 158 // Remove the long stdout. 159 ASSERT_NO_ERROR(fs::remove(Twine(LongPath))); 160 ASSERT_NO_ERROR(fs::remove(Twine(TestDirectory))); 161 } 162 #endif 163 164 TEST_F(ProgramEnvTest, CreateProcessTrailingSlash) { 165 if (getenv("LLVM_PROGRAM_TEST_CHILD")) { 166 if (ProgramTestStringArg1 == "has\\\\ trailing\\" && 167 ProgramTestStringArg2 == "has\\\\ trailing\\") { 168 exit(0); // Success! The arguments were passed and parsed. 169 } 170 exit(1); 171 } 172 173 std::string my_exe = 174 sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); 175 const char *argv[] = { 176 my_exe.c_str(), 177 "--gtest_filter=ProgramEnvTest.CreateProcessTrailingSlash", 178 "-program-test-string-arg1", "has\\\\ trailing\\", 179 "-program-test-string-arg2", "has\\\\ trailing\\", 180 nullptr 181 }; 182 183 // Add LLVM_PROGRAM_TEST_CHILD to the environment of the child. 184 addEnvVar("LLVM_PROGRAM_TEST_CHILD=1"); 185 186 std::string error; 187 bool ExecutionFailed; 188 // Redirect stdout and stdin to NUL, but let stderr through. 189 #ifdef LLVM_ON_WIN32 190 StringRef nul("NUL"); 191 #else 192 StringRef nul("/dev/null"); 193 #endif 194 const StringRef *redirects[] = { &nul, &nul, nullptr }; 195 int rc = ExecuteAndWait(my_exe, argv, getEnviron(), redirects, 196 /*secondsToWait=*/ 10, /*memoryLimit=*/ 0, &error, 197 &ExecutionFailed); 198 EXPECT_FALSE(ExecutionFailed) << error; 199 EXPECT_EQ(0, rc); 200 } 201 202 TEST_F(ProgramEnvTest, TestExecuteNoWait) { 203 using namespace llvm::sys; 204 205 if (getenv("LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT")) { 206 sleep_for(/*seconds*/ 1); 207 exit(0); 208 } 209 210 std::string Executable = 211 sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); 212 const char *argv[] = { 213 Executable.c_str(), 214 "--gtest_filter=ProgramEnvTest.TestExecuteNoWait", 215 nullptr 216 }; 217 218 // Add LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT to the environment of the child. 219 addEnvVar("LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT=1"); 220 221 std::string Error; 222 bool ExecutionFailed; 223 ProcessInfo PI1 = ExecuteNoWait(Executable, argv, getEnviron(), nullptr, 0, 224 &Error, &ExecutionFailed); 225 ASSERT_FALSE(ExecutionFailed) << Error; 226 ASSERT_NE(PI1.Pid, 0) << "Invalid process id"; 227 228 unsigned LoopCount = 0; 229 230 // Test that Wait() with WaitUntilTerminates=true works. In this case, 231 // LoopCount should only be incremented once. 232 while (true) { 233 ++LoopCount; 234 ProcessInfo WaitResult = Wait(PI1, 0, true, &Error); 235 ASSERT_TRUE(Error.empty()); 236 if (WaitResult.Pid == PI1.Pid) 237 break; 238 } 239 240 EXPECT_EQ(LoopCount, 1u) << "LoopCount should be 1"; 241 242 ProcessInfo PI2 = ExecuteNoWait(Executable, argv, getEnviron(), nullptr, 0, 243 &Error, &ExecutionFailed); 244 ASSERT_FALSE(ExecutionFailed) << Error; 245 ASSERT_NE(PI2.Pid, 0) << "Invalid process id"; 246 247 // Test that Wait() with SecondsToWait=0 performs a non-blocking wait. In this 248 // cse, LoopCount should be greater than 1 (more than one increment occurs). 249 while (true) { 250 ++LoopCount; 251 ProcessInfo WaitResult = Wait(PI2, 0, false, &Error); 252 ASSERT_TRUE(Error.empty()); 253 if (WaitResult.Pid == PI2.Pid) 254 break; 255 } 256 257 ASSERT_GT(LoopCount, 1u) << "LoopCount should be >1"; 258 } 259 260 TEST_F(ProgramEnvTest, TestExecuteAndWaitTimeout) { 261 using namespace llvm::sys; 262 263 if (getenv("LLVM_PROGRAM_TEST_TIMEOUT")) { 264 sleep_for(/*seconds*/ 10); 265 exit(0); 266 } 267 268 std::string Executable = 269 sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); 270 const char *argv[] = { 271 Executable.c_str(), 272 "--gtest_filter=ProgramEnvTest.TestExecuteAndWaitTimeout", 273 nullptr 274 }; 275 276 // Add LLVM_PROGRAM_TEST_TIMEOUT to the environment of the child. 277 addEnvVar("LLVM_PROGRAM_TEST_TIMEOUT=1"); 278 279 std::string Error; 280 bool ExecutionFailed; 281 int RetCode = 282 ExecuteAndWait(Executable, argv, getEnviron(), nullptr, /*secondsToWait=*/1, 0, 283 &Error, &ExecutionFailed); 284 ASSERT_EQ(-2, RetCode); 285 } 286 287 TEST(ProgramTest, TestExecuteNegative) { 288 std::string Executable = "i_dont_exist"; 289 const char *argv[] = { Executable.c_str(), nullptr }; 290 291 { 292 std::string Error; 293 bool ExecutionFailed; 294 int RetCode = ExecuteAndWait(Executable, argv, nullptr, nullptr, 0, 0, 295 &Error, &ExecutionFailed); 296 ASSERT_TRUE(RetCode < 0) << "On error ExecuteAndWait should return 0 or " 297 "positive value indicating the result code"; 298 ASSERT_TRUE(ExecutionFailed); 299 ASSERT_FALSE(Error.empty()); 300 } 301 302 { 303 std::string Error; 304 bool ExecutionFailed; 305 ProcessInfo PI = ExecuteNoWait(Executable, argv, nullptr, nullptr, 0, 306 &Error, &ExecutionFailed); 307 ASSERT_EQ(PI.Pid, 0) 308 << "On error ExecuteNoWait should return an invalid ProcessInfo"; 309 ASSERT_TRUE(ExecutionFailed); 310 ASSERT_FALSE(Error.empty()); 311 } 312 313 } 314 315 #ifdef LLVM_ON_WIN32 316 const char utf16le_text[] = 317 "\x6c\x00\x69\x00\x6e\x00\x67\x00\xfc\x00\x69\x00\xe7\x00\x61\x00"; 318 const char utf16be_text[] = 319 "\x00\x6c\x00\x69\x00\x6e\x00\x67\x00\xfc\x00\x69\x00\xe7\x00\x61"; 320 #endif 321 const char utf8_text[] = "\x6c\x69\x6e\x67\xc3\xbc\x69\xc3\xa7\x61"; 322 323 TEST(ProgramTest, TestWriteWithSystemEncoding) { 324 SmallString<128> TestDirectory; 325 ASSERT_NO_ERROR(fs::createUniqueDirectory("program-test", TestDirectory)); 326 errs() << "Test Directory: " << TestDirectory << '\n'; 327 errs().flush(); 328 SmallString<128> file_pathname(TestDirectory); 329 path::append(file_pathname, "international-file.txt"); 330 // Only on Windows we should encode in UTF16. For other systems, use UTF8 331 ASSERT_NO_ERROR(sys::writeFileWithEncoding(file_pathname.c_str(), utf8_text, 332 sys::WEM_UTF16)); 333 int fd = 0; 334 ASSERT_NO_ERROR(fs::openFileForRead(file_pathname.c_str(), fd)); 335 #if defined(LLVM_ON_WIN32) 336 char buf[18]; 337 ASSERT_EQ(::read(fd, buf, 18), 18); 338 if (strncmp(buf, "\xfe\xff", 2) == 0) { // UTF16-BE 339 ASSERT_EQ(strncmp(&buf[2], utf16be_text, 16), 0); 340 } else if (strncmp(buf, "\xff\xfe", 2) == 0) { // UTF16-LE 341 ASSERT_EQ(strncmp(&buf[2], utf16le_text, 16), 0); 342 } else { 343 FAIL() << "Invalid BOM in UTF-16 file"; 344 } 345 #else 346 char buf[10]; 347 ASSERT_EQ(::read(fd, buf, 10), 10); 348 ASSERT_EQ(strncmp(buf, utf8_text, 10), 0); 349 #endif 350 ::close(fd); 351 ASSERT_NO_ERROR(fs::remove(file_pathname.str())); 352 ASSERT_NO_ERROR(fs::remove(TestDirectory.str())); 353 } 354 355 } // end anonymous namespace 356