* Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
427 lines
18 KiB
C++
427 lines
18 KiB
C++
/*
|
||
DiffComplexApplication(Test) - apply structural changes to unspecific private data structures
|
||
|
||
Copyright (C)
|
||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**Lumiera** 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. See the file COPYING for further details.
|
||
|
||
* *****************************************************************/
|
||
|
||
/** @file diff-complex-application-test.cpp
|
||
** unit test \ref DiffComplexApplication_test.
|
||
** Demonstrates the concept of tree mutation by diff messages.
|
||
** This is an elaborate demonstration setup to show how a binding
|
||
** is setup in practice and to highlight some of the more intricate
|
||
** implementation corner cases, allowing for much flexibility when
|
||
** binding to otherwise opaque target data structures.
|
||
*/
|
||
|
||
|
||
#include "lib/test/run.hpp"
|
||
#include "lib/format-util.hpp"
|
||
#include "lib/diff/tree-diff-application.hpp"
|
||
#include "lib/diff/test-mutation-target.hpp"
|
||
#include "lib/iter-adapter-stl.hpp"
|
||
#include "lib/time/timevalue.hpp"
|
||
#include "lib/format-string.hpp"
|
||
#include "lib/format-cout.hpp"
|
||
#include "lib/util.hpp"
|
||
|
||
#include <string>
|
||
#include <vector>
|
||
#include <memory>
|
||
|
||
using util::isnil;
|
||
using util::join;
|
||
using util::_Fmt;
|
||
using util::BOTTOM_INDICATOR;
|
||
using lib::iter_stl::snapshot;
|
||
using lib::time::Time;
|
||
using std::unique_ptr;
|
||
using std::string;
|
||
using std::vector;
|
||
|
||
|
||
namespace lib {
|
||
namespace diff{
|
||
namespace test{
|
||
|
||
namespace {//Test fixture....
|
||
|
||
// define some GenNode elements
|
||
// to act as templates within the concrete diff
|
||
// NOTE: everything in this diff language is by-value
|
||
const GenNode ATTRIB1("α", 1), // attribute α = 1
|
||
ATTRIB2("β", int64_t(2)), // attribute α = 2L (int64_t)
|
||
ATTRIB3("γ", 3.45), // attribute γ = 3.45 (double)
|
||
TYPE_X("type", "ξ"), // a "magic" type attribute "Xi"
|
||
TYPE_Z("type", "ζ"), //
|
||
CHILD_A("a"), // unnamed string child node
|
||
CHILD_B('b'), // unnamed char child node
|
||
CHILD_T(Time(12,34,56,78)), // unnamed time value child
|
||
SUB_NODE = MakeRec().genNode(), // empty anonymous node used to open a sub scope
|
||
ATTRIB_NODE = MakeRec().genNode("δ"), // empty named node to be attached as attribute δ
|
||
GAMMA_PI("γ", 3.14159265); // happens to have the same identity (ID) as ATTRIB3
|
||
|
||
|
||
/**
|
||
* opaque private data structure to apply the diff.
|
||
* This class offers to build a binding for diff messages,
|
||
* which basically maps its internal structures onto the
|
||
* generic "object" scheme underlying the diff language.
|
||
*/
|
||
class Opaque
|
||
{
|
||
idi::BareEntryID key_;
|
||
string type_ = Rec::TYPE_NIL;
|
||
|
||
int alpha_ = -1;
|
||
int64_t beta_ = -1;
|
||
double gamma_ = -1;
|
||
|
||
unique_ptr<Opaque> delta_;
|
||
|
||
vector<Opaque> nestedObj_;
|
||
vector<string> nestedData_;
|
||
|
||
public:
|
||
Opaque()
|
||
: key_(idi::EntryID<Opaque>())
|
||
{ }
|
||
|
||
explicit
|
||
Opaque (string keyID)
|
||
: key_(idi::EntryID<Opaque>(keyID))
|
||
{ }
|
||
|
||
explicit
|
||
Opaque (idi::BareEntryID id)
|
||
: key_(id)
|
||
{ }
|
||
|
||
Opaque (Opaque const& o)
|
||
: key_(o.key_)
|
||
, type_(o.type_)
|
||
, alpha_(o.alpha_)
|
||
, beta_(o.beta_)
|
||
, gamma_(o.gamma_)
|
||
, delta_()
|
||
, nestedObj_(o.nestedObj_)
|
||
, nestedData_(o.nestedData_)
|
||
{
|
||
if (o.delta_)
|
||
delta_.reset(new Opaque(*o.delta_));
|
||
}
|
||
|
||
Opaque&
|
||
operator= (Opaque const& o)
|
||
{
|
||
if (&o != this)
|
||
{
|
||
Opaque tmp(o);
|
||
swap (*this, tmp);
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
bool verifyType(string x) const { return x == type_; }
|
||
bool verifyAlpha(int x) const { return x == alpha_;}
|
||
bool verifyBeta(int64_t x) const { return x == beta_; }
|
||
bool verifyGamma(double x) const { return x == gamma_;}
|
||
bool verifyData(string desc) const { return desc == join(nestedData_); }
|
||
const Opaque* nestedDelta() const { return not delta_? NULL : delta_.get(); }
|
||
const Opaque* nestedObj_1() const { return isnil(nestedObj_)? NULL : &nestedObj_[0]; }
|
||
|
||
|
||
operator string() const
|
||
{
|
||
return _Fmt{"%s__(α:%d β:%s γ:%7.5f δ:%s\n......|nested:%s\n......|data:%s\n )__END_%s"}
|
||
% identity()
|
||
% alpha_
|
||
% beta_
|
||
% gamma_
|
||
% delta_
|
||
% join (nestedObj_, "\n......|")
|
||
% join (nestedData_)
|
||
% identity()
|
||
;
|
||
}
|
||
|
||
string
|
||
identity() const
|
||
{
|
||
string symbol = key_.getSym() + (isTyped()? "≺"+type_+"≻" : "");
|
||
return lib::idi::format::instance_hex_format(symbol, key_.getHash());
|
||
}
|
||
|
||
bool
|
||
isTyped() const
|
||
{
|
||
return Rec::TYPE_NIL != type_;
|
||
}
|
||
|
||
|
||
/** the _only way_ this opaque object exposes itself for mutation through diff messages.
|
||
* This function builds a TreeMutator implementation into the given buffer space
|
||
* @note some crucial details for this binding to work properly...
|
||
* - we define several "onion layers" of binding to deal with various scopes.
|
||
* - the priority of these bindings is layered backwards from lowest to highest,
|
||
* i.e. the resulting mutator will fist check for attribute δ and then work
|
||
* its way down do the `collection(nestedData_)`
|
||
* - actually this is a quite complicated setup, including object fields
|
||
* to represent attributes, where only one specific attribute actually holds
|
||
* a nested object and thus needs special treatment; beyond that we have both
|
||
* a collection of child objects and a collection of child data values
|
||
* - the selector predicate (`isApplicableIf`) actually decides if a binding layer
|
||
* becomes responsible for a given diff verb. Here, this decision is based on
|
||
* the classification of the verb or spec to be handled, either being an
|
||
* attribute (named, key-value pair), a nested sub-scope ("object") and
|
||
* finally just any unnamed (non attribute) value
|
||
* - the recursive mutation of nested scopes is simply initiated by invoking
|
||
* the same Opaque::buildMutator on the respective children recursively.
|
||
* - such an unusually complicated TreeMutator binding leads to increased
|
||
* buffer space requirements for the actual TreeMutator to be generated;
|
||
* Thus we need to implement the _extension point_ treeMutatorSize()
|
||
* to supply a sufficient buffer size value. This function is
|
||
* picked up through ADL, based on the target type `Opaque`
|
||
*/
|
||
void
|
||
buildMutator (TreeMutator::Handle buff)
|
||
{
|
||
buff.emplace (
|
||
TreeMutator::build()
|
||
.attach (collection(nestedData_)
|
||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||
{
|
||
return not spec.isNamed(); // »Selector« : accept anything unnamed value-like
|
||
})
|
||
.matchElement ([&](GenNode const& spec, string const& elm) -> bool
|
||
{
|
||
return elm == render(spec.data); // »Matcher« : does the diff verb #spec apply to this object?
|
||
})
|
||
.constructFrom ([&](GenNode const& spec) -> string
|
||
{
|
||
return render (spec.data); // »Constructor« : build a new child entity to reflect the given diff #spec
|
||
})
|
||
.assignElement ([&](string& target, GenNode const& spec) -> bool
|
||
{
|
||
target = render (spec.data); // »Assigner« : treat this object as value and assign data from the #spec payload
|
||
return true;
|
||
}))
|
||
.attach (collection(nestedObj_)
|
||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||
{
|
||
return spec.data.isNested(); // »Selector« : require object-like sub scope
|
||
})
|
||
.matchElement ([&](GenNode const& spec, Opaque const& elm) -> bool
|
||
{
|
||
return spec.idi == elm.key_;
|
||
})
|
||
.constructFrom ([&](GenNode const& spec) -> Opaque
|
||
{
|
||
return Opaque{spec.idi};
|
||
})
|
||
.buildChildMutator ([&](Opaque& target, GenNode::ID const&, TreeMutator::Handle buff) -> bool
|
||
{
|
||
target.buildMutator (buff); // »Recursive Mutator« : delegate to child for building a nested TreeMutator
|
||
return true;
|
||
}))
|
||
.change("type", [&](string typeID)
|
||
{
|
||
type_ = typeID;
|
||
})
|
||
.change("α", [&](int val)
|
||
{
|
||
alpha_ = val;
|
||
})
|
||
.change("β", [&](int64_t val)
|
||
{
|
||
beta_ = val;
|
||
})
|
||
.change("γ", [&](double val)
|
||
{
|
||
gamma_ = val;
|
||
})
|
||
.mutateAttrib("δ", [&](TreeMutator::Handle buff)
|
||
{
|
||
if (not delta_) // note: object is managed automatically,
|
||
delta_.reset (new Opaque("δ")); // thus no INS-implementation necessary
|
||
REQUIRE (delta_);
|
||
|
||
delta_->buildMutator(buff);
|
||
}));
|
||
}
|
||
|
||
/** override default size traits
|
||
* to allow for sufficient buffer,
|
||
* able to hold the mutator defined above.
|
||
*/
|
||
friend constexpr size_t
|
||
treeMutatorSize (const Opaque*)
|
||
{
|
||
return 430;
|
||
}
|
||
};
|
||
|
||
}//(End)Test fixture
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/***********************************************************************//**
|
||
* @test Demonstration: apply a structural change to unspecified private
|
||
* data structures, with the help of an [dynamic adapter](\ref TreeMutator)
|
||
* - we use private data classes, defined right here in the test fixture
|
||
* to represent "just some" pre-existing data structure.
|
||
* - we re-assign some attribute values
|
||
* - we add, re-order and delete child "elements", without knowing
|
||
* what these elements actually are and how they are to be handled.
|
||
* - we recurse into mutating such an _"unspecified"_ child element.
|
||
*
|
||
* @note this test uses the same verb sequence as is assumed for the
|
||
* coverage of diff building blocks in TreeMutatorBinding_test
|
||
*
|
||
* @see DiffTreeApplication_test generic variant of tree diff application
|
||
* @see TreeMutatorBinding_test coverage of the "building blocks"
|
||
* @see TreeMutator_test base operations of the adapter
|
||
* @see diff-tree-application.hpp
|
||
* @see tree-diff.hpp
|
||
*/
|
||
class DiffComplexApplication_test
|
||
: public Test
|
||
, TreeDiffLanguage
|
||
{
|
||
using DiffSeq = iter_stl::IterSnapshot<DiffStep>;
|
||
|
||
DiffSeq
|
||
populationDiff()
|
||
{
|
||
return snapshot({ins(ATTRIB1)
|
||
, ins(ATTRIB3)
|
||
, ins(ATTRIB3)
|
||
, ins(CHILD_B)
|
||
, ins(CHILD_B)
|
||
, ins(CHILD_T)
|
||
});
|
||
} // ==> ATTRIB1, ATTRIB3, (ATTRIB3), CHILD_B, CHILD_B, CHILD_T
|
||
|
||
DiffSeq
|
||
reorderingDiff()
|
||
{
|
||
return snapshot({after(Ref::ATTRIBS)
|
||
, ins(ATTRIB2)
|
||
, del(CHILD_B)
|
||
, ins(SUB_NODE)
|
||
, find(CHILD_T)
|
||
, pick(CHILD_B)
|
||
, skip(CHILD_T)
|
||
});
|
||
} // ==> ATTRIB1, ATTRIB3, (ATTRIB3), ATTRIB2, SUB_NODE, CHILD_T, CHILD_B
|
||
|
||
DiffSeq
|
||
mutationDiff()
|
||
{
|
||
return snapshot({after(CHILD_B)
|
||
, after(Ref::END)
|
||
, set(GAMMA_PI)
|
||
, mut(SUB_NODE)
|
||
, ins(TYPE_X)
|
||
, ins(ATTRIB2)
|
||
, ins(CHILD_B)
|
||
, ins(CHILD_A)
|
||
, emu(SUB_NODE)
|
||
, ins(ATTRIB_NODE)
|
||
, mut(ATTRIB_NODE)
|
||
, ins(TYPE_Z)
|
||
, ins(CHILD_A)
|
||
, ins(CHILD_A)
|
||
, ins(CHILD_A)
|
||
, emu(ATTRIB_NODE)
|
||
});
|
||
} // ==> ATTRIB1, ATTRIB3 := π, (ATTRIB3), ATTRIB2,
|
||
// ATTRIB_NODE{ type ζ, CHILD_A, CHILD_A, CHILD_A }
|
||
// SUB_NODE{ type ξ, ATTRIB2, CHILD_B, CHILD_A },
|
||
// CHILD_T, CHILD_B
|
||
|
||
|
||
|
||
|
||
virtual void
|
||
run (Arg)
|
||
{
|
||
Opaque subject;
|
||
DiffApplicator<Opaque> application(subject);
|
||
//
|
||
cout << "before..."<<endl << subject<<endl;
|
||
CHECK (subject.verifyAlpha(-1));
|
||
CHECK (subject.verifyBeta(-1));
|
||
CHECK (subject.verifyGamma(-1));
|
||
CHECK (not subject.nestedDelta());
|
||
CHECK (not subject.nestedObj_1());
|
||
CHECK (subject.verifyData(""));
|
||
|
||
|
||
// Part I : apply attribute changes
|
||
application.consume(populationDiff());
|
||
//
|
||
cout << "after...I"<<endl << subject<<endl;
|
||
// ==> ATTRIB1, ATTRIB3, (ATTRIB3), CHILD_B, CHILD_B, CHILD_T
|
||
CHECK (subject.verifyAlpha(1));
|
||
CHECK (subject.verifyGamma(ATTRIB3.data.get<double>()));
|
||
CHECK (subject.verifyData("b, b, 78:56:34.012"));
|
||
// unchanged...
|
||
CHECK (subject.verifyBeta(-1));
|
||
CHECK (not subject.nestedDelta());
|
||
CHECK (not subject.nestedObj_1());
|
||
|
||
|
||
// Part II : apply child population
|
||
application.consume(reorderingDiff());
|
||
//
|
||
cout << "after...II"<<endl << subject<<endl;
|
||
// ==> ATTRIB1, ATTRIB3, (ATTRIB3), ATTRIB2, SUB_NODE, CHILD_T, CHILD_B
|
||
CHECK (subject.verifyAlpha(1));
|
||
CHECK (subject.verifyBeta (2)); // attribute β has been set
|
||
CHECK (subject.verifyGamma(3.45));
|
||
CHECK (subject.verifyData("78:56:34.012, b")); // one child deleted, the other ones re-ordered
|
||
CHECK (subject.nestedObj_1()); // plus inserted a nested child object
|
||
CHECK (subject.nestedObj_1()->verifyType(Rec::TYPE_NIL));
|
||
CHECK (subject.nestedObj_1()->verifyBeta(-1)); // ...which is empty (default constructed)
|
||
CHECK (subject.nestedObj_1()->verifyData(""));
|
||
|
||
|
||
// Part III : apply child mutations
|
||
application.consume(mutationDiff());
|
||
//
|
||
cout << "after...III"<<endl << subject<<endl;
|
||
// ==> ATTRIB1, ATTRIB3 := π, (ATTRIB3), ATTRIB2,
|
||
// ATTRIB_NODE{ type ζ, CHILD_A, CHILD_A, CHILD_A }
|
||
// SUB_NODE{ type ξ, ATTRIB2, CHILD_B, CHILD_A },
|
||
// CHILD_T, CHILD_B
|
||
CHECK (subject.verifyAlpha(1));
|
||
CHECK (subject.verifyBeta (2));
|
||
CHECK (subject.verifyGamma(GAMMA_PI.data.get<double>())); // new value assigned to attribute γ
|
||
CHECK (subject.nestedDelta()); // attribute δ (object valued) is now present
|
||
CHECK (subject.nestedDelta()->verifyType("ζ")); // ...and has an explicitly defined type field
|
||
CHECK (subject.nestedDelta()->verifyData("a, a, a"));//...plus three similar child values
|
||
CHECK (subject.verifyData("78:56:34.012, b")); // the child values weren't altered
|
||
CHECK (subject.nestedObj_1()->verifyType("ξ")); // but the nested child object's type has been set
|
||
CHECK (subject.nestedObj_1()->verifyBeta(2)); // ...and the attribute β has been set on the nested object
|
||
CHECK (subject.nestedObj_1()->verifyData("b, a")); // ...plus some child values where added here
|
||
}
|
||
};
|
||
|
||
|
||
/** Register this test class... */
|
||
LAUNCHER (DiffComplexApplication_test, "unit common");
|
||
|
||
|
||
|
||
}}} // namespace lib::diff::test
|