OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / tests / scm / MultiAtomSpaceUTest.cxxtest

405 lines · cxxtest ref:
/*
 * tests/scm/MultiAtomSpace.cxxtest
 *
 * Copyright (C) 2009, 2011, 2014 Linas Vepstas <linasvepstas@gmail.com>
 * 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/atomspace/AtomSpace.h>
#include <opencog/atoms/base/Node.h>
#include <opencog/atoms/value/FloatValue.h>
#include <opencog/guile/SchemeEval.h>
#include <opencog/util/Logger.h>

using namespace opencog;

class MultiAtomSpace :  public CxxTest::TestSuite
{
private:
	AtomSpacePtr main_as;
	SchemeEval* evaluator;
	Handle disorder;
	Handle disordered;
	Handle disor_more;

public:

	MultiAtomSpace(void)
	{
		logger().set_level(Logger::DEBUG);
		logger().set_print_to_stdout_flag(true);
	}

	~MultiAtomSpace()
	{
		// erase the log file if no assertions failed
		if (!CxxTest::TestTracker::tracker().suiteFailed())
			std::remove(logger().get_filename().c_str());
	}

	void setUp(void);
	void tearDown(void);

	void test_two_scm(void);
	void test_three(void);
	void test_nest(void);
	void test_nest_scm(void);
	void test_pm_scm(void);
	void test_as_of_atom_scm(void);
	void test_readonly_scm(void);
};

/*
 * This function sets up .. whatever.
 */
void MultiAtomSpace::setUp(void)
{
	main_as = createAtomSpace();
	evaluator = SchemeEval::get_scheme_evaluator(main_as);
	evaluator->eval("(add-to-load-path \"" PROJECT_SOURCE_DIR "\")");
	evaluator->eval("(add-to-load-path \"../../..\")");
}

void MultiAtomSpace::tearDown(void)
{
	evaluator = nullptr;
	main_as = nullptr;
}

#define CHKERR \
	TSM_ASSERT("Caught scm error during eval", \
		(false == evaluator->eval_error()));
/*
 * Test two atomspaces, both accessed from one evaluator.
 */
void MultiAtomSpace::test_two_scm(void)
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	// Define an alternate atomspace
	evaluator->eval("(define alt-as (AtomSpace))");
	std::string msg = evaluator->eval("alt-as");
	printf("New atomspace is: %s\n", msg.c_str());

	// Define a function that performs a thunk in the alternate atomspace.
	evaluator->eval("(define (alt-env f) "
   	"(let ((curr-as (cog-set-atomspace! alt-as)) "
		"      (result (f))) "
   	"   (cog-set-atomspace! curr-as) result))");

	msg = evaluator->eval("(cog-atomspace)");
	printf("Primary atomspace is: %s\n", msg.c_str());

	msg = evaluator->eval("(alt-env cog-atomspace)");
	printf("Alternate atomspace is: %s\n", msg.c_str());

	// Populate atomspace with some misc junk
	evaluator->eval("(NumberNode \"42\")");
	evaluator->eval("(VariableNode \"$var\")");

	Handle h1 = evaluator->eval_h("(ConceptNode \"stuff\")");
	CHKERR;

	TSM_ASSERT("Failed to create atom", Handle::UNDEFINED != h1);
	TSM_ASSERT_EQUALS("Wrong atom type", CONCEPT_NODE, h1->get_type());

	// Create the 'same' ConceptNode in a different atomspace
	Handle h2 = evaluator->eval_h(
		"(alt-env (lambda () (ConceptNode \"stuff\")))");
	CHKERR;

	TSM_ASSERT("Failed to create atom", Handle::UNDEFINED != h2);
	TSM_ASSERT_EQUALS("Wrong atom type", CONCEPT_NODE, h2->get_type());

	printf ("h1 = %llu %p\n", (long long unsigned int) h1.value(), h1.operator->());
	printf ("h2 = %llu %p\n", (long long unsigned int) h2.value(), h2.operator->());

	// h1 and h2 had better be different.
	TSM_ASSERT("Wrong handles", h1 != h2);

	logger().debug("END TEST: %s", __FUNCTION__);
}

