/*
 * testcallback.cpp --
 *
 * Tests of the callback facility.
 *
 * Copyright (c) 2000-2003, JYL Software Inc.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE, EVEN IF
 * JYL SOFTWARE INC. IS MADE AWARE OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#ifndef	_WIN32
#include <unistd.h>
#endif
#include "test.h"

/*
 * These counters are incremented by the callbacks.
 */

static int cbaddnode;
static int cbdetnode;
static int cbaddvertex;
static int cbdetvertex;
static int cbmodvertex;

/*
 * This function resets the callback counters.
 */

static void
ResetCallbackCounters()
{
    cbaddnode = 0;
    cbdetnode = 0;
    cbaddvertex = 0;
    cbdetvertex = 0;
    cbmodvertex = 0;
}

/*
 * Test how many callbacks actually occurred, against a set of expected
 * values.
 */

static bool
TestCallbackCounters(int ean, int edn, int eav, int edv)
{
    if ((ean != cbaddnode) ||
	(edn != cbdetnode) ||
	(eav != cbaddvertex) ||
	(edv != cbdetvertex)) {
	fprintf(stderr,
		"Expected: ean %d edn %d eav %d edv %d, found: %d %d %d %d\n",
		ean, edn, eav, edv,
		cbaddnode, cbdetnode, cbaddvertex, cbdetvertex);
	return false;
    }
    return true;
}

/*
 * These functions are the actual callbacks:
 */

static void NodeAddCallback(void *cd, const e4_RefCount &r, void *csdata)
{
    cbaddnode++;
}
static void NodeDetCallback(void *cd, const e4_RefCount &r, void *csdata)
{
    cbdetnode++;
}
static void VertexAddCallback(void *cd, const e4_RefCount &r, void *csdata)
{
    cbaddvertex++;
}
static void VertexDetCallback(void *cd, const e4_RefCount &r, void *csdata)
{
    cbdetvertex++;
}

static void NodeCLAddCallback(void *cd, const e4_RefCount &r, void *csdata)
{
    e4_Storage s;
    e4_Node n = (e4_Node) r;

    if (!n.GetStorage(s) || !s.IsValid()) {
	return;
    }
    cbaddnode++;
}

static void NodeCLDetCallback(void *cd, const e4_RefCount &r, void *csdata)
{
    e4_Storage s;
    e4_Node n = (e4_Node) r;

    if (!n.GetStorage(s) || !s.IsValid()) {
	return;
    }
    cbdetnode++;
}

static void VertexCLAddCallback(void *cd, const e4_RefCount &r, void *csdata)
{
    e4_Storage s;
    e4_Vertex v = (e4_Vertex) r;

    if (!v.GetStorage(s) || !s.IsValid()) {
	return;
    }
    cbaddvertex++;
}

static void VertexCLDetCallback(void *cd, const e4_RefCount &r, void *csdata)
{
    e4_Storage s;
    e4_Vertex v = (e4_Vertex) r;

    if (!v.GetStorage(s) || !s.IsValid()) {
	return;
    }
    cbdetvertex++;
}

static void VertexCLModCallback(void *cd, const e4_RefCount &r,	void *csdata)
{
    e4_Storage s;
    e4_Vertex v = (e4_Vertex) r;

    if (!v.GetStorage(s) || !s.IsValid()) {
	return;
    }
    cbmodvertex++;
}

/*
 * These are the tests:
 */

static int
test_callback1()
{
    ResetCallbackCounters();

    {
	e4_Storage s;

	if (!clean_storage("foo.db", s)) {
	    fprintf(stderr, "test callback1 failed (step 0)\n");
	    return 1;
	}
	if (!TestCallbackCounters(0, 0, 0, 0)) {
	    fprintf(stderr, "test callback1 failed (step 1)\n");
	    return 1;
	}

	s.Delete();
    }

    if (!TestCallbackCounters(0, 0, 0, 0)) {
	fprintf(stderr, "test callback1 failed (step 2)\n");
	return 1;
    }

    return 0;
}

