* 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.
402 lines
14 KiB
C++
402 lines
14 KiB
C++
/*
|
||
ScopePath(Test) - verify handling of logical access path down from Session root
|
||
|
||
Copyright (C)
|
||
2009, 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 scope-path-test.cpp
|
||
** unit test \ref ScopePath_test
|
||
*/
|
||
|
||
|
||
#include "lib/test/run.hpp"
|
||
#include "lib/test/test-helper.hpp"
|
||
#include "steam/mobject/session/test-scopes.hpp"
|
||
#include "steam/mobject/session/placement-index.hpp"
|
||
#include "steam/mobject/session/scope-path.hpp"
|
||
#include "steam/mobject/session/test-scope-invalid.hpp"
|
||
#include "lib/format-cout.hpp"
|
||
#include "lib/util.hpp"
|
||
|
||
#include <string>
|
||
|
||
|
||
namespace steam {
|
||
namespace mobject {
|
||
namespace session {
|
||
namespace test {
|
||
|
||
using std::string;
|
||
using util::isnil;
|
||
using util::isSameObject;
|
||
|
||
using LERR_(LOGIC);
|
||
using LERR_(INVALID_SCOPE);
|
||
using LERR_(NOT_IN_SESSION);
|
||
using LERR_(EMPTY_SCOPE_PATH);
|
||
|
||
|
||
|
||
/***********************************************************************//**
|
||
* @test properties and behaviour of the path of nested scopes.
|
||
* Using a pseudo-session (actually just a PlacementIndex),
|
||
* this test creates some nested scopes, builds scope paths
|
||
* and executes various comparisons navigation moves on them.
|
||
* Especially detection of invalid scopes and paths and the
|
||
* special handling of empty and root paths is covered.
|
||
* @see mobject::Placement
|
||
* @see mobject::session::ScopePath
|
||
* @see mobject::session::QueryFocus
|
||
*/
|
||
class ScopePath_test : public Test
|
||
{
|
||
|
||
virtual void
|
||
run (Arg)
|
||
{
|
||
// Prepare an (test)Index backing the PlacementRefs
|
||
PPIdx index = build_testScopes();
|
||
PMO& startPlacement = retrieve_startElm();
|
||
CHECK (startPlacement.isValid());
|
||
|
||
checkInvalidScopeDetection();
|
||
ScopePath testPath = buildPath (startPlacement);
|
||
checkRelations (testPath,startPlacement);
|
||
invalidPath (testPath,startPlacement);
|
||
rootPath (testPath);
|
||
check_Identity_and_Copy (startPlacement);
|
||
check_RefcountProtection (startPlacement);
|
||
navigate (testPath, index);
|
||
clear (testPath, index);
|
||
}
|
||
|
||
|
||
ScopePath
|
||
buildPath (PMO& startPla)
|
||
{
|
||
Scope startScope (startPla);
|
||
ScopePath path (startScope);
|
||
ScopePath path2 (startScope);
|
||
ScopePath path3 (path2);
|
||
|
||
CHECK (path);
|
||
CHECK (path.contains (startScope));
|
||
CHECK ( path.getLeaf() == path2.getLeaf());
|
||
CHECK (path2.getLeaf() == path3.getLeaf());
|
||
|
||
return path;
|
||
}
|
||
|
||
|
||
void
|
||
checkInvalidScopeDetection()
|
||
{
|
||
// verify detection of illegal scopes and paths...
|
||
TestPlacement<> notRelated2anything (*new DummyMO);
|
||
VERIFY_ERROR (NOT_IN_SESSION, Scope invalid (notRelated2anything) );
|
||
|
||
Scope const& scopeOfEvil = fabricate_invalidScope();
|
||
CHECK (!scopeOfEvil.isValid());
|
||
|
||
VERIFY_ERROR (INVALID_SCOPE, ScopePath outsideCurrentModel (scopeOfEvil) );
|
||
|
||
// but there is one exception to this rule...
|
||
ScopePath theInvalidToken (Scope::INVALID);
|
||
CHECK (!theInvalidToken.isValid());
|
||
CHECK (theInvalidToken.empty());
|
||
}
|
||
|
||
|
||
|
||
void
|
||
checkIteration (ScopePath path, PMO& refPlacement)
|
||
{
|
||
Scope refScope(refPlacement);
|
||
ScopePath::iterator ii = path.begin();
|
||
CHECK (ii);
|
||
while (++ii)
|
||
{
|
||
CHECK (*ii == refScope.getParent());
|
||
refScope = *ii;
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
checkRelations (ScopePath path1, PMO& refPlacement)
|
||
{
|
||
CHECK (path1.contains (refPlacement));
|
||
|
||
Scope refScope (refPlacement);
|
||
CHECK (path1.contains (refScope));
|
||
CHECK (path1.endsAt (refScope));
|
||
|
||
ScopePath path2 (refScope);
|
||
CHECK (path2.contains (refScope));
|
||
CHECK (path2.endsAt (refScope));
|
||
|
||
CHECK (path1 == path2);
|
||
CHECK (!isSameObject (path1,path2));
|
||
|
||
Scope parent = path2.moveUp();
|
||
CHECK (path2.endsAt (parent));
|
||
CHECK (path1.endsAt (refScope));
|
||
CHECK (parent == refScope.getParent());
|
||
CHECK (path1 != path2);
|
||
CHECK (path2 != path1);
|
||
CHECK (path1.contains (path2));
|
||
CHECK (!disjoint(path1,path2));
|
||
CHECK (path2 == commonPrefix(path1,path2));
|
||
CHECK (path2 == commonPrefix(path2,path1));
|
||
CHECK (path1 != commonPrefix(path1,path2));
|
||
CHECK (path1 != commonPrefix(path2,path1));
|
||
}
|
||
|
||
|
||
void
|
||
rootPath (ScopePath refPath)
|
||
{
|
||
CHECK ( refPath);
|
||
refPath.goRoot();
|
||
CHECK (!refPath);
|
||
CHECK (!refPath.empty());
|
||
CHECK (!refPath.isValid());
|
||
CHECK (1 == refPath.length());
|
||
|
||
ScopePath defaultPath;
|
||
CHECK (!defaultPath);
|
||
CHECK (refPath == defaultPath);
|
||
}
|
||
|
||
|
||
void
|
||
invalidPath (ScopePath refPath, PMO& refPlacement)
|
||
{
|
||
CHECK (refPath);
|
||
CHECK (!ScopePath::INVALID);
|
||
CHECK (isnil (ScopePath::INVALID));
|
||
CHECK ("!" == string(ScopePath::INVALID));
|
||
|
||
ScopePath invalidP (ScopePath::INVALID);
|
||
CHECK (isnil (invalidP));
|
||
CHECK (invalidP == ScopePath::INVALID);
|
||
CHECK (!isSameObject (invalidP, ScopePath::INVALID));
|
||
|
||
CHECK (refPath.contains (refPlacement));
|
||
CHECK (!invalidP.contains (refPlacement));
|
||
|
||
Scope refScope (refPlacement);
|
||
CHECK (!invalidP.contains (refScope));
|
||
VERIFY_ERROR (EMPTY_SCOPE_PATH, invalidP.endsAt (refScope) ); // Logic: can't inspect the end of nothing
|
||
|
||
CHECK (refPath.contains (invalidP)); // If the moon is made of green cheese, I'll eat my hat!
|
||
CHECK (!invalidP.contains (refPath));
|
||
CHECK (invalidP == commonPrefix(refPath,invalidP));
|
||
CHECK (invalidP == commonPrefix(invalidP,refPath));
|
||
|
||
VERIFY_ERROR (EMPTY_SCOPE_PATH, invalidP.moveUp() );
|
||
Scope root = refPath.goRoot();
|
||
CHECK (1 == refPath.length());
|
||
|
||
Scope const& nil = refPath.moveUp();
|
||
CHECK (refPath.empty());
|
||
CHECK (!nil.isValid());
|
||
CHECK (refPath == invalidP);
|
||
CHECK (invalidP.contains (nil));
|
||
CHECK (invalidP.contains (refPath));
|
||
CHECK (!invalidP.contains (refScope));
|
||
|
||
VERIFY_ERROR (EMPTY_SCOPE_PATH, refPath.navigate(root) );
|
||
|
||
//ScopePath::INVALID.navigate(root); // doesn't compile: INVALID is immutable
|
||
}
|
||
|
||
|
||
void
|
||
check_Identity_and_Copy (PMO& refPlacement)
|
||
{
|
||
Scope startScope (refPlacement);
|
||
ScopePath path1 (startScope);
|
||
ScopePath path2 (startScope);
|
||
ScopePath path3 (path2);
|
||
|
||
CHECK (path1.contains (startScope));
|
||
CHECK (path2.contains (startScope));
|
||
CHECK (path3.contains (startScope));
|
||
|
||
CHECK (path1 == path2);
|
||
CHECK (path2 == path3);
|
||
CHECK (path1 == path3);
|
||
CHECK (!isSameObject (path1,path2));
|
||
CHECK (!isSameObject (path2,path3));
|
||
CHECK (!isSameObject (path1,path3));
|
||
|
||
Scope parent = path3.moveUp(); // mutation
|
||
CHECK (parent == path2.getLeaf().getParent());
|
||
|
||
CHECK (path1 == path2); // the others are not affected
|
||
CHECK (path2 != path3);
|
||
CHECK (path1 != path3);
|
||
|
||
path2 = path3;
|
||
CHECK (path1 != path2);
|
||
CHECK (path2 == path3);
|
||
CHECK (path1 != path3);
|
||
|
||
path2 = ScopePath::INVALID;
|
||
CHECK (path1 != path2);
|
||
CHECK (path2 != path3);
|
||
CHECK (path1 != path3);
|
||
}
|
||
|
||
|
||
/** @test the embedded refcount is handled sensibly
|
||
* when it comes to copying. (This refcount
|
||
* is used by QueryFocusStack) */
|
||
void
|
||
check_RefcountProtection (PMO& refPlacement)
|
||
{
|
||
Scope startScope (refPlacement);
|
||
ScopePath path1 (startScope);
|
||
ScopePath path2 (path1);
|
||
|
||
path1 = path2;
|
||
CHECK (!isSameObject (path1,path2));
|
||
CHECK (0 == path1.ref_count());
|
||
CHECK (0 == path2.ref_count());
|
||
|
||
intrusive_ptr_add_ref (&path2);
|
||
CHECK (0 == path1.ref_count());
|
||
CHECK (0 < path2.ref_count());
|
||
|
||
ScopePath path3 (path2);
|
||
CHECK (0 == path3.ref_count()); // refcount not copied
|
||
|
||
path3.moveUp();
|
||
|
||
VERIFY_ERROR (LOGIC, path2 = path3 ); // overwriting of path with refcount is prohibited
|
||
CHECK (path1 != path3);
|
||
path1 = path2; // but path without refcount may be overwritten
|
||
path1 = path3;
|
||
CHECK (path1 == path3);
|
||
|
||
intrusive_ptr_release (&path2); // refcount drops to zero...
|
||
CHECK (0 == path1.ref_count());
|
||
CHECK (0 == path2.ref_count());
|
||
}
|
||
|
||
|
||
|
||
/** @test modify a path by \em navigating it.
|
||
* - move one step above the current leaf
|
||
* - move up to the root element
|
||
* - move back to the parent and verify we're just above the leaf
|
||
* - attach a new sibling node and move the path down to there
|
||
* - extract the common prefix, which should again point to the parent
|
||
* - find a placement in a completely separate branch (only sharing the
|
||
* root node). Navigate to there and verify root is the common prefix.
|
||
*/
|
||
void
|
||
navigate (const ScopePath refPath, PPIdx index)
|
||
{
|
||
#define __SHOWPATH(N) cout << "Step("<<N<<"): "<< path << endl;
|
||
|
||
ScopePath path (refPath); __SHOWPATH(1)
|
||
CHECK (path == refPath);
|
||
|
||
Scope leaf = path.getLeaf();
|
||
Scope parent = path.moveUp(); __SHOWPATH(2)
|
||
CHECK (path != refPath);
|
||
CHECK (refPath.contains (path));
|
||
CHECK (refPath.endsAt (leaf));
|
||
CHECK (path.endsAt (parent));
|
||
CHECK (parent == leaf.getParent());
|
||
CHECK (parent == path.getLeaf());
|
||
|
||
Scope root = path.goRoot(); __SHOWPATH(3)
|
||
CHECK (path != refPath);
|
||
CHECK (path.endsAt (root));
|
||
CHECK (refPath.contains (path));
|
||
CHECK (!path.endsAt (parent));
|
||
CHECK (!path.endsAt (leaf));
|
||
|
||
path.navigate (parent); __SHOWPATH(4)
|
||
CHECK (path.endsAt (parent));
|
||
CHECK (!path.endsAt (root));
|
||
CHECK (!path.endsAt (leaf));
|
||
|
||
TestPlacement<> newNode (*new DummyMO);
|
||
PMO& parentRefPoint = parent.getTop();
|
||
Scope newLocation =
|
||
index->find( // place newNode as sibling of "leaf"
|
||
index->insert (newNode, parentRefPoint));
|
||
path.navigate (newLocation); __SHOWPATH(5)
|
||
Scope sibling = path.getLeaf();
|
||
CHECK (sibling == newLocation);
|
||
CHECK (parent == sibling.getParent());
|
||
CHECK (path.endsAt (sibling));
|
||
CHECK (path.contains (parent));
|
||
CHECK (path.contains (root));
|
||
CHECK (!refPath.contains (path));
|
||
CHECK (!path.contains (refPath));
|
||
CHECK (!disjoint (path,refPath));
|
||
CHECK (!disjoint (refPath,path));
|
||
|
||
ScopePath prefix = commonPrefix (path,refPath);
|
||
CHECK (prefix == commonPrefix (refPath,path));
|
||
CHECK (prefix.endsAt (parent));
|
||
CHECK (!prefix.contains (leaf));
|
||
CHECK (!prefix.contains (sibling));
|
||
path.navigate (prefix.getLeaf()); __SHOWPATH(6)
|
||
CHECK (path == prefix);
|
||
|
||
// try to navigate to an unconnected location...
|
||
ScopePath beforeInvalidNavigation = path;
|
||
Scope const& unrelatedScope (fabricate_invalidScope());
|
||
VERIFY_ERROR (INVALID_SCOPE, path.navigate (unrelatedScope) );
|
||
CHECK (path == beforeInvalidNavigation); // not messed up by the incident
|
||
|
||
// now explore a completely separate branch....
|
||
PMO& separatePlacement = *explore_testScope (
|
||
*explore_testScope (
|
||
retrieve_firstTestSubMO21()));
|
||
path.navigate (separatePlacement);
|
||
CHECK (path);
|
||
CHECK (disjoint (path,refPath));
|
||
CHECK (path.contains(separatePlacement));
|
||
Scope other = path.getLeaf();
|
||
CHECK (isSameObject (other.getTop(), separatePlacement));
|
||
ScopePath rootPrefix = commonPrefix (path,refPath);
|
||
CHECK (rootPrefix.endsAt (root));
|
||
}
|
||
|
||
|
||
|
||
void
|
||
clear (ScopePath& path, PPIdx index)
|
||
{
|
||
CHECK (path);
|
||
PMO& rootNode = index->getRoot();
|
||
CHECK (path.getLeaf() != rootNode);
|
||
|
||
path.clear();
|
||
CHECK (!path);
|
||
CHECK (!isnil (path));
|
||
CHECK (path.getLeaf() == rootNode);
|
||
}
|
||
};
|
||
|
||
|
||
|
||
/** Register this test class... */
|
||
LAUNCHER (ScopePath_test, "unit session");
|
||
|
||
|
||
}}}} // namespace steam::mobject::session::test
|