/* SplitSplice(Test) - verify interval splicing Copyright (C) Lumiera.org 2023, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *****************************************************/ /** @file split-splice-test.cpp ** unit test \ref SplitSplice_test */ #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" #include "lib/split-splice.hpp" #include "lib/nocopy.hpp" #include "lib/util.hpp" #include #include #include namespace lib { namespace test { using util::_Fmt; 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 { int start; int after; bool empty; ~Seg() { check -= id; if (id) --cnt; } Seg (int s, int a, bool nil=false) : start{s} , after{a} , empty{nil} , id{++idGen} { ++cnt; check += id; } /** create a clone, but modify bounds */ Seg (Seg const& ref, int s, int a) : start{s} , after{a} , empty{ref.empty} , id{ref.id} { ++cnt; check += id; } /** move-init: causes source-ref to be invalidated */ Seg (Seg&& rr) : start{rr.start} , after{rr.after} , empty{rr.empty} , id{0} { std::swap (id, rr.id); }//transfer identity operator string() const { return _Fmt{"[%i%s%i["} % start % (empty? "~":"_") % after ; } //-- 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. * 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 { SegL (std::initializer_list breaks) { int p = SMIN; bool bound = true; for (int b : breaks) { emplace_back (p,b, bound); bound = false; p = b; } emplace_back(p,SMAX, true); } SegL() : SegL({}) { } // using standard copy operator string() const { return renderContent() + assess(); } bool isValid() const { return isnil (this->assess()); } string renderContent() const { return "├"+util::join(*this,"")+"┤"; } string assess() const { string diagnosis; if (empty()) diagnosis = "!empty!"; else { if (front().start != SMIN) diagnosis += "missing-lower-bound!"; if (back().after != SMAX) diagnosis += "missing-upper-bound!"; int p = SMIN; for (auto const& s : *this) { if (s.start != p) diagnosis += _Fmt{"!gap_%i<>%i_!"} % p % s.start; if (s.start == s.after) diagnosis += _Fmt{"!degen_%i_!"} % s.start; if (s.start > s.after) diagnosis += _Fmt{"!order_%i>%i_!"} % s.start % s.after; p = s.after; } } return diagnosis; } }; }//(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. * - demonstrate how to setup the invocation with custom data types * - systematic coverage of all possible arrangements of intervals * - handling of irregular cases * @see split-splice.hpp * @see steam::fixture::Segmentation::splitSplice */ class SplitSplice_test : public Test { virtual void run (Arg) { demonstrate_usage(); verify_testFixture(); verify_standardCases(); verify_cornerCases(); } /** * @test demonstrate how to use the »Split-Splice« algorithm with custom data */ void demonstrate_usage() { SegL segments; cout << segments <; using Iter = typename SegL::iterator; auto getStart = [](Iter elm) -> int { return elm->start; }; auto getAfter = [](Iter elm) -> int { return elm->after; }; auto createSeg= [&](Iter pos, int start, int after) -> Iter { return segments.emplace (pos, start, after); }; auto emptySeg = [&](Iter pos, int start, int after) -> Iter { return segments.emplace (pos, start, after, true); }; auto cloneSeg = [&](Iter pos, int start, int after, Iter src) -> Iter { return segments.emplace (pos, *src, start, after); }; auto discard = [&](Iter pos, Iter after) -> Iter { return segments.erase (pos,after); }; lib::splitsplice::Algo splicer{ getStart , getAfter , createSeg , emptySeg , cloneSeg , discard , SMAX , segments.begin(),segments.end() , OInt{5}, OInt{23} }; splicer.determineRelations(); auto [s,n,e] = splicer.performSplitSplice(); cout << segments <-5_!"_expect); 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()); CHECK (l3.assess() == "missing-lower-bound!!gap_-100<>5_!!order_5>-5_!"_expect); CHECK ( 8 == Seg::cnt ); // also one object less alive 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); } // all objects go out of scope CHECK (0 == Seg::cnt ); CHECK (0 == Seg::check); CHECK (9 == Seg::idGen); } /** * @test cover all possible cases of splicing an interval */ void verify_standardCases() { UNIMPLEMENTED ("standard cases"); } /** * @test cover special and boundary cases */ void verify_cornerCases() { UNIMPLEMENTED ("corner cases"); } }; LAUNCHER (SplitSplice_test, "unit common"); }} // namespace lib::test