OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / tests / atoms / grant / StateLinkUTest.cxxtest

555 lines · cxxtest ref:
/*
 * 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__);
}