/*
* tests/atoms/core/StateUTest.cxxtest
*
* Copyright (C) 2015 Linas Vepstas
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License v3 as
* published by the Free Software Foundation and including the exceptions
* at http://opencog.org/wiki/Licenses
*
* 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 Affero General Public License
* along with this program; if not, write to:
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <opencog/atoms/base/Atom.h>
#include <opencog/atoms/base/Node.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/atoms/grant/StateLink.h>
#include <opencog/atoms/value/LinkValue.h>
using namespace opencog;
// Test the StateLink.
//
class StateLinkUTest : public CxxTest::TestSuite
{
private:
AtomSpacePtr _asp;
AtomSpace& _as;
public:
StateLinkUTest() :
_asp(createAtomSpace()),
_as(* ((AtomSpace*) _asp.get()))
{
logger().set_print_to_stdout_flag(true);
}
void setUp() { _as.clear(); }
void tearDown() {}
void test_setting();
void test_getting();
void test_putting();
void test_list();
void test_scope();
void test_spaces();
};
#define N _as.add_node
#define L _as.add_link
// Test to make sure that there is only ever one StateLink.
void StateLinkUTest::test_setting()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
// Set the state to apple.
Handle fruit, apple, bananna;
Handle link =
L(STATE_LINK,
fruit = N(ANCHOR_NODE, "fruit"),
apple = N(CONCEPT_NODE, "apple"));
bananna = N(CONCEPT_NODE, "bananna");
// The incoming set should be simple.
TS_ASSERT_EQUALS(1, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(1, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(0, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(apple, link->getOutgoingAtom(1));
// Change the state to bananna
link =
L(STATE_LINK,
N(ANCHOR_NODE, "fruit"),
N(CONCEPT_NODE, "bananna"));
// The incoming set should be simple.
TS_ASSERT_EQUALS(1, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(0, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(1, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(bananna, link->getOutgoingAtom(1));
// Change the state back to apple.
link =
L(STATE_LINK,
N(ANCHOR_NODE, "fruit"),
N(CONCEPT_NODE, "apple"));
// The incoming set should be simple.
TS_ASSERT_EQUALS(1, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(1, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(0, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(apple, link->getOutgoingAtom(1));
// Change the state back to bananna, again
link =
L(STATE_LINK,
N(ANCHOR_NODE, "fruit"),
N(CONCEPT_NODE, "bananna"));
// The incoming set should be simple.
TS_ASSERT_EQUALS(1, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(0, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(1, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(bananna, link->getOutgoingAtom(1));
// Change the state back to apple, again
link =
L(STATE_LINK,
N(ANCHOR_NODE, "fruit"),
N(CONCEPT_NODE, "apple"));
// The incoming set should be simple.
TS_ASSERT_EQUALS(1, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(1, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(0, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(apple, link->getOutgoingAtom(1));
// --------------------------------------
// ... But variable nodes are OK...
Handle vara = N(VARIABLE_NODE, "$vara");
Handle varb = N(VARIABLE_NODE, "$varb");
L(STATE_LINK, N(ANCHOR_NODE, "fruit"), vara);
L(STATE_LINK, N(ANCHOR_NODE, "fruit"), varb);
// The incoming set should have 2 vars and one apple.
TS_ASSERT_EQUALS(3, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(1, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(0, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(apple, link->getOutgoingAtom(1));
// Change the state to bananna, again
link =
L(STATE_LINK,
N(ANCHOR_NODE, "fruit"),
N(CONCEPT_NODE, "bananna"));
// The incoming set should have vars and bananna.
TS_ASSERT_EQUALS(3, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(0, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(1, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(bananna, link->getOutgoingAtom(1));
// Change the state back to apple, again
link =
L(STATE_LINK,
N(ANCHOR_NODE, "fruit"),
N(CONCEPT_NODE, "apple"));
// The incoming set should two vars, one apple.
TS_ASSERT_EQUALS(3, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(1, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(0, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(apple, link->getOutgoingAtom(1));
logger().info("END TEST: %s", __FUNCTION__);
}
// Test to make sure MeetLink works.
void StateLinkUTest::test_getting()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
Handle empty_get =
L(MEET_LINK,
L(STATE_LINK, N(ANCHOR_NODE, "fruit"), N(VARIABLE_NODE, "$x")));
ValuePtr vgotten = empty_get->execute(&_as);
printf("Got %s\n", vgotten->to_string().c_str());
// MeetLink returns empty LinkValue for no results
ValuePtr empty_result = createLinkValue(HandleSeq());
// The result of the meet should be correct.
TS_ASSERT(*empty_result == *vgotten);
TS_ASSERT(*vgotten == *empty_result);
// Set the state to apple.
Handle fruit, apple, bananna;
L(STATE_LINK,
fruit = N(ANCHOR_NODE, "fruit"),
apple = N(CONCEPT_NODE, "apple"));
bananna = N(CONCEPT_NODE, "bananna");
Handle get =
L(MEET_LINK,
L(STATE_LINK, N(ANCHOR_NODE, "fruit"), N(VARIABLE_NODE, "$x")));
vgotten = get->execute(&_as);
printf("Got %s\n", vgotten->to_string().c_str());
// MeetLink returns LinkValue containing the apple
ValuePtr apple_result = createLinkValue(HandleSeq{N(CONCEPT_NODE, "apple")});
// The result of the meet should be correct.
TS_ASSERT(*apple_result == *vgotten);
TS_ASSERT(*vgotten == *apple_result);
logger().info("END TEST: %s", __FUNCTION__);
}
// Test to make sure PutLink works.
void StateLinkUTest::test_putting()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
// Set the state to apple.
Handle fruit, apple, bananna;
L(STATE_LINK,
fruit = N(ANCHOR_NODE, "fruit"),
apple = N(CONCEPT_NODE, "apple"));
bananna = N(CONCEPT_NODE, "bananna");
Handle put =
L(PUT_LINK,
L(STATE_LINK, N(ANCHOR_NODE, "fruit"), N(VARIABLE_NODE, "$y")),
N(CONCEPT_NODE, "strawberry"));
Handle putten = _as.add_atom(HandleCast(put->execute(&_as)));
printf("Put %s\n", putten->to_string().c_str());
Handle strawberry = N(CONCEPT_NODE, "strawberry");
Handle state = StateLink::get_state(N(ANCHOR_NODE, "fruit"), &_as);
// The incoming set should be simple.
TS_ASSERT_EQUALS(state, strawberry);
logger().info("END TEST: %s", __FUNCTION__);
}
// Test to make sure a ListLink can be used as the key.
// Cut-n-paste of the test_setting() test, except that the
// anchor will be a list.
#define NA(T,S) Handle(createNode(T,S))
#define LA(T,A1,A2) Handle(createLink(T,A1,A2))
void StateLinkUTest::test_list()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
Handle fruit =
L(LIST_LINK, N(ANCHOR_NODE, "fruit"), N(CONCEPT_NODE, "fresh"));
// Set the state to apple.
Handle apple, bananna;
Handle link =
L(STATE_LINK,
LA(LIST_LINK, NA(ANCHOR_NODE, "fruit"), NA(CONCEPT_NODE, "fresh")),
apple = N(CONCEPT_NODE, "apple"));
bananna = N(CONCEPT_NODE, "bananna");
// The incoming set should be simple.
TS_ASSERT_EQUALS(1, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(1, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(0, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(apple, link->getOutgoingAtom(1));
// Change the state to bananna
link =
L(STATE_LINK,
LA(LIST_LINK, NA(ANCHOR_NODE, "fruit"), NA(CONCEPT_NODE, "fresh")),
N(CONCEPT_NODE, "bananna"));
// The incoming set should be simple.
TS_ASSERT_EQUALS(1, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(0, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(1, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(bananna, link->getOutgoingAtom(1));
// Change the state back to apple.
link =
L(STATE_LINK,
LA(LIST_LINK, NA(ANCHOR_NODE, "fruit"), NA(CONCEPT_NODE, "fresh")),
N(CONCEPT_NODE, "apple"));
// The incoming set should be simple.
TS_ASSERT_EQUALS(1, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(1, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(0, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(apple, link->getOutgoingAtom(1));
// Change the state back to bananna, again
link =
L(STATE_LINK,
LA(LIST_LINK, NA(ANCHOR_NODE, "fruit"), NA(CONCEPT_NODE, "fresh")),
N(CONCEPT_NODE, "bananna"));
// The incoming set should be simple.
TS_ASSERT_EQUALS(1, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(0, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(1, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(bananna, link->getOutgoingAtom(1));
// Change the state back to apple, again
link =
L(STATE_LINK,
LA(LIST_LINK, NA(ANCHOR_NODE, "fruit"), NA(CONCEPT_NODE, "fresh")),
N(CONCEPT_NODE, "apple"));
// The incoming set should be simple.
TS_ASSERT_EQUALS(1, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(1, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(0, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(apple, link->getOutgoingAtom(1));
// --------------------------------------
// ... But variable nodes are OK...
Handle vara = N(VARIABLE_NODE, "$vara");
Handle varb = N(VARIABLE_NODE, "$varb");
L(STATE_LINK,
LA(LIST_LINK, NA(ANCHOR_NODE, "fruit"), NA(CONCEPT_NODE, "fresh")),
vara);
L(STATE_LINK,
LA(LIST_LINK, NA(ANCHOR_NODE, "fruit"), NA(CONCEPT_NODE, "fresh")),
varb);
// The incoming set should have 2 vars and one apple.
TS_ASSERT_EQUALS(3, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(1, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(0, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(apple, link->getOutgoingAtom(1));
// Change the state to bananna, again
link =
L(STATE_LINK,
LA(LIST_LINK, NA(ANCHOR_NODE, "fruit"), NA(CONCEPT_NODE, "fresh")),
N(CONCEPT_NODE, "bananna"));
// The incoming set should have vars and bananna.
TS_ASSERT_EQUALS(3, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(0, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(1, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(bananna, link->getOutgoingAtom(1));
// Change the state back to apple, again
link =
L(STATE_LINK,
LA(LIST_LINK, NA(ANCHOR_NODE, "fruit"), NA(CONCEPT_NODE, "fresh")),
N(CONCEPT_NODE, "apple"));
// The incoming set should two vars, one apple.
TS_ASSERT_EQUALS(3, fruit->getIncomingSetSize());
TS_ASSERT_EQUALS(1, apple->getIncomingSetSize());
TS_ASSERT_EQUALS(0, bananna->getIncomingSetSize());
TS_ASSERT_EQUALS(fruit, link->getOutgoingAtom(0));
TS_ASSERT_EQUALS(apple, link->getOutgoingAtom(1));
logger().info("END TEST: %s", __FUNCTION__);
}
// Test correct scoping of MeetLink (issue #1576)
void StateLinkUTest::test_scope()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
// Initial data
L(STATE_LINK, N(ANCHOR_NODE, "rule"),
L(IMPLICATION_LINK,
L(EVALUATION_LINK,
N(PREDICATE_NODE, "predurnatural"),
L(LIST_LINK, N(VARIABLE_NODE, "$foo"))),
N(CONCEPT_NODE, "golem")));
// This should NOT find the above!
Handle free_get =
L(MEET_LINK,
L(TYPED_VARIABLE_LINK,
N(VARIABLE_NODE, "$x"), N(TYPE_NODE, "ImplicationLink")),
L(STATE_LINK, N(ANCHOR_NODE, "rule"), N(VARIABLE_NODE, "$x")));
ValuePtr vgotten = free_get->execute(&_as);
printf("Free Got %s\n", vgotten->to_string().c_str());
ValuePtr empty_result = createLinkValue(HandleSeq());
// The result of the meet should be correct.
TS_ASSERT(*vgotten == *empty_result);
// -------------------------------------------
// Alternate data
Handle scoped =
L(RULE_LINK,
L(EVALUATION_LINK,
N(PREDICATE_NODE, "predatory"),
L(LIST_LINK, N(VARIABLE_NODE, "$foo"))),
N(CONCEPT_NODE, "loan shark"));
L(STATE_LINK, N(ANCHOR_NODE, "rule"), scoped);
Handle scoped_get =
L(MEET_LINK,
L(TYPED_VARIABLE_LINK,
N(VARIABLE_NODE, "$x"), N(TYPE_NODE, "RuleLink")),
L(STATE_LINK, N(ANCHOR_NODE, "rule"), N(VARIABLE_NODE, "$x")));
ValuePtr vsgotten = scoped_get->execute(&_as);
printf("Scoped Got %s\n", vsgotten->to_string().c_str());
ValuePtr scope_result = createLinkValue(HandleSeq{scoped});
// The result of the meet should be correct.
TS_ASSERT(*scope_result == *vsgotten);
TS_ASSERT(*vsgotten == *scope_result);
// -------------------------------------------
// Repeating the free-var get should still NOT find the above!
ValuePtr vregotten = free_get->execute(&_as);
printf("Re-Free Got %s\n", vregotten->to_string().c_str());
// The result of the meet should be correct (still empty).
TS_ASSERT(*empty_result == *vregotten);
TS_ASSERT(*vregotten == *empty_result);
logger().info("END TEST: %s", __FUNCTION__);
}
// Test to make sure that there is only ever one StateLink,
// even as child AtomSpaces are used. That is, the StateLink
// in each child overloads the one in it's parent.
#define N2 as2->add_node
#define L2 as2->add_link
#define N3 as3->add_node
#define L3 as3->add_link
void StateLinkUTest::test_spaces()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
// Empty robot gripper.
Handle robot = N(CONCEPT_NODE, "robot arm");
Handle empty = N(CONCEPT_NODE, "empty");
// Set the gripper to empty.
Handle rempty = L(STATE_LINK, robot, empty);
AtomSpacePtr as2 = createAtomSpace(_asp);
Handle b42 =
L2(EVALUATION_LINK,
N2(PREDICATE_NODE, "holding"),
N2(CONCEPT_NODE, "block 42"));
Handle r42 = L2(STATE_LINK, robot, b42);
AtomSpacePtr as3 = createAtomSpace(as2);
Handle b43 =
L3(EVALUATION_LINK,
N3(PREDICATE_NODE, "holding"),
N3(CONCEPT_NODE, "block 43"));
Handle r43 = L3(STATE_LINK, robot, b43);
// Verify static methods
Handle gempty = StateLink::get_link(robot, &_as);
TS_ASSERT_EQUALS(gempty, rempty)
Handle g42 = StateLink::get_link(robot, as2.get());
TS_ASSERT_EQUALS(g42, r42)
Handle g43 = StateLink::get_link(robot, as3.get());
TS_ASSERT_EQUALS(g43, r43)
// Verify AtomTable getter
HandleSeq hset;
_as.get_handles_by_type(hset, STATE_LINK);
Handle tempty = *hset.begin();
TS_ASSERT_EQUALS(tempty, rempty)
hset.clear();
as2->get_handles_by_type(hset, STATE_LINK);
Handle t42 = *hset.begin();
TS_ASSERT_EQUALS(t42, r42)
hset.clear();
as3->get_handles_by_type(hset, STATE_LINK);
Handle t43 = *hset.begin();
TS_ASSERT_EQUALS(t43, r43)
// -----------------------
// Test atmospace-dependent incoming sets.
TS_ASSERT_EQUALS(1, robot->getIncomingSetSize(&_as));
TS_ASSERT_EQUALS(2, robot->getIncomingSetSize(as2.get()));
TS_ASSERT_EQUALS(3, robot->getIncomingSetSize(as3.get()));
Handle iempty = *robot->getIncomingSet(&_as).begin();
TS_ASSERT_EQUALS(iempty, rempty)
// -----------------------
Handle full = N(CONCEPT_NODE, "full");
Handle rfull = L(STATE_LINK, robot, full);
Handle gfull = StateLink::get_link(robot, &_as);
TS_ASSERT_EQUALS(gfull, rfull)
g42 = StateLink::get_link(robot, as2.get());
TS_ASSERT_EQUALS(g42, r42)
g43 = StateLink::get_link(robot, as3.get());
TS_ASSERT_EQUALS(g43, r43)
// Verify no change to atmospace-dependent incoming sets.
TS_ASSERT_EQUALS(1, robot->getIncomingSetSize(&_as));
TS_ASSERT_EQUALS(2, robot->getIncomingSetSize(as2.get()));
TS_ASSERT_EQUALS(3, robot->getIncomingSetSize(as3.get()));
Handle ifull = *robot->getIncomingSet(&_as).begin();
TS_ASSERT_EQUALS(ifull, rfull)
hset.clear();
_as.get_handles_by_type(hset, STATE_LINK);
Handle tfull = *hset.begin();
TS_ASSERT_EQUALS(tfull, rfull)
hset.clear();
as2->get_handles_by_type(hset, STATE_LINK);
t42 = *hset.begin();
TS_ASSERT_EQUALS(t42, r42)
hset.clear();
as3->get_handles_by_type(hset, STATE_LINK);
t43 = *hset.begin();
TS_ASSERT_EQUALS(t43, r43)
// ------------------------------------------------------------
// We are done.
logger().info("END TEST: %s", __FUNCTION__);
}