LUMIERA.clone/tests/library/hierarchy-orientation-indicator-test.cpp
Ichthyostega 25459028cc extend and adjust semantics of the HierarchyOrientationIndicator
This helper was drafted for the Job / JobPlanning and Scheduler
interface in 2013, but seemingly not yet put into action. While
in the original use case, we have a genuine measuerment for the
tree depth (given by the depth of the processing stack), in other
use cases we want to use to offset embedded within the indicator
itself for keeping track of the depth. Thus I add a second
mark operation, which usess the current offset to set a new
reference level. This has the consequence that the offset
has now to reflect the new reference point immediately
2015-09-04 22:15:44 +02:00

421 lines
13 KiB
C++

/*
HierarchyOrientationIndicator(Test) - mechanism for reproducing a tree (of jobs)
Copyright (C) Lumiera.org
2013, Hermann Vosseler <Ichthyostega@web.de>
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.
* *****************************************************/
#include "lib/test/run.hpp"
#include "lib/hierarchy-orientation-indicator.hpp"
#include "lib/iter-adapter-stl.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/itertools.hpp"
#include "lib/util.hpp"
#include <boost/operators.hpp>
#include <functional>
#include <string>
#include <vector>
#include <cstdlib>
namespace lib {
namespace test {
namespace { // test fixture: a random Tree to navigate...
using std::rand;
using std::function;
using lib::transformIterator;
using lib::iter_stl::eachAddress;
using util::contains;
using util::max;
/* -- size of the test tree ---- */
const uint MAX_CHILDREN_CNT(5); // children per Node (5 means 0 to 4 children)
const double CHILD_PROBABILITY(0.45); // probability for a Node to have any children
const uint TEST_SEQUENCE_LENGTH(50); // test uses a sequence of Node trees
// 5 - 45% - 50 produce roughly 1000 Nodes and tree depths of about 12
uint nextChildID(1);
/**
* pick a random child count below #MAX_CHILDREN_CNT
* with a probability to get any count above zero
* as defined by CHILD_PROBABILITY
*/
inline uint
pick_random_count()
{
uint bottom((1.0/CHILD_PROBABILITY - 1) * MAX_CHILDREN_CNT);
uint limit = bottom + MAX_CHILDREN_CNT;
ASSERT (0 < bottom);
ASSERT (bottom < limit);
int cnt = (rand() % limit) - bottom;
return max(0, cnt);
}
/** (sub)tree of test data */
struct Node
: boost::equality_comparable<Node>
{
typedef std::vector<Node> Children;
int id_;
Children children_;
Node(int i) ///< build node explicitly without children
: id_(i)
{ }
Node() ///< build a random test subtree
: id_(nextChildID++)
{
uint c = pick_random_count();
for (uint j=0; j<c; ++j) // populate with c random children
children_.push_back(Node());
}
Node const&
child (uint i) const
{
REQUIRE (i < children_.size());
return children_[i];
}
bool
hasChild (Node const& o)
{
return util::contains (children_, o);
}
Node&
makeChild (int childID)
{
children_.push_back (Node(childID));
return children_.back();
}
};
inline bool
have_equivalent_children (Node const& l, Node const& r)
{
if (l.children_.size() != r.children_.size()) return false;
for (uint i=0; i<l.children_.size(); ++i)
if (l.child(i) != r.child(i)) return false;
return true;
}
inline bool
operator== (Node const& l, Node const& r)
{
return l.id_ == r.id_
&& have_equivalent_children(l,r);
}
typedef lib::IterQueue<Node*> NodeSeq;
/**
* Function to generate a depth-first tree visitation
*/
NodeSeq
exploreChildren (Node* node)
{
NodeSeq children_to_visit;
build(children_to_visit).usingSequence (eachAddress (node->children_));
return children_to_visit;
}
struct VisitationData
{
int id;
int orientation;
VisitationData(int refID,
int direction =0)
: id(refID)
, orientation(direction)
{ }
};
/**
* This functor visits the nodes to produce the actual test data.
* The intention is to describe a visitation path through a tree structure
* by a sequence of "up", "down", and "level" orientations. The test we're
* preparing here will attempt to re-create a given tree based on these
* directional information. The actual visitation path is created by
* a depth-first exploration of the source tree.
*/
class NodeVisitor
{
typedef std::deque<Node*> NodePath;
typedef NodePath::reverse_iterator PathIter;
NodePath path_;
public:
// using default ctor and copy operations
static function<VisitationData(Node*)>
create () { return NodeVisitor(); }
VisitationData
operator() (Node* node)
{
int direction = establishRelation (node);
return VisitationData(node->id_, direction);
}
private:
/** Helper for this test only: find out about the hierarchical relation.
* In the real usage situation, the key point is that we \em record
* this relation on-the-fly, when visiting the tree, instead of
* determining it after the fact. */
int
establishRelation (Node* nextNode)
{
REQUIRE (nextNode);
uint level = path_.size();
uint refLevel = level;
for (PathIter p = path_.rbegin();
0 < level ; --level, ++p )
{
Node* parent = *p;
if (parent->hasChild (*nextNode))
{
// visitation continues with children below this level
path_.resize(level);
path_.push_back(nextNode);
return (level - refLevel) + 1;
}
}
ASSERT (0 == level);
// nextNode not found as child (i.e. fork) within current tree path
// --> start new tree path at root
path_.clear();
path_.push_back(nextNode);
return (0 - refLevel) + 1;
} // by convention, root is an implicitly pre-existing context at level 0
};
/**
* the core of this test: rebuilding a tree
* based on visitation data, including the \em orientation
* of the visitation path (up, down, siblings). After construction,
* the embedded #children_ will reflect the original sequence as
* described by the given treeTraversal.
* @remarks this is a blueprint for the scheduler interface,
* which accepts a sequence of jobs with dependencies.
*/
struct TreeRebuilder
: Node
{
template<class IT>
TreeRebuilder (IT treeTraversal)
: Node(0)
{
populate (transformIterator (treeTraversal,
NodeVisitor::create()));
}
private:
template<class IT>
void
populate (IT treeVisitation)
{
struct Builder
{
Builder (Node& startPoint)
: parent_(NULL)
, current_(&startPoint)
{ }
void
populateBy (IT& treeVisitation)
{
while (treeVisitation)
{
int direction = treeVisitation->orientation;
if (direction < 0)
{
treeVisitation->orientation += 1;
return;
}
else
if (direction > 0)
{
treeVisitation->orientation -= 1;
Node* refPoint = startChildTransaction();
populateBy (treeVisitation);
commitChildTransaction(refPoint);
}
else
{
addNode (treeVisitation->id);
++treeVisitation;
}}}
private:
Node* parent_;
Node* current_;
void
addNode (int id)
{
current_ = & parent_->makeChild(id);
}
Node*
startChildTransaction()
{
Node* oldRefPoint = parent_;
ASSERT (current_);
parent_ = current_; // set new ref point
return oldRefPoint;
}
void
commitChildTransaction(Node* refPoint)
{
parent_ = refPoint;
current_ = parent_;
}
};
Builder builder(*this); // pre-existing implicit root context
builder.populateBy (treeVisitation);
}
};
} //(End) test fixture
/***********************************************************************//**
* @test describing and rebuilding a tree structure
* while visiting the tree in depth first order.
* To keep track of the level changes during that navigation,
* we use an indicator to represent the relative level difference
* compared to the previously visited node in the tree.
*
* @see HierarchyOrientationIndicator
* @see DispatcherInterface_test
*/
class HierarchyOrientationIndicator_test : public Test
{
virtual void
run (Arg)
{
demonstrate_tree_rebuilding();
verify_OrientationIndicator();
}
/** @test demonstrate how a Node tree structure can be rebuilt
* just based on the visitation sequence of an original tree.
* This visitation captures the local data of the Node (here the ID)
* and the orientation of the visitation path (down, next sibling, up)
*
* This is a demonstration and blueprint for constructing the scheduler interface.
* The Scheduler accepts a series of new jobs, but jobs may depend on each other,
* and the jobs are created while exploring the dependencies in the render engine's
* node graph (low-level-model).
*/
void
demonstrate_tree_rebuilding ()
{
Node::Children testWood;
for (uint i=0; i < TEST_SEQUENCE_LENGTH; ++i)
testWood.push_back(Node());
TreeRebuilder reconstructed (depthFirst (eachAddress(testWood)) >>= exploreChildren);
CHECK (reconstructed.children_ == testWood);
}
void
verify_OrientationIndicator ()
{
OrientationIndicator orient;
CHECK (0 == orient);
++orient;
CHECK (+1 == orient);
----orient;
CHECK (-1 == orient);
orient.markRefLevel();
CHECK ( 0 == orient);
orient.markRefLevel (2);
CHECK (-3 == orient);
orient.markRefLevel (2);
CHECK (-3 == orient);
orient -= orient;
CHECK ( 0 == orient);
++orient;
CHECK (+1 == orient);
orient.markRefLevel();
orient.markRefLevel (4);
orient.markRefLevel (5);
CHECK (-2 == orient);
orient.markRefLevel (2);
CHECK (+1 == orient);
orient.resetToRef();
++orient;
orient.markRefLevel(); // now at level == 3
CHECK ( 0 == orient);
orient += 200;
orient -= 190;
CHECK (+7 == orient);
OrientationIndicator o2(orient);
o2.markRefLevel(12);
CHECK (-2 == o2);
CHECK (+7 == orient);
}
};
/** Register this test class... */
LAUNCHER(HierarchyOrientationIndicator_test, "unit common");
}} // namespace lib::test