/*
* 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__);
}