diff --git a/CppcheckSuppressions.txt b/CppcheckSuppressions.txt index 33e28c23..b48f5382 100644 --- a/CppcheckSuppressions.txt +++ b/CppcheckSuppressions.txt @@ -3,6 +3,6 @@ internalAstError:* missingIncludeSystem:* normalCheckLevelMaxBranches:* nullPointer:* -returnDanglingLifetime:ZenUnitAndMetalMock\ZenUnit.h:3548 +returnDanglingLifetime:* unusedFunction:* useStlAlgorithm:* diff --git a/ZenUnitAndMetalMock/ZenUnit.h b/ZenUnitAndMetalMock/ZenUnit.h index b613831d..a53460d1 100644 --- a/ZenUnitAndMetalMock/ZenUnit.h +++ b/ZenUnitAndMetalMock/ZenUnit.h @@ -356,6 +356,10 @@ Example ZenUnit command line arguments: ZenUnit::SETS_ARE_EQUAL_Defined(expectedSet, #expectedSet, actualSet, #actualSet, \ ZENUNIT_FILELINE, ZENUNIT_VA_ARGS_TEXT(__VA_ARGS__), ##__VA_ARGS__) +// Asserts that two std::spans have equal sizes and equal elements according to ARE_EQUAL(expectedElement, actualElement) assertions. +#define SPANS_ARE_EQUAL(expectedSpan, actualSpan, ...) \ + ZenUnit::SPANS_ARE_EQUAL_Defined(expectedSpan, #expectedSpan, actualSpan, #actualSpan, ZENUNIT_FILELINE) + // Asserts that the elements of expectedMap are equal to the elements of actualMap according to ARE_EQUAL(expectedElement, actualElement) assertions. #define MAPS_ARE_EQUAL(expectedMap, actualMap, ...) \ ZenUnit::MAPS_ARE_EQUAL_Defined(expectedMap, #expectedMap, actualMap, #actualMap, \ @@ -4216,6 +4220,70 @@ namespace ZenUnit throw anomaly; } + template + NOINLINE void SPANS_ARE_EQUAL_ThrowAnomaly( + const Anomaly& becauseAnomaly, + const std::span& expectedSpan, const char* expectedSpanText, + const std::span& actualSpan, const char* actualSpanText, + FilePathLineNumber filePathLineNumber) + { + const std::string toStringedExpectedSpan = ToStringer::ToString(expectedSpan); + const std::string toStringedActualSpan = ToStringer::ToString(actualSpan); + const Anomaly anomaly( + "SPANS_ARE_EQUAL", + expectedSpanText, + actualSpanText, + "", + "", + becauseAnomaly, + toStringedExpectedSpan, + toStringedActualSpan, + ExpectedActualFormat::Fields, filePathLineNumber); + throw anomaly; + } + + template + void SPANS_ARE_EQUAL_Defined( + const std::span& expectedSpan, const char* expectedSpanText, + const std::span& actualSpan, const char* actualSpanText, + FilePathLineNumber filePathLineNumber) + { + try + { + ARE_EQUAL(expectedSpan.size(), actualSpan.size()); + } + catch (const Anomaly& becauseAnomaly) + { + SPANS_ARE_EQUAL_ThrowAnomaly( + becauseAnomaly, + expectedSpan, expectedSpanText, + actualSpan, actualSpanText, + filePathLineNumber); + } + const size_t expectedSpanSize = expectedSpan.size(); + constexpr size_t IEqualsSignLength = 2; + constexpr size_t SizeTMaxValueLength = 21; // strlen("18446744073709551615") + char indexMessage[IEqualsSignLength + SizeTMaxValueLength]{ "i=" }; + for (size_t i = 0; i < expectedSpanSize; ++i) + { + const T& ithExpectedElement = expectedSpan[i]; + const T& ithActualElement = actualSpan[i]; + WriteIntegerToCharArray(i, indexMessage + IEqualsSignLength); + try + { + ARE_EQUAL(ithExpectedElement, ithActualElement, indexMessage); + } + catch (const Anomaly& becauseAnomaly) + { + SPANS_ARE_EQUAL_ThrowAnomaly( + becauseAnomaly, + expectedSpan, expectedSpanText, + actualSpan, actualSpanText, + filePathLineNumber); + } + } + } + template< template class IndexableType, @@ -7435,6 +7503,16 @@ or change TEST(TestName) to TESTNXN(TestName, ...), where N can be 1 through 10, } }; + template + class Printer> + { + public: + static void Print(std::ostream& os, const std::span& sp) + { + PrintCollection(os, sp); + } + }; + template class Printer> { diff --git a/ZenUnitUtilsAndAssertionTests/Assertions/SPANS_ARE_EQUALTests.cpp b/ZenUnitUtilsAndAssertionTests/Assertions/SPANS_ARE_EQUALTests.cpp new file mode 100644 index 00000000..659914f9 --- /dev/null +++ b/ZenUnitUtilsAndAssertionTests/Assertions/SPANS_ARE_EQUALTests.cpp @@ -0,0 +1,156 @@ +#include "pch.h" + +namespace ZenUnit +{ + template + TEMPLATE_TESTS(SPANS_ARE_EQUALTests, T) + AFACT(ConstEmptySpans_DoesNothing) + AFACT(NonConstEmptySpans_DoesNothing) + AFACT(ConstNonEmptySpans_AllElementsEqual_DoesNothing) + AFACT(ConstNonEmptySpans_SizesAreNotEqual_ThrowsAnomaly) + AFACT(ConstNonEmptySpans_SizesAreEqual_ElementsAreNotEqual_ThrowsAnomaly__TestCase1) + AFACT(ConstNonEmptySpans_SizesAreEqual_ElementsAreNotEqual_ThrowsAnomaly__TestCase2) + AFACT(NonConstNonEmptySpans_SizesAreEqual_ElementsAreNotEqual_ThrowsAnomaly) + EVIDENCE + + const string TypeName = *Type::GetName(); + const string _constSpanTypeName = *Type::GetName>(); + const string _nonConstSpanTypeName = *Type::GetName>(); + + TEST(ConstEmptySpans_DoesNothing) + { + const span expectedSpan; + const span actualSpan; + SPANS_ARE_EQUAL(expectedSpan, actualSpan); + } + + TEST(NonConstEmptySpans_DoesNothing) + { + const span expectedSpan; + const span actualSpan; + SPANS_ARE_EQUAL(expectedSpan, actualSpan); + } + + TEST(ConstNonEmptySpans_AllElementsEqual_DoesNothing) + { + const vector expectedElements{ 1, 2, 3 }; + const vector actualElements{ 1, 2, 3 }; + + const span expectedSpan = expectedElements; + const span actualSpan = actualElements; + SPANS_ARE_EQUAL(expectedSpan, actualSpan); + } + + TEST(ConstNonEmptySpans_SizesAreNotEqual_ThrowsAnomaly) + { + const vector expectedElements; + const vector actualElements{ 1 }; + + const span expectedSpan = expectedElements; + const span actualSpan = actualElements; + + THROWS_EXCEPTION(SPANS_ARE_EQUAL(expectedSpan, actualSpan), + Anomaly, TestUtil::NewlineConcat("", +" Failed: SPANS_ARE_EQUAL(expectedSpan, actualSpan)", +"Expected: " + _constSpanTypeName + " (size 0):", +"{", +"}", +" Actual: " + _constSpanTypeName + " (size 1):", +"{", +" 1", +"}", +" Because: ARE_EQUAL(expectedSpan.size(), actualSpan.size()) failed", +"Expected: 0", +" Actual: 1", +"File.cpp(1)", +"File.cpp(1)")); + } + + TEST(ConstNonEmptySpans_SizesAreEqual_ElementsAreNotEqual_ThrowsAnomaly__TestCase1) + { + const vector expectedElements{ 0 }; + const vector actualElements{ 1 }; + + const span expectedSpan = expectedElements; + const span actualSpan = actualElements; + + THROWS_EXCEPTION(SPANS_ARE_EQUAL(expectedSpan, actualSpan), + Anomaly, TestUtil::NewlineConcat("", +" Failed: SPANS_ARE_EQUAL(expectedSpan, actualSpan)", +"Expected: " + _constSpanTypeName + " (size 1):", +"{", +" 0", +"}", +" Actual: " + _constSpanTypeName + " (size 1):", +"{", +" 1", +"}", +" Because: ARE_EQUAL(ithExpectedElement, ithActualElement, indexMessage) failed", +"Expected: 0", +" Actual: 1", +" Message: \"i=0\"", +"File.cpp(1)", +"File.cpp(1)")); + } + + TEST(ConstNonEmptySpans_SizesAreEqual_ElementsAreNotEqual_ThrowsAnomaly__TestCase2) + { + const vector expectedElements{ 0, 1, 20 }; + const vector actualElements{ 0, 1, 30 }; + + const span expectedSpan = expectedElements; + const span actualSpan = actualElements; + + THROWS_EXCEPTION(SPANS_ARE_EQUAL(expectedSpan, actualSpan), + Anomaly, TestUtil::NewlineConcat("", +" Failed: SPANS_ARE_EQUAL(expectedSpan, actualSpan)", +"Expected: " + _constSpanTypeName + " (size 3):", +"{", +" 0,", +" 1,", +" 20", +"}", +" Actual: " + _constSpanTypeName + " (size 3):", +"{", +" 0,", +" 1,", +" 30", +"}", +" Because: ARE_EQUAL(ithExpectedElement, ithActualElement, indexMessage) failed", +"Expected: 20", +" Actual: 30", +" Message: \"i=2\"", +"File.cpp(1)", +"File.cpp(1)")); + } + + TEST(NonConstNonEmptySpans_SizesAreEqual_ElementsAreNotEqual_ThrowsAnomaly) + { + vector expectedElements{ 0 }; + vector actualElements{ 1 }; + + const span expectedSpan = expectedElements; + const span actualSpan = actualElements; + + THROWS_EXCEPTION(SPANS_ARE_EQUAL(expectedSpan, actualSpan), + Anomaly, TestUtil::NewlineConcat("", +" Failed: SPANS_ARE_EQUAL(expectedSpan, actualSpan)", +"Expected: " + _nonConstSpanTypeName + " (size 1):", +"{", +" 0", +"}", +" Actual: " + _nonConstSpanTypeName + " (size 1):", +"{", +" 1", +"}", +" Because: ARE_EQUAL(ithExpectedElement, ithActualElement, indexMessage) failed", +"Expected: 0", +" Actual: 1", +" Message: \"i=0\"", +"File.cpp(1)", +"File.cpp(1)")); + } + + RUN_TEMPLATE_TESTS(SPANS_ARE_EQUALTests, int) + THEN_RUN_TEMPLATE_TESTS(SPANS_ARE_EQUALTests, unsigned long long) +}