#include "drake/common/identifier.h" #include #include #include #include #include #include "absl/container/flat_hash_set.h" #include #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; using BId = Identifier; 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 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::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{}(a1_); absl::flat_hash_set::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 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> ids; EXPECT_EQ(ids.size(), 0u); ids.insert({a1_, a2_}); EXPECT_EQ(ids.size(), 1u); // An equivalent pair to what was inserted. SortedPair 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 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 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(), 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 () OP std::declval())> \ bool has_ ## OP_NAME ## _helper(int) { return true; } \ template \ bool has_ ## OP_NAME ## _helper(...) { return false; } \ template \ bool has_ ## OP_NAME() { return has_ ## OP_NAME ## _helper(1); } \ TEST_F(IdentifierTests, OP_NAME ## OperatorAvailiblity) { \ EXPECT_FALSE((has_ ## OP_NAME())); \ EXPECT_TRUE((has_ ## OP_NAME())); \ EXPECT_FALSE((has_ ## OP_NAME())); \ EXPECT_FALSE((has_ ## OP_NAME())); \ EXPECT_FALSE((has_ ## OP_NAME())); \ } 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, "Identifiers of different types should not be convertible."); static_assert(!std::is_convertible_v, "Identifiers should not be convertible to ints."); static_assert(!std::is_convertible_v, "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 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