|  | // 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. | 
|  |  | 
|  | // Tests for the Windows implementation of the test wrapper. | 
|  |  | 
|  | #include <windows.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <vector> | 
|  |  | 
|  | #include "gtest/gtest.h" | 
|  | #include "src/main/cpp/util/path_platform.h" | 
|  | #include "src/main/native/windows/file.h" | 
|  | #include "src/main/native/windows/util.h" | 
|  | #include "src/test/cpp/util/windows_test_util.h" | 
|  | #include "third_party/ijar/common.h" | 
|  | #include "third_party/ijar/zip.h" | 
|  | #include "tools/test/windows/tw.h" | 
|  |  | 
|  | #if !defined(_WIN32) && !defined(__CYGWIN__) | 
|  | #error("This test should only be run on Windows") | 
|  | #endif  // !defined(_WIN32) && !defined(__CYGWIN__) | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | using bazel::tools::test_wrapper::FileInfo; | 
|  | using bazel::tools::test_wrapper::IFStream; | 
|  | using bazel::tools::test_wrapper::ZipEntryPaths; | 
|  | using bazel::tools::test_wrapper::testing::TestOnly_AsMixedPath; | 
|  | using bazel::tools::test_wrapper::testing::TestOnly_CdataEncode; | 
|  | using bazel::tools::test_wrapper::testing::TestOnly_CreateIFStream; | 
|  | using bazel::tools::test_wrapper::testing::TestOnly_CreateTee; | 
|  | using bazel::tools::test_wrapper::testing:: | 
|  | TestOnly_CreateUndeclaredOutputsAnnotations; | 
|  | using bazel::tools::test_wrapper::testing:: | 
|  | TestOnly_CreateUndeclaredOutputsManifest; | 
|  | using bazel::tools::test_wrapper::testing::TestOnly_CreateZip; | 
|  | using bazel::tools::test_wrapper::testing::TestOnly_GetEnv; | 
|  | using bazel::tools::test_wrapper::testing::TestOnly_GetFileListRelativeTo; | 
|  | using bazel::tools::test_wrapper::testing::TestOnly_GetMimeType; | 
|  | using bazel::tools::test_wrapper::testing::TestOnly_ToZipEntryPaths; | 
|  |  | 
|  | class TestWrapperWindowsTest : public ::testing::Test { | 
|  | public: | 
|  | void TearDown() override { | 
|  | blaze_util::DeleteAllUnder(blaze_util::GetTestTmpDirW()); | 
|  | } | 
|  | }; | 
|  |  | 
|  | void GetTestTmpdir(std::wstring* result, int line) { | 
|  | EXPECT_TRUE(TestOnly_GetEnv(L"TEST_TMPDIR", result)) | 
|  | << __FILE__ << "(" << line << "): assertion failed here"; | 
|  | ASSERT_GT(result->size(), 0) | 
|  | << __FILE__ << "(" << line << "): assertion failed here"; | 
|  | std::replace(result->begin(), result->end(), L'/', L'\\'); | 
|  | if (!bazel::windows::HasUncPrefix(result->c_str())) { | 
|  | *result = L"\\\\?\\" + *result; | 
|  | } | 
|  | } | 
|  |  | 
|  | void CreateJunction(const std::wstring& name, const std::wstring& target, | 
|  | int line) { | 
|  | std::wstring wname; | 
|  | std::wstring wtarget; | 
|  | EXPECT_TRUE(blaze_util::AsWindowsPath(name, &wname, nullptr)) | 
|  | << __FILE__ << "(" << line << "): assertion failed here"; | 
|  | EXPECT_TRUE(blaze_util::AsWindowsPath(target, &wtarget, nullptr)) | 
|  | << __FILE__ << "(" << line << "): assertion failed here"; | 
|  | EXPECT_EQ(bazel::windows::CreateJunction(wname, wtarget, nullptr), | 
|  | bazel::windows::CreateJunctionResult::kSuccess) | 
|  | << __FILE__ << "(" << line << "): assertion failed here"; | 
|  | } | 
|  |  | 
|  | void CompareFileInfos(std::vector<FileInfo> actual, | 
|  | std::vector<FileInfo> expected, int line) { | 
|  | ASSERT_EQ(actual.size(), expected.size()) | 
|  | << __FILE__ << "(" << line << "): assertion failed here"; | 
|  | std::sort(actual.begin(), actual.end(), | 
|  | [](const FileInfo& a, const FileInfo& b) { | 
|  | return a.RelativePath() > b.RelativePath(); | 
|  | }); | 
|  | std::sort(expected.begin(), expected.end(), | 
|  | [](const FileInfo& a, const FileInfo& b) { | 
|  | return a.RelativePath() > b.RelativePath(); | 
|  | }); | 
|  | for (std::vector<FileInfo>::size_type i = 0; i < actual.size(); ++i) { | 
|  | ASSERT_EQ(actual[i].RelativePath(), expected[i].RelativePath()) | 
|  | << __FILE__ << "(" << line << "): assertion failed here; index: " << i; | 
|  | ASSERT_EQ(actual[i].Size(), expected[i].Size()) | 
|  | << __FILE__ << "(" << line << "): assertion failed here; index: " << i; | 
|  | ASSERT_EQ(actual[i].IsDirectory(), expected[i].IsDirectory()) | 
|  | << __FILE__ << "(" << line << "): assertion failed here; index: " << i; | 
|  | } | 
|  | } | 
|  |  | 
|  | // According to this StackOverflow post [1] `const` modifies what's on its | 
|  | // *left*, and "const char" is equivalent to "char const". | 
|  | // `ZipBuilder::EstimateSize`'s argument type is "char const* const*" meaning a | 
|  | // mutable array (right *) of const pointers (left *) to const data (char). | 
|  | // | 
|  | // [1] https://stackoverflow.com/a/8091846/7778502 | 
|  | void CompareZipEntryPaths(char const* const* actual, | 
|  | std::vector<const char*> expected, int line) { | 
|  | for (int i = 0; i < expected.size(); ++i) { | 
|  | EXPECT_NE(actual[i], nullptr) | 
|  | << __FILE__ << "(" << line << "): assertion failed here"; | 
|  | EXPECT_EQ(std::string(actual[i]), std::string(expected[i])) | 
|  | << __FILE__ << "(" << line << "): assertion failed here"; | 
|  | } | 
|  | EXPECT_EQ(actual[expected.size()], nullptr) | 
|  | << __FILE__ << "(" << line << "): assertion failed here; value was (" | 
|  | << actual[expected.size()] << ")"; | 
|  | } | 
|  |  | 
|  | #define GET_TEST_TMPDIR(result) GetTestTmpdir(result, __LINE__) | 
|  | #define CREATE_JUNCTION(name, target) CreateJunction(name, target, __LINE__) | 
|  | #define COMPARE_FILE_INFOS(actual, expected) \ | 
|  | CompareFileInfos(actual, expected, __LINE__) | 
|  | #define COMPARE_ZIP_ENTRY_PATHS(actual, expected) \ | 
|  | CompareZipEntryPaths(actual, expected, __LINE__) | 
|  |  | 
|  | #define TOSTRING1(x) #x | 
|  | #define TOSTRING(x) TOSTRING1(x) | 
|  | #define TOWSTRING1(x) L##x | 
|  | #define TOWSTRING(x) TOWSTRING1(x) | 
|  | #define WLINE TOWSTRING(TOSTRING(__LINE__)) | 
|  |  | 
|  | HANDLE FopenRead(const std::wstring& unc_path) { | 
|  | return CreateFileW(unc_path.c_str(), GENERIC_READ, | 
|  | FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, | 
|  | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); | 
|  | } | 
|  |  | 
|  | HANDLE FopenContents(const wchar_t* wline, const char* contents, DWORD size) { | 
|  | std::wstring tmpdir; | 
|  | GET_TEST_TMPDIR(&tmpdir); | 
|  | std::wstring filename = tmpdir + L"\\tmp" + wline; | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(filename, contents, size)); | 
|  | return FopenRead(filename); | 
|  | } | 
|  |  | 
|  | HANDLE FopenContents(const wchar_t* wline, const char* contents) { | 
|  | return FopenContents(wline, contents, strlen(contents)); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestGetFileListRelativeTo) { | 
|  | std::wstring tmpdir; | 
|  | GET_TEST_TMPDIR(&tmpdir); | 
|  |  | 
|  | // Create a directory structure to parse. | 
|  | std::wstring root = tmpdir + L"\\tmp" + WLINE; | 
|  | EXPECT_TRUE(CreateDirectoryW(root.c_str(), nullptr)); | 
|  | EXPECT_TRUE(CreateDirectoryW((root + L"\\foo").c_str(), nullptr)); | 
|  | EXPECT_TRUE(CreateDirectoryW((root + L"\\foo\\sub").c_str(), nullptr)); | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(root + L"\\foo\\sub\\file1", "")); | 
|  | EXPECT_TRUE( | 
|  | blaze_util::CreateDummyFile(root + L"\\foo\\sub\\file2", "hello")); | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(root + L"\\foo\\file1", "foo")); | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(root + L"\\foo\\file2", "foobar")); | 
|  | CREATE_JUNCTION(root + L"\\foo\\junc", root + L"\\foo\\sub"); | 
|  |  | 
|  | // Assert traversal of "root" -- should include all files, and also traverse | 
|  | // the junction. | 
|  | std::vector<FileInfo> actual; | 
|  | ASSERT_TRUE(TestOnly_GetFileListRelativeTo(root, &actual)); | 
|  |  | 
|  | std::vector<FileInfo> expected = {FileInfo(L"foo"), | 
|  | FileInfo(L"foo\\sub"), | 
|  | FileInfo(L"foo\\sub\\file1", 0), | 
|  | FileInfo(L"foo\\sub\\file2", 5), | 
|  | FileInfo(L"foo\\file1", 3), | 
|  | FileInfo(L"foo\\file2", 6), | 
|  | FileInfo(L"foo\\junc"), | 
|  | FileInfo(L"foo\\junc\\file1", 0), | 
|  | FileInfo(L"foo\\junc\\file2", 5)}; | 
|  | COMPARE_FILE_INFOS(actual, expected); | 
|  |  | 
|  | // Assert traversal of "foo" -- should include all files, but now with paths | 
|  | // relative to "foo". | 
|  | actual.clear(); | 
|  | ASSERT_TRUE( | 
|  | TestOnly_GetFileListRelativeTo((root + L"\\foo").c_str(), &actual)); | 
|  |  | 
|  | expected = {FileInfo(L"sub"), | 
|  | FileInfo(L"sub\\file1", 0), | 
|  | FileInfo(L"sub\\file2", 5), | 
|  | FileInfo(L"file1", 3), | 
|  | FileInfo(L"file2", 6), | 
|  | FileInfo(L"junc"), | 
|  | FileInfo(L"junc\\file1", 0), | 
|  | FileInfo(L"junc\\file2", 5)}; | 
|  | COMPARE_FILE_INFOS(actual, expected); | 
|  |  | 
|  | // Assert traversal limited to the current directory (depth of 0). | 
|  | actual.clear(); | 
|  | ASSERT_TRUE(TestOnly_GetFileListRelativeTo(root, &actual, 0)); | 
|  | expected = {FileInfo(L"foo")}; | 
|  | COMPARE_FILE_INFOS(actual, expected); | 
|  |  | 
|  | // Assert traversal limited to depth of 1. | 
|  | actual.clear(); | 
|  | ASSERT_TRUE(TestOnly_GetFileListRelativeTo(root, &actual, 1)); | 
|  | expected = {FileInfo(L"foo"), FileInfo(L"foo\\sub"), | 
|  | FileInfo(L"foo\\file1", 3), FileInfo(L"foo\\file2", 6), | 
|  | FileInfo(L"foo\\junc")}; | 
|  | COMPARE_FILE_INFOS(actual, expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestToZipEntryPaths) { | 
|  | // Pretend we already acquired a file list. The files don't have to exist. | 
|  | std::wstring root = L"c:\\nul\\root"; | 
|  | std::vector<FileInfo> files = {FileInfo(L"foo"), | 
|  | FileInfo(L"foo\\sub"), | 
|  | FileInfo(L"foo\\sub\\file1", 0), | 
|  | FileInfo(L"foo\\sub\\file2", 5), | 
|  | FileInfo(L"foo\\file1", 3), | 
|  | FileInfo(L"foo\\file2", 6), | 
|  | FileInfo(L"foo\\junc"), | 
|  | FileInfo(L"foo\\junc\\file1", 0), | 
|  | FileInfo(L"foo\\junc\\file2", 5)}; | 
|  |  | 
|  | ZipEntryPaths actual; | 
|  | ASSERT_TRUE(TestOnly_ToZipEntryPaths(root, files, &actual)); | 
|  | ASSERT_EQ(actual.Size(), 9); | 
|  |  | 
|  | std::vector<const char*> expected_abs_paths = { | 
|  | "c:/nul/root/foo/",          "c:/nul/root/foo/sub/", | 
|  | "c:/nul/root/foo/sub/file1", "c:/nul/root/foo/sub/file2", | 
|  | "c:/nul/root/foo/file1",     "c:/nul/root/foo/file2", | 
|  | "c:/nul/root/foo/junc/",     "c:/nul/root/foo/junc/file1", | 
|  | "c:/nul/root/foo/junc/file2"}; | 
|  | COMPARE_ZIP_ENTRY_PATHS(actual.AbsPathPtrs(), expected_abs_paths); | 
|  |  | 
|  | std::vector<const char*> expected_entry_paths = { | 
|  | "foo/",      "foo/sub/",  "foo/sub/file1",  "foo/sub/file2", "foo/file1", | 
|  | "foo/file2", "foo/junc/", "foo/junc/file1", "foo/junc/file2"}; | 
|  | COMPARE_ZIP_ENTRY_PATHS(actual.EntryPathPtrs(), expected_entry_paths); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestToZipEntryPathsLongPathRoot) { | 
|  | // Pretend we already acquired a file list. The files don't have to exist. | 
|  | // Assert that the root is allowed to have the `\\?\` prefix, but the zip | 
|  | // entry paths won't have it. | 
|  | std::wstring root = L"\\\\?\\c:\\nul\\unc"; | 
|  | std::vector<FileInfo> files = {FileInfo(L"foo"), | 
|  | FileInfo(L"foo\\sub"), | 
|  | FileInfo(L"foo\\sub\\file1", 0), | 
|  | FileInfo(L"foo\\sub\\file2", 5), | 
|  | FileInfo(L"foo\\file1", 3), | 
|  | FileInfo(L"foo\\file2", 6), | 
|  | FileInfo(L"foo\\junc"), | 
|  | FileInfo(L"foo\\junc\\file1", 0), | 
|  | FileInfo(L"foo\\junc\\file2", 5)}; | 
|  |  | 
|  | ZipEntryPaths actual; | 
|  | ASSERT_TRUE(TestOnly_ToZipEntryPaths(root, files, &actual)); | 
|  | ASSERT_EQ(actual.Size(), 9); | 
|  |  | 
|  | std::vector<const char*> expected_abs_paths = { | 
|  | "c:/nul/unc/foo/",          "c:/nul/unc/foo/sub/", | 
|  | "c:/nul/unc/foo/sub/file1", "c:/nul/unc/foo/sub/file2", | 
|  | "c:/nul/unc/foo/file1",     "c:/nul/unc/foo/file2", | 
|  | "c:/nul/unc/foo/junc/",     "c:/nul/unc/foo/junc/file1", | 
|  | "c:/nul/unc/foo/junc/file2"}; | 
|  | COMPARE_ZIP_ENTRY_PATHS(actual.AbsPathPtrs(), expected_abs_paths); | 
|  |  | 
|  | std::vector<const char*> expected_entry_paths = { | 
|  | "foo/",      "foo/sub/",  "foo/sub/file1",  "foo/sub/file2", "foo/file1", | 
|  | "foo/file2", "foo/junc/", "foo/junc/file1", "foo/junc/file2"}; | 
|  | COMPARE_ZIP_ENTRY_PATHS(actual.EntryPathPtrs(), expected_entry_paths); | 
|  | } | 
|  |  | 
|  | class InMemoryExtractor : public devtools_ijar::ZipExtractorProcessor { | 
|  | public: | 
|  | struct ExtractedFile { | 
|  | std::string path; | 
|  | std::unique_ptr<devtools_ijar::u1[]> data; | 
|  | size_t size; | 
|  | }; | 
|  |  | 
|  | InMemoryExtractor(std::vector<ExtractedFile>* extracted) | 
|  | : extracted_(extracted) {} | 
|  |  | 
|  | bool Accept(const char* filename, const devtools_ijar::u4 attr) override { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void Process(const char* filename, const devtools_ijar::u4 attr, | 
|  | const devtools_ijar::u1* data, const size_t size) override { | 
|  | extracted_->push_back({}); | 
|  | extracted_->back().path = filename; | 
|  | if (size > 0) { | 
|  | extracted_->back().data.reset(new devtools_ijar::u1[size]); | 
|  | memcpy(extracted_->back().data.get(), data, size); | 
|  | } | 
|  | extracted_->back().size = size; | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::vector<ExtractedFile>* extracted_; | 
|  | }; | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestCreateZip) { | 
|  | std::wstring tmpdir; | 
|  | GET_TEST_TMPDIR(&tmpdir); | 
|  |  | 
|  | // Create a directory structure to archive. | 
|  | std::wstring root = tmpdir + L"\\tmp" + WLINE; | 
|  | EXPECT_TRUE(CreateDirectoryW(root.c_str(), nullptr)); | 
|  | EXPECT_TRUE(CreateDirectoryW((root + L"\\foo").c_str(), nullptr)); | 
|  | EXPECT_TRUE(CreateDirectoryW((root + L"\\foo\\sub").c_str(), nullptr)); | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(root + L"\\foo\\sub\\file1", "")); | 
|  | EXPECT_TRUE( | 
|  | blaze_util::CreateDummyFile(root + L"\\foo\\sub\\file2", "hello")); | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(root + L"\\foo\\file1", "foo")); | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(root + L"\\foo\\file2", "foobar")); | 
|  | CREATE_JUNCTION(root + L"\\foo\\junc", root + L"\\foo\\sub"); | 
|  |  | 
|  | std::vector<FileInfo> file_list = {FileInfo(L"foo"), | 
|  | FileInfo(L"foo\\sub"), | 
|  | FileInfo(L"foo\\sub\\file1", 0), | 
|  | FileInfo(L"foo\\sub\\file2", 5), | 
|  | FileInfo(L"foo\\file1", 3), | 
|  | FileInfo(L"foo\\file2", 6), | 
|  | FileInfo(L"foo\\junc"), | 
|  | FileInfo(L"foo\\junc\\file1", 0), | 
|  | FileInfo(L"foo\\junc\\file2", 5)}; | 
|  |  | 
|  | ASSERT_TRUE(TestOnly_CreateZip(root, file_list, root + L"\\x.zip")); | 
|  |  | 
|  | std::string zip_path; | 
|  | EXPECT_TRUE(TestOnly_AsMixedPath(root + L"\\x.zip", &zip_path)); | 
|  |  | 
|  | // Extract the zip file into memory to verify its contents. | 
|  | std::vector<InMemoryExtractor::ExtractedFile> extracted; | 
|  | InMemoryExtractor extractor(&extracted); | 
|  | std::unique_ptr<devtools_ijar::ZipExtractor> zip( | 
|  | devtools_ijar::ZipExtractor::Create(zip_path.c_str(), &extractor)); | 
|  | EXPECT_NE(zip.get(), nullptr); | 
|  | EXPECT_EQ(zip->ProcessAll(), 0); | 
|  |  | 
|  | EXPECT_EQ(extracted.size(), 9); | 
|  |  | 
|  | EXPECT_EQ(extracted[0].path, std::string("foo/")); | 
|  | EXPECT_EQ(extracted[1].path, std::string("foo/sub/")); | 
|  | EXPECT_EQ(extracted[2].path, std::string("foo/sub/file1")); | 
|  | EXPECT_EQ(extracted[3].path, std::string("foo/sub/file2")); | 
|  | EXPECT_EQ(extracted[4].path, std::string("foo/file1")); | 
|  | EXPECT_EQ(extracted[5].path, std::string("foo/file2")); | 
|  | EXPECT_EQ(extracted[6].path, std::string("foo/junc/")); | 
|  | EXPECT_EQ(extracted[7].path, std::string("foo/junc/file1")); | 
|  | EXPECT_EQ(extracted[8].path, std::string("foo/junc/file2")); | 
|  |  | 
|  | EXPECT_EQ(extracted[0].size, 0); | 
|  | EXPECT_EQ(extracted[1].size, 0); | 
|  | EXPECT_EQ(extracted[2].size, 0); | 
|  | EXPECT_EQ(extracted[3].size, 5); | 
|  | EXPECT_EQ(extracted[4].size, 3); | 
|  | EXPECT_EQ(extracted[5].size, 6); | 
|  | EXPECT_EQ(extracted[6].size, 0); | 
|  | EXPECT_EQ(extracted[7].size, 0); | 
|  | EXPECT_EQ(extracted[8].size, 5); | 
|  |  | 
|  | EXPECT_EQ(memcmp(extracted[3].data.get(), "hello", 5), 0); | 
|  | EXPECT_EQ(memcmp(extracted[4].data.get(), "foo", 3), 0); | 
|  | EXPECT_EQ(memcmp(extracted[5].data.get(), "foobar", 6), 0); | 
|  | EXPECT_EQ(memcmp(extracted[8].data.get(), "hello", 5), 0); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestGetMimeType) { | 
|  | // As of 2018-11-08, TestOnly_GetMimeType looks up the MIME type from the | 
|  | // registry under `HKCR\<extension>\Content Type`, e.g. | 
|  | // 'HKCR\.bmp\Content Type`. | 
|  | // Bazel's CI machines run Windows Server 2016 Core, whose registry contains | 
|  | // the Content Type for .ico and .bmp but not for common types such as .txt, | 
|  | // hence the file types we choose to test for. | 
|  | EXPECT_EQ(TestOnly_GetMimeType("foo.ico"), std::string("image/x-icon")); | 
|  | EXPECT_EQ(TestOnly_GetMimeType("foo.bmp"), std::string("image/bmp")); | 
|  | EXPECT_EQ(TestOnly_GetMimeType("foo"), | 
|  | std::string("application/octet-stream")); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestUndeclaredOutputsManifest) { | 
|  | // Pretend we already acquired a file list. The files don't have to exist. | 
|  | // Assert that the root is allowed to have the `\\?\` prefix, but the zip | 
|  | // entry paths won't have it. | 
|  | std::vector<FileInfo> files = {FileInfo(L"foo"), | 
|  | FileInfo(L"foo\\sub"), | 
|  | FileInfo(L"foo\\sub\\file1.ico", 0), | 
|  | FileInfo(L"foo\\sub\\file2.bmp", 5), | 
|  | FileInfo(L"foo\\file2", 6)}; | 
|  |  | 
|  | std::string content; | 
|  | ASSERT_TRUE(TestOnly_CreateUndeclaredOutputsManifest(files, &content)); | 
|  | ASSERT_EQ(content, std::string("foo/sub/file1.ico\t0\timage/x-icon\n" | 
|  | "foo/sub/file2.bmp\t5\timage/bmp\n" | 
|  | "foo/file2\t6\tapplication/octet-stream\n")); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestCreateUndeclaredOutputsAnnotations) { | 
|  | std::wstring tmpdir; | 
|  | GET_TEST_TMPDIR(&tmpdir); | 
|  |  | 
|  | // Create a directory structure to parse. | 
|  | std::wstring root = tmpdir + L"\\tmp" + WLINE; | 
|  | EXPECT_TRUE(CreateDirectoryW(root.c_str(), nullptr)); | 
|  | EXPECT_TRUE(CreateDirectoryW((root + L"\\foo").c_str(), nullptr)); | 
|  | EXPECT_TRUE(CreateDirectoryW((root + L"\\bar.part").c_str(), nullptr)); | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(root + L"\\a.part", "Hello a")); | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(root + L"\\b.txt", "Hello b")); | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(root + L"\\c.part", "Hello c")); | 
|  | EXPECT_TRUE(blaze_util::CreateDummyFile(root + L"\\foo\\d.part", "Hello d")); | 
|  | EXPECT_TRUE( | 
|  | blaze_util::CreateDummyFile(root + L"\\bar.part\\e.part", "Hello e")); | 
|  |  | 
|  | std::wstring annot = root + L"\\x.annot"; | 
|  | ASSERT_TRUE(TestOnly_CreateUndeclaredOutputsAnnotations(root, annot)); | 
|  |  | 
|  | HANDLE h = FopenRead(annot); | 
|  | ASSERT_NE(h, INVALID_HANDLE_VALUE); | 
|  | char content[100]; | 
|  | DWORD read; | 
|  | bool success = ReadFile(h, content, 100, &read, nullptr) != FALSE; | 
|  | CloseHandle(h); | 
|  | EXPECT_TRUE(success); | 
|  | ASSERT_EQ(std::string(content, read), std::string("Hello aHello c")); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestTee) { | 
|  | HANDLE read1_h, write1_h; | 
|  | EXPECT_TRUE(CreatePipe(&read1_h, &write1_h, nullptr, 0)); | 
|  | bazel::windows::AutoHandle read1(read1_h), write1(write1_h); | 
|  | HANDLE read2_h, write2_h; | 
|  | EXPECT_TRUE(CreatePipe(&read2_h, &write2_h, nullptr, 0)); | 
|  | bazel::windows::AutoHandle read2(read2_h), write2(write2_h); | 
|  | HANDLE read3_h, write3_h; | 
|  | EXPECT_TRUE(CreatePipe(&read3_h, &write3_h, nullptr, 0)); | 
|  | bazel::windows::AutoHandle read3(read3_h), write3(write3_h); | 
|  |  | 
|  | std::unique_ptr<bazel::tools::test_wrapper::Tee> tee; | 
|  | EXPECT_TRUE(TestOnly_CreateTee(&read1, &write2, &write3, &tee)); | 
|  |  | 
|  | DWORD written, read; | 
|  | char content[100]; | 
|  |  | 
|  | EXPECT_TRUE(WriteFile(write1, "hello", 5, &written, nullptr)); | 
|  | EXPECT_EQ(written, 5); | 
|  | EXPECT_TRUE(ReadFile(read2, content, 100, &read, nullptr)); | 
|  | EXPECT_EQ(read, 5); | 
|  | EXPECT_EQ(std::string(content, read), "hello"); | 
|  | EXPECT_TRUE(ReadFile(read3, content, 100, &read, nullptr)); | 
|  | EXPECT_EQ(read, 5); | 
|  | EXPECT_EQ(std::string(content, read), "hello"); | 
|  |  | 
|  | EXPECT_TRUE(WriteFile(write1, "foo", 3, &written, nullptr)); | 
|  | EXPECT_EQ(written, 3); | 
|  | EXPECT_TRUE(ReadFile(read2, content, 100, &read, nullptr)); | 
|  | EXPECT_EQ(read, 3); | 
|  | EXPECT_EQ(std::string(content, read), "foo"); | 
|  | EXPECT_TRUE(ReadFile(read3, content, 100, &read, nullptr)); | 
|  | EXPECT_EQ(read, 3); | 
|  | EXPECT_EQ(std::string(content, read), "foo"); | 
|  |  | 
|  | write1 = INVALID_HANDLE_VALUE;  // closes handle so the Tee thread can exit | 
|  | } | 
|  |  | 
|  | void AssertCdataEncodeBuffer(const wchar_t* wline, const char* input, | 
|  | DWORD size, const char* expected_output) { | 
|  | bazel::windows::AutoHandle h(FopenContents(wline, input, size)); | 
|  | std::unique_ptr<IFStream> istm(TestOnly_CreateIFStream(h, /* page_size */ 4)); | 
|  | std::stringstream out_stm; | 
|  | ASSERT_TRUE(TestOnly_CdataEncode(istm.get(), &out_stm)); | 
|  | ASSERT_EQ(expected_output, out_stm.str()); | 
|  | } | 
|  |  | 
|  | void AssertCdataEncodeBuffer(const wchar_t* wline, const char* input, | 
|  | const char* expected_output) { | 
|  | AssertCdataEncodeBuffer(wline, input, strlen(input), expected_output); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestCdataEscapeNullTerminator) { | 
|  | AssertCdataEncodeBuffer(WLINE, "x\0y", 3, "x?y"); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestCdataEscapeCdataEndings) { | 
|  | AssertCdataEncodeBuffer( | 
|  | WLINE, | 
|  | // === Input === | 
|  | // CDATA end sequence, followed by some arbitrary octet. | 
|  | "]]>x" | 
|  | // CDATA end sequence twice. | 
|  | "]]>]]>x" | 
|  | // CDATA end sequence at the end of the string. | 
|  | "]]>", | 
|  |  | 
|  | // === Expected output === | 
|  | "]]>]]<![CDATA[>x" | 
|  | "]]>]]<![CDATA[>]]>]]<![CDATA[>x" | 
|  | "]]>]]<![CDATA[>"); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestCdataEscapeSingleOctets) { | 
|  | AssertCdataEncodeBuffer(WLINE, | 
|  | // === Input === | 
|  | // Legal single-octets. | 
|  | "AB\x9\xA\xD\x20\x7F" | 
|  | // Illegal single-octets. | 
|  | "\x8\xB\xC\x1F\x80\xFF" | 
|  | "x", | 
|  |  | 
|  | // === Expected output === | 
|  | // Legal single-octets. | 
|  | "AB\x9\xA\xD\x20\x7F" | 
|  | // Illegal single-octets. | 
|  | "??????" | 
|  | "x"); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestCdataEscapeDoubleOctets) { | 
|  | // Legal range: [\xc0-\xdf][\x80-\xbf] | 
|  | AssertCdataEncodeBuffer( | 
|  | WLINE, | 
|  | "x" | 
|  | // Legal double-octet sequences. | 
|  | "\xC0\x80" | 
|  | "\xDE\xB0" | 
|  | "\xDF\xBF" | 
|  | // Illegal double-octet sequences, first octet is bad, second is good. | 
|  | "\xBF\x80"  // each are matched as single bad octets | 
|  | "\xE0\x80" | 
|  | // Illegal double-octet sequences, first octet is good, second is bad. | 
|  | "\xC0\x7F"  // 0x7F is legal as a single-octet, retained | 
|  | "\xDF\xC0"  // 0xC0 starts a legal two-octet sequence... | 
|  | // Illegal double-octet sequences, both octets bad. | 
|  | "\xBF\xFF"  // ...and 0xBF finishes that sequence | 
|  | "x", | 
|  |  | 
|  | // === Expected output === | 
|  | "x" | 
|  | // Legal double-octet sequences. | 
|  | "\xC0\x80" | 
|  | "\xDE\xB0" | 
|  | "\xDF\xBF" | 
|  | // Illegal double-octet sequences, first octet is bad, second is good. | 
|  | "??" | 
|  | "??" | 
|  | // Illegal double-octet sequences, first octet is good, second is bad. | 
|  | "?\x7F"  // 0x7F is legal as a single-octet, retained | 
|  | "?\xC0"  // 0xC0 starts a legal two-octet sequence... | 
|  | // Illegal double-octet sequences, both octets bad. | 
|  | "\xBF?"  // ...and 0xBF finishes that sequence | 
|  | "x"); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestCdataEscapeAndAppend) { | 
|  | std::wstring tmpdir; | 
|  | GET_TEST_TMPDIR(&tmpdir); | 
|  |  | 
|  | AssertCdataEncodeBuffer(WLINE, | 
|  | // === Input === | 
|  | "AB\xA\xC\xD" | 
|  | "]]>" | 
|  | "]]]>" | 
|  | "\xC0\x80" | 
|  | "a" | 
|  | "\xED\x9F\xBF" | 
|  | "b" | 
|  | "\xEF\xBF\xB0" | 
|  | "c" | 
|  | "\xF7\xB0\x80\x81" | 
|  | "d" | 
|  | "]]>", | 
|  |  | 
|  | // === Output === | 
|  | "AB\xA?\xD" | 
|  | "]]>]]<![CDATA[>" | 
|  | "]]]>]]<![CDATA[>" | 
|  | "\xC0\x80" | 
|  | "a" | 
|  | "\xED\x9F\xBF" | 
|  | "b" | 
|  | "\xEF\xBF\xB0" | 
|  | "c" | 
|  | "\xF7\xB0\x80\x81" | 
|  | "d" | 
|  | "]]>]]<![CDATA[>"); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestIFStreamNoData) { | 
|  | bazel::windows::AutoHandle h(FopenContents(WLINE, "")); | 
|  | std::unique_ptr<IFStream> s(TestOnly_CreateIFStream(h, 6)); | 
|  | uint8_t buf[3] = {0, 0, 0}; | 
|  |  | 
|  | ASSERT_EQ(s->Get(), IFStream::kIFStreamErrorEOF); | 
|  | ASSERT_EQ(s->Peek(0, buf), 0); | 
|  | ASSERT_EQ(s->Peek(1, buf), 0); | 
|  | ASSERT_EQ(s->Peek(2, buf), 0); | 
|  | ASSERT_EQ(s->Peek(100, buf), 0); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestIFStreamLessDataThanPageSize) { | 
|  | // The data is "abc" (3 bytes), page size is 6 bytes. | 
|  | bazel::windows::AutoHandle h(FopenContents(WLINE, "abc")); | 
|  | std::unique_ptr<IFStream> s(TestOnly_CreateIFStream(h, 6)); | 
|  | uint8_t buf[3] = {0, 0, 0}; | 
|  |  | 
|  | // Read position is at "a". | 
|  | ASSERT_EQ(s->Get(), 'a'); | 
|  | ASSERT_EQ(s->Peek(1, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'b'); | 
|  | ASSERT_EQ(s->Peek(2, buf), 2); | 
|  | ASSERT_EQ(buf[0], 'b'); | 
|  | ASSERT_EQ(buf[1], 'c'); | 
|  | ASSERT_EQ(s->Peek(100, buf), 2); | 
|  | ASSERT_EQ(buf[0], 'b'); | 
|  | ASSERT_EQ(buf[1], 'c'); | 
|  |  | 
|  | // Read position is at "b". | 
|  | ASSERT_EQ(s->Get(), 'b'); | 
|  | ASSERT_EQ(s->Peek(1, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'c'); | 
|  | ASSERT_EQ(s->Peek(2, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'c'); | 
|  | ASSERT_EQ(s->Peek(100, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'c'); | 
|  |  | 
|  | // Read position is at "c". | 
|  | ASSERT_EQ(s->Get(), 'c'); | 
|  | ASSERT_EQ(s->Peek(1, buf), 0); | 
|  | ASSERT_EQ(s->Peek(2, buf), 0); | 
|  | ASSERT_EQ(s->Peek(100, buf), 0); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestIFStreamExactlySinglePageSize) { | 
|  | // The data is "abcdef" (6 bytes), page size is 6 bytes. | 
|  | bazel::windows::AutoHandle h(FopenContents(WLINE, "abcdef")); | 
|  | std::unique_ptr<IFStream> s(TestOnly_CreateIFStream(h, 6)); | 
|  | uint8_t buf[6] = {0, 0, 0}; | 
|  |  | 
|  | // Read position is at "a". | 
|  | ASSERT_EQ(s->Get(), 'a'); | 
|  | ASSERT_EQ(s->Peek(1, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'b'); | 
|  | ASSERT_EQ(s->Peek(5, buf), 5); | 
|  | ASSERT_EQ(buf[0], 'b'); | 
|  | ASSERT_EQ(buf[1], 'c'); | 
|  | ASSERT_EQ(buf[2], 'd'); | 
|  | ASSERT_EQ(buf[3], 'e'); | 
|  | ASSERT_EQ(buf[4], 'f'); | 
|  |  | 
|  | ASSERT_EQ(s->Get(), 'b'); | 
|  | ASSERT_EQ(s->Get(), 'c'); | 
|  | ASSERT_EQ(s->Get(), 'd'); | 
|  | ASSERT_EQ(s->Get(), 'e'); | 
|  | ASSERT_EQ(s->Peek(1, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'f'); | 
|  | ASSERT_EQ(s->Peek(5, buf), 1); | 
|  |  | 
|  | // Read position is at "f". No more peeking or moving. | 
|  | ASSERT_EQ(s->Get(), 'f'); | 
|  | ASSERT_EQ(s->Peek(1, buf), 0); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestIFStreamLessDataThanDoublePageSize) { | 
|  | bazel::windows::AutoHandle h(FopenContents(WLINE, "abcdefghi")); | 
|  | std::unique_ptr<IFStream> s(TestOnly_CreateIFStream(h, 6)); | 
|  | uint8_t buf[3] = {0, 0, 0}; | 
|  |  | 
|  | // Move near the page boundary. | 
|  | for (int c = s->Get(); c != 'e'; c = s->Get()) { | 
|  | } | 
|  |  | 
|  | // Read position is at "e". Peek2 and Peek3 will need to read from next page. | 
|  | ASSERT_EQ(s->Peek(1, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'f'); | 
|  | ASSERT_EQ(s->Peek(2, buf), 2); | 
|  | ASSERT_EQ(buf[0], 'f'); | 
|  | ASSERT_EQ(buf[1], 'g'); | 
|  | ASSERT_EQ(s->Peek(3, buf), 3); | 
|  | ASSERT_EQ(buf[0], 'f'); | 
|  | ASSERT_EQ(buf[1], 'g'); | 
|  | ASSERT_EQ(buf[2], 'h'); | 
|  |  | 
|  | for (int c = s->Get(); c != 'h'; c = s->Get()) { | 
|  | } | 
|  | ASSERT_EQ(s->Peek(1, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'i'); | 
|  | ASSERT_EQ(s->Peek(5, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'i'); | 
|  | } | 
|  |  | 
|  | TEST_F(TestWrapperWindowsTest, TestIFStreamLessDataThanTriplePageSize) { | 
|  | // Data is 15 bytes, page size is 6 bytes, we'll cross 2 page boundaries. | 
|  | bazel::windows::AutoHandle h(FopenContents(WLINE, "abcdefghijklmno")); | 
|  | std::unique_ptr<IFStream> s(TestOnly_CreateIFStream(h, 6)); | 
|  | uint8_t buf[12]; | 
|  |  | 
|  | // Move near the first page boundary. | 
|  | for (int c = s->Get(); c != 'e'; c = s->Get()) { | 
|  | } | 
|  | ASSERT_EQ(s->Peek(100, buf), 7); | 
|  | ASSERT_EQ(buf[0], 'f'); | 
|  | ASSERT_EQ(buf[1], 'g'); | 
|  | ASSERT_EQ(buf[2], 'h'); | 
|  | ASSERT_EQ(buf[3], 'i'); | 
|  | ASSERT_EQ(buf[4], 'j'); | 
|  | ASSERT_EQ(buf[5], 'k'); | 
|  | ASSERT_EQ(buf[6], 'l'); | 
|  |  | 
|  | // Last read character is "k". | 
|  | // Peek(2) and Peek(3) will need to read from last page. | 
|  | for (int c = s->Get(); c != 'k'; c = s->Get()) { | 
|  | } | 
|  | ASSERT_EQ(s->Peek(1, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'l'); | 
|  | ASSERT_EQ(s->Peek(100, buf), 4); | 
|  | ASSERT_EQ(buf[0], 'l'); | 
|  | ASSERT_EQ(buf[1], 'm'); | 
|  | ASSERT_EQ(buf[2], 'n'); | 
|  | ASSERT_EQ(buf[3], 'o'); | 
|  |  | 
|  | // Move near the end of the last page. | 
|  | for (int c = s->Get(); c != 'm'; c = s->Get()) { | 
|  | } | 
|  | ASSERT_EQ(s->Peek(1, buf), 1); | 
|  | ASSERT_EQ(buf[0], 'n'); | 
|  | ASSERT_EQ(s->Peek(100, buf), 2); | 
|  | ASSERT_EQ(buf[0], 'n'); | 
|  | ASSERT_EQ(buf[1], 'o'); | 
|  | } | 
|  |  | 
|  | }  // namespace |