Skip to content

Commit e1bc3a7

Browse files
Mario Gonzalezfwyzard
andcommitted
Added unit tests for TrivialCopyTraits and the Serialiser plugin factory
Co-authored-by: Andrea Bocci <[email protected]>
1 parent 12f946d commit e1bc3a7

File tree

7 files changed

+671
-0
lines changed

7 files changed

+671
-0
lines changed
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
#include <catch.hpp>
2+
3+
#include "DataFormats/Common/interface/TrivialCopyTraits.h"
4+
5+
#include <cmath>
6+
#include <map>
7+
#include <type_traits>
8+
#include <vector>
9+
#include <numeric>
10+
#include <cstring>
11+
12+
// --------------------------------------------------------------------------
13+
// Definitions of various types, and their TrivialCopyTraits specializations
14+
15+
// A trivially copyable type and its nice TrivialCopyTraits specialization
16+
struct S {
17+
S() = default;
18+
S(std::string m, std::vector<float> v) : msg{std::move(m)}, vec{std::move(v)} { setVecSum(); }
19+
20+
std::string msg;
21+
std::vector<float> vec;
22+
float vec_sum = 0.0f; // something that needs to be calculated after the copy is finished
23+
void setVecSum() { vec_sum = std::accumulate(vec.begin(), vec.end(), 0.0f); }
24+
};
25+
26+
template <>
27+
struct edm::TrivialCopyTraits<S> {
28+
using value_type = S;
29+
30+
using Properties = std::array<size_t, 2>; // {vec.size(), s.size()}
31+
32+
static Properties properties(value_type const& object) {
33+
return std::array<size_t, 2>{{object.vec.size(), object.msg.size()}};
34+
}
35+
36+
static void initialize(value_type& object, Properties const& sizes) {
37+
object.vec.resize(sizes.at(0));
38+
object.msg.resize(sizes.at(1));
39+
}
40+
41+
static void finalize(value_type& object) { object.setVecSum(); }
42+
43+
static std::vector<std::span<std::byte>> regions(value_type& object) {
44+
return {{reinterpret_cast<std::byte*>(object.msg.data()), object.msg.size()},
45+
{reinterpret_cast<std::byte*>(object.vec.data()), object.vec.size() * sizeof(float)}};
46+
}
47+
48+
static std::vector<std::span<const std::byte>> regions(value_type const& object) {
49+
return {{reinterpret_cast<std::byte const*>(object.msg.data()), object.msg.size()},
50+
{reinterpret_cast<std::byte const*>(object.vec.data()), object.vec.size() * sizeof(float)}};
51+
}
52+
};
53+
54+
// A type that does not have properties but requires initialization, and its valid
55+
// TrivialCopyTraits specialization
56+
struct S2 {
57+
S2() { vec.resize(size); }
58+
59+
// vec requires initialization, but doesn't need properties because its size is
60+
// fixed
61+
const size_t size = 10;
62+
std::vector<int> vec;
63+
};
64+
65+
template <>
66+
struct edm::TrivialCopyTraits<S2> {
67+
static void initialize(S2& object) { object.vec.resize(object.size); }
68+
69+
static std::vector<std::span<std::byte>> regions(S2& object) {
70+
return {{reinterpret_cast<std::byte*>(object.vec.data()), object.vec.size() * sizeof(int)}};
71+
}
72+
73+
static std::vector<std::span<const std::byte>> regions(S2 const& object) {
74+
return {{reinterpret_cast<std::byte const*>(object.vec.data()), object.vec.size() * sizeof(int)}};
75+
}
76+
};
77+
78+
// A bad TrivialCopyTraits specialization, that is missing regions()
79+
struct S3 {
80+
std::vector<int> vec;
81+
};
82+
83+
template <>
84+
struct edm::TrivialCopyTraits<S3> {
85+
static void initialize(S3& object) { object.vec.resize(10); }
86+
};
87+
88+
// A bad TrivialCopyTraits specialization, with a non-valid initialize()
89+
struct S4 {
90+
std::vector<int> data;
91+
};
92+
93+
template <>
94+
struct edm::TrivialCopyTraits<S4> {
95+
using Properties = size_t;
96+
97+
static Properties properties(S4 const& object) { return object.data.size(); }
98+
99+
// initialize should take two arguments; the object and its properties (size)
100+
static void initialize(S4& object) { object.data.resize(5); }
101+
102+
static std::vector<std::span<std::byte>> regions(S4& object) {
103+
return {{reinterpret_cast<std::byte*>(object.data.data()), object.data.size() * sizeof(int)}};
104+
}
105+
106+
static std::vector<std::span<const std::byte>> regions(S4 const& object) {
107+
return {{reinterpret_cast<std::byte const*>(object.data.data()), object.data.size() * sizeof(int)}};
108+
}
109+
};
110+
111+
// A bad TrivialCopyTraits specialization, missing only the const regions
112+
struct S5 {
113+
int value;
114+
};
115+
116+
template <>
117+
struct edm::TrivialCopyTraits<S5> {
118+
static std::vector<std::span<std::byte>> regions(S5& object) {
119+
return {{reinterpret_cast<std::byte*>(&object.value), sizeof(int)}};
120+
}
121+
};
122+
123+
// --------------------------------------------------------------------------
124+
// The tests
125+
126+
TEST_CASE("test TrivialCopyTraits", "[TrivialCopyTraits]") {
127+
SECTION("int") {
128+
REQUIRE(std::is_same_v<edm::TrivialCopyTraits<int>::value_type, int>);
129+
130+
auto checkInt = [](int v) {
131+
// test non-const regions
132+
auto regions = edm::TrivialCopyTraits<int>::regions(v);
133+
REQUIRE(regions.size() == 1);
134+
REQUIRE(regions[0].size() == sizeof(int));
135+
REQUIRE(regions[0].data() == reinterpret_cast<std::byte*>(&v));
136+
137+
// test const regions
138+
const int const_v = v;
139+
auto const_regions = edm::TrivialCopyTraits<int>::regions(const_v);
140+
REQUIRE(const_regions.size() == 1);
141+
REQUIRE(const_regions[0].size() == sizeof(int));
142+
REQUIRE(const_regions[0].data() == reinterpret_cast<const std::byte*>(&const_v));
143+
};
144+
145+
checkInt(-1);
146+
checkInt(42);
147+
checkInt(std::numeric_limits<int>::max());
148+
checkInt(std::numeric_limits<int>::min());
149+
}
150+
151+
SECTION("double") {
152+
REQUIRE(std::is_same_v<edm::TrivialCopyTraits<double>::value_type, double>);
153+
154+
auto checkDouble = [](double v) {
155+
// test non-const regions
156+
auto regions = edm::TrivialCopyTraits<double>::regions(v);
157+
REQUIRE(regions.size() == 1);
158+
REQUIRE(regions[0].size() == sizeof(double));
159+
REQUIRE(regions[0].data() == reinterpret_cast<std::byte*>(&v));
160+
161+
// test const regions
162+
const double const_v = v;
163+
auto const_regions = edm::TrivialCopyTraits<double>::regions(const_v);
164+
REQUIRE(const_regions.size() == 1);
165+
REQUIRE(const_regions[0].size() == sizeof(double));
166+
REQUIRE(const_regions[0].data() == reinterpret_cast<const std::byte*>(&const_v));
167+
};
168+
169+
checkDouble(-1.0);
170+
checkDouble(std::sqrt(2.));
171+
checkDouble(std::numeric_limits<double>::max());
172+
checkDouble(std::numeric_limits<double>::min());
173+
checkDouble(std::numeric_limits<double>::epsilon());
174+
}
175+
176+
SECTION("std::vector<float>") {
177+
using VectorType = std::vector<float>;
178+
REQUIRE(std::is_same_v<edm::TrivialCopyTraits<VectorType>::value_type, VectorType>);
179+
REQUIRE(std::is_same_v<edm::TrivialCopyTraits<VectorType>::Properties, VectorType::size_type>);
180+
181+
VectorType vec = {-5.5f, -3.3f, -1.1f, 4.4f, 8.8f};
182+
183+
// Test properties
184+
auto vec_size = edm::TrivialCopyTraits<VectorType>::properties(vec);
185+
REQUIRE(vec_size == 5);
186+
187+
// Test initialize
188+
VectorType new_vec;
189+
edm::TrivialCopyTraits<VectorType>::initialize(new_vec, vec_size);
190+
REQUIRE(new_vec.size() == 5);
191+
192+
// Test non-const regions
193+
auto regions = edm::TrivialCopyTraits<VectorType>::regions(vec);
194+
REQUIRE(regions.size() == 1);
195+
REQUIRE(regions[0].size() == vec.size() * sizeof(float));
196+
REQUIRE(regions[0].data() == reinterpret_cast<std::byte*>(vec.data()));
197+
198+
// Test const regions
199+
const VectorType const_vec = {1.1f, 2.2f, 3.3f, 4.4f, 5.5f};
200+
auto const_regions = edm::TrivialCopyTraits<VectorType>::regions(const_vec);
201+
REQUIRE(const_regions.size() == 1);
202+
REQUIRE(const_regions[0].size() == const_vec.size() * sizeof(float));
203+
REQUIRE(const_regions[0].data() == reinterpret_cast<const std::byte*>(const_vec.data()));
204+
}
205+
206+
SECTION("memcpy-able struct") {
207+
std::string test_msg = "hello!";
208+
std::vector<float> test_vec = {-1.0f, 4.0f, 42.0f};
209+
float test_vec_sum = std::accumulate(test_vec.begin(), test_vec.end(), 0.0f);
210+
211+
// initialize a memcpy-able struct s
212+
S s{test_msg, test_vec};
213+
214+
REQUIRE(std::is_same_v<edm::TrivialCopyTraits<S>::value_type, S>);
215+
REQUIRE(std::is_same_v<edm::TrivialCopyTraits<S>::Properties, std::array<size_t, 2>>);
216+
217+
// initialize a clone of s
218+
S s_clone;
219+
edm::TrivialCopyTraits<S>::initialize(s_clone, edm::TrivialCopyTraits<S>::properties(s));
220+
221+
// Get memory regions
222+
auto const s_regions = edm::TrivialCopyTraits<S>::regions(s);
223+
auto s_clone_regions = edm::TrivialCopyTraits<S>::regions(s_clone);
224+
225+
REQUIRE(s_regions.size() == s_clone_regions.size());
226+
REQUIRE(s_clone.msg.size() == s.msg.size());
227+
REQUIRE(s_clone.vec.size() == s.vec.size());
228+
229+
for (size_t i = 0; i < s_regions.size(); ++i) {
230+
// check that initialize worked, i.e. enough memory in s_clone has been made available to copy s into it
231+
REQUIRE(s_regions.at(i).size_bytes() == s_clone_regions.at(i).size_bytes());
232+
233+
// do the copy
234+
std::memcpy(s_clone_regions.at(i).data(), s_regions.at(i).data(), s_regions.at(i).size_bytes());
235+
}
236+
237+
//s_clone.vec_sum has not been touched yet
238+
REQUIRE(s_clone.vec_sum == 0.0f);
239+
240+
// finalize the clone, which should calculate vec_sum
241+
edm::TrivialCopyTraits<S>::finalize(s_clone);
242+
243+
// check that the copy worked
244+
REQUIRE(s_clone.vec == test_vec);
245+
REQUIRE(s_clone.msg == test_msg);
246+
247+
// check that finalize worked
248+
REQUIRE(s_clone.vec_sum == test_vec_sum);
249+
}
250+
251+
SECTION("std::map<int,int>") {
252+
using MapType = std::map<int, int>;
253+
254+
// there is no TrivialCopyTraits specialization for std::map (and there shouldn't be, since std::map is not trivially copyable)
255+
static_assert(!edm::HasTrivialCopyTraits<MapType>);
256+
}
257+
258+
SECTION("A valid specialization with initialize() but without properties()") {
259+
static_assert(edm::HasTrivialCopyTraits<S2>);
260+
static_assert(!edm::HasTrivialCopyProperties<S2>);
261+
static_assert(edm::HasValidInitialize<S2>);
262+
263+
S2 s2;
264+
// fill its member vector with some data
265+
for (size_t i = 0; i < s2.vec.size(); ++i) {
266+
s2.vec[i] = static_cast<int>(i * 10);
267+
}
268+
269+
S2 s2_clone;
270+
// initialize the clone (no properties required)
271+
edm::TrivialCopyTraits<S2>::initialize(s2_clone);
272+
273+
// get memory regions
274+
auto const s2_regions = edm::TrivialCopyTraits<S2>::regions(s2);
275+
auto s2_clone_regions = edm::TrivialCopyTraits<S2>::regions(s2_clone);
276+
277+
// Only one memory region (the vector)
278+
REQUIRE(s2_regions.size() == 1);
279+
REQUIRE(s2_clone_regions.size() == 1);
280+
REQUIRE(s2_regions[0].size_bytes() == s2_clone_regions[0].size_bytes());
281+
282+
// before the copy:
283+
REQUIRE(s2_clone.vec != s2.vec);
284+
285+
// do the copy
286+
std::memcpy(s2_clone_regions[0].data(), s2_regions[0].data(), s2_regions[0].size_bytes());
287+
288+
// and now,
289+
REQUIRE(s2_clone.vec == s2.vec);
290+
}
291+
292+
SECTION("Invalid specializations") {
293+
// S3: Missing regions() method
294+
static_assert(!edm::HasTrivialCopyTraits<S3>);
295+
static_assert(!edm::HasRegions<S3>);
296+
297+
// S5: Missing just the const regions() overload
298+
static_assert(!edm::HasTrivialCopyTraits<S5>);
299+
static_assert(!edm::HasRegions<S5>);
300+
301+
// S4: Has properties, but the initialize() declaration takes only one
302+
// argument.
303+
static_assert(!edm::HasTrivialCopyTraits<S4>);
304+
static_assert(edm::HasTrivialCopyProperties<S4>);
305+
static_assert(!edm::HasValidInitialize<S4>);
306+
}
307+
}

