// Copyright 2016 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. #include "src/tools/singlejar/combiners.h" #include "src/tools/singlejar/input_jar.h" #include "src/tools/singlejar/zip_headers.h" #include "src/tools/singlejar/zlib_interface.h" #include "googletest/include/gtest/gtest.h" namespace { static const char kTag1Contents[] = "Contents1"; static const char kTag2Contents[] = "Contents2"; static const char kCombinedXmlContents[] = "\nContents1Contents2\n"; static const char kConcatenatedContents[] = "Contents1\nContents2"; const uint8_t kPoison = 0xFA; // A test fixture is used because test case setup is needed. class CombinersTest : public ::testing::Test { protected: static void SetUpTestCase() { ASSERT_EQ(0, chdir(getenv("TEST_TMPDIR"))); ASSERT_TRUE(CreateFile("tag1.xml", kTag1Contents)); ASSERT_TRUE(CreateFile("tag2.xml", kTag2Contents)); ASSERT_EQ(0, system("zip -qm combiners.zip tag1.xml tag2.xml")); } static void TearDownTestCase() { system("rm -f xmls.zip"); } static bool CreateFile(const char *filename, const char *contents) { FILE *fp = fopen(filename, "wb"); size_t contents_size = strlen(contents); if (fp == nullptr || fwrite(contents, contents_size, 1, fp) != 1 || fclose(fp)) { perror(filename); return false; } return true; } }; // Test Concatenator. TEST_F(CombinersTest, ConcatenatorSmall) { InputJar input_jar; Concatenator concatenator("concat"); ASSERT_TRUE(input_jar.Open("combiners.zip")); const LH *lh; const CDH *cdh; while ((cdh = input_jar.NextEntry(&lh))) { if (cdh->file_name_is("tag1.xml") || cdh->file_name_is("tag2.xml")) { ASSERT_TRUE(concatenator.Merge(cdh, lh)); } } // Create output, verify Local Header contents. LH *entry = reinterpret_cast(concatenator.OutputEntry(true)); EXPECT_TRUE(entry->is()); EXPECT_EQ(20, entry->version()); EXPECT_EQ(Z_DEFLATED, entry->compression_method()); uint64_t original_size = entry->uncompressed_file_size(); uint64_t compressed_size = entry->compressed_file_size(); EXPECT_EQ(strlen(kConcatenatedContents), original_size); EXPECT_LE(compressed_size, original_size); EXPECT_TRUE(entry->file_name_is("concat")); EXPECT_EQ(0, entry->extra_fields_length()); // Decompress and check contents. Inflater inflater; inflater.DataToInflate(entry->data(), compressed_size); uint8_t buffer[256]; memset(buffer, kPoison, sizeof(buffer)); ASSERT_EQ(Z_STREAM_END, inflater.Inflate((buffer), sizeof(buffer))); EXPECT_EQ(kPoison, buffer[original_size]); EXPECT_EQ(kConcatenatedContents, std::string(reinterpret_cast(buffer), original_size)); free(reinterpret_cast(entry)); // And if we just copy instead of compress: entry = reinterpret_cast(concatenator.OutputEntry(false)); EXPECT_TRUE(entry->is()); EXPECT_EQ(20, entry->version()); EXPECT_EQ(Z_NO_COMPRESSION, entry->compression_method()); original_size = entry->uncompressed_file_size(); compressed_size = entry->compressed_file_size(); EXPECT_EQ(compressed_size, original_size); EXPECT_EQ( kConcatenatedContents, std::string(reinterpret_cast(entry->data()), original_size)); EXPECT_TRUE(entry->file_name_is("concat")); EXPECT_EQ(0, entry->extra_fields_length()); free(reinterpret_cast(entry)); } // Tests that Concatenator creates huge (>4GB original/compressed sizes) // correctly. This test is slow. TEST_F(CombinersTest, ConcatenatorHuge) { Concatenator concatenator("huge"); // Append 5,000,000,000 bytes to the concatenator. const int kBufSize = 1000000; char *buf = reinterpret_cast(malloc(kBufSize)); memset(buf, kPoison, kBufSize); for (int i = 0; i < 5000; ++i) { concatenator.Append(buf, kBufSize); } free(buf); // Now hope that we have enough memory :-) LH *entry = reinterpret_cast(concatenator.OutputEntry(true)); ASSERT_NE(nullptr, entry); ASSERT_TRUE(entry->is()); ASSERT_EQ(20, entry->version()); EXPECT_EQ(Z_DEFLATED, entry->compression_method()); uint64_t original_size = entry->uncompressed_file_size(); uint64_t compressed_size = entry->compressed_file_size(); ASSERT_EQ(5000000000, original_size); ASSERT_LE(compressed_size, original_size); free(reinterpret_cast(entry)); } // Test NullCombiner. TEST_F(CombinersTest, NullCombiner) { NullCombiner null_combiner; ASSERT_TRUE(null_combiner.Merge(nullptr, nullptr)); ASSERT_EQ(nullptr, null_combiner.OutputEntry(true)); ASSERT_EQ(nullptr, null_combiner.OutputEntry(false)); } // Test XmlCombiner. TEST_F(CombinersTest, XmlCombiner) { InputJar input_jar; XmlCombiner xml_combiner("combined.xml", "toplevel"); XmlCombiner xml_combiner2("combined2.xml", "toplevel"); ASSERT_TRUE(input_jar.Open("combiners.zip")); const LH *lh; const CDH *cdh; while ((cdh = input_jar.NextEntry(&lh))) { if (cdh->file_name_is("tag1.xml") || cdh->file_name_is("tag2.xml")) { ASSERT_TRUE(xml_combiner.Merge(cdh, lh)); ASSERT_TRUE(xml_combiner2.Merge(cdh, lh)); } } // Create output, verify Local Header contents. LH *entry = reinterpret_cast(xml_combiner.OutputEntry(true)); EXPECT_TRUE(entry->is()); EXPECT_EQ(20, entry->version()); EXPECT_EQ(Z_DEFLATED, entry->compression_method()); uint64_t original_size = entry->uncompressed_file_size(); uint64_t compressed_size = entry->compressed_file_size(); EXPECT_EQ(strlen(kCombinedXmlContents), original_size); EXPECT_LE(compressed_size, original_size); EXPECT_TRUE(entry->file_name_is("combined.xml")); EXPECT_EQ(0, entry->extra_fields_length()); // Decompress and check contents. Inflater inflater; inflater.DataToInflate(entry->data(), compressed_size); uint8_t buffer[256]; memset(buffer, kPoison, sizeof(buffer)); ASSERT_EQ(Z_STREAM_END, inflater.Inflate((buffer), sizeof(buffer))); EXPECT_EQ(kPoison, buffer[original_size]); EXPECT_EQ(kCombinedXmlContents, std::string(reinterpret_cast(buffer), original_size)); free(reinterpret_cast(entry)); // And for the combiner that just copies out: entry = reinterpret_cast(xml_combiner2.OutputEntry(false)); EXPECT_TRUE(entry->is()); EXPECT_EQ(20, entry->version()); EXPECT_EQ(Z_NO_COMPRESSION, entry->compression_method()); original_size = entry->uncompressed_file_size(); compressed_size = entry->compressed_file_size(); EXPECT_EQ(compressed_size, original_size); EXPECT_EQ( kCombinedXmlContents, std::string(reinterpret_cast(entry->data()), original_size)); EXPECT_TRUE(entry->file_name_is("combined2.xml")); EXPECT_EQ(0, entry->extra_fields_length()); free(reinterpret_cast(entry)); } // Test PropertyCombiner. TEST_F(CombinersTest, PropertyCombiner) { static char kProperties[] = "name=value\n" "name_str=value_str\n"; PropertyCombiner property_combiner("properties"); property_combiner.AddProperty("name", "value"); property_combiner.AddProperty(std::string("name_str"), std::string("value_str")); // Merge should not be called. ASSERT_FALSE(property_combiner.Merge(nullptr, nullptr)); // Create output, verify Local Header contents. LH *entry = reinterpret_cast(property_combiner.OutputEntry(true)); EXPECT_TRUE(entry->is()); EXPECT_EQ(20, entry->version()); EXPECT_EQ(Z_DEFLATED, entry->compression_method()); uint64_t original_size = entry->uncompressed_file_size(); uint64_t compressed_size = entry->compressed_file_size(); EXPECT_EQ(strlen(kProperties), original_size); EXPECT_LE(compressed_size, original_size); EXPECT_EQ("properties", entry->file_name_string()); EXPECT_EQ(0, entry->extra_fields_length()); // Decompress and check contents. Inflater inflater; inflater.DataToInflate(entry->data(), compressed_size); uint8_t buffer[256]; memset(buffer, kPoison, sizeof(buffer)); ASSERT_EQ(Z_STREAM_END, inflater.Inflate((buffer), sizeof(buffer))); EXPECT_EQ(kPoison, buffer[original_size]); EXPECT_EQ(kProperties, std::string(reinterpret_cast(buffer), original_size)); free(reinterpret_cast(entry)); // Create output, verify Local Header contents. entry = reinterpret_cast(property_combiner.OutputEntry(false)); EXPECT_TRUE(entry->is()); EXPECT_EQ(20, entry->version()); EXPECT_EQ(Z_NO_COMPRESSION, entry->compression_method()); original_size = entry->uncompressed_file_size(); compressed_size = entry->compressed_file_size(); EXPECT_EQ(compressed_size, original_size); EXPECT_EQ( kProperties, std::string(reinterpret_cast(entry->data()), original_size)); EXPECT_EQ("properties", entry->file_name_string()); EXPECT_EQ(0, entry->extra_fields_length()); free(reinterpret_cast(entry)); } } // anonymous namespace