/* MutationMessage(Test) - verify diff mutation message container Copyright (C) 2017, Hermann Vosseler   **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 mutation-message-test.cpp ** unit test \ref MutationMessage_test */ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" #include "lib/diff/mutation-message.hpp" #include "lib/diff/tree-diff-application.hpp" #include "lib/iter-adapter-stl.hpp" #include "lib/time/timevalue.hpp" #include "lib/format-util.hpp" #include "lib/util.hpp" #include #include using LERR_(ITER_EXHAUST); using lib::iter_stl::IterSnapshot; using lib::iter_stl::snapshot; using lib::time::Time; using util::contains; using util::isnil; using util::join; using std::string; using std::vector; namespace lib { namespace diff{ namespace test{ namespace {//Test fixture.... int instances = 0; ///< verify instance management // 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", "X"), // a "magic" type attribute "X" TYPE_Y("type", "Y"), // 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 δ CHILD_NODE = SUB_NODE; // yet another child node, same ID as SUB_NODE (!) }//(End)Test fixture /***********************************************************************//** * @test properties of a container to transport a diff from an abstracted * source generator to an abstracted consumer. * - this covers a standard usage scenario within Lumiera, where some * producer in the Session core detects changes in session structure * and sends a message to make the UI conform to the new structure * - diff messages are hard to debug and test, since they are designed * as opaque sequences to be consumed only once. Thus for we provide * a snapshot decorator to offer diagnostic facilities * - moreover we provide a simplified builder function to create * hard wired diff messages in a concise way * - and finally this test repeats the scenario of DiffTreeApplication_test, * but this time the diff sequences are encapsulated as MutationMessage. * @remarks like all the other _diff related_ tests, this code might be hard * to follow, unless you're familiar with the underlying concepts. Basically, * a _Diff_ is represented as _a linearised sequence of verb tokens_. Together * these tokens form a _diff language_. The semantics of that language are * oriented towards application of this diff onto a target structure. The * goal is to represent structure changes without being bound to a specific * data structure implementation. Yet there is one _meta_ data representation * used within the diff itself, as well as for various test and demonstration * examples: the [generic data record](\ref diff::Record) together with its * [variant node element](\ref diff::GenNode). The key point to note is the * (recursive) usage of Record elements as payload within GenNode, which * allows to represent tree shaped object like data structures. * * @see AbstractTangible_test::mutate() concrete usage scenario for UI-elements * @see DiffTreeApplication_test change a tree-like data structure by diff * @see DiffComplexApplication_test handling arbitrary data structures * @see DiffListApplication_test * @see MutationMessage * @see ui-bus.hpp */ class MutationMessage_test : public Test , TreeDiffLanguage { virtual void run (Arg) { demonstrate_standardUsage(); verify_builder(); verify_diagnostics(); demonstrate_treeApplication(); } /** @test demonstrate the intended usage pattern * - a diff generation context is allocated * - the MutationMessage takes ownership * - and exposes the generated diff sequence * - which is pulled during iteration */ void demonstrate_standardUsage() { using Source = WrappedLumieraIter>; /* opaque generation context */ struct Generator : TreeDiffLanguage , Source { Generator() : Source{snapshot({ins(TYPE_X) ,set(ATTRIB1) ,del(CHILD_T)})} { ++instances; } ~Generator() { --instances; } }; CHECK (0 == instances); { MutationMessage diffMsg{new Generator}; CHECK (!isnil (diffMsg)); CHECK (1 == instances); CHECK (diffMsg); CHECK (ins(TYPE_X) == *diffMsg); // and this effectively means.... CHECK ("ins" == string(diffMsg->verb())); CHECK ("type" == diffMsg->elm().idi.getSym()); CHECK ("X" == diffMsg->elm().data.get()); // now iterate one step ++diffMsg; CHECK (diffMsg); CHECK (set(ATTRIB1) == *diffMsg); CHECK ("set" == string(diffMsg->verb())); CHECK ("α" == diffMsg->elm().idi.getSym()); CHECK ( 1 == diffMsg->elm().data.get()); // cloning is allowed, yet implementation defined // in the actual case the underlying generator is based on a vector + a pointer // and thus the full state can be cloned into an independent instance MutationMessage clone{diffMsg}; CHECK (clone == diffMsg); CHECK (set(ATTRIB1) == *clone); CHECK (1 == instances); // the iterator front-end was cloned, not the generator ++clone; CHECK (del(CHILD_T) == *clone); CHECK (set(ATTRIB1) == *diffMsg); CHECK (clone != diffMsg); ++clone; CHECK (not clone); CHECK (isnil (clone)); VERIFY_ERROR(ITER_EXHAUST, *clone); // note the weird behaviour: // both instances share a common backend and thus state get mixed up. // The diffMsg front-End still points at a state already obsoleted CHECK (set(ATTRIB1) == *diffMsg); ++diffMsg; // So better don't do this at home... VERIFY_ERROR(ITER_EXHAUST, *diffMsg); clone = MutationMessage{new Generator}; CHECK (2 == instances); // now we got two independent generator instances CHECK (clone); CHECK (ins(TYPE_X) == *clone); ++clone; CHECK (set(ATTRIB1) == *clone); // first instance unaffected as before CHECK (isnil (diffMsg)); }// NOTE: automatic clean-up when leaving the scope CHECK (0 == instances); } void verify_builder() { MutationMessage diffMsg{ins(TYPE_X) ,set(ATTRIB1) ,del(CHILD_T)}; CHECK (!isnil (diffMsg)); CHECK (ins(TYPE_X) == *diffMsg); CHECK (set(ATTRIB1) == *++diffMsg); CHECK (del(CHILD_T) == *++diffMsg); CHECK (isnil (++diffMsg)); VERIFY_ERROR(ITER_EXHAUST, *diffMsg); // likewise works with a std::initializer_list diffMsg = MutationMessage{{ins(TYPE_X) ,set(ATTRIB1) ,del(CHILD_T)} }; CHECK (!isnil (diffMsg)); CHECK (ins(TYPE_X) == *diffMsg); CHECK (set(ATTRIB1) == *++diffMsg); CHECK (del(CHILD_T) == *++diffMsg); CHECK (isnil (++diffMsg)); // even passing any suitable iterable works diffMsg = MutationMessage{snapshot({ins(TYPE_X) ,set(ATTRIB1) ,del(CHILD_T)}) }; CHECK (!isnil (diffMsg)); CHECK (ins(TYPE_X) == *diffMsg); CHECK (set(ATTRIB1) == *++diffMsg); CHECK (del(CHILD_T) == *++diffMsg); CHECK (isnil (++diffMsg)); // really anything iterable... std::vector steps; CHECK (isnil (steps)); append_all(snapshot({ins(TYPE_X) ,set(ATTRIB1) ,del(CHILD_T)}), steps); diffMsg = MutationMessage{steps}; CHECK (!isnil (diffMsg)); CHECK (ins(TYPE_X) == *diffMsg); CHECK (set(ATTRIB1) == *++diffMsg); CHECK (del(CHILD_T) == *++diffMsg); CHECK (isnil (++diffMsg)); } void verify_diagnostics() { MutationMessage diffMsg{ins(TYPE_X) ,set(ATTRIB1) ,del(CHILD_T)}; // initially only the default diagnostics of IterSource is shown (rendering the element type) CHECK (string(diffMsg) == "IterSource::DiffStep>"); // transparently take a snapshot diffMsg.updateDiagnostics(); // now the whole sequence is rendered explicitly string expectedRendering = join ({ins(TYPE_X), set(ATTRIB1), del(CHILD_T)}); CHECK (contains (string(diffMsg), expectedRendering)); CHECK (string(set(ATTRIB1)) == "set(GenNode-ID(\"α\")-DataCap|«int»|1)"); // and we can still iterate... CHECK (!isnil (diffMsg)); CHECK (ins(TYPE_X) == *diffMsg); CHECK (set(ATTRIB1) == *++diffMsg); // NOTE: in fact only the remainder of the sequence is captured... diffMsg.updateDiagnostics(); CHECK (not contains (string(diffMsg), string(ins(TYPE_X)))); CHECK ( contains (string(diffMsg), string(set(ATTRIB1)))); CHECK ( contains (string(diffMsg), string(del(CHILD_T)))); // and we can still continue to iterate... CHECK (del(CHILD_T) == *++diffMsg); CHECK (isnil (++diffMsg)); diffMsg.updateDiagnostics(); CHECK (string(diffMsg) == "Diff--{}"); } MutationMessage populationDiff() { return { ins(TYPE_X) , ins(ATTRIB1) , ins(ATTRIB2) , ins(ATTRIB3) , ins(CHILD_A) , ins(CHILD_T) , ins(CHILD_T) , ins(SUB_NODE) , mut(SUB_NODE) , ins(CHILD_B) , ins(CHILD_A) , emu(SUB_NODE) }; } MutationMessage mutationDiff() { // prepare for direct assignment of new value // NOTE: the target ID will be reconstructed, including hash GenNode childA_upper(CHILD_A.idi.getSym(), "A"); return { after(Ref::ATTRIBS) // fast forward to the first child , find(CHILD_T) , pick(CHILD_A) , skip(CHILD_T) , del(CHILD_T) , after(Ref::END) // accept anything beyond as-is , mut(SUB_NODE) , ins(ATTRIB3) , ins(ATTRIB_NODE) // attributes can also be nested objects , find(CHILD_A) , del(CHILD_B) , ins(CHILD_NODE) , ins(CHILD_T) , skip(CHILD_A) , mut(CHILD_NODE) , ins(TYPE_Y) , ins(ATTRIB2) , emu(CHILD_NODE) , set(childA_upper) // direct assignment, target found by ID (out of order) , mut(ATTRIB_NODE) // mutation can be out-of order, target found by ID , ins(CHILD_A) , ins(CHILD_A) , ins(CHILD_A) , emu(ATTRIB_NODE) , emu(SUB_NODE) }; } /** @test use MutationMessage to transport and apply changes to target data * @remarks this almost literally repeats the DiffTreeApplication_test */ void demonstrate_treeApplication() { Rec::Mutator target; Rec& subject = target; DiffApplicator application(target); // Part I : apply diff to populate application.consume (populationDiff()); CHECK (!isnil (subject)); // nonempty -- content has been added CHECK ("X" == subject.getType()); // type was set to "X" CHECK (1 == subject.get("α").data.get()); // has gotten our int attribute "α" CHECK (2L == subject.get("β").data.get()); // ... the long attribute "β" CHECK (3.45 == subject.get("γ").data.get()); // ... and double attribute "γ" auto scope = subject.scope(); // look into the scope contents... CHECK ( *scope == CHILD_A); // there is CHILD_A CHECK (*++scope == CHILD_T); // followed by a copy of CHILD_T CHECK (*++scope == CHILD_T); // and another copy of CHILD_T CHECK (*++scope == MakeRec().appendChild(CHILD_B) // and there is a nested Record .appendChild(CHILD_A) // with CHILD_B .genNode(SUB_NODE.idi.getSym())); // and CHILD_A CHECK (isnil(++scope)); // thats all -- no more children // Part II : apply the second diff application.consume (mutationDiff()); CHECK (join (subject.keys()) == "α, β, γ"); // the attributes weren't altered scope = subject.scope(); // but the scope was reordered CHECK ( *scope == CHILD_T); // CHILD_T CHECK (*++scope == CHILD_A); // CHILD_A Rec nested = (++scope)->data.get(); // and our nested Record, which too has been altered: CHECK (nested.get("γ").data.get() == 3.45); // it carries now an attribute "δ", which is again CHECK (nested.get("δ") == MakeRec().appendChild(CHILD_A) // a nested Record with three children CHILD_A .appendChild(CHILD_A) // .appendChild(CHILD_A) // .genNode("δ")); // auto subScope = nested.scope(); // and within the nested sub-scope we find CHECK ( *subScope != CHILD_A); // CHILD_A has been altered by assignment CHECK (CHILD_A.idi == subScope->idi); // ...: same ID as CHILD_A CHECK ("A" == subScope->data.get()); // ...: but mutated payload CHECK (*++subScope == MakeRec().type("Y") // a yet-again nested sub-Record of type "Y" .set("β", int64_t(2)) // with just an attribute "β" == 2L .genNode(CHILD_NODE.idi.getSym())); // (and an empty child scope) CHECK (*++subScope == CHILD_T); // followed by another copy of CHILD_T CHECK (isnil (++subScope)); // CHECK (isnil (++scope)); // and nothing beyond that. } }; /** Register this test class... */ LAUNCHER (MutationMessage_test, "unit common"); }}} // namespace lib::diff::test