/*
* tests/persist/rocks/DeleteUTest.cxxtest
*
* Test deletion from backing store. Assumes PersistUTest is passing.
* Ported from tests/persist/sql/multi-driver/DeleteUTest.cxxtest
*
* Copyright (C) 2008, 2009, 2013, 2017, 2019 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 <filesystem>
#include <opencog/atoms/atom_types/atom_types.h>
#include <opencog/atoms/base/Atom.h>
#include <opencog/atoms/base/Link.h>
#include <opencog/atoms/base/Node.h>
#include <opencog/atoms/value/FloatValue.h>
#include <opencog/atoms/value/VoidValue.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/persist/rocks-types/atom_types.h>
#include <opencog/persist/rocks/RocksStorage.h>
#include <opencog/util/Logger.h>
using namespace opencog;
class DeleteUTest : public CxxTest::TestSuite
{
private:
AtomSpacePtr _as;
std::string uri;
NodePtr n1[10];
NodePtr n2[10];
NodePtr n3[10];
NodePtr n4[10];
LinkPtr l[10];
LinkPtr l2[10];
LinkPtr l3[10];
Handle h1[10];
Handle h2[10];
Handle h3[10];
Handle h4[10];
Handle hl[10];
Handle hl2[10];
Handle hl3[10];
public:
DeleteUTest(void);
~DeleteUTest()
{
// erase the log file if no assertions failed
if (!CxxTest::TestTracker::tracker().suiteFailed())
{
std::remove(logger().get_filename().c_str());
// Also remove the database directory
// URI is "rocks:///tmp/..." so path starts at position 8
std::string dbpath = uri.substr(8);
std::filesystem::remove_all(dbpath);
}
}
void setUp(void);
void tearDown(void);
void add_to_space(int, AtomSpace *, std::string id);
void fetch_space(int, StorageNodePtr);
void check_space(int, AtomSpace *, std::string dbgmsg);
void check_remove(int, AtomSpace *, std::string dbgmsg);
void check_empty(int, AtomSpace *);
void extract_links(int, AtomSpace *);
void delete_links(int, StorageNodePtr);
void extract_nodes(int, AtomSpace *);
void delete_nodes(int, StorageNodePtr);
void atomCompare(Handle, Handle, std::string);
void tvMiscompare(Handle, Handle, std::string);
void test_remove(void);
void test_recurse(void);
};
DeleteUTest:: DeleteUTest(void)
{
// logger().set_level(Logger::DEBUG);
logger().set_level(Logger::INFO);
logger().set_print_to_stdout_flag(true);
// logger().set_sync_flag(true);
uri = "rocks:///tmp/cog-rocks-delete-utest";
}
/*
* This is called once before each test, for each test (!!)
*/
void DeleteUTest::setUp(void)
{
_as = createAtomSpace();
}
void DeleteUTest::tearDown(void)
{
_as = nullptr;
}
// ============================================================
void DeleteUTest::atomCompare(Handle a, Handle b, std::string where)
{
logger().debug("Check %s expect %s", where.c_str(), a->to_string().c_str());
TSM_ASSERT("No atom found", b != nullptr);
if (nullptr == b) return;
TSM_ASSERT_EQUALS("Type mismatch", a->get_type(), b->get_type());
NodePtr na(NodeCast(a));
NodePtr nb(NodeCast(b));
if (na and nb)
{
TSM_ASSERT_EQUALS("Name mismatch", na->get_name(), nb->get_name());
}
LinkPtr la(LinkCast(a));
LinkPtr lb(LinkCast(b));
if (la and lb)
{
TSM_ASSERT_EQUALS("Arity mismatch", la->get_arity(), lb->get_arity());
if (*la != *lb)
{
printf("Mismatching la = %s\n", la->to_string().c_str());
printf("Mismatching lb = %s\n", lb->to_string().c_str());
TSM_ASSERT_EQUALS("Link mis-match", la, lb);
}
}
ValuePtr ta = a->getValue(truth_key());
ValuePtr tb = b->getValue(truth_key());
if (ta or tb)
{
TSM_ASSERT("Missing truth value", ta);
TSM_ASSERT("Missing truth value", tb);
if (ta and tb)
{
if (not ((*ta) == (*tb)))
{
FloatValuePtr fva = FloatValueCast(ta);
FloatValuePtr fvb = FloatValueCast(tb);
printf("Error, truth value miscompare on %s\n"
"\tma=%f mb=%f ca=%f cb=%f\n",
where.c_str(),
fva->value()[0], fvb->value()[0],
fva->value()[1], fvb->value()[1]);
}
TSM_ASSERT("Truth value miscompare", (*ta)==(*tb));
}
}
}
// Passes only if the TV's are different!
void DeleteUTest::tvMiscompare(Handle a, Handle b, std::string where)
{
if (nullptr == a or nullptr == b) return;
ValuePtr ta = a->getValue(truth_key());
ValuePtr tb = b->getValue(truth_key());
if (ta and tb)
{
TSM_ASSERT("Truth value should not match! " + where, (*ta)!=(*tb));
}
if (nullptr==ta and nullptr==tb)
{
TSM_ASSERT("Truth value null!! " + where, false);
}
}
// ============================================================
void DeleteUTest::add_to_space(int idx, AtomSpace *as, std::string id)
{
// Create an atom ...
ValuePtr stv(createFloatValue(std::vector<double>({0.11, 100.0+idx})));
h1[idx] = as->add_node(SCHEMA_NODE, id + "fromNode");
h1[idx]->setValue(truth_key(), stv);
n1[idx] = NodeCast(h1[idx]);
ValuePtr stv2(createFloatValue(std::vector<double>({0.22, 200.0+idx})));
h2[idx] = as->add_node(SCHEMA_NODE, id + "toNode");
h2[idx]->setValue(truth_key(), stv2);
n2[idx] = NodeCast(h2[idx]);
ValuePtr stv3(createFloatValue(std::vector<double>({0.33, 300.0+idx})));
h3[idx] = as->add_node(SCHEMA_NODE, id + "third wheel");
h3[idx]->setValue(truth_key(), stv3);
n3[idx] = NodeCast(h3[idx]);
// The NumberNode will go through the AtomTable clone factory
// and should thus elicit any errors in clone uuid handling.
char buf[40]; sprintf(buf, "%f", idx+0.14159265358979);
h4[idx] = as->add_node(NUMBER_NODE, buf);
ValuePtr stv4(createFloatValue(std::vector<double>({0.44, 400.0+idx})));
h4[idx]->setValue(truth_key(), stv4);
n4[idx] = NodeCast(h4[idx]);
HandleSeq hvec;
hvec.push_back(h1[idx]);
hvec.push_back(h2[idx]);
hvec.push_back(h3[idx]);
hvec.push_back(h4[idx]);
// Note that SetLink is an unordered link.
ValuePtr stv5(createFloatValue(std::vector<double>({0.55, 500.0+idx})));
hl[idx] = as->add_link(SET_LINK, std::move(hvec));
hl[idx]->setValue(truth_key(), stv5);
l[idx] = LinkCast(hl[idx]);
ValuePtr stv6(createFloatValue(std::vector<double>({0.66, 600.0+idx})));
hl2[idx] = as->add_link(LIST_LINK, hl[idx], h2[idx]);
hl2[idx]->setValue(truth_key(), stv6);
l2[idx] = LinkCast(hl2[idx]);
ValuePtr stv7(createFloatValue(std::vector<double>({0.77, 700.0+idx})));
hl3[idx] = as->add_link(EVALUATION_LINK, h1[idx], hl2[idx], h3[idx]);
hl3[idx]->setValue(truth_key(), stv7);
l3[idx] = LinkCast(hl3[idx]);
}
void DeleteUTest::fetch_space(int idx, StorageNodePtr space)
{
Handle ab1 = createNode(n1[idx]->get_type(), n1[idx]->get_name());
Handle hb1(ab1->get_handle());
space->fetch_atom(hb1);
Handle ab2 = createNode(n2[idx]->get_type(), n2[idx]->get_name());
Handle hb2(ab2->get_handle());
space->fetch_atom(hb2);
Handle ab3 = createNode(n3[idx]->get_type(), n3[idx]->get_name());
Handle hb3(ab3->get_handle());
space->fetch_atom(hb3);
Handle ab4 = createNode(n4[idx]->get_type(), n4[idx]->get_name());
Handle hb4(ab4->get_handle());
space->fetch_atom(hb4);
HandleSeq hvec;
hvec.push_back(hb1);
hvec.push_back(hb2);
hvec.push_back(hb3);
hvec.push_back(hb4);
Handle alb = createLink(hvec, hl[idx]->get_type());
Handle hlb(alb->get_handle());
Handle flb = space->fetch_atom(hlb);
HandleSeq hv2({hlb, hb2});
Handle alb2 = createLink(hv2, hl2[idx]->get_type());
Handle hlb2(alb2->get_handle());
space->fetch_atom(hlb2);
HandleSeq hv3({hb1, hlb2, hb3});
Handle alb3 = createLink(hv3, hl3[idx]->get_type());
Handle hlb3(alb3->get_handle());
space->fetch_atom(hlb3);
space->barrier();
}
void DeleteUTest::check_space(int idx, AtomSpace *space, std::string dbgmsg)
{
dbgmsg += "-idx=" + std::to_string(idx);
std::string nam1 = n1[idx]->get_name();
Handle hb1 = space->get_node(n1[idx]->get_type(), std::move(nam1));
atomCompare(h1[idx], hb1, dbgmsg + "-hb1");
std::string nam2 = n2[idx]->get_name();
Handle hb2 = space->get_node(n2[idx]->get_type(), std::move(nam2));
atomCompare(h2[idx], hb2, dbgmsg + "-hb2");
std::string nam3 = n3[idx]->get_name();
Handle hb3 = space->get_node(n3[idx]->get_type(), std::move(nam3));
atomCompare(h3[idx], hb3, dbgmsg + "-hb3");
std::string nam4 = n4[idx]->get_name();
Handle hb4 = space->get_node(n4[idx]->get_type(), std::move(nam4));
atomCompare(h4[idx], hb4, dbgmsg + "-hb4");
HandleSeq hvec;
hvec.push_back(hb1);
hvec.push_back(hb2);
hvec.push_back(hb3);
hvec.push_back(hb4);
Handle hlb = space->get_link(hl[idx]->get_type(), std::move(hvec));
atomCompare(hl[idx], hlb, dbgmsg + "-hlinkb");
HandleSeq hv2({hlb, hb2});
Handle hlb2 = space->get_link(hl2[idx]->get_type(), std::move(hv2));
atomCompare(hl2[idx], hlb2, dbgmsg + "-hlinkb2");
HandleSeq hv3({hb1, hlb2, hb3});
Handle hlb3 = space->get_link(hl3[idx]->get_type(), std::move(hv3));
atomCompare(hl3[idx], hlb3, dbgmsg + "-hlinkb3");
}
void DeleteUTest::check_remove(int idx, AtomSpace *space, std::string dbgmsg)
{
std::string nam1 = n1[idx]->get_name();
Handle hb1 = space->get_node(n1[idx]->get_type(), std::move(nam1));
std::string nam2 = n2[idx]->get_name();
Handle hb2 = space->get_node(n2[idx]->get_type(), std::move(nam2));
std::string nam3 = n3[idx]->get_name();
Handle hb3 = space->get_node(n3[idx]->get_type(), std::move(nam3));
std::string nam4 = n4[idx]->get_name();
Handle hb4 = space->get_node(n4[idx]->get_type(), std::move(nam4));
HandleSeq hvec;
hvec.push_back(hb1);
hvec.push_back(hb2);
hvec.push_back(hb3);
hvec.push_back(hb4);
Handle hlb = space->get_link(hl[idx]->get_type(), std::move(hvec));
tvMiscompare(hl[idx], hlb, dbgmsg + "-hlinkb");
HandleSeq hv2({hlb, hb2});
Handle hlb2 = space->get_link(hl2[idx]->get_type(), std::move(hv2));
tvMiscompare(hl2[idx], hlb2, dbgmsg + "-hlinkb2");
HandleSeq hv3({hb1, hlb2, hb3});
Handle hlb3 = space->get_link(hl3[idx]->get_type(), std::move(hv3));
tvMiscompare(hl3[idx], hlb3, dbgmsg + "-hlinkb3");
}
void DeleteUTest::check_empty(int idx, AtomSpace *space)
{
std::string nam1 = n1[idx]->get_name();
Handle hb1 = space->get_node(n1[idx]->get_type(), std::move(nam1));
TSM_ASSERT("Should not find this atom", hb1 == Handle::UNDEFINED);
std::string nam2 = n2[idx]->get_name();
Handle hb2 = space->get_node(n2[idx]->get_type(), std::move(nam2));
TSM_ASSERT("Should not find this atom", hb2 == Handle::UNDEFINED);
std::string nam3 = n3[idx]->get_name();
Handle hb3 = space->get_node(n3[idx]->get_type(), std::move(nam3));
TSM_ASSERT("Should not find this atom", hb3 == Handle::UNDEFINED);
std::string nam4 = n4[idx]->get_name();
Handle hb4 = space->get_node(n4[idx]->get_type(), std::move(nam4));
TSM_ASSERT("Should not find this atom", hb4 == Handle::UNDEFINED);
HandleSeq hvec;
hvec.push_back(h1[idx]);
hvec.push_back(h2[idx]);
hvec.push_back(h3[idx]);
hvec.push_back(h4[idx]);
Handle hlb = space->get_link(hl[idx]->get_type(), std::move(hvec));
TSM_ASSERT("Should not find this atom", hlb == Handle::UNDEFINED);
}
// This removes from atomspace, but not from storage.
void DeleteUTest::extract_links(int idx, AtomSpace *as)
{
as->extract_atom(hl3[idx]);
as->extract_atom(hl2[idx]);
as->extract_atom(hl[idx]);
}
// This should delete all the links from storage (and also
// the AtomSpace)
void DeleteUTest::delete_links(int idx, StorageNodePtr store)
{
store->remove_atom(_as, hl3[idx]);
store->barrier();
store->remove_atom(_as, hl2[idx]);
store->barrier();
store->remove_atom(_as, hl[idx]);
store->barrier();
_as->extract_atom(hl3[idx]);
_as->extract_atom(hl2[idx]);
_as->extract_atom(hl[idx]);
}
// This removes all the links, and one node, from atomspace,
// but not from storage.
void DeleteUTest::extract_nodes(int idx, AtomSpace *as)
{
as->extract_atom(h1[idx], true);
}
// This should delete all the links and one node from storage.
void DeleteUTest::delete_nodes(int idx, StorageNodePtr store)
{
store->remove_atom(_as, h3[idx], true);
store->barrier();
_as->extract_atom(h3[idx], true);
}
// ============================================================
void DeleteUTest::test_remove(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
Handle hsn = _as->add_node(ROCKS_STORAGE_NODE, std::string(uri));
StorageNodePtr store = StorageNodeCast(hsn);
store->setValue(_as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
TS_ASSERT(store->connected())
int idx = 0;
add_to_space(idx++, _as.get(), "AA-aa-wow ");
add_to_space(idx++, _as.get(), "BB-bb-wow ");
add_to_space(idx++, _as.get(), "CC-cc-wow ");
add_to_space(idx++, _as.get(), "DD-dd-wow ");
add_to_space(idx++, _as.get(), "EE-ee-wow ");
/* Make sure UTF-8 works fine. */
add_to_space(idx++, _as.get(), "Попытка выбраться вызвала слабый стон ");
add_to_space(idx++, _as.get(), "はにがうりだそうであってるのかはち ");
add_to_space(idx++, _as.get(), "係拉丁字母");
/* Verify that the atoms were added */
int i;
for (i=0; i<idx; i++)
check_space(i, _as.get(), "verify-add");
/* Push all atoms out to storage */
store->store_atomspace();
// +2 for *-open-* predicate and storage node
printf("Initial Atomspace size=%lu expect=%d\n", _as->get_size(), 7*idx+2);
TSM_ASSERT("Initial unexpected atomspace size",
((unsigned)(7*idx+2)) == _as->get_size());
/* Delete some atoms with non-empty incoming sets.
* This should result in their being removed from the atomspace,
* but NOT fom the backend!
*/
store->barrier();
for (i=0; i<idx; i++)
extract_links(i, _as.get());
// +2 for *-open-* predicate and storage node
printf("Atomspace size=%lu expect=%d\n", _as->get_size(), 4*idx+2);
TSM_ASSERT("Post-extract unexpected atomspace size", ((unsigned)(4*idx+2)) == _as->get_size());
/* Verify that the atoms can still be fetched from storage. */
for (i=0; i<idx; i++) {
fetch_space(i, store);
check_space(i, _as.get(), "verify-fetch");
}
/* This time, kill them in the backend too. */
for (i=0; i<idx; i++)
delete_links(i, store);
// +2 for *-TruthValueKey-* and *-IsKeyFlag-*, +1 for *-open-*
printf("Post-remove size=%lu expect=%d\n", _as->get_size(), 4*idx+4);
TSM_ASSERT("Post-remove unexpected atomspace size",
((unsigned)(4*idx+4)) == _as->get_size());
/* Verify that the atoms are really gone from storage. */
for (i=0; i<idx; i++) {
fetch_space(i, store);
check_remove(i, _as.get(), "verify-gone");
}
// +2 for *-TruthValueKey-* and *-IsKeyFlag-*, +1 for *-open-*
printf("Post-referesh size=%lu expect=%d\n", _as->get_size(), 7*idx+4);
TSM_ASSERT("Post-refetch unexpected atomspace size",
((unsigned)(7*idx+4)) == _as->get_size());
/* Kill data for good */
_as->clear();
TSM_ASSERT("Non-empty atomspace", 0 == _as->get_size());
/* Verify that the atoms are no longer being found. */
for (i=0; i<idx; i++)
check_empty(i, _as.get());
logger().debug("END TEST: %s", __FUNCTION__);
}
// ============================================================
void DeleteUTest::test_recurse(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
Handle hsn = _as->add_node(ROCKS_STORAGE_NODE, std::string(uri));
StorageNodePtr store = StorageNodeCast(hsn);
store->setValue(_as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
TS_ASSERT(store->connected())
int idx = 0;
add_to_space(idx++, _as.get(), "AA-aa-wow ");
add_to_space(idx++, _as.get(), "BB-bb-wow ");
add_to_space(idx++, _as.get(), "CC-cc-wow ");
add_to_space(idx++, _as.get(), "DD-dd-wow ");
add_to_space(idx++, _as.get(), "EE-ee-wow ");
/* Make sure UTF-8 works fine. */
add_to_space(idx++, _as.get(), "Попытка выбраться вызвала слабый стон ");
add_to_space(idx++, _as.get(), "はにがうりだそうであってるのかはち ");
add_to_space(idx++, _as.get(), "係拉丁字母");
/* Verify that the atoms were added */
int i;
for (i=0; i<idx; i++)
check_space(i, _as.get(), "verify-add");
/* Push all atoms out to the SQL DB */
store->store_atomspace();
/* Perform recursive extract of some nodes.
* This should result in their being removed from the atomspace,
* but NOT fom the backend!
*/
store->barrier();
int zap = 4;
for (i=0; i<zap; i++)
extract_nodes(i, _as.get());
// +1 for *-open-*
size_t expsz = 7*(idx-zap) + 3*zap + 2;
printf("Atomspace size=%lu expect=%lu\n",
_as->get_size(), expsz);
TSM_ASSERT("Unexpected atomspace size", expsz == _as->get_size());
/* Verify that the atoms can still be fetched from storage. */
for (i=0; i<idx; i++) {
fetch_space(i, store);
check_space(i, _as.get(), "verify-fetch");
}
/* This time, kill them in the backend too. */
for (i=0; i<zap; i++)
delete_nodes(i, store);
// +1 for *-IsKeyFlag-*, expsz already has +1 for *-open-*
printf("Post-remove size=%lu expect=%lu\n",
_as->get_size(), expsz+2);
TSM_ASSERT("Unexpected atomspace size", expsz+2 == _as->get_size());
store->barrier();
/* Verify that the atoms are really gone from storage. */
for (i=0; i<idx; i++) {
fetch_space(i, store);
}
// +2 for *-TruthValueKey-* and *-IsKeyFlag-*, +1 for *-open-*
printf("Post-refetch size=%lu expect=%d\n",
_as->get_size(), 7*idx+4);
TSM_ASSERT("Unexpected atomspace size",
((unsigned) 7*idx+4) == _as->get_size());
/* Check that the removed atoms really got zapped. */
for (i=0; i<zap; i++) {
check_remove(i, _as.get(), "verify-zapped");
}
/* However, others should still be there. */
for (i=zap; i<idx; i++) {
check_space(i, _as.get(), "verify-zap");
}
/* Kill data for good */
_as->clear();
TSM_ASSERT("Non-empty atomspace", 0 == _as->get_size());
/* Verify that the atoms are no longer being found. */
for (i=0; i<idx; i++)
check_empty(i, _as.get());
logger().debug("END TEST: %s", __FUNCTION__);
}
/* ============================= END OF FILE ================= */