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:
parent
476c0f6493
commit
00ca84a2aa
5 changed files with 175 additions and 33 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 === */
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -70302,6 +70302,28 @@
|
|||
<node COLOR="#338800" CREATED="1683120165480" ID="ID_1260440964" MODIFIED="1683120205165" TEXT="Diagnose-Setup fü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ätte ich schon seit JAHREN machen kö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ä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ögliche Intervall-Anordnungen">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1683047146727" ID="ID_1666065871" MODIFIED="1683047163542" TEXT="Seg in leer">
|
||||
|
|
|
|||
Loading…
Reference in a new issue