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 nullptr,
235 store.size() / 2};
236 ServiceRegistry registry;
237 InputRecord inputs{
238 schema,
239 span,
240 registry};
241
242 writer(inputs);
243 writer.close();
244
245 checkTree(filename.c_str(), treename,
246 BranchContent<decltype(a)>{"intbranch", a},
247 BranchContent<decltype(b)>{"containerbranch_0", b},
248 BranchContent<decltype(c)>{"containerbranch_1", c},
249 BranchContent<decltype(msgable)>{"msgablebranch", msgable},
250 BranchContent<decltype(intvec)>{"intvecbranch", intvec},
251 BranchContent<decltype(trivvec)>{"trivvecbranch", trivvec},
252 BranchContent<decltype(trivvec)>{"srlzdvecbranch", trivvec});
253}
254
255template <typename T>
257
258TEST_CASE("test_MakeRootTreeWriterSpec")
259{
260 // setup the spec helper and retrieve the spec by calling the operator
261 struct Printer {
262 Printer()
263 {
264 // TODO: to be fully correct we need to check at exit if we have been here instead
265 // of a thumb log message
266 std::cout << "Setting up a spectator" << std::endl;
267 }
268 };
269 auto logger = [printer = std::make_shared<Printer>()](float const&) {
270 };
271 MakeRootTreeWriterSpec("writer-process", //
272 BranchDefinition<int>{InputSpec{"input1", "TST", "INTDATA"}, "intbranch"}, //
273 BranchDefinition<float>{InputSpec{"input2", "TST", "FLOATDATA"}, //
274 "floatbranch", "floatbranchname", //
275 1, logger} //
276 )();
277}
278
279TEST_CASE("test_ThrowOnMissingDictionary")
280{
281 // trying to set up a branch for a collection class without dictionary which must throw
282 const char* filename = "test_RootTreeWriterTrow.root";
283 const char* treename = "testtree";
284 RootTreeWriter writer(nullptr, nullptr, RootTreeWriter::BranchDef<std::vector<TrivialStruct>>{"input1", "vecbranch"});
285 REQUIRE_THROWS(writer.init(filename, treename));
286 // we print this note to explain the error message in the log
287 INFO("Note: This error has been provoked by the configuration, the exception has been handled");
288}
289
290template <typename T>
291using Trait = RootTreeWriter::StructureElementTypeTrait<T>;
292template <typename T>
293using BinaryBranchStoreType = RootTreeWriter::BinaryBranchStoreType<T>;
294TEST_CASE("test_RootTreeWriterSpec_store_types")
295{
298
299 // simple fundamental type
300 // type itself used as store type
301 static_assert(std::is_same<Trait<int>::store_type, int>::value == true);
302
303 // messageable type with or without ROOT dictionary
304 // type itself used as store type
305 static_assert(std::is_same<Trait<TriviallyCopyable>::store_type, TriviallyCopyable>::value == true);
306
307 // non-messageable type with ROOT dictionary
308 // pointer type used as store type
309 static_assert(std::is_same<Trait<Polymorphic>::store_type, Polymorphic*>::value == true);
310
311 // binary branch indicated through const char*
312 // BinaryBranchStoreType is used
313 static_assert(std::is_same<Trait<const char*>::store_type, BinaryBranchStoreType<char>>::value == true);
314
315 // vectors of fundamental types
316 // type itself (the vector) is used
317 static_assert(std::is_same<Trait<std::vector<int>>::store_type, std::vector<int>*>::value == true);
318
319 // vector of messageable type with or without ROOT dictionary
320 // type itself (the vector) is used
321 static_assert(std::is_same<Trait<std::vector<TriviallyCopyable>>::store_type, std::vector<TriviallyCopyable>*>::value == true);
322
323 // vector of non-messageable type with ROOT dictionary
324 // pointer type used as store type
325 static_assert(std::is_same<Trait<std::vector<Polymorphic>>::store_type, std::vector<Polymorphic>*>::value == true);
326}
327
328TEST_CASE("TestCanAssign")
329{
330 using Callback = std::function<bool(int, float)>;
331 auto matching = [](int, float) -> bool {
332 return true;
333 };
334 auto otherReturn = [](int, float) -> int {
335 return 0;
336 };
337 auto otherParam = [](int, int) -> bool {
338 return true;
339 };
340 REQUIRE((can_assign<decltype(matching), Callback>::value == true));
341 REQUIRE((can_assign<decltype(otherReturn), Callback>::value == false));
342 REQUIRE((can_assign<decltype(otherParam), Callback>::value == false));
343}
344} // namespace o2::test
std::string getName(const TDataMember *dm, int index, int size)
std::shared_ptr< arrow::Schema > schema
@ INFO
Definition FairLogger.h:32
std::vector< std::string > header
int32_t i
configStandalone Light OpenGL display display_light Apply smoothing to points Enable Z buffer Highlight links Highlight seeds Highlight sector tracks Highlight final tracks Show individual Show related Highlight grid Show only individual collision Color clusters belonging to track objects Set line Enable drawing TPC data Enable drawing TOF data Enable drawing magnetic field Set field line step count Invert colors configStandalone Heavy OpenGL display display_heavy Show tracks with ITS contribution Show tracks with TOF contribution Enable propagation of loopers Show clusters at nominal x of pad row for early transformed data Split CE tracks when they cross the central Mark clusters Mark adjacent clusters Hide rejected tracks Apply filter on tracks to be displayed Separate increase X h configStandalone Camera window renderer OpenGL display display_camera Orient the camera such that the y axis is always upwards Full Screen MultiSample Anti Downsampling Anti Enable Vertical Sync Target frame or if the backend cannot limit to the refresh Use OpenGL indirect draws to reduce number of draw calls Display h configStandalone Vulkan display display_vulkan Have uniform buffers in host accessible device memory h configStandalone Internal display display_internal Number of updates the Qt GUI may send ahead of the last one the display has before it waits for the display Opacity of the console background Number of console lines the history is shortened to this when it reaches twice the size Time in seconds an info message fades out after setInfoTime h OpenGL display display Font size Do not use Freetype for font Renderer for event Frontend to use for event ROOT macros used as track filter Console script file
configStandalone Reconstruction rec_tpc float
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 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:331
constexpr o2::header::SerializationMethod gSerializationMethodNone
Definition DataHeader.h:330
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:623
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()))