1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #include <boost/asio/buffer.hpp>
4
5 #include <algorithm>
6 #include <climits>
7 #include <cstdint>
8 #include <functional>
9 #include <vector>
10 #ifdef HAVE_ZSTD
11 #include "zstd_compressor.hpp"
12 #include "zstd_decompressor.hpp"
13 #include "zstd_test_arrays.hpp"
14
15 #include <cstddef>
16 #include <optional>
17 #include <random>
18 #include <span>
19
20 #include <gmock/gmock.h>
21 #include <gtest/gtest.h>
22
23 using ::testing::ElementsAreArray;
24
25 namespace bmcweb
26 {
27 namespace
28 {
29
TEST(ZstdCompressor,EmptyFile)30 TEST(ZstdCompressor, EmptyFile)
31 {
32 ZstdCompressor comp;
33 ASSERT_TRUE(comp.init(0U));
34 std::vector<uint8_t> out;
35 bool more = false;
36 std::vector<uint8_t> empty;
37 std::optional<std::span<const uint8_t>> segmentOut =
38 comp.compress(empty, more);
39 ASSERT_TRUE(segmentOut);
40 if (!segmentOut)
41 {
42 return;
43 }
44
45 EXPECT_THAT(*segmentOut, ElementsAreArray(zstd::empty));
46 }
47
TEST(ZstdCompressor,AllZeros)48 TEST(ZstdCompressor, AllZeros)
49 {
50 for (size_t chunkSize : {1U, 2U, 4U, 8U, 1024U, 1048576U})
51 {
52 ZstdCompressor comp;
53 constexpr size_t fileSize = 1048576U;
54 ASSERT_TRUE(comp.init(fileSize));
55 std::vector<uint8_t> out;
56
57 std::vector<uint8_t> zeros(chunkSize, 0x00);
58 for (size_t i = 0; i < fileSize; i += chunkSize)
59 {
60 bool more = i != fileSize - chunkSize;
61 std::optional<std::span<const uint8_t>> segmentOut =
62 comp.compress(zeros, more);
63 ASSERT_TRUE(segmentOut);
64 if (!segmentOut)
65 {
66 return;
67 }
68 out.insert(out.end(), segmentOut->begin(), segmentOut->end());
69 }
70 EXPECT_THAT(out, ElementsAreArray(zstd::zeros));
71 }
72 }
73
TEST(ZstdCompressor,AllOnes)74 TEST(ZstdCompressor, AllOnes)
75 {
76 ZstdCompressor comp;
77 ASSERT_TRUE(comp.init(1048576U));
78 std::vector<uint8_t> out;
79 std::vector<uint8_t> zeros(1024U, 0xFF);
80 for (size_t i = 0; i < 1024U; i++)
81 {
82 bool more = i < 1023U;
83 std::optional<std::span<const uint8_t>> segmentOut =
84 comp.compress(zeros, more);
85 ASSERT_TRUE(segmentOut);
86 if (!segmentOut)
87 {
88 return;
89 }
90 out.insert(out.end(), segmentOut->begin(), segmentOut->end());
91 }
92
93 EXPECT_THAT(out, ElementsAreArray(zstd::ones));
94 }
95
TEST(Zstd,RoundTrip)96 TEST(Zstd, RoundTrip)
97 {
98 using random_bytes_engine =
99 std::independent_bits_engine<std::default_random_engine, CHAR_BIT,
100 unsigned char>;
101
102 // This is a unit test, we WANT reproducible tests
103 // NOLINTNEXTLINE(cert-msc51-cpp, cert-msc32-c)
104 random_bytes_engine rbe;
105 std::vector<unsigned char> data(1048576U);
106 std::ranges::generate(data, std::ref(rbe));
107
108 for (size_t chunkSize : {1U, 2U, 4U, 8U, 1024U, 1048576U})
109 {
110 ZstdCompressor comp;
111 std::vector<uint8_t> compressed;
112 ASSERT_TRUE(comp.init(data.size()));
113 for (size_t i = 0; i < data.size(); i += chunkSize)
114 {
115 bool more = i != data.size() - chunkSize;
116 std::optional<std::span<const uint8_t>> segmentOut =
117 comp.compress(std::span(data).subspan(i, chunkSize), more);
118 ASSERT_TRUE(segmentOut);
119 if (!segmentOut)
120 {
121 return;
122 }
123 compressed.insert(compressed.end(), segmentOut->begin(),
124 segmentOut->end());
125 }
126 ZstdDecompressor decomp;
127
128 std::optional<boost::asio::const_buffer> segmentOut =
129 decomp.decompress(boost::asio::buffer(compressed));
130 ASSERT_TRUE(segmentOut);
131 if (!segmentOut)
132 {
133 continue;
134 }
135 std::span<const uint8_t> decompressedSpan = std::span<const uint8_t>(
136 static_cast<const uint8_t*>(segmentOut->data()),
137 segmentOut->size());
138
139 EXPECT_THAT(decompressedSpan, ElementsAreArray(data));
140 }
141 }
142
143 } // namespace
144 } // namespace bmcweb
145 #endif
146