DataFormats/Portable/test/BuildFile.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313
<use name="HeterogeneousCore/AlpakaInterface"/>
1414
<flags ALPAKA_BACKENDS="1"/>
1515
</bin>
16+
17+
<test name="TestDataFormatsPortableGenericCloner" command="cmsRun ${LOCALTOP}/src/FWCore/TestModules/test/testGenericCloner_cfg.py"/>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import FWCore.ParameterSet.Config as cms
2+
3+
process = cms.Process("TEST")
4+
5+
process.load("FWCore.MessageService.MessageLogger_cfi")
6+
process.MessageLogger.cerr.INFO.limit = 10000000
7+
8+
process.options.numberOfThreads = 1
9+
process.options.numberOfStreams = 1
10+
11+
process.source = cms.Source("EmptySource")
12+
process.maxEvents.input = 10
13+
14+
15+
16+
#produce, clone and validate a portable object, a portable collection, and some portable multicollections
17+
process.load('Configuration.StandardSequences.Accelerators_cff')
18+
process.load('HeterogeneousCore.AlpakaCore.ProcessAcceleratorAlpaka_cfi')
19+
20+
process.producePortableObjects = cms.EDProducer('TestAlpakaProducer@alpaka',
21+
size = cms.int32(42),
22+
size2 = cms.int32(33),
23+
size3 = cms.int32(61),
24+
alpaka = cms.untracked.PSet(
25+
backend = cms.untracked.string("") #"serial_sync", "cuda_async", "rocm_async"
26+
)
27+
)
28+
29+
process.clonePortableObjects = cms.EDProducer("edmtest::GenericCloner",
30+
eventProducts = cms.vstring("producePortableObjects"),
31+
verbose = cms.untracked.bool(True)
32+
)
33+
34+
process.validatePortableCollections = cms.EDAnalyzer('TestAlpakaAnalyzer',
35+
source = cms.InputTag('clonePortableObjects')
36+
)
37+
38+
process.validatePortableObject = cms.EDAnalyzer('TestAlpakaObjectAnalyzer',
39+
source = cms.InputTag('clonePortableObjects')
40+
)
41+
42+
process.taskSoA = cms.Task(process.producePortableObjects, process.clonePortableObjects)
43+
44+
process.pathSoA = cms.Path(process.validatePortableCollections + process.validatePortableObject, process.taskSoA)
45+

FWCore/TestModules/plugins/BuildFile.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<use name="DataFormats/Provenance"/>
2+
<use name="TrivialSerialisation/Common"/>
23
<use name="FWCore/Framework"/>
34
<use name="FWCore/ParameterSet"/>
45
<use name="FWCore/PluginManager"/>

0 commit comments

Comments
 (0)