Project
Loading...
Searching...
No Matches
test_Decoder.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12#include "AODNative/Decoder.h"
13#include "AODNative/Errors.h"
14#include "RootFraming.h"
15#include <catch_amalgamated.hpp>
16#include <bit>
17
18using namespace o2::aod::native;
19using namespace o2::aod::native::test;
20
21namespace
22{
23// Serializes values big-endian, as ROOT stores them.
24template <typename T>
25std::vector<uint8_t> bigEndian(std::vector<T> const& values)
26{
28 for (auto v : values) {
29 w.put(std::bit_cast<std::conditional_t<sizeof(T) == 4, uint32_t, std::conditional_t<sizeof(T) == 8, uint64_t, std::conditional_t<sizeof(T) == 2, uint16_t, uint8_t>>>>(v));
30 }
31 return w.bytes;
32}
33
35struct BranchBuilder {
36 std::vector<uint8_t> file = std::vector<uint8_t>(100, 0); // some leading bytes, so seeks are non-zero
37 BranchInfo branch;
38
39 BranchBuilder(char leafClass, int fixedLen = 1)
40 {
41 branch.name = "fX";
42 branch.leafClass = leafClass;
43 branch.isUnsigned = false;
44 branch.fixedLen = fixedLen;
45 branch.entries = branch.totBytes = branch.zipBytes = 0;
46 }
47
48 void add(Algo algo, std::vector<uint8_t> const& values, int32_t nEntries, size_t tail = 0, size_t chunkSize = 0xffffff)
49 {
50 auto basket = makeBasket(algo, values, nEntries, tail, chunkSize);
51 branch.baskets.push_back(BasketRef{file.size(), (uint32_t)basket.size(), branch.entries, nEntries, false, {}});
52 file.insert(file.end(), basket.begin(), basket.end());
53 branch.entries += nEntries;
54 }
55
56 void addEmbedded(std::vector<uint8_t> const& values, int32_t nEntries)
57 {
58 branch.baskets.push_back(BasketRef{0, 0, branch.entries, nEntries, true, values});
59 branch.entries += nEntries;
60 }
61};
62} // namespace
63
64TEST_CASE("byteswapInPlace")
65{
66 std::vector<uint8_t> v{1, 2, 3, 4, 5, 6, 7, 8};
67 byteswapInPlace(v.data(), 2, 4);
68 CHECK(v == std::vector<uint8_t>{4, 3, 2, 1, 8, 7, 6, 5});
69 byteswapInPlace(v.data(), 1, 8);
70 CHECK(v == std::vector<uint8_t>{5, 6, 7, 8, 1, 2, 3, 4});
71 byteswapInPlace(v.data(), 4, 2);
72 CHECK(v == std::vector<uint8_t>{6, 5, 8, 7, 2, 1, 4, 3});
73}
74
75TEST_CASE("float values across codecs, uncompressed and embedded baskets")
76{
77 BranchBuilder b{'F'};
78 std::vector<float> expected;
79 int n = 0;
80 for (auto algo : {Algo::ZSTD, Algo::None, Algo::ZLIB, Algo::LZMA, Algo::LZ4}) {
81 std::vector<float> values;
82 for (int i = 0; i < 1000; ++i) {
83 values.push_back(n++ * 0.5f);
84 }
85 // Entry-offset tail after the values must never reach the destination.
86 b.add(algo, bigEndian(values), values.size(), 4004);
87 expected.insert(expected.end(), values.begin(), values.end());
88 }
89 std::vector<float> tail{-1.f, -2.f, -3.f};
90 b.addEmbedded(bigEndian(tail), tail.size());
91 expected.insert(expected.end(), tail.begin(), tail.end());
92
93 for (bool mappable : {true, false}) {
94 for (int threads : {1, 4}) {
95 MemoryByteSource source{b.file, mappable};
96 Options options;
97 options.threads = threads;
98 BranchDecoder decoder{source, options};
99 std::vector<float> out(expected.size(), 42.f);
100 decoder.decodeValues(b.branch, (uint8_t*)out.data(), out.size() * sizeof(float));
101 CHECK(out == expected);
102 }
103 }
104}
105
106TEST_CASE("decoding stops exactly at the value region for every codec")
107{
108 // A poisoned entry-offset tail after the values: nothing past fLast - fKeylen
109 // may reach the destination, which is followed by guard bytes. Sizes span
110 // several zstd blocks and compression chunks.
111 for (auto algo : {Algo::ZSTD, Algo::ZLIB, Algo::LZMA, Algo::LZ4, Algo::None}) {
112 for (size_t chunkSize : {size_t(100000), size_t(0xffffff)}) {
113 INFO("algorithm " << (int)algo << ", chunk size " << chunkSize);
114 BranchBuilder b{'D'};
115 std::vector<double> values(70001);
116 for (size_t i = 0; i < values.size(); ++i) {
117 values[i] = i * 0.25 - 7;
118 }
119 b.add(algo, bigEndian(values), values.size(), 8 * values.size() + 4, chunkSize);
121 BranchDecoder decoder{source, Options{}};
122 std::vector<double> out(values.size() + 16, 1e300);
123 decoder.decodeValues(b.branch, (uint8_t*)out.data(), values.size() * sizeof(double));
124 CHECK(std::equal(values.begin(), values.end(), out.begin()));
125 CHECK(std::all_of(out.begin() + values.size(), out.end(), [](double v) { return v == 1e300; }));
126 }
127 }
128}
129
130TEST_CASE("fixed-size arrays and wrong destination sizes")
131{
132 BranchBuilder b{'S', 3};
133 std::vector<int16_t> values{1, 2, 3, -4, -5, -6};
134 b.add(Algo::ZSTD, bigEndian(values), 2);
136 BranchDecoder decoder{source, Options{}};
137 std::vector<int16_t> out(6);
138 decoder.decodeValues(b.branch, (uint8_t*)out.data(), 12);
139 CHECK(out == values);
140 CHECK_THROWS_AS(decoder.decodeValues(b.branch, (uint8_t*)out.data(), 10), FormatError);
141}
142
143TEST_CASE("booleans are packed across basket and window boundaries")
144{
145 // Basket sizes chosen so that boundaries fall inside bitmap bytes, and one
146 // basket is larger than the 64 KiB window.
147 BranchBuilder b{'O'};
148 std::vector<bool> expected;
149 for (int size : {3, 13, (int)BranchDecoder::kBooleanWindow + 5, 1, 7}) {
150 std::vector<uint8_t> bytes;
151 for (int i = 0; i < size; ++i) {
152 bool v = ((expected.size() * 7) % 3) == 0;
153 expected.push_back(v);
154 // Any non-zero byte is true.
155 bytes.push_back(v ? (i % 2 ? 1 : 0x80) : 0);
156 }
157 b.add(Algo::ZSTD, bytes, size, 8, 30000);
158 }
160 BranchDecoder decoder{source, Options{}};
161 std::vector<uint8_t> bitmap((expected.size() + 7) / 8, 0xFF);
162 decoder.decodeBooleans(b.branch, bitmap.data(), bitmap.size());
163 for (size_t i = 0; i < expected.size(); ++i) {
164 INFO("value " << i);
165 CHECK(((bitmap[i / 8] >> (i % 8)) & 1) == expected[i]);
166 }
167 // Padding bits are zero.
168 for (size_t i = expected.size(); i < bitmap.size() * 8; ++i) {
169 CHECK(((bitmap[i / 8] >> (i % 8)) & 1) == 0);
170 }
171}
172
173TEST_CASE("variable-length arrays")
174{
175 BranchBuilder sizes{'I'};
176 sizes.branch.name = "fY_size";
177 std::vector<int32_t> s1{2, 0, 3}, s2{1};
178 sizes.add(Algo::ZLIB, bigEndian(s1), 3);
179 sizes.add(Algo::None, bigEndian(s2), 1);
180
181 // Values baskets do not need to align with size baskets.
182 BranchBuilder values{'D'};
183 values.branch.name = "fY";
184 values.branch.countBranch = "fY_size";
185 std::vector<double> v1{1.5, 2.5}, v2{3.5, 4.5, 5.5, 6.5};
186 values.add(Algo::LZ4, bigEndian(v1), 2);
187 values.add(Algo::ZSTD, bigEndian(v2), 2);
188
189 std::vector<uint8_t> file = sizes.file;
190 // Place the values baskets after the size baskets.
191 auto shift = file.size() - 100;
192 file.insert(file.end(), values.file.begin() + 100, values.file.end());
193 for (auto& basket : values.branch.baskets) {
194 basket.seek += shift;
195 }
196
198 BranchDecoder decoder{source, Options{}};
199 auto offsets = decoder.decodeOffsets(sizes.branch);
200 CHECK(offsets == std::vector<int32_t>{0, 2, 2, 5, 6});
201 std::vector<double> out(6);
202 decoder.decodeVLAValues(values.branch, offsets, (uint8_t*)out.data(), out.size() * sizeof(double));
203 CHECK(out == std::vector<double>{1.5, 2.5, 3.5, 4.5, 5.5, 6.5});
204}
205
206TEST_CASE("inconsistent baskets raise CorruptData")
207{
208 BranchBuilder b{'I'};
209 std::vector<int32_t> values{1, 2, 3};
210 b.add(Algo::ZSTD, bigEndian(values), 3);
212 BranchDecoder decoder{source, Options{}};
213 std::vector<int32_t> out(3);
214
215 SECTION("entry count in the header differs from the TBranch")
216 {
217 auto branch = b.branch;
218 branch.baskets[0].nEntries = 2;
219 branch.entries = 2;
220 CHECK_THROWS_AS(decoder.decodeValues(branch, (uint8_t*)out.data(), 8), CorruptData);
221 }
222 SECTION("baskets do not cover the entries")
223 {
224 auto branch = b.branch;
225 branch.entries = 4;
226 std::vector<int32_t> big(4);
227 CHECK_THROWS_AS(decoder.decodeValues(branch, (uint8_t*)big.data(), 16), CorruptData);
228 }
229 SECTION("basket size differs from the TBranch")
230 {
231 auto branch = b.branch;
232 branch.baskets[0].nbytes -= 1;
233 CHECK_THROWS_AS(decoder.decodeValues(branch, (uint8_t*)out.data(), 12), CorruptData);
234 }
235 SECTION("basket location past the end of the file")
236 {
237 auto branch = b.branch;
238 branch.baskets[0].seek = b.file.size();
239 MemoryByteSource unmapped{b.file, false};
240 BranchDecoder d2{unmapped, Options{}};
241 CHECK_THROWS_AS(d2.decodeValues(branch, (uint8_t*)out.data(), 12), CorruptData);
242 }
243}
int32_t i
int32_t nEntries
Definition Objects.cxx:42
size_t bytes
static constexpr size_t kBooleanWindow
Size of the boolean window, the only decode-side scratch memory.
Definition Decoder.h:35
A file made of concatenated in-memory buffers.
#define CHECK
GLdouble n
Definition glcorearb.h:1982
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLsizei const GLuint const GLintptr * offsets
Definition glcorearb.h:2595
const GLdouble * v
Definition glcorearb.h:832
GLuint GLsizei const GLuint const GLintptr const GLsizeiptr * sizes
Definition glcorearb.h:2595
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat s1
Definition glcorearb.h:5034
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLenum GLsizei GLsizei GLint * values
Definition glcorearb.h:1576
GLfloat GLfloat v1
Definition glcorearb.h:812
GLubyte GLubyte GLubyte GLubyte w
Definition glcorearb.h:852
GLfloat GLfloat GLfloat v2
Definition glcorearb.h:813
std::vector< uint8_t > makeBasket(Algo algo, std::span< const uint8_t > values, int32_t nevbuf, size_t tailBytes=0, size_t chunkSize=0xffffff)
void byteswapInPlace(uint8_t *data, size_t count, size_t typeSize)
Byteswaps count big-endian values of typeSize bytes in place.
Definition Decoder.cxx:46
int32_t const char * file
std::vector< BasketRef > baskets
Definition Objects.h:46
std::map< std::string, ID > expected
TEST_CASE("byteswapInPlace")