|  | /* | 
|  | * Copyright (C) 2022 The Android Open Source Project | 
|  | * | 
|  | * 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 "test/gtest_and_gmock.h" | 
|  |  | 
|  | #include "src/trace_processor/util/gzip_utils.h" | 
|  |  | 
|  | #include <zlib.h> | 
|  | #include <fstream> | 
|  | #include <iostream> | 
|  | #include "perfetto/base/logging.h" | 
|  |  | 
|  | using std::string; | 
|  |  | 
|  | namespace perfetto { | 
|  | namespace trace_processor { | 
|  | namespace util { | 
|  |  | 
|  | static std::string TrivialGzipCompress(const std::string& input) { | 
|  | constexpr auto buffer_len = 10000; | 
|  | std::unique_ptr<char[]> output_ptr(new char[buffer_len]); | 
|  | char* output = output_ptr.get(); | 
|  | z_stream defstream; | 
|  | defstream.zalloc = Z_NULL; | 
|  | defstream.zfree = Z_NULL; | 
|  | defstream.opaque = Z_NULL; | 
|  | defstream.avail_in = uint32_t(input.size()); | 
|  | defstream.next_in = | 
|  | const_cast<Bytef*>(reinterpret_cast<const Bytef*>(input.data())); | 
|  | defstream.avail_out = buffer_len; | 
|  | defstream.next_out = reinterpret_cast<Bytef*>(output); | 
|  | deflateInit(&defstream, Z_BEST_COMPRESSION);  // GZip decompress | 
|  | deflate(&defstream, Z_FINISH); | 
|  | deflateEnd(&defstream); | 
|  | PERFETTO_CHECK(defstream.avail_out > 0); | 
|  | return std::string(output, buffer_len - defstream.avail_out); | 
|  | } | 
|  |  | 
|  | // Trivially decompress using ZlibOnlineDecompress. | 
|  | // It's called 'trivial' because we are feeding the entire input in one shot. | 
|  | static std::string TrivialDecompress(const std::string& input) { | 
|  | string output; | 
|  | GzipDecompressor decompressor; | 
|  | decompressor.FeedAndExtract( | 
|  | reinterpret_cast<const uint8_t*>(input.data()), uint32_t(input.size()), | 
|  | [&](const uint8_t* data, size_t len) { | 
|  | output.append(reinterpret_cast<const char*>(data), len); | 
|  | }); | 
|  | return output; | 
|  | } | 
|  |  | 
|  | // Decompress a large GZip file using a in-memory buffer of 4KB, and write the | 
|  | // decompressed output in another file. | 
|  | static void DecompressGzipFileInFileOut(const std::string& input_file, | 
|  | const std::string& output_file) { | 
|  | std::ofstream output(output_file.c_str(), std::ios::out | std::ios::binary); | 
|  | std::ifstream input(input_file.c_str(), std::ios::binary); | 
|  | GzipDecompressor decompressor; | 
|  | constexpr uint32_t buffer_sizeof = 4096; | 
|  | char buffer[buffer_sizeof]; | 
|  | while (!input.eof()) { | 
|  | input.read(buffer, buffer_sizeof); | 
|  | decompressor.FeedAndExtract( | 
|  | reinterpret_cast<const uint8_t*>(buffer), size_t(input.gcount()), | 
|  | [&](const uint8_t* data, size_t len) { | 
|  | output.write(reinterpret_cast<const char*>(data), | 
|  | std::streamsize(len)); | 
|  | }); | 
|  | } | 
|  | EXPECT_FALSE(input.bad()); | 
|  | } | 
|  |  | 
|  | TEST(GzipDecompressor, Basic) { | 
|  | string input = "Abc..Def..Ghi"; | 
|  | string compressed = TrivialGzipCompress(input); | 
|  | EXPECT_EQ(21u, compressed.size()); | 
|  | string decompressed = TrivialDecompress(compressed); | 
|  | EXPECT_EQ(input, decompressed); | 
|  | } | 
|  |  | 
|  | TEST(GzipDecompressor, Streaming) { | 
|  | string input = "Abc..Def..Ghi"; | 
|  | string compressed = TrivialGzipCompress(input); | 
|  | string decompressed; | 
|  | auto consumer = [&](const uint8_t* data, size_t len) { | 
|  | decompressed.append(reinterpret_cast<const char*>(data), len); | 
|  | }; | 
|  | GzipDecompressor decompressor; | 
|  | auto compressed_u8 = reinterpret_cast<const uint8_t*>(compressed.data()); | 
|  | ASSERT_GT(compressed.size(), 17u); | 
|  | decompressor.FeedAndExtract(compressed_u8, 7, consumer); | 
|  | decompressor.FeedAndExtract(compressed_u8 + 7, 10, consumer); | 
|  | decompressor.FeedAndExtract(compressed_u8 + 17, compressed.size() - 17, | 
|  | consumer); | 
|  |  | 
|  | EXPECT_EQ(input, decompressed); | 
|  | } | 
|  |  | 
|  | static std::string ReadFile(const std::string& file_name) { | 
|  | std::ifstream fd(file_name, std::ios::binary); | 
|  | std::stringstream buffer; | 
|  | buffer << fd.rdbuf(); | 
|  | fd.close(); | 
|  | return buffer.str(); | 
|  | } | 
|  |  | 
|  | static void WriteFile(const std::string& file_name, | 
|  | const std::string& content) { | 
|  | std::ofstream fd(file_name, std::ios::out | std::ios::binary); | 
|  | fd.write(content.data(), std::streamsize(content.size())); | 
|  | fd.close(); | 
|  | } | 
|  |  | 
|  | TEST(GzipDecompressor, DISABLED_FileInFileOut) { | 
|  | auto big_string = []() { | 
|  | std::string output; | 
|  | for (int i = 0; i < 1000; i++) { | 
|  | output += "Abc..Def..Ghi.";  // len = 14 | 
|  | } | 
|  | return output; | 
|  | }(); | 
|  | constexpr auto gz_file = "/tmp/abc.gz"; | 
|  | constexpr auto txt_file = "/tmp/abc.txt"; | 
|  | EXPECT_EQ(size_t(1000 * 14), big_string.size()); | 
|  | WriteFile(gz_file, TrivialGzipCompress(big_string)); | 
|  | DecompressGzipFileInFileOut(gz_file, txt_file); | 
|  | EXPECT_TRUE(ReadFile(txt_file) == big_string); | 
|  | } | 
|  |  | 
|  | }  // namespace util | 
|  | }  // namespace trace_processor | 
|  | }  // namespace perfetto |