test-helper for comparison with expected (string) result

...this is something I should have done since YEARS, really...

Whenever working with symbolically represented data, tests
typically involve checking *hundreds* of expected results,
and thus it can be really hard to find out where the
failure actually happens; it is better for readability
to have the expected result string immediately in the
test code; now this expected result can be marked
with a user-defined literal, and then on mismatch
the expected and the real value will be printed.
This commit is contained in:
Fischlurch 2023-05-04 00:48:29 +02:00
parent 476c0f6493
commit 00ca84a2aa
5 changed files with 175 additions and 33 deletions

View file

@ -33,6 +33,7 @@
#include "lib/test/test-helper.hpp"
#include "lib/test/testdummy.hpp"
#include "lib/format-string.hpp"
#include "lib/format-cout.hpp"
#include "lib/unique-malloc-owner.hpp"
#include <string>
@ -42,6 +43,12 @@ using std::string;
namespace lib {
namespace test{
/** storage for test-dummy flags */
long Dummy::_local_checksum = 0;
bool Dummy::_throw_in_ctor = false;
string
@ -54,7 +61,6 @@ namespace test{
/** @todo probably this can be done in a more clever way. Anyone...?
*/
string
@ -72,9 +78,24 @@ namespace test{
/** storage for test-dummy flags */
long Dummy::_local_checksum = 0;
bool Dummy::_throw_in_ctor = false;
/**
* @internal check equality and print difference
* @remark defined here to avoid inclusion of `<iostream>` in header
*/
bool
ExpectString::verify (std::string const& actual) const
{
std::string const& expected{*this}; // to avoid endless recursion
bool expectationMatch {actual == expected};
if (not expectationMatch)
{
cerr << "FAIL___expectation___________"
<< "\nexpect:"<<expected
<< "\nactual:"<<actual
<< endl;
}
return expectationMatch;
}
}} // namespace lib::test

View file

@ -253,7 +253,6 @@ namespace test{
/** create a random but not insane Time value */
inline lib::time::Time
randTime ()
@ -267,10 +266,59 @@ namespace test{
string randStr (size_t len);
/**
* Helper to produce better diagnostic messages when comparing
* to an expected result string. This type can be used to mark a
* `std::string` in order to invoke a special rigged equality test.
* The counterpart for equality conversion can be any arbitrary type,
* on which some kind of _string conversion_ can be performed
* @see format-obj.hpp
*/
class ExpectString
: public std::string
{
using std::string::string;
template<typename X>
friend bool
operator== (X const& x, ExpectString const& expected)
{
std::string actual{util::StringConv<X>::invoke (x)};
return expected.verify (actual);
}
template<typename X>
friend bool
operator== (ExpectString const& expected, X const& x)
{
std::string actual{util::StringConv<X>::invoke (x)};
return expected.verify (actual);
}
bool verify (std::string const& actual) const;
};
}} // namespace lib::test
/**
* user defined literal for expected result strings.
* On equality comparison to any other string convertible object,
* the difference to this expected string is printed to STDERR
*
* @example
* \code
* CHECK (result23 == "[-100..100]"_expect);
* \endcode
*/
inline lib::test::ExpectString
operator""_expect (const char* lit, size_t siz)
{
return lib::test::ExpectString{lit, siz};
}
/* === test helper macros === */

View file

@ -585,6 +585,11 @@ return: 0
END
PLANNED "Split/Splice segmentation" SplitSplice_test <<END
return: 0
END
TEST "Symbol_test" Symbol_test <<END
out: one
out: sizeof. Literal.+ = (4|8)

View file

@ -26,8 +26,8 @@
#include "lib/error.hpp"
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/format-cout.hpp"
#include "lib/format-util.hpp"
#include "lib/format-string.hpp"
@ -47,9 +47,15 @@ namespace test {
using util::isnil;
using std::string;
using std::move;
namespace {// Test Fixture....
/**
* Test Dummy: a "segment" representing an integer interval.
* Memory management can be tracked since each instance gets a
* distinct ID number. Moreover, a Seg can be marked as "empty",
* which is visible on the `string` conversion
*/
struct Seg
: util::MoveOnly
{
@ -63,12 +69,14 @@ namespace test {
, empty{nil}
, id{++idGen}
{
++cnt;
check += id;
}
~Seg()
{
check -= id;
if (id) --cnt;
}
Seg (Seg&& rr)
@ -89,23 +97,30 @@ namespace test {
;
}
static size_t check;
private:
//-- Diagnostics --
uint id;
static uint idGen;
static size_t cnt;
static size_t check;
};
// Storage for static ID-Generator
size_t Seg::check{0};
size_t Seg::cnt{0};
uint Seg::idGen{0};
const int SMIN = -100;
const int SMAX = +100;
/**
* Test-Segmentation comprised of a sequence of Seg entries
* Test-Segmentation comprised of a sequence of Seg entries.
* It can be checked for _validity_ according to the following conditions
* - the segmentation spans the complete range -100 ... +100
* - segments follow each other _seamlessly_
* - the bounds within each segment are ordered ascending
* - segments are not empty (i.e. start differs from end)
* The assessment of this validity conditions is appended on the string conversion.
*/
struct SegL
: std::list<Seg>
@ -130,6 +145,18 @@ namespace test {
// using standard copy
operator string() const
{
return renderContent() + assess();
}
bool
isValid() const
{
return isnil (this->assess());
}
string
renderContent() const
{
return ""+util::join(*this,"")+"";
}
@ -160,19 +187,13 @@ namespace test {
}
return diagnosis;
}
bool
isValid() const
{
return isnil (this->assess());
}
};
}//(End)Test Fixture
/****************************************************************************//**
* @test verify proper working of a generic procedure to splice an interval
* into a complete segmentation of an ordered axis into seamless intervals.
@ -210,29 +231,54 @@ namespace test {
verify_testFixture()
{
{
Seg x{1,3}, u{2,4,true};
cout << x << u << Seg::check<<endl;
Seg z{move(u)};
cout <<u<<z<< Seg::check<<endl;
Seg x{1,3}; // a segment 1 (inclusive) to 3 (exclusive)
Seg u{2,4,true}; // an "empty" segment 2 (incl) to 4 (excl)
CHECK (x == "[1_3["_expect);
CHECK (u == "[2~4["_expect); // "empty" interval is marked with '~' in string stylisation
CHECK (3 == Seg::check);
CHECK (2 == Seg::cnt);
SegL l1;
Seg z{move(u)};
CHECK (z == "[2~4["_expect);
CHECK (3 == Seg::check);
CHECK (2 == Seg::cnt); // the "dead" instance u is not counted
CHECK (0 == u.id); // (its ID has been reset to zero in move-ctor)
CHECK (2 == z.id);
SegL l1; // default ctor always adds an empty base segment -100 ... +100
SegL l2{3};
SegL l3{5,-5,10};
CHECK (l1 == "├[-100~100[┤"_expect);
CHECK (l2 == "├[-100~3[[3~100[┤"_expect);
CHECK (l3 == "├[-100~5[[5_-5[[-5_10[[10~100[┤!order_5>-5_!"_expect);
cout <<"l1:"<<l1<<l1.assess()<<endl;
cout <<"l2:"<<l2<<l2.assess()<<endl;
cout <<"l3:"<<l3<<l3.assess()<<endl;
CHECK (l1.isValid());
CHECK (l2.isValid());
CHECK (not l3.isValid()); // l3 violates validity condition, because segment [5 ... -5[ is reversed
CHECK (l3.assess() == "!order_5>-5_!"_expect);
CHECK ( 9 == Seg::cnt ); // 9 objects are alive
CHECK ( 9 == Seg::idGen); // ID generator sticks at 9
CHECK (45 == Seg::check); // checksum 1+..+9
l3.erase(l3.begin());
cout <<"l3x:"<<l3<<l3.assess()<<endl;
l3.begin()->after = 5;
cout <<"l3x:"<<l3<<l3.assess()<<endl;
l3.clear();
cout <<"l3x:"<<l3<<l3.assess()<<endl;
CHECK (l3.assess() == "missing-lower-bound!!gap_-100<>5_!!order_5>-5_!"_expect);
CHECK ( 8 == Seg::cnt ); // also one object less alive
cout << Seg::check<<endl;
l3.begin()->after = 5; // manipulate first segment to make it degenerate (empty
CHECK (l3.renderContent() == "├[5_5[[-5_10[[10~100[┤"_expect);
CHECK (l3.assess() == "missing-lower-bound!!gap_-100<>5_!!degen_5_!!gap_5<>-5_!"_expect);
l3.clear();
CHECK (l3.assess() == "!empty!"_expect);
CHECK ( 5 == Seg::cnt );
CHECK ( 9 == Seg::idGen);
CHECK (15 == Seg::check);
}
cout << Seg::check<<endl;
// all objects go out of scope
CHECK (0 == Seg::cnt );
CHECK (0 == Seg::check);
CHECK (9 == Seg::idGen);
}

View file

@ -70302,6 +70302,28 @@
<node COLOR="#338800" CREATED="1683120165480" ID="ID_1260440964" MODIFIED="1683120205165" TEXT="Diagnose-Setup f&#xfc;r Memory-Management">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1683153490521" ID="ID_855593043" MODIFIED="1683153582188" TEXT="Diagnose-Helper zum Vergleichen mit erwartetem Inhalt">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
tja...
</p>
<p>
das h&#228;tte ich schon seit JAHREN machen k&#246;nnen....
</p>
<p>
</p>
<p>
(aber auf den Kniff mit dem Marker-Typ bin ich erst gekommen, seitdem ich mich neulich nochmal mit user-defined-Literals besch&#228;ftigt habe)
</p>
</body>
</html></richcontent>
<icon BUILTIN="idea"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1683047015402" ID="ID_1729864005" MODIFIED="1683120259296" TEXT="M&#xf6;gliche Intervall-Anordnungen">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1683047146727" ID="ID_1666065871" MODIFIED="1683047163542" TEXT="Seg in leer">