/*
* tests/persist/cog-simple/SimpleBasicDeleteUTest.cxxtest
*
* Verify that deletion of keys works, even if some Atoms are still
* using those keys.
*
* Copyright (C) 2008, 2009, 2019, 2021 Linas Vepstas <linasvepstas@gmail.com>
*
* LICENSE:
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* 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 <cstdio>
#include <opencog/atoms/base/Atom.h>
#include <opencog/atoms/base/Link.h>
#include <opencog/atoms/base/Node.h>
#include <opencog/atoms/atom_types/atom_types.h>
#include <opencog/persist/cog-types/atom_types.h>
#include <opencog/atoms/value/FloatValue.h>
#include <opencog/atoms/value/LinkValue.h>
#include <opencog/atoms/value/VoidValue.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/persist/api/StorageNode.h>
#include <opencog/util/Logger.h>
#include "../TestCogServer.h"
using namespace opencog;
class SimpleBasicDeleteUTest : public CxxTest::TestSuite
{
private:
std::string uri;
DECLARE_TEST_COGSERVER
public:
SimpleBasicDeleteUTest(void)
{
logger().set_level(Logger::INFO);
// logger().set_level(Logger::DEBUG);
logger().set_print_to_stdout_flag(true);
uri = "cog://localhost:16310";
INIT_TEST_COGSERVER(16310);
printf("Started CogServer\n");
}
~SimpleBasicDeleteUTest()
{
STOP_TEST_COGSERVER
// 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_duplicates(void);
void test_recursive(void);
void test_key_deletion(void);
void test_key_corruption(void);
};
/*
* This is called once before each test, for each test (!!)
*/
void SimpleBasicDeleteUTest::setUp(void)
{
}
void SimpleBasicDeleteUTest::tearDown(void)
{
}
// 9 more from CogServerNode and gorp; 2 for *-TruthValueKey-* and
// *-IsKeyFlag-*; 1 more for *-open-*
int basic_offset = 9 + 3;
// ============================================================
void SimpleBasicDeleteUTest::test_duplicates(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
AtomSpacePtr as = createAtomSpace();
Handle hsn = as->add_node(COG_SIMPLE_STORAGE_NODE, std::string(uri));
StorageNodePtr store = StorageNodeCast(hsn);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
TS_ASSERT(store->connected())
// Clear out left-over junk, just in case.
::kill_data(store.get(), _test_asp.get());
Handle ha(as->add_node(CONCEPT_NODE, "a"));
Handle hb(as->add_node(CONCEPT_NODE, "b"));
Handle li(as->add_link(LIST_LINK, ha, hb, ha, hb));
// Store the link.
store->store_atom(li);
store->barrier();
// Verify the atomspace size (+1 for *-open-* predicate)
TS_ASSERT_EQUALS(as->get_size(), 5);
// Remove the link. Since the link has duplicate atoms
// in it, this can cause trouble, depending on how the
// incoming set is managed.
store->remove_atom(as, li);
TS_ASSERT_EQUALS(as->get_size(), 5);
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
as->extract_atom(li);
TS_ASSERT_EQUALS(as->get_size(), 5);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
store->load_atomspace();
// +1 for *-close-* predicate from above
TS_ASSERT_EQUALS(as->get_size(), 4 + basic_offset);
::kill_data(store.get(), _test_asp.get());
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
logger().debug("END TEST: %s", __FUNCTION__);
}
// ============================================================
void SimpleBasicDeleteUTest::test_recursive(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
AtomSpacePtr as = createAtomSpace();
Handle hsn = as->add_node(COG_SIMPLE_STORAGE_NODE, std::string(uri));
StorageNodePtr store = StorageNodeCast(hsn);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
TS_ASSERT(store->connected())
// Clear out left-over junk, just in case.
::kill_data(store.get(), _test_asp.get());
Handle ha(as->add_node(CONCEPT_NODE, "a"));
Handle hb(as->add_node(CONCEPT_NODE, "b"));
Handle li(as->add_link(LIST_LINK, ha, hb));
Handle lj(as->add_link(LIST_LINK, ha, li));
// Store the link.
store->store_atom(lj);
store->barrier();
printf("Atomspace size=%lu\n", as->get_size());
HandleSeq hset;
as->get_root_set_by_type(hset, ATOM, true);
for (const Handle& h : hset)
printf("Atomspace contains: %s\n", h->to_short_string().c_str());
// Verify the atomspace size (+1 for *-open-* predicate)
TS_ASSERT_EQUALS(as->get_size(), 6);
// Remove (Concept "a"). Since this appears twice, recursion
// will hit it twice, in stange ways, and maybe crash.
store->remove_atom(as, ha, true);
printf("Post-delete atomspace size=%lu\n", as->get_size());
hset.clear();
as->get_root_set_by_type(hset, ATOM, true);
for (const Handle& h : hset)
printf("Post delete: %s\n", h->to_short_string().c_str());
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
as->extract_atom(ha, true);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
store->load_atomspace();
// +1 for *-close-* predicate from above
TS_ASSERT_EQUALS(as->get_size(), 3 + basic_offset);
// --------------------------------------
// Do it again, but with a different order.
ha = as->add_node(CONCEPT_NODE, "a");
li = as->add_link(LIST_LINK, ha, hb);
lj = as->add_link(LIST_LINK, li, ha);
store->store_atom(lj);
store->barrier();
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
store->load_atomspace();
// +1 for *-close-* predicate from above
TS_ASSERT_EQUALS(as->get_size(), 6 + basic_offset);
store->remove_atom(as, ha, true);
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
as->extract_atom(ha, true);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
store->load_atomspace();
// +1 for *-close-* predicate from above
TS_ASSERT_EQUALS(as->get_size(), 3 + basic_offset);
// --------------------------------------
// Do it again, but with a complex order.
ha = as->add_node(CONCEPT_NODE, "a");
li = as->add_link(LIST_LINK, ha, hb);
lj = as->add_link(LIST_LINK, ha, li, ha);
store->store_atom(lj);
store->barrier();
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
store->load_atomspace();
// +1 for *-close-* predicate from above
TS_ASSERT_EQUALS(as->get_size(), 6 + basic_offset);
store->remove_atom(as, ha, true);
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
as->extract_atom(ha, true);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
store->load_atomspace();
// +1 for *-close-* predicate from above
TS_ASSERT_EQUALS(as->get_size(), 3 + basic_offset);
// --------------------------------------
// Do it again, but more deeply.
ha = as->add_node(CONCEPT_NODE, "a");
li = as->add_link(LIST_LINK, ha, hb);
lj = as->add_link(LIST_LINK, ha, li, ha);
Handle lk = as->add_link(LIST_LINK, ha, lj, ha);
store->store_atom(lk);
store->barrier();
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
store->load_atomspace();
// +1 for *-close-* predicate from above
TS_ASSERT_EQUALS(as->get_size(), 7 + basic_offset);
store->remove_atom(as, ha, true);
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
as->extract_atom(ha, true);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
store->load_atomspace();
// +1 for *-close-* predicate from above
TS_ASSERT_EQUALS(as->get_size(), 3 + basic_offset);
// --------------------------------------
::kill_data(store.get(), _test_asp.get());
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
logger().debug("END TEST: %s", __FUNCTION__);
}
// ============================================================
void SimpleBasicDeleteUTest::test_key_deletion(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
// Create an Atom with some keys on it.
AtomSpacePtr as = createAtomSpace();
Handle hsn = as->add_node(COG_SIMPLE_STORAGE_NODE, std::string(uri));
StorageNodePtr store = StorageNodeCast(hsn);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
TS_ASSERT(store->connected())
// Clear out left-over junk, just in case.
::kill_data(store.get(), _test_asp.get());
Handle ha(as->add_node(CONCEPT_NODE, "a"));
Handle ka(as->add_node(PREDICATE_NODE, "key-a"));
Handle kb(as->add_node(PREDICATE_NODE, "key-b"));
Handle kc(as->add_node(PREDICATE_NODE, "key-c"));
ha = as->set_value(ha, ka, createFloatValue(std::vector<double>({1, 2, 3})));
ha = as->set_value(ha, kb, createFloatValue(std::vector<double>({4, 5, 6})));
ha = as->set_value(ha, kc, createFloatValue(std::vector<double>({7, 8, 9})));
// Store all three keys.
store->store_atom(ha);
store->barrier();
// Verify the atomspace size (+1 for *-IsKeyFlag-*, +1 for *-open-*)
TS_ASSERT_EQUALS(as->get_size(), 7);
// Verify that there are three keys
HandleSet hs = ha->getKeys();
for (const Handle& k : hs)
printf("key=%s\n", k->get_name().c_str());
TS_ASSERT_EQUALS(hs.size(), 3);
// Attempt to remove one of the keys. But this is not possible,
// both storage, and the AtomSpace itself block this,
store->remove_atom(as, kb);
as->extract_atom(kb);
// +1 for *-IsKeyFlag-*, +1 for *-open-*
TS_ASSERT_EQUALS(as->get_size(), 7);
// Verify the key was NOT deleted by loading only PredicateNodes.
as = createAtomSpace();
hsn = as->add_node(COG_SIMPLE_STORAGE_NODE, std::string(uri));
store = StorageNodeCast(hsn);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
store->fetch_all_atoms_of_type(PREDICATE_NODE);
// All keys should have been loaded.
TS_ASSERT(nullptr != as->get_node(PREDICATE_NODE, "key-b"));
TS_ASSERT(nullptr != as->get_node(PREDICATE_NODE, "key-a"));
TS_ASSERT(nullptr != as->get_node(PREDICATE_NODE, "key-c"));
// FYI, we should call store->close() at this point, but we
// don't, because ... most users forget to do this too.
// So this is a softball check that everything worked, even
// without the close().
// -------------------------------------------------------
// Start again, with a fresh connection.
// Verify that the delete key is really deleted.
as = createAtomSpace();
hsn = as->add_node(COG_SIMPLE_STORAGE_NODE, std::string(uri));
store = StorageNodeCast(hsn);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
TS_ASSERT(store->connected())
// We expect 5 atoms
printf("Loading the atomspace ...\n");
store->load_atomspace();
TS_ASSERT_EQUALS(as->get_size(), 5 + basic_offset);
// Verify that there are only two keys .. haha no, three.
// That is because the deleted key gets restored when the
// values on (Concept "a") are fetched.
// Try this:
// (cog-set-values! (ConceptNode "a")(list (cons (PredicateNode "key-b")(FloatValue 4 5 6))(cons (PredicateNode "key-c")(FloatValue 7 8 9))(cons (PredicateNode "key-a")(FloatValue 1 2 3))))
// (cog-extract! (PredicateNode "key-b"))
// (cog-get-atoms 'Node #t)
// ; Excellent! key-b is gone!
// (cog-keys->alist (ConceptNode "a"))
// ; Oh hey, key-b is back!
//
ha = as->add_node(CONCEPT_NODE, "a");
hs = ha->getKeys();
for (const Handle& k : hs)
printf("key=%s\n", k->get_name().c_str());
TS_ASSERT_EQUALS(hs.size(), 3);
::kill_data(store.get(), _test_asp.get());
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
logger().debug("END TEST: %s", __FUNCTION__);
}
// ============================================================
// A few more that cannot be deleted.
int full_offset = basic_offset + 3;
// This tests for a nasty off-by-one bug that showed up
// in RocksDB. Make sure it never happens again.
void SimpleBasicDeleteUTest::test_key_corruption(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
// Create an Atom with some keys on it.
AtomSpacePtr as = createAtomSpace();
Handle hsn = as->add_node(COG_SIMPLE_STORAGE_NODE, std::string(uri));
StorageNodePtr store = StorageNodeCast(hsn);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
TS_ASSERT(store->connected())
// Clear out left-over junk, just in case.
::kill_data(store.get(), _test_asp.get());
Handle hb(as->add_node(CONCEPT_NODE, "b"));
Handle hx(as->add_node(CONCEPT_NODE, "x"));
Handle kx(as->add_node(PREDICATE_NODE, "key-x"));
hb = as->set_value(hb, kx, hx);
// Store everything.
store->store_atom(hb);
store->barrier();
TS_ASSERT_EQUALS(as->get_size(), 6);
// Remove the value
store->remove_atom(as, hx);
as->extract_atom(hx);
TS_ASSERT_EQUALS(as->get_size(), 5);
// FYI, we should call store->close() at this point, but we
// don't, because ... most users forget to do this too.
// So this is a softball check that everything worked, even
// without the close().
// -------------------------------------------------------
// Start again, with a fresh connection.
// Verify that the delete key is really deleted.
as = createAtomSpace();
hsn = as->add_node(COG_SIMPLE_STORAGE_NODE, std::string(uri));
store = StorageNodeCast(hsn);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
TS_ASSERT(store->connected())
// We expect 5 atoms: b, x, key-x, *-IsKeyFlag-*, StorageNode
// That's because the (Concept "x") sitting in the Value
// was never actually deleted; thus it is restored.
printf("Loading the atomspace ...\n");
store->load_atomspace();
HandleSeq hset;
as->get_root_set_by_type(hset, ATOM, true);
for (const Handle& h: hset)
printf("Atomspace contains h=%s\n", h->to_short_string().c_str());
// Size is 4 because (Concept "x") cannot be deleted.
TS_ASSERT_EQUALS(as->get_size(), 4 + full_offset);
hb = as->add_node(CONCEPT_NODE, "b");
hx = as->add_node(CONCEPT_NODE, "x");
Handle hy(as->add_node(CONCEPT_NODE, "y"));
Handle kl(as->add_node(PREDICATE_NODE, "key-li"));
hb = as->set_value(hb, kl, createLinkValue(ValueSeq({hx, hy})));
// Store the new value
store->store_atom(hb);
store->barrier();
TS_ASSERT_EQUALS(as->get_size(), 6 + full_offset);
// Remove the value .. again
store->remove_atom(as, hx);
as->extract_atom(hx);
TS_ASSERT_EQUALS(as->get_size(), 5 + full_offset);
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
store->load_atomspace();
// Size is 6 because (Concept "x") cannot be deleted. +1 for *-close-*
TS_ASSERT_EQUALS(as->get_size(), 7 + full_offset);
// We are done.
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
// -------------------------------------------------------
// Start again, with a fresh connection.
// Verify that the delete key is really deleted.
as = createAtomSpace();
hsn = as->add_node(COG_SIMPLE_STORAGE_NODE, std::string(uri));
store = StorageNodeCast(hsn);
store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
TS_ASSERT(store->connected())
// We expect 6 atoms
printf("Loading the atomspace ...\n");
store->load_atomspace();
TS_ASSERT_EQUALS(as->get_size(), 6 + full_offset);
// Verify that there are two keys
hb = as->add_node(CONCEPT_NODE, "b");
HandleSet hs = hb->getKeys();
for (const Handle& k : hs)
{
ValuePtr vp = hb->getValue(k);
printf("key=%s value=%s\n", k->to_short_string().c_str(),
vp->to_short_string().c_str());
}
TS_ASSERT_EQUALS(hs.size(), 2);
::kill_data(store.get(), _test_asp.get());
store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
logger().debug("END TEST: %s", __FUNCTION__);
}
/* ============================= END OF FILE ================= */