1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <stdlib.h>
6
7 #include <algorithm>
8 #include <memory>
9
10 #include <blobfs/lz4.h>
11 #include <unittest/unittest.h>
12
13 namespace blobfs {
14 namespace {
15
16 // Tests the API of using an unset Compressor.
NullCompressor()17 bool NullCompressor() {
18 BEGIN_TEST;
19
20 Compressor compressor;
21 EXPECT_FALSE(compressor.Compressing());
22 EXPECT_EQ(ZX_ERR_BUFFER_TOO_SMALL, compressor.Initialize(nullptr, 0));
23
24 END_TEST;
25 }
26
GenerateInput(unsigned seed,size_t size)27 std::unique_ptr<char[]> GenerateInput(unsigned seed, size_t size) {
28 std::unique_ptr<char[]> input(new char[size]);
29 for (size_t i = 0; i < size; i++) {
30 input[i] = static_cast<char>(rand_r(&seed));
31 }
32 return input;
33 }
34
CompressionHelper(Compressor * compressor,const char * input,size_t size,size_t step,std::unique_ptr<char[]> * out_compressed)35 bool CompressionHelper(Compressor* compressor, const char* input, size_t size, size_t step,
36 std::unique_ptr<char[]>* out_compressed) {
37 BEGIN_HELPER;
38
39 size_t max_output = Compressor::BufferMax(size);
40 std::unique_ptr<char[]> compressed(new char[max_output]);
41 ASSERT_EQ(ZX_OK, compressor->Initialize(compressed.get(), max_output));
42 EXPECT_TRUE(compressor->Compressing());
43
44 size_t offset = 0;
45 while (offset != size) {
46 const void* data = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(input) + offset);
47 const size_t incremental_size = std::min(step, size - offset);
48 ASSERT_EQ(ZX_OK, compressor->Update(data, incremental_size));
49 offset += incremental_size;
50 }
51 ASSERT_EQ(ZX_OK, compressor->End());
52 EXPECT_GT(compressor->Size(), 0);
53
54 *out_compressed = std::move(compressed);
55
56 END_HELPER;
57 }
58
DecompressionHelper(const char * compressed,size_t compressed_size,const char * expected,size_t expected_size)59 bool DecompressionHelper(const char* compressed, size_t compressed_size,
60 const char* expected, size_t expected_size) {
61 BEGIN_HELPER;
62 std::unique_ptr<char[]> output(new char[expected_size]);
63 size_t target_size = expected_size;
64 size_t src_size = compressed_size;
65 ASSERT_EQ(ZX_OK, Decompressor::Decompress(output.get(), &target_size, compressed, &src_size));
66 EXPECT_EQ(expected_size, target_size);
67 EXPECT_EQ(compressed_size, src_size);
68 EXPECT_EQ(0, memcmp(expected, output.get(), expected_size));
69
70 END_HELPER;
71 }
72
73 // Tests a contained case of compression and decompression.
74 //
75 // kSize: The Size of the input buffer.
76 // kStep: The step size of updating the compression buffer.
77 template <size_t kSize, size_t kStep>
CompressDecompressRandom()78 bool CompressDecompressRandom() {
79 BEGIN_TEST;
80
81 static_assert(kStep <= kSize, "Step size too large");
82
83 // Generate input.
84 std::unique_ptr<char[]> input(GenerateInput(0, kSize));
85
86 // Compress a buffer.
87 Compressor compressor;
88 std::unique_ptr<char[]> compressed;
89 ASSERT_TRUE(CompressionHelper(&compressor, input.get(), kSize, kStep, &compressed));
90
91 // Decompress the buffer.
92 ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), kSize));
93
94 END_TEST;
95 }
96
CompressDecompressReset()97 bool CompressDecompressReset() {
98 BEGIN_TEST;
99
100 Compressor compressor;
101
102 size_t step = 128;
103 size_t input_size = 1024;
104 std::unique_ptr<char[]> input(GenerateInput(0, input_size));
105 std::unique_ptr<char[]> compressed;
106 ASSERT_TRUE(CompressionHelper(&compressor, input.get(), input_size, step, &compressed));
107 ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), input_size));
108
109 // We should be able to re-use a buffer of the same size.
110 compressor.Reset();
111 ASSERT_TRUE(CompressionHelper(&compressor, input.get(), input_size, step, &compressed));
112 ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), input_size));
113
114 // We should be able to re-use a buffer of a different size (larger).
115 compressor.Reset();
116 input_size = 2048;
117 input = GenerateInput(0, input_size);
118 ASSERT_TRUE(CompressionHelper(&compressor, input.get(), input_size, step, &compressed));
119 ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), input_size));
120
121 // We should be able to re-use a buffer of a different size (smaller).
122 compressor.Reset();
123 input_size = 512;
124 input = GenerateInput(0, input_size);
125 ASSERT_TRUE(CompressionHelper(&compressor, input.get(), input_size, step, &compressed));
126 ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), input_size));
127
128 END_TEST;
129 }
130
UpdateNoData()131 bool UpdateNoData() {
132 BEGIN_TEST;
133
134 Compressor compressor;
135 const size_t input_size = 1024;
136 std::unique_ptr<char[]> input(GenerateInput(0, input_size));
137 const size_t max_output = Compressor::BufferMax(input_size);
138 std::unique_ptr<char[]> compressed(new char[max_output]);
139 ASSERT_EQ(ZX_OK, compressor.Initialize(compressed.get(), max_output));
140
141 // Test that using "Update(data, 0)" acts a no-op, rather than corrupting the buffer.
142 ASSERT_EQ(ZX_OK, compressor.Update(input.get(), 0));
143 ASSERT_EQ(ZX_OK, compressor.Update(input.get(), input_size));
144 ASSERT_EQ(ZX_OK, compressor.End());
145
146 // Ensure that even with the addition of a zero-length buffer, we still decompress
147 // to the expected output.
148 ASSERT_TRUE(DecompressionHelper(compressed.get(), compressor.Size(), input.get(), input_size));
149
150 END_TEST;
151 }
152
153 // Tests Compressor returns an error if we try to compress more data than the buffer can hold.
BufferTooSmall()154 bool BufferTooSmall() {
155 BEGIN_TEST;
156
157 // Pretend we're going to compress only one byte of data.
158 const size_t buf_size = Compressor::BufferMax(1);
159 std::unique_ptr<char[]> buf(new char[buf_size]);
160 Compressor compressor;
161 ASSERT_EQ(ZX_OK, compressor.Initialize(buf.get(), buf_size));
162
163 // Create data that is just too big to fit within this buffer size.
164 size_t data_size = 0;
165 while (Compressor::BufferMax(++data_size) <= buf_size) {}
166 ASSERT_GT(data_size, 0);
167
168 unsigned int seed = 0;
169 std::unique_ptr<char[]> data(new char[data_size]);
170
171 for (size_t i = 0; i < data_size; i++) {
172 data[i] = static_cast<char>(rand_r(&seed));
173 }
174
175 ASSERT_EQ(ZX_ERR_IO_DATA_INTEGRITY, compressor.Update(&data, data_size));
176 END_TEST;
177 }
178
179 } // namespace
180 } // namespace blobfs
181
182 BEGIN_TEST_CASE(blobfsCompressorTests)
183 RUN_TEST(blobfs::NullCompressor)
184 RUN_TEST((blobfs::CompressDecompressRandom<1 << 0, 1 << 0>))
185 RUN_TEST((blobfs::CompressDecompressRandom<1 << 1, 1 << 0>))
186 RUN_TEST((blobfs::CompressDecompressRandom<1 << 10, 1 << 5>))
187 RUN_TEST((blobfs::CompressDecompressRandom<1 << 15, 1 << 10>))
188 RUN_TEST(blobfs::CompressDecompressReset)
189 RUN_TEST(blobfs::UpdateNoData)
190 RUN_TEST(blobfs::BufferTooSmall)
191 END_TEST_CASE(blobfsCompressorTests);
192