kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/cxx/common/kzip_writer_test.cc (about) 1 /* 2 * Copyright 2018 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "kythe/cxx/common/kzip_writer.h" 18 19 #include <zip.h> 20 21 #include <cstdlib> 22 #include <string> 23 #include <unordered_map> 24 #include <unordered_set> 25 #include <vector> 26 27 #include "absl/log/log.h" 28 #include "absl/status/status.h" 29 #include "absl/status/statusor.h" 30 #include "absl/strings/str_cat.h" 31 #include "absl/strings/str_replace.h" 32 #include "absl/strings/string_view.h" 33 #include "absl/strings/strip.h" 34 #include "gmock/gmock.h" 35 #include "gtest/gtest.h" 36 #include "kythe/cxx/common/index_reader.h" 37 #include "kythe/cxx/common/index_writer.h" 38 #include "kythe/cxx/common/kzip_encoding.h" 39 #include "kythe/cxx/common/kzip_reader.h" 40 #include "kythe/cxx/common/libzip/error.h" 41 #include "kythe/cxx/common/testutil.h" 42 #include "kythe/proto/go.pb.h" 43 44 namespace kythe { 45 namespace { 46 using ::testing::ElementsAre; 47 using ::testing::Values; 48 49 absl::string_view TestTmpdir() { 50 return absl::StripSuffix(std::getenv("TEST_TMPDIR"), "/"); 51 } 52 53 std::string TestFile(absl::string_view basename) { 54 return absl::StrCat(TestSourceRoot(), "kythe/testdata/platform/", 55 absl::StripPrefix(basename, "/")); 56 } 57 58 template <typename T> 59 struct WithStatusFn { 60 bool operator()(absl::string_view digest) { 61 *status = function(digest); 62 return status->ok(); 63 } 64 65 absl::Status* status; 66 T function; 67 }; 68 69 template <typename T> 70 WithStatusFn<T> WithStatus(absl::Status* status, T function) { 71 return WithStatusFn<T>{status, std::move(function)}; 72 } 73 74 std::string TestOutputFile(absl::string_view basename) { 75 const auto* test_info = testing::UnitTest::GetInstance()->current_test_info(); 76 const auto filename = 77 absl::StrReplaceAll(absl::StrCat(test_info->test_case_name(), "_", 78 test_info->name(), "_", basename), 79 {{"/", "-"}}); 80 return absl::StrCat(TestTmpdir(), "/", filename); 81 } 82 83 absl::StatusOr<std::unordered_map<std::string, std::unordered_set<std::string>>> 84 CopyIndex(IndexReader* reader, IndexWriter* writer) { 85 absl::Status error; 86 std::unordered_map<std::string, std::unordered_set<std::string>> digests; 87 absl::Status scan = 88 reader->Scan(WithStatus(&error, [&](absl::string_view digest) { 89 auto unit = reader->ReadUnit(digest); 90 if (!unit.ok()) { 91 return unit.status(); 92 } 93 auto written_digest = writer->WriteUnit(*unit); 94 if (!written_digest.ok()) { 95 return written_digest.status(); 96 } 97 for (const auto& file : unit->unit().required_input()) { 98 auto data = reader->ReadFile(file.info().digest()); 99 if (!data.ok()) { 100 return data.status(); 101 } 102 auto written_file = writer->WriteFile(*data); 103 if (!written_file.ok()) { 104 return written_file.status(); 105 } 106 digests[*written_digest].insert(*written_file); 107 } 108 return absl::OkStatus(); 109 })); 110 if (!scan.ok()) { 111 return scan; 112 } 113 if (!error.ok()) { 114 return error; 115 } 116 return digests; 117 } 118 119 absl::StatusOr<std::unordered_map<std::string, std::unordered_set<std::string>>> 120 ReadDigests(IndexReader* reader) { 121 absl::Status error; 122 std::unordered_map<std::string, std::unordered_set<std::string>> digests; 123 absl::Status scan = 124 reader->Scan(WithStatus(&error, [&](absl::string_view digest) { 125 auto unit = reader->ReadUnit(digest); 126 if (!unit.ok()) { 127 return unit.status(); 128 } 129 for (const auto& file : unit->unit().required_input()) { 130 digests[std::string(digest)].insert(file.info().digest()); 131 } 132 return absl::OkStatus(); 133 })); 134 if (!scan.ok()) { 135 return scan; 136 } 137 if (!error.ok()) { 138 return error; 139 } 140 return digests; 141 } 142 143 class FullKzipWriterTest : public ::testing::TestWithParam<KzipEncoding> {}; 144 145 TEST_P(FullKzipWriterTest, RecapitulatesSimpleKzip) { 146 // This forces the GoDetails proto descriptor to be added to the pool so we 147 // can deserialize it. If we don't do this, we get an error like: 148 // "Invalid type URL, unknown type: kythe.proto.GoDetails for type Any". 149 proto::GoDetails needed_for_proto_deserialization; 150 151 absl::StatusOr<IndexReader> reader = 152 KzipReader::Open(TestFile("stringset.kzip")); 153 ASSERT_TRUE(reader.ok()) << reader.status(); 154 155 std::string output_file = TestOutputFile("stringset.kzip"); 156 LOG(INFO) << output_file; 157 absl::StatusOr<IndexWriter> writer = 158 KzipWriter::Create(output_file, GetParam()); 159 ASSERT_TRUE(writer.ok()) << writer.status(); 160 auto written_digests = CopyIndex(&*reader, &*writer); 161 ASSERT_TRUE(written_digests.ok()) << written_digests.status(); 162 { 163 auto status = writer->Close(); 164 ASSERT_TRUE(status.ok()) << status; 165 } 166 167 reader = KzipReader::Open(output_file); 168 ASSERT_TRUE(reader.ok()) << reader.status(); 169 auto read_digests = ReadDigests(&*reader); 170 ASSERT_TRUE(read_digests.ok()) << read_digests.status(); 171 EXPECT_EQ(*written_digests, *read_digests); 172 } 173 174 TEST(KzipWriterTest, IncludesDirectoryEntries) { 175 std::string dummy_file = TestOutputFile("dummy.kzip"); 176 absl::StatusOr<IndexWriter> writer = KzipWriter::Create(dummy_file); 177 ASSERT_TRUE(writer.ok()) << writer.status(); 178 { 179 auto digest = writer->WriteFile("contents"); 180 ASSERT_TRUE(digest.ok()) << digest.status(); 181 } 182 { 183 auto status = writer->Close(); 184 ASSERT_TRUE(status.ok()) << status; 185 } 186 187 std::vector<std::string> contents; 188 { 189 auto* archive = zip_open(dummy_file.c_str(), ZIP_RDONLY, nullptr); 190 ASSERT_NE(archive, nullptr); 191 struct Closer { 192 ~Closer() { zip_discard(a); } 193 zip_t* a; 194 } closer{archive}; 195 for (int i = 0; i < zip_get_num_entries(archive, 0); ++i) { 196 const char* name = zip_get_name(archive, i, 0); 197 ASSERT_NE(archive, nullptr) << libzip::ToStatus(zip_get_error(archive)); 198 contents.push_back(name); 199 } 200 } 201 EXPECT_THAT( 202 contents, 203 // Order matters here as "root/" must come first. 204 // We don't really care about the rest of the entries, but it's easy 205 // enough to fix the order of the subdirectories and minimally harmful. 206 ElementsAre( 207 "root/", "root/files/", "root/pbunits/", 208 "root/files/" 209 "d1b2a59fbea7e20077af9f91b27e95e865061b270be03ff539ab3b73587882e8")); 210 } 211 212 TEST(KzipWriterTest, DuplicateFilesAreIgnored) { 213 absl::StatusOr<IndexWriter> writer = 214 KzipWriter::Create(TestOutputFile("dummy.kzip")); 215 ASSERT_TRUE(writer.ok()) << writer.status(); 216 { 217 auto digest = writer->WriteFile("contents"); 218 ASSERT_TRUE(digest.ok()) << digest.status(); 219 } 220 { 221 auto digest = writer->WriteFile("contents"); 222 ASSERT_TRUE(digest.ok()) << digest.status(); 223 } 224 { 225 auto status = writer->Close(); 226 ASSERT_TRUE(status.ok()) << status; 227 } 228 } 229 230 INSTANTIATE_TEST_SUITE_P(AllEncodings, FullKzipWriterTest, 231 Values(KzipEncoding::kJson, KzipEncoding::kProto, 232 KzipEncoding::kAll)); 233 } // namespace 234 } // namespace kythe