|  | // Copyright 2018 The Bazel Authors. All rights reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | // Test wrapper implementation for Windows. | 
|  | // Design: | 
|  | // https://github.com/laszlocsomor/proposals/blob/win-test-runner/designs/2018-07-18-windows-native-test-runner.md | 
|  |  | 
|  | #include "tools/test/windows/tw.h" | 
|  |  | 
|  | #ifndef WIN32_LEAN_AND_MEAN | 
|  | #define WIN32_LEAN_AND_MEAN | 
|  | #endif | 
|  | #include <windows.h> | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <limits.h>  // INT_MAX | 
|  | #include <lmcons.h>  // UNLEN | 
|  | #include <string.h> | 
|  | #include <sys/types.h> | 
|  | #include <wchar.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cstdio> | 
|  | #include <fstream> | 
|  | #include <functional> | 
|  | #include <iomanip> | 
|  | #include <memory> | 
|  | #include <sstream> | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include "src/main/cpp/util/file_platform.h" | 
|  | #include "src/main/cpp/util/path_platform.h" | 
|  | #include "src/main/cpp/util/strings.h" | 
|  | #include "src/main/native/windows/file.h" | 
|  | #include "src/main/native/windows/process.h" | 
|  | #include "src/main/native/windows/util.h" | 
|  | #include "third_party/ijar/common.h" | 
|  | #include "third_party/ijar/platform_utils.h" | 
|  | #include "third_party/ijar/zip.h" | 
|  | #include "tools/cpp/runfiles/runfiles.h" | 
|  |  | 
|  | namespace bazel { | 
|  | namespace tools { | 
|  | namespace test_wrapper { | 
|  | namespace { | 
|  |  | 
|  | class Defer { | 
|  | public: | 
|  | explicit Defer(std::function<void()> f) : f_(f) {} | 
|  | ~Defer() { f_(); } | 
|  |  | 
|  | void DoNow() { | 
|  | f_(); | 
|  | f_ = kEmpty; | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::function<void()> f_; | 
|  | static const std::function<void()> kEmpty; | 
|  | }; | 
|  |  | 
|  | const std::function<void()> Defer::kEmpty = []() {}; | 
|  |  | 
|  | // Streams data from an input to two outputs. | 
|  | // Inspired by tee(1) in the GNU coreutils. | 
|  | class TeeImpl : Tee { | 
|  | public: | 
|  | // Creates a background thread to stream data from `input` to the two outputs. | 
|  | // The thread terminates when ReadFile fails on the input (e.g. the input is | 
|  | // the reading end of a pipe and the writing end is closed) or when WriteFile | 
|  | // fails on one of the outputs (e.g. the same output handle is closed | 
|  | // elsewhere). | 
|  | static bool Create(bazel::windows::AutoHandle* input, | 
|  | bazel::windows::AutoHandle* output1, | 
|  | bazel::windows::AutoHandle* output2, | 
|  | std::unique_ptr<Tee>* result); | 
|  |  | 
|  | private: | 
|  | static DWORD WINAPI ThreadFunc(LPVOID lpParam); | 
|  |  | 
|  | TeeImpl(bazel::windows::AutoHandle* input, | 
|  | bazel::windows::AutoHandle* output1, | 
|  | bazel::windows::AutoHandle* output2) | 
|  | : input_(input), output1_(output1), output2_(output2) {} | 
|  | TeeImpl(const TeeImpl&) = delete; | 
|  | TeeImpl& operator=(const TeeImpl&) = delete; | 
|  |  | 
|  | bool MainFunc() const; | 
|  |  | 
|  | bazel::windows::AutoHandle input_; | 
|  | bazel::windows::AutoHandle output1_; | 
|  | bazel::windows::AutoHandle output2_; | 
|  | }; | 
|  |  | 
|  | // Buffered input stream (based on a Windows HANDLE) with peek-ahead support. | 
|  | // | 
|  | // This class uses two consecutive "pages" where it buffers data from the | 
|  | // underlying HANDLE (wrapped in an AutoHandle). Both pages are always loaded | 
|  | // with data until there's no more data to read. | 
|  | // | 
|  | // The "active" page is the one where the read cursor is pointing. The other | 
|  | // page is the next one to be read once the client moves the read cursor beyond | 
|  | // the end of the active page. | 
|  | // | 
|  | // The client advances the read cursor with Advance(). When the cursor reaches | 
|  | // the end of the active page, the other page becomes the active one (whose data | 
|  | // is already buffered), and the old active page is loaded with new data from | 
|  | // the underlying file. | 
|  | class IFStreamImpl : IFStream { | 
|  | public: | 
|  | // Creates a new IFStream. | 
|  | // | 
|  | // If successful, then takes ownership of the HANDLE in 'handle', and returns | 
|  | // a new IFStream pointer. Otherwise leaves 'handle' alone and returns | 
|  | // nullptr. | 
|  | static IFStream* Create(HANDLE handle, DWORD page_size = 0x100000 /* 1 MB */); | 
|  |  | 
|  | int Get() override; | 
|  | DWORD Peek(DWORD n, uint8_t* out) const override; | 
|  |  | 
|  | private: | 
|  | HANDLE handle_; | 
|  | const std::unique_ptr<uint8_t[]> pages_; | 
|  | const DWORD page_size_; | 
|  | DWORD pos_, end_, next_size_; | 
|  |  | 
|  | IFStreamImpl(HANDLE handle, std::unique_ptr<uint8_t[]>&& pages, DWORD n, | 
|  | DWORD page_size) | 
|  | : handle_(handle), | 
|  | pages_(std::move(pages)), | 
|  | page_size_(page_size), | 
|  | pos_(0), | 
|  | end_(n < page_size ? n : page_size), | 
|  | next_size_(n < page_size | 
|  | ? 0 | 
|  | : (n < page_size * 2 ? n - page_size : page_size)) {} | 
|  | }; | 
|  |  | 
|  | // A lightweight path abstraction that stores a Unicode Windows path. | 
|  | // | 
|  | // The class allows extracting the underlying path as a (immutable) string so | 
|  | // it's easy to pass the path to WinAPI functions, but the class does not allow | 
|  | // mutating the unterlying path so it's safe to pass around Path objects. | 
|  | class Path { | 
|  | public: | 
|  | Path() {} | 
|  | Path(const Path& other) : path_(other.path_) {} | 
|  | Path(Path&& other) : path_(std::move(other.path_)) {} | 
|  | Path& operator=(const Path& other) = default; | 
|  | const std::wstring& Get() const { return path_; } | 
|  | bool Set(const std::wstring& path); | 
|  |  | 
|  | // Makes this path absolute. | 
|  | // Returns true if the path was changed (i.e. was not absolute before). | 
|  | // Returns false and has no effect if this path was empty or already absolute. | 
|  | bool Absolutize(const Path& cwd); | 
|  |  | 
|  | Path Dirname() const; | 
|  |  | 
|  | private: | 
|  | std::wstring path_; | 
|  | }; | 
|  |  | 
|  | struct UndeclaredOutputs { | 
|  | Path root; | 
|  | Path zip; | 
|  | Path manifest; | 
|  | Path annotations; | 
|  | Path annotations_dir; | 
|  | }; | 
|  |  | 
|  | struct Duration { | 
|  | static constexpr int kMax = INT_MAX; | 
|  |  | 
|  | int seconds; | 
|  |  | 
|  | bool FromString(const wchar_t* str); | 
|  | }; | 
|  |  | 
|  | enum class MainType { kTestWrapperMain, kXmlWriterMain }; | 
|  | enum class DeleteAfterwards { kEnabled, kDisabled }; | 
|  |  | 
|  | void WriteStdout(const std::string& s) { | 
|  | DWORD written; | 
|  | WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), s.c_str(), s.size(), &written, | 
|  | nullptr); | 
|  | } | 
|  |  | 
|  | void LogError(const int line) { | 
|  | std::stringstream ss; | 
|  | ss << "ERROR(" << __FILE__ << ":" << line << ")" << std::endl; | 
|  | WriteStdout(ss.str()); | 
|  | } | 
|  |  | 
|  | void LogError(const int line, const std::string& msg) { | 
|  | std::stringstream ss; | 
|  | ss << "ERROR(" << __FILE__ << ":" << line << ") " << msg << std::endl; | 
|  | WriteStdout(ss.str()); | 
|  | } | 
|  |  | 
|  | void LogError(const int line, const std::wstring& msg) { | 
|  | std::string acp_msg; | 
|  | if (blaze_util::WcsToAcp(msg, &acp_msg)) { | 
|  | LogError(line, acp_msg); | 
|  | } | 
|  | } | 
|  |  | 
|  | void LogErrorWithValue(const int line, const std::string& msg, DWORD value) { | 
|  | std::stringstream ss; | 
|  | ss << "value: " << value << " (0x"; | 
|  | ss.setf(std::ios_base::hex, std::ios_base::basefield); | 
|  | ss << std::setw(8) << std::setfill('0') << value << "): "; | 
|  | ss.setf(std::ios_base::dec, std::ios_base::basefield); | 
|  | ss << msg; | 
|  | LogError(line, ss.str()); | 
|  | } | 
|  |  | 
|  | void LogErrorWithValue(const int line, const std::wstring& msg, DWORD value) { | 
|  | std::string acp_msg; | 
|  | if (blaze_util::WcsToAcp(msg, &acp_msg)) { | 
|  | LogErrorWithValue(line, acp_msg, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | void LogErrorWithArg(const int line, const std::string& msg, | 
|  | const std::string& arg) { | 
|  | std::stringstream ss; | 
|  | ss << msg << " (arg: " << arg << ")"; | 
|  | LogError(line, ss.str()); | 
|  | } | 
|  |  | 
|  | void LogErrorWithArg(const int line, const std::string& msg, | 
|  | const std::wstring& arg) { | 
|  | std::string acp_arg; | 
|  | if (blaze_util::WcsToAcp(arg, &acp_arg)) { | 
|  | LogErrorWithArg(line, msg, acp_arg); | 
|  | } | 
|  | } | 
|  |  | 
|  | void LogErrorWithArg2(const int line, const std::string& msg, | 
|  | const std::string& arg1, const std::string& arg2) { | 
|  | std::stringstream ss; | 
|  | ss << msg << " (arg1: " << arg1 << ", arg2: " << arg2 << ")"; | 
|  | LogError(line, ss.str()); | 
|  | } | 
|  |  | 
|  | void LogErrorWithArg2(const int line, const std::string& msg, | 
|  | const std::wstring& arg1, const std::wstring& arg2) { | 
|  | std::string acp_arg1, acp_arg2; | 
|  | if (blaze_util::WcsToAcp(arg1, &acp_arg1) && | 
|  | blaze_util::WcsToAcp(arg2, &acp_arg2)) { | 
|  | LogErrorWithArg2(line, msg, acp_arg1, acp_arg2); | 
|  | } | 
|  | } | 
|  |  | 
|  | void LogErrorWithArgAndValue(const int line, const std::string& msg, | 
|  | const std::string& arg, DWORD value) { | 
|  | std::stringstream ss; | 
|  | ss << "value: " << value << " (0x"; | 
|  | ss.setf(std::ios_base::hex, std::ios_base::basefield); | 
|  | ss << std::setw(8) << std::setfill('0') << value << "), arg: "; | 
|  | ss.setf(std::ios_base::dec, std::ios_base::basefield); | 
|  | ss << arg << ": " << msg; | 
|  | LogError(line, ss.str()); | 
|  | } | 
|  |  | 
|  | void LogErrorWithArgAndValue(const int line, const std::string& msg, | 
|  | const std::wstring& arg, DWORD value) { | 
|  | std::string acp_arg; | 
|  | if (blaze_util::WcsToAcp(arg, &acp_arg)) { | 
|  | LogErrorWithArgAndValue(line, msg, acp_arg, value); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::wstring AddUncPrefixMaybe(const Path& p) { | 
|  | return bazel::windows::AddUncPrefixMaybe(p.Get()); | 
|  | } | 
|  |  | 
|  | std::wstring RemoveUncPrefixMaybe(const Path& p) { | 
|  | return bazel::windows::RemoveUncPrefixMaybe(p.Get()); | 
|  | } | 
|  |  | 
|  | inline bool CreateDirectories(const Path& path) { | 
|  | blaze_util::MakeDirectoriesW(AddUncPrefixMaybe(path), 0777); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | inline bool ToInt(const wchar_t* s, int* result) { | 
|  | return std::swscanf(s, L"%d", result) == 1; | 
|  | } | 
|  |  | 
|  | bool WcsToAcp(const std::wstring& wcs, std::string* acp) { | 
|  | uint32_t err; | 
|  | if (!blaze_util::WcsToAcp(wcs, acp, &err)) { | 
|  | LogErrorWithArgAndValue(__LINE__, "Failed to convert string", wcs, err); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Converts a Windows-style path to a mixed (Unix-Windows) style. | 
|  | // The path is mixed-style because it is a Windows path (begins with a drive | 
|  | // letter) but uses forward slashes as directory separators. | 
|  | // We must export envvars as mixed style path because some tools confuse the | 
|  | // backslashes in Windows paths for Unix-style escape characters. | 
|  | std::wstring AsMixedPath(const std::wstring& path) { | 
|  | std::wstring value = path; | 
|  | std::replace(value.begin(), value.end(), L'\\', L'/'); | 
|  | return value; | 
|  | } | 
|  |  | 
|  | bool IsReadableFile(const Path& p) { | 
|  | HANDLE h = | 
|  | CreateFileW(AddUncPrefixMaybe(p).c_str(), GENERIC_READ, | 
|  | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | 
|  | nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); | 
|  | if (h == INVALID_HANDLE_VALUE) { | 
|  | return false; | 
|  | } | 
|  | CloseHandle(h); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Gets an environment variable's value. | 
|  | // Returns: | 
|  | // - true, if the envvar is defined and successfully fetched, or it's empty or | 
|  | //   undefined | 
|  | // - false, if some error occurred | 
|  | bool GetEnv(const wchar_t* name, std::wstring* result) { | 
|  | static constexpr size_t kSmallBuf = MAX_PATH; | 
|  | WCHAR value[kSmallBuf]; | 
|  | DWORD size = GetEnvironmentVariableW(name, value, kSmallBuf); | 
|  | DWORD err = GetLastError(); | 
|  | if (size == 0 && err == ERROR_ENVVAR_NOT_FOUND) { | 
|  | result->clear(); | 
|  | return true; | 
|  | } else if (0 < size && size < kSmallBuf) { | 
|  | *result = value; | 
|  | return true; | 
|  | } else if (size >= kSmallBuf) { | 
|  | std::unique_ptr<WCHAR[]> value_big(new WCHAR[size]); | 
|  | GetEnvironmentVariableW(name, value_big.get(), size); | 
|  | *result = value_big.get(); | 
|  | return true; | 
|  | } else { | 
|  | LogErrorWithArgAndValue(__LINE__, "Failed to read envvar", name, err); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Gets an environment variable's value as a Path. | 
|  | // Returns: | 
|  | // - true, if the envvar is defined and successfully fetched, or it's empty or | 
|  | //   undefined | 
|  | // - false, if some error occurred | 
|  | bool GetPathEnv(const wchar_t* name, Path* result) { | 
|  | std::wstring value; | 
|  | if (!GetEnv(name, &value)) { | 
|  | LogError(__LINE__, name); | 
|  | return false; | 
|  | } | 
|  | return result->Set(value); | 
|  | } | 
|  |  | 
|  | // Gets an environment variable's value as integer and as the original string. | 
|  | // Returns: | 
|  | // - true, if the envvar is defined and successfully fetched, or it's empty or | 
|  | //   undefined (in that case 'as_int' will be 0 and 'as_wstr' empty) | 
|  | // - false, if ToInt cannot parse the string to an int, or some error occurred | 
|  | bool GetIntEnv(const wchar_t* name, std::wstring* as_wstr, int* as_int) { | 
|  | *as_int = 0; | 
|  | if (!GetEnv(name, as_wstr) || | 
|  | (!as_wstr->empty() && !ToInt(as_wstr->c_str(), as_int))) { | 
|  | LogError(__LINE__, name); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SetEnv(const wchar_t* name, const std::wstring& value) { | 
|  | if (SetEnvironmentVariableW(name, value.c_str()) != 0) { | 
|  | return true; | 
|  | } else { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithArgAndValue(__LINE__, "Failed to set envvar", name, err); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool SetPathEnv(const wchar_t* name, const Path& path) { | 
|  | return SetEnv(name, AsMixedPath(path.Get())); | 
|  | } | 
|  |  | 
|  | bool UnsetEnv(const wchar_t* name) { | 
|  | if (SetEnvironmentVariableW(name, nullptr) != 0) { | 
|  | return true; | 
|  | } else { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithArgAndValue(__LINE__, "Failed to unset envvar", name, err); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool AddCurrentDirectoryToPATH() { | 
|  | std::wstring path; | 
|  | return GetEnv(L"PATH", &path) && SetEnv(L"PATH", L".;" + path); | 
|  | } | 
|  |  | 
|  | bool GetCwd(Path* result) { | 
|  | static constexpr size_t kSmallBuf = MAX_PATH; | 
|  | WCHAR value[kSmallBuf]; | 
|  | DWORD size = GetCurrentDirectoryW(kSmallBuf, value); | 
|  | DWORD err = GetLastError(); | 
|  | if (size > 0 && size < kSmallBuf) { | 
|  | return result->Set(value); | 
|  | } else if (size >= kSmallBuf) { | 
|  | std::unique_ptr<WCHAR[]> value_big(new WCHAR[size]); | 
|  | GetCurrentDirectoryW(size, value_big.get()); | 
|  | return result->Set(value_big.get()); | 
|  | } else { | 
|  | LogErrorWithValue(__LINE__, "Failed to get current directory", err); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ChdirToRunfiles(const Path& abs_exec_root, const Path& abs_test_srcdir) { | 
|  | Path dir = abs_test_srcdir; | 
|  | std::wstring preserve_cwd; | 
|  | if (!GetEnv(L"RUNTEST_PRESERVE_CWD", &preserve_cwd)) { | 
|  | return false; | 
|  | } | 
|  | if (preserve_cwd.empty()) { | 
|  | std::wstring workspace; | 
|  | if (!GetEnv(L"TEST_WORKSPACE", &workspace)) { | 
|  | return false; | 
|  | } | 
|  | if (!workspace.empty()) { | 
|  | Path joined; | 
|  | if (!joined.Set(dir.Get() + L"\\" + workspace)) { | 
|  | LogErrorWithArg2(__LINE__, "Could not join paths", dir.Get(), | 
|  | workspace); | 
|  | return false; | 
|  | } | 
|  | dir = joined; | 
|  | } | 
|  | } else { | 
|  | dir = abs_exec_root; | 
|  | } | 
|  | dir.Absolutize(abs_exec_root); | 
|  |  | 
|  | // Non-sandboxed commands run in the exec_root, where they have access to the | 
|  | // entire source tree. By chdir'ing to the runfiles root, tests only have | 
|  | // direct access to their runfiles tree (if it exists), i.e. to their declared | 
|  | // dependencies. | 
|  | std::wstring coverage_dir; | 
|  | if (!GetEnv(L"COVERAGE_DIR", &coverage_dir) || coverage_dir.empty()) { | 
|  | if (!SetCurrentDirectoryW(dir.Get().c_str())) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithArgAndValue(__LINE__, "Could not chdir", dir.Get(), err); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Set USER as required by the Bazel Test Encyclopedia. | 
|  | bool ExportUserName() { | 
|  | std::wstring value; | 
|  | if (!GetEnv(L"USER", &value)) { | 
|  | return false; | 
|  | } | 
|  | if (!value.empty()) { | 
|  | // Respect the value passed by Bazel via --test_env. | 
|  | return true; | 
|  | } | 
|  | WCHAR buffer[UNLEN + 1]; | 
|  | DWORD len = UNLEN + 1; | 
|  | if (GetUserNameW(buffer, &len) == 0) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithValue(__LINE__, "Failed to query user name", err); | 
|  | return false; | 
|  | } | 
|  | return SetEnv(L"USER", buffer); | 
|  | } | 
|  |  | 
|  | // Gets a path envvar, and re-exports it as an absolute path. | 
|  | // Returns: | 
|  | // - true, if the envvar was defined, and was already absolute or was | 
|  | //   successfully absolutized and re-exported | 
|  | // - false, if the envvar was undefined or empty, or it could not be absolutized | 
|  | //   or re-exported | 
|  | bool ExportAbsolutePathEnv(const wchar_t* envvar, const Path& cwd, | 
|  | Path* result) { | 
|  | if (!GetPathEnv(envvar, result)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to get envvar", envvar); | 
|  | return false; | 
|  | } | 
|  | if (result->Get().empty()) { | 
|  | LogErrorWithArg(__LINE__, "Envvar was empty", envvar); | 
|  | return false; | 
|  | } | 
|  | if (result->Absolutize(cwd) && !SetPathEnv(envvar, *result)) { | 
|  | LogErrorWithArg2(__LINE__, "Failed to set absolutized envvar", envvar, | 
|  | result->Get()); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Set TEST_SRCDIR as required by the Bazel Test Encyclopedia. | 
|  | bool ExportSrcPath(const Path& cwd, Path* result) { | 
|  | if (!ExportAbsolutePathEnv(L"TEST_SRCDIR", cwd, result)) { | 
|  | LogError(__LINE__, "Failed to export TEST_SRCDIR"); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Set TEST_TMPDIR as required by the Bazel Test Encyclopedia. | 
|  | bool ExportTmpPath(const Path& cwd, Path* result) { | 
|  | if (!ExportAbsolutePathEnv(L"TEST_TMPDIR", cwd, result)) { | 
|  | LogError(__LINE__, "Failed to export TEST_TMPDIR"); | 
|  | return false; | 
|  | } | 
|  | // Create the test temp directory, which may not exist on the remote host when | 
|  | // doing a remote build. | 
|  | return CreateDirectories(*result); | 
|  | } | 
|  |  | 
|  | // Set HOME as required by the Bazel Test Encyclopedia. | 
|  | bool ExportHome(const Path& test_tmpdir) { | 
|  | Path home; | 
|  | if (!GetPathEnv(L"HOME", &home)) { | 
|  | return false; | 
|  | } | 
|  | if (blaze_util::IsAbsolute(home.Get())) { | 
|  | // Respect the user-defined HOME in case they set passed it with | 
|  | // --test_env=HOME or --test_env=HOME=C:\\foo | 
|  | return true; | 
|  | } else { | 
|  | // Set TEST_TMPDIR as required by the Bazel Test Encyclopedia. | 
|  | return SetPathEnv(L"HOME", test_tmpdir); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ExportRunfiles(const Path& cwd, const Path& test_srcdir) { | 
|  | Path runfiles_dir; | 
|  | if (!GetPathEnv(L"RUNFILES_DIR", &runfiles_dir) || | 
|  | (runfiles_dir.Absolutize(cwd) && | 
|  | !SetPathEnv(L"RUNFILES_DIR", runfiles_dir))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // TODO(ulfjack): Standardize on RUNFILES_DIR and remove the | 
|  | // {JAVA,PYTHON}_RUNFILES vars. | 
|  | Path java_rf, py_rf; | 
|  | if (!GetPathEnv(L"JAVA_RUNFILES", &java_rf) || | 
|  | (java_rf.Absolutize(cwd) && !SetPathEnv(L"JAVA_RUNFILES", java_rf)) || | 
|  | !GetPathEnv(L"PYTHON_RUNFILES", &py_rf) || | 
|  | (py_rf.Absolutize(cwd) && !SetPathEnv(L"PYTHON_RUNFILES", py_rf))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::wstring mf_only_str; | 
|  | int mf_only_value = 0; | 
|  | if (!GetIntEnv(L"RUNFILES_MANIFEST_ONLY", &mf_only_str, &mf_only_value)) { | 
|  | return false; | 
|  | } | 
|  | if (mf_only_value == 1) { | 
|  | // If RUNFILES_MANIFEST_ONLY is set to 1 then test programs should use the | 
|  | // manifest file to find their runfiles. | 
|  | Path runfiles_mf; | 
|  | if (!runfiles_mf.Set(test_srcdir.Get() + L"\\MANIFEST") || | 
|  | (IsReadableFile(runfiles_mf) && | 
|  | !SetPathEnv(L"RUNFILES_MANIFEST_FILE", runfiles_mf))) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ExportShardStatusFile(const Path& cwd) { | 
|  | Path status_file; | 
|  | if (!GetPathEnv(L"TEST_SHARD_STATUS_FILE", &status_file) || | 
|  | (!status_file.Get().empty() && status_file.Absolutize(cwd) && | 
|  | !SetPathEnv(L"TEST_SHARD_STATUS_FILE", status_file))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return status_file.Get().empty() || | 
|  | // The test shard status file is only set for sharded tests. | 
|  | CreateDirectories(status_file.Dirname()); | 
|  | } | 
|  |  | 
|  | bool ExportGtestVariables(const Path& test_tmpdir) { | 
|  | // # Tell googletest about Bazel sharding. | 
|  | std::wstring total_shards_str; | 
|  | int total_shards_value = 0; | 
|  | if (!GetIntEnv(L"TEST_TOTAL_SHARDS", &total_shards_str, | 
|  | &total_shards_value)) { | 
|  | return false; | 
|  | } | 
|  | if (total_shards_value > 0) { | 
|  | std::wstring shard_index; | 
|  | if (!GetEnv(L"TEST_SHARD_INDEX", &shard_index) || | 
|  | !SetEnv(L"GTEST_SHARD_INDEX", shard_index) || | 
|  | !SetEnv(L"GTEST_TOTAL_SHARDS", total_shards_str)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return SetPathEnv(L"GTEST_TMP_DIR", test_tmpdir); | 
|  | } | 
|  |  | 
|  | bool ExportMiscEnvvars(const Path& cwd) { | 
|  | for (const wchar_t* name : | 
|  | {L"TEST_INFRASTRUCTURE_FAILURE_FILE", L"TEST_LOGSPLITTER_OUTPUT_FILE", | 
|  | L"TEST_PREMATURE_EXIT_FILE", L"TEST_UNUSED_RUNFILES_LOG_FILE", | 
|  | L"TEST_WARNINGS_OUTPUT_FILE"}) { | 
|  | Path value; | 
|  | if (!GetPathEnv(name, &value) || | 
|  | (value.Absolutize(cwd) && !SetPathEnv(name, value))) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool _GetFileListRelativeTo(const std::wstring& unc_root, | 
|  | const std::wstring& subdir, int depth_limit, | 
|  | std::vector<FileInfo>* result) { | 
|  | const std::wstring full_subdir = | 
|  | unc_root + (subdir.empty() ? L"" : (L"\\" + subdir)) + L"\\*"; | 
|  | WIN32_FIND_DATAW info; | 
|  | HANDLE handle = FindFirstFileW(full_subdir.c_str(), &info); | 
|  | if (handle == INVALID_HANDLE_VALUE) { | 
|  | DWORD err = GetLastError(); | 
|  | if (err == ERROR_FILE_NOT_FOUND) { | 
|  | // No files found, nothing to do. | 
|  | return true; | 
|  | } | 
|  | LogErrorWithArgAndValue(__LINE__, "Failed to list directory contents", | 
|  | full_subdir, err); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | Defer close_handle([handle]() { FindClose(handle); }); | 
|  | static const std::wstring kDot(1, L'.'); | 
|  | static const std::wstring kDotDot(2, L'.'); | 
|  | std::vector<std::wstring> subdirectories; | 
|  | while (true) { | 
|  | if (kDot != info.cFileName && kDotDot != info.cFileName) { | 
|  | std::wstring rel_path = | 
|  | subdir.empty() ? info.cFileName : (subdir + L"\\" + info.cFileName); | 
|  | if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | 
|  | if (depth_limit != 0) { | 
|  | // depth_limit is negative ==> unlimited depth | 
|  | // depth_limit is zero     ==> do not recurse further | 
|  | // depth_limit is positive ==> recurse further | 
|  | subdirectories.push_back(rel_path); | 
|  | } | 
|  | result->push_back(FileInfo(rel_path)); | 
|  | } else { | 
|  | if (info.nFileSizeHigh > 0 || info.nFileSizeLow > INT_MAX) { | 
|  | // devtools_ijar::Stat::total_size is declared as `int`, so the file | 
|  | // size limit is INT_MAX. Additionally we limit the files to be below | 
|  | // 4 GiB, not only because int is typically 4 bytes long, but also | 
|  | // because such huge files are unreasonably large as an undeclared | 
|  | // output. | 
|  | LogErrorWithArgAndValue(__LINE__, "File is too large to archive", | 
|  | rel_path, 0); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | result->push_back(FileInfo(rel_path, | 
|  | // File size is already validated to be | 
|  | // smaller than min(INT_MAX, 4 GiB) | 
|  | static_cast<int>(info.nFileSizeLow))); | 
|  | } | 
|  | } | 
|  | if (FindNextFileW(handle, &info) == 0) { | 
|  | DWORD err = GetLastError(); | 
|  | if (err == ERROR_NO_MORE_FILES) { | 
|  | break; | 
|  | } | 
|  | LogErrorWithArgAndValue(__LINE__, | 
|  | "Failed to get next element in directory", | 
|  | unc_root + L"\\" + subdir, err); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | close_handle.DoNow(); | 
|  |  | 
|  | if (depth_limit != 0) { | 
|  | // depth_limit is negative ==> unlimited depth | 
|  | // depth_limit is zero     ==> do not recurse further | 
|  | // depth_limit is positive ==> recurse further | 
|  | for (const auto& s : subdirectories) { | 
|  | if (!_GetFileListRelativeTo( | 
|  | unc_root, s, depth_limit > 0 ? depth_limit - 1 : depth_limit, | 
|  | result)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool GetFileListRelativeTo(const Path& root, std::vector<FileInfo>* result, | 
|  | int depth_limit = -1) { | 
|  | if (!blaze_util::IsAbsolute(root.Get())) { | 
|  | LogError(__LINE__, "Root should be absolute"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return _GetFileListRelativeTo(AddUncPrefixMaybe(root), std::wstring(), | 
|  | depth_limit, result); | 
|  | } | 
|  |  | 
|  | bool ToZipEntryPaths(const Path& root, const std::vector<FileInfo>& files, | 
|  | ZipEntryPaths* result) { | 
|  | std::string acp_root; | 
|  | if (!WcsToAcp(AsMixedPath(RemoveUncPrefixMaybe(root)), &acp_root)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to convert path", root.Get()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Convert all UTF-16 paths to ANSI paths. | 
|  | std::vector<std::string> acp_file_list; | 
|  | acp_file_list.reserve(files.size()); | 
|  | for (const auto& e : files) { | 
|  | std::string acp_path; | 
|  | if (!WcsToAcp(AsMixedPath(e.RelativePath()), &acp_path)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to convert path", e.RelativePath()); | 
|  | return false; | 
|  | } | 
|  | if (e.IsDirectory()) { | 
|  | acp_path += "/"; | 
|  | } | 
|  | acp_file_list.push_back(acp_path); | 
|  | } | 
|  |  | 
|  | result->Create(acp_root, acp_file_list); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CreateZipBuilder(const Path& zip, const ZipEntryPaths& entry_paths, | 
|  | std::unique_ptr<devtools_ijar::ZipBuilder>* result) { | 
|  | const devtools_ijar::u8 estimated_size = | 
|  | devtools_ijar::ZipBuilder::EstimateSize(entry_paths.AbsPathPtrs(), | 
|  | entry_paths.EntryPathPtrs(), | 
|  | entry_paths.Size()); | 
|  |  | 
|  | if (estimated_size == 0) { | 
|  | LogError(__LINE__, "Failed to estimate zip size"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::string acp_zip; | 
|  | if (!WcsToAcp(zip.Get(), &acp_zip)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to convert path", zip.Get()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | result->reset( | 
|  | devtools_ijar::ZipBuilder::Create(acp_zip.c_str(), estimated_size)); | 
|  | if (result->get() == nullptr) { | 
|  | LogErrorWithValue(__LINE__, "Failed to create zip builder", errno); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool OpenFileForWriting(const Path& path, bazel::windows::AutoHandle* result) { | 
|  | HANDLE h = CreateFileW(AddUncPrefixMaybe(path).c_str(), GENERIC_WRITE, | 
|  | FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, | 
|  | CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); | 
|  | if (h == INVALID_HANDLE_VALUE) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithArgAndValue(__LINE__, "Failed to open file", path.Get(), err); | 
|  | return false; | 
|  | } | 
|  | *result = h; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool OpenExistingFileForRead(const Path& abs_path, | 
|  | bazel::windows::AutoHandle* result) { | 
|  | HANDLE h = | 
|  | CreateFileW(AddUncPrefixMaybe(abs_path).c_str(), GENERIC_READ, | 
|  | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | 
|  | nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); | 
|  | if (h == INVALID_HANDLE_VALUE) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithArgAndValue(__LINE__, "Failed to open file", abs_path.Get(), | 
|  | err); | 
|  | return false; | 
|  | } | 
|  | *result = h; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CreateEmptyFile(const Path& path) { | 
|  | bazel::windows::AutoHandle handle; | 
|  | return OpenFileForWriting(path, &handle); | 
|  | } | 
|  |  | 
|  | bool ReadFromFile(HANDLE handle, uint8_t* dest, DWORD max_read) { | 
|  | if (max_read == 0) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | DWORD total_read = 0; | 
|  | DWORD read = 0; | 
|  | do { | 
|  | if (!ReadFile(handle, dest + total_read, max_read - total_read, &read, | 
|  | nullptr)) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithValue(__LINE__, "Failed to read file", err); | 
|  | return false; | 
|  | } | 
|  | total_read += read; | 
|  | } while (read > 0 && total_read < max_read); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool WriteToFile(HANDLE output, const void* buffer, const size_t size) { | 
|  | // Write `size` many bytes to the output file. | 
|  | DWORD total_written = 0; | 
|  | while (total_written < size) { | 
|  | DWORD written; | 
|  | if (!WriteFile(output, static_cast<const uint8_t*>(buffer) + total_written, | 
|  | size - total_written, &written, nullptr)) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithValue(__LINE__, "Failed to write file", err); | 
|  | return false; | 
|  | } | 
|  | total_written += written; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool AppendFileTo(const Path& file, const size_t total_size, HANDLE output) { | 
|  | bazel::windows::AutoHandle input; | 
|  | if (!OpenExistingFileForRead(file, &input)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to open file for reading", file.Get()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const size_t buf_size = std::min<size_t>(total_size, /* 10 MB */ 10000000); | 
|  | std::unique_ptr<uint8_t[]> buffer(new uint8_t[buf_size]); | 
|  |  | 
|  | while (true) { | 
|  | // Read at most `buf_size` many bytes from the input file. | 
|  | DWORD read = 0; | 
|  | if (!ReadFile(input, buffer.get(), buf_size, &read, nullptr)) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithArgAndValue(__LINE__, "Failed to read file", file.Get(), err); | 
|  | return false; | 
|  | } | 
|  | if (read == 0) { | 
|  | // Reached end of input file. | 
|  | return true; | 
|  | } | 
|  | if (!WriteToFile(output, buffer.get(), read)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to write contents from file", | 
|  | file.Get()); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Returns the MIME type of the file name. | 
|  | // If the MIME type is unknown or an error occurs, the method returns | 
|  | // "application/octet-stream". | 
|  | std::string GetMimeType(const std::string& filename) { | 
|  | static constexpr char* kDefaultMimeType = "application/octet-stream"; | 
|  | std::string::size_type pos = filename.find_last_of('.'); | 
|  | if (pos == std::string::npos) { | 
|  | return kDefaultMimeType; | 
|  | } | 
|  | char data[1000]; | 
|  | DWORD data_size = 1000 * sizeof(char); | 
|  | if (RegGetValueA(HKEY_CLASSES_ROOT, filename.c_str() + pos, "Content Type", | 
|  | RRF_RT_REG_SZ, nullptr, data, &data_size) == ERROR_SUCCESS) { | 
|  | return data; | 
|  | } | 
|  | // The file extension is unknown, or it does not have a "Content Type" value, | 
|  | // or the value is too long. We don't care; just return the default. | 
|  | return kDefaultMimeType; | 
|  | } | 
|  |  | 
|  | bool CreateUndeclaredOutputsManifestContent(const std::vector<FileInfo>& files, | 
|  | std::string* result) { | 
|  | std::stringstream stm; | 
|  | for (const auto& e : files) { | 
|  | if (!e.IsDirectory()) { | 
|  | // For each file, write a tab-separated line to the manifest with name | 
|  | // (relative to TEST_UNDECLARED_OUTPUTS_DIR), size, and mime type. | 
|  | // Example: | 
|  | //   foo.txt<TAB>9<TAB>text/plain | 
|  | //   bar/baz<TAB>2944<TAB>application/octet-stream | 
|  | std::string acp_path; | 
|  | if (!WcsToAcp(AsMixedPath(e.RelativePath()), &acp_path)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | stm << acp_path << "\t" << e.Size() << "\t" << GetMimeType(acp_path) | 
|  | << "\n"; | 
|  | } | 
|  | } | 
|  | *result = stm.str(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CreateUndeclaredOutputsManifest(const std::vector<FileInfo>& files, | 
|  | const Path& output) { | 
|  | std::string content; | 
|  | if (!CreateUndeclaredOutputsManifestContent(files, &content)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to create manifest content for file", | 
|  | output.Get()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bazel::windows::AutoHandle handle; | 
|  | if (!OpenFileForWriting(output, &handle)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to open file for writing", output.Get()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!WriteToFile(handle, content.c_str(), content.size())) { | 
|  | LogErrorWithArg(__LINE__, "Failed to write file", output.Get()); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ExportXmlPath(const Path& cwd, Path* test_outerr, Path* xml_log) { | 
|  | if (!GetPathEnv(L"XML_OUTPUT_FILE", xml_log)) { | 
|  | LogError(__LINE__); | 
|  | return false; | 
|  | } | 
|  | xml_log->Absolutize(cwd); | 
|  | if (!test_outerr->Set(xml_log->Get() + L".log")) { | 
|  | LogError(__LINE__); | 
|  | return false; | 
|  | } | 
|  | std::wstring unix_result = AsMixedPath(xml_log->Get()); | 
|  | return SetEnv(L"XML_OUTPUT_FILE", unix_result) && | 
|  | // TODO(ulfjack): Update Gunit to accept XML_OUTPUT_FILE and drop the | 
|  | // GUNIT_OUTPUT env variable. | 
|  | SetEnv(L"GUNIT_OUTPUT", L"xml:" + unix_result) && | 
|  | CreateDirectories(xml_log->Dirname()) && CreateEmptyFile(*test_outerr); | 
|  | } | 
|  |  | 
|  | devtools_ijar::u4 GetZipAttr(const FileInfo& info) { | 
|  | // We use these hard-coded Unix permission masks because they are: | 
|  | // - stable, so the zip file is deterministic | 
|  | // - useful, because stat_to_zipattr expects a mode_t | 
|  | static constexpr mode_t kDirectoryMode = 040750;  // drwxr-x--- (directory) | 
|  | static constexpr mode_t kFileMode = 0100640;      // -rw-r----- (regular file) | 
|  |  | 
|  | devtools_ijar::Stat file_stat; | 
|  | file_stat.total_size = info.Size(); | 
|  | file_stat.is_directory = info.IsDirectory(); | 
|  | file_stat.file_mode = info.IsDirectory() ? kDirectoryMode : kFileMode; | 
|  | return devtools_ijar::stat_to_zipattr(file_stat); | 
|  | } | 
|  |  | 
|  | bool GetZipEntryPtr(devtools_ijar::ZipBuilder* zip_builder, | 
|  | const char* entry_name, const devtools_ijar::u4 attr, | 
|  | devtools_ijar::u1** result) { | 
|  | *result = zip_builder->NewFile(entry_name, attr); | 
|  | if (*result == nullptr) { | 
|  | LogErrorWithArg2(__LINE__, "Failed to add new zip entry for file", | 
|  | entry_name, zip_builder->GetError()); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CreateZip(const Path& root, const std::vector<FileInfo>& files, | 
|  | const Path& abs_zip) { | 
|  | bool restore_oem_api = false; | 
|  | if (!AreFileApisANSI()) { | 
|  | // devtools_ijar::ZipBuilder uses the ANSI file APIs so we must set the | 
|  | // active code page to ANSI. | 
|  | SetFileApisToANSI(); | 
|  | restore_oem_api = true; | 
|  | } | 
|  | Defer restore_file_apis([restore_oem_api]() { | 
|  | if (restore_oem_api) { | 
|  | SetFileApisToOEM(); | 
|  | } | 
|  | }); | 
|  |  | 
|  | ZipEntryPaths zip_entry_paths; | 
|  | if (!ToZipEntryPaths(root, files, &zip_entry_paths)) { | 
|  | LogError(__LINE__, "Failed to create zip entry paths"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<devtools_ijar::ZipBuilder> zip_builder; | 
|  | if (!CreateZipBuilder(abs_zip, zip_entry_paths, &zip_builder)) { | 
|  | LogError(__LINE__, "Failed to create zip builder"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (size_t i = 0; i < files.size(); ++i) { | 
|  | bazel::windows::AutoHandle handle; | 
|  | Path path; | 
|  | if (!path.Set(root.Get() + L"\\" + files[i].RelativePath()) || | 
|  | (!files[i].IsDirectory() && !OpenExistingFileForRead(path, &handle))) { | 
|  | LogErrorWithArg(__LINE__, "Failed to open file for reading", path.Get()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | devtools_ijar::u1* dest; | 
|  | if (!GetZipEntryPtr(zip_builder.get(), zip_entry_paths.EntryPathPtrs()[i], | 
|  | GetZipAttr(files[i]), &dest) || | 
|  | (!files[i].IsDirectory() && | 
|  | !ReadFromFile(handle, dest, files[i].Size()))) { | 
|  | LogErrorWithArg(__LINE__, "Failed to dump file into zip", path.Get()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (zip_builder->FinishFile(files[i].Size(), /* compress */ false, | 
|  | /* compute_crc */ true) == -1) { | 
|  | LogErrorWithArg(__LINE__, "Failed to finish writing file to zip", | 
|  | path.Get()); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (zip_builder->Finish() == -1) { | 
|  | LogErrorWithArg(__LINE__, "Failed to add file to zip", | 
|  | zip_builder->GetError()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool GetAndUnexportUndeclaredOutputsEnvvars(const Path& cwd, | 
|  | UndeclaredOutputs* result) { | 
|  | // The test may only see TEST_UNDECLARED_OUTPUTS_DIR and | 
|  | // TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR, so keep those but unexport others. | 
|  | if (!GetPathEnv(L"TEST_UNDECLARED_OUTPUTS_ZIP", &(result->zip)) || | 
|  | !UnsetEnv(L"TEST_UNDECLARED_OUTPUTS_ZIP") || | 
|  |  | 
|  | !GetPathEnv(L"TEST_UNDECLARED_OUTPUTS_MANIFEST", &(result->manifest)) || | 
|  | !UnsetEnv(L"TEST_UNDECLARED_OUTPUTS_MANIFEST") || | 
|  |  | 
|  | !GetPathEnv(L"TEST_UNDECLARED_OUTPUTS_ANNOTATIONS", | 
|  | &(result->annotations)) || | 
|  | !UnsetEnv(L"TEST_UNDECLARED_OUTPUTS_ANNOTATIONS") || | 
|  |  | 
|  | !GetPathEnv(L"TEST_UNDECLARED_OUTPUTS_DIR", &(result->root)) || | 
|  |  | 
|  | !GetPathEnv(L"TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR", | 
|  | &(result->annotations_dir))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | result->root.Absolutize(cwd); | 
|  | result->annotations_dir.Absolutize(cwd); | 
|  | result->zip.Absolutize(cwd); | 
|  | result->manifest.Absolutize(cwd); | 
|  | result->annotations.Absolutize(cwd); | 
|  |  | 
|  | return SetPathEnv(L"TEST_UNDECLARED_OUTPUTS_DIR", result->root) && | 
|  | SetPathEnv(L"TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR", | 
|  | result->annotations_dir) && | 
|  | CreateDirectories(result->root) && | 
|  | CreateDirectories(result->annotations_dir); | 
|  | } | 
|  |  | 
|  | bool PrintTestLogStartMarker() { | 
|  | std::wstring test_target; | 
|  | std::string acp_test_target; | 
|  | if (!GetEnv(L"TEST_TARGET", &test_target) || | 
|  | !WcsToAcp(test_target, &acp_test_target)) { | 
|  | return false; | 
|  | } | 
|  | std::stringstream ss; | 
|  | if (test_target.empty()) { | 
|  | // According to the Bazel Test Encyclopedia, setting TEST_TARGET is | 
|  | // optional. | 
|  | ss << "Executing tests from unknown target\n"; | 
|  | } else { | 
|  | ss << "Executing tests from " << acp_test_target << "\n"; | 
|  | } | 
|  |  | 
|  | // This header marks where --test_output=streamed will start being printed. | 
|  | ss << "---------------------------------------------------------------------" | 
|  | "--------\n"; | 
|  | WriteStdout(ss.str()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | inline bool GetWorkspaceName(std::wstring* result) { | 
|  | return GetEnv(L"TEST_WORKSPACE", result) && !result->empty(); | 
|  | } | 
|  |  | 
|  | inline void ComputeRunfilePath(const std::wstring& test_workspace, | 
|  | std::wstring* s) { | 
|  | if (s->size() >= 2 && (*s)[0] == L'.' && (*s)[1] == L'/') { | 
|  | s->erase(0, 2); | 
|  | } | 
|  | // Runfiles paths of external tests start with "../". | 
|  | if (s->find(L"../") == 0) { | 
|  | s->erase(0, 3); | 
|  | } else { | 
|  | *s = test_workspace + L"/" + *s; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool FindTestBinary(const Path& argv0, const Path& cwd, std::wstring test_path, | 
|  | const Path& abs_test_srcdir, Path* result) { | 
|  | if (!blaze_util::IsAbsolute(test_path)) { | 
|  | std::string argv0_acp; | 
|  | if (!WcsToAcp(argv0.Get(), &argv0_acp)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to convert path", argv0.Get()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::string error; | 
|  | std::unique_ptr<bazel::tools::cpp::runfiles::Runfiles> runfiles( | 
|  | bazel::tools::cpp::runfiles::Runfiles::Create(argv0_acp, &error)); | 
|  | if (runfiles == nullptr) { | 
|  | LogError(__LINE__, "Failed to load runfiles"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::wstring workspace; | 
|  | if (!GetEnv(L"TEST_WORKSPACE", &workspace) || workspace.empty()) { | 
|  | LogError(__LINE__, "Failed to read %TEST_WORKSPACE%"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | ComputeRunfilePath(workspace, &test_path); | 
|  |  | 
|  | Path test_bin_in_runfiles; | 
|  | if (!test_bin_in_runfiles.Set(abs_test_srcdir.Get() + L"\\" + test_path)) { | 
|  | LogErrorWithArg2(__LINE__, "Could not join paths", abs_test_srcdir.Get(), | 
|  | test_path); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::wstring mf_only_str; | 
|  | int mf_only_value = 0; | 
|  | if (!GetIntEnv(L"RUNFILES_MANIFEST_ONLY", &mf_only_str, &mf_only_value)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // If runfiles is enabled on Windows, we use the test binary in the runfiles | 
|  | // tree, which is consistent with the behavior on Linux and macOS. | 
|  | // Otherwise, we use Rlocation function to find the actual test binary | 
|  | // location. | 
|  | if (mf_only_value != 1 && IsReadableFile(test_bin_in_runfiles)) { | 
|  | test_path = test_bin_in_runfiles.Get(); | 
|  | } else { | 
|  | std::string utf8_test_path; | 
|  | uint32_t err; | 
|  | if (!blaze_util::WcsToUtf8(test_path, &utf8_test_path, &err)) { | 
|  | LogErrorWithArgAndValue(__LINE__, "Failed to convert string to UTF-8", | 
|  | test_path, err); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::string rloc = runfiles->Rlocation(utf8_test_path); | 
|  | if (!blaze_util::Utf8ToWcs(rloc, &test_path, &err)) { | 
|  | LogErrorWithArgAndValue(__LINE__, "Failed to convert string", | 
|  | utf8_test_path, err); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!result->Set(test_path)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to set path", test_path); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | (void)result->Absolutize(cwd); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CreateCommandLine(const Path& path, const std::wstring& args, | 
|  | std::unique_ptr<WCHAR[]>* result) { | 
|  | // kMaxCmdline value: see lpCommandLine parameter of CreateProcessW. | 
|  | static constexpr size_t kMaxCmdline = 32767; | 
|  |  | 
|  | if (path.Get().size() + args.size() > kMaxCmdline) { | 
|  | LogErrorWithValue(__LINE__, L"Command is too long", | 
|  | path.Get().size() + args.size()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Add an extra character for the final null-terminator. | 
|  | result->reset(new WCHAR[path.Get().size() + args.size() + 1]); | 
|  |  | 
|  | wcsncpy(result->get(), path.Get().c_str(), path.Get().size()); | 
|  | wcsncpy(result->get() + path.Get().size(), args.c_str(), args.size() + 1); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool StartSubprocess(const Path& path, const std::wstring& args, | 
|  | const Path& outerr, std::unique_ptr<Tee>* tee, | 
|  | LARGE_INTEGER* start_time, | 
|  | bazel::windows::WaitableProcess* process) { | 
|  | SECURITY_ATTRIBUTES inheritable_handle_sa = {sizeof(SECURITY_ATTRIBUTES), | 
|  | nullptr, TRUE}; | 
|  |  | 
|  | // Create a pipe to stream the output of the subprocess to this process. | 
|  | // The subprocess inherits two copies of the writing end (one for stdout, one | 
|  | // for stderr). This process closes its copies of the handles. | 
|  | // This process keeps the reading end and streams data from the pipe to the | 
|  | // test log and to stdout. | 
|  | HANDLE pipe_read_h, pipe_write_h; | 
|  | if (!CreatePipe(&pipe_read_h, &pipe_write_h, &inheritable_handle_sa, 0)) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithValue(__LINE__, "CreatePipe", err); | 
|  | return false; | 
|  | } | 
|  | bazel::windows::AutoHandle pipe_read(pipe_read_h), pipe_write(pipe_write_h); | 
|  |  | 
|  | // Duplicate the write end of the pipe. | 
|  | // The original will be connected to the stdout of the process, the duplicate | 
|  | // to stderr. | 
|  | HANDLE pipe_write_dup_h; | 
|  | if (!DuplicateHandle(GetCurrentProcess(), pipe_write, GetCurrentProcess(), | 
|  | &pipe_write_dup_h, 0, TRUE, DUPLICATE_SAME_ACCESS)) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithValue(__LINE__, "DuplicateHandle", err); | 
|  | return false; | 
|  | } | 
|  | bazel::windows::AutoHandle pipe_write_dup(pipe_write_dup_h); | 
|  |  | 
|  | // Open a readonly handle to NUL. The subprocess inherits this handle that's | 
|  | // connected to its stdin. | 
|  | bazel::windows::AutoHandle devnull_read(CreateFileW( | 
|  | L"NUL", GENERIC_READ, | 
|  | FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, | 
|  | &inheritable_handle_sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); | 
|  | if (devnull_read == INVALID_HANDLE_VALUE) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithValue(__LINE__, "CreateFileW", err); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Open a handle to the test log file. The "tee" thread will write everything | 
|  | // into it that the subprocess writes to the pipe. | 
|  | bazel::windows::AutoHandle test_outerr; | 
|  | if (!OpenFileForWriting(outerr, &test_outerr)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to open file for writing", outerr.Get()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Duplicate stdout's handle, and pass it to the tee thread, who will own it | 
|  | // and close it in the end. | 
|  | HANDLE stdout_dup_h; | 
|  | if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_OUTPUT_HANDLE), | 
|  | GetCurrentProcess(), &stdout_dup_h, 0, FALSE, | 
|  | DUPLICATE_SAME_ACCESS)) { | 
|  | DWORD err = GetLastError(); | 
|  | LogErrorWithValue(__LINE__, "DuplicateHandle", err); | 
|  | return false; | 
|  | } | 
|  | bazel::windows::AutoHandle stdout_dup(stdout_dup_h); | 
|  |  | 
|  | // Create the tee thread, and transfer ownerships of the `pipe_read`, | 
|  | // `test_outerr`, and `stdout_dup` handles. | 
|  | if (!TeeImpl::Create(&pipe_read, &test_outerr, &stdout_dup, tee)) { | 
|  | LogError(__LINE__); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::wstring werror; | 
|  | if (!process->Create(path.Get(), args, nullptr, L"", devnull_read, pipe_write, | 
|  | pipe_write_dup, start_time, &werror)) { | 
|  | LogError(__LINE__, werror); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ArchiveUndeclaredOutputs(const UndeclaredOutputs& undecl) { | 
|  | if (undecl.root.Get().empty()) { | 
|  | // TEST_UNDECLARED_OUTPUTS_DIR was undefined, there's nothing to archive. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::vector<FileInfo> files; | 
|  | return GetFileListRelativeTo(undecl.root, &files) && | 
|  | (files.empty() || | 
|  | (CreateZip(undecl.root, files, undecl.zip) && | 
|  | CreateUndeclaredOutputsManifest(files, undecl.manifest))); | 
|  | } | 
|  |  | 
|  | // Creates the Undeclared Outputs Annotations file. | 
|  | // | 
|  | // This file is a concatenation of every *.part file directly under | 
|  | // `undecl_annot_dir`. The file is written to `output`. | 
|  | bool CreateUndeclaredOutputsAnnotations(const Path& undecl_annot_dir, | 
|  | const Path& output) { | 
|  | if (undecl_annot_dir.Get().empty()) { | 
|  | // The directory's environment variable | 
|  | // (TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR) was probably undefined, nothing | 
|  | // to do. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::vector<FileInfo> files; | 
|  | if (!GetFileListRelativeTo(undecl_annot_dir, &files, 0)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to get directory contents", | 
|  | undecl_annot_dir.Get()); | 
|  | return false; | 
|  | } | 
|  | // There are no *.part files under `undecl_annot_dir`, nothing to do. | 
|  | if (files.empty()) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bazel::windows::AutoHandle handle; | 
|  | if (!OpenFileForWriting(output, &handle)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to open file for writing", output.Get()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (const auto& e : files) { | 
|  | if (!e.IsDirectory() && | 
|  | e.RelativePath().rfind(L".part") == e.RelativePath().size() - 5) { | 
|  | // Only consume "*.part" files. | 
|  | Path path; | 
|  | if (!path.Set(undecl_annot_dir.Get() + L"\\" + e.RelativePath()) || | 
|  | !AppendFileTo(path, e.Size(), handle)) { | 
|  | LogErrorWithArg2(__LINE__, "Failed to append file to another", | 
|  | path.Get(), output.Get()); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ParseArgs(int argc, wchar_t** argv, Path* out_argv0, | 
|  | std::wstring* out_test_path_arg, std::wstring* out_args) { | 
|  | if (!out_argv0->Set(argv[0])) { | 
|  | return false; | 
|  | } | 
|  | argc--; | 
|  | argv++; | 
|  |  | 
|  | if (argc < 1) { | 
|  | LogError(__LINE__, "Usage: $0 <test_path> [test_args...]"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | *out_test_path_arg = argv[0]; | 
|  | std::wstringstream stm; | 
|  | for (int i = 1; i < argc; i++) { | 
|  | stm << L' ' << bazel::windows::WindowsEscapeArg(argv[i]); | 
|  | } | 
|  | *out_args = stm.str(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ParseXmlWriterArgs(int argc, wchar_t** argv, const Path& cwd, | 
|  | Path* out_test_log, Path* out_xml_log, | 
|  | Duration* out_duration, int* out_exit_code) { | 
|  | if (argc < 5) { | 
|  | LogError(__LINE__, | 
|  | "Usage: $0 <test_output_path> <xml_log_path>" | 
|  | " <duration_in_seconds> <exit_code>"); | 
|  | return false; | 
|  | } | 
|  | if (!out_test_log->Set(argv[1]) || out_test_log->Get().empty()) { | 
|  | LogErrorWithArg(__LINE__, "Failed to parse test log path argument", | 
|  | argv[1]); | 
|  | return false; | 
|  | } | 
|  | out_test_log->Absolutize(cwd); | 
|  | if (!out_xml_log->Set(argv[2]) || out_xml_log->Get().empty()) { | 
|  | LogErrorWithArg(__LINE__, "Failed to parse XML log path argument", argv[2]); | 
|  | return false; | 
|  | } | 
|  | out_xml_log->Absolutize(cwd); | 
|  | if (!out_duration->FromString(argv[3])) { | 
|  | LogErrorWithArg(__LINE__, "Failed to parse test duration argument", | 
|  | argv[3]); | 
|  | return false; | 
|  | } | 
|  | if (!ToInt(argv[4], out_exit_code)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to parse exit code argument", argv[4]); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool TeeImpl::Create(bazel::windows::AutoHandle* input, | 
|  | bazel::windows::AutoHandle* output1, | 
|  | bazel::windows::AutoHandle* output2, | 
|  | std::unique_ptr<Tee>* result) { | 
|  | std::unique_ptr<TeeImpl> tee(new TeeImpl(input, output1, output2)); | 
|  | bazel::windows::AutoHandle thread( | 
|  | CreateThread(nullptr, 0, ThreadFunc, tee.get(), 0, nullptr)); | 
|  | if (!thread.IsValid()) { | 
|  | return false; | 
|  | } | 
|  | result->reset(tee.release()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | DWORD WINAPI TeeImpl::ThreadFunc(LPVOID lpParam) { | 
|  | return reinterpret_cast<TeeImpl*>(lpParam)->MainFunc() ? 0 : 1; | 
|  | } | 
|  |  | 
|  | bool TeeImpl::MainFunc() const { | 
|  | static constexpr size_t kBufferSize = 0x10000; | 
|  | DWORD read; | 
|  | uint8_t content[kBufferSize]; | 
|  | while (ReadFile(input_, content, kBufferSize, &read, nullptr)) { | 
|  | DWORD written; | 
|  | if (read > 0 && (!WriteFile(output1_, content, read, &written, nullptr) || | 
|  | !WriteFile(output2_, content, read, &written, nullptr))) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | int RunSubprocess(const Path& test_path, const std::wstring& args, | 
|  | const Path& test_outerr, Duration* test_duration) { | 
|  | std::unique_ptr<Tee> tee; | 
|  | bazel::windows::WaitableProcess process; | 
|  | LARGE_INTEGER start, end, freq; | 
|  | if (!StartSubprocess(test_path, args, test_outerr, &tee, &start, &process)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to start test process", test_path.Get()); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | std::wstring werror; | 
|  | int wait_res = process.WaitFor(-1, &end, &werror); | 
|  | if (wait_res != bazel::windows::WaitableProcess::kWaitSuccess) { | 
|  | LogErrorWithValue(__LINE__, werror, wait_res); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | werror.clear(); | 
|  | int result = process.GetExitCode(&werror); | 
|  | if (!werror.empty()) { | 
|  | LogError(__LINE__, werror); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | QueryPerformanceFrequency(&freq); | 
|  | end.QuadPart -= start.QuadPart; | 
|  | decltype(LARGE_INTEGER::QuadPart) seconds; | 
|  | // Compute the number of seconds the test ran for. | 
|  | seconds = end.QuadPart / freq.QuadPart; | 
|  | // Check the remainder: if it's at least 0.5 seconds, round up. | 
|  | if ((end.QuadPart - seconds * freq.QuadPart) * 2 >= freq.QuadPart) { | 
|  | seconds += 1; | 
|  | } | 
|  | test_duration->seconds = | 
|  | (seconds > Duration::kMax) ? Duration::kMax : seconds; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Replace invalid XML characters and locate invalid CDATA sequences. | 
|  | // | 
|  | // The legal Unicode code points and ranges are U+0009, U+000A, U+000D, | 
|  | // U+0020..U+D7FF, U+E000..U+FFFD, and U+10000..U+10FFFF. | 
|  | // | 
|  | // Assuming the input is UTF-8 encoded, that translates to the following | 
|  | // regexps: | 
|  | //   [\x9\xa\xd\x20-\x7f]                         <--- (9,A,D,20-7F) | 
|  | //   [\xc0-\xdf][\x80-\xbf]                       <--- (0080-07FF) | 
|  | //   [\xe0-\xec][\x80-\xbf][\x80-\xbf]            <--- (0800-CFFF) | 
|  | //   [\xed][\x80-\x9f][\x80-\xbf]                 <--- (D000-D7FF) | 
|  | //   [\xee][\x80-\xbf][\x80-\xbf]                 <--- (E000-EFFF) | 
|  | //   [\xef][\x80-\xbe][\x80-\xbf]                 <--- (F000-FFEF) | 
|  | //   [\xef][\xbf][\x80-\xbd]                      <--- (FFF0-FFFD) | 
|  | //   [\xf0-\xf7][\x80-\xbf][\x80-\xbf][\x80-\xbf] <--- (010000-10FFFF) | 
|  | // | 
|  | // (See https://github.com/bazelbuild/bazel/issues/4691#issuecomment-408089257) | 
|  | // | 
|  | // Every octet-sequence matching one of these regexps will be left alone, all | 
|  | // other octet-sequences will be replaced by '?' characters. | 
|  | bool CdataEscape(IFStream* in, std::basic_ostream<char>* out) { | 
|  | int c0 = in->Get(); | 
|  | uint8_t p[3]; | 
|  | for (; c0 < 256; c0 = in->Get()) { | 
|  | if (c0 == ']' && in->Peek(2, p) == 2 && p[0] == ']' && p[1] == '>') { | 
|  | *out << "]]>]]<![CDATA[>"; | 
|  | if (!out->good()) { | 
|  | return false; | 
|  | } | 
|  | (void)in->Get(); | 
|  | (void)in->Get(); | 
|  | } else if (c0 == 0x9 || c0 == 0xA || c0 == 0xD || | 
|  | (c0 >= 0x20 && c0 <= 0x7F)) { | 
|  | // Matched legal single-octet sequence. | 
|  | *out << (char)c0; | 
|  | if (!out->good()) { | 
|  | return false; | 
|  | } | 
|  | } else if (c0 >= 0xC0 && c0 <= 0xDF && in->Peek(1, p) == 1 && | 
|  | p[0] >= 0x80 && p[0] <= 0xBF) { | 
|  | // Matched legal double-octet sequence. Skip the next octet. | 
|  | *out << (char)c0 << (char)p[0]; | 
|  | if (!out->good()) { | 
|  | return false; | 
|  | } | 
|  | (void)in->Get(); | 
|  | } else if (in->Peek(2, p) == 2 && | 
|  | ((c0 >= 0xE0 && c0 <= 0xEC && p[0] >= 0x80 && p[0] <= 0xBF && | 
|  | p[1] >= 0x80 && p[1] <= 0xBF) || | 
|  | (c0 == 0xED && p[0] >= 0x80 && p[0] <= 0x9F && p[1] >= 0x80 && | 
|  | p[1] <= 0xBF) || | 
|  | (c0 == 0xEE && p[0] >= 0x80 && p[0] <= 0xBF && p[1] >= 0x80 && | 
|  | p[1] <= 0xBF) || | 
|  | (c0 == 0xEF && p[0] >= 0x80 && p[0] <= 0xBE && p[1] >= 0x80 && | 
|  | p[1] <= 0xBF) || | 
|  | (c0 == 0xEF && p[0] == 0xBF && p[1] >= 0x80 && p[1] <= 0xBD))) { | 
|  | // Matched legal triple-octet sequence. Skip the next two octets. | 
|  | *out << (char)c0 << (char)p[0] << (char)p[1]; | 
|  | if (!out->good()) { | 
|  | return false; | 
|  | } | 
|  | (void)in->Get(); | 
|  | (void)in->Get(); | 
|  | } else if (in->Peek(3, p) == 3 && c0 >= 0xF0 && c0 <= 0xF7 && | 
|  | p[0] >= 0x80 && p[0] <= 0xBF && p[1] >= 0x80 && p[1] <= 0xBF && | 
|  | p[2] >= 0x80 && p[2] <= 0xBF) { | 
|  | // Matched legal quadruple-octet sequence. Skip the next three octets. | 
|  | *out << (char)c0 << (char)p[0] << (char)p[1] << (char)p[2]; | 
|  | if (!out->good()) { | 
|  | return false; | 
|  | } | 
|  | (void)in->Get(); | 
|  | (void)in->Get(); | 
|  | (void)in->Get(); | 
|  | } else { | 
|  | // Illegal octet; replace. | 
|  | *out << (char)'?'; | 
|  | if (!out->good()) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | return c0 == IFStream::kIFStreamErrorEOF; | 
|  | } | 
|  |  | 
|  | bool GetTestName(std::wstring* result) { | 
|  | if (!GetEnv(L"TEST_BINARY", result) || result->empty()) { | 
|  | LogError(__LINE__, L"Failed to get test name"); | 
|  | return false; | 
|  | } | 
|  | if (result->size() >= 2 && (*result)[0] == '.' && (*result)[1] == '/') { | 
|  | result->erase(0, 2); | 
|  | } else if (result->size() >= 3 && (*result)[0] == '.' && | 
|  | (*result)[1] == '.' && (*result)[2] == '/') { | 
|  | result->erase(0, 3); | 
|  | } | 
|  |  | 
|  | // Ensure that test shards have unique names in the xml output, by including | 
|  | // the shard index in the test name. | 
|  | std::wstring total_shards_str; | 
|  | int total_shards = 0, shard_index = 0; | 
|  | if (!GetIntEnv(L"TEST_TOTAL_SHARDS", &total_shards_str, &total_shards)) { | 
|  | LogError(__LINE__); | 
|  | return false; | 
|  | } | 
|  | if (total_shards > 0) { | 
|  | std::wstring shard_index_str; | 
|  | if (!GetIntEnv(L"TEST_SHARD_INDEX", &shard_index_str, &shard_index) || | 
|  | shard_index_str.empty()) { | 
|  | LogError(__LINE__); | 
|  | return false; | 
|  | } | 
|  | std::wstringstream stm; | 
|  | stm << *result << L"_shard_" << (shard_index + 1) << L"/" | 
|  | << total_shards_str; | 
|  | *result = stm.str(); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::string CreateErrorTag(int exit_code) { | 
|  | if (exit_code != 0) { | 
|  | std::stringstream ss; | 
|  | ss << "<error message=\"exited with error code " << exit_code | 
|  | << "\"></error>"; | 
|  | return ss.str(); | 
|  | } else { | 
|  | return std::string(); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ShouldCreateXml(const Path& xml_log, const MainType main_type, | 
|  | bool* result) { | 
|  | *result = true; | 
|  |  | 
|  | // If running from the xml generator binary, we should always create the xml | 
|  | // file. | 
|  | if (main_type == MainType::kXmlWriterMain) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | DWORD attr = GetFileAttributesW(AddUncPrefixMaybe(xml_log).c_str()); | 
|  | if (attr != INVALID_FILE_ATTRIBUTES) { | 
|  | // The XML file already exists, maybe the test framework wrote it. | 
|  | // Leave the file alone. | 
|  | *result = false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::wstring split_xml_generation; | 
|  | if (!GetEnv(L"EXPERIMENTAL_SPLIT_XML_GENERATION", &split_xml_generation)) { | 
|  | LogError(__LINE__, "Failed to get %EXPERIMENTAL_SPLIT_XML_GENERATION%"); | 
|  | return false; | 
|  | } | 
|  | if (split_xml_generation == L"1") { | 
|  | // Bazel generates the test xml as a separate action, so we don't have to | 
|  | // create it. | 
|  | *result = false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CreateXmlLog(const Path& output, const Path& test_outerr, | 
|  | const Duration duration, const int exit_code, | 
|  | const DeleteAfterwards delete_afterwards, | 
|  | const MainType main_type) { | 
|  | bool should_create_xml; | 
|  | if (!ShouldCreateXml(output, main_type, &should_create_xml)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to decide if XML log is needed", | 
|  | output.Get()); | 
|  | return false; | 
|  | } | 
|  | if (!should_create_xml) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | Defer delete_test_outerr([test_outerr, delete_afterwards]() { | 
|  | // Delete the test's outerr file after we have the XML file. | 
|  | // We don't care if this succeeds or not, because the outerr file is not a | 
|  | // declared output. | 
|  | if (delete_afterwards == DeleteAfterwards::kEnabled) { | 
|  | DeleteFileW(test_outerr.Get().c_str()); | 
|  | } | 
|  | }); | 
|  |  | 
|  | std::wstring test_name; | 
|  | int errors = (exit_code == 0) ? 0 : 1; | 
|  | std::string error_msg = CreateErrorTag(exit_code); | 
|  | if (!GetTestName(&test_name)) { | 
|  | LogError(__LINE__); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::string acp_test_name; | 
|  | if (!WcsToAcp(test_name, &acp_test_name)) { | 
|  | LogError(__LINE__, test_name.c_str()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bazel::windows::AutoHandle test_log; | 
|  | if (!OpenExistingFileForRead(test_outerr, &test_log)) { | 
|  | LogError(__LINE__, test_outerr.Get().c_str()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<IFStream> istm(IFStreamImpl::Create(test_log)); | 
|  | if (istm == nullptr) { | 
|  | LogError(__LINE__, test_outerr.Get().c_str()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::ofstream ostm( | 
|  | AddUncPrefixMaybe(output).c_str(), | 
|  | std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); | 
|  | if (!ostm.is_open() || !ostm.good()) { | 
|  | LogError(__LINE__, output.Get().c_str()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Create XML file stub. | 
|  | ostm << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | 
|  | "<testsuites>\n" | 
|  | "<testsuite name=\"" | 
|  | << acp_test_name << "\" tests=\"1\" failures=\"0\" errors=\"" << errors | 
|  | << "\">\n" | 
|  | "<testcase name=\"" | 
|  | << acp_test_name << "\" status=\"run\" duration=\"" << duration.seconds | 
|  | << "\" time=\"" << duration.seconds << "\">" << error_msg | 
|  | << "</testcase>\n" | 
|  | "<system-out><![CDATA["; | 
|  | if (!ostm.good()) { | 
|  | LogError(__LINE__, output.Get().c_str()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Encode test log to make it embeddable in CDATA. | 
|  | if (!CdataEscape(istm.get(), &ostm)) { | 
|  | LogError(__LINE__, output.Get().c_str()); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Append CDATA end and closing tags. | 
|  | ostm << "]]></system-out>\n</testsuite>\n</testsuites>\n"; | 
|  | if (!ostm.good()) { | 
|  | LogError(__LINE__, output.Get().c_str()); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Duration::FromString(const wchar_t* str) { | 
|  | int result; | 
|  | if (!ToInt(str, &result)) { | 
|  | LogErrorWithArg(__LINE__, "Failed to parse int from string", str); | 
|  | return false; | 
|  | } | 
|  | this->seconds = result; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Path::Set(const std::wstring& path) { | 
|  | std::wstring result; | 
|  | std::string error; | 
|  | if (!blaze_util::AsWindowsPath(path, &result, &error)) { | 
|  | LogError(__LINE__, error); | 
|  | return false; | 
|  | } | 
|  | path_ = result; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Path::Absolutize(const Path& cwd) { | 
|  | if (!path_.empty() && !blaze_util::IsAbsolute(path_)) { | 
|  | // Both paths are normalized, but this->path_ may begin with ".."s so we | 
|  | // must normalize after joining. | 
|  | // We wouldn't need full normalization, just normlize at the joined edges, | 
|  | // but let's keep the code simple and normalize fully. (AsWindowsPath in | 
|  | // Set normalizes.) | 
|  | return Set(cwd.path_ + L"\\" + path_); | 
|  | } else { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | Path Path::Dirname() const { | 
|  | Path result; | 
|  | result.path_ = blaze_util::SplitPathW(path_).first; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | IFStream* IFStreamImpl::Create(HANDLE handle, DWORD page_size) { | 
|  | std::unique_ptr<uint8_t[]> data(new uint8_t[page_size * 2]); | 
|  | DWORD read; | 
|  | if (!ReadFile(handle, data.get(), page_size * 2, &read, nullptr)) { | 
|  | DWORD err = GetLastError(); | 
|  | if (err == ERROR_BROKEN_PIPE) { | 
|  | read = 0; | 
|  | } else { | 
|  | LogErrorWithValue(__LINE__, "Failed to read from file", err); | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  | return new IFStreamImpl(handle, std::move(data), read, page_size); | 
|  | } | 
|  |  | 
|  | int IFStreamImpl::Get() { | 
|  | if (pos_ == end_) { | 
|  | return kIFStreamErrorEOF; | 
|  | } | 
|  |  | 
|  | int result = pages_[pos_]; | 
|  | if (pos_ + 1 < end_) { | 
|  | pos_++; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Overwrite the *active* page: we are about to move off of it. | 
|  | DWORD offs = (pos_ < page_size_) ? 0 : page_size_; | 
|  | DWORD read; | 
|  | if (!ReadFile(handle_, pages_.get() + offs, page_size_, &read, nullptr)) { | 
|  | DWORD err = GetLastError(); | 
|  | if (err == ERROR_BROKEN_PIPE) { | 
|  | // The stream is reading from a pipe, and there's no more data. | 
|  | } else { | 
|  | LogErrorWithValue(__LINE__, "Failed to read from file", err); | 
|  | return kIFStreamErrorIO; | 
|  | } | 
|  | } | 
|  | pos_ = (pos_ < page_size_) ? page_size_ : 0; | 
|  | end_ = pos_ + next_size_; | 
|  | next_size_ = read; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | DWORD IFStreamImpl::Peek(DWORD n, uint8_t* out) const { | 
|  | if (pos_ == end_) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | DWORD n1 = end_ - pos_; | 
|  | if (n1 > n) { | 
|  | n1 = n;  // all 'n' bytes are on the current page | 
|  | } | 
|  | memcpy(out, pages_.get() + pos_, n1); | 
|  | if (n1 == n) { | 
|  | return n; | 
|  | } | 
|  |  | 
|  | DWORD offs = (pos_ < page_size_) ? page_size_ : 0; | 
|  | DWORD n2 = n - n1;  // how much is left to read | 
|  | if (n2 > next_size_) { | 
|  | n2 = next_size_;  // read no more than the other page's size | 
|  | } | 
|  | memcpy(out + n1, pages_.get() + offs, n2); | 
|  | return n1 + n2; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | void ZipEntryPaths::Create(const std::string& root, | 
|  | const std::vector<std::string>& relative_paths) { | 
|  | size_ = relative_paths.size(); | 
|  |  | 
|  | size_t total_size = 0; | 
|  | for (const auto& e : relative_paths) { | 
|  | // Increase total size for absolute paths by <root> + "/" + <path> + | 
|  | // null-terminator. | 
|  | total_size += root.size() + 1 + e.size() + 1; | 
|  | } | 
|  |  | 
|  | // Store all absolute paths in one continuous char array. | 
|  | abs_paths_.reset(new char[total_size]); | 
|  |  | 
|  | // Store pointers in two arrays. The pointers point into `abs_path`. | 
|  | // We'll pass these paths to devtools_ijar::ZipBuilder::EstimateSize that | 
|  | // expects an array of char pointers. The last element must be NULL, so | 
|  | // allocate one extra pointer. | 
|  | abs_path_ptrs_.reset(new char*[relative_paths.size() + 1]); | 
|  | entry_path_ptrs_.reset(new char*[relative_paths.size() + 1]); | 
|  |  | 
|  | char* p = abs_paths_.get(); | 
|  | // Create all full paths (root + '/' + relative_paths[i] + '\0'). | 
|  | // | 
|  | // If `root` is "c:/foo", then store the following: | 
|  | // | 
|  | // - Store each absolute path consecutively in `abs_paths_` (via `p`). | 
|  | //   Store paths with forward slashes and not backslashes, because we use them | 
|  | //   as zip entry paths, as well as paths we open with CreateFileA (which can | 
|  | //   convert these paths internally to Windows-style). | 
|  | //   Example: "c:/foo/bar.txt\0c:/foo/sub/baz.txt\0" | 
|  | // | 
|  | // - Store pointers in `abs_path_ptrs_`, pointing to the start of each | 
|  | //   string inside `abs_paths_`. | 
|  | //   Example: "c:/foo/bar.txt\0c:/foo/sub/baz.txt\0" | 
|  | //             ^ here          ^ here | 
|  | // | 
|  | // - Store pointers in `entry_path_ptrs_`, pointing to the start of each | 
|  | //   zip entry path inside `abs_paths_`, which is the part of each path | 
|  | //   that's relative to `root`. | 
|  | //   Example: "c:/foo/bar.txt\0c:/foo/sub/baz.txt\0" | 
|  | //                    ^ here          ^ here | 
|  | // | 
|  | // - Because the ZipBuilder requires that the file paths and zip entry paths | 
|  | //   are null-terminated arrays, we insert an extra null at their ends. | 
|  | for (size_t i = 0; i < relative_paths.size(); ++i) { | 
|  | abs_path_ptrs_.get()[i] = p; | 
|  | strncpy(p, root.c_str(), root.size()); | 
|  | p += root.size(); | 
|  | *p++ = '/'; | 
|  | entry_path_ptrs_.get()[i] = p; | 
|  | strncpy(p, relative_paths[i].c_str(), relative_paths[i].size() + 1); | 
|  | p += relative_paths[i].size() + 1; | 
|  | } | 
|  | abs_path_ptrs_.get()[relative_paths.size()] = nullptr; | 
|  | entry_path_ptrs_.get()[relative_paths.size()] = nullptr; | 
|  | } | 
|  |  | 
|  | int TestWrapperMain(int argc, wchar_t** argv) { | 
|  | Path argv0; | 
|  | std::wstring test_path_arg; | 
|  | Path test_path, exec_root, srcdir, tmpdir, test_outerr, xml_log; | 
|  | UndeclaredOutputs undecl; | 
|  | std::wstring args; | 
|  | if (!AddCurrentDirectoryToPATH() || | 
|  | !ParseArgs(argc, argv, &argv0, &test_path_arg, &args) || | 
|  | !PrintTestLogStartMarker() || !GetCwd(&exec_root) || !ExportUserName() || | 
|  | !ExportSrcPath(exec_root, &srcdir) || | 
|  | !FindTestBinary(argv0, exec_root, test_path_arg, srcdir, &test_path) || | 
|  | !ChdirToRunfiles(exec_root, srcdir) || | 
|  | !ExportTmpPath(exec_root, &tmpdir) || !ExportHome(tmpdir) || | 
|  | !ExportRunfiles(exec_root, srcdir) || !ExportShardStatusFile(exec_root) || | 
|  | !ExportGtestVariables(tmpdir) || !ExportMiscEnvvars(exec_root) || | 
|  | !ExportXmlPath(exec_root, &test_outerr, &xml_log) || | 
|  | !GetAndUnexportUndeclaredOutputsEnvvars(exec_root, &undecl)) { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | Duration test_duration; | 
|  | int result = RunSubprocess(test_path, args, test_outerr, &test_duration); | 
|  | if (!CreateXmlLog(xml_log, test_outerr, test_duration, result, | 
|  | DeleteAfterwards::kEnabled, MainType::kTestWrapperMain) || | 
|  | !ArchiveUndeclaredOutputs(undecl) || | 
|  | !CreateUndeclaredOutputsAnnotations(undecl.annotations_dir, | 
|  | undecl.annotations)) { | 
|  | return 1; | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int XmlWriterMain(int argc, wchar_t** argv) { | 
|  | Path cwd, test_outerr, test_xml_log; | 
|  | Duration duration; | 
|  | int exit_code = 0; | 
|  |  | 
|  | if (!GetCwd(&cwd) || | 
|  | !ParseXmlWriterArgs(argc, argv, cwd, &test_outerr, &test_xml_log, | 
|  | &duration, &exit_code) || | 
|  | !CreateXmlLog(test_xml_log, test_outerr, duration, exit_code, | 
|  | DeleteAfterwards::kDisabled, MainType::kXmlWriterMain)) { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | namespace testing { | 
|  |  | 
|  | bool TestOnly_GetEnv(const wchar_t* name, std::wstring* result) { | 
|  | return GetEnv(name, result); | 
|  | } | 
|  |  | 
|  | bool TestOnly_GetFileListRelativeTo(const std::wstring& abs_root, | 
|  | std::vector<FileInfo>* result, | 
|  | int depth_limit) { | 
|  | Path root; | 
|  | return blaze_util::IsAbsolute(abs_root) && root.Set(abs_root) && | 
|  | GetFileListRelativeTo(root, result, depth_limit); | 
|  | } | 
|  |  | 
|  | bool TestOnly_ToZipEntryPaths(const std::wstring& abs_root, | 
|  | const std::vector<FileInfo>& files, | 
|  | ZipEntryPaths* result) { | 
|  | Path root; | 
|  | return blaze_util::IsAbsolute(abs_root) && root.Set(abs_root) && | 
|  | ToZipEntryPaths(root, files, result); | 
|  | } | 
|  |  | 
|  | bool TestOnly_CreateZip(const std::wstring& abs_root, | 
|  | const std::vector<FileInfo>& files, | 
|  | const std::wstring& abs_zip) { | 
|  | Path root, zip; | 
|  | return blaze_util::IsAbsolute(abs_root) && root.Set(abs_root) && | 
|  | blaze_util::IsAbsolute(abs_zip) && zip.Set(abs_zip) && | 
|  | CreateZip(root, files, zip); | 
|  | } | 
|  |  | 
|  | std::string TestOnly_GetMimeType(const std::string& filename) { | 
|  | return GetMimeType(filename); | 
|  | } | 
|  |  | 
|  | bool TestOnly_CreateUndeclaredOutputsManifest( | 
|  | const std::vector<FileInfo>& files, std::string* result) { | 
|  | return CreateUndeclaredOutputsManifestContent(files, result); | 
|  | } | 
|  |  | 
|  | bool TestOnly_CreateUndeclaredOutputsAnnotations( | 
|  | const std::wstring& abs_root, const std::wstring& abs_output) { | 
|  | Path root, output; | 
|  | return blaze_util::IsAbsolute(abs_root) && root.Set(abs_root) && | 
|  | blaze_util::IsAbsolute(abs_output) && output.Set(abs_output) && | 
|  | CreateUndeclaredOutputsAnnotations(root, output); | 
|  | } | 
|  |  | 
|  | bool TestOnly_AsMixedPath(const std::wstring& path, std::string* result) { | 
|  | Path p; | 
|  | return p.Set(path) && WcsToAcp(AsMixedPath(RemoveUncPrefixMaybe(p)), result); | 
|  | } | 
|  |  | 
|  | bool TestOnly_CreateTee(bazel::windows::AutoHandle* input, | 
|  | bazel::windows::AutoHandle* output1, | 
|  | bazel::windows::AutoHandle* output2, | 
|  | std::unique_ptr<Tee>* result) { | 
|  | return TeeImpl::Create(input, output1, output2, result); | 
|  | } | 
|  |  | 
|  | bool TestOnly_CdataEncode(IFStream* in_stm, std::basic_ostream<char>* out_stm) { | 
|  | return CdataEscape(in_stm, out_stm); | 
|  | } | 
|  |  | 
|  | IFStream* TestOnly_CreateIFStream(HANDLE handle, DWORD page_size) { | 
|  | return IFStreamImpl::Create(handle, page_size); | 
|  | } | 
|  |  | 
|  | }  // namespace testing | 
|  | }  // namespace test_wrapper | 
|  | }  // namespace tools | 
|  | }  // namespace bazel |