static int
test_callback2()
{
    e4_Node n;

    ResetCallbackCounters();

    /*
     * Test basic callback functionality.
     */

    {
	e4_Storage s;

	if (!clean_storage("foo.db", s)) {
	    fprintf(stderr, "test callback2 failed (step 0)\n");
	    return 2;
	}
	if (!TestCallbackCounters(0, 0, 0, 0)) {
	    fprintf(stderr, "test callback2 failed (step 1)\n");
	    return 2;
	}

	/*
	 * Declaring callbacks doesn't cause any events.
	 */

	s.DeclareCallback(E4_ECADDNODE, NodeAddCallback, (void *) 0);
	s.DeclareCallback(E4_ECDETNODE, NodeDetCallback, (void *) 0);

	if (!s.GetRootNode(n) || !n.IsValid() || !n.IsRoot()) {
	    fprintf(stderr, "test callback2 failed (step 2)\n");
	    return 2;
	}
	if (!TestCallbackCounters(0, 0, 0, 0)) {
	    fprintf(stderr, "test callback2 failed (step 3)\n");
	    return 2;
	}

	s.Delete();
    }

    if (!TestCallbackCounters(0, 0, 0, 0)) {
	fprintf(stderr, "test callback2 failed (step 8)\n");
	return 2;
    }

    return 0;
}

