1*c5591576SRob Johnston /*
2*c5591576SRob Johnston  * This file and its contents are supplied under the terms of the
3*c5591576SRob Johnston  * Common Development and Distribution License ("CDDL"), version 1.0.
4*c5591576SRob Johnston  * You may only use this file in accordance with the terms of version
5*c5591576SRob Johnston  * 1.0 of the CDDL.
6*c5591576SRob Johnston  *
7*c5591576SRob Johnston  * A full copy of the text of the CDDL should have accompanied this
8*c5591576SRob Johnston  * source.  A copy of the CDDL is also available via the Internet at
9*c5591576SRob Johnston  * http://www.illumos.org/license/CDDL.
10*c5591576SRob Johnston  */
11*c5591576SRob Johnston 
12*c5591576SRob Johnston /*
13*c5591576SRob Johnston  * Copyright 2020 Joyent, Inc.
14*c5591576SRob Johnston  */
15*c5591576SRob Johnston 
16*c5591576SRob Johnston #include <stdio.h>
17*c5591576SRob Johnston #include <stdlib.h>
18*c5591576SRob Johnston #include <errno.h>
19*c5591576SRob Johnston #include <fcntl.h>
20*c5591576SRob Johnston #include <libnvpair.h>
21*c5591576SRob Johnston #include <string.h>
22*c5591576SRob Johnston #include <stropts.h>
23*c5591576SRob Johnston #include <unistd.h>
24*c5591576SRob Johnston #include <fm/libtopo.h>
25*c5591576SRob Johnston #include <sys/debug.h>
26*c5591576SRob Johnston #include <sys/stat.h>
27*c5591576SRob Johnston #include <sys/types.h>
28*c5591576SRob Johnston #include <sys/varargs.h>
29*c5591576SRob Johnston 
30*c5591576SRob Johnston 
31*c5591576SRob Johnston #define	TEST_HOME		"/opt/os-tests/tests/libtopo/"
32*c5591576SRob Johnston #define	TEST_XML_IN		"digraph-test-in.xml"
33*c5591576SRob Johnston #define	TEST_XML_IN_BADSCHEME	"digraph-test-in-badscheme.xml"
34*c5591576SRob Johnston #define	TEST_XML_IN_BADNUM	"digraph-test-in-badnum.xml"
35*c5591576SRob Johnston #define	TEST_XML_IN_BADEDGE	"digraph-test-in-badedge.xml"
36*c5591576SRob Johnston #define	TEST_XML_IN_BADELEMENT	"digraph-test-in-badelement.xml"
37*c5591576SRob Johnston #define	TEST_GRAPH_SZ		7
38*c5591576SRob Johnston #define	TEST_XML_OUT_DIR	"/var/tmp"
39*c5591576SRob Johnston #define	TEST_XML_OUT_PREFIX	"digraph-test-out"
40*c5591576SRob Johnston 
41*c5591576SRob Johnston static const char *pname;
42*c5591576SRob Johnston 
43*c5591576SRob Johnston extern int topo_hdl_errno(topo_hdl_t *);
44*c5591576SRob Johnston 
45*c5591576SRob Johnston /*
46*c5591576SRob Johnston  * Generate an ISO 8601 timestamp
47*c5591576SRob Johnston  */
48*c5591576SRob Johnston static void
get_timestamp(char * buf,size_t bufsize)49*c5591576SRob Johnston get_timestamp(char *buf, size_t bufsize)
50*c5591576SRob Johnston {
51*c5591576SRob Johnston 	time_t utc_time;
52*c5591576SRob Johnston 	struct tm *p_tm;
53*c5591576SRob Johnston 
54*c5591576SRob Johnston 	(void) time(&utc_time);
55*c5591576SRob Johnston 	p_tm = localtime(&utc_time);
56*c5591576SRob Johnston 
57*c5591576SRob Johnston 	(void) strftime(buf, bufsize, "%FT%TZ", p_tm);
58*c5591576SRob Johnston }
59*c5591576SRob Johnston 
60*c5591576SRob Johnston /* PRINTFLIKE1 */
61*c5591576SRob Johnston static void
logmsg(const char * format,...)62*c5591576SRob Johnston logmsg(const char *format, ...)
63*c5591576SRob Johnston {
64*c5591576SRob Johnston 	char timestamp[128];
65*c5591576SRob Johnston 	va_list ap;
66*c5591576SRob Johnston 
67*c5591576SRob Johnston 	get_timestamp(timestamp, sizeof (timestamp));
68*c5591576SRob Johnston 	(void) fprintf(stdout, "%s ", timestamp);
69*c5591576SRob Johnston 	va_start(ap, format);
70*c5591576SRob Johnston 	(void) vfprintf(stdout, format, ap);
71*c5591576SRob Johnston 	va_end(ap);
72*c5591576SRob Johnston 	(void) fprintf(stdout, "\n");
73*c5591576SRob Johnston 	(void) fflush(stdout);
74*c5591576SRob Johnston }
75*c5591576SRob Johnston 
76*c5591576SRob Johnston static topo_digraph_t *
test_deserialize(topo_hdl_t * thp,const char * path)77*c5591576SRob Johnston test_deserialize(topo_hdl_t *thp, const char *path)
78*c5591576SRob Johnston {
79*c5591576SRob Johnston 	struct stat statbuf = { 0 };
80*c5591576SRob Johnston 	char *buf = NULL;
81*c5591576SRob Johnston 	int fd = -1;
82*c5591576SRob Johnston 	topo_digraph_t *tdg = NULL;
83*c5591576SRob Johnston 
84*c5591576SRob Johnston 	logmsg("\tOpening test XML topology");
85*c5591576SRob Johnston 	if ((fd = open(path, O_RDONLY)) < 0) {
86*c5591576SRob Johnston 		logmsg("\tfailed to open %s (%s)", path, strerror(errno));
87*c5591576SRob Johnston 		goto out;
88*c5591576SRob Johnston 	}
89*c5591576SRob Johnston 	if (fstat(fd, &statbuf) != 0) {
90*c5591576SRob Johnston 		logmsg("\tfailed to stat %s (%s)", path, strerror(errno));
91*c5591576SRob Johnston 		goto out;
92*c5591576SRob Johnston 	}
93*c5591576SRob Johnston 	if ((buf = malloc(statbuf.st_size)) == NULL) {
94*c5591576SRob Johnston 		logmsg("\tfailed to alloc read buffer: (%s)", strerror(errno));
95*c5591576SRob Johnston 		goto out;
96*c5591576SRob Johnston 	}
97*c5591576SRob Johnston 	if (read(fd, buf, statbuf.st_size) != statbuf.st_size) {
98*c5591576SRob Johnston 		logmsg("\tfailed to read file: (%s)", strerror(errno));
99*c5591576SRob Johnston 		goto out;
100*c5591576SRob Johnston 	}
101*c5591576SRob Johnston 
102*c5591576SRob Johnston 	logmsg("\tDeserializing XML topology");
103*c5591576SRob Johnston 	tdg = topo_digraph_deserialize(thp, buf, statbuf.st_size);
104*c5591576SRob Johnston 	if (tdg == NULL) {
105*c5591576SRob Johnston 		logmsg("\ttopo_digraph_deserialize() failed!");
106*c5591576SRob Johnston 		goto out;
107*c5591576SRob Johnston 	}
108*c5591576SRob Johnston 	logmsg("\ttopo_digraph_deserialize() succeeded");
109*c5591576SRob Johnston out:
110*c5591576SRob Johnston 	free(buf);
111*c5591576SRob Johnston 	if (fd > 0) {
112*c5591576SRob Johnston 		(void) close(fd);
113*c5591576SRob Johnston 	}
114*c5591576SRob Johnston 	return (tdg);
115*c5591576SRob Johnston }
116*c5591576SRob Johnston 
117*c5591576SRob Johnston struct cb_arg {
118*c5591576SRob Johnston 	topo_vertex_t	**vertices;
119*c5591576SRob Johnston };
120*c5591576SRob Johnston 
121*c5591576SRob Johnston static int
test_paths_cb(topo_hdl_t * thp,topo_vertex_t * vtx,boolean_t last_vtx,void * arg)122*c5591576SRob Johnston test_paths_cb(topo_hdl_t *thp, topo_vertex_t *vtx, boolean_t last_vtx,
123*c5591576SRob Johnston     void *arg)
124*c5591576SRob Johnston {
125*c5591576SRob Johnston 	struct cb_arg *cbarg = arg;
126*c5591576SRob Johnston 	uint_t idx = topo_node_instance(topo_vertex_node(vtx));
127*c5591576SRob Johnston 
128*c5591576SRob Johnston 	cbarg->vertices[idx] = vtx;
129*c5591576SRob Johnston 
130*c5591576SRob Johnston 	return (TOPO_WALK_NEXT);
131*c5591576SRob Johnston }
132*c5591576SRob Johnston 
133*c5591576SRob Johnston static int
test_paths(topo_hdl_t * thp,topo_digraph_t * tdg)134*c5591576SRob Johnston test_paths(topo_hdl_t *thp, topo_digraph_t *tdg)
135*c5591576SRob Johnston {
136*c5591576SRob Johnston 	topo_vertex_t *vertices[TEST_GRAPH_SZ];
137*c5591576SRob Johnston 	struct cb_arg cbarg = { 0 };
138*c5591576SRob Johnston 	int ret = -1;
139*c5591576SRob Johnston 	topo_path_t **paths;
140*c5591576SRob Johnston 	uint_t np;
141*c5591576SRob Johnston 
142*c5591576SRob Johnston 	cbarg.vertices = vertices;
143*c5591576SRob Johnston 	if (topo_vertex_iter(thp, tdg, test_paths_cb, &cbarg) != 0) {
144*c5591576SRob Johnston 		logmsg("\tfailed to iterate over graph vertices");
145*c5591576SRob Johnston 		goto out;
146*c5591576SRob Johnston 	}
147*c5591576SRob Johnston 
148*c5591576SRob Johnston 	logmsg("\tCalculating number of paths between node 0 and node 4");
149*c5591576SRob Johnston 	if (topo_digraph_paths(thp, tdg, vertices[0], vertices[4], &paths,
150*c5591576SRob Johnston 	    &np) < 0) {
151*c5591576SRob Johnston 		logmsg("\ttopo_digraph_paths() failed");
152*c5591576SRob Johnston 		goto out;
153*c5591576SRob Johnston 	}
154*c5591576SRob Johnston 	if (np != 2) {
155*c5591576SRob Johnston 		logmsg("\t%d paths found (expected 2)", np);
156*c5591576SRob Johnston 		goto out;
157*c5591576SRob Johnston 	}
158*c5591576SRob Johnston 	for (uint_t i = 0; i < np; i++) {
159*c5591576SRob Johnston 		topo_path_destroy(thp, paths[i]);
160*c5591576SRob Johnston 	}
161*c5591576SRob Johnston 	topo_hdl_free(thp, paths, np * sizeof (topo_path_t *));
162*c5591576SRob Johnston 
163*c5591576SRob Johnston 	logmsg("\tCalculating number of paths between node 6 and node 4");
164*c5591576SRob Johnston 	if (topo_digraph_paths(thp, tdg, vertices[6], vertices[4], &paths,
165*c5591576SRob Johnston 	    &np) < 0) {
166*c5591576SRob Johnston 		logmsg("\ttopo_digraph_paths() failed");
167*c5591576SRob Johnston 		goto out;
168*c5591576SRob Johnston 	}
169*c5591576SRob Johnston 	if (np != 1) {
170*c5591576SRob Johnston 		logmsg("\t%d paths found (expected 1)", np);
171*c5591576SRob Johnston 		goto out;
172*c5591576SRob Johnston 	}
173*c5591576SRob Johnston 	for (uint_t i = 0; i < np; i++) {
174*c5591576SRob Johnston 		topo_path_destroy(thp, paths[i]);
175*c5591576SRob Johnston 	}
176*c5591576SRob Johnston 	topo_hdl_free(thp, paths, np * sizeof (topo_path_t *));
177*c5591576SRob Johnston 
178*c5591576SRob Johnston 	logmsg("\tCalculating number of paths between node 5 and node 1");
179*c5591576SRob Johnston 	if (topo_digraph_paths(thp, tdg, vertices[5], vertices[1], &paths,
180*c5591576SRob Johnston 	    &np) < 0) {
181*c5591576SRob Johnston 		logmsg("\ttopo_digraph_paths() failed");
182*c5591576SRob Johnston 		goto out;
183*c5591576SRob Johnston 	}
184*c5591576SRob Johnston 	if (np != 0) {
185*c5591576SRob Johnston 		logmsg("\t%d paths found (expected 0)", np);
186*c5591576SRob Johnston 		goto out;
187*c5591576SRob Johnston 	}
188*c5591576SRob Johnston 	ret = 0;
189*c5591576SRob Johnston 
190*c5591576SRob Johnston out:
191*c5591576SRob Johnston 	if (np > 0) {
192*c5591576SRob Johnston 		for (uint_t i = 0; i < np; i++) {
193*c5591576SRob Johnston 			topo_path_destroy(thp, paths[i]);
194*c5591576SRob Johnston 		}
195*c5591576SRob Johnston 		topo_hdl_free(thp, paths, np * sizeof (topo_path_t *));
196*c5591576SRob Johnston 	}
197*c5591576SRob Johnston 	return (ret);
198*c5591576SRob Johnston }
199*c5591576SRob Johnston 
200*c5591576SRob Johnston static int
test_serialize(topo_hdl_t * thp,topo_digraph_t * tdg,const char * path)201*c5591576SRob Johnston test_serialize(topo_hdl_t *thp, topo_digraph_t *tdg, const char *path)
202*c5591576SRob Johnston {
203*c5591576SRob Johnston 	FILE *xml_out;
204*c5591576SRob Johnston 
205*c5591576SRob Johnston 	if ((xml_out = fopen(path, "w")) == NULL) {
206*c5591576SRob Johnston 		logmsg("\tfailed to open %s for writing (%s)",
207*c5591576SRob Johnston 		    strerror(errno));
208*c5591576SRob Johnston 		return (-1);
209*c5591576SRob Johnston 	}
210*c5591576SRob Johnston 	logmsg("\tSerializing topology to XML (%s)", path);
211*c5591576SRob Johnston 	if (topo_digraph_serialize(thp, tdg, xml_out) != 0) {
212*c5591576SRob Johnston 		logmsg("\ttopo_digraph_serialize() failed!");
213*c5591576SRob Johnston 		(void) fclose(xml_out);
214*c5591576SRob Johnston 		return (-1);
215*c5591576SRob Johnston 	}
216*c5591576SRob Johnston 	(void) fclose(xml_out);
217*c5591576SRob Johnston 	return (0);
218*c5591576SRob Johnston }
219*c5591576SRob Johnston 
220*c5591576SRob Johnston int
main(int argc,char ** argv)221*c5591576SRob Johnston main(int argc, char **argv)
222*c5591576SRob Johnston {
223*c5591576SRob Johnston 	topo_hdl_t *thp = NULL;
224*c5591576SRob Johnston 	topo_digraph_t *tdg;
225*c5591576SRob Johnston 	char *root = "/", *out_path = NULL;
226*c5591576SRob Johnston 	boolean_t abort_on_exit = B_FALSE;
227*c5591576SRob Johnston 	int err, status = EXIT_FAILURE;
228*c5591576SRob Johnston 
229*c5591576SRob Johnston 	pname = argv[0];
230*c5591576SRob Johnston 
231*c5591576SRob Johnston 	/*
232*c5591576SRob Johnston 	 * Setting DIGRAPH_TEST_CORE causes us to abort and dump core before
233*c5591576SRob Johnston 	 * exiting.  This is useful for examining for memory leaks.
234*c5591576SRob Johnston 	 */
235*c5591576SRob Johnston 	if (getenv("DIGRAPH_TEST_CORE") != NULL) {
236*c5591576SRob Johnston 		abort_on_exit = B_TRUE;
237*c5591576SRob Johnston 	}
238*c5591576SRob Johnston 
239*c5591576SRob Johnston 	logmsg("Opening libtopo");
240*c5591576SRob Johnston 	if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) {
241*c5591576SRob Johnston 		logmsg("failed to get topo handle: %s", topo_strerror(err));
242*c5591576SRob Johnston 		goto out;
243*c5591576SRob Johnston 	}
244*c5591576SRob Johnston 
245*c5591576SRob Johnston 	logmsg("TEST: Deserialize directed graph topology");
246*c5591576SRob Johnston 	if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN)) == NULL) {
247*c5591576SRob Johnston 		logmsg("FAIL");
248*c5591576SRob Johnston 		goto out;
249*c5591576SRob Johnston 	}
250*c5591576SRob Johnston 	logmsg("PASS");
251*c5591576SRob Johnston 
252*c5591576SRob Johnston 	logmsg("TEST: Serialize directed graph topology");
253*c5591576SRob Johnston 	if ((out_path = tempnam(TEST_XML_OUT_DIR, TEST_XML_OUT_PREFIX)) ==
254*c5591576SRob Johnston 	    NULL) {
255*c5591576SRob Johnston 		logmsg("\tFailed to create temporary file name under %s (%s)",
256*c5591576SRob Johnston 		    TEST_XML_OUT_DIR, strerror(errno));
257*c5591576SRob Johnston 		logmsg("FAIL");
258*c5591576SRob Johnston 		goto out;
259*c5591576SRob Johnston 	}
260*c5591576SRob Johnston 	if (test_serialize(thp, tdg, out_path) != 0) {
261*c5591576SRob Johnston 		logmsg("FAIL");
262*c5591576SRob Johnston 		goto out;
263*c5591576SRob Johnston 	}
264*c5591576SRob Johnston 	logmsg("PASS");
265*c5591576SRob Johnston 
266*c5591576SRob Johnston 	logmsg("Closing libtopo");
267*c5591576SRob Johnston 	topo_close(thp);
268*c5591576SRob Johnston 
269*c5591576SRob Johnston 	logmsg("Reopening libtopo");
270*c5591576SRob Johnston 	if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) {
271*c5591576SRob Johnston 		logmsg("failed to get topo handle: %s", topo_strerror(err));
272*c5591576SRob Johnston 		goto out;
273*c5591576SRob Johnston 	}
274*c5591576SRob Johnston 
275*c5591576SRob Johnston 	logmsg("TEST: Deserialize directed graph topology (pass 2)");
276*c5591576SRob Johnston 	if ((tdg = test_deserialize(thp, out_path)) == NULL) {
277*c5591576SRob Johnston 		logmsg("FAIL");
278*c5591576SRob Johnston 		goto out;
279*c5591576SRob Johnston 	}
280*c5591576SRob Johnston 	logmsg("PASS");
281*c5591576SRob Johnston 
282*c5591576SRob Johnston 	logmsg("TEST: Calculating paths between vertices");
283*c5591576SRob Johnston 	if (test_paths(thp, tdg) != 0) {
284*c5591576SRob Johnston 		logmsg("FAIL");
285*c5591576SRob Johnston 		goto out;
286*c5591576SRob Johnston 	}
287*c5591576SRob Johnston 	logmsg("PASS");
288*c5591576SRob Johnston 
289*c5591576SRob Johnston 	logmsg("Closing libtopo");
290*c5591576SRob Johnston 	topo_close(thp);
291*c5591576SRob Johnston 
292*c5591576SRob Johnston 	logmsg("Reopening libtopo");
293*c5591576SRob Johnston 	if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) {
294*c5591576SRob Johnston 		logmsg("failed to get topo handle: %s", topo_strerror(err));
295*c5591576SRob Johnston 		goto out;
296*c5591576SRob Johnston 	}
297*c5591576SRob Johnston 
298*c5591576SRob Johnston 	/*
299*c5591576SRob Johnston 	 * The following tests attempt to deserialize XML files that either
300*c5591576SRob Johnston 	 * violate the DTD or contain invalid attribute values.
301*c5591576SRob Johnston 	 *
302*c5591576SRob Johnston 	 * The expection is that topo_digraph_deserialize() should fail
303*c5591576SRob Johnston 	 * gracefully (i.e. not segfault) and topo_errno should be set.
304*c5591576SRob Johnston 	 */
305*c5591576SRob Johnston 	logmsg("TEST: Deserialize directed graph topology (bad scheme)");
306*c5591576SRob Johnston 	if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADSCHEME)) !=
307*c5591576SRob Johnston 	    NULL) {
308*c5591576SRob Johnston 		logmsg("FAIL");
309*c5591576SRob Johnston 		goto out;
310*c5591576SRob Johnston 	} else if (topo_hdl_errno(thp) == 0) {
311*c5591576SRob Johnston 		logmsg("\texpected topo_errno to be non-zero");
312*c5591576SRob Johnston 		logmsg("FAIL");
313*c5591576SRob Johnston 		goto out;
314*c5591576SRob Johnston 	} else {
315*c5591576SRob Johnston 		logmsg("PASS");
316*c5591576SRob Johnston 	}
317*c5591576SRob Johnston 
318*c5591576SRob Johnston 	logmsg("TEST: Deserialize directed graph topology (bad number)");
319*c5591576SRob Johnston 	if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADNUM)) !=
320*c5591576SRob Johnston 	    NULL) {
321*c5591576SRob Johnston 		logmsg("FAIL");
322*c5591576SRob Johnston 		goto out;
323*c5591576SRob Johnston 	} else if (topo_hdl_errno(thp) == 0) {
324*c5591576SRob Johnston 		logmsg("\texpected topo_errno to be non-zero");
325*c5591576SRob Johnston 		logmsg("FAIL");
326*c5591576SRob Johnston 		goto out;
327*c5591576SRob Johnston 	} else {
328*c5591576SRob Johnston 		logmsg("PASS");
329*c5591576SRob Johnston 	}
330*c5591576SRob Johnston 
331*c5591576SRob Johnston 	logmsg("TEST: Deserialize directed graph topology (bad edge)");
332*c5591576SRob Johnston 	if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADEDGE)) !=
333*c5591576SRob Johnston 	    NULL) {
334*c5591576SRob Johnston 		logmsg("FAIL");
335*c5591576SRob Johnston 		goto out;
336*c5591576SRob Johnston 	} else if (topo_hdl_errno(thp) == 0) {
337*c5591576SRob Johnston 		logmsg("\texpected topo_errno to be non-zero");
338*c5591576SRob Johnston 		logmsg("FAIL");
339*c5591576SRob Johnston 		goto out;
340*c5591576SRob Johnston 	} else {
341*c5591576SRob Johnston 		logmsg("PASS");
342*c5591576SRob Johnston 	}
343*c5591576SRob Johnston 
344*c5591576SRob Johnston 	logmsg("TEST: Deserialize directed graph topology (bad element)");
345*c5591576SRob Johnston 	if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADELEMENT)) !=
346*c5591576SRob Johnston 	    NULL) {
347*c5591576SRob Johnston 		logmsg("FAIL");
348*c5591576SRob Johnston 		goto out;
349*c5591576SRob Johnston 	} else if (topo_hdl_errno(thp) == 0) {
350*c5591576SRob Johnston 		logmsg("\texpected topo_errno to be non-zero");
351*c5591576SRob Johnston 		logmsg("FAIL");
352*c5591576SRob Johnston 		goto out;
353*c5591576SRob Johnston 	} else {
354*c5591576SRob Johnston 		logmsg("PASS");
355*c5591576SRob Johnston 	}
356*c5591576SRob Johnston 
357*c5591576SRob Johnston 	/*
358*c5591576SRob Johnston 	 * If any tests failed, we don't unlink the temp file, as its contents
359*c5591576SRob Johnston 	 * may be useful for root-causing the test failure.
360*c5591576SRob Johnston 	 */
361*c5591576SRob Johnston 	if (unlink(out_path) != 0) {
362*c5591576SRob Johnston 		logmsg("Failed to unlink temp file: %s (%s)", out_path,
363*c5591576SRob Johnston 		    strerror(errno));
364*c5591576SRob Johnston 	}
365*c5591576SRob Johnston 	status = EXIT_SUCCESS;
366*c5591576SRob Johnston out:
367*c5591576SRob Johnston 	if (thp != NULL) {
368*c5591576SRob Johnston 		topo_close(thp);
369*c5591576SRob Johnston 	}
370*c5591576SRob Johnston 	if (out_path != NULL) {
371*c5591576SRob Johnston 		free(out_path);
372*c5591576SRob Johnston 	}
373*c5591576SRob Johnston 	logmsg("digraph tests %s",
374*c5591576SRob Johnston 	    status == EXIT_SUCCESS ? "passed" : "failed");
375*c5591576SRob Johnston 
376*c5591576SRob Johnston 	if (abort_on_exit) {
377*c5591576SRob Johnston 		abort();
378*c5591576SRob Johnston 	}
379*c5591576SRob Johnston 	return (status);
380*c5591576SRob Johnston }