#define CHKEV(ev) \
	TSM_ASSERT("Caught scm error during eval", \
		(false == ev->eval_error()));

void MultiAtomSpace::test_three(void)
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	AtomSpacePtr as1 = createAtomSpace();
	AtomSpacePtr as2 = createAtomSpace();
	AtomSpacePtr as3 = createAtomSpace();
	SchemeEval* ev1 = SchemeEval::get_scheme_evaluator(as1);
	SchemeEval* ev2 = SchemeEval::get_scheme_evaluator(as2);
	SchemeEval* ev3 = SchemeEval::get_scheme_evaluator(as3);

	Handle h1 = ev1->eval_h("(cog-new-node 'ConceptNode \"stuff\")");
	CHKEV(ev1);
	TSM_ASSERT("Failed to create atom", Handle::UNDEFINED != h1);
	Handle h2 = ev2->eval_h("(cog-new-node 'ConceptNode \"stuff\")");
	CHKEV(ev2);
	TSM_ASSERT("Failed to create atom", Handle::UNDEFINED != h2);
	Handle h3 = ev3->eval_h("(cog-new-node 'ConceptNode \"stuff\")");
	CHKEV(ev3);
	TSM_ASSERT("Failed to create atom", Handle::UNDEFINED != h3);

	// We expect the handles to be all different, because they are
	// each in a different atomspace.
	TSM_ASSERT("Bad handles", h1 != h2);
	TSM_ASSERT("Bad handles", h1 != h3);
	TSM_ASSERT("Bad handles", h2 != h3);

	// The cogserver atomspace is a fourth place ...
	Handle h4 = evaluator->eval_h("(ConceptNode \"stuff\")");
	CHKERR;
	TSM_ASSERT("Failed to create atom", Handle::UNDEFINED != h4);
	TSM_ASSERT("Bad handles", h1 != h4);
	TSM_ASSERT("Bad handles", h2 != h4);
	TSM_ASSERT("Bad handles", h3 != h4);

	Handle h5 = ev2->eval_h("(cog-new-node 'ConceptNode \"solitary\")");
	CHKEV(ev2);
	TSM_ASSERT("Failed to create atom", Handle::UNDEFINED != h5);

	// We expect to not be able to find solitary anywhere but in as2
	Handle h6 = ev1->eval_h("(cog-node 'ConceptNode \"solitary\")");
	CHKEV(ev1);
	TSM_ASSERT("Did not expect to find atom", Handle::UNDEFINED == h6);

	Handle h7 = ev3->eval_h("(cog-node 'ConceptNode \"solitary\")");
	CHKEV(ev3);
	TSM_ASSERT("Did not expect to find atom", Handle::UNDEFINED == h7);

	Handle h8 = evaluator->eval_h("(cog-node 'ConceptNode \"solitary\")");
	CHKERR;
	TSM_ASSERT("Did not expect to find atom", Handle::UNDEFINED == h8);

	Handle h9 = ev2->eval_h("(cog-node 'ConceptNode \"solitary\")");
	CHKEV(ev2);
	TSM_ASSERT("Expect to find atom", h5 == h9);

	logger().debug("END TEST: %s", __FUNCTION__);
}

