• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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