OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace-cog / tests / persist / cog-storage / ValueSaveUTest.cxxtest

1035 lines · cxxtest ref:
/*
 * tests/persist/cog/ValueSaveUTest.cxxtest
 *
 * Test of save and restore of generic values.
 * Ported from tests/persist/sql/multi-driver/ValueSaveUTest.cxxtest
 *
 * Copyright (C) 2008, 2009, 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 <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/atomspace/AtomSpace.h>

#include <opencog/atoms/value/FloatValue.h>
#include <opencog/atoms/value/LinkValue.h>
#include <opencog/atoms/value/StringValue.h>
#include <opencog/atoms/value/VoidValue.h>
#include "../TestCogServer.h"

#include <opencog/persist/cog-storage/CogStorage.h>


#include <opencog/util/Logger.h>

using namespace opencog;

class ValueSaveUTest :  public CxxTest::TestSuite
{
	private:
		std::string uri;
		DECLARE_TEST_COGSERVER

	public:

		ValueSaveUTest(void)
		{
			logger().set_level(Logger::INFO);
			// logger().set_level(Logger::DEBUG);
			logger().set_print_to_stdout_flag(true);
			// logger().set_sync_flag(true);

			uri = "cog://localhost:16002";

			INIT_TEST_COGSERVER(16002);
			printf("Started CogServer\n");
		}

		~ValueSaveUTest()
		{
			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_single_atom_save();
		void test_save_restore(bool);
		void check_one(ValuePtr, bool);
		void test_load_by_type();
		void test_link_by_type();
		void test_link_by_type_quoted();
		void test_incoming();
		void test_load_by_key(bool);
};

/*
 * This is called once before each test, for each test (!!)
 */
void ValueSaveUTest::setUp(void) {}

void ValueSaveUTest::tearDown(void) {}

// ============================================================
/**
 * A simple test case that tests the saving of various values.
 * Test passes if the save succeeds; the accuracy of the save
 * is not tested (i.e. there's no restore; that's in the next test.
 */
