forked from RobotLocomotion/drake
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathidentifier_test.cc
300 lines (258 loc) · 9.41 KB
/
identifier_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
#include "drake/common/identifier.h"
#include <set>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "absl/container/flat_hash_set.h"
#include <gtest/gtest.h>
#include "drake/common/sorted_pair.h"
#include "drake/common/test_utilities/expect_no_throw.h"
#include "drake/common/test_utilities/expect_throws_message.h"
namespace drake {
namespace {
// Creates various dummy index types to test.
using std::set;
using std::stringstream;
using std::unordered_set;
using std::unordered_map;
using AId = Identifier<class ATag>;
using BId = Identifier<class BTag>;
class IdentifierTests : public ::testing::Test {
protected:
// Configuration of test *case* variables (instead of per-test) is important.
// The tests can evaluate in any order and run in the same memory space.
// Those tests that depend on identifier *value* could easily become invalid
// based on execution order. This guarantees a fixed set of identifiers
// with known values.
static void SetUpTestCase() {
a1_ = AId::get_new_id(); // Should have the value 1.
a2_ = AId::get_new_id(); // Should have the value 2.
a3_ = AId::get_new_id(); // Should have the value 3.
b_ = BId::get_new_id(); // Should have the value 1.
}
static AId a1_;
static AId a2_;
static AId a3_;
static BId b_;
};
AId IdentifierTests::a1_;
AId IdentifierTests::a2_;
AId IdentifierTests::a3_;
BId IdentifierTests::b_;
// Verifies the copy constructor. This implicitly tests the expected property
// of the get_new_id() factory method and the get_value() method.
TEST_F(IdentifierTests, Constructor) {
EXPECT_EQ(a1_.get_value(), 1);
EXPECT_EQ(a2_.get_value(), 2);
EXPECT_EQ(a3_.get_value(), 3);
AId temp(a2_);
EXPECT_EQ(temp.get_value(), 2);
AId bad;
EXPECT_FALSE(bad.is_valid());
EXPECT_TRUE(a2_.is_valid());
}
// Confirms that assignment behaves correctly. This also implicitly tests
// equality and inequality.
TEST_F(IdentifierTests, AssignmentAndComparison) {
EXPECT_TRUE(a2_ != a3_);
AId temp = a2_;
EXPECT_TRUE(temp == a2_);
temp = a3_;
EXPECT_TRUE(temp == a3_);
}
// Check the specialized internal-use compare that can be used on an invalid
// Identifier.
TEST_F(IdentifierTests, InvalidOrSameComparison) {
AId same_as_a1 = a1_;
EXPECT_TRUE(same_as_a1.is_same_as_valid_id(a1_));
EXPECT_FALSE(a1_.is_same_as_valid_id(a2_));
AId invalid;
EXPECT_FALSE(invalid.is_same_as_valid_id(a1_));
}
// Confirms that ids are configured to serve as unique keys in
// STL containers.
TEST_F(IdentifierTests, ServeAsMapKey) {
unordered_set<AId> ids;
// This is a *different* id with the *same* value as a1. It should *not*
// introduce a new value to the set.
AId temp = a1_;
EXPECT_EQ(ids.size(), 0);
ids.insert(a1_);
EXPECT_NE(ids.find(a1_), ids.end());
EXPECT_NE(ids.find(temp), ids.end());
EXPECT_EQ(ids.size(), 1);
ids.insert(a2_);
EXPECT_EQ(ids.size(), 2);
ids.insert(temp);
EXPECT_EQ(ids.size(), 2);
EXPECT_EQ(ids.find(a3_), ids.end());
}
// Checks the abseil-specific hash function. When a class does not provide an
// abseil-specific hash function, abseil will fall back to invoking std::hash
// on the class to obtain a size_t hash value and then feeding that size_t into
// the abseil hasher. This leads to slow and bloated object code. We want to
// prove that our abseil-specific hash function is being used; we can do that
// by checking which hash value comes out of an absl container hasher.
TEST_F(IdentifierTests, AbslHash) {
// Compute the hash value used by an absl unordered container.
// We'll want to demonstrate that this is the specialized hash value.
absl::flat_hash_set<AId>::hasher absl_id_hasher;
const size_t absl_hash = absl_id_hasher(a1_);
// Compute the unspecialized hash value that would be seen in case absl
// delegated to the std hasher.
const size_t std_hash = std::hash<AId>{}(a1_);
absl::flat_hash_set<size_t>::hasher absl_uint_hasher;
const size_t absl_hash_via_std_hash = absl_uint_hasher(std_hash);
// To demonstrate that the specialization worked, the specialized hash must
// differ from the fallback hash.
EXPECT_NE(absl_hash, absl_hash_via_std_hash);
}
// Confirms that SortedPair<FooId> can serve as a key in STL containers.
// This shows that Identifier is not just hashable, but implicitly shows that
// it is compatible with the Drake hash mechanism (because it assumes that the
// SortedPair is compatible).
TEST_F(IdentifierTests, SortedPairAsKey) {
unordered_set<SortedPair<AId>> ids;
EXPECT_EQ(ids.size(), 0u);
ids.insert({a1_, a2_});
EXPECT_EQ(ids.size(), 1u);
// An equivalent pair to what was inserted.
SortedPair<AId> pair{a1_, a2_};
EXPECT_NE(ids.find(pair), ids.end());
}
// Confirms that ids are configured to serve as values in STL containers.
TEST_F(IdentifierTests, ServeAsMapValue) {
unordered_map<BId, AId> ids;
BId b1 = BId::get_new_id();
BId b2 = BId::get_new_id();
BId b3 = BId::get_new_id();
ids.emplace(b1, a1_);
ids.emplace(b2, a2_);
EXPECT_EQ(ids.find(b3), ids.end());
EXPECT_NE(ids.find(b2), ids.end());
EXPECT_NE(ids.find(b1), ids.end());
ids[b3] = a3_;
EXPECT_NE(ids.find(b3), ids.end());
}
// Confirms that ids can be put into a set.
TEST_F(IdentifierTests, PutInSet) {
set<AId> ids;
AId a1 = AId::get_new_id();
AId a2 = AId::get_new_id();
EXPECT_EQ(ids.size(), 0u);
ids.insert(a1);
EXPECT_EQ(ids.size(), 1u);
EXPECT_EQ(ids.count(a1), 1u);
ids.insert(a2);
EXPECT_EQ(ids.size(), 2u);
EXPECT_EQ(ids.count(a2), 1u);
ids.insert(a1);
EXPECT_EQ(ids.size(), 2u);
EXPECT_EQ(ids.count(a1), 1u);
}
// Tests the streaming behavior.
TEST_F(IdentifierTests, StreamOperator) {
stringstream ss;
ss << a2_;
EXPECT_EQ(ss.str(), "2");
}
// Tests the ability to convert the id to string via std::to_string.
TEST_F(IdentifierTests, ToString) {
using std::to_string;
EXPECT_EQ(to_string(a2_), to_string(a2_.get_value()));
}
// These tests confirm that behavior that *shouldn't* be compilable isn't.
// This code allows us to turn compile-time errors into run-time errors that
// we can incorporate in a unit test. The macro simplifies the boilerplate.
// This macro confirms binary operations are *valid* between two ids of
// the same type, but invalid between an id and objects of *any* other type.
// (Although the space of "all other types", is sparsely sampled).
//
// The use is:
// BINARY_TEST( op, op_name )
// It produces the templated method: has_op_name<T, U>(), which returns true
// if `t op u` is a valid operation for `T t` and `U u`.
//
// Examples of invocations:
// op | op_name
// ---------+-------------
// == | equals
// < | less_than
// + | add
#define BINARY_TEST(OP, OP_NAME) \
template <typename T, typename U, \
typename = decltype(std::declval<T>() OP std::declval<U>())> \
bool has_ ## OP_NAME ## _helper(int) { return true; } \
template <typename T, typename U> \
bool has_ ## OP_NAME ## _helper(...) { return false; } \
template <typename T, typename U> \
bool has_ ## OP_NAME() { return has_ ## OP_NAME ## _helper<T, U>(1); } \
TEST_F(IdentifierTests, OP_NAME ## OperatorAvailiblity) { \
EXPECT_FALSE((has_ ## OP_NAME<AId, BId>())); \
EXPECT_TRUE((has_ ## OP_NAME<AId, AId>())); \
EXPECT_FALSE((has_ ## OP_NAME<AId, int>())); \
EXPECT_FALSE((has_ ## OP_NAME<AId, size_t>())); \
EXPECT_FALSE((has_ ## OP_NAME<AId, int64_t>())); \
}
BINARY_TEST(==, Equals)
BINARY_TEST(!=, NotEquals)
BINARY_TEST(=, Assignment)
// This test should pass, as long as it compiles. If the Identifier class were
// to change and allow conversion between identifiers (or between identifiers
// and ints), this would fail to compile.
TEST_F(IdentifierTests, Convertible) {
static_assert(!std::is_convertible_v<AId, BId>,
"Identifiers of different types should not be convertible.");
static_assert(!std::is_convertible_v<AId, int>,
"Identifiers should not be convertible to ints.");
static_assert(!std::is_convertible_v<int, AId>,
"Identifiers should not be convertible from ints");
}
// Attempting to acquire the value is an error.
TEST_F(IdentifierTests, InvalidGetValueCall) {
if (kDrakeAssertIsDisarmed) { return; }
AId invalid;
DRAKE_EXPECT_THROWS_MESSAGE(
invalid.get_value(),
".*is_valid.*failed.*");
}
// Comparison of invalid ids is an error.
TEST_F(IdentifierTests, InvalidEqualityCompare) {
if (kDrakeAssertIsDisarmed) { return; }
AId invalid;
DRAKE_EXPECT_THROWS_MESSAGE(invalid == a1_, ".*is_valid.*failed.*");
}
// Comparison of invalid ids is an error.
TEST_F(IdentifierTests, InvalidInequalityCompare) {
if (kDrakeAssertIsDisarmed) { return; }
AId invalid;
DRAKE_EXPECT_THROWS_MESSAGE(invalid != a1_, ".*is_valid.*failed.*");
}
// Comparison of invalid ids is an error.
TEST_F(IdentifierTests, BadInvalidOrSameComparison) {
if (kDrakeAssertIsDisarmed) {
return;
}
AId invalid;
DRAKE_EXPECT_THROWS_MESSAGE(a1_.is_same_as_valid_id(invalid),
".*is_valid.*failed.*");
}
// Hashing an invalid id is *not* an error.
TEST_F(IdentifierTests, InvalidHash) {
if (kDrakeAssertIsDisarmed) {
return;
}
std::unordered_set<AId> ids;
AId invalid;
DRAKE_EXPECT_NO_THROW(ids.insert(invalid));
}
// Streaming an invalid id is an error.
TEST_F(IdentifierTests, InvalidStream) {
if (kDrakeAssertIsDisarmed) { return; }
AId invalid;
std::stringstream ss;
DRAKE_EXPECT_THROWS_MESSAGE(ss << invalid, ".*is_valid.*failed.*");
}
} // namespace
} // namespace drake