Files
simgear/simgear/props/props_test.cxx
T
Julian Smith 1d978429f5 simgear/props/props.cxx: Avoid possible SEGV e.g. if listener removes itself.
simgear/props/props_test.cxx: test for self-unregister.

SGPropertyNode::removeChangeListener() used to modify the _listeners vector or
even delete it and set _listeners to NULL, if a listener was removed.

This could cause a SEGV if removeChangeListener() was called from
a listener, because listeners are called from methods such as
SGPropertyNode::fireValueChanged() which iterate through _listeners and don't
expect it to be changed to NULL.

The fix here is to keep a track of whether we are iterating through the
_listeners vector; if we are, SGPropertyNode::removeChangeListener() sets the
entry to NULL, instead of removing it.

We have a new internal function forEachListener() which takes
care of the iterating details - incrementing/decrementing the new
_listeners->_num_iterators count and skipping NULL entries. Before returning,
if _num_iterators is now zero it removes any NULL entries in the vector of
listeners.

The nListeners() member is no longer inline, because it needs to ignore NULL
entries in the vector. And _listeners now points to an internally-defined
struct which contains a new _num_iterators as well as the vector of listeners.

Added self-unregister test to simgear/props/props_test.cxx.
2020-06-13 18:11:49 +01:00

988 lines
31 KiB
C++

