uhd/host/tests/expert_test.cpp
Martin Braun 87de94748a experts: Change coercion policy for regular prop nodes
The experts framework has two ways of integrating expert nodes into the
property tree: add_prop_node() and add_dual_prop_node(). In the latter
case, the experts should take care of coercion, and thus, we subscribe
to the desired value.

In the former case, this is not necessary, and precludes us from using
set_coercer() with prop nodes on the prop tree. This change lets us use
regular nodes in the expert framework that also use property tree
coercers.

As of now, there is not a single property node in UHD that uses
add_prop_node() and also does any kind of coercion, so this change has
no effect on current code (this is only used in TwinRX as of now).
2021-05-04 14:34:44 -05:00

270 lines
8.9 KiB
C++

//
// Copyright 2010-2011 Ettus Research LLC
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include <uhd/property_tree.hpp>
#include <uhdlib/experts/expert_container.hpp>
#include <uhdlib/experts/expert_factory.hpp>
#include <boost/format.hpp>
#include <boost/test/unit_test.hpp>
#include <fstream>
#include <memory>
using namespace uhd::experts;
class worker1_t : public worker_node_t
{
public:
worker1_t(const node_retriever_t& db)
: worker_node_t("A+B=C"), _a(db, "A/desired"), _b(db, "B"), _c(db, "C")
{
bind_accessor(_a);
bind_accessor(_b);
bind_accessor(_c);
}
private:
void resolve() override
{
_c = _a + _b;
}
data_reader_t<int> _a;
data_reader_t<int> _b;
data_writer_t<int> _c;
};
//=============================================================================
class worker2_t : public worker_node_t
{
public:
worker2_t(const node_retriever_t& db)
: worker_node_t("C*D=E"), _c(db, "C"), _d(db, "D"), _e(db, "E")
{
bind_accessor(_c);
bind_accessor(_d);
bind_accessor(_e);
}
private:
void resolve() override
{
_e.set(_c.get() * _d.get());
}
data_reader_t<int> _c;
data_reader_t<int> _d;
data_writer_t<int> _e;
};
//=============================================================================
class worker3_t : public worker_node_t
{
public:
worker3_t(const node_retriever_t& db)
: worker_node_t("-B=F"), _b(db, "B"), _f(db, "F")
{
bind_accessor(_b);
bind_accessor(_f);
}
private:
void resolve() override
{
_f.set(-_b.get());
}
data_reader_t<int> _b;
data_writer_t<int> _f;
};
//=============================================================================
class worker4_t : public worker_node_t
{
public:
worker4_t(const node_retriever_t& db)
: worker_node_t("E-F=G"), _e(db, "E"), _f(db, "F"), _g(db, "G")
{
bind_accessor(_e);
bind_accessor(_f);
bind_accessor(_g);
}
private:
void resolve() override
{
_g.set(_e.get() - _f.get());
}
data_reader_t<int> _e;
data_reader_t<int> _f;
data_writer_t<int> _g;
};
//=============================================================================
class worker5_t : public worker_node_t
{
public:
worker5_t(const node_retriever_t& db, std::shared_ptr<int> output)
: worker_node_t("Consume_G"), _g(db, "G"), _c(db, "C"), _output(output)
{
bind_accessor(_g);
// bind_accessor(_c);
}
private:
void resolve() override
{
*_output = _g;
}
data_reader_t<int> _g;
data_writer_t<int> _c;
std::shared_ptr<int> _output;
};
class worker6_t : public worker_node_t
{
public:
worker6_t() : worker_node_t("null_worker") {}
private:
void resolve() override {}
};
//=============================================================================
#define DUMP_VARS \
BOOST_TEST_MESSAGE(str( \
boost::format( \
"### State = {A=%d%s, B=%d%s, C=%d%s, D=%d%s, E=%d%s, F=%d%s, G=%d%s}\n") \
% nodeA.get() % (nodeA.is_dirty() ? "*" : "") % nodeB.get() \
% (nodeB.is_dirty() ? "*" : "") % nodeC.get() % (nodeC.is_dirty() ? "*" : "") \
% nodeD.get() % (nodeD.is_dirty() ? "*" : "") % nodeE.get() \
% (nodeE.is_dirty() ? "*" : "") % nodeF.get() % (nodeF.is_dirty() ? "*" : "") \
% nodeG.get() % (nodeG.is_dirty() ? "*" : "")));
#define VALIDATE_ALL_DEPENDENCIES \
BOOST_CHECK(!nodeA.is_dirty()); \
BOOST_CHECK(!nodeB.is_dirty()); \
BOOST_CHECK(!nodeC.is_dirty()); \
BOOST_CHECK(!nodeD.is_dirty()); \
BOOST_CHECK(!nodeE.is_dirty()); \
BOOST_CHECK(!nodeF.is_dirty()); \
BOOST_CHECK(!nodeG.is_dirty()); \
BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get()); \
BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get()); \
BOOST_CHECK(nodeF.get() == -nodeB.get()); \
BOOST_CHECK(nodeG.get() == nodeE.get() - nodeF.get()); \
BOOST_CHECK(nodeG.get() == *final_output);
BOOST_AUTO_TEST_CASE(test_experts)
{
// Initialize container object
expert_container::sptr container = expert_factory::create_container("example");
uhd::property_tree::sptr tree = uhd::property_tree::make();
// Output of expert tree
std::shared_ptr<int> final_output = std::make_shared<int>();
// Add data nodes to container
expert_factory::add_dual_prop_node<int>(
container, tree, "A", 0, uhd::experts::AUTO_RESOLVE_ON_WRITE);
expert_factory::add_prop_node<int>(container, tree, "B", 0);
expert_factory::add_data_node<int>(container, "C", 0);
expert_factory::add_data_node<int>(container, "D", 1);
expert_factory::add_prop_node<int>(
container, tree, "E", 0, uhd::experts::AUTO_RESOLVE_ON_READ);
expert_factory::add_data_node<int>(container, "F", 0);
expert_factory::add_data_node<int>(container, "G", 0);
// B also gets a coercer. It coerces 4 to 3.
tree->access<int>("B").set_coercer([](const int b) { return b == 4 ? 3 : b; });
// Add worker nodes to container
expert_factory::add_worker_node<worker1_t>(container, container->node_retriever());
expert_factory::add_worker_node<worker2_t>(container, container->node_retriever());
expert_factory::add_worker_node<worker3_t>(container, container->node_retriever());
expert_factory::add_worker_node<worker4_t>(container, container->node_retriever());
expert_factory::add_worker_node<worker5_t>(
container, container->node_retriever(), final_output);
expert_factory::add_worker_node<worker6_t>(container);
// Once initialized, getting modify access to graph nodes is possible (by design) but
// extremely red-flaggy! But we do it here to monitor things
data_node_t<int>& nodeA =
*(const_cast<data_node_t<int>*>(dynamic_cast<const data_node_t<int>*>(
&container->node_retriever().lookup("A/desired"))));
data_node_t<int>& nodeB = *(const_cast<data_node_t<int>*>(
dynamic_cast<const data_node_t<int>*>(&container->node_retriever().lookup("B"))));
data_node_t<int>& nodeC = *(const_cast<data_node_t<int>*>(
dynamic_cast<const data_node_t<int>*>(&container->node_retriever().lookup("C"))));
data_node_t<int>& nodeD = *(const_cast<data_node_t<int>*>(
dynamic_cast<const data_node_t<int>*>(&container->node_retriever().lookup("D"))));
data_node_t<int>& nodeE = *(const_cast<data_node_t<int>*>(
dynamic_cast<const data_node_t<int>*>(&container->node_retriever().lookup("E"))));
data_node_t<int>& nodeF = *(const_cast<data_node_t<int>*>(
dynamic_cast<const data_node_t<int>*>(&container->node_retriever().lookup("F"))));
data_node_t<int>& nodeG = *(const_cast<data_node_t<int>*>(
dynamic_cast<const data_node_t<int>*>(&container->node_retriever().lookup("G"))));
DUMP_VARS
// Ensure init behavior
BOOST_CHECK(nodeA.is_dirty());
BOOST_CHECK(nodeB.is_dirty());
BOOST_CHECK(nodeC.is_dirty());
BOOST_CHECK(nodeD.is_dirty());
BOOST_CHECK(nodeE.is_dirty());
BOOST_CHECK(nodeF.is_dirty());
BOOST_CHECK(nodeG.is_dirty());
container->resolve_all();
VALIDATE_ALL_DEPENDENCIES; // Ensure a default resolve
// Ensure basic node value propagation
tree->access<int>("B").set(4); // Set it 4, but that will get coerced to 3
BOOST_CHECK(nodeB.get() == 3); // Ensure value propagated
BOOST_CHECK(nodeB.is_dirty()); // Ensure that nothing got resolved...
container->resolve_all();
VALIDATE_ALL_DEPENDENCIES
nodeD.set(2); // Hack for testing
// Ensure auto-resolve on write
tree->access<int>("A").set(200);
BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get());
BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get());
BOOST_CHECK(nodeG.get() == nodeE.get() - nodeF.get());
container->resolve_all();
VALIDATE_ALL_DEPENDENCIES
container->resolve_all();
VALIDATE_ALL_DEPENDENCIES
// Ensure auto-resolve on read
tree->access<int>("E").get();
BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get());
BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get());
BOOST_CHECK(!nodeE.is_dirty());
tree->access<int>("E").set(-10);
container->resolve_all(true);
VALIDATE_ALL_DEPENDENCIES
// Resolve to/from
tree->access<int>("A").set(-1);
container->resolve_to("C");
BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get());
BOOST_CHECK(!nodeC.is_dirty());
container->resolve_to("Consume_G");
VALIDATE_ALL_DEPENDENCIES
}