root/usr/src/test/os-tests/tests/libtopo/digraph-test.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2020 Joyent, Inc.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <libnvpair.h>
#include <string.h>
#include <stropts.h>
#include <unistd.h>
#include <fm/libtopo.h>
#include <sys/debug.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/varargs.h>


#define TEST_HOME               "/opt/os-tests/tests/libtopo/"
#define TEST_XML_IN             "digraph-test-in.xml"
#define TEST_XML_IN_BADSCHEME   "digraph-test-in-badscheme.xml"
#define TEST_XML_IN_BADNUM      "digraph-test-in-badnum.xml"
#define TEST_XML_IN_BADEDGE     "digraph-test-in-badedge.xml"
#define TEST_XML_IN_BADELEMENT  "digraph-test-in-badelement.xml"
#define TEST_GRAPH_SZ           7
#define TEST_XML_OUT_DIR        "/var/tmp"
#define TEST_XML_OUT_PREFIX     "digraph-test-out"

static const char *pname;

extern int topo_hdl_errno(topo_hdl_t *);

/*
 * Generate an ISO 8601 timestamp
 */
static void
get_timestamp(char *buf, size_t bufsize)
{
        time_t utc_time;
        struct tm *p_tm;

        (void) time(&utc_time);
        p_tm = localtime(&utc_time);

        (void) strftime(buf, bufsize, "%FT%TZ", p_tm);
}

/* PRINTFLIKE1 */
static void
logmsg(const char *format, ...)
{
        char timestamp[128];
        va_list ap;

        get_timestamp(timestamp, sizeof (timestamp));
        (void) fprintf(stdout, "%s ", timestamp);
        va_start(ap, format);
        (void) vfprintf(stdout, format, ap);
        va_end(ap);
        (void) fprintf(stdout, "\n");
        (void) fflush(stdout);
}

static topo_digraph_t *
test_deserialize(topo_hdl_t *thp, const char *path)
{
        struct stat statbuf = { 0 };
        char *buf = NULL;
        int fd = -1;
        topo_digraph_t *tdg = NULL;

        logmsg("\tOpening test XML topology");
        if ((fd = open(path, O_RDONLY)) < 0) {
                logmsg("\tfailed to open %s (%s)", path, strerror(errno));
                goto out;
        }
        if (fstat(fd, &statbuf) != 0) {
                logmsg("\tfailed to stat %s (%s)", path, strerror(errno));
                goto out;
        }
        if ((buf = malloc(statbuf.st_size)) == NULL) {
                logmsg("\tfailed to alloc read buffer: (%s)", strerror(errno));
                goto out;
        }
        if (read(fd, buf, statbuf.st_size) != statbuf.st_size) {
                logmsg("\tfailed to read file: (%s)", strerror(errno));
                goto out;
        }

        logmsg("\tDeserializing XML topology");
        tdg = topo_digraph_deserialize(thp, buf, statbuf.st_size);
        if (tdg == NULL) {
                logmsg("\ttopo_digraph_deserialize() failed!");
                goto out;
        }
        logmsg("\ttopo_digraph_deserialize() succeeded");
out:
        free(buf);
        if (fd > 0) {
                (void) close(fd);
        }
        return (tdg);
}

struct cb_arg {
        topo_vertex_t   **vertices;
};

static int
test_paths_cb(topo_hdl_t *thp, topo_vertex_t *vtx, boolean_t last_vtx,
    void *arg)
{
        struct cb_arg *cbarg = arg;
        uint_t idx = topo_node_instance(topo_vertex_node(vtx));

        cbarg->vertices[idx] = vtx;

        return (TOPO_WALK_NEXT);
}

static int
test_paths(topo_hdl_t *thp, topo_digraph_t *tdg)
{
        topo_vertex_t *vertices[TEST_GRAPH_SZ];
        struct cb_arg cbarg = { 0 };
        int ret = -1;
        topo_path_t **paths;
        uint_t np;

        cbarg.vertices = vertices;
        if (topo_vertex_iter(thp, tdg, test_paths_cb, &cbarg) != 0) {
                logmsg("\tfailed to iterate over graph vertices");
                goto out;
        }

        logmsg("\tCalculating number of paths between node 0 and node 4");
        if (topo_digraph_paths(thp, tdg, vertices[0], vertices[4], &paths,
            &np) < 0) {
                logmsg("\ttopo_digraph_paths() failed");
                goto out;
        }
        if (np != 2) {
                logmsg("\t%d paths found (expected 2)", np);
                goto out;
        }
        for (uint_t i = 0; i < np; i++) {
                topo_path_destroy(thp, paths[i]);
        }
        topo_hdl_free(thp, paths, np * sizeof (topo_path_t *));

        logmsg("\tCalculating number of paths between node 6 and node 4");
        if (topo_digraph_paths(thp, tdg, vertices[6], vertices[4], &paths,
            &np) < 0) {
                logmsg("\ttopo_digraph_paths() failed");
                goto out;
        }
        if (np != 1) {
                logmsg("\t%d paths found (expected 1)", np);
                goto out;
        }
        for (uint_t i = 0; i < np; i++) {
                topo_path_destroy(thp, paths[i]);
        }
        topo_hdl_free(thp, paths, np * sizeof (topo_path_t *));

        logmsg("\tCalculating number of paths between node 5 and node 1");
        if (topo_digraph_paths(thp, tdg, vertices[5], vertices[1], &paths,
            &np) < 0) {
                logmsg("\ttopo_digraph_paths() failed");
                goto out;
        }
        if (np != 0) {
                logmsg("\t%d paths found (expected 0)", np);
                goto out;
        }
        ret = 0;

out:
        if (np > 0) {
                for (uint_t i = 0; i < np; i++) {
                        topo_path_destroy(thp, paths[i]);
                }
                topo_hdl_free(thp, paths, np * sizeof (topo_path_t *));
        }
        return (ret);
}

