From ffd40d86e70249ddeecaa7f610b2208f8c6fccc2 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 29 Aug 2016 19:39:19 +0200 Subject: [PATCH] finish integration test and TreeMutator binding (#992) This implementation draft is now roughly complete --- src/lib/diff/tree-diff-application.hpp | 2 +- src/lib/diff/tree-diff-mutator-binding.cpp | 1 + src/lib/diff/tree-diff-mutator-binding.hpp | 35 +++++--- .../diff/diff-complex-application-test.cpp | 87 ++++++++++++++++--- 4 files changed, 100 insertions(+), 25 deletions(-) diff --git a/src/lib/diff/tree-diff-application.hpp b/src/lib/diff/tree-diff-application.hpp index 6bb97138e..450e1f20c 100644 --- a/src/lib/diff/tree-diff-application.hpp +++ b/src/lib/diff/tree-diff-application.hpp @@ -133,7 +133,7 @@ namespace diff{ * * In the extended configuration for tree-diff-application to given opaque target * data, the setup uses the [metaprogramming adapter traits](\ref TreeDiffTraits) - * to pave a way for building the custom TreeMutator implementation internally wired + * to pave a way for building the custom TreeMutator implementation, wired internally * to the given opaque target. Moreover, based on the concrete target type, a suitable * ScopeManager implementation can be provided. Together, these two dynamically created * adapters allow the generic TreeDiffMutatorBinding to perform all of the actual diff --git a/src/lib/diff/tree-diff-mutator-binding.cpp b/src/lib/diff/tree-diff-mutator-binding.cpp index 25e8d693a..b1edc5533 100644 --- a/src/lib/diff/tree-diff-mutator-binding.cpp +++ b/src/lib/diff/tree-diff-mutator-binding.cpp @@ -51,6 +51,7 @@ namespace diff{ ScopeManager::~ScopeManager() { }; ///< emit VTable here... + /* ======= Implementation of Tree Diff Application via TreeMutator ======= */ using util::unConst; diff --git a/src/lib/diff/tree-diff-mutator-binding.hpp b/src/lib/diff/tree-diff-mutator-binding.hpp index babe73f86..7afe32e21 100644 --- a/src/lib/diff/tree-diff-mutator-binding.hpp +++ b/src/lib/diff/tree-diff-mutator-binding.hpp @@ -46,8 +46,8 @@ ** Unfortunately this leads to yet another indirection layer: Implementing a ** language in itself is necessarily a double dispatch (we have to abstract the ** verbs and we have to abstract the implementation side). And now we're decoupling - ** the implementation side from a concrete data structure. Which means, that the - ** use will have to provide a set of closures (which might even partially be generated + ** the implementation side from a concrete data structure. Which means, that the user + ** will have to provide a set of closures (which might even partially be generated ** functors) to translate the _implementation actions_ underlying the language into ** _concrete actions_ working on local data. ** @@ -56,14 +56,27 @@ ** interpretation and the concrete yet undisclosed private data structure, and ** most of this implementation is entirely generic, since the specifics are ** abstracted away behind the TreeMutator interface. For this reason, most of - ** this explicit template specialisation code, especially. the virtual functions, - ** can be emitted right here, within the library module. This helps to reduce - ** "template bloat" and simplifies the dynamic linking. Thus, this header - ** only contains the definition and the ctor code, which indeed needs to - ** be adapted to each usage situation, while the main body of the - ** functionality has been moved to the corresponding implementation - ** file, where this template is explicitly instantiated, to force - ** code generation into the library module. + ** this _delegating implementation_ can be emitted right here, within the + ** library module. With the sole exception of the ctor, which needs to + ** figure out a way how to "get" a suitable TreeMutator implementation + ** for the given concrete target data. + ** + ** ### the TreeMutator DSL + ** In the end, this concrete TreeMutator needs to be built or provided within + ** the realm of the actual data implementation anyway, so the knowledge about the + ** actual data layout remains confined there. Unfortunately, implementing a TreeMutator + ** is quite an involved and technical task, requiring intimate knowledge of structure + ** and semantics of the diff language. On a second thought, it turns out that most + ** data implementation will rely on some very common representation techniques, + ** like using object fields as "attributes" and a STL collection to hold the + ** "children". Based on this insight, it is possible to provide standard adapters + ** and building blocks, in the form of an DSL, to generate the actual TreeMutator. + ** The usage site thus needs to supply only some lambda expressions to specify + ** how to deal with the representation data values + ** - how to construct a new entity + ** - when the binding actually becomes relevant + ** - how to determine a diff verb "matches" the actual data + ** - how to set a value or how to recurse into a sub scope ** ** @todo this is WIP as of 2/2016 -- in the end it might be merged back or even ** replace the tree-diff-application.hpp @@ -217,7 +230,7 @@ namespace diff{ private: - /* == Forwarding: error handling == */ + /* == error handling helpers == */ void __failMismatch (Literal oper, GenNode const& spec); void __expect_further_elements (GenNode const& elm); diff --git a/tests/library/diff/diff-complex-application-test.cpp b/tests/library/diff/diff-complex-application-test.cpp index 94fa1b2af..67fb80ff6 100644 --- a/tests/library/diff/diff-complex-application-test.cpp +++ b/tests/library/diff/diff-complex-application-test.cpp @@ -27,26 +27,23 @@ #include "lib/diff/test-mutation-target.hpp" #include "lib/iter-adapter-stl.hpp" #include "lib/time/timevalue.hpp" -#include "lib/format-cout.hpp" //////////TODO necessary? #include "lib/format-string.hpp" -//#include "lib/format-util.hpp" +#include "lib/format-cout.hpp" #include "lib/util.hpp" #include #include #include -using lib::iter_stl::snapshot; using util::isnil; using util::join; using util::_Fmt; -using util::join; +using util::BOTTOM_INDICATOR; +using lib::iter_stl::snapshot; +using lib::time::Time; using std::unique_ptr; -using std::make_unique; using std::string; using std::vector; -using lib::time::Time; -using util::BOTTOM_INDICATOR; namespace lib { @@ -131,6 +128,14 @@ namespace test{ 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 { @@ -160,6 +165,28 @@ namespace test{ } + /** 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 ordered from lowest to highest + * - actually this is a quite complicated setup, including object fields + * to represent attributes, where one special attribute which actually holds + * a nested object, then both a collection of child objects and a collection + * of 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) { @@ -330,30 +357,64 @@ namespace test{ run (Arg) { Opaque subject; -// auto target = mutatorBinding (subject); -// DiffApplicator application(target); DiffApplicator application(subject); // - // TODO verify results cout << "before..."< ATTRIB1, ATTRIB3, (ATTRIB3), CHILD_B, CHILD_B, CHILD_T + CHECK (subject.verifyAlpha(1)); + CHECK (subject.verifyGamma(ATTRIB3.data.get())); + 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()); // - // TODO verify results cout << "after...II"< 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()); // - // TODO verify results cout << "after...III"< 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())); // 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 } };