PODIO v00-16-03
An Event-Data-Model Toolkit for High Energy Physics Experiments
Loading...
Searching...
No Matches
frame.cpp
Go to the documentation of this file.
1#include "podio/Frame.h"
2
3#include "catch2/catch_test_macros.hpp"
4
5#include "datamodel/ExampleClusterCollection.h"
6#include "datamodel/ExampleHitCollection.h"
7
8#include <string>
9#include <thread>
10#include <vector>
11
12TEST_CASE("Frame collections", "[frame][basics]") {
13 auto event = podio::Frame();
14 auto clusters = ExampleClusterCollection();
15 clusters.create(3.14f);
16 clusters.create(42.0f);
17
18 event.put(std::move(clusters), "clusters");
19
20 auto& coll = event.get<ExampleClusterCollection>("clusters");
21 REQUIRE(coll[0].energy() == 3.14f);
22 REQUIRE(coll[1].energy() == 42.0f);
23}
24
25TEST_CASE("Frame parameters", "[frame][basics]") {
26 auto event = podio::Frame();
27
28 event.putParameter("aString", "from a string literal");
29 REQUIRE(event.getParameter<std::string>("aString") == "from a string literal");
30
31 event.putParameter("someInts", {42, 123});
32 const auto& ints = event.getParameter<std::vector<int>>("someInts");
33 REQUIRE(ints.size() == 2);
34 REQUIRE(ints[0] == 42);
35 REQUIRE(ints[1] == 123);
36
37 event.putParameter("someStrings", {"one", "two", "three"});
38 const auto& strings = event.getParameter<std::vector<std::string>>("someStrings");
39 REQUIRE(strings.size() == 3);
40 REQUIRE(strings[0] == "one");
41 REQUIRE(strings[1] == "two");
42 REQUIRE(strings[2] == "three");
43
44 const auto stringKeys = event.getParameterKeys<std::string>();
45 REQUIRE(stringKeys.size() == 2);
46 // Can't rely on an insertion order here
47 REQUIRE(std::find(stringKeys.begin(), stringKeys.end(), "aString") != stringKeys.end());
48 REQUIRE(std::find(stringKeys.begin(), stringKeys.end(), "someStrings") != stringKeys.end());
49}
50
51// NOTE: Due to the extremly small tasks that are done in these tests, they will
52// most likely succeed with a very high probability and only running with a
53// ThreadSanitizer will detect race conditions, so make sure to have that
54// enabled (-DUSE_SANITIZER=Thread) when working on these tests
55
56TEST_CASE("Frame collections multithreaded insert", "[frame][basics][multithread]") {
57 constexpr int nThreads = 10;
58 std::vector<std::thread> threads;
59 threads.reserve(10);
60
61 auto frame = podio::Frame();
62
63 // Fill collections from different threads
64 for (int i = 0; i < nThreads; ++i) {
65 threads.emplace_back([&frame, i]() {
66 auto clusters = ExampleClusterCollection();
67 clusters.create(i * 3.14);
68 clusters.create(i * 3.14);
69 frame.put(std::move(clusters), "clusters_" + std::to_string(i));
70
71 auto hits = ExampleHitCollection();
72 hits.create(i * 100ULL);
73 hits.create(i * 100ULL);
74 hits.create(i * 100ULL);
75 frame.put(std::move(hits), "hits_" + std::to_string(i));
76 });
77 }
78
79 for (auto& t : threads) {
80 t.join();
81 }
82
83 // Check the frame contents after all threads have finished
84 for (int i = 0; i < nThreads; ++i) {
85 auto& hits = frame.get<ExampleHitCollection>("hits_" + std::to_string(i));
86 REQUIRE(hits.size() == 3);
87 for (const auto h : hits) {
88 REQUIRE(h.cellID() == i * 100ULL);
89 }
90
91 auto& clusters = frame.get<ExampleClusterCollection>("clusters_" + std::to_string(i));
92 REQUIRE(clusters.size() == 2);
93 for (const auto c : clusters) {
94 REQUIRE(c.energy() == i * 3.14);
95 }
96 }
97}
98
99// Helper function to create a frame in the tests below
101 auto frame = podio::Frame();
102
103 frame.put(ExampleClusterCollection(), "emptyClusters");
104
105 // Create a few hits inline (to avoid having to have two identifiers)
106 auto& hits = frame.put(
107 []() {
108 auto coll = ExampleHitCollection();
109 auto hit = coll.create(0x42ULL, 0., 0., 0., 0.);
110 auto hit2 = coll.create(0x123ULL, 1., 1., 1., 1.);
111 return coll;
112 }(),
113 "hits");
114
115 auto clusters = ExampleClusterCollection();
116 auto cluster = clusters.create(3.14f);
117 cluster.addHits(hits[0]);
118 auto cluster2 = clusters.create(42.0f);
119 cluster2.addHits(hits[1]);
120 cluster2.addClusters(cluster);
121
122 // Create a few clustes inline and relate them to the hits from above
123 frame.put(std::move(clusters), "clusters");
124
125 frame.putParameter("anInt", 42);
126 frame.putParameter("someFloats", {1.23f, 2.34f, 3.45f});
127
128 return frame;
129}
130
131// Helper function to get names easily below
132std::string makeName(const std::string& prefix, int index) {
133 return prefix + "_" + std::to_string(index);
134}
135
136// The Catch2 assertions are not threadsafe
137// https://github.com/catchorg/Catch2/blob/devel/docs/limitations.md#thread-safe-assertions
138// This is a poor-mans implementation where it is our responsibility to only
139// pass in unshared counters
140void CHECK_INCREASE(const bool condition, int& counter) {
141 if (condition) {
142 counter++;
143 }
144}
145
146TEST_CASE("Frame collections multithreaded insert and read", "[frame][basics][multithread]") {
147 constexpr int nThreads = 10;
148 std::vector<std::thread> threads;
149 threads.reserve(10);
150
151 // create a pre-populated frame
152 auto frame = createFrame();
153
154 // The Catch2 assertions are not threadsafe:
155 // https://github.com/catchorg/Catch2/blob/devel/docs/limitations.md#thread-safe-assertions
156 // Count the successes in this array here and check them outside
157 // Once the Catch2 deficiencies are resolved, this can be changed again
158 std::array<int, nThreads> successes{};
159
160 // Fill collections from different threads
161 for (int i = 0; i < nThreads; ++i) {
162 threads.emplace_back([&frame, i, &successes]() {
163 auto clusters = ExampleClusterCollection();
164 clusters.create(i * 3.14);
165 clusters.create(i * 3.14);
166 frame.put(std::move(clusters), makeName("clusters", i));
167
168 // Retrieve a few collections in between and do iust a very basic testing
169 auto& existingClu = frame.get<ExampleClusterCollection>("clusters");
170 CHECK_INCREASE(existingClu.size() == 2, successes[i]);
171 auto& existingHits = frame.get<ExampleHitCollection>("hits");
172 CHECK_INCREASE(existingHits.size() == 2, successes[i]);
173
174 auto hits = ExampleHitCollection();
175 hits.create(i * 100ULL);
176 hits.create(i * 100ULL);
177 hits.create(i * 100ULL);
178 frame.put(std::move(hits), makeName("hits", i));
179
180 // Fill in a lot of new collections to trigger a rehashing of the
181 // internal map, which invalidates iterators
182 constexpr int nColls = 100;
183 for (int k = 0; k < nColls; ++k) {
184 frame.put(ExampleHitCollection(), "h_" + std::to_string(i) + "_" + std::to_string(k));
185 }
186 });
187 }
188
189 for (auto& t : threads) {
190 t.join();
191 }
192
193 // Check the frame contents after all threads have finished
194 for (int i = 0; i < nThreads; ++i) {
195 // Check whether the insertsions are as expected
196 REQUIRE(successes[i] == 2);
197
198 auto& hits = frame.get<ExampleHitCollection>(makeName("hits", i));
199 REQUIRE(hits.size() == 3);
200 for (const auto h : hits) {
201 REQUIRE(h.cellID() == i * 100ULL);
202 }
203
204 auto& clusters = frame.get<ExampleClusterCollection>(makeName("clusters", i));
205 REQUIRE(clusters.size() == 2);
206 for (const auto c : clusters) {
207 REQUIRE(c.energy() == i * 3.14);
208 }
209 }
210}
211
212// Helper function to keep the tests below a bit easier to read and not having
213// to repeat this bit all the time. This checks that the contents are the ones
214// that would be expected from the createFrame above
215void checkFrame(const podio::Frame& frame) {
216 auto& hits = frame.get<ExampleHitCollection>("hits");
217 REQUIRE(hits.size() == 2);
218 REQUIRE(hits[0].energy() == 0);
219 REQUIRE(hits[0].cellID() == 0x42ULL);
220 REQUIRE(hits[1].energy() == 1);
221 REQUIRE(hits[1].cellID() == 0x123ULL);
222
223 REQUIRE(frame.get<ExampleClusterCollection>("emptyClusters").size() == 0);
224
225 auto& clusters = frame.get<ExampleClusterCollection>("clusters");
226 REQUIRE(clusters.size() == 2);
227 REQUIRE(clusters[0].energy() == 3.14f);
228 REQUIRE(clusters[0].Hits().size() == 1);
229 REQUIRE(clusters[0].Hits()[0] == hits[0]);
230 REQUIRE(clusters[0].Clusters().empty());
231
232 REQUIRE(clusters[1].energy() == 42.f);
233 REQUIRE(clusters[1].Hits().size() == 1);
234 REQUIRE(clusters[1].Hits()[0] == hits[1]);
235 REQUIRE(clusters[1].Clusters()[0] == clusters[0]);
236
237 REQUIRE(frame.getParameter<int>("anInt") == 42);
238 auto& floats = frame.getParameter<std::vector<float>>("someFloats");
239 REQUIRE(floats.size() == 3);
240 REQUIRE(floats[0] == 1.23f);
241 REQUIRE(floats[1] == 2.34f);
242 REQUIRE(floats[2] == 3.45f);
243}
244
245TEST_CASE("Frame movability", "[frame][move-semantics]") {
246 auto frame = createFrame();
247 checkFrame(frame); // just to ensure that the setup is as expected
248
249 SECTION("Move constructor") {
250 auto otherFrame = std::move(frame);
251 checkFrame(otherFrame);
252 }
253
254 SECTION("Move assignment operator") {
255 auto otherFrame = podio::Frame();
256 otherFrame = std::move(frame);
257 checkFrame(otherFrame);
258 }
259
260 SECTION("Use after move construction") {
261 auto otherFrame = std::move(frame); // NOLINT(clang-analyzer-cplusplus.Move) clang-tidy and the Catch2 sections
262 // setup do not go along here
263 otherFrame.putParameter("aString", "Can add strings after move-constructing");
264 REQUIRE(otherFrame.getParameter<std::string>("aString") == "Can add strings after move-constructing");
265
266 otherFrame.put(
267 []() {
268 auto coll = ExampleHitCollection();
269 coll.create();
270 coll.create();
271 coll.create();
272 return coll;
273 }(),
274 "moreHits");
275
276 auto& hits = otherFrame.get<ExampleHitCollection>("moreHits");
277 REQUIRE(hits.size() == 3);
278 checkFrame(otherFrame);
279 }
280}
281
282TEST_CASE("Frame parameters multithread insert", "[frame][basics][multithread]") {
283 // Test that parameter access is thread safe
284 constexpr int nThreads = 10;
285 std::vector<std::thread> threads;
286 threads.reserve(nThreads);
287
288 auto frame = podio::Frame();
289
290 for (int i = 0; i < nThreads; ++i) {
291 threads.emplace_back([&frame, i]() {
292 frame.putParameter(makeName("int_par", i), i);
293
294 frame.putParameter(makeName("float_par", i), (float)i);
295
296 frame.putParameter(makeName("string_par", i), std::to_string(i));
297 });
298 }
299
300 for (auto& t : threads) {
301 t.join();
302 }
303
304 for (int i = 0; i < nThreads; ++i) {
305 REQUIRE(frame.getParameter<int>(makeName("int_par", i)) == i);
306 REQUIRE(frame.getParameter<float>(makeName("float_par", i)) == (float)i);
307 REQUIRE(frame.getParameter<std::string>(makeName("string_par", i)) == std::to_string(i));
308 }
309}
310
311TEST_CASE("Frame parameters multithread insert and read", "[frame][basics][multithread]") {
312 constexpr int nThreads = 10;
313 std::vector<std::thread> threads;
314 threads.reserve(nThreads);
315
316 auto frame = podio::Frame();
317 frame.putParameter("int_par", 42);
318 frame.putParameter("string_par", "some string");
319 frame.putParameter("float_pars", {1.23f, 4.56f, 7.89f});
320
321 // The Catch2 assertions are not threadsafe:
322 // https://github.com/catchorg/Catch2/blob/devel/docs/limitations.md#thread-safe-assertions
323 // Count the successes in this array here and check them outside
324 // Once the Catch2 deficiencies are resolved, this can be changed again
325 std::array<int, nThreads> successes{};
326
327 for (int i = 0; i < nThreads; ++i) {
328 threads.emplace_back([&frame, i, &successes]() {
329 frame.putParameter(makeName("int", i), i);
330 frame.putParameter(makeName("float", i), (float)i);
331
332 CHECK_INCREASE(frame.getParameter<int>("int_par") == 42, successes[i]);
333 CHECK_INCREASE(frame.getParameter<float>(makeName("float", i)) == (float)i, successes[i]);
334
335 frame.putParameter(makeName("string", i), std::to_string(i));
336 CHECK_INCREASE(frame.getParameter<std::string>("string_par") == "some string", successes[i]);
337
338 const auto& floatPars = frame.getParameter<std::vector<float>>("float_pars");
339 CHECK_INCREASE(floatPars.size() == 3, successes[i]);
340 CHECK_INCREASE(floatPars[0] == 1.23f, successes[i]);
341 CHECK_INCREASE(floatPars[1] == 4.56f, successes[i]);
342 CHECK_INCREASE(floatPars[2] == 7.89f, successes[i]);
343
344 // Fill in a lot of new parameters to trigger rehashing of the internal
345 // map, which invalidates iterators
346 constexpr int nParams = 100;
347 for (int k = 0; k < nParams; ++k) {
348 frame.putParameter(makeName("intPar", i) + std::to_string(k), i * k);
349 frame.putParameter(makeName("floatPar", i) + std::to_string(k), (float)i * k);
350 frame.putParameter(makeName("stringPar", i) + std::to_string(k), std::to_string(i * k));
351 }
352 });
353 }
354
355 for (auto& t : threads) {
356 t.join();
357 }
358
359 for (int i = 0; i < nThreads; ++i) {
360 // Check the insertion successes
361 REQUIRE(successes[i] == 7);
362
363 REQUIRE(frame.getParameter<int>(makeName("int", i)) == i);
364 REQUIRE(frame.getParameter<float>(makeName("float", i)) == (float)i);
365 REQUIRE(frame.getParameter<std::string>(makeName("string", i)) == std::to_string(i));
366 }
367}
const CollT & get(const std::string &name) const
Definition: Frame.h:297
podio::GenericDataReturnType< T > getParameter(const std::string &key) const
Definition: Frame.h:232
TEST_CASE("Frame collections", "[frame][basics]")
Definition: frame.cpp:12
auto createFrame()
Definition: frame.cpp:100
void checkFrame(const podio::Frame &frame)
Definition: frame.cpp:215
void CHECK_INCREASE(const bool condition, int &counter)
Definition: frame.cpp:140
std::string makeName(const std::string &prefix, int index)
Definition: frame.cpp:132