void ValueSaveUTest::test_single_atom_save()
{
	logger().info("BEGIN TEST: %s", __FUNCTION__);

	AtomSpacePtr as = createAtomSpace();
	Handle hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	StorageNodePtr store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	Handle key = as->add_node(PREDICATE_NODE, "some pred key");
	Handle atom = as->add_node(CONCEPT_NODE, "some node");

	// Store the key and the atom, so as to avoid any UUID
	// confusions.
	store->store_atom(key);
	store->store_atom(atom);
	store->barrier();

	// --------------------
	// Now set some values
	ValuePtr pvf = createFloatValue(
		std::vector<double>({1.12345678901234567, 2.12345678901234567e35,
		                     3.09876543210987654e200}));

	atom->setValue(key, pvf);
	store->store_atom(atom);
	store->barrier();

	// --------------------
	ValuePtr pvs = createStringValue(
		std::vector<std::string>({"aaa", "bb bb bb", "ccc ccc ccc"}));

	atom->setValue(key, pvs);
	store->store_atom(atom);
	store->barrier();

	// --------------------
	ValuePtr pvl = createLinkValue(
		std::vector<ValuePtr>({pvf, pvs}));

	atom->setValue(key, pvl);
	store->store_atom(atom);
	store->barrier();

	// --------------------
	ValuePtr pvl2 = createLinkValue(
		std::vector<ValuePtr>({pvl, pvl, pvf, pvs}));

	atom->setValue(key, pvl2);
	store->store_atom(atom);
	store->barrier();

	store->setValue(as->add_node(PREDICATE_NODE, "*-close-*"), createVoidValue());
	as = nullptr;

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

// ============================================================
/**
 * Similar to above, but attempts to verify that the saved
 * values are correct.
 */
void ValueSaveUTest::check_one(ValuePtr pap, bool fetchkey)
{
	Handle bkey(createNode(PREDICATE_NODE, "some pred key"));
	Handle batom(createNode(CONCEPT_NODE, "some node"));

	// ------
	// First, save the value
	AtomSpacePtr as = createAtomSpace();
	Handle hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	StorageNodePtr store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	Handle key = store->fetch_atom(bkey);
	Handle atom = store->fetch_atom(batom);
	printf ("Setup atom %s\n", atom->to_string().c_str());
	printf ("Vals %s\n", atom->valuesToString().c_str());

	atom->setValue(key, pap);
	store->store_atom(atom);
	store->barrier();

	printf ("Stored atom %s\n", atom->to_string().c_str());
	printf ("Stored vals %s\n", atom->valuesToString().c_str());

	as = nullptr;

	// ---------------------------------
	// Now, fetch the value and compare.

	as = createAtomSpace();
	hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	// Storage uses a different code path if the key is not yet
	// known when the values are fetched.  Test this code path.
	if (fetchkey) key = store->fetch_atom(bkey);
	atom = store->fetch_atom(batom);

	printf("Got atom %s\n", atom->to_string().c_str());
	printf("Got vals %s\n", atom->valuesToString().c_str());
	ValuePtr fap = atom->getValue(key);

	printf("Expecting %s\n", pap->to_string().c_str());
	TS_ASSERT(*pap == *fap);

	as = nullptr;
}

void ValueSaveUTest::test_save_restore(bool fkey)
{
	logger().info("BEGIN TEST: %s", __FUNCTION__);

	AtomSpacePtr as = createAtomSpace();
	Handle hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	StorageNodePtr store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	Handle key = as->add_node(PREDICATE_NODE, "some pred key");
	Handle atom = as->add_node(CONCEPT_NODE, "some node");

	// Store the key and the atom, so as to avoid any UUID
	// confusions.
	store->store_atom(key);
	store->store_atom(atom);
	store->barrier();

	// --------------------
	// Now set some values
	ValuePtr pvf = createFloatValue(
		std::vector<double>({1.6543210987654321, 2.67890123456789012e-35,
		                     3.2109876543210987e-200}));

	check_one(pvf, fkey);

	// --------------------
	ValuePtr pvs = createStringValue(
		std::vector<std::string>({"aaa", "bb bb bb", "ccc ccc ccc"}));

	check_one(pvs, fkey);

	// --------------------
	ValuePtr pvl = createLinkValue(
		std::vector<ValuePtr>({pvf, pvs}));

	check_one(pvl, fkey);

	// --------------------
	ValuePtr pvl2 = createLinkValue(
		std::vector<ValuePtr>({pvl, pvl, pvf, pvs}));

	check_one(pvl2, fkey);

	::kill_data(store.get(), _test_asp.get());
	as = nullptr;

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

// ============================================================

// Test the fetch_all_atoms_of_type() method in the atomspace.
// The fetch should restore the truth values, and the other values,
// as well.  This tests the fetching of nodes.
void ValueSaveUTest::test_load_by_type()
{
	logger().info("BEGIN TEST: %s", __FUNCTION__);

	AtomSpacePtr as = createAtomSpace();
	Handle hsn = as->add_node(COG_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 key = as->add_node(PREDICATE_NODE, "some pred key");
	Handle af = as->add_node(CONCEPT_NODE, "float node");
	Handle at = as->add_node(CONCEPT_NODE, "string node");
	Handle al = as->add_node(CONCEPT_NODE, "link depth-1 node");
	Handle av = as->add_node(CONCEPT_NODE, "link depth-2 node");

	// --------------------
	// Now set some values
	ValuePtr pvf = createFloatValue(
		std::vector<double>({1.1098765432109876, 2.1234567890123456e37,
		                     3.2109876543210987e-250}));
	af->setValue(key, pvf);

	ValuePtr tf(createFloatValue(std::vector<double>({0.11, 100})));
	af->setValue(truth_key(), tf);

	// --------------------
	ValuePtr pvt = createStringValue(
		std::vector<std::string>({"aaa", "bb bb bb", "ccc ccc ccc"}));
	at->setValue(key, pvt);

	ValuePtr tt(createFloatValue(std::vector<double>({0.22, 200})));
	at->setValue(truth_key(), tt);

	// --------------------
	ValuePtr pvl = createLinkValue(
		std::vector<ValuePtr>({pvf, pvt}));
	al->setValue(key, pvl);

	ValuePtr tl(createFloatValue(std::vector<double>({0.33, 300})));
	al->setValue(truth_key(), tl);

	// --------------------
	ValuePtr pvv = createLinkValue(
		std::vector<ValuePtr>({pvl, pvl, pvf, pvt}));
	av->setValue(key, pvv);

	ValuePtr tv(createFloatValue(std::vector<double>({0.44, 400})));
	av->setValue(truth_key(), tv);

	// --------------------
	// Save, and close things down.
	store->store_atom(af);
	store->store_atom(at);
	store->store_atom(al);
	store->store_atom(av);
	store->barrier();
	as = nullptr;

	// --------------------
	// Start it up again.

	as = createAtomSpace();
	hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	// --------------------
	// Fetch by type. This is what we are testing.
	store->fetch_all_atoms_of_type(CONCEPT_NODE);
	store->barrier();

	// Nine: 4 ConceptNodes, the PredicateNode "*-TruthValueKey-*",
	// PredicateNode "some pred key", PredicateNode "*-IsKeyFlag-*",
	// the CogStorageNode, and PredicateNode "*-open-*"
	// -- the preds show up when the attached values are fetched.
	printf ("fetched %lu atoms, expecting 9\n", as->get_size());
	// printf("contents=%s\n", as->to_string().c_str());
	TSM_ASSERT("Wrong number of Atoms fetched!", as->get_size() == 9);

	Handle gkey = as->add_node(PREDICATE_NODE, "some pred key");
	Handle gaf = as->add_node(CONCEPT_NODE, "float node");
	Handle gat = as->add_node(CONCEPT_NODE, "string node");
	Handle gal = as->add_node(CONCEPT_NODE, "link depth-1 node");
	Handle gav = as->add_node(CONCEPT_NODE, "link depth-2 node");

	TS_ASSERT(*gaf == *af);
	TS_ASSERT(*gat == *at);
	TS_ASSERT(*gal == *al);
	TS_ASSERT(*gav == *av);

	ValuePtr gtf = gaf->getValue(truth_key());
	ValuePtr gtt = gat->getValue(truth_key());
	ValuePtr gtl = gal->getValue(truth_key());
	ValuePtr gtv = gav->getValue(truth_key());

	printf("Wanted: %s\n", tf->to_string().c_str());
	printf("Got:    %s\n", gtf->to_string().c_str());
	TS_ASSERT(*gtf == *tf);
	TS_ASSERT(*gtt == *tt);
	TS_ASSERT(*gtl == *tl);
	TS_ASSERT(*gtv == *tv);

	ValuePtr gpf = gaf->getValue(gkey);
	ValuePtr gpt = gat->getValue(gkey);
	ValuePtr gpl = gal->getValue(gkey);
	ValuePtr gpv = gav->getValue(gkey);

	TS_ASSERT(*gpf == *pvf);
	TS_ASSERT(*gpt == *pvt);
	TS_ASSERT(*gpl == *pvl);
	TS_ASSERT(*gpv == *pvv);

	// --------------------
	as = nullptr;

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

// ============================================================

// Test the fetch_all_atoms_of_type() method in the atomspace.
// The fetch should restore the truth values, and the other values,
// as well.  This tests the fetching of links. This differs from
// the above, in that the values of the outgoing set should NOT
// be fetched.
void ValueSaveUTest::test_link_by_type()
{
	logger().info("BEGIN TEST: %s", __FUNCTION__);

	AtomSpacePtr as = createAtomSpace();
	Handle hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	StorageNodePtr store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	Handle key = as->add_node(PREDICATE_NODE, "some pred key");
	Handle af = as->add_node(CONCEPT_NODE, "a float node");
	Handle at = as->add_node(CONCEPT_NODE, "a string node");
	Handle al = as->add_node(CONCEPT_NODE, "a depth-1 node");
	Handle av = as->add_node(CONCEPT_NODE, "a depth-2 node");

	Handle bf = as->add_node(CONCEPT_NODE, "b float node");
	Handle bt = as->add_node(CONCEPT_NODE, "b string node");
	Handle bl = as->add_node(CONCEPT_NODE, "b depth-1 node");
	Handle bv = as->add_node(CONCEPT_NODE, "b depth-2 node");

	Handle cf = as->add_link(LIST_LINK, {af, bf});
	Handle ct = as->add_link(LIST_LINK, {at, bt});
	Handle cl = as->add_link(LIST_LINK, {al, bl});
	Handle cv = as->add_link(LIST_LINK, {av, bv});

	// --------------------
	// Now set some values
	ValuePtr pvf = createFloatValue(
		std::vector<double>({1.1098765432109876, 2.1234567890123456e37,
		                     3.2109876543210987e-250}));
	af->setValue(key, pvf);
	bf->setValue(key, pvf);
	cf->setValue(key, pvf);

	ValuePtr tf(createFloatValue(std::vector<double>({0.11, 100})));
	af->setValue(truth_key(), tf);
	bf->setValue(truth_key(), tf);
	cf->setValue(truth_key(), tf);

	// --------------------
	ValuePtr pvt = createStringValue(
		std::vector<std::string>({"aaa", "bb bb bb", "ccc ccc ccc"}));
	at->setValue(key, pvt);
	bt->setValue(key, pvt);
	ct->setValue(key, pvt);

	ValuePtr tt(createFloatValue(std::vector<double>({0.22, 200})));
	at->setValue(truth_key(), tt);
	bt->setValue(truth_key(), tt);
	ct->setValue(truth_key(), tt);

	// --------------------
	ValuePtr pvl = createLinkValue(
		std::vector<ValuePtr>({pvf, pvt}));
	al->setValue(key, pvl);
	bl->setValue(key, pvl);
	cl->setValue(key, pvl);

	ValuePtr tl(createFloatValue(std::vector<double>({0.33, 300})));
	al->setValue(truth_key(), tl);
	bl->setValue(truth_key(), tl);
	cl->setValue(truth_key(), tl);

	// --------------------
	ValuePtr pvv = createLinkValue(
		std::vector<ValuePtr>({pvl, pvl, pvf, pvt}));
	av->setValue(key, pvv);
	bv->setValue(key, pvv);
	cv->setValue(key, pvv);

	ValuePtr tv(createFloatValue(std::vector<double>({0.44, 400})));
	av->setValue(truth_key(), tv);
	bv->setValue(truth_key(), tv);
	cv->setValue(truth_key(), tv);

	// --------------------
	// Save, and close things down.
	// Note that this store will also *recursively* store the
	// values on the outgoing atoms, as well.
	store->store_atom(cf);
	store->store_atom(ct);
	store->store_atom(cl);
	store->store_atom(cv);
	store->barrier();

	as = nullptr;

	// --------------------
	// Start it up again.

	as = createAtomSpace();
	hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	// --------------------
	// Fetch by type. This is what we are testing.

	Handle gkey = as->add_node(PREDICATE_NODE, "some pred key");
	Handle gaf = as->add_node(CONCEPT_NODE, "a float node");
	Handle gat = as->add_node(CONCEPT_NODE, "a string node");
	Handle gal = as->add_node(CONCEPT_NODE, "a depth-1 node");
	Handle gav = as->add_node(CONCEPT_NODE, "a depth-2 node");

	// Note that the A-nodes above are added *before* the fetch;
	// The B-nodes are added after the fetch. In either case,
	// there should not be any values fetched for either A or B;
	// only the values on the C-links should be fetched.
	// +1 for (PredicateNode "*-IsKeyFlag-*"), +1 for *-open-*
	store->fetch_all_atoms_of_type(LIST_LINK);
	store->barrier();
	printf ("fetched %lu atoms, expecting 17\n", as->get_size());
	// printf("contents=%s\n", as->to_string().c_str());
	TSM_ASSERT("Wrong number of Atoms fetched!", as->get_size() == 17);

	Handle gbf = as->add_node(CONCEPT_NODE, "b float node");
	Handle gbt = as->add_node(CONCEPT_NODE, "b string node");
	Handle gbl = as->add_node(CONCEPT_NODE, "b depth-1 node");
	Handle gbv = as->add_node(CONCEPT_NODE, "b depth-2 node");

	Handle gcf = as->add_link(LIST_LINK, {gaf, gbf});
	Handle gct = as->add_link(LIST_LINK, {gat, gbt});
	Handle gcl = as->add_link(LIST_LINK, {gal, gbl});
	Handle gcv = as->add_link(LIST_LINK, {gav, gbv});

	TS_ASSERT(*gcf == *cf);
	TS_ASSERT(*gct == *ct);
	TS_ASSERT(*gcl == *cl);
	TS_ASSERT(*gcv == *cv);

	ValuePtr gtf = gaf->getValue(truth_key());
	ValuePtr gtt = gat->getValue(truth_key());
	ValuePtr gtl = gal->getValue(truth_key());
	ValuePtr gtv = gav->getValue(truth_key());

	TS_ASSERT(gtf == nullptr);
	TS_ASSERT(gtt == nullptr);
	TS_ASSERT(gtl == nullptr);
	TS_ASSERT(gtv == nullptr);

	gtf = gbf->getValue(truth_key());
	gtt = gbt->getValue(truth_key());
	gtl = gbl->getValue(truth_key());
	gtv = gbv->getValue(truth_key());

	TS_ASSERT(gtf == nullptr);
	TS_ASSERT(gtt == nullptr);
	TS_ASSERT(gtl == nullptr);
	TS_ASSERT(gtv == nullptr);

	gtf = gcf->getValue(truth_key());
	gtt = gct->getValue(truth_key());
	gtl = gcl->getValue(truth_key());
	gtv = gcv->getValue(truth_key());

	TS_ASSERT(*gtf == *tf);
	TS_ASSERT(*gtt == *tt);
	TS_ASSERT(*gtl == *tl);
	TS_ASSERT(*gtv == *tv);

	// --------------------

	ValuePtr gpf;
	ValuePtr gpt;
	ValuePtr gpl;
	ValuePtr gpv;

	try { gpf = gaf->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpt = gat->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpl = gal->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpv = gav->getValue(gkey); } catch(const RuntimeException&) {}

	TS_ASSERT(gpf == nullptr);
	TS_ASSERT(gpt == nullptr);
	TS_ASSERT(gpl == nullptr);
	TS_ASSERT(gpv == nullptr);

	try { gpf = gbf->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpt = gbt->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpl = gbl->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpv = gbv->getValue(gkey); } catch(const RuntimeException&) {}

	TS_ASSERT(gpf == nullptr);
	TS_ASSERT(gpt == nullptr);
	TS_ASSERT(gpl == nullptr);
	TS_ASSERT(gpv == nullptr);

	gpf = gcf->getValue(gkey);
	gpt = gct->getValue(gkey);
	gpl = gcl->getValue(gkey);
	gpv = gcv->getValue(gkey);

	TS_ASSERT(*gpf == *pvf);
	TS_ASSERT(*gpt == *pvt);
	TS_ASSERT(*gpl == *pvl);
	TS_ASSERT(*gpv == *pvv);

	// --------------------
	as = nullptr;

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

// ============================================================

// Same as test_link_by_type() except that the StringValue
// has some nasties intended to confuse parsing.
void ValueSaveUTest::test_link_by_type_quoted()
{
	logger().info("BEGIN TEST: %s", __FUNCTION__);

	AtomSpacePtr as = createAtomSpace();
	Handle hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	StorageNodePtr store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	Handle key = as->add_node(PREDICATE_NODE, "some (\"pred\"))) key");
	Handle af = as->add_node(CONCEPT_NODE, "x \"float\" node");
	Handle at = as->add_node(CONCEPT_NODE, "x \"string\" node");
	Handle al = as->add_node(CONCEPT_NODE, "x \"depth-1\" node");
	Handle av = as->add_node(CONCEPT_NODE, "x \"depth-2\" node");

	Handle bf = as->add_node(CONCEPT_NODE, "b float node");
	Handle bt = as->add_node(CONCEPT_NODE, "b string node");
	Handle bl = as->add_node(CONCEPT_NODE, "b depth-1 node");
	Handle bv = as->add_node(CONCEPT_NODE, "b depth-2 node");

	Handle cf = as->add_link(LIST_LINK, {af, bf});
	Handle ct = as->add_link(LIST_LINK, {at, bt});
	Handle cl = as->add_link(LIST_LINK, {al, bl});
	Handle cv = as->add_link(LIST_LINK, {av, bv});

	// --------------------
	// Now set some values
	ValuePtr pvf = createFloatValue(
		std::vector<double>({1.1098765432109876, 2.1234567890123456e37,
		                     3.2109876543210987e-250}));
	af->setValue(key, pvf);
	bf->setValue(key, pvf);
	cf->setValue(key, pvf);

	ValuePtr tf(createFloatValue(std::vector<double>({0.11, 100})));
	af->setValue(truth_key(), tf);
	bf->setValue(truth_key(), tf);
	cf->setValue(truth_key(), tf);

	// --------------------
	ValuePtr pvt = createStringValue(
		std::vector<std::string>({"a\"a\"a", "bb) (bb) (bb", "ccc \")(\"ccc))\" ccc"}));
	at->setValue(key, pvt);
	bt->setValue(key, pvt);
	ct->setValue(key, pvt);

	ValuePtr tt(createFloatValue(std::vector<double>({0.22, 200})));
	at->setValue(truth_key(), tt);
	bt->setValue(truth_key(), tt);
	ct->setValue(truth_key(), tt);

	// --------------------
	ValuePtr pvl = createLinkValue(
		std::vector<ValuePtr>({pvf, pvt}));
	al->setValue(key, pvl);
	bl->setValue(key, pvl);
	cl->setValue(key, pvl);

	ValuePtr tl(createFloatValue(std::vector<double>({0.33, 300})));
	al->setValue(truth_key(), tl);
	bl->setValue(truth_key(), tl);
	cl->setValue(truth_key(), tl);

	// --------------------
	ValuePtr pvv = createLinkValue(
		std::vector<ValuePtr>({pvl, pvl, pvf, pvt}));
	av->setValue(key, pvv);
	bv->setValue(key, pvv);
	cv->setValue(key, pvv);

	ValuePtr tv(createFloatValue(std::vector<double>({0.44, 400})));
	av->setValue(truth_key(), tv);
	bv->setValue(truth_key(), tv);
	cv->setValue(truth_key(), tv);

	// --------------------
	// Save, and close things down.
	// Note that this store will also *recursively* store the
	// values on the outgoing atoms, as well.
	store->store_atom(cf);
	store->store_atom(ct);
	store->store_atom(cl);
	store->store_atom(cv);
	store->barrier();

	as = nullptr;

	// --------------------
	// Start it up again.

	as = createAtomSpace();
	hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	// --------------------
	// Fetch by type. This is what we are testing.

	Handle gkey = as->add_node(PREDICATE_NODE, "some (\"pred\"))) key");
	Handle gaf = as->add_node(CONCEPT_NODE, "x \"float\" node");
	Handle gat = as->add_node(CONCEPT_NODE, "x \"string\" node");
	Handle gal = as->add_node(CONCEPT_NODE, "x \"depth-1\" node");
	Handle gav = as->add_node(CONCEPT_NODE, "x \"depth-2\" node");

	// Note that the A-nodes above are added *before* the fetch;
	// The B-nodes are added after the fetch. In either case,
	// there should not be any values fetched for either A or B;
	// only the values on the C-links should be fetched.
	store->fetch_all_atoms_of_type(LIST_LINK);
	store->barrier();

	// We created 9 new atoms above, so expect 17 from before plus 9
	// (+1 for *-IsKeyFlag-*, +1 for *-open-*)
	printf ("fetched %lu atoms, expecting 26\n", as->get_size());
	TSM_ASSERT("Wrong number of Atoms fetched!", as->get_size() == 26);

	Handle gbf = as->add_node(CONCEPT_NODE, "b float node");
	Handle gbt = as->add_node(CONCEPT_NODE, "b string node");
	Handle gbl = as->add_node(CONCEPT_NODE, "b depth-1 node");
	Handle gbv = as->add_node(CONCEPT_NODE, "b depth-2 node");

	Handle gcf = as->add_link(LIST_LINK, {gaf, gbf});
	Handle gct = as->add_link(LIST_LINK, {gat, gbt});
	Handle gcl = as->add_link(LIST_LINK, {gal, gbl});
	Handle gcv = as->add_link(LIST_LINK, {gav, gbv});

	TS_ASSERT(*gcf == *cf);
	TS_ASSERT(*gct == *ct);
	TS_ASSERT(*gcl == *cl);
	TS_ASSERT(*gcv == *cv);

	ValuePtr gtf = gaf->getValue(truth_key());
	ValuePtr gtt = gat->getValue(truth_key());
	ValuePtr gtl = gal->getValue(truth_key());
	ValuePtr gtv = gav->getValue(truth_key());

	TS_ASSERT(gtf == nullptr);
	TS_ASSERT(gtt == nullptr);
	TS_ASSERT(gtl == nullptr);
	TS_ASSERT(gtv == nullptr);

	gtf = gbf->getValue(truth_key());
	gtt = gbt->getValue(truth_key());
	gtl = gbl->getValue(truth_key());
	gtv = gbv->getValue(truth_key());

	TS_ASSERT(gtf == nullptr);
	TS_ASSERT(gtt == nullptr);
	TS_ASSERT(gtl == nullptr);
	TS_ASSERT(gtv == nullptr);

	gtf = gcf->getValue(truth_key());
	gtt = gct->getValue(truth_key());
	gtl = gcl->getValue(truth_key());
	gtv = gcv->getValue(truth_key());

	printf("Expecting %s\n", tf->to_string().c_str());
	printf("Got %s\n", gtf->to_string().c_str());
	TS_ASSERT(*gtf == *tf);
	TS_ASSERT(*gtt == *tt);
	TS_ASSERT(*gtl == *tl);
	TS_ASSERT(*gtv == *tv);

	// --------------------

	ValuePtr gpf;
	ValuePtr gpt;
	ValuePtr gpl;
	ValuePtr gpv;

	try { gpf = gaf->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpt = gat->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpl = gal->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpv = gav->getValue(gkey); } catch(const RuntimeException&) {}

	TS_ASSERT(gpf == nullptr);
	TS_ASSERT(gpt == nullptr);
	TS_ASSERT(gpl == nullptr);
	TS_ASSERT(gpv == nullptr);

	try { gpf = gbf->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpt = gbt->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpl = gbl->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpv = gbv->getValue(gkey); } catch(const RuntimeException&) {}

	TS_ASSERT(gpf == nullptr);
	TS_ASSERT(gpt == nullptr);
	TS_ASSERT(gpl == nullptr);
	TS_ASSERT(gpv == nullptr);

	gpf = gcf->getValue(gkey);
	gpt = gct->getValue(gkey);
	gpl = gcl->getValue(gkey);
	gpv = gcv->getValue(gkey);

	TS_ASSERT(*gpf == *pvf);
	TS_ASSERT(*gpt == *pvt);
	TS_ASSERT(*gpl == *pvl);
	TS_ASSERT(*gpv == *pvv);

	// --------------------
	as = nullptr;

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

// ============================================================

// Test the fetch_incoming_set() method in the atomspace.
// The fetch should restore the truth values, and the other values,
// as well, but only on the incoming set!
void ValueSaveUTest::test_incoming()
{
	logger().info("BEGIN TEST: %s", __FUNCTION__);

	AtomSpacePtr as = createAtomSpace();
	Handle hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	StorageNodePtr store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	Handle key = as->add_node(PREDICATE_NODE, "some pred key");
	Handle af = as->add_node(CONCEPT_NODE, "a float node");
	Handle at = as->add_node(CONCEPT_NODE, "a string node");
	Handle al = as->add_node(CONCEPT_NODE, "a depth-1 node");
	Handle av = as->add_node(CONCEPT_NODE, "a depth-2 node");

	Handle bf = as->add_node(CONCEPT_NODE, "b float node");
	Handle bt = as->add_node(CONCEPT_NODE, "b string node");
	Handle bl = as->add_node(CONCEPT_NODE, "b depth-1 node");
	Handle bv = as->add_node(CONCEPT_NODE, "b depth-2 node");

	Handle cf = as->add_link(LIST_LINK, {af, bf});
	Handle ct = as->add_link(LIST_LINK, {at, bt});
	Handle cl = as->add_link(LIST_LINK, {al, bl});
	Handle cv = as->add_link(LIST_LINK, {av, bv});

	// --------------------
	// Now set some values
	ValuePtr pvf = createFloatValue(
		std::vector<double>({1.1098765432109876, 2.1234567890123456e-37,
		                     3.2109876543210987e250}));
	af->setValue(key, pvf);
	bf->setValue(key, pvf);
	cf->setValue(key, pvf);

	ValuePtr tf(createFloatValue(std::vector<double>({0.11, 100})));
	af->setValue(truth_key(), tf);
	bf->setValue(truth_key(), tf);
	cf->setValue(truth_key(), tf);

	// --------------------
	ValuePtr pvt = createStringValue(
		std::vector<std::string>({"aaa", "bb bb bb", "ccc ccc ccc"}));
	at->setValue(key, pvt);
	bt->setValue(key, pvt);
	ct->setValue(key, pvt);

	ValuePtr tt(createFloatValue(std::vector<double>({0.22, 200})));
	at->setValue(truth_key(), tt);
	bt->setValue(truth_key(), tt);
	ct->setValue(truth_key(), tt);

	// --------------------
	ValuePtr pvl = createLinkValue(
		std::vector<ValuePtr>({pvf, pvt}));
	al->setValue(key, pvl);
	bl->setValue(key, pvl);
	cl->setValue(key, pvl);

	ValuePtr tl(createFloatValue(std::vector<double>({0.33, 300})));
	al->setValue(truth_key(), tl);
	bl->setValue(truth_key(), tl);
	cl->setValue(truth_key(), tl);

	// --------------------
	ValuePtr pvv = createLinkValue(
		std::vector<ValuePtr>({pvl, pvl, pvf, pvt}));
	av->setValue(key, pvv);
	bv->setValue(key, pvv);
	cv->setValue(key, pvv);

	ValuePtr tv(createFloatValue(std::vector<double>({0.44, 400})));
	av->setValue(truth_key(), tv);
	bv->setValue(truth_key(), tv);
	cv->setValue(truth_key(), tv);

	// --------------------
	// Save, and close things down.
	// Note that this store will also *recursively* store the
	// values on the outgoing atoms, as well.
	store->store_atom(cf);
	store->store_atom(ct);
	store->store_atom(cl);
	store->store_atom(cv);
	store->barrier();

	as = nullptr;

	// --------------------
	// Start it up again.

	as = createAtomSpace();
	hsn = as->add_node(COG_STORAGE_NODE, std::string(uri));
	store = StorageNodeCast(hsn);
	store->setValue(as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
	TS_ASSERT(store->connected())

	// --------------------
	// Fetch incoming set. This is what we are testing.

	Handle gkey = as->add_node(PREDICATE_NODE, "some pred key");
	Handle gaf = as->add_node(CONCEPT_NODE, "a float node");
	Handle gat = as->add_node(CONCEPT_NODE, "a string node");
	Handle gal = as->add_node(CONCEPT_NODE, "a depth-1 node");
	Handle gav = as->add_node(CONCEPT_NODE, "a depth-2 node");

	// Note that the A-nodes above are added *before* the fetch;
	// The B-nodes are added after the fetch. In either case,
	// there should not be any values fetched for either A or B;
	// only the values on the C-links should be fetched.
	store->fetch_incoming_set(gaf, false);
	store->fetch_incoming_set(gat, false);
	store->fetch_incoming_set(gal, false);
	store->fetch_incoming_set(gav, false);
	store->barrier();
	// +1 for *-IsKeyFlag-*, +1 for *-open-*
	TSM_ASSERT("Wrong number of Atoms fetched!", as->get_size() == 17);

	Handle gbf = as->add_node(CONCEPT_NODE, "b float node");
	Handle gbt = as->add_node(CONCEPT_NODE, "b string node");
	Handle gbl = as->add_node(CONCEPT_NODE, "b depth-1 node");
	Handle gbv = as->add_node(CONCEPT_NODE, "b depth-2 node");

	Handle gcf = as->add_link(LIST_LINK, {gaf, gbf});
	Handle gct = as->add_link(LIST_LINK, {gat, gbt});
	Handle gcl = as->add_link(LIST_LINK, {gal, gbl});
	Handle gcv = as->add_link(LIST_LINK, {gav, gbv});

	TS_ASSERT(*gcf == *cf);
	TS_ASSERT(*gct == *ct);
	TS_ASSERT(*gcl == *cl);
	TS_ASSERT(*gcv == *cv);

	ValuePtr gtf = gaf->getValue(truth_key());
	ValuePtr gtt = gat->getValue(truth_key());
	ValuePtr gtl = gal->getValue(truth_key());
	ValuePtr gtv = gav->getValue(truth_key());

	TS_ASSERT(gtf == nullptr);
	TS_ASSERT(gtt == nullptr);
	TS_ASSERT(gtl == nullptr);
	TS_ASSERT(gtv == nullptr);

	gtf = gbf->getValue(truth_key());
	gtt = gbt->getValue(truth_key());
	gtl = gbl->getValue(truth_key());
	gtv = gbv->getValue(truth_key());

	TS_ASSERT(gtf == nullptr);
	TS_ASSERT(gtt == nullptr);
	TS_ASSERT(gtl == nullptr);
	TS_ASSERT(gtv == nullptr);

	gtf = gcf->getValue(truth_key());
	gtt = gct->getValue(truth_key());
	gtl = gcl->getValue(truth_key());
	gtv = gcv->getValue(truth_key());

	TS_ASSERT(*gtf == *tf);
	TS_ASSERT(*gtt == *tt);
	TS_ASSERT(*gtl == *tl);
	TS_ASSERT(*gtv == *tv);

	// --------------------

	ValuePtr gpf;
	ValuePtr gpt;
	ValuePtr gpl;
	ValuePtr gpv;

	try { gpf = gaf->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpt = gat->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpl = gal->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpv = gav->getValue(gkey); } catch(const RuntimeException&) {}

	TS_ASSERT(gpf == nullptr);
	TS_ASSERT(gpt == nullptr);
	TS_ASSERT(gpl == nullptr);
	TS_ASSERT(gpv == nullptr);

	try { gpf = gbf->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpt = gbt->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpl = gbl->getValue(gkey); } catch(const RuntimeException&) {}
	try { gpv = gbv->getValue(gkey); } catch(const RuntimeException&) {}

	TS_ASSERT(gpf == nullptr);
	TS_ASSERT(gpt == nullptr);
	TS_ASSERT(gpl == nullptr);
	TS_ASSERT(gpv == nullptr);

	gpf = gcf->getValue(gkey);
	gpt = gct->getValue(gkey);
	gpl = gcl->getValue(gkey);
	gpv = gcv->getValue(gkey);

	TS_ASSERT(*gpf == *pvf);
	TS_ASSERT(*gpt == *pvt);
	TS_ASSERT(*gpl == *pvl);
	TS_ASSERT(*gpv == *pvv);

	// --------------------
	as = nullptr;

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

/* ============================= END OF FILE ================= */