template<class T>
Corrade::TestSuite::Comparator class

Default comparator implementation.

See CORRADE_COMPARE_AS(), CORRADE_COMPARE_WITH() for more information and the Compare namespace for additional comparator implementations.

Subclassing

You can reimplement this class for your own data types to provide additional means of comparison. At the very least you need to provide the operator()() comparing two values of arbitrary types, returning empty ComparisonStatusFlags on success and ComparisonStatusFlag::Failed when the comparison fails. Then, printMessage() gets called in case of a comparison failure to print a detailed message. It receives stringified "actual" and "expected" expressions from the comparison macro and is expected to provide further details from its internal state saved by operator()().

Comparing with pseudo-types

Imagine you have two filenames and you want to compare their contents instead of comparing the filename strings. Because you want to also compare strings elsewhere, you cannot override the default behavior. The solution is to have some pseudo-type, for which you create a Comparator template specialization, but the actual comparison operator will still take strings as parameters:

class FileContents {};

namespace Corrade { namespace TestSuite { // the namespace is important

template<> class Comparator<FileContents> {
    public:
        ComparisonStatusFlags operator()(const std::string& actual, const std::string& expected) {
            _actualContents = Utility::Directory::readString(actual);
            _expectedContents = Utility::Directory::readString(expected);
            return _actualContents == _expectedContents ? ComparisonStatusFlags{} :
                ComparisonStatusFlag::Failed;
        }

        void printMessage(ComparisonStatusFlags flags, Utility::Debug& out, const char* actual, const char* expected) const {
            CORRADE_INTERNAL_ASSERT(flags & ComparisonStatusFlag::Failed);
            out << "Files" << actual << "and" << expected << "are not the same, actual" << _actualContents << "but expected" << _expectedContents;
        }

    private:
        std::string _actualContents, _expectedContents;
};

}}

The actual use in the unit test would be like this:

CORRADE_COMPARE_AS("/path/to/actual.dat", "/path/to/expected.dat",
    FileContents);

Passing parameters to comparators

Sometimes you need to pass additional parameters to the comparator class so you can then use it in the CORRADE_COMPARE_WITH() macro. In that case you need to implement the constructor and a comparator() function in your pseudo-type. The comparator() function returns a reference to a pre-configured Comparator instance. Don't forget to allow default construction of Comparator, if you want to be able to use it also with CORRADE_COMPARE_AS().Example:

class FileContents; // forward declaration to avoid a cyclic dependency

namespace Corrade { namespace TestSuite { // the namespace is important

template<> class Comparator<FileContents> {
    public:
        Comparator(const std::string& pathPrefix = {});
        ComparisonStatusFlags operator()(const std::string& actual, const std::string& expected);
        void printMessage(ComparisonStatusFlags flags, Utility::Debug& out, const std::string& actual, const std::string& expected) const;

        // ...
};

}}

class FileContents {
    public:
        explicit FileContents(const std::string& pathPrefix = {}): _c{pathPrefix} {}

        Corrade::TestSuite::Comparator<FileContents>& comparator() {
            return _c;
        }

    private:
        Corrade::TestSuite::Comparator<FileContents> _c;
};

The actual use in a test would be like this:

CORRADE_COMPARE_WITH("actual.dat", "expected.dat",
    FileContents{"/common/path/prefix"});

Printing additional messages

By default, the comparator is asked to print a message using printMessage() only in case the comparison fails. In some cases it's desirable to provide extended info also in case the comparison doesn't fail. That can be done by returning ComparisonStatusFlag::Warning, ComparisonStatusFlag::Message or ComparisonStatusFlag::Verbose from operator()() in addition to the actual comparison status. The printMessage() then gets passed those flags to know what to print.

The printMessage() is always called at most once per comparison, with the ComparisonStatusFlag::Verbose flag filtered out when the --verbose command-line option is not passed.

Saving diagnostic files

In addition to messages, the comparison can also save diagnostic files. This is achieved by returning either ComparisonStatusFlag::Diagnostic or ComparisonStatusFlag::VerboseDiagnostic from operator()(). The comparator is then required to implement the saveDiagnostic() function (which doesn't need to be present otherwise). This function gets called when the --save-diagnostic command-line option, is specified, in case of ComparisonStatusFlag::VerboseDiagnostic the flag only when --verbose is enabled as well.

The function receives a path to where the diagnostic files should be be saved and is free to do about anything – for example a file comparator can write both the actual file and a diff to the original. The message is printed right after test case name and the comparator has a full freedom in its formatting as well.

template<> class Comparator<FileContents> {
    public:
        ComparisonStatusFlags operator()(const std::string& actual, const std::string& expected);

        // ...

        void saveDiagnostic(ComparisonStatusFlags flags, Utility::Debug& out, const std::string& path) {
            CORRADE_INTERNAL_ASSERT(flags & ComparisonStatusFlag::Diagnostic);
            std::string filename = Utility::Directory::join(path, _expectedFilename);
            if(Utility::Directory::writeString(filename, _actualContents))
                out << "->" << filename;
        }

    private:
        std::string _actualContents;
        // ...
        std::string _expectedFilename;
};

In the above case, the message will look for example like this:

$ ./MyTest --save-diagnostic some/path
Starting MyTest with 1 test cases...
  FAIL [1] generateFile() at MyTest.cpp on line 73
        Files "a.txt" and "expected.txt" are different, actual ABC but expected abc
 SAVED [1] generateFile() -> some/path/expected.txt
Finished MyTest with 1 errors out of 1 checks. 1 check saved a diagnostic file.

Public functions

auto operator()(const T& actual, const T& expected) -> ComparisonStatusFlags
Compare two values.
void printMessage(ComparisonStatusFlags status, Utility::Debug& out, const char* actual, const char* expected)
Print a message.
void saveDiagnostic(ComparisonStatusFlags status, Utility::Debug& out, const std::string& path)
Save a diagnostic.

Function documentation

template<class T>
ComparisonStatusFlags Corrade::TestSuite::Comparator<T>::operator()(const T& actual, const T& expected)

Compare two values.

If the comparison fails, ComparisonStatusFlag::Failed should be returned. In addition, if the comparison desires to print additional messages or save diagnostic file, it can include other flags.

template<class T>
void Corrade::TestSuite::Comparator<T>::printMessage(ComparisonStatusFlags status, Utility::Debug& out, const char* actual, const char* expected)

Print a message.

This function gets called only if operator()() returned one of ComparisonStatusFlag::Failed, ComparisonStatusFlag::Warning, ComparisonStatusFlag::Message or ComparisonStatusFlag::Verbose. The status is a result of that call. The ComparisonStatusFlag::Verbose flag gets filtered out in case the --verbose command-line option is not set (and the function not being called at all if none of the other above-mentioned flags are present).

template<class T>
void Corrade::TestSuite::Comparator<T>::saveDiagnostic(ComparisonStatusFlags status, Utility::Debug& out, const std::string& path)

Save a diagnostic.

This function only needs to be present in the comparator implementation if operator()() can return either ComparisonStatusFlag::Diagnostic or ComparisonStatusFlag::VerboseDiagnostic, doesn't need to be implemented at all otherwise. This function gets called only if operator()() returned one of those two flags and the --save-diagnostic command-line option is present. The status is a result of that call. The ComparisonStatusFlag::VerboseDiagnostic flag gets filtered out in case the --verbose command-line option is not set (and the function not being called at all if ComparisonStatusFlag::Diagnostic is not present as well).