// Test the operation of nested atomspaces
void MultiAtomSpace::test_nest(void)
{
	main_as->clear();
	AtomSpacePtr locas = createAtomSpace(main_as);
	SchemeEval* lev = SchemeEval::get_scheme_evaluator(locas);

	// Push some atoms into the main space
	Handle ha = evaluator->eval_h("(ConceptNode \"aaa\")");
	Handle hb = evaluator->eval_h("(ConceptNode \"bbb\")");

	evaluator->eval("(define a (ConceptNode \"aaa\"))");
	evaluator->eval("(define b (ConceptNode \"bbb\"))");

	// Put a link into the local space
	Handle hl = lev->eval_h("(ListLink a b)");

	// The atomspace sizes should be as expected ...
	size_t main_sz = main_as->get_num_atoms_of_type(ATOM, true);
	size_t main_no = main_as->get_num_atoms_of_type(NODE, true);
	size_t main_li = main_as->get_num_atoms_of_type(LINK, true);
	TSM_ASSERT("Expect two atoms in main atomspace", 2 == main_sz);
	TSM_ASSERT("Expect two nodes in main atomspace", 2 == main_no);
	TSM_ASSERT("Expect zero links in main atomspace", 0 == main_li);

	size_t locas_sz = locas->get_num_atoms_of_type(ATOM, true) - main_sz;
	size_t locas_no = locas->get_num_atoms_of_type(NODE, true) - main_no;
	size_t locas_li = locas->get_num_atoms_of_type(LINK, true) - main_li;
	TSM_ASSERT("Expect one atoms in local atomspace", 1 == locas_sz);
	TSM_ASSERT("Expect zero nodes in local atomspace", 0 == locas_no);
	TSM_ASSERT("Expect one link in local atomspace", 1 == locas_li);

	// The outgoing set should not be copied, but should be the original.
	TSM_ASSERT("Expect atom a in position 0", ha == hl->getOutgoingAtom(0));
	TSM_ASSERT("Expect atom b in position 1", hb == hl->getOutgoingAtom(1));


	// Above deletion should not affect the main atomspace
	TSM_ASSERT("Still two atoms in main atomspace",
		2 == main_as->get_num_atoms_of_type(ATOM, true));
	TSM_ASSERT("Still two nodes in main atomspace",
		2 == main_as->get_num_atoms_of_type(NODE, true));
	TSM_ASSERT("Still zero links in main atomspace",
		0 == main_as->get_num_atoms_of_type(LINK, true));

	// The pointer hl should still be alive, even though the atomspace
	// is gone.
	TSM_ASSERT("Still atom a in position 0", ha == hl->getOutgoingAtom(0));
	TSM_ASSERT("Still atom b in position 1", hb == hl->getOutgoingAtom(1));
}

void MultiAtomSpace::test_nest_scm(void)
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	// Define an alternate atomspace
	evaluator->eval("(define nest-as (AtomSpace (cog-atomspace)))");
	std::string msg = evaluator->eval("nest-as");
	printf("Nested atomspace is: %s\n", msg.c_str());

	// Define a function that performs a thunk in the nested atomspace.
	evaluator->eval("(define (nest-env f) "
   	"(let ((curr-as (cog-set-atomspace! nest-as)) "
		"      (result (f))) "
   	"   (cog-set-atomspace! curr-as) result))");

	msg = evaluator->eval("(cog-atomspace)");
	printf("Primary atomspace is: %s\n", msg.c_str());

	msg = evaluator->eval("(nest-env cog-atomspace)");
	printf("Check nested atomspace is: %s\n", msg.c_str());

	// Populate atomspace with some misc junk
	Handle ha = evaluator->eval_h("(ConceptNode \"aaa-stuff\")");
	CHKERR;
	Handle hb = evaluator->eval_h("(ConceptNode \"bbb-stuff\")");
	CHKERR;

	TSM_ASSERT("Failed to create atom", Handle::UNDEFINED != ha);
	TSM_ASSERT("Failed to create atom", Handle::UNDEFINED != hb);
	TSM_ASSERT_EQUALS("Wrong atom type", CONCEPT_NODE, ha->get_type());
	TSM_ASSERT_EQUALS("Wrong atom type", CONCEPT_NODE, hb->get_type());

	// Get these same atoms in the nested space
	Handle hna = evaluator->eval_h(
		"(nest-env (lambda () (ConceptNode \"aaa-stuff\")))");
	CHKERR;
	Handle hnb = evaluator->eval_h(
		"(nest-env (lambda () (ConceptNode \"bbb-stuff\")))");
	CHKERR;
	TSM_ASSERT_EQUALS("Expect the same atom", ha, hna);
	TSM_ASSERT_EQUALS("Expect the same atom", hb, hnb);

	// Create a link in the nested atomspace
	Handle hl = evaluator->eval_h(
		"(nest-env (lambda () (ListLink "
		"	(ConceptNode \"aaa-stuff\")"
		"	(ConceptNode \"bbb-stuff\")"
		")))");
	CHKERR;

	// Get these same atoms, again in the nested space
	Handle hpa = evaluator->eval_h(
		"(nest-env (lambda () (ConceptNode \"aaa-stuff\")))");
	CHKERR;
	Handle hpb = evaluator->eval_h(
		"(nest-env (lambda () (ConceptNode \"bbb-stuff\")))");
	CHKERR;
	TSM_ASSERT_EQUALS("Expect the same atom", ha, hpa);
	TSM_ASSERT_EQUALS("Expect the same atom", hb, hpb);

	// The Link should NOT be in the primary atomspace.
	Handle hnol = evaluator->eval_h(
		"(cog-link 'ListLink "
		"	(ConceptNode \"aaa-stuff\")"
		"	(ConceptNode \"bbb-stuff\")"
		")");
	CHKERR;
	TSM_ASSERT_EQUALS("Expect undefined handle", hnol, Handle::UNDEFINED);

	// The Link should be in the nested atomspace.
	Handle hnestl = evaluator->eval_h(
		"(nest-env (lambda () "
		"(cog-link 'ListLink "
		"	(ConceptNode \"aaa-stuff\")"
		"	(ConceptNode \"bbb-stuff\")"
		")))");
	CHKERR;
	TSM_ASSERT_EQUALS("Expect list handle", hnestl, hl);

	// The Link should STILL NOT be in the primary atomspace.
	Handle hsil = evaluator->eval_h(
		"(cog-link 'ListLink "
		"	(ConceptNode \"aaa-stuff\")"
		"	(ConceptNode \"bbb-stuff\")"
		")");
	CHKERR;
	TSM_ASSERT_EQUALS("Expect undefined handle", hsil, Handle::UNDEFINED);

	evaluator->eval("(set! nest-as #f) (gc)(sleep 1)(gc)");
	logger().debug("END TEST: %s", __FUNCTION__);
}

