diff --git a/src/Corrade/TestSuite/CMakeLists.txt b/src/Corrade/TestSuite/CMakeLists.txt index 1a3f3be8d..55ec26064 100644 --- a/src/Corrade/TestSuite/CMakeLists.txt +++ b/src/Corrade/TestSuite/CMakeLists.txt @@ -32,6 +32,7 @@ set(CorradeTestSuite_SRCS Comparator.cpp Tester.cpp + Compare/Container.cpp Compare/File.cpp Compare/FileToString.cpp Compare/FloatingPoint.cpp diff --git a/src/Corrade/TestSuite/Comparator.cpp b/src/Corrade/TestSuite/Comparator.cpp index 07f175590..d55870d1c 100644 --- a/src/Corrade/TestSuite/Comparator.cpp +++ b/src/Corrade/TestSuite/Comparator.cpp @@ -27,6 +27,7 @@ #include "Comparator.h" #include "Corrade/Containers/EnumSet.hpp" +#include "Corrade/Containers/StringView.h" namespace Corrade { namespace TestSuite { @@ -57,4 +58,22 @@ Utility::Debug& operator<<(Utility::Debug& debug, const ComparisonStatusFlags va ComparisonStatusFlag::VerboseDiagnostic}); } +namespace Implementation { + +void ComparatorBase::printMessage(ComparisonStatusFlags, Utility::Debug& out, const char* const actual, const char* const expected, void(*printer)(Utility::Debug&, const void*)) const { + CORRADE_INTERNAL_ASSERT(actualValue && expectedValue); + out << "Values" << actual << "and" << expected << "are not the same, actual is\n "; + printer(out, actualValue); + out << Utility::Debug::newline << " but expected\n "; + printer(out, expectedValue); +} + +/* LCOV_EXCL_START */ +void ComparatorBase::saveDiagnostic(ComparisonStatusFlags, Utility::Debug&, Containers::StringView) { + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); +} +/* LCOV_EXCL_STOP */ + +} + }} diff --git a/src/Corrade/TestSuite/Comparator.h b/src/Corrade/TestSuite/Comparator.h index d0905006f..8a8b7b691 100644 --- a/src/Corrade/TestSuite/Comparator.h +++ b/src/Corrade/TestSuite/Comparator.h @@ -110,6 +110,21 @@ CORRADE_TESTSUITE_EXPORT Utility::Debug& operator<<(Utility::Debug& debug, Compa /** @debugoperatorenum{ComparisonStatusFlags} */ CORRADE_TESTSUITE_EXPORT Utility::Debug& operator<<(Utility::Debug& debug, ComparisonStatusFlags value); +namespace Implementation { + +class CORRADE_TESTSUITE_EXPORT ComparatorBase { + public: + void saveDiagnostic(ComparisonStatusFlags status, Utility::Debug& out, Containers::StringView path); // TODO why even this?? + + protected: + void printMessage(ComparisonStatusFlags status, Utility::Debug& out, const char* actual, const char* expected, void(*printer)(Utility::Debug&, const void*)) const; + + const void* actualValue{}; + const void* expectedValue{}; +}; + +} + /** @brief Default comparator implementation @@ -202,10 +217,8 @@ In the above case, the message will look for example like this: @include testsuite-save-diagnostic.ansi */ -template class Comparator { +template class Comparator: public Implementation::ComparatorBase { public: - explicit Comparator(); - /** * @brief Compare two values * @@ -230,6 +243,8 @@ template class Comparator { */ void printMessage(ComparisonStatusFlags status, Utility::Debug& out, const char* actual, const char* expected); + /* Defined in the base class already */ + #ifdef DOXYGEN_GENERATING_OUTPUT /** * @brief Save a diagnostic * @@ -247,20 +262,10 @@ template class Comparator { * (and the function not being called at all if * @ref ComparisonStatusFlag::Diagnostic is not present as well). */ - #ifdef DOXYGEN_GENERATING_OUTPUT void saveDiagnostic(ComparisonStatusFlags status, Utility::Debug& out, Containers::StringView path); - #else - /* using const& to avoid having to include StringView.h */ - void saveDiagnostic(ComparisonStatusFlags status, Utility::Debug& out, const Containers::StringView& path); #endif - - private: - const T* actualValue; - const T* expectedValue; }; -template Comparator::Comparator(): actualValue(), expectedValue() {} - template ComparisonStatusFlags Comparator::operator()(const T& actual, const T& expected) { if(actual == expected) return {}; @@ -269,17 +274,12 @@ template ComparisonStatusFlags Comparator::operator()(const T& actua return ComparisonStatusFlag::Failed; } -template void Comparator::printMessage(ComparisonStatusFlags, Utility::Debug& out, const char* actual, const char* expected) { - CORRADE_INTERNAL_ASSERT(actualValue && expectedValue); - out << "Values" << actual << "and" << expected << "are not the same, actual is\n " - << *actualValue << Utility::Debug::newline << " but expected\n " << *expectedValue; -} - -/* LCOV_EXCL_START */ -template void Comparator::saveDiagnostic(ComparisonStatusFlags, Utility::Debug&, const Containers::StringView&) { - CORRADE_INTERNAL_ASSERT_UNREACHABLE(); +template void Comparator::printMessage(const ComparisonStatusFlags status, Utility::Debug& out, const char* const actual, const char* const expected) { + Implementation::ComparatorBase::printMessage(status, out, actual, expected, + [](Utility::Debug& out, const void* value) { + out << *static_cast(value); + }); } -/* LCOV_EXCL_STOP */ namespace Implementation { diff --git a/src/Corrade/TestSuite/Compare/Container.cpp b/src/Corrade/TestSuite/Compare/Container.cpp new file mode 100644 index 000000000..856b0bd05 --- /dev/null +++ b/src/Corrade/TestSuite/Compare/Container.cpp @@ -0,0 +1,62 @@ +/* + This file is part of Corrade. + + Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, + 2017, 2018, 2019, 2020, 2021, 2022 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Container.h" + +namespace Corrade { namespace TestSuite { namespace Implementation { + +void ContainerComparatorBase::printMessage(ComparisonStatusFlags, Utility::Debug& out, const char* const actual, const char* const expected, void(*printer)(Utility::Debug&, const void*), void(*itemPrinter)(Utility::Debug&, const void*, std::size_t)) const { + CORRADE_INTERNAL_ASSERT(_actualContents && _expectedContents); + + out << "Containers" << actual << "and" << expected << "have different"; + if(_actualContentsSize != _expectedContentsSize) + out << "size, actual" << _actualContentsSize << "but" << _expectedContentsSize << "expected. Actual contents:\n "; + else + out << "contents, actual:\n "; + + printer(out, _actualContents); + out << Utility::Debug::newline << " but expected\n "; + printer(out, _expectedContents); + out << Utility::Debug::newline << " "; + + if(_actualContentsSize <= _firstDifferent) { + out << "Expected has"; + itemPrinter(out, _expectedContents, _firstDifferent); + } else if(_expectedContentsSize <= _firstDifferent) { + out << "Actual has"; + itemPrinter(out, _actualContents, _firstDifferent); + } else { + out << "Actual"; + itemPrinter(out, _actualContents, _firstDifferent); + out << "but"; + itemPrinter(out, _expectedContents, _firstDifferent); + out << "expected"; + } + + out << "on position" << _firstDifferent << Utility::Debug::nospace << "."; +} + +}}} diff --git a/src/Corrade/TestSuite/Compare/Container.h b/src/Corrade/TestSuite/Compare/Container.h index 31c2d07e5..f5993fff8 100644 --- a/src/Corrade/TestSuite/Compare/Container.h +++ b/src/Corrade/TestSuite/Compare/Container.h @@ -59,58 +59,65 @@ template class Container {}; } +namespace Implementation { + +class CORRADE_TESTSUITE_EXPORT ContainerComparatorBase { + protected: + void printMessage(ComparisonStatusFlags status, Utility::Debug& out, const char* actual, const char* expected, void(*printer)(Utility::Debug&, const void*), void(*itemPrinter)(Utility::Debug&, const void*, std::size_t)) const; + + const void* _actualContents{}; + const void* _expectedContents{}; + std::size_t _actualContentsSize{}; + std::size_t _expectedContentsSize{}; + std::size_t _firstDifferent{}; +}; + +} + #ifndef DOXYGEN_GENERATING_OUTPUT -template class Comparator> { +template class Comparator>: public Implementation::ContainerComparatorBase { public: ComparisonStatusFlags operator()(const T& actual, const T& expected); void printMessage(ComparisonStatusFlags, Utility::Debug& out, const char* actual, const char* expected) const; - - private: - const T* _actualContents; - const T* _expectedContents; }; template ComparisonStatusFlags Comparator>::operator()(const T& actual, const T& expected) { _actualContents = &actual; _expectedContents = &expected; + _actualContentsSize = actual.size(); + _expectedContentsSize = expected.size(); + + ComparisonStatusFlags status; + if(_actualContentsSize != _expectedContentsSize) + status = ComparisonStatusFlag::Failed; + + /* Recursively use the comparator on the values, find the first different + item in the common prefix. If there's none, then the first different + item is right after the common prefix, and if both have the same size + then it means the containers are the same. */ + Comparator::type> comparator; + const std::size_t commonPrefixSize = Utility::min(_actualContentsSize, _expectedContentsSize); + _firstDifferent = commonPrefixSize; + for(std::size_t i = 0; i != commonPrefixSize; ++i) { + if(comparator(actual[i], expected[i]) & ComparisonStatusFlag::Failed) { + _firstDifferent = i; + status = ComparisonStatusFlag::Failed; + break; + } + } - if(_actualContents->size() != _expectedContents->size()) - return ComparisonStatusFlag::Failed; - - /* Recursively use comparator on the values */ - Comparator::type> comparator; - for(std::size_t i = 0; i != _actualContents->size(); ++i) - if(comparator((*_actualContents)[i], (*_expectedContents)[i]) & ComparisonStatusFlag::Failed) - return ComparisonStatusFlag::Failed; - - return {}; + return status; } -template void Comparator>::printMessage(ComparisonStatusFlags, Utility::Debug& out, const char* actual, const char* expected) const { - out << "Containers" << actual << "and" << expected << "have different"; - if(_actualContents->size() != _expectedContents->size()) - out << "size, actual" << _actualContents->size() << "but" << _expectedContents->size() << "expected. Actual contents:\n "; - else - out << "contents, actual:\n "; - - out << *_actualContents << Utility::Debug::newline << " but expected\n " << *_expectedContents << Utility::Debug::newline << " "; - - Comparator::type> comparator; - for(std::size_t i = 0, end = Utility::max(_actualContents->size(), _expectedContents->size()); i != end; ++i) { - if(_actualContents->size() > i && _expectedContents->size() > i && - !(comparator((*_actualContents)[i], (*_expectedContents)[i]) & ComparisonStatusFlag::Failed)) continue; - - if(_actualContents->size() <= i) - out << "Expected has" << (*_expectedContents)[i]; - else if(_expectedContents->size() <= i) - out << "Actual has" << (*_actualContents)[i]; - else - out << "Actual" << (*_actualContents)[i] << "but" << (*_expectedContents)[i] << "expected"; - - out << "on position" << i << Utility::Debug::nospace << "."; - break; - } +template void Comparator>::printMessage(const ComparisonStatusFlags status, Utility::Debug& out, const char* actual, const char* expected) const { + Implementation::ContainerComparatorBase::printMessage(status, out, actual, expected, + [](Utility::Debug& out, const void* contents) { + out << *static_cast(contents); + }, + [](Utility::Debug& out, const void* contents, std::size_t i) { + out << (*static_cast(contents))[i]; + }); } #endif