summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorShane Kerr <shane@isc.org>2007-11-16 11:04:12 +0000
committerShane Kerr <shane@isc.org>2007-11-16 11:04:12 +0000
commit6e999c3c8a96354e088da8a5a35c9ab92f419ec7 (patch)
tree375ea7f6ca88c7732d4e675d2d52b3f3693e21ec /tests
parentb9ced0286885a375857acb742091a10a787b07c7 (diff)
downloadisc-dhcp-6e999c3c8a96354e088da8a5a35c9ab92f419ec7.tar.gz
Unit test framework now added.
See RT ticket #17223 for more.
Diffstat (limited to 'tests')
-rw-r--r--tests/HOWTO-unit-test153
-rw-r--r--tests/Makefile.am33
-rw-r--r--tests/Makefile.in406
-rw-r--r--tests/t_api.c823
-rw-r--r--tests/t_api_dhcp.c44
-rw-r--r--tests/unit_test_sample.c25
6 files changed, 1484 insertions, 0 deletions
diff --git a/tests/HOWTO-unit-test b/tests/HOWTO-unit-test
new file mode 100644
index 00000000..b38185c9
--- /dev/null
+++ b/tests/HOWTO-unit-test
@@ -0,0 +1,153 @@
+Introduction
+------------
+
+In DHCP, a unit test exercises a particular piece of code in
+isolation. There is a separate unit test per module or API. Each unit
+test lives in a directory beneath the code it is designed to exercise.
+So, we have:
+
+ client/tests/
+ common/tests/
+ dhcpctl/tests/
+
+And so on.
+
+Ideally each function would be invoked with every possible type of
+input, and each branch of every function would be checked. In practice
+we try to be a bit more pragmatic, and target the most basic
+operations, as well tricky code, and areas we have seen bugs in the
+past.
+
+
+Running Unit Tests
+------------------
+
+In order to run the unit tests for DHCP, use:
+
+$ make check
+
+This will run all of the unit tests.
+
+You can run a single test by going to the appropriate test directory
+and invoking the test directly:
+
+$ cd common/tests
+$ make test_alloc
+$ ./test_alloc
+
+There are also a number of options that you can use when running a
+test. To see these, use the "-u" flag on the program.
+
+
+Adding a New Unit Test
+----------------------
+
+To add an additional test to an existing test program, you must create
+a function for the new test in the C source file:
+
+static void
+mynewtest(void) {
+ static const char *test_desc = "describe the test";
+
+ t_assert("mynewtest", 1, T_REQUIRED, test_desc);
+
+ /* ... test code ... */
+
+ t_result(T_PASS);
+}
+
+Then add this function to the T_testlist[] array in the file:
+
+testspec_t T_testlist[] = {
+ ...
+ { mynewtest, "some new test" },
+ { NULL, NULL }
+};
+
+Then you should be able to compile and run your new test.
+
+
+Adding a New Unit Test Program
+------------------------------
+
+To add a new program, such as when a new module is added, you can copy
+the "unit_test_sample.c" file (in this directory) to a new name, add
+the new file as a target in Makefile.am, and begin adding tests. Do
+not forget to add it to CVS via "cvs add".
+
+If there is no "tests" directory for a given subdirectory, then one
+must be created. This can be done by:
+
+1. Creating the directory:
+
+ $ mkdir $subdir/tests
+ $ cvs add tests
+
+2. Adding the subdirectory to the build system:
+
+ Add to $subdir/Makefile.am:
+
+ SUBDIRS = tests
+
+ Add to the AC_OUTPUT macro in configure.ac:
+
+ $subdir/tests/Makefile
+
+3. Create a Makefile.am in the new directory, something like this:
+
+ AM_CPPFLAGS = -I../..
+
+ check_PROGRAMS = test_foo
+
+ TESTS = test_foo
+
+ test_foo_SOURCES = test_foo.c
+ test_foo_LDADD = ../../tests/libt_api.a # plus others...
+
+
+See existing Makefile.am for examples, and the Automake documentation:
+
+ http://www.gnu.org/software/automake/manual/html_node/Tests.html
+
+
+Support Functions
+-----------------
+
+Here are a few of the most useful functions defined in t_api that you
+can use in testing:
+
+ void
+ t_assert(const char *component, int anum, int class,
+ const char *what, ...);
+
+ The name of this function is slightly misleading. It
+ actually just prints out an error message in the test
+ output.
+
+ void
+ t_info(const char *format, ...);
+
+ Prints out a message in the test output. You should
+ include "\n" at the end.
+
+ void
+ t_result(int result);
+
+ Prints out the result in the test output. You should
+ use one of the constants for this:
+
+ T_PASS
+ T_FAIL
+ T_UNRESOLVED
+ T_UNSUPPORTED
+ T_UNTESTED
+ T_THREADONLY
+
+Additional Testing
+------------------
+
+Other static or runtime testing is always an option. For instance, you
+can use valgrind to check for memory leaks.
+
+
+$Id: HOWTO-unit-test,v 1.2 2007/11/16 11:04:12 shane Exp $
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 00000000..9c6c650e
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,33 @@
+EXTRA_DIST = failover/dhcp-1.cf failover/dhcp-2.cf failover/new-failover \
+ DHCPv6/000-badmsgtype.pl \
+ DHCPv6/010-solicit-noclientid.pl \
+ DHCPv6/011-solicit-serverid.pl \
+ DHCPv6/020-advertise-mcast.pl \
+ DHCPv6/030-request-noclientid.pl \
+ DHCPv6/031-request-noserverid.pl \
+ DHCPv6/032-request-badduid.pl \
+ DHCPv6/110-information-request-ia_na.pl \
+ DHCPv6/111-information-request-ia_ta.pl \
+ DHCPv6/112-badduid.pl \
+ DHCPv6/210-solicit-nohost.pl \
+ DHCPv6/211-solicit-opt-in-na.pl \
+ DHCPv6/212-solicit-opt-in-na-norapidcommit.pl \
+ DHCPv6/280-release-nohost.pl \
+ DHCPv6/281-release-bad-address.pl \
+ DHCPv6/282-release-no-address.pl \
+ DHCPv6/283-release.pl \
+ DHCPv6/290-decline-nohost.pl \
+ DHCPv6/291-decline-bad-address.pl \
+ DHCPv6/292-decline-no-address.pl \
+ DHCPv6/293-decline.pl \
+ DHCPv6/README DHCPv6/dhcp_client.pm \
+ DHCPv6/stubcli-opt-in-na.pl DHCPv6/stubcli.pl \
+ DHCPv6/test-a.conf DHCPv6/test-b.conf \
+ HOWTO-unit-test \
+ unit_test_sample.c
+
+AM_CPPFLAGS = -I..
+
+check_LIBRARIES = libt_api.a
+libt_api_a_SOURCES = t_api.c t_api_dhcp.c
+
diff --git a/tests/Makefile.in b/tests/Makefile.in
new file mode 100644
index 00000000..af0c0b01
--- /dev/null
+++ b/tests/Makefile.in
@@ -0,0 +1,406 @@
+# Makefile.in generated by automake 1.9.6 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+top_builddir = ..
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+INSTALL = @INSTALL@
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = tests
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/includes/config.h
+CONFIG_CLEAN_FILES =
+AR = ar
+ARFLAGS = cru
+libt_api_a_AR = $(AR) $(ARFLAGS)
+libt_api_a_LIBADD =
+am_libt_api_a_OBJECTS = t_api.$(OBJEXT) t_api_dhcp.$(OBJEXT)
+libt_api_a_OBJECTS = $(am_libt_api_a_OBJECTS)
+DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir)/includes
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+SOURCES = $(libt_api_a_SOURCES)
+DIST_SOURCES = $(libt_api_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMDEP_FALSE = @AMDEP_FALSE@
+AMDEP_TRUE = @AMDEP_TRUE@
+AMTAR = @AMTAR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GREP = @GREP@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+ac_ct_CC = @ac_ct_CC@
+am__fastdepCC_FALSE = @am__fastdepCC_FALSE@
+am__fastdepCC_TRUE = @am__fastdepCC_TRUE@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+byte_order = @byte_order@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+EXTRA_DIST = failover/dhcp-1.cf failover/dhcp-2.cf failover/new-failover \
+ DHCPv6/000-badmsgtype.pl \
+ DHCPv6/010-solicit-noclientid.pl \
+ DHCPv6/011-solicit-serverid.pl \
+ DHCPv6/020-advertise-mcast.pl \
+ DHCPv6/030-request-noclientid.pl \
+ DHCPv6/031-request-noserverid.pl \
+ DHCPv6/032-request-badduid.pl \
+ DHCPv6/110-information-request-ia_na.pl \
+ DHCPv6/111-information-request-ia_ta.pl \
+ DHCPv6/112-badduid.pl \
+ DHCPv6/210-solicit-nohost.pl \
+ DHCPv6/211-solicit-opt-in-na.pl \
+ DHCPv6/212-solicit-opt-in-na-norapidcommit.pl \
+ DHCPv6/280-release-nohost.pl \
+ DHCPv6/281-release-bad-address.pl \
+ DHCPv6/282-release-no-address.pl \
+ DHCPv6/283-release.pl \
+ DHCPv6/290-decline-nohost.pl \
+ DHCPv6/291-decline-bad-address.pl \
+ DHCPv6/292-decline-no-address.pl \
+ DHCPv6/293-decline.pl \
+ DHCPv6/README DHCPv6/dhcp_client.pm \
+ DHCPv6/stubcli-opt-in-na.pl DHCPv6/stubcli.pl \
+ DHCPv6/test-a.conf DHCPv6/test-b.conf \
+ HOWTO-unit-test \
+ unit_test_sample.c
+
+AM_CPPFLAGS = -I..
+check_LIBRARIES = libt_api.a
+libt_api_a_SOURCES = t_api.c t_api_dhcp.c
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign tests/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --foreign tests/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+clean-checkLIBRARIES:
+ -test -z "$(check_LIBRARIES)" || rm -f $(check_LIBRARIES)
+libt_api.a: $(libt_api_a_OBJECTS) $(libt_api_a_DEPENDENCIES)
+ -rm -f libt_api.a
+ $(libt_api_a_AR) libt_api.a $(libt_api_a_OBJECTS) $(libt_api_a_LIBADD)
+ $(RANLIB) libt_api.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_api.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_api_dhcp.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \
+@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+uninstall-info-am:
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$tags $$unique; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) ' { files[$$0] = 1; } \
+ END { for (i in files) print i; }'`; \
+ test -z "$(CTAGS_ARGS)$$tags$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$tags $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && cd $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ $(mkdir_p) $(distdir)/DHCPv6 $(distdir)/failover
+ @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
+ list='$(DISTFILES)'; for file in $$list; do \
+ case $$file in \
+ $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \
+ $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \
+ esac; \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test "$$dir" != "$$file" && test "$$dir" != "."; then \
+ dir="/$$dir"; \
+ $(mkdir_p) "$(distdir)$$dir"; \
+ else \
+ dir=''; \
+ fi; \
+ if test -d $$d/$$file; then \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_LIBRARIES)
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-checkLIBRARIES clean-generic mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-exec-am:
+
+install-info: install-info-am
+
+install-man:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-info-am
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean \
+ clean-checkLIBRARIES clean-generic ctags distclean \
+ distclean-compile distclean-generic distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-exec install-exec-am \
+ install-info install-info-am install-man install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+ uninstall-am uninstall-info-am
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/tests/t_api.c b/tests/t_api.c
new file mode 100644
index 00000000..6685cec0
--- /dev/null
+++ b/tests/t_api.c
@@ -0,0 +1,823 @@
+/*
+ * Copyright (C) 2004, 2005, 2007 Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (C) 1999-2003 Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* $Id: t_api.c,v 1.2 2007/11/16 11:04:12 shane Exp $ */
+
+/*! \file */
+
+/*
+ * This test API framework is taken from the BIND 9 code. It has been
+ * modified to remove the DNS-specific parts, and the BIND-specific
+ * parts.
+ *
+ * The DNS-specific parts are now wrapped with the DNS_SUPPORT macro,
+ * and the BIND-specific parts are now wrapped with the BIND_SUPPORT
+ * macro.
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/wait.h>
+
+#include <isc-dhcp/boolean.h>
+#include <isc-dhcp/commandline.h>
+#include <isc-dhcp/print.h>
+#include <isc-dhcp/string.h>
+#include <isc-dhcp/mem.h>
+
+#ifdef DNS_SUPPORT
+#include <dns/compress.h>
+#include <dns/result.h>
+#endif /* DNS_SUPPORT */
+
+#ifndef BIND_SUPPORT
+#define isc_commandline_parse getopt
+#define isc_commandline_argument optarg
+#define isc_commandline_option optopt
+#endif /* BIND_SUPPORT */
+
+#include "t_api.h"
+
+static const char *Usage =
+ "\t-a : run all tests\n"
+ "\t-b <dir> : chdir to dir before running tests"
+ "\t-c <config_file> : use specified config file\n"
+ "\t-d <debug_level> : set debug level to debug_level\n"
+ "\t-h : print test info\n"
+ "\t-u : print usage info\n"
+ "\t-n <test_name> : run specified test name\n"
+ "\t-t <test_number> : run specified test number\n"
+ "\t-x : don't execute tests in a subproc\n"
+ "\t-q <timeout> : use 'timeout' as the timeout value\n";
+/*!<
+ * -a --> run all tests
+ * -b dir --> chdir to dir before running tests
+ * -c config --> use config file 'config'
+ * -d --> turn on api debugging
+ * -h --> print out available test names
+ * -u --> print usage info
+ * -n name --> run test named name
+ * -tn --> run test n
+ * -x --> don't execute testcases in a subproc
+ * -q timeout --> use 'timeout' as the timeout value
+ */
+
+#define T_MAXTESTS 256 /*% must be 0 mod 8 */
+#define T_MAXENV 256
+#define T_DEFAULT_CONFIG "t_config"
+#define T_BUFSIZ 256
+#define T_BIGBUF 4096
+
+#define T_TCTOUT 60
+
+int T_debug;
+int T_timeout;
+pid_t T_pid;
+static const char * T_config;
+static char T_tvec[T_MAXTESTS / 8];
+static char * T_env[T_MAXENV + 1];
+static char T_buf[T_BIGBUF];
+static char * T_dir;
+
+static int
+t_initconf(const char *path);
+
+static int
+t_dumpconf(const char *path);
+
+static int
+t_putinfo(const char *key, const char *info);
+
+static char *
+t_getdate(char *buf, size_t buflen);
+
+static void
+printhelp(void);
+
+static void
+printusage(void);
+
+static int T_int;
+
+static void
+t_sighandler(int sig) {
+ T_int = sig;
+}
+
+int
+main(int argc, char **argv) {
+ int c;
+ int tnum;
+ int subprocs;
+ pid_t deadpid;
+ int status;
+ int len;
+ isc_boolean_t first;
+ testspec_t *pts;
+ struct sigaction sa;
+
+#ifdef BIND_SUPPORT
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+#endif /* BIND_SUPPORT */
+ first = ISC_TRUE;
+ subprocs = 1;
+ T_timeout = T_TCTOUT;
+
+ /*
+ * -a option is now default.
+ */
+ memset(T_tvec, 0xffff, sizeof(T_tvec));
+
+ /*
+ * Parse args.
+ */
+ while ((c = isc_commandline_parse(argc, argv, ":at:c:d:n:huxq:b:"))
+ != -1) {
+ if (c == 'a') {
+ /*
+ * Flag all tests to be run.
+ */
+ memset(T_tvec, 0xffff, sizeof(T_tvec));
+ }
+ else if (c == 'b') {
+ T_dir = isc_commandline_argument;
+ }
+ else if (c == 't') {
+ tnum = atoi(isc_commandline_argument);
+ if ((tnum > 0) && (tnum < T_MAXTESTS)) {
+ if (first) {
+ /*
+ * Turn off effect of -a default
+ * and allow multiple -t and -n
+ * options.
+ */
+ memset(T_tvec, 0, sizeof(T_tvec));
+ first = ISC_FALSE;
+ }
+ /*
+ * Flag test tnum to be run.
+ */
+ tnum -= 1;
+ T_tvec[tnum / 8] |= (0x01 << (tnum % 8));
+ }
+ }
+ else if (c == 'c') {
+ T_config = isc_commandline_argument;
+ }
+ else if (c == 'd') {
+ T_debug = atoi(isc_commandline_argument);
+ }
+ else if (c == 'n') {
+ pts = &T_testlist[0];
+ tnum = 0;
+ while (pts->pfv != NULL) {
+ if (! strcmp(pts->func_name,
+ isc_commandline_argument)) {
+ if (first) {
+ memset(T_tvec, 0,
+ sizeof(T_tvec));
+ first = ISC_FALSE;
+ }
+ T_tvec[tnum/8] |= (0x01 << (tnum%8));
+ break;
+ }
+ ++pts;
+ ++tnum;
+ }
+ if (pts->pfv == NULL) {
+ fprintf(stderr, "no such test %s\n",
+ isc_commandline_argument);
+ exit(1);
+ }
+ }
+ else if (c == 'h') {
+ printhelp();
+ exit(0);
+ }
+ else if (c == 'u') {
+ printusage();
+ exit(0);
+ }
+ else if (c == 'x') {
+ subprocs = 0;
+ }
+ else if (c == 'q') {
+ T_timeout = atoi(isc_commandline_argument);
+ }
+ else if (c == ':') {
+ fprintf(stderr, "Option -%c requires an argument\n",
+ isc_commandline_option);
+ exit(1);
+ }
+ else if (c == '?') {
+ fprintf(stderr, "Unrecognized option -%c\n",
+ isc_commandline_option);
+ exit(1);
+ }
+ }
+
+ /*
+ * Set cwd.
+ */
+
+ if (T_dir != NULL)
+ (void) chdir(T_dir);
+
+ /*
+ * We don't want buffered output.
+ */
+
+ (void)setbuf(stdout, NULL);
+ (void)setbuf(stderr, NULL);
+
+ /*
+ * Setup signals.
+ */
+
+ sa.sa_flags = 0;
+ sigfillset(&sa.sa_mask);
+
+#ifdef SIGCHLD
+ /*
+ * This is mostly here for NetBSD's pthread implementation, until
+ * people catch up to the latest unproven-pthread package.
+ */
+ sa.sa_handler = SIG_DFL;
+ (void)sigaction(SIGCHLD, &sa, NULL);
+#endif
+
+ sa.sa_handler = t_sighandler;
+ (void)sigaction(SIGINT, &sa, NULL);
+ (void)sigaction(SIGALRM, &sa, NULL);
+
+ /*
+ * Output start stanza to journal.
+ */
+
+ snprintf(T_buf, sizeof(T_buf), "%s:", argv[0]);
+ len = strlen(T_buf);
+ (void) t_getdate(T_buf + len, T_BIGBUF - len);
+ t_putinfo("S", T_buf);
+
+ /*
+ * Setup the test environment using the config file.
+ */
+
+ if (T_config == NULL)
+ T_config = T_DEFAULT_CONFIG;
+
+ t_initconf(T_config);
+ if (T_debug)
+ t_dumpconf(T_config);
+
+ /*
+ * Now invoke all the test cases.
+ */
+
+ tnum = 0;
+ pts = &T_testlist[0];
+ while (*pts->pfv != NULL) {
+ if (T_tvec[tnum / 8] & (0x01 << (tnum % 8))) {
+ if (subprocs) {
+ T_pid = fork();
+ if (T_pid == 0) {
+ (*pts->pfv)();
+ exit(0);
+ } else if (T_pid > 0) {
+
+ T_int = 0;
+ sa.sa_handler = t_sighandler;
+ (void)sigaction(SIGALRM, &sa, NULL);
+ alarm(T_timeout);
+
+ deadpid = (pid_t) -1;
+ while (deadpid != T_pid) {
+ deadpid =
+ waitpid(T_pid, &status, 0);
+ if (deadpid == T_pid) {
+ if (WIFSIGNALED(status)) {
+ if (WTERMSIG(status) ==
+ SIGTERM)
+ t_info(
+ "the test case timed out\n");
+ else
+ t_info(
+ "the test case caused exception %d\n",
+ WTERMSIG(status));
+ t_result(T_UNRESOLVED);
+ }
+ } else if ((deadpid == -1) &&
+ (errno == EINTR) &&
+ T_int) {
+ kill(T_pid, SIGTERM);
+ T_int = 0;
+ }
+ else if ((deadpid == -1) &&
+ ((errno == ECHILD) ||
+ (errno == ESRCH)))
+ break;
+ }
+
+ alarm(0);
+ sa.sa_handler = SIG_IGN;
+ (void)sigaction(SIGALRM, &sa, NULL);
+ } else {
+ t_info("fork failed, errno == %d\n",
+ errno);
+ t_result(T_UNRESOLVED);
+ }
+ }
+ else {
+ (*pts->pfv)();
+ }
+ }
+ ++pts;
+ ++tnum;
+ }
+
+ snprintf(T_buf, sizeof(T_buf), "%s:", argv[0]);
+ len = strlen(T_buf);
+ (void) t_getdate(T_buf + len, T_BIGBUF - len);
+ t_putinfo("E", T_buf);
+
+ return(0);
+}
+
+void
+t_assert(const char *component, int anum, int class, const char *what, ...) {
+ va_list args;
+
+ (void)printf("T:%s:%d:%s\n", component, anum, class == T_REQUIRED ?
+ "A" : "C");
+
+ /*
+ * Format text to a buffer.
+ */
+ va_start(args, what);
+ (void)vsnprintf(T_buf, sizeof(T_buf), what, args);
+ va_end(args);
+
+ (void)t_putinfo("A", T_buf);
+ (void)printf("\n");
+}
+
+void
+t_info(const char *format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ (void) vsnprintf(T_buf, sizeof(T_buf), format, args);
+ va_end(args);
+ (void) t_putinfo("I", T_buf);
+}
+
+void
+t_result(int result) {
+ const char *p;
+
+ switch (result) {
+ case T_PASS:
+ p = "PASS";
+ break;
+ case T_FAIL:
+ p = "FAIL";
+ break;
+ case T_UNRESOLVED:
+ p = "UNRESOLVED";
+ break;
+ case T_UNSUPPORTED:
+ p = "UNSUPPORTED";
+ break;
+ case T_UNTESTED:
+ p = "UNTESTED";
+ break;
+ case T_THREADONLY:
+ p = "THREADONLY";
+ break;
+ default:
+ p = "UNKNOWN";
+ break;
+ }
+ printf("R:%s\n", p);
+}
+
+char *
+t_getenv(const char *name) {
+ char *n;
+ char **p;
+ size_t len;
+
+ n = NULL;
+ if (name && *name) {
+
+ p = &T_env[0];
+ len = strlen(name);
+
+ while (*p != NULL) {
+ if (strncmp(*p, name, len) == 0) {
+ if ( *(*p + len) == '=') {
+ n = *p + len + 1;
+ break;
+ }
+ }
+ ++p;
+ }
+ }
+ return(n);
+}
+
+/*
+ *
+ * Read in the config file at path, initializing T_env.
+ *
+ * note: no format checking for now ...
+ *
+ */
+
+static int
+t_initconf(const char *path) {
+
+ int n;
+ int rval;
+ char **p;
+ FILE *fp;
+
+ rval = -1;
+
+ fp = fopen(path, "r");
+ if (fp != NULL) {
+ n = 0;
+ p = &T_env[0];
+ while (n < T_MAXENV) {
+ *p = t_fgetbs(fp);
+ if (*p == NULL)
+ break;
+ if ((**p == '#') || (strchr(*p, '=') == NULL)) {
+ /*
+ * Skip comments and other junk.
+ */
+ (void)free(*p);
+ continue;
+ }
+ ++p; ++n;
+ }
+ (void)fclose(fp);
+ rval = 0;
+ }
+
+ return (rval);
+}
+
+/*
+ *
+ * Dump T_env to stdout.
+ *
+ */
+
+static int
+t_dumpconf(const char *path) {
+ int rval;
+ char **p;
+ FILE *fp;
+
+ rval = -1;
+ fp = fopen(path, "r");
+ if (fp != NULL) {
+ p = &T_env[0];
+ while (*p != NULL) {
+ printf("C:%s\n", *p);
+ ++p;
+ }
+ (void) fclose(fp);
+ rval = 0;
+ }
+ return(rval);
+}
+
+/*
+ *
+ * Read a newline or EOF terminated string from fp.
+ * On success:
+ * return a malloc'd buf containing the string with
+ * the newline converted to a '\0'.
+ * On error:
+ * return NULL.
+ *
+ * Caller is responsible for freeing buf.
+ *
+ */
+
+char *
+t_fgetbs(FILE *fp) {
+ int c;
+ size_t n;
+ size_t size;
+ char *buf;
+ char *p;
+
+ n = 0;
+ size = T_BUFSIZ;
+ buf = (char *) malloc(T_BUFSIZ * sizeof(char));
+
+ if (buf != NULL) {
+ p = buf;
+ while ((c = fgetc(fp)) != EOF) {
+
+ if (c == '\n')
+ break;
+
+ *p++ = c;
+ ++n;
+ if ( n >= size ) {
+ size += T_BUFSIZ;
+ buf = (char *)realloc(buf,
+ size * sizeof(char));
+ if (buf == NULL)
+ break;
+ p = buf + n;
+ }
+ }
+ *p = '\0';
+ if (c == EOF && n == 0U) {
+ free(buf);
+ return (NULL);
+ }
+ return (buf);
+ } else {
+ fprintf(stderr, "malloc failed %d", errno);
+ return(NULL);
+ }
+}
+
+/*
+ *
+ * Put info to log, using key.
+ * For now, just dump it out.
+ * Later format into pretty lines.
+ *
+ */
+
+static int
+t_putinfo(const char *key, const char *info) {
+ int rval;
+
+ /*
+ * For now.
+ */
+ rval = printf("%s:%s", key, info);
+ return(rval);
+}
+
+static char *
+t_getdate(char *buf, size_t buflen) {
+ size_t n;
+ time_t t;
+ struct tm *p;
+
+ t = time(NULL);
+ p = localtime(&t);
+ n = strftime(buf, buflen - 1, "%A %d %B %H:%M:%S %Y\n", p);
+ return(n != 0U ? buf : NULL);
+}
+
+/*
+ * Some generally used utilities.
+ */
+#ifdef DNS_SUPPORT
+struct dns_errormap {
+ isc_result_t result;
+ const char *text;
+} dns_errormap[] = {
+ { ISC_R_SUCCESS, "ISC_R_SUCCESS" },
+ { ISC_R_EXISTS, "ISC_R_EXISTS" },
+ { ISC_R_NOTFOUND, "ISC_R_NOTFOUND" },
+ { ISC_R_NOSPACE, "ISC_R_NOSPACE" },
+ { ISC_R_UNEXPECTED, "ISC_R_UNEXPECTED" },
+ { ISC_R_UNEXPECTEDEND, "ISC_R_UNEXPECTEDEND" },
+ { ISC_R_RANGE, "ISC_R_RANGE" },
+ { DNS_R_LABELTOOLONG, "DNS_R_LABELTOOLONG" },
+ { DNS_R_BADESCAPE, "DNS_R_BADESCAPE" },
+ /* { DNS_R_BADBITSTRING, "DNS_R_BADBITSTRING" }, */
+ /* { DNS_R_BITSTRINGTOOLONG, "DNS_R_BITSTRINGTOOLONG"}, */
+ { DNS_R_EMPTYLABEL, "DNS_R_EMPTYLABEL" },
+ { DNS_R_BADDOTTEDQUAD, "DNS_R_BADDOTTEDQUAD" },
+ { DNS_R_UNKNOWN, "DNS_R_UNKNOWN" },
+ { DNS_R_BADLABELTYPE, "DNS_R_BADLABELTYPE" },
+ { DNS_R_BADPOINTER, "DNS_R_BADPOINTER" },
+ { DNS_R_TOOMANYHOPS, "DNS_R_TOOMANYHOPS" },
+ { DNS_R_DISALLOWED, "DNS_R_DISALLOWED" },
+ { DNS_R_EXTRATOKEN, "DNS_R_EXTRATOKEN" },
+ { DNS_R_EXTRADATA, "DNS_R_EXTRADATA" },
+ { DNS_R_TEXTTOOLONG, "DNS_R_TEXTTOOLONG" },
+ { DNS_R_SYNTAX, "DNS_R_SYNTAX" },
+ { DNS_R_BADCKSUM, "DNS_R_BADCKSUM" },
+ { DNS_R_BADAAAA, "DNS_R_BADAAAA" },
+ { DNS_R_NOOWNER, "DNS_R_NOOWNER" },
+ { DNS_R_NOTTL, "DNS_R_NOTTL" },
+ { DNS_R_BADCLASS, "DNS_R_BADCLASS" },
+ { DNS_R_PARTIALMATCH, "DNS_R_PARTIALMATCH" },
+ { DNS_R_NEWORIGIN, "DNS_R_NEWORIGIN" },
+ { DNS_R_UNCHANGED, "DNS_R_UNCHANGED" },
+ { DNS_R_BADTTL, "DNS_R_BADTTL" },
+ { DNS_R_NOREDATA, "DNS_R_NOREDATA" },
+ { DNS_R_CONTINUE, "DNS_R_CONTINUE" },
+ { DNS_R_DELEGATION, "DNS_R_DELEGATION" },
+ { DNS_R_GLUE, "DNS_R_GLUE" },
+ { DNS_R_DNAME, "DNS_R_DNAME" },
+ { DNS_R_CNAME, "DNS_R_CNAME" },
+ { DNS_R_NXDOMAIN, "DNS_R_NXDOMAIN" },
+ { DNS_R_NXRRSET, "DNS_R_NXRRSET" },
+ { DNS_R_BADDB, "DNS_R_BADDB" },
+ { DNS_R_ZONECUT, "DNS_R_ZONECUT" },
+ { DNS_R_NOTZONETOP, "DNS_R_NOTZONETOP" },
+ { DNS_R_SEENINCLUDE, "DNS_R_SEENINCLUDE" },
+ { DNS_R_SINGLETON, "DNS_R_SINGLETON" },
+ { (isc_result_t)0, NULL }
+};
+
+isc_result_t
+t_dns_result_fromtext(char *name) {
+
+ isc_result_t result;
+ struct dns_errormap *pmap;
+
+ result = ISC_R_UNEXPECTED;
+
+ pmap = dns_errormap;
+ while (pmap->text != NULL) {
+ if (strcmp(name, pmap->text) == 0)
+ break;
+ ++pmap;
+ }
+
+ if (pmap->text != NULL)
+ result = pmap->result;
+
+ return (result);
+}
+
+struct dc_method_map {
+ unsigned int dc_method;
+ const char *text;
+} dc_method_map[] = {
+
+ { DNS_COMPRESS_NONE, "DNS_COMPRESS_NONE" },
+ { DNS_COMPRESS_GLOBAL14, "DNS_COMPRESS_GLOBAL14" },
+ { DNS_COMPRESS_ALL, "DNS_COMPRESS_ALL" },
+ { 0, NULL }
+};
+
+unsigned int
+t_dc_method_fromtext(char *name) {
+ unsigned int dc_method;
+ struct dc_method_map *pmap;
+
+ dc_method = DNS_COMPRESS_NONE;
+
+ pmap = dc_method_map;
+ while (pmap->text != NULL) {
+ if (strcmp(name, pmap->text) == 0)
+ break;
+ ++pmap;
+ }
+
+ if (pmap->text != NULL)
+ dc_method = pmap->dc_method;
+
+ return(dc_method);
+}
+#endif /* DNS_SUPPORT */
+
+int
+t_bustline(char *line, char **toks) {
+ int cnt;
+ char *p;
+
+ cnt = 0;
+ if (line && *line) {
+ while ((p = strtok(line, "\t")) && (cnt < T_MAXTOKS)) {
+ *toks++ = p;
+ line = NULL;
+ ++cnt;
+ }
+ }
+ return(cnt);
+}
+
+static void
+printhelp(void) {
+ int cnt;
+ testspec_t *pts;
+
+ cnt = 1;
+ pts = &T_testlist[0];
+
+ printf("Available tests:\n");
+ while (pts->func_name) {
+ printf("\t%d\t%s\n", cnt, pts->func_name);
+ ++pts;
+ ++cnt;
+ }
+}
+
+static void
+printusage(void) {
+ printf("Usage:\n%s\n", Usage);
+}
+
+int
+t_eval(const char *filename, int (*func)(char **), int nargs) {
+ FILE *fp;
+ char *p;
+ int line;
+ int cnt;
+ int result;
+ int nfails;
+ int nprobs;
+ int npass;
+ char *tokens[T_MAXTOKS + 1];
+
+ npass = 0;
+ nfails = 0;
+ nprobs = 0;
+
+ fp = fopen(filename, "r");
+ if (fp != NULL) {
+ line = 0;
+ while ((p = t_fgetbs(fp)) != NULL) {
+
+ ++line;
+
+ /*
+ * Skip comment lines.
+ */
+ if ((isspace((unsigned char)*p)) || (*p == '#')) {
+ (void)free(p);
+ continue;
+ }
+
+ cnt = t_bustline(p, tokens);
+ if (cnt == nargs) {
+ result = func(tokens);
+ switch (result) {
+ case T_PASS:
+ ++npass;
+ break;
+ case T_FAIL:
+ ++nfails;
+ break;
+ case T_UNTESTED:
+ break;
+ default:
+ ++nprobs;
+ break;
+ }
+ } else {
+ t_info("bad format in %s at line %d\n",
+ filename, line);
+ ++nprobs;
+ }
+
+ (void)free(p);
+ }
+ (void)fclose(fp);
+ } else {
+ t_info("Missing datafile %s\n", filename);
+ ++nprobs;
+ }
+
+ result = T_UNRESOLVED;
+
+ if (nfails == 0 && nprobs == 0 && npass > 0)
+ result = T_PASS;
+ else if (nfails > 0)
+ result = T_FAIL;
+ else if (npass == 0)
+ result = T_UNTESTED;
+
+ return (result);
+}
diff --git a/tests/t_api_dhcp.c b/tests/t_api_dhcp.c
new file mode 100644
index 00000000..9ef221a1
--- /dev/null
+++ b/tests/t_api_dhcp.c
@@ -0,0 +1,44 @@
+/*
+ * We have to have a number of symbols defined in order to build a
+ * DHCP program.
+ */
+
+#include <config.h>
+#include "dhcpd.h"
+
+void
+bootp(struct packet *packet) {
+}
+
+void
+dhcp(struct packet *packet) {
+}
+
+void
+dhcpv6(struct packet *packet) {
+}
+
+isc_result_t
+dhcp_set_control_state(control_object_state_t old, control_object_state_t new) {
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+int
+check_collection(struct packet *p, struct lease *l, struct collection *c) {
+ return 0;
+}
+
+void
+classify (struct packet *p, struct class *c) {
+}
+
+isc_result_t
+find_class(struct class **class, const char *c1, const char *c2, int i) {
+ return ISC_R_NOTFOUND;
+}
+
+int
+parse_allow_deny(struct option_cache **oc, struct parse *p, int i) {
+ return 0;
+}
+
diff --git a/tests/unit_test_sample.c b/tests/unit_test_sample.c
new file mode 100644
index 00000000..86d63365
--- /dev/null
+++ b/tests/unit_test_sample.c
@@ -0,0 +1,25 @@
+#include "config.h"
+#include "t_api.h"
+
+static void foo(void);
+
+/*
+ * T_testlist is a list of tests that are invoked.
+ */
+testspec_t T_testlist[] = {
+ { foo, "sample test" },
+ { NULL, NULL }
+};
+
+static void
+foo(void) {
+ static const char *test_desc =
+ "this is an example test, for no actual module";
+
+ t_assert("sample", 1, T_REQUIRED, test_desc);
+
+ /* ... */ /* Test code would go here. */
+
+ t_result(T_PASS);
+}
+