1 /*
2 * Copyright (C) 2022 The Android Open Source Project
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 "test/gtest_and_gmock.h"
18
19 #include "src/trace_processor/util/gzip_utils.h"
20
21 #include <zconf.h>
22 #include <zlib.h>
23 #include <cstddef>
24 #include <cstdint>
25 #include <fstream>
26 #include <iostream>
27 #include <memory>
28 #include <sstream>
29 #include <string>
30 #include "perfetto/base/logging.h"
31
32 using std::string;
33
34 namespace perfetto::trace_processor::util {
35
TrivialGzipCompress(const std::string & input)36 static std::string TrivialGzipCompress(const std::string& input) {
37 constexpr auto buffer_len = 10000;
38 std::unique_ptr<char[]> output_ptr(new char[buffer_len]);
39 char* output = output_ptr.get();
40 z_stream defstream;
41 defstream.zalloc = Z_NULL;
42 defstream.zfree = Z_NULL;
43 defstream.opaque = Z_NULL;
44 defstream.avail_in = uint32_t(input.size());
45 defstream.next_in =
46 const_cast<Bytef*>(reinterpret_cast<const Bytef*>(input.data()));
47 defstream.avail_out = buffer_len;
48 defstream.next_out = reinterpret_cast<Bytef*>(output);
49 deflateInit(&defstream, Z_BEST_COMPRESSION); // GZip decompress
50 deflate(&defstream, Z_FINISH);
51 deflateEnd(&defstream);
52 PERFETTO_CHECK(defstream.avail_out > 0);
53 return {output, buffer_len - defstream.avail_out};
54 }
55
56 // Trivially decompress using ZlibOnlineDecompress.
57 // It's called 'trivial' because we are feeding the entire input in one shot.
TrivialDecompress(const std::string & input)58 static std::string TrivialDecompress(const std::string& input) {
59 string output;
60 GzipDecompressor decompressor;
61 decompressor.FeedAndExtract(
62 reinterpret_cast<const uint8_t*>(input.data()), uint32_t(input.size()),
63 [&](const uint8_t* data, size_t len) {
64 output.append(reinterpret_cast<const char*>(data), len);
65 });
66 return output;
67 }
68
69 // Decompress a large GZip file using a in-memory buffer of 4KB, and write the
70 // decompressed output in another file.
DecompressGzipFileInFileOut(const std::string & input_file,const std::string & output_file)71 static void DecompressGzipFileInFileOut(const std::string& input_file,
72 const std::string& output_file) {
73 std::ofstream output(output_file.c_str(), std::ios::out | std::ios::binary);
74 std::ifstream input(input_file.c_str(), std::ios::binary);
75 GzipDecompressor decompressor;
76 constexpr uint32_t buffer_sizeof = 4096;
77 char buffer[buffer_sizeof];
78 while (!input.eof()) {
79 input.read(buffer, buffer_sizeof);
80 decompressor.FeedAndExtract(
81 reinterpret_cast<const uint8_t*>(buffer), size_t(input.gcount()),
82 [&](const uint8_t* data, size_t len) {
83 output.write(reinterpret_cast<const char*>(data),
84 std::streamsize(len));
85 });
86 }
87 EXPECT_FALSE(input.bad());
88 }
89
TEST(GzipDecompressor,Basic)90 TEST(GzipDecompressor, Basic) {
91 string input = "Abc..Def..Ghi";
92 string compressed = TrivialGzipCompress(input);
93 EXPECT_EQ(21u, compressed.size());
94 string decompressed = TrivialDecompress(compressed);
95 EXPECT_EQ(input, decompressed);
96 }
97
TEST(GzipDecompressor,Streaming)98 TEST(GzipDecompressor, Streaming) {
99 string input = "Abc..Def..Ghi";
100 string compressed = TrivialGzipCompress(input);
101 string decompressed;
102 auto consumer = [&](const uint8_t* data, size_t len) {
103 decompressed.append(reinterpret_cast<const char*>(data), len);
104 };
105 GzipDecompressor decompressor;
106 const auto* compressed_u8 =
107 reinterpret_cast<const uint8_t*>(compressed.data());
108 ASSERT_GT(compressed.size(), 17u);
109 decompressor.FeedAndExtract(compressed_u8, 7, consumer);
110 decompressor.FeedAndExtract(compressed_u8 + 7, 10, consumer);
111 decompressor.FeedAndExtract(compressed_u8 + 17, compressed.size() - 17,
112 consumer);
113
114 EXPECT_EQ(input, decompressed);
115 }
116
ReadFile(const std::string & file_name)117 static std::string ReadFile(const std::string& file_name) {
118 std::ifstream fd(file_name, std::ios::binary);
119 std::stringstream buffer;
120 buffer << fd.rdbuf();
121 fd.close();
122 return buffer.str();
123 }
124
WriteFile(const std::string & file_name,const std::string & content)125 static void WriteFile(const std::string& file_name,
126 const std::string& content) {
127 std::ofstream fd(file_name, std::ios::out | std::ios::binary);
128 fd.write(content.data(), std::streamsize(content.size()));
129 fd.close();
130 }
131
TEST(GzipDecompressor,DISABLED_FileInFileOut)132 TEST(GzipDecompressor, DISABLED_FileInFileOut) {
133 auto big_string = []() {
134 std::string output;
135 for (int i = 0; i < 1000; i++) {
136 output += "Abc..Def..Ghi."; // len = 14
137 }
138 return output;
139 }();
140 constexpr auto gz_file = "/tmp/abc.gz";
141 constexpr auto txt_file = "/tmp/abc.txt";
142 EXPECT_EQ(size_t(1000 * 14), big_string.size());
143 WriteFile(gz_file, TrivialGzipCompress(big_string));
144 DecompressGzipFileInFileOut(gz_file, txt_file);
145 EXPECT_TRUE(ReadFile(txt_file) == big_string);
146 }
147
148 } // namespace perfetto::trace_processor::util
149