Project
Loading...
Searching...
No Matches
test_RootTreeWriter.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 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 <catch_amalgamated.hpp>
14#include <iomanip>
15#include "Headers/DataHeader.h"
16#include <fairmq/Message.h>
17#include <fairmq/TransportFactory.h>
20#include "Framework/InputSpan.h"
21#include "Framework/DataRef.h"
25#include "../../Core/test/TestClasses.h"
26#include <vector>
27#include <memory>
28#include <iostream>
29#include <type_traits> // std::is_fundamental
30#include <TClass.h>
31#include <TFile.h>
32#include <TTree.h>
33#include <TBranch.h>
34#include <TSystem.h>
35
36#define CHECK_MESSAGE(cond, msg) \
37 do { \
38 INFO(msg); \
39 CHECK(cond); \
40 } while ((void)0, 0)
41#define REQUIRE_MESSAGE(cond, msg) \
42 do { \
43 INFO(msg); \
44 REQUIRE(cond); \
45 } while ((void)0, 0)
46
47using namespace o2::framework;
49
50namespace o2::test
51{
52
55 int x;
56 int y;
57};
58
59template <typename T>
61 using ref_type = T;
62 const char* branchName;
64};
65
66template <typename T>
67bool checkBranch(TTree& tree, BranchContent<T>&& content)
68{
69 TBranch* branch = tree.GetBranch(content.branchName);
70 REQUIRE(branch != nullptr);
71 T store;
72 T* pointer = &store;
73 // in general, pointer to pointer has to be used for setting the branch
74 // address to a store object, however this does not work for fundamental
75 // types, there the address to the variable has to be used in order
76 // to read back the value. Why? no clue.
77 if (std::is_fundamental<T>::value) {
78 branch->SetAddress(&store);
79 } else {
80 branch->SetAddress(&pointer);
81 }
82 branch->GetEntry(0);
83 CHECK_MESSAGE(store == content.reference, "mismatch for branch " << content.branchName);
84 return store == content.reference;
85}
86
87template <typename T, typename... Args>
88bool checkBranch(TTree& tree, BranchContent<T>&& content, Args&&... args)
89{
90 return checkBranch(tree, std::forward<BranchContent<T>>(content)) && checkBranch(tree, std::forward<Args>(args)...);
91}
92
93template <typename... Args>
94bool checkTree(const char* filename, const char* treename, Args&&... args)
95{
96 TFile* file = TFile::Open(filename);
97 REQUIRE(file != nullptr);
98 auto* tree = reinterpret_cast<TTree*>(file->GetObjectChecked(treename, "TTree"));
99 REQUIRE(tree != nullptr);
100 return checkBranch(*tree, std::forward<Args>(args)...);
101}
102
103TEST_CASE("test_RootTreeWriter")
104{
105 std::string filename = "test_RootTreeWriter.root";
106 const char* treename = "testtree";
107
108 using Container = std::vector<o2::test::Polymorphic>;
109 // setting up the writer with two branch definitions
110 // first definition is for a single input and simple type written to one branch
111 // second branch handles two inputs of the same data type, the mapping of the
112 // input data to the target branch is taken from the sub specification
113 auto getIndex = [](o2::framework::DataRef const& ref) -> size_t {
114 auto const* dataHeader = DataRefUtils::getHeader<o2::header::DataHeader*>(ref);
115 return dataHeader->subSpecification;
116 };
117 auto getName = [](std::string base, size_t i) -> std::string {
118 return base + "_" + std::to_string(i);
119 };
120 auto customClose = [](TFile* file, TTree* tree) {
121 // branches are filled independently of the tree, so the tree state needs to be
122 // synchronized with the branch states
123 tree->SetEntries();
124 INFO("Custom close, tree has " << tree->GetEntries() << " entries");
125 // there was one write cycle and each branch should have one entry
126 CHECK(tree->GetEntries() == 1);
127 tree->Write();
128 file->Close();
129 };
130 RootTreeWriter writer(filename.c_str(), treename, // file and tree name
131 customClose,
132 RootTreeWriter::BranchDef<int>{"input1", "intbranch"},
134 std::vector<std::string>({"input2", "input3"}), "containerbranch",
135 // define two target branches (this matches the input list)
136 2,
137 // the callback extracts the sub specification from the DataHeader as index
138 getIndex,
139 // the branch names are simply built by adding the index
140 getName},
141 RootTreeWriter::BranchDef<const char*>{"input4", "binarybranch"},
143 RootTreeWriter::BranchDef<std::vector<int>>{"input6", "intvecbranch"},
145 // TriviallyCopyable can be sent with either serialization methods NONE or ROOT
147
148 CHECK(writer.getStoreSize() == 7);
149
150 // need to mimic a context to actually call the processing
151 auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
152 std::vector<fair::mq::MessagePtr> store;
153
154 auto createPlainMessage = [&transport, &store](DataHeader&& dh, auto& data) {
155 dh.payloadSize = sizeof(data);
156 dh.payloadSerializationMethod = o2::header::gSerializationMethodNone;
157 DataProcessingHeader dph{0, 1};
158 o2::header::Stack stack{dh, dph};
159 fair::mq::MessagePtr header = transport->CreateMessage(stack.size());
160 fair::mq::MessagePtr payload = transport->CreateMessage(sizeof(data));
161 memcpy(header->GetData(), stack.data(), stack.size());
162 memcpy(payload->GetData(), &data, sizeof(data));
163 store.emplace_back(std::move(header));
164 store.emplace_back(std::move(payload));
165 };
166
167 auto createVectorMessage = [&transport, &store](DataHeader&& dh, auto& data) {
168 dh.payloadSize = data.size() * sizeof(typename std::remove_reference<decltype(data)>::type::value_type);
169 dh.payloadSerializationMethod = o2::header::gSerializationMethodNone;
170 DataProcessingHeader dph{0, 1};
171 o2::header::Stack stack{dh, dph};
172 fair::mq::MessagePtr header = transport->CreateMessage(stack.size());
173 fair::mq::MessagePtr payload = transport->CreateMessage(dh.payloadSize);
174 memcpy(header->GetData(), stack.data(), stack.size());
175 memcpy(payload->GetData(), data.data(), dh.payloadSize);
176 store.emplace_back(std::move(header));
177 store.emplace_back(std::move(payload));
178 };
179
180 auto createSerializedMessage = [&transport, &store](DataHeader&& dh, auto& data) {
181 fair::mq::MessagePtr payload = transport->CreateMessage();
182 payload->Rebuild(4096, {64});
183 auto* cl = TClass::GetClass(typeid(decltype(data)));
184 TMessageSerializer().Serialize(*payload, &data, cl);
185 dh.payloadSize = payload->GetSize();
186 dh.payloadSerializationMethod = o2::header::gSerializationMethodROOT;
187 DataProcessingHeader dph{0, 1};
188 o2::header::Stack stack{dh, dph};
189 fair::mq::MessagePtr header = transport->CreateMessage(stack.size());
190 memcpy(header->GetData(), stack.data(), stack.size());
191 store.emplace_back(std::move(header));
192 store.emplace_back(std::move(payload));
193 };
194
195 int a = 23;
196 Container b{{0}};
197 Container c{{21}};
198 o2::test::TriviallyCopyable msgable{10, 21, 42};
199 std::vector<int> intvec{10, 21, 42};
200 std::vector<o2::test::TriviallyCopyable> trivvec{{10, 21, 42}, {1, 2, 3}};
201 createPlainMessage(o2::header::DataHeader{"INT", "TST", 0}, a);
202 createSerializedMessage(o2::header::DataHeader{"CONTAINER", "TST", 0}, b);
203 createSerializedMessage(o2::header::DataHeader{"CONTAINER", "TST", 1}, c);
204 createPlainMessage(o2::header::DataHeader{"BINARY", "TST", 0}, a);
205 createPlainMessage(o2::header::DataHeader{"MSGABLE", "TST", 0}, msgable);
206 createVectorMessage(o2::header::DataHeader{"FDMTLVEC", "TST", 0}, intvec);
207 createVectorMessage(o2::header::DataHeader{"TRIV_VEC", "TST", 0}, trivvec);
208 createSerializedMessage(o2::header::DataHeader{"SRLZDVEC", "TST", 0}, trivvec);
209
210 // Note: InputRecord works on references to the schema and the message vector
211 // so we can not specify the schema definition directly in the definition of
212 // the InputRecord. Intrestingly enough, the compiler does not complain about
213 // getting reference to temporary rvalue argument. So it might work if the
214 // temporary argument is still in memory
215 // FIXME: check why the compiler does not detect this
216 std::vector<InputRoute> schema = {
217 {InputSpec{"input1", "TST", "INT"}, 0, "input1", 0}, //
218 {InputSpec{"input2", "TST", "CONTAINER"}, 1, "input2", 0}, //
219 {InputSpec{"input3", "TST", "CONTAINER"}, 2, "input3", 0}, //
220 {InputSpec{"input4", "TST", "BINARY"}, 3, "input4", 0}, //
221 {InputSpec{"input5", "TST", "MSGABLE"}, 4, "input5", 0}, //
222 {InputSpec{"input6", "TST", "FDMTLVEC"}, 5, "input6", 0}, //
223 {InputSpec{"input7", "TST", "TRIV_VEC"}, 6, "input7", 0}, //
224 {InputSpec{"input8", "TST", "SRLZDVEC"}, 7, "input8", 0}, //
225 };
226
227 InputSpan span{
228 [](size_t) -> size_t { return 1; },
229 nullptr,
230 [&store](size_t i, DataRefIndices idx) -> DataRef {
231 return DataRef{nullptr, static_cast<char const*>(store[2 * i + idx.headerIdx]->GetData()), static_cast<char const*>(store[2 * i + idx.payloadIdx]->GetData())};
232 },
233 [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; },
234 store.size() / 2};
235 ServiceRegistry registry;
236 InputRecord inputs{
237 schema,
238 span,
239 registry};
240
241 writer(inputs);
242 writer.close();
243
244 checkTree(filename.c_str(), treename,
245 BranchContent<decltype(a)>{"intbranch", a},
246 BranchContent<decltype(b)>{"containerbranch_0", b},
247 BranchContent<decltype(c)>{"containerbranch_1", c},
248 BranchContent<decltype(msgable)>{"msgablebranch", msgable},
249 BranchContent<decltype(intvec)>{"intvecbranch", intvec},
250 BranchContent<decltype(trivvec)>{"trivvecbranch", trivvec},
251 BranchContent<decltype(trivvec)>{"srlzdvecbranch", trivvec});
252}
253
254template <typename T>
256
257TEST_CASE("test_MakeRootTreeWriterSpec")
258{
259 // setup the spec helper and retrieve the spec by calling the operator
260 struct Printer {
261 Printer()
262 {
263 // TODO: to be fully correct we need to check at exit if we have been here instead
264 // of a thumb log message
265 std::cout << "Setting up a spectator" << std::endl;
266 }
267 };
268 auto logger = [printer = std::make_shared<Printer>()](float const&) {
269 };
270 MakeRootTreeWriterSpec("writer-process", //
271 BranchDefinition<int>{InputSpec{"input1", "TST", "INTDATA"}, "intbranch"}, //
272 BranchDefinition<float>{InputSpec{"input2", "TST", "FLOATDATA"}, //
273 "floatbranch", "floatbranchname", //
274 1, logger} //
275 )();
276}
277
278TEST_CASE("test_ThrowOnMissingDictionary")
279{
280 // trying to set up a branch for a collection class without dictionary which must throw
281 const char* filename = "test_RootTreeWriterTrow.root";
282 const char* treename = "testtree";
283 RootTreeWriter writer(nullptr, nullptr, RootTreeWriter::BranchDef<std::vector<TrivialStruct>>{"input1", "vecbranch"});
284 REQUIRE_THROWS(writer.init(filename, treename));
285 // we print this note to explain the error message in the log
286 INFO("Note: This error has been provoked by the configuration, the exception has been handled");
287}
288
289template <typename T>
290using Trait = RootTreeWriter::StructureElementTypeTrait<T>;
291template <typename T>
292using BinaryBranchStoreType = RootTreeWriter::BinaryBranchStoreType<T>;
293TEST_CASE("test_RootTreeWriterSpec_store_types")
294{
297
298 // simple fundamental type
299 // type itself used as store type
300 static_assert(std::is_same<Trait<int>::store_type, int>::value == true);
301
302 // messageable type with or without ROOT dictionary
303 // type itself used as store type
304 static_assert(std::is_same<Trait<TriviallyCopyable>::store_type, TriviallyCopyable>::value == true);
305
306 // non-messageable type with ROOT dictionary
307 // pointer type used as store type
308 static_assert(std::is_same<Trait<Polymorphic>::store_type, Polymorphic*>::value == true);
309
310 // binary branch indicated through const char*
311 // BinaryBranchStoreType is used
312 static_assert(std::is_same<Trait<const char*>::store_type, BinaryBranchStoreType<char>>::value == true);
313
314 // vectors of fundamental types
315 // type itself (the vector) is used
316 static_assert(std::is_same<Trait<std::vector<int>>::store_type, std::vector<int>*>::value == true);
317
318 // vector of messageable type with or without ROOT dictionary
319 // type itself (the vector) is used
320 static_assert(std::is_same<Trait<std::vector<TriviallyCopyable>>::store_type, std::vector<TriviallyCopyable>*>::value == true);
321
322 // vector of non-messageable type with ROOT dictionary
323 // pointer type used as store type
324 static_assert(std::is_same<Trait<std::vector<Polymorphic>>::store_type, std::vector<Polymorphic>*>::value == true);
325}
326
327TEST_CASE("TestCanAssign")
328{
329 using Callback = std::function<bool(int, float)>;
330 auto matching = [](int, float) -> bool {
331 return true;
332 };
333 auto otherReturn = [](int, float) -> int {
334 return 0;
335 };
336 auto otherParam = [](int, int) -> bool {
337 return true;
338 };
339 REQUIRE((can_assign<decltype(matching), Callback>::value == true));
340 REQUIRE((can_assign<decltype(otherReturn), Callback>::value == false));
341 REQUIRE((can_assign<decltype(otherParam), Callback>::value == false));
342}
343} // namespace o2::test
std::string getName(const TDataMember *dm, int index, int size)
std::shared_ptr< arrow::Schema > schema
int32_t i
Configurable generator for RootTreeWriter processor spec.
uint32_t c
Definition RawData.h:2
uint32_t stack
Definition RawData.h:1
A generic writer for ROOT TTrees.
The input API of the Data Processing Layer This class holds the inputs which are valid for processing...
Generate a processor spec for the RootTreeWriter utility.
A generic writer interface for ROOT TTree objects.
#define CHECK
GLenum void ** pointer
Definition glcorearb.h:805
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLboolean * data
Definition glcorearb.h:298
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLint ref
Definition glcorearb.h:291
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
TEST_CASE("test_prepareArguments")
constexpr o2::header::SerializationMethod gSerializationMethodROOT
Definition DataHeader.h:328
constexpr o2::header::SerializationMethod gSerializationMethodNone
Definition DataHeader.h:327
bool checkBranch(TTree &tree, BranchContent< T > &&content)
TEST_CASE("test_RootTreeWriter")
bool checkTree(const char *filename, const char *treename, Args &&... args)
RootTreeWriter::BinaryBranchStoreType< T > BinaryBranchStoreType
RootTreeWriter::StructureElementTypeTrait< T > Trait
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
std::string filename()
BranchDef is used to define the mapping between inputs and branches.
static void Serialize(fair::mq::Message &msg, const TObject *input)
the main header struct
Definition DataHeader.h:620
a move-only header stack with serialized headers This is the flat buffer where all the headers in a m...
Definition Stack.h:33
test struct without ROOT dictionary, used to provoke raising of runtime_error
#define CHECK_MESSAGE(cond, msg)
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))