static int
test_serialize(topo_hdl_t *thp, topo_digraph_t *tdg, const char *path)
{
        FILE *xml_out;

        if ((xml_out = fopen(path, "w")) == NULL) {
                logmsg("\tfailed to open %s for writing (%s)",
                    strerror(errno));
                return (-1);
        }
        logmsg("\tSerializing topology to XML (%s)", path);
        if (topo_digraph_serialize(thp, tdg, xml_out) != 0) {
                logmsg("\ttopo_digraph_serialize() failed!");
                (void) fclose(xml_out);
                return (-1);
        }
        (void) fclose(xml_out);
        return (0);
}

int
main(int argc, char **argv)
{
        topo_hdl_t *thp = NULL;
        topo_digraph_t *tdg;
        char *root = "/", *out_path = NULL;
        boolean_t abort_on_exit = B_FALSE;
        int err, status = EXIT_FAILURE;

        pname = argv[0];

        /*
         * Setting DIGRAPH_TEST_CORE causes us to abort and dump core before
         * exiting.  This is useful for examining for memory leaks.
         */
        if (getenv("DIGRAPH_TEST_CORE") != NULL) {
                abort_on_exit = B_TRUE;
        }

        logmsg("Opening libtopo");
        if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) {
                logmsg("failed to get topo handle: %s", topo_strerror(err));
                goto out;
        }

        logmsg("TEST: Deserialize directed graph topology");
        if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN)) == NULL) {
                logmsg("FAIL");
                goto out;
        }
        logmsg("PASS");

        logmsg("TEST: Serialize directed graph topology");
        if ((out_path = tempnam(TEST_XML_OUT_DIR, TEST_XML_OUT_PREFIX)) ==
            NULL) {
                logmsg("\tFailed to create temporary file name under %s (%s)",
                    TEST_XML_OUT_DIR, strerror(errno));
                logmsg("FAIL");
                goto out;
        }
        if (test_serialize(thp, tdg, out_path) != 0) {
                logmsg("FAIL");
                goto out;
        }
        logmsg("PASS");

        logmsg("Closing libtopo");
        topo_close(thp);

        logmsg("Reopening libtopo");
        if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) {
                logmsg("failed to get topo handle: %s", topo_strerror(err));
                goto out;
        }

        logmsg("TEST: Deserialize directed graph topology (pass 2)");
        if ((tdg = test_deserialize(thp, out_path)) == NULL) {
                logmsg("FAIL");
                goto out;
        }
        logmsg("PASS");

        logmsg("TEST: Calculating paths between vertices");
        if (test_paths(thp, tdg) != 0) {
                logmsg("FAIL");
                goto out;
        }
        logmsg("PASS");

        logmsg("Closing libtopo");
        topo_close(thp);

        logmsg("Reopening libtopo");
        if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) {
                logmsg("failed to get topo handle: %s", topo_strerror(err));
                goto out;
        }

        /*
         * The following tests attempt to deserialize XML files that either
         * violate the DTD or contain invalid attribute values.
         *
         * The expection is that topo_digraph_deserialize() should fail
         * gracefully (i.e. not segfault) and topo_errno should be set.
         */
        logmsg("TEST: Deserialize directed graph topology (bad scheme)");
        if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADSCHEME)) !=
            NULL) {
                logmsg("FAIL");
                goto out;
        } else if (topo_hdl_errno(thp) == 0) {
                logmsg("\texpected topo_errno to be non-zero");
                logmsg("FAIL");
                goto out;
        } else {
                logmsg("PASS");
        }

        logmsg("TEST: Deserialize directed graph topology (bad number)");
        if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADNUM)) !=
            NULL) {
                logmsg("FAIL");
                goto out;
        } else if (topo_hdl_errno(thp) == 0) {
                logmsg("\texpected topo_errno to be non-zero");
                logmsg("FAIL");
                goto out;
        } else {
                logmsg("PASS");
        }

        logmsg("TEST: Deserialize directed graph topology (bad edge)");
        if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADEDGE)) !=
            NULL) {
                logmsg("FAIL");
                goto out;
        } else if (topo_hdl_errno(thp) == 0) {
                logmsg("\texpected topo_errno to be non-zero");
                logmsg("FAIL");
                goto out;
        } else {
                logmsg("PASS");
        }

        logmsg("TEST: Deserialize directed graph topology (bad element)");
        if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADELEMENT)) !=
            NULL) {
                logmsg("FAIL");
                goto out;
        } else if (topo_hdl_errno(thp) == 0) {
                logmsg("\texpected topo_errno to be non-zero");
                logmsg("FAIL");
                goto out;
        } else {
                logmsg("PASS");
        }

        /*
         * If any tests failed, we don't unlink the temp file, as its contents
         * may be useful for root-causing the test failure.
         */
        if (unlink(out_path) != 0) {
                logmsg("Failed to unlink temp file: %s (%s)", out_path,
                    strerror(errno));
        }
        status = EXIT_SUCCESS;
out:
        if (thp != NULL) {
                topo_close(thp);
        }
        if (out_path != NULL) {
                free(out_path);
        }
        logmsg("digraph tests %s",
            status == EXIT_SUCCESS ? "passed" : "failed");

        if (abort_on_exit) {
                abort();
        }
        return (status);
}