/**
 * Create a new atomspace from scheme and work (use the pattern
 * matcher) inside this atomspace.
 */
void MultiAtomSpace::test_pm_scm(void)
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	evaluator->eval("(use-modules (opencog))");

	std::string rs = evaluator->eval("(load-from-path \"tests/scm/multi-atom-space-gc-bug.scm\")");
	rs.erase(rs.find_last_not_of(" \n\r\t") + 1);
	std::cout << "rs = >>" << rs << "<<" << std::endl;

	TSM_ASSERT("Crashed while working on the new atomspace", rs.empty());

	logger().debug("END TEST: %s", __FUNCTION__);
}

/**
 * An atom is created in an auxiliary atomspace, then that atom is
 * used to access the auxiliary atomspace in a peculiar fashion
 * which can lead to the auxiliary atomspace to be garbage collected.
 * See bug opencog/atomspace#2382 for details.
 */
void MultiAtomSpace::test_as_of_atom_scm(void)
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	std::string rs = evaluator->eval("(load-from-path \"tests/scm/multi-atom-space-as-of-atom.scm\")");
	logger().debug() << "rs = " << rs;
	Handle A = evaluator->eval_h("A");
	AtomSpacePtr A_as = evaluator->eval_as("A-as");

	TS_ASSERT_DIFFERS(A, Handle::UNDEFINED);
	TS_ASSERT_DIFFERS(A_as, nullptr);

	logger().debug("END TEST: %s", __FUNCTION__);
}

/**
 * Make sure modifying an atom in a readonly atomspace is forbidden.
 */
void MultiAtomSpace::test_readonly_scm(void)
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	evaluator->eval("(define A (Concept \"A\"))");
	evaluator->eval("(define tvkey (Predicate \"*-TruthValueKey-*\"))");
	evaluator->eval("(cog-value A tvkey)");
	evaluator->eval("(cog-atomspace-ro!)");
	evaluator->eval("(define tv (FloatValue 0.1 0.1))");

	TS_ASSERT_THROWS_ANYTHING(evaluator->eval_h("(cog-set-value! A tvkey tv)"));

	// Verify that the TV never changed.
	ValuePtr tvp = evaluator->eval_v("(cog-value (Concept \"A\") tvkey)");
	TS_ASSERT(tvp == nullptr);

	logger().debug("END TEST: %s", __FUNCTION__);	
}