static int
test_callback5()
{
    e4_Node n1, n2, n3;
    int rank;

    /*
     * Test various conditions under which add and detach node callbacks
     * should occur.
     */

    ResetCallbackCounters();

    {
	e4_Storage s;

	if (!clean_storage("foo.db", s)) {
	    fprintf(stderr, "test callback5 failed (step 0)\n");
	    return 5;
	}
	if (!TestCallbackCounters(0, 0, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 1)\n");
	    return 5;
	}
	if (!s.GetRootNode(n1) || !n1.IsValid() || !n1.IsRoot()) {
	    fprintf(stderr, "test callback5 failed (step 2)\n");
	    return 5;
	}

	/*
	 * No callbacks are registered so no callbacks should
	 * have occurred.
	 */
	
	if (!TestCallbackCounters(0, 0, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 3)\n");
	    return 5;
	}

	s.DeclareCallback(E4_ECADDNODE, NodeAddCallback, (void *) 0);
	s.DeclareCallback(E4_ECDETNODE, NodeDetCallback, (void *) 0);

	/*
	 * Adding a detached node causes a node add callback but
	 * no node detach callback.
	 */

	if (!s.CreateDetachedNode(n2) || !n2.IsValid() || n2.IsRoot()) {
	    fprintf(stderr, "test callback5 failed (step 4)\n");
	    return 5;
	}
	if (!TestCallbackCounters(1, 0, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 5) %d %d\n",
		    cbaddnode, cbdetnode);
	    return 5;
	}

	/*
	 * Adding a node and then detaching it causes a node add and
	 * a node detach callback.
	 */

	if (!n1.AddNode("n2", E4_IOLAST, rank, n3)) {
	    fprintf(stderr, "test callback5 failed (step 6.0)\n");
	    return 5;
	}
	if (!n3.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 6.1)\n");
	    return 5;
	}
	if (n3.IsDetached()) {
	    fprintf(stderr, "test callback5 failed (step 6.2)\n");
	    return 5;
	}

	if (!TestCallbackCounters(2, 0, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 7)\n");
	    return 5;
	}
	if (!n3.Detach() || !n3.IsValid() || !n3.IsDetached()) {
	    fprintf(stderr, "test callback5 failed (step 8)\n");
	    return 5;
	}
	if (!TestCallbackCounters(2, 1, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 9)\n");
	    if (TestCallbackCounters(2, 0, 0, 0)) {
	      fprintf(stderr, "Missing detach node callback\n");
	    }
	    if (TestCallbackCounters(2, 2, 0, 0)) {
	      fprintf(stderr, "Additional detach node callback\n");
	    }
	    return 5;
	}

	/*
	 * In a new storage the root node is already detached. Test
	 * that calling detach on a detached node does not cause
	 * detach callbacks.
	 */

	if (!n1.Detach() || !n1.IsValid() || !n1.IsDetached()) {
	    fprintf(stderr, "test callback5 failed (step 10)\n");
	    return 5;
	}
	if (!TestCallbackCounters(2, 1, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 11)\n");
	    return 5;
	}

	/*
	 * Retrieving the root node does not cause node addition callbacks.
	 */

	if (!s.GetRootNode(n1) || !n1.IsValid() || !n1.IsRoot() ||
	    !n1.IsDetached()) {
	    fprintf(stderr, "test callback5 failed (step 12)\n");
	    return 5;
	}
	if (!TestCallbackCounters(2, 1, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 13)\n");
	    return 5;
	}

	/*
	 * Adding a node field should cause an add node callback.
	 */

	if (!n1.AddNode("foo", E4_IOFIRST, rank, n2) || !n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 14)\n");
	    return 5;
	}
	if (!TestCallbackCounters(3, 1, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 15)\n");
	    return 5;
	}

	/*
	 * Detaching a vertex whose value is n2 should cause
	 * a detach node callback on n2, because n2 now became detached.
	 */

	if (!n1.DetachVertex("foo") || !n2.IsValid() || !n2.IsDetached()) {
	    fprintf(stderr, "test callback5 failed (step 16)\n");
	    return 5;
	}
	if (!TestCallbackCounters(3, 2, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 17)\n");
	    return 5;
	}

	/*
	 * Setting an existing vertex to a new node should cause an add node
	 * callback to happen.
	 */

	if (!n1.AddVertex("foo", E4_IOFIRST, rank, 32)) {
	    fprintf(stderr, "test callback5 failed (step 18)\n");
	    return 5;
	}
	if (!n1.SetNode("foo", n2) || !n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 19)\n");
	    return 5;
	}
	if (!TestCallbackCounters(4, 2, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 20)\n");
	    return 5;
	}
	if (!n2.Detach() || !n2.IsValid() || !n2.IsDetached()) {
	    fprintf(stderr, "test callback5 failed (step 21)\n");
	    return 5;
	}
	if (!TestCallbackCounters(4, 3, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 22)\n");
	    return 5;
	}
	n2 = invalidNode;

	if (n1.GetVertex("foo", n2) || n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 23)\n");
	    return 5;
	}

	/*
	 * Same exercise except we're setting the vertex by rank.
	 */

	if (!n1.AddVertex("foo", E4_IOFIRST, rank, 32)) {
	    fprintf(stderr, "test callback5 failed (step 24)\n");
	    return 5;
	}
	if (!n1.SetNodeByRank(rank, n2) || !n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 25)\n");
	    return 5;
	}
	if (!TestCallbackCounters(5, 3, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 26)\n");
	    return 5;
	}
	if (!n2.Detach() || !n2.IsValid() || !n2.IsDetached()) {
	    fprintf(stderr, "test callback5 failed (step 27)\n");
	    return 5;
	}
	if (!TestCallbackCounters(5, 4, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 28)\n");
	    return 5;
	}
	n2 = invalidNode;

	if (n1.GetVertex("foo", n2) || n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 29)\n");
	    return 5;
	}

	/*
	 * Adding another add node callback causes two callbacks to happen,
	 * one for each declared callback, when a new node is created.
	 */

	s.DeclareCallback(E4_ECADDNODE, NodeAddCallback, (void *) 1);

	if (!n1.AddVertex("foo", E4_IOFIRST, rank, 34)) {
	    fprintf(stderr, "test callback5 failed (step 30)\n");
	    return 5;
	}
	if (!n1.SetNodeByRank(rank, n2) || !n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 31)\n");
	    return 5;
	}
	if (!TestCallbackCounters(7, 4, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 32)\n");
	    return 5;
	}
	if (!n2.Detach() || !n2.IsValid() || !n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 33)\n");
	    return 5;
	}

	/*
	 * But only one detach callback is caused because there is only
	 * one detach callback declared.
	 */

	if (!TestCallbackCounters(7, 5, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 34)\n");
	    return 5;
	}
	n2 = invalidNode;

	if (n1.GetVertex("foo", n2) || n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 35)\n");
	    return 5;
	}

	/*
	 * Removing one of the callbacks means there is only one
	 * callback when a node is created.
	 */

	s.DeleteCallback(E4_ECADDNODE, NodeAddCallback, (void *) 1);

	if (!n1.AddVertex("foo", E4_IOFIRST, rank, 34)) {
	    fprintf(stderr, "test callback5 failed (step 36)\n");
	    return 5;
	}
	if (!n1.SetNodeByRank(rank, n2) || !n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 37)\n");
	    return 5;
	}
	if (!TestCallbackCounters(8, 5, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 38)\n");
	    return 5;
	}
	if (!n2.Detach() || !n2.IsValid() || !n2.IsDetached()) {
	    fprintf(stderr, "test callback5 failed (step 39)\n");
	    return 5;
	}
	if (!TestCallbackCounters(8, 6, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 40)\n");
	    return 5;
	}
	n2 = invalidNode;

	if (n1.GetVertex("foo", n2) || n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 41)\n");
	    return 5;
	}

	/*
	 * Try deleting a non-existent add node callback and ensure that
	 * this does not impact existing callbacks.
	 */

	s.DeleteCallback(E4_ECADDNODE, NodeAddCallback, (void *) 3);

	if (!n1.AddVertex("foo", E4_IOFIRST, rank, 34)) {
	    fprintf(stderr, "test callback5 failed (step 42)\n");
	    return 5;
	}
	if (!n1.SetNodeByRank(rank, n2) || !n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 43)\n");
	    return 5;
	}
	if (!TestCallbackCounters(9, 6, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 44)\n");
	    return 5;
	}
	if (!n2.Detach() || !n2.IsValid() || !n2.IsDetached()) {
	    fprintf(stderr, "test callback5 failed (step 45)\n");
	    return 5;
	}
	if (!TestCallbackCounters(9, 7, 0, 0)) {
	    fprintf(stderr, "test callback5 failed (step 46)\n");
	    return 5;
	}
	n2 = invalidNode;

	if (n1.GetVertex("foo", n2) || n2.IsValid()) {
	    fprintf(stderr, "test callback5 failed (step 47)\n");
	    return 5;
	}

	s.Delete();
    }

    if (!TestCallbackCounters(9, 7, 0, 0)) {
	fprintf(stderr, "test callback5 failed (step 48)\n");
	return 5;
    }

    return 0;
}

