forked from sammy-tri/drake
-
Notifications
You must be signed in to change notification settings - Fork 0
/
geometry_properties_test.cc
502 lines (427 loc) · 17.9 KB
/
geometry_properties_test.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
#include "drake/geometry/geometry_properties.h"
#include <Eigen/Dense>
#include <gtest/gtest.h>
#include "drake/common/drake_copyable.h"
#include "drake/common/test_utilities/expect_no_throw.h"
#include "drake/common/test_utilities/expect_throws_message.h"
#include "drake/common/unused.h"
#include "drake/geometry/rgba.h"
namespace drake {
namespace geometry {
namespace {
using Eigen::Vector4d;
using std::string;
// A constructible sub-class of GeometryProperties.
class TestProperties : public GeometryProperties {
public:
DRAKE_DEFAULT_COPY_AND_MOVE_AND_ASSIGN(TestProperties);
TestProperties() = default;
};
GTEST_TEST(GeometryProperties, ManagingGroups) {
TestProperties properties;
const string& group_name{"some_group"};
// Only contains the default group.
ASSERT_EQ(1, properties.num_groups());
ASSERT_FALSE(properties.HasGroup(group_name));
ASSERT_TRUE(properties.HasGroup(TestProperties::default_group_name()));
// Add the group for the first time by adding a property.
properties.AddProperty(group_name, "junk_value", 1);
ASSERT_TRUE(properties.HasGroup(group_name));
ASSERT_EQ(2, properties.num_groups());
// Retrieve the group.
using PropertyGroup = GeometryProperties::Group;
const PropertyGroup& group = properties.GetPropertiesInGroup(group_name);
EXPECT_EQ(1u, group.size());
DRAKE_EXPECT_THROWS_MESSAGE(properties.GetPropertiesInGroup("invalid_name"),
".*Can't retrieve properties for a group that "
"doesn't exist: 'invalid_name'");
}
// Tests adding properties (successfully and otherwise). Uses a call to
// GetProperty() to confirm successful add.
GTEST_TEST(GeometryProperties, AddProperty) {
TestProperties properties;
const string& group_name{"some_group"};
// Confirm property doesn't exist.
const string prop_name("some_property");
ASSERT_FALSE(properties.HasProperty(group_name, prop_name));
// Add the property.
const int int_value{7};
DRAKE_EXPECT_NO_THROW(
properties.AddProperty(group_name, prop_name, int_value));
// Confirm existence.
ASSERT_TRUE(properties.HasProperty(group_name, prop_name));
ASSERT_EQ(properties.GetProperty<int>(group_name, prop_name), int_value);
// Redundant add fails.
DRAKE_EXPECT_THROWS_MESSAGE(
properties.AddProperty(group_name, prop_name, int_value),
fmt::format(
".*Trying to add property \\('{}', '{}'\\).+ name already exists",
group_name, prop_name));
ASSERT_TRUE(properties.HasProperty(group_name, prop_name));
}
// Tests updating properties (successfully and otherwise). Uses a call to
// GetProperty() to confirm successful add.
GTEST_TEST(GeometryProperties, UpdateProperty) {
TestProperties properties;
// Initialize with a single property.
const string group_name{"some_group"};
const string prop_name("some_property");
const int int_value{7};
DRAKE_EXPECT_NO_THROW(
properties.AddProperty(group_name, prop_name, int_value));
EXPECT_EQ(properties.GetProperty<int>(group_name, prop_name), int_value);
// UpdateProperty adds new property.
const string& prop_name2("other_property");
EXPECT_FALSE(properties.HasProperty(group_name, prop_name2));
EXPECT_NO_THROW(
properties.UpdateProperty(group_name, prop_name2, "from_update"));
EXPECT_EQ(properties.GetProperty<string>(group_name, prop_name2),
"from_update");
// UpdateProperty alias works for changing value (with same type).
EXPECT_NO_THROW(
properties.UpdateProperty(group_name, prop_name, int_value + 10));
EXPECT_EQ(properties.GetProperty<int>(group_name, prop_name), int_value + 10);
// UpdateProperty alias fails for changing type.
DRAKE_EXPECT_THROWS_MESSAGE(
properties.UpdateProperty(group_name, prop_name, 17.0),
fmt::format(".*Trying to update property \\('{}', '{}'\\).+ is of "
"different type.+",
group_name, prop_name));
}
GTEST_TEST(GeometryProperties, RemoveProperty) {
TestProperties properties;
const string group1{"group1"};
const string group2{"group2"};
const string prop1{"prop1"};
const string prop2{"prop2"};
// Add two groups with two properties each.
properties.AddProperty(group1, prop1, 1);
properties.AddProperty(group1, prop2, 2);
properties.AddProperty(group2, prop1, 3);
properties.AddProperty(group2, prop2, 4);
ASSERT_TRUE(properties.HasProperty(group1, prop1));
ASSERT_TRUE(properties.HasProperty(group1, prop2));
ASSERT_TRUE(properties.HasProperty(group2, prop1));
ASSERT_TRUE(properties.HasProperty(group2, prop2));
// Remove property, make sure all others are still intact.
EXPECT_TRUE(properties.RemoveProperty(group1, prop1));
ASSERT_FALSE(properties.HasProperty(group1, prop1));
ASSERT_TRUE(properties.HasProperty(group1, prop2));
ASSERT_TRUE(properties.HasProperty(group2, prop1));
ASSERT_TRUE(properties.HasProperty(group2, prop2));
// Trying to remove it again has no effect.
EXPECT_FALSE(properties.RemoveProperty(group1, prop1));
EXPECT_FALSE(properties.HasProperty(group1, prop1));
EXPECT_TRUE(properties.HasProperty(group1, prop2));
EXPECT_TRUE(properties.HasProperty(group2, prop1));
EXPECT_TRUE(properties.HasProperty(group2, prop2));
}
// Struct for the AddPropertyStruct test.
struct TestData {
int i{};
double d{};
string s;
};
// Tests the case where the property value is a struct.
GTEST_TEST(GeometryProperties, AddPropertyStruct) {
TestProperties properties;
const string prop_name("test data");
TestData data{1, 2., "3"};
DRAKE_EXPECT_NO_THROW(properties.AddProperty(
TestProperties::default_group_name(), prop_name, data));
const TestData& read = properties.GetProperty<TestData>(
TestProperties::default_group_name(), prop_name);
EXPECT_EQ(data.i, read.i);
EXPECT_EQ(data.d, read.d);
EXPECT_EQ(data.s, read.s);
}
// Tests property access with default.
GTEST_TEST(GeometryProperties, GetPropertyOrDefault) {
// Create one group with a single property.
TestProperties properties;
const string group_name{"some_group"};
const double double_value{7};
const double default_value = double_value - 1;
const string prop_name("some_property");
DRAKE_EXPECT_NO_THROW(
properties.AddProperty(group_name, prop_name, double_value));
// Case: default value that can be *implicitly* converted to the desired
// type requires explicit template declaration.
DRAKE_EXPECT_THROWS_MESSAGE(
properties.GetPropertyOrDefault(group_name, prop_name, 3),
fmt::format(
".*The property \\('{}', '{}'\\) exists, but is of a different type. "
"Requested 'int', but found 'double'",
group_name, prop_name));
DRAKE_EXPECT_NO_THROW(
properties.GetPropertyOrDefault<double>(group_name, prop_name, 3));
// Case: read an existing property.
int read_value =
properties.GetPropertyOrDefault(group_name, prop_name, default_value);
EXPECT_EQ(double_value, read_value);
// Case: read from valid group, but invalid property.
read_value = properties.GetPropertyOrDefault(group_name, "invalid_prop",
default_value);
EXPECT_EQ(default_value, read_value);
// Case: read from invalid group.
read_value = properties.GetPropertyOrDefault("invalid_group", "invalid_prop",
default_value);
EXPECT_EQ(default_value, read_value);
// Case: Property exists of different type.
DRAKE_EXPECT_THROWS_MESSAGE(
properties.GetPropertyOrDefault(group_name, prop_name, "test"),
fmt::format(".*The property \\('{}', '{}'\\) exists, but is of a "
"different type. Requested 'std::string', but found 'double'",
group_name, prop_name));
// Using r-values as defaults; this tests both compatibility and correctness.
properties.AddProperty("strings", "valid_string", "valid_string");
string valid_value =
properties.GetPropertyOrDefault("strings", "valid_string", "missing");
EXPECT_EQ("valid_string", valid_value);
string default_value_return = properties.GetPropertyOrDefault(
"strings", "invalid_string", "rvalue_string");
EXPECT_EQ("rvalue_string", default_value_return);
}
// Tests the unsuccessful access to properties (successful access has been
// implicitly tested in the functions that added/set properties).
GTEST_TEST(GeometryProperties, GetPropertyFailure) {
TestProperties properties;
const string& group_name{"some_group"};
const string prop_name("some_property");
// Getter errors
// Case: Asking for property from non-existent group.
DRAKE_EXPECT_THROWS_MESSAGE(
properties.GetProperty<int>(group_name, prop_name),
fmt::format(
".*Trying to read property \\('{}', '{}'\\), but the group does not "
"exist.",
group_name, prop_name));
// Case: Group exists, property does not.
properties.AddProperty(group_name, prop_name + "_alt", 1);
DRAKE_EXPECT_THROWS_MESSAGE(
properties.GetProperty<int>(group_name, prop_name),
fmt::format(".*There is no property \\('{}', '{}'\\)", group_name,
prop_name));
// Case: Group and property exists, but property is of different type.
DRAKE_EXPECT_NO_THROW(properties.AddProperty(group_name, prop_name, 7.0));
DRAKE_EXPECT_THROWS_MESSAGE(
properties.GetProperty<int>(group_name, prop_name),
fmt::format(
".*The property \\('{}', '{}'\\) exists, but is of a different type. "
"Requested 'int', but found 'double'",
group_name, prop_name));
}
// Tests iteration through a group's properties.
GTEST_TEST(GeometryProperties, PropertyIteration) {
TestProperties properties;
const string& default_group = TestProperties::default_group_name();
std::unordered_map<string, int> reference{{"prop1", 10}, {"prop2", 20}};
for (const auto& pair : reference) {
properties.AddProperty(default_group, pair.first, pair.second);
}
// Get exception for non-existent group.
DRAKE_EXPECT_THROWS_MESSAGE(properties.GetPropertiesInGroup("bad_group"),
".*Can't retrieve properties for a group that "
"doesn't exist: 'bad_group'");
// Confirm that all properties have the right value and get visited.
std::set<string> visited_properties;
for (const auto& pair : properties.GetPropertiesInGroup(default_group)) {
const string& name = pair.first;
EXPECT_GT(reference.count(name), 0);
EXPECT_EQ(reference[name],
properties.GetProperty<int>(default_group, name));
visited_properties.insert(name);
}
EXPECT_EQ(reference.size(), visited_properties.size());
}
// Confirms that derived classes *can* be copied/moved.
GTEST_TEST(GeometryProperties, CopyMoveSemantics) {
// Populate a property set with an arbitrary set of properties. In this case,
// they are all int-valued to facilitate comparison between property sets.
auto make_properties = []() {
TestProperties props;
const string& default_group = TestProperties::default_group_name();
props.AddProperty(default_group, "prop1", 1);
props.AddProperty(default_group, "prop2", 2);
const string group1("group1");
// NOTE: Duplicate property name differentiated by different group.
props.AddProperty(group1, "prop1", 3);
props.AddProperty(group1, "prop3", 4);
props.AddProperty(group1, "prop4", 5);
const string group2("group2");
props.AddProperty(group2, "prop5", 6);
return props;
};
// Only works for int-valued properties.
auto properties_equal =
[](const TestProperties& reference,
const TestProperties& test) -> ::testing::AssertionResult {
if (reference.num_groups() != test.num_groups()) {
return ::testing::AssertionFailure()
<< "Different number of groups. Expected "
<< reference.num_groups() << " found " << test.num_groups();
}
for (const auto& group_name : reference.GetGroupNames()) {
if (test.HasGroup(group_name)) {
for (const auto& pair : reference.GetPropertiesInGroup(group_name)) {
const string& name = pair.first;
int expected_value = pair.second->get_value<int>();
if (test.HasProperty(group_name, name)) {
int test_value = test.GetProperty<int>(group_name, name);
if (expected_value != test_value) {
return ::testing::AssertionFailure()
<< "Expected value for '" << group_name << "':'" << name
<< "' to be " << expected_value << ". Found "
<< test_value;
}
} else {
return ::testing::AssertionFailure()
<< "Expected group '" << group_name << "' to have property '"
<< name << "'. It does not exist.";
}
}
} else {
return ::testing::AssertionFailure()
<< "Expected group '" << group_name
<< "' is missing from test properties";
}
}
return ::testing::AssertionSuccess();
};
TestProperties source = make_properties();
TestProperties reference = make_properties();
// Copy construction.
TestProperties copy_construct(source);
EXPECT_TRUE(properties_equal(reference, copy_construct));
// Copy assignment.
TestProperties copy_assign;
EXPECT_FALSE(properties_equal(reference, copy_assign));
copy_assign = source;
EXPECT_TRUE(properties_equal(reference, copy_assign));
// Strictly speaking, confirming that the move *source* has changed isn't
// necessary. The move semantics aren't documented. However, given that this
// is using default move semantics on unordered_map, we can assume that the
// source is modified by the move. So, we'll go ahead and test that.
// Move construction.
TestProperties move_construct(std::move(source));
EXPECT_FALSE(properties_equal(reference, source));
EXPECT_TRUE(properties_equal(reference, move_construct));
// Move assignment.
TestProperties move_assign;
EXPECT_FALSE(properties_equal(reference, move_assign));
move_assign = std::move(move_construct);
EXPECT_FALSE(properties_equal(reference, move_construct));
EXPECT_TRUE(properties_equal(reference, move_assign));
}
// Counts the number of instances constructed. Ignores destruction.
// TODO(eric.cousineau): Hoist this to more general testing code (e.g. for
// value_test.cc).
class GloballyCounted {
public:
struct Stats {
int num_copies{};
int num_moves{};
::testing::AssertionResult Equal(Stats other) {
if (num_copies != other.num_copies || num_moves != other.num_moves) {
return ::testing::AssertionFailure() << fmt::format(
"(num_copies, num_moves): ({}, {}) != ({}, {})", num_copies,
num_moves, other.num_copies, other.num_moves);
}
return ::testing::AssertionSuccess();
}
};
static Stats get_stats_and_reset() {
Stats out = stats;
stats = {0, 0};
return out;
}
GloballyCounted() {}
GloballyCounted(GloballyCounted&&) { stats.num_moves++; }
GloballyCounted& operator=(GloballyCounted&&) {
stats.num_moves++;
return *this;
}
GloballyCounted(const GloballyCounted&) { stats.num_copies++; }
GloballyCounted& operator=(const GloballyCounted&) {
stats.num_copies++;
return *this;
}
private:
static Stats stats;
};
GloballyCounted::Stats GloballyCounted::stats;
GTEST_TEST(GeometryProperties, GloballyCounted) {
// Unittest basic utility.
const GloballyCounted value;
EXPECT_TRUE(GloballyCounted::get_stats_and_reset().Equal({0, 0}));
// Copy construction.
{
const GloballyCounted copy = value;
unused(copy);
EXPECT_TRUE(GloballyCounted::get_stats_and_reset().Equal({1, 0}));
}
// Copy assignment.
{
GloballyCounted copy;
copy = value;
EXPECT_TRUE(GloballyCounted::get_stats_and_reset().Equal({1, 0}));
}
// Move construction.
{
GloballyCounted moved_from;
GloballyCounted moved_to = std::move(moved_from);
unused(moved_to);
EXPECT_TRUE(GloballyCounted::get_stats_and_reset().Equal({0, 1}));
}
// Move assigment.
{
GloballyCounted moved_from;
GloballyCounted moved_to;
moved_to = std::move(moved_from);
EXPECT_TRUE(GloballyCounted::get_stats_and_reset().Equal({0, 1}));
}
}
// Confirms the amount of copying that occurs.
GTEST_TEST(GeometryProperties, CopyCountCheck) {
TestProperties properties;
const string& group_name{"some_group"};
const string name_1("name_1");
const string name_2("name_2");
// When adding a property, 2 copies should occur: once when constructing a
// value, then another when cloning it.
const GloballyCounted value;
properties.AddPropertyAbstract(group_name, name_1, Value(value));
EXPECT_TRUE(GloballyCounted::get_stats_and_reset().Equal({2, 0}));
// Same as above.
properties.AddProperty(group_name, name_2, value);
EXPECT_TRUE(GloballyCounted::get_stats_and_reset().Equal({2, 0}));
// No copies upon retrieving the type.
properties.GetProperty<GloballyCounted>(group_name, name_1);
EXPECT_TRUE(GloballyCounted::get_stats_and_reset().Equal({0, 0}));
}
GTEST_TEST(GeometryProperties, RgbaAndVector4) {
const Rgba color(0.75, 0.5, 0.25, 1.);
const Vector4d vector(0.75, 0.5, 0.25, 1.);
TestProperties properties;
const string& group_name{"some_group"};
const string color_name("color_name");
const string fake_name("fake_name");
// Add<Rgba>.
properties.AddProperty(group_name, color_name, color);
// - Get<Rgba>.
EXPECT_EQ(color, properties.GetProperty<Rgba>(group_name, color_name));
// - Get<Vector4d>.
EXPECT_EQ(vector, properties.GetProperty<Vector4d>(group_name, color_name));
EXPECT_EQ(vector, properties.GetPropertyOrDefault<Vector4d>(
group_name, fake_name, vector));
// Add<Vector4d>.
const string vector_name("vector_name");
properties.AddProperty(group_name, vector_name, vector);
// - Get<Rgba>.
EXPECT_EQ(color, properties.GetProperty<Rgba>(group_name, vector_name));
// - Get<Vector4d>.
EXPECT_EQ(vector, properties.GetProperty<Vector4d>(group_name, vector_name));
}
} // namespace
} // namespace geometry
} // namespace drake