////////////////////////////////////////////////////////////////////////
// Test harness.
////////////////////////////////////////////////////////////////////////
#ifdef HAVE_CONFIG_H
# include <simgear_config.h>
#endif
#include <simgear/compiler.h>
#include <algorithm>
#include <memory> // std::unique_ptr
#include <iostream>
#include <map>
#include "props.hxx"
#include "props_io.hxx"
#include <simgear/misc/test_macros.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/test_macros.hxx>
using std::cout;
using std::cerr;
using std::endl;
////////////////////////////////////////////////////////////////////////
// Sample object.
////////////////////////////////////////////////////////////////////////
class Stuff {
public:
Stuff () : _stuff(199.0) {}
virtual float getStuff () const { return _stuff; }
virtual void setStuff (float stuff) { _stuff = stuff; }
virtual float getStuff (int index) const { return _stuff * index; }
virtual void setStuff (int index, float val) {
_stuff = val / index;
}
private:
float _stuff;
};
////////////////////////////////////////////////////////////////////////
// Sample function.
////////////////////////////////////////////////////////////////////////
static int get100 () { return 100; }
static double getNum (int index) { return 1.0 / index; }
////////////////////////////////////////////////////////////////////////
// Show a value.
////////////////////////////////////////////////////////////////////////
static void
show_values (const SGPropertyNode * node)
{
cout << "Bool: " << (node->getBoolValue() ? "true" : "false") << endl;
cout << "Int: " << node->getIntValue() << endl;
cout << "Float: " << node->getFloatValue() << endl;
cout << "Double: " << node->getDoubleValue() << endl;
cout << "String: " << node->getStringValue() << endl;
}
////////////////////////////////////////////////////////////////////////
// Test individual values.
////////////////////////////////////////////////////////////////////////
static void
test_value ()
{
SGPropertyNode * node;
cout << endl << "Value" << endl << endl;
//
// Test coercion for getters.
//
cout << "Testing coercion from bool (expect true)" << endl;
node = new SGPropertyNode;
node->setBoolValue(true);
show_values(node);
delete node;
cout << endl;
cout << "Testing coercion from int (expect 128)" << endl;
node = new SGPropertyNode;
node->setIntValue(128);
show_values(node);
delete node;
cout << endl;
cout << "Testing coercion from float (expect 1.0/3.0)" << endl;
node = new SGPropertyNode;
node->setFloatValue(1.0/3.0);
show_values(node);
delete node;
cout << endl;
cout << "Testing coercion from double (expect 1.0/3.0)" << endl;
node = new SGPropertyNode;
node->setDoubleValue(1.0/3.0);
show_values(node);
delete node;
cout << endl;
cout << "Testing coercion from string (expect 10e4)" << endl;
node = new SGPropertyNode;
node->setStringValue("10e4");
show_values(node);
delete node;
cout << endl;
cout << "Testing coercion from unspecified (expect -10e-4)" << endl;
node = new SGPropertyNode;
node->setUnspecifiedValue("-10e-4");
show_values(node);
delete node;
cout << endl;
//
// Test coercion for setters.
//
node = new SGPropertyNode;
cout << "Testing coercion to bool from bool (expect false)" << endl;
node->setBoolValue(false);
show_values(node);
cout << endl;
cout << "Testing coercion to bool from int (expect 1)" << endl;
node->setIntValue(1);
show_values(node);
cout << endl;
cout << "Testing coercion to bool from float (expect 1.1)" << endl;
node->setFloatValue(1.1);
show_values(node);
cout << endl;
cout << "Testing coercion to bool from double (expect 1.1)" << endl;
node->setDoubleValue(1.1);
show_values(node);
cout << endl;
cout << "Testing coercion to bool from string (expect 1e10)" << endl;
node->setStringValue("1e10");
show_values(node);
cout << endl;
cout << "Testing coercion to bool from unspecified (expect 1e10)" << endl;
node->setUnspecifiedValue("1e10");
show_values(node);
cout << endl;
// Test tying to a pointer.
static int myValue = 10;
cout << "Testing tying to a pointer (expect 10)" << endl;
if (!node->tie(SGRawValuePointer<int>(&myValue), false))
cout << "*** FAILED TO TIE VALUE!!!" << endl;
show_values(node);
cout << endl;
cout << "Changing base variable (expect -5)" << endl;
myValue = -5;
show_values(node);
if (!node->untie())
cout << "*** FAILED TO UNTIE VALUE!!!" << endl;
cout << endl;
// Test tying to static functions.
cout << "Create a new int value (expect 10)" << endl;
node->setIntValue(10);
show_values(node);
cout << endl;
cout << "Testing tying to static getter (expect 100)" << endl;
if (!node->tie(SGRawValueFunctions<int>(get100)))
cout << "*** FAILED TO TIE VALUE!!!" << endl;
show_values(node);
cout << endl;
cout << "Try changing value with no setter (expect 100)" << endl;
if (node->setIntValue(200))
cout << "*** setIntValue did not return false!!!" << endl;
show_values(node);
cout << endl;
cout << "Untie value (expect 100)" << endl;
if (!node->untie())
cout << "*** FAILED TO UNTIE VALUE!!!" << endl;
show_values(node);
cout << endl;
cout << "Try changing value (expect 200)" << endl;
if (!node->setIntValue(200))
cout << "*** setIntValue RETURNED FALSE!!!" << endl;
show_values(node);
cout << endl;
// Test tying to indexed static functions.
cout << "Create a new int value (expect 10)" << endl;
node->setIntValue(10);
show_values(node);
cout << endl;
cout << "Testing tying to indexed static getter (0.3333...)" << endl;
if (!node->tie(SGRawValueFunctionsIndexed<double>(3, getNum)))
cout << "*** FAILED TO TIE VALUE!!!" << endl;
show_values(node);
cout << endl;
cout << "Untie value (expect 0.3333...)" << endl;
if (!node->untie())
cout << "*** FAILED TO UNTIE VALUE!!!" << endl;
show_values(node);
cout << endl;
// Test methods.
cout << "Try tying to an object without defaults (expect 199)" << endl;
Stuff stuff;
SGRawValueMethods<class Stuff,float> tiedstuff(stuff,
&Stuff::getStuff,
&Stuff::setStuff);
if (!node->tie(tiedstuff, false))
cout << "*** FAILED TO TIE VALUE!!!" << endl;
show_values(node);
cout << endl;
cout << "Try untying from object (expect 199)" << endl;
if (!node->untie())
cout << "*** FAILED TO UNTIE VALUE!!!" << endl;
show_values(node);
cout << endl;
cout << "Try tying to an indexed method (expect 199)" << endl;
if (!node->tie(SGRawValueMethodsIndexed<class Stuff, float>
(stuff, 2, &Stuff::getStuff, &Stuff::setStuff)))
cout << "*** FAILED TO TIE VALUE!!!" << endl;
show_values(node);
cout << endl;
node->untie();
cout << "Change value (expect 50)" << endl;
if (!node->setIntValue(50))
cout << "*** FAILED TO SET VALUE!!!" << endl;
show_values(node);
cout << endl;
cout << "Try tying to an object with defaults (expect 50)" << endl;
if (!node->tie(tiedstuff, true))
cout << "*** FAILED TO TIE VALUE!!!" << endl;
show_values(node);
cout << endl;
delete node;
}
////////////////////////////////////////////////////////////////////////
// Check property nodes.
////////////////////////////////////////////////////////////////////////
static void
dump_node (const SGPropertyNode * node)
{
writeProperties(cout, node, true);
}
static void
test_property_nodes ()
{
SGPropertyNode root;
cout << "Created root node " << root.getPath() << endl;
SGPropertyNode * child = root.getChild("foo", 0, true);
SGPropertyNode *grandchild = child->getChild("bar", 0, true);
grandchild->setDoubleValue(100);
grandchild = child->getChild("bar", 1, true);
grandchild->setDoubleValue(200);
grandchild = child->getChild("bar", 2, true);
grandchild->setDoubleValue(300);
grandchild = child->getChild("bar", 3, true);
grandchild->setDoubleValue(400);
SG_CHECK_EQUAL(grandchild->getPosition(), 3);
child = root.getChild("hack", 0, true);
SG_CHECK_EQUAL(child->getPosition(), 1);
grandchild = child->getChild("bar", 0, true);
grandchild->setDoubleValue(100);
grandchild = child->getChild("bar", 3, true);
grandchild->setDoubleValue(200);
SG_CHECK_EQUAL(grandchild->getPosition(), 1);
grandchild = child->getChild("bar", 1, true);
grandchild->setDoubleValue(300);
grandchild = child->getChild("bar", 2, true);
grandchild->setDoubleValue(400);
SG_CHECK_EQUAL(grandchild->getPosition(), 3);
dump_node(&root);
SG_CHECK_EQUAL(child->getPosition(), 1);
SG_CHECK_EQUAL(grandchild->getPosition(), 3);
cout << "Trying path (expect /foo[0]/bar[0])" << endl;
grandchild = root.getNode("/hack/../foo/./bar[0]");
cout << "Path is " << grandchild->getPath() << endl;
cout << endl;
cout << "Looking for all /hack[0]/bar children" << endl;
std::vector<SGPropertyNode_ptr> bar = child->getChildren("bar");
cout << "There are " << bar.size() << " matches" << endl;
for (int i = 0; i < (int)bar.size(); i++)
cout << bar[i]->getName() << '[' << bar[i]->getIndex() << ']' << endl;
cout << endl;
cout << "Testing addition of a totally empty node" << endl;
if (root.getNode("/a/b/c", true) == 0)
cerr << "** FAILED to create /a/b/c" << endl;
dump_node(&root);
cout << endl;
}
void test_addChild()
{
SGPropertyNode root;
cout << "Testing the addChild function " << endl;
cout << "Created root node " << root.getPath() << endl;
SGPropertyNode *test = root.getChild("test", 0, true);
SGPropertyNode *n = test->getNode("foo", true);
cout << "Testing appending initial child node" << endl;
test = root.addChild("child", 0, true);
if (test == 0)
cerr << "** FAILED to append initial child node" << endl;
if (root.getNode("child", 0, false) != test)
cerr << "** FAILED to create initial child node at expected index #0" << endl;
n->getChild("child", 1, true)->setIntValue(1);
n->getChild("child", 2, true)->setIntValue(2);
n->getChild("child", 4, true)->setIntValue(4);
dump_node(&root);
SGPropertyNode *ch = n->addChild("child");
ch->setIntValue(5);
if (n->getChild("child", 5, false) != ch)
cerr << "** FAILED to create child node at expected index #5" << endl;
cerr << endl << "ADDED: " << ch->getPath() << endl << endl;
cout << "Testing appending child node at first empty index (Skipping 0)" << endl;
ch = n->addChild("child", 1, false);
ch->setIntValue(3);
if (n->getChild("child", 3, false) != ch)
cerr << "** FAILED to create child node at expected index #3" << endl;
cout << "Testing appending child node at first empty index" << endl;
ch = n->addChild("child", 0, false);
ch->setIntValue(0);
if (n->getChild("child", 0, false) != ch)
cerr << "** FAILED to create child node at expected index #0" << endl;
cout << "Testing appending child node" << endl;
ch = n->addChild("first", 0, false);
if (ch == 0)
cerr << "** Failed to add child node" << endl;
else
ch->setStringValue("append first");
if (n->getChild("first", 0, false) != ch)
cerr << "** FAILED to append child node with expected index #0" << endl;
cerr << "ADDED: " << ch->getPath() << endl;
if (root.getNode("test/foo/first", false) != ch)
cerr << "** FAILED to append child node at expected path 'test/foo/first'" << endl;
dump_node(&root);
}
bool ensureNListeners(SGPropertyNode* node, int n)
{
if (node->nListeners() != n) {
return false;
}
for (int c=0; c < node->nChildren(); ++c) {
if (!ensureNListeners(node->getChild(c), n)) {
return false;
}
}
return true;
}
void defineSamplePropertyTree(SGPropertyNode_ptr root)
{
root->setIntValue("position/body/a", 42);
root->setIntValue("position/body/c", 1);
root->setIntValue("position/body/d", 600);
root->setDoubleValue("velocity/body/x", 1.23);
root->setDoubleValue("velocity/body/y", 4.56);
root->setDoubleValue("velocity/body/z", 7.89);
root->setStringValue("settings/render/foo", "flightgear");
root->setStringValue("version", "10.3.19");
for (int i=0; i<4; ++i) {
root->setDoubleValue("controls/engine[" + std::to_string(i) + "]/throttle", 0.1 * i);
root->setBoolValue("controls/engine[" + std::to_string(i) + "]/starter", i % 2);
root->setDoubleValue("engine[" + std::to_string(i) + "]/rpm", 1000 * i);
root->setDoubleValue("engine[" + std::to_string(i) + "]/temp", 100 * i);
}
root->setDoubleValue("controls/doors/door[2]/position-norm", 0.5);
}
class TestListener : public SGPropertyChangeListener
{
public:
TestListener(SGPropertyNode* root, bool recursive = false, bool self_unregister = false) :
_root(root), _self_unregister(self_unregister) {}
virtual void valueChanged(SGPropertyNode* node) override
{
std::cout << "saw value changed for:" << node->getPath() << std::endl;
valueChangeCount[node]++;
if (_self_unregister) {
std::cout << "valueChanged(): calling removeChangeListener() to self-remove\n";
node->removeChangeListener(this);
}
}
int checkValueChangeCount(const std::string& path) const
{
auto it = valueChangeCount.find(_root->getNode(path));
if (it == valueChangeCount.end()) {
return 0;
}
return it->second;
}
struct ParentChange
{
SGPropertyNode_ptr parent, child;
};
virtual void childAdded(SGPropertyNode * parent, SGPropertyNode * child) override
{
adds.push_back({parent, child});
}
virtual void childRemoved(SGPropertyNode * parent, SGPropertyNode * child) override
{
removes.push_back({parent, child});
}
std::vector<SGPropertyNode*> addedTo(SGPropertyNode* pr)
{
std::vector<SGPropertyNode*> result;
for (auto c : adds) {
if (c.parent == pr) {
result.push_back(c.child);
}
}
return result;
}
std::vector<SGPropertyNode*> removedFrom(SGPropertyNode* pr)
{
std::vector<SGPropertyNode*> result;
for (auto c : removes) {
if (c.parent == pr) {
result.push_back(c.child);
}
}
return result;
}
bool checkAdded(const std::string& pr, SGPropertyNode* n)
{
auto adds = addedTo(_root->getNode(pr));
return std::find(adds.begin(), adds.end(), n) != adds.end();
}
bool checkRemoved(const std::string& pr, SGPropertyNode* n)
{
auto removes = removedFrom(_root->getNode(pr));
return std::find(removes.begin(), removes.end(), n) != removes.end();
}
bool checkRemoved(SGPropertyNode* pr, SGPropertyNode* n)
{
auto removes = removedFrom(pr);
return std::find(removes.begin(), removes.end(), n) != removes.end();
}
void resetChangeCounts()
{
valueChangeCount.clear();
adds.clear();
removes.clear();
}
private:
SGPropertyNode* _root;
bool _self_unregister;
std::map<SGPropertyNode*, unsigned int> valueChangeCount;
std::vector<ParentChange> adds;
std::vector<ParentChange> removes;
};
void testListener()
{
SGPropertyNode_ptr tree(new SGPropertyNode);
defineSamplePropertyTree(tree);
// basic listen
{
TestListener l(tree.get());
tree->getNode("position/body/c")->addChangeListener(&l);
tree->getNode("velocity/body/z")->addChangeListener(&l);
tree->getNode("controls/engine[2]/starter")->addChangeListener(&l);
tree->setBoolValue("controls/engine[2]/starter", true);
SG_CHECK_EQUAL(l.checkValueChangeCount("controls/engine[2]/starter"), 1);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/c"), 0);
tree->setIntValue("position/body/c", 123);
SG_CHECK_EQUAL(l.checkValueChangeCount("controls/engine[2]/starter"), 1);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/c"), 1);
// verify that changing non-listened props doesn't affect anything
tree->setIntValue("position/body/a", 19);
tree->setBoolValue("controls/engine[1]/starter", true);
SG_CHECK_EQUAL(l.checkValueChangeCount("controls/engine[2]/starter"), 1);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/c"), 1);
}
// initial value set
{
TestListener l(tree.get());
tree->getNode("velocity/body/y")->addChangeListener(&l, true);
tree->getNode("controls/engine[2]/starter")->addChangeListener(&l, true);
SG_CHECK_EQUAL(l.checkValueChangeCount("controls/engine[2]/starter"), 1);
SG_CHECK_EQUAL(l.checkValueChangeCount("velocity/body/y"), 1);
}
// delete listener while listening, should be fine
{
std::unique_ptr<TestListener> l( new TestListener(tree.get()));
tree->getNode("position/body/c")->addChangeListener(l.get());
tree->getNode("velocity/body/z")->addChangeListener(l.get());
tree->getNode("controls/engine[2]/starter")->addChangeListener(l.get());
SG_CHECK_EQUAL(tree->getNode("position/body/c")->nListeners(), 1);
SG_CHECK_EQUAL(
tree->getNode("controls/engine[2]/starter")->nListeners(), 1);
l.reset();
SG_CHECK_EQUAL(tree->getNode("position/body/c")->nListeners(), 0);
SG_CHECK_EQUAL(
tree->getNode("controls/engine[2]/starter")->nListeners(), 0);
tree->getNode("position/body/c")->setIntValue(49.0);
tree->getNode("controls/engine[2]/starter")->setBoolValue(true);
}
// add/remove of child listen
{
TestListener l(tree.get());
tree->getNode("controls")->addChangeListener(&l);
tree->getNode("velocity/body")->addChangeListener(&l);
tree->setDoubleValue("controls/pitch", 0.5);
SG_VERIFY(l.checkAdded("controls", tree->getNode("controls/pitch")));
tree->setIntValue("controls/yaw", 12);
SG_VERIFY(l.checkAdded("controls", tree->getNode("controls/yaw")));
// branch as well as leaf nodes should work the same
tree->setBoolValue("controls/gears/gear[1]/locked", true);
SG_VERIFY(l.checkAdded("controls", tree->getNode("controls/gears")));
SGPropertyNode_ptr rm = tree->getNode("velocity/body/z");
tree->getNode("velocity/body")->removeChild("z");
SG_VERIFY(l.checkRemoved("velocity/body", rm.get()));
SG_CHECK_EQUAL(
l.checkRemoved("velocity/body", tree->getNode("velocity/body/x")),
false);
}
tree = new SGPropertyNode;
defineSamplePropertyTree(tree);
// ensure basic listenter is not recursive
// disabled for now, since all listeners are recurisve
#if 0
{
TestListener l(tree.get(), false /* not recursive */);
tree->getNode("position/body")->addChangeListener(&l);
tree->getNode("controls/")->addChangeListener(&l);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 0);
tree->setIntValue("position/body/a", 111);
tree->setIntValue("position/body/z", 43);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 0);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/z"), 0);
tree->getNode("position/body/new", true);
SG_VERIFY(l.checkAdded("position/body",
tree->getNode("position/body/new")));
}
#endif
tree = new SGPropertyNode;
defineSamplePropertyTree(tree);
// recursive listen
{
TestListener l(tree.get(), true /* recursive */);
tree->getNode("position/body")->addChangeListener(&l);
tree->getNode("controls/")->addChangeListener(&l);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 0);
tree->setIntValue("position/body/a", 111);
tree->setIntValue("position/body/z", 43);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/z"), 1);
SG_VERIFY(l.checkAdded("position/body",
tree->getNode("position/body/z")));
tree->setBoolValue("controls/engine[3]/starter", true);
SG_CHECK_EQUAL(l.checkValueChangeCount("controls/engine[3]/starter"), 1);
tree->setBoolValue("controls/engines[1]/fuel-cutoff", true);
SG_VERIFY(
l.checkAdded("controls/engines[1]",
tree->getNode("controls/engines[1]/fuel-cutoff")));
SG_CHECK_EQUAL(
l.checkValueChangeCount("controls/engines[1]/fuel-cutoff"), 1);
tree->setDoubleValue("controls/doors/door[2]/position-norm", 0.5);
SG_CHECK_EQUAL(
l.checkValueChangeCount("controls/doors/door[2]/position-norm"), 1);
SGPropertyNode_ptr door2Node = tree->getNode("controls/doors/door[2]");
SGPropertyNode_ptr door2PosNode = tree->getNode("controls/doors/door[2]/position-norm");
tree->getNode("controls/doors")->removeChild(door2Node);
SG_VERIFY(l.checkRemoved("controls/doors", door2Node));
// default is not recurse for children
SG_CHECK_EQUAL(l.checkRemoved(door2Node, door2PosNode), false);
// adds *are* seen recursively
tree->setStringValue("controls/lights/light[3]/foo/state", "dim");
SG_VERIFY(
l.checkAdded("controls/lights/light[3]/foo",
tree->getNode("controls/lights/light[3]/foo/state")));
// remove a listener
tree->getNode("controls")->removeChangeListener(&l);
// removing listener does not trigger remove notifications
SG_VERIFY(!l.checkRemoved("controls",
tree->getNode("controls/engine[3]")));
SG_VERIFY(!l.checkRemoved("controls/engine[3]",
tree->getNode("controls/engine[3]/starter")));
tree->setBoolValue("controls/engines[1]/fuel-cutoff", false);
tree->setBoolValue("controls/engines[9]/fuel-cutoff", true);
// values should be unchanged
SG_CHECK_EQUAL(
l.checkValueChangeCount("controls/engines[1]/fuel-cutoff"), 1);
SG_CHECK_EQUAL(
l.checkValueChangeCount("controls/engines[9]/fuel-cutoff"), 0);
// ensure additional calls to fireChildrenRecursive don't cause multiple adds
// disabled for now since we don't add recursive listeners to sub-trees
// SG_VERIFY(ensureNListeners(tree->getNode("position/body"), 1));
SG_VERIFY(ensureNListeners(tree->getNode("controls"), 0));
tree->getNode("position/body")->fireCreatedRecursive();
// disabled for now since we don't add recursive listeners to sub-trees
//SG_VERIFY(ensureNListeners(tree->getNode("position/body"), 1));
}
}
void testAliasedListeners()
{
SGPropertyNode_ptr tree(new SGPropertyNode);
defineSamplePropertyTree(tree);
TestListener l(tree.get());
tree->getNode("position/world/x", true)->alias(tree->getNode("position/body/a"));
tree->getNode("position/earth", true)->alias(tree->getNode("position/body"));
tree->getNode("position/world/x")->addChangeListener(&l);
tree->getNode("position/earth")->addChangeListener(&l);
tree->setIntValue("position/body/a", 99);
SG_CHECK_EQUAL(tree->getIntValue("position/world/x"), 99);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/world/x"), 1);
tree->setIntValue("position/world/x", 101);
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 101);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/world/x"), 2);
}
class TiedPropertyDonor
{
public:
int someMember = 100;
const char* getWrappedA() const { return wrappedMember.c_str(); }
void setWrappedA(const char* s) { wrappedMember = std::string(s); }
std::string wrappedMember;
double wrappedB = 1.23;
double getWrappedB() const { return wrappedB; }
};
void tiedPropertiesTest()
{
TiedPropertyDonor donor;
SGPropertyNode_ptr tree(new SGPropertyNode);
defineSamplePropertyTree(tree);
tree->setDoubleValue("position/body/mass", 145.0);
tree->tie("position/body/a", SGRawValuePointer<int>(&donor.someMember));
tree->tie("settings/render/foo", SGRawValueMethods<TiedPropertyDonor, const char*>(donor, &TiedPropertyDonor::getWrappedA, &TiedPropertyDonor::setWrappedA));
tree->tie("position/body/mass", SGRawValueMethods<TiedPropertyDonor, double>(donor, &TiedPropertyDonor::getWrappedB, nullptr));
// tie sets current values of the property onto the setter
SG_CHECK_EQUAL(tree->getStringValue("settings/render/foo"),
std::string("flightgear"));
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 42);
// but can't write to this one!
SG_CHECK_EQUAL(tree->getDoubleValue("position/body/mass"), 1.23);
donor.setWrappedA("hello world");
donor.someMember = 13;
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 13);
SG_CHECK_EQUAL(tree->getStringValue("settings/render/foo"),
std::string("hello world"));
donor.someMember = 45;
donor.wrappedMember = "apples";
donor.wrappedB = 5000.0;
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 45);
SG_CHECK_EQUAL(tree->getStringValue("settings/render/foo"),
std::string("apples"));
SG_CHECK_EQUAL(tree->getDoubleValue("position/body/mass"), 5000.0);
// set value externally
tree->setIntValue("position/body/a", 99);
SG_CHECK_EQUAL(donor.someMember, 99);
tree->setStringValue("settings/render/foo", "lemons");
SG_CHECK_EQUAL(donor.wrappedMember, "lemons");
// set read-only
bool success = tree->setDoubleValue("position/body/mass", 10000.0);
SG_VERIFY(!success);
SG_CHECK_EQUAL(donor.wrappedB, 5000.0); // must not have changed
// un-tieing
tree->untie("position/body/a");
tree->untie("position/body/mass");
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 99);
SG_CHECK_EQUAL(tree->getDoubleValue("position/body/mass"), 5000.0);
}
void tiedPropertiesListeners()
{
TiedPropertyDonor donor;
SGPropertyNode_ptr tree(new SGPropertyNode);
defineSamplePropertyTree(tree);
tree->tie("position/body/a", SGRawValuePointer<int>(&donor.someMember));
tree->tie("settings/render/foo", SGRawValueMethods<TiedPropertyDonor, const char*>(donor, &TiedPropertyDonor::getWrappedA, &TiedPropertyDonor::setWrappedA));
tree->tie("position/body/mass", SGRawValueMethods<TiedPropertyDonor, double>(donor, &TiedPropertyDonor::getWrappedB, nullptr));
// tie sets current values of the property onto the setter
SG_CHECK_EQUAL(tree->getStringValue("settings/render/foo"),
std::string("flightgear"));
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 42);
TestListener l(tree.get());
tree->getNode("position/body/a")->addChangeListener(&l);
tree->getNode("position/body/mass")->addChangeListener(&l);
tree->getNode("settings/render/foo")->addChangeListener(&l);
// firstly test changes via setXXX API and verify they work
tree->setIntValue("position/body/a", 99);
SG_CHECK_EQUAL(donor.someMember, 99);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1);
tree->setDoubleValue("position/body/mass", -123.0);
SG_CHECK_EQUAL(donor.wrappedB, 1.23); // read-only!
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/mass"), 0);
tree->setStringValue("settings/render/foo", "thingA");
tree->setStringValue("settings/render/foo", "thingB");
SG_CHECK_EQUAL(donor.wrappedMember, std::string("thingB"));
SG_CHECK_EQUAL(l.checkValueChangeCount("settings/render/foo"), 2);
// now change values from inside the donor and verify it doesn't fire
// the listener
donor.wrappedMember = "pineapples";
SG_CHECK_EQUAL(tree->getStringValue("settings/render/foo"),
std::string("pineapples"));
SG_CHECK_EQUAL(l.checkValueChangeCount("settings/render/foo"), 2);
donor.someMember = 256;
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 256);
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1);
// now fire value changed
l.resetChangeCounts();
donor.wrappedB = 4.000000001;
tree->getNode("position/body/a")->fireValueChanged();
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1);
SG_CHECK_EQUAL(tree->getIntValue("position/body/a"), 256);
l.resetChangeCounts();
tree->getNode("position/body/a")->fireValueChanged();
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1);
tree->setDoubleValue("position/body/mass", 4.000000001);
// Not changed
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/mass"), 0);
donor.someMember = 970;
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 1); // not changed
tree->getNode("position/body/a")->fireValueChanged();
SG_CHECK_EQUAL(l.checkValueChangeCount("position/body/a"), 2); // changed
donor.wrappedMember = "pears";
// Not changed
SG_CHECK_EQUAL(l.checkValueChangeCount("settings/render/foo"), 0);
tree->getNode("settings/render/foo")->fireValueChanged();
// Changed
SG_CHECK_EQUAL(l.checkValueChangeCount("settings/render/foo"), 1);
}
void testDeleterListener()
{
SGPropertyNode_ptr tree = new SGPropertyNode;
defineSamplePropertyTree(tree);
// recursive listen
{
TestListener* l = new TestListener(tree.get(), true /* recursive */);
tree->getNode("position/body")->addChangeListener(l);
tree->getNode("controls/")->addChangeListener(l);
SG_CHECK_EQUAL(l->checkValueChangeCount("position/body/a"), 0);
// create additional children
tree->setFloatValue("position/body/sub/theta", 0.1234);
SG_VERIFY(l->checkAdded("position/body/sub",
tree->getNode("position/body/sub/theta")));
SG_CHECK_EQUAL(l->checkValueChangeCount("position/body/sub/theta"), 1);
delete l;
tree->setFloatValue("position/body/sub/theta", 99.123);
tree->setIntValue("position/body/a", 33);
// verify no listeners at all
SG_VERIFY(ensureNListeners(tree, 0));
}
// Self-unregister. prior to 2019-07-08 this would segv.
{
std::shared_ptr<TestListener> l1( new TestListener(tree.get(), true /* recursive */, true /*self_unregister*/));
tree->setFloatValue("position/body/sub/self-unregister", 0.1);
tree->getNode("position/body/sub/self-unregister")->addChangeListener(l1.get());
tree->setFloatValue("position/body/sub/self-unregister", 0.2);
}
}
int main (int ac, char ** av)
{
test_value();
test_property_nodes();
for (int i = 1; i < ac; i++) {
try {
// cout << "Reading " << av[i] << endl;
SGPropertyNode root;
readProperties(SGPath::fromLocal8Bit(av[i]), &root);
writeProperties(cout, &root, true);
// cout << endl;
} catch (std::string &message) {
cout << "Aborted with " << message << endl;
}
}
test_addChild();
testListener();
tiedPropertiesTest();
tiedPropertiesListeners();
testDeleterListener();
// disable test for the moment
// testAliasedListeners();
return 0;
}