static int
test_callback6()
{
    e4_Node n1, n2;
    e4_Vertex v, v1, v2;
    int rank = 0, i = 0;

    /*
     * Test various conditions under which add and detach vertex callbacks
     * should occur.
     */

    ResetCallbackCounters();

    {
	e4_Storage s;

	if (!clean_storage("foo.db", s)) {
	    fprintf(stderr, "test callback6 failed (step 0)\n");
	    return 6;
	}
	if (!TestCallbackCounters(0, 0, 0, 0)) {
	    fprintf(stderr, "test callback6 failed (step 1)\n");
	    return 6;
	}

	s.DeclareCallback(E4_ECADDVERTEX, VertexAddCallback, (void *) 0);
	s.DeclareCallback(E4_ECDETVERTEX, VertexDetCallback, (void *) 0);

	if (!s.GetRootNode(n1) || !n1.IsValid() || !n1.IsRoot()) {
	    fprintf(stderr, "test callback6 failed (step 2)\n");
	    return 6;
	}

	/*
	 * Add a bunch of vertices and see if the callbacks happened.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.AddVertex("foo", E4_IOLAST, rank, 34)) {
		fprintf(stderr, "test callback6 failed (step 3.1.%d)\n", i);
		return 6;
	    }
	    if (!TestCallbackCounters(0, 0, 0, 0)) {
		fprintf(stderr, "test callback6 failed (step 3.2.%d)\n", i);
		return 6;
	    }
	}

	/*
	 * Detach a bunch of vertices and see if the callbacks happen.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.GetVertexRef("foo", v) || !v.IsValid() || v.IsDetached()) {
		fprintf(stderr, "test callback6 failed (step 4.0.%d)\n", i);
		return 6;
	    }
	    if (!n1.DetachVertex("foo")) {
		fprintf(stderr, "test callback6 failed (step 4.1.%d)\n", i);
		return 6;
	    }
	    if (!TestCallbackCounters(0, 0, 0, i+1)) {
		fprintf(stderr, "test callback6 failed (step 4.2,%d)\n", i);
		return 6;
	    }
	    if (!v.IsValid() || !v.IsDetached()) {
		fprintf(stderr, "test callback6 failed (step 4.3.%d)\n", i);
		return 6;
	    }
	}
	v = invalidVertex;

	/*
	 * Add another add vertex and detach vertex callback, and see if the
	 * number of callbacks happening is 2 when a vertex is added and
	 * detachd.
	 */

	s.DeclareCallback(E4_ECADDVERTEX, VertexAddCallback, (void *) 1);
	s.DeclareCallback(E4_ECDETVERTEX, VertexDetCallback, (void *) 1);

	/*
	 * Add a bunch of vertices and see if the callbacks happened.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.AddVertex("foo", E4_IOLAST, rank, 34)) {
		fprintf(stderr, "test callback6 failed (step 5.1.%d)\n", i);
		return 6;
	    }

	    /*
	     * Callbacks for addvertex wont happen unless the vertex is
	     * returned to the user program.
	     */

	    if (!TestCallbackCounters(0, 0, 0, 32)) {
		fprintf(stderr, "test callback6 failed (step 5.2.%d)\n", i);
		return 6;
	    }
	}

	/*
	 * Detach a bunch of vertices and see if the callbacks happen.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.GetVertexRef("foo", v) || !v.IsValid() || v.IsDetached()) {
		fprintf(stderr, "test callback6 failed (step 6.0.%d)\n", i);
		return 6;
	    }
	    if (!n1.DetachVertex("foo")) {
		fprintf(stderr, "test callback6 failed (step 6.1.%d)\n", i);
		return 6;
	    }
	    if (!TestCallbackCounters(0, 0, 0, 32+((i+1)*2))) {
		fprintf(stderr, "test callback6 failed (step 6.2,%d)\n", i);
		return 6;
	    }
	    if (!v.IsValid() || !v.IsDetached()) {
		fprintf(stderr, "test callback6 failed (step 6.3.%d)\n", i);
		return 6;
	    }
	}
	v = invalidVertex;

	/*
	 * Delete the add vertex and detach vertex callbacks just added,
	 * and check that the number of callbacks per add vertex and detach
	 * vertex event goes back to 1.
	 */

	s.DeleteCallback(E4_ECADDVERTEX, VertexAddCallback, (void *) 1);
	s.DeleteCallback(E4_ECDETVERTEX, VertexDetCallback, (void *) 1);

	/*
	 * Add a bunch of vertices and see if the callbacks happened.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.AddVertexRef("foo", E4_IOLAST, rank, 34, v)) {
		fprintf(stderr, "test callback6 failed (step 7.1.%d)\n", i);
		return 6;
	    }
	    if (!TestCallbackCounters(0, 0, i+1, 96)) {
		fprintf(stderr, "test callback6 failed (step 7.2.%d)\n", i);
		return 6;
	    }
	}

	/*
	 * Detach a bunch of vertices and see if the callbacks happen.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.GetVertexRef("foo", v) || !v.IsValid() || v.IsDetached()) {
		fprintf(stderr, "test callback6 failed (step 8.0.%d)\n", i);
		return 6;
	    }
	    if (!n1.DetachVertex("foo")) {
		fprintf(stderr, "test callback6 failed (step 8.1.%d)\n", i);
		return 6;
	    }
	    if (!TestCallbackCounters(0, 0, 32, 96+(i+1))) {
		fprintf(stderr, "test callback6 failed (step 8.2,%d)\n", i);
		return 6;
	    }
	    if (!v.IsValid() || !v.IsDetached()) {
		fprintf(stderr, "test callback6 failed (step 8.1.%d)\n", i);
		return 6;
	    }
	}
	v = invalidVertex;

	/*
	 * Same exercise except use n.DetachVertexByRank to detach the
	 * vertices, and see if the detach vertex callback happens.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.AddVertex("foo", E4_IOLAST, rank, 34)) {
		fprintf(stderr, "test callback6 failed (step 9.1.%d)\n", i);
		return 6;
	    }
	    if (!TestCallbackCounters(0, 0, 32, 128)) {
		fprintf(stderr, "test callback6 failed (step 9.2.%d)\n", i);
		return 6;
	    }
	}

	for (i = 0; i < 32; i++) {
	    if (!n1.GetVertexRefByRank(1, v) ||
		!v.IsValid() || v.IsDetached()) {
		fprintf(stderr, "test callback6 failed (step 10.0.%d)\n", i);
		return 6;
	    }
	    if (!n1.DetachVertexByRank(1)) {
		fprintf(stderr, "test callback6 failed (step 10.1.%d)\n", i);
		return 6;
	    }
	    if (!TestCallbackCounters(0, 0, 32, 128+(i+1))) {
		fprintf(stderr, "test callback6 failed (step 10.2,%d)\n", i);
		return 6;
	    }
	    if (!v.IsValid() || !v.IsDetached()) {
		fprintf(stderr, "test callback6 failed (step 10.3.%d)\n", i);
		return 6;
	    }
	}
	v = invalidVertex;

	/*
	 * Try removing non-existent callbacks, ensure that that has no
	 * impact on the installed callbacks.
	 */

	s.DeleteCallback(E4_ECADDVERTEX, VertexAddCallback, (void *) 3);
	s.DeleteCallback(E4_ECDETVERTEX, VertexDetCallback, (void *) 3);

	/*
	 * Add a bunch of vertices and see if the callbacks happened.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.AddVertexRef("foo", E4_IOLAST, rank, 34, v)) {
		fprintf(stderr, "test callback6 failed (step 11.1.%d)\n", i);
		return 6;
	    }
	    if (!TestCallbackCounters(0, 0, 32+(i+1), 160)) {
		fprintf(stderr, "test callback6 failed (step 11.2.%d)\n", i);
		return 6;
	    }
	}

	/*
	 * Detach a bunch of vertices and see if the callbacks happen.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.GetVertexRef("foo", v) || !v.IsValid() || v.IsDetached()) {
		fprintf(stderr, "test callback6 failed (step 12.0.%d)\n", i);
		return 6;
	    }
	    if (!n1.DetachVertex("foo")) {
		fprintf(stderr, "test callback6 failed (step 12.1.%d)\n", i);
		return 6;
	    }
	    if (!TestCallbackCounters(0, 0, 64, 160+(i+1))) {
		fprintf(stderr, "test callback6 failed (step 12.2,%d)\n", i);
		return 6;
	    }
	    if (!v.IsValid() || !v.IsDetached()) {
		fprintf(stderr, "test callback6 failed (step 12.3.%d)\n", i);
		return 6;
	    }
	}
	v = invalidVertex;

	/*
	 * Add a bunch of vertices and see if the callbacks happened.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.AddVertexRef("foo", E4_IOLAST, rank, 34, v)) {
		fprintf(stderr, "test callback6 failed (step 13.1.%d)\n", i);
		return 6;
	    }
	    if (!TestCallbackCounters(0, 0, 64+(i+1), 192)) {
		fprintf(stderr, "test callback6 failed (step 13.2.%d)\n", i);
		return 6;
	    }
	}

	/*
	 * Detach the node and no vertex detach callbacks happen.
	 */

	n1.Detach();

	if (!TestCallbackCounters(0, 0, 96, 192)) {
	    fprintf(stderr, "test callback6 failed (step 14), %d %d\n",
		    cbaddvertex, cbdetvertex);
	    return 6;
	}

	s.Delete();
    }

    if (!TestCallbackCounters(0, 0, 96, 192)) {
	fprintf(stderr, "test callback6 failed (step 15)\n");
	return 6;
    }

    return 0;
}

static int
test_callback7()
{
    e4_Node n1;
    e4_Vertex v, v1, v2;
    int rank = 0, i = 0;

    /*
     * Test that the storage is set to locked during callbacks.
     */

    ResetCallbackCounters();
    {
	e4_Storage s;

	if (!clean_storage("foo.db", s)) {
	    fprintf(stderr, "test callback7 failed (step 0)\n");
	    return 7;
	}
	if (!TestCallbackCounters(0, 0, 0, 0)) {
	    fprintf(stderr, "test callback7 failed (step 1)\n");
	    return 7;
	}

	s.DeclareCallback(E4_ECADDNODE, NodeCLAddCallback, (void *) 0);
	s.DeclareCallback(E4_ECDETNODE, NodeCLDetCallback, (void *) 0);
	s.DeclareCallback(E4_ECADDVERTEX, VertexCLAddCallback, (void *) 0);
	s.DeclareCallback(E4_ECDETVERTEX, VertexCLDetCallback, (void *) 0);
	s.DeclareCallback(E4_ECMODVERTEX, VertexCLModCallback, (void *) 0);

	if (!s.GetRootNode(n1) || !n1.IsValid() || !n1.IsRoot()) {
	    fprintf(stderr, "test callback7 failed (step 2)\n");
	    return 7;
	}

	/*
	 * Getting the root node does not cause a callback.
	 */

	if (!TestCallbackCounters(0, 0, 0, 0)) {
	    fprintf(stderr, "test callback7 failed (step 3)\n");
	    return 7;
	}
	if (!n1.Detach() || !n1.IsValid() || !n1.IsDetached()) {
	    fprintf(stderr, "test callback7 failed (step 4)\n");
	    return 7;
	}

	/*
	 * The root node starts out detached, so detaching it is a no-op
	 * and doesn't cause callbacks.
	 */

	if (!TestCallbackCounters(0, 0, 0, 0)) {
	    fprintf(stderr, "test callback7 failed (step 5)\n");
	    return 7;
	}

	/*
	 * Add a bunch of vertices and see if the callbacks happened.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.AddVertex("foo", E4_IOLAST, rank, 34)) {
		fprintf(stderr, "test callback7 failed (step 5.1.%d)\n", i);
		return 7;
	    }
	    if (!TestCallbackCounters(0, 0, 0, 0)) {
		fprintf(stderr, "test callback7 failed (step 5.2.%d)\n", i);
		return 7;
	    }
	}

	/*
	 * Add a bunch of vertices and see if the callbacks happened.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.AddVertexRef("foo", E4_IOLAST, rank, 34, v)) {
		fprintf(stderr, "test callback7 failed (step 6.1.%d)\n", i);
		return 7;
	    }
	    if (!TestCallbackCounters(0, 0, i+1, 0)) {
		fprintf(stderr, "test callback7 failed (step 6.2.%d)\n", i);
		return 7;
	    }
	}

	/*
	 * Grab a vertex and modify it, see if the callback happens.
	 */

	if (!n1.GetVertexRefByRank(11, v1) || !v1.IsValid()) {
	    fprintf(stderr, "test callback7 failed (step 7)\n");
	    return 7;
	}
	if (!v1.Set((float) 34.45)) {
	    fprintf(stderr, "test callback7 failed (step 8)\n");
	    return 7;
	}
	if (cbmodvertex != 1) {
	    fprintf(stderr, "test callback7 failed (step 9)\n");
	    return 7;
	}
	v1 = invalidVertex;

	/*
	 * Detach 32 vertices but the program doesn't reference them
	 * directly -- no detach vertex callbacks should happen:
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.DetachVertexByRank(1)) {
		fprintf(stderr, "test callback7 failed (step 10.0.%d)\n", i);
		return 7;
	    }
	    if (!TestCallbackCounters(0, 0, 32, 0)) {
		fprintf(stderr, "test callback7 failed (step 10.1.%d)\n", i);
		return 7;
	    }
	}

	/*
	 * Now detach the next 32 vertices and hold a reference to each.
	 * In this case the detach callback should happen on each vertex.
	 */

	for (i = 0; i < 32; i++) {
	    if (!n1.GetVertexRefByRank(1, v1) || !v1.IsValid() ||
		v1.IsDetached()) {
		fprintf(stderr, "test callback7 failed (step 11.0.%d)\n", i);
		return 7;
	    }
	    if (!n1.DetachVertexByRank(1)) {
		fprintf(stderr, "test callback7 failed (step 11.1.%d)\n", i);
		return 7;
	    }
	    if (!v1.IsDetached() || !v1.IsValid()) {
		fprintf(stderr, "test callback7 failed (step 11.2.%d)\n", i);
		return 7;
	    }
	    if (!TestCallbackCounters(0, 0, 32, i+1)) {
		fprintf(stderr, "test callback7 failed (step 11.3.%d)\n", i);
		return 7;
	    }
	}

	s.Delete();

	if (!TestCallbackCounters(0, 0, 32, 32)) {
	    fprintf(stderr, "test callback7 failed (step 12)\n");
	    return 7;
	}
    }

    return 0;
}

