In Short
Construct one foo::list
and one std::list
and then compare them as you perform operations on them. Really the only difference from a normal unit test is that you have two containers and instead of directly using REQUIRE()
for each operation on the type you are testing, you perform the operation on the type you are testing and the reference type and then compare them. For this we assume that std::list
or whatever is bug-free. We then use it as our reference point for not failing. In other words, if the operation succeeds with std::list
and succeeds with foo::list
, and they compare equal, the operation succeeded.
An Example
You know what the subset of operations are that you can use to compare state and I do not, so here's a mock comparison function
template <class T, class U>
bool compare_types(const T &t, const U &u)
{
bool equivalent = true;
//Generic comparisons here, like iterating over the elements to compare their values.
//Of course update equal or just return false if the comparison fails.
//If your types are not containers, perform whatever is needed to test equivalent state.
return equivalent;
}
As Jarod42 pointed out, this can get more fun and more generic, particularly if the Op f
below is a lambda (C++14 needed for generic lambdas):
template <class ValueT, class RefT, class TestT, class Op>
bool compare_op_with_value(RefT &t, TestT &u, Op f, const ValueT &value)
{
if (!compare_types(t, u))
return false;
f(t, value);
f(u, value);
return compare_types(t, u);
}
Your function may return a value:
template <class ValueT, class RefT, class TestT, class Op>
bool compare_op_with_ret(RefT &t, TestT &u, Op f)
{
if (!compare_types(t, u))
return false;
ValueT ret1 = f(t);
ValueT ret2 = f(u);
return ret1 == ret2 && compare_types(t, u);
}
...and so on for dereferenceable return types, etc. You'll need to write a new comparison function for each kind of test, but that's pretty trivial. You'll need to add another template parameter for return types that are not the same (e.g. an iterator).
Then you need your test case (I subbed in std::vector
as foo::list
for exposition)...
TEMPLATE_TEST_CASE("StdFooCompare", "[list]", int)
{
using std_type = std::list<TestType>;
using foo_type = std::vector<TestType>;
auto initializer = {0,1,2,3,4};
std_type list1 = initializer;
foo_type list2 = initializer;
//testing insertion, using auto since insert() returns iterators
auto insert_f = [](auto list, TestType value) -> auto {
return list.insert(list.begin(), value);
};
REQUIRE(compare_op_with_value(list1, list2, insert_f, -1));
//testing front(), being explicit about the return type rather than using auto
auto front_f = [](auto list) -> TestType & {
return list.front();
};
REQUIRE(compare_op_with_ret<TestType>(list1, list2, front_f));
//continue testing along these lines
}
I could spend a couple more hours on this, but I hope you get the idea. I spent more time on this.
Note: I did not actually run this code, so consider it all pseudo-code to get the idea across, e.g. I may have missed a semicolon or some such.