static int
test_callback8()
{
    e4_Node n1, n2, n3;
    e4_Vertex v;
    int rank = 0, i = 0;

    /*
     * Test delivery of callbacks on referenced items.
     */

    ResetCallbackCounters();
    {
	e4_Storage s;

	if (!clean_storage("foo.db", s)) {
	    fprintf(stderr, "test callback8 failed (step 0)\n");
	    return 8;
	}
	if (!TestCallbackCounters(0, 0, 0, 0)) {
	    fprintf(stderr, "test callback8 failed (step 1)\n");
	    return 8;
	}
	s.DeclareCallback(E4_ECDETNODE, NodeCLDetCallback, (void *) 0);
	s.DeclareCallback(E4_ECDETVERTEX, VertexCLDetCallback, (void *) 0);

	if (!s.GetRootNode(n1) || !n1.IsValid() || !n1.IsRoot()) {
	    fprintf(stderr, "test callback8 failed (step 2)\n");
	    return 8;
	}
	if (!n1.AddNode("n2", E4_IOLAST, rank, n2) || !n2.IsValid() ||
	    n2.IsRoot() || n2.IsDetached()) {
	    fprintf(stderr, "test callback8 failed (step 3)\n");
	    return 8;
	}
	if (!n2.AddVertexRef("v", E4_IOLAST, rank, 12, v) || !v.IsValid() ||
	    v.IsDetached()) {
	    fprintf(stderr, "test callback8 failed (step 4.0)\n");
	    return 8;
	}
	if (v.IsDetached() || !v.GetNode(n3) || !n3.IsValid() || (n3 != n2)) {
	    fprintf(stderr, "test callback8 failed (step 4.1)\n");
	    return 8;
	}
	n2 = invalidNode; n3 = invalidNode;
	if (!n1.DetachVertex("n2") || (n1.VertexCount() != 0)) {
	    fprintf(stderr, "test callback8 failed (step 5)\n");
	    return 8;
	}
	if (!TestCallbackCounters(0, 0, 0, 1)) {
	    fprintf(stderr, "test callback8 failed (step 6.0)\n");
	    return 8;
	}
	if (!v.IsValid() || !v.IsDetached()) {
	    fprintf(stderr, "test callback8 failed (step 6.1)\n");
	    return 8;
	}

	s.Delete();

	if (!TestCallbackCounters(0, 0, 0, 1)) {
	    fprintf(stderr, "test callback8 failed (step 7)\n");
	    return 8;
	}
    }

    return 0;
}

int
test_callback()
{
    int result = 0;

    fprintf(stderr, "Running callback tests: ");

    result = test_callback1();
    if (result != 0) {
	return result;
    }
    fprintf(stderr, ".");
    result = test_callback2();
    if (result != 0) {
        return result;
    }
    fprintf(stderr, ".");

    /*
     * Tests test_callback3 and test_callback4 were removed.
     */

    result = test_callback5();
    if (result != 0) {
        return result;
    }
    fprintf(stderr, ".");
    result = test_callback6();
    if (result != 0) {
        return result;
    }
    fprintf(stderr, ".");
    result = test_callback7();
    if (result != 0) {
	return result;
    }
    fprintf(stderr, ".");
    result = test_callback8();
    if (result != 0) {
	return result;
    }
    fprintf(stderr, ".\n");
    
    return 0;
}
