diff options
Diffstat (limited to 'test')
36 files changed, 2345 insertions, 271 deletions
diff --git a/test/ar/tc/add-nonexistent/out/archive.a b/test/ar/tc/add-nonexistent/out/archive.a new file mode 100644 index 0000000000000..8b277f0dd5dcd --- /dev/null +++ b/test/ar/tc/add-nonexistent/out/archive.a @@ -0,0 +1 @@ +!<arch> diff --git a/test/libelf/tset/Makefile b/test/libelf/tset/Makefile index 63e9586913062..5a5e96f6ebd14 100644 --- a/test/libelf/tset/Makefile +++ b/test/libelf/tset/Makefile @@ -1,5 +1,5 @@ # -# $Id: Makefile 3025 2014-04-18 16:20:25Z jkoshy $ +# $Id: Makefile 3715 2019-03-18 09:15:40Z jkoshy $ # TOP= ../../.. @@ -36,6 +36,7 @@ SUBDIR+= elf_ndxscn SUBDIR+= elf_next SUBDIR+= elf_newscn SUBDIR+= elf_nextscn +SUBDIR+= elf_rand SUBDIR+= elf_rawfile SUBDIR+= elf_strptr SUBDIR+= elf_update diff --git a/test/libelf/tset/bin/elfc b/test/libelf/tset/bin/elfc index 98995e820e26c..4c77e4220b18f 100755 --- a/test/libelf/tset/bin/elfc +++ b/test/libelf/tset/bin/elfc @@ -74,7 +74,7 @@ # sections, a section index may be manually specified using a # 'sh_index' pseudo field. # -# $Id: elfc 3614 2018-04-21 19:48:04Z jkoshy $ +# $Id: elfc 3689 2019-02-23 22:50:51Z jkoshy $ version = "%prog 1.0" usage = "usage: %prog [options] [input-file]" @@ -442,6 +442,14 @@ def check_dict(d, l, node=None): raise ElfError(node, "{%s} Unknown key(s) %s" % \ (node.tag, unknown)) +def bounded_value(v, encoding): + """Return the value of 'v' bounded to the maximum size for a type.""" + if encoding == "H": + return (v & 0xFFFF) + elif encoding == "I": + return (v & 0xFFFFFFFF) + return v + # # Helper classes. # @@ -559,8 +567,10 @@ class ElfType: else: n = 3 for t in self.fields: - if t[n] != "": - a.append(getattr(self, t[0])) + field_encoding = t[n] + if field_encoding != "": + v = getattr(self, t[0]) + a.append(bounded_value(v, field_encoding)) return tuple(a) def getfields(self, elfclass): diff --git a/test/libelf/tset/common/Makefile b/test/libelf/tset/common/Makefile index 5dd5bf9c1a5b3..8f8400a397537 100644 --- a/test/libelf/tset/common/Makefile +++ b/test/libelf/tset/common/Makefile @@ -1,10 +1,11 @@ -# $Id: Makefile 1719 2011-08-12 08:24:14Z jkoshy $ +# $Id: Makefile 3690 2019-02-23 22:51:13Z jkoshy $ TOP= ../../../.. YAML_FILES= check_elf \ getclass \ ehdr \ + ehdr-malformed-1 \ fsize \ newehdr newscn newscn2 \ phdr \ diff --git a/test/libelf/tset/common/ehdr-malformed-1.yaml b/test/libelf/tset/common/ehdr-malformed-1.yaml new file mode 100644 index 0000000000000..d7c000f47aede --- /dev/null +++ b/test/libelf/tset/common/ehdr-malformed-1.yaml @@ -0,0 +1,23 @@ +%YAML 1.1 +# $Id$ +--- +ehdr: !Ehdr + e_ident: !Ident # e_ident[] members + ei_class: ELFCLASSNONE + ei_data: ELFDATANONE + ei_osabi: ELFOSABI_SYSV + ei_abiversion: 0 + # other members + e_type: 0xFF03 + e_machine: 0x42 + e_version: 0xFFFFFFFF + e_entry: 0xFFFFFFFFFFFFFFFF + e_phoff: 0xFFFFFFFFFFFFFFFF + e_shoff: 0xFFFFFFFFFFFFFFFF + e_flags: [ 64, 8, 2, 1] + e_ehsize: 62 + e_phentsize: 228 + e_phnum: 0 + e_shentsize: 8192 + e_shnum: 0 + e_shstrndx: 0 diff --git a/test/libelf/tset/common/ehdr_template.m4 b/test/libelf/tset/common/ehdr_template.m4 index 872e0ff3b7c88..31cacd4de541a 100644 --- a/test/libelf/tset/common/ehdr_template.m4 +++ b/test/libelf/tset/common/ehdr_template.m4 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: ehdr_template.m4 3174 2015-03-27 17:13:41Z emaste $ + * $Id: ehdr_template.m4 3703 2019-03-02 20:41:03Z jkoshy $ */ include(`elfts.m4') @@ -367,3 +367,51 @@ tcElfWrongSize$1(void) FN(`LSB') FN(`MSB') + +/* + * Verify that malformed ELF objects are rejected. + */ + +undefine(`FN') +define(`FN',` +void +tcMalformed1$1(void) +{ + int error, fd, result; + Elf *e; + char *fn; + TS_EHDR *eh; + + TP_CHECK_INITIALIZATION(); + + TP_ANNOUNCE("TS_ICNAME with a malformed ELF header " + "fails with ELF_E_HEADER."); + + e = NULL; + fd = -1; + fn = "ehdr-malformed-1.TOLOWER($1)`'TS_EHDRSZ"; + result = TET_UNRESOLVED; + + _TS_OPEN_FILE(e, fn, ELF_C_READ, fd, goto done;); + + error = 0; + if ((eh = TS_ICFUNC`'(e)) != NULL) { + TP_FAIL("\"%s\" TS_ICNAME`'() succeeded.", fn); + goto done; + } else if ((error = elf_errno()) != ELF_E_HEADER) { + TP_FAIL("\"%s\" incorrect error (%d).", fn, error); + goto done; + } + + result = TET_PASS; + +done: + if (e) + (void) elf_end(e); + if (fd != -1) + (void) close(fd); + tet_result(result); +}') + +FN(`LSB') +FN(`MSB') diff --git a/test/libelf/tset/elf32_getehdr/Makefile b/test/libelf/tset/elf32_getehdr/Makefile index 8285b1f8ed6e3..1b01505e750c7 100644 --- a/test/libelf/tset/elf32_getehdr/Makefile +++ b/test/libelf/tset/elf32_getehdr/Makefile @@ -1,8 +1,8 @@ -# $Id: Makefile 1368 2011-01-22 09:09:15Z jkoshy $ +# $Id: Makefile 3691 2019-02-23 23:34:04Z jkoshy $ TOP= ../../../.. TS_SRCS= ehdr.m4 -TS_YAML= ehdr +TS_YAML= ehdr ehdr-malformed-1 .include "${TOP}/mk/elftoolchain.tet.mk" diff --git a/test/libelf/tset/elf32_newehdr/Makefile b/test/libelf/tset/elf32_newehdr/Makefile index 78c4f1238af3b..7c109199cf4f6 100644 --- a/test/libelf/tset/elf32_newehdr/Makefile +++ b/test/libelf/tset/elf32_newehdr/Makefile @@ -1,9 +1,10 @@ -# $Id: Makefile 1358 2011-01-08 05:40:41Z jkoshy $ +# $Id: Makefile 3702 2019-03-02 20:40:55Z jkoshy $ TOP= ../../../.. TS_SRCS= ehdr.m4 TS_DATA= ehdr.msb32 ehdr.lsb32 ehdr.msb64 ehdr.lsb64 \ + ehdr-malformed-1.lsb32 ehdr-malformed-1.msb32 \ newehdr.lsb32 newehdr.msb32 .include "${TOP}/mk/elftoolchain.tet.mk" diff --git a/test/libelf/tset/elf64_getehdr/Makefile b/test/libelf/tset/elf64_getehdr/Makefile index e8bb49ad4d25a..1b01505e750c7 100644 --- a/test/libelf/tset/elf64_getehdr/Makefile +++ b/test/libelf/tset/elf64_getehdr/Makefile @@ -1,8 +1,8 @@ -# $Id: Makefile 1358 2011-01-08 05:40:41Z jkoshy $ +# $Id: Makefile 3691 2019-02-23 23:34:04Z jkoshy $ TOP= ../../../.. TS_SRCS= ehdr.m4 -TS_YAML= ehdr +TS_YAML= ehdr ehdr-malformed-1 .include "${TOP}/mk/elftoolchain.tet.mk" diff --git a/test/libelf/tset/elf64_newehdr/Makefile b/test/libelf/tset/elf64_newehdr/Makefile index 88ccf4d1791b3..1e56f41133745 100644 --- a/test/libelf/tset/elf64_newehdr/Makefile +++ b/test/libelf/tset/elf64_newehdr/Makefile @@ -1,9 +1,10 @@ -# $Id: Makefile 1358 2011-01-08 05:40:41Z jkoshy $ +# $Id: Makefile 3702 2019-03-02 20:40:55Z jkoshy $ TOP= ../../../.. TS_SRCS= ehdr.m4 TS_DATA= ehdr.msb64 ehdr.lsb64 ehdr.msb32 ehdr.lsb32 \ + ehdr-malformed-1.lsb64 ehdr-malformed-1.msb64 \ newehdr.lsb64 newehdr.msb64 .include "${TOP}/mk/elftoolchain.tet.mk" diff --git a/test/libelf/tset/elf_begin/Makefile b/test/libelf/tset/elf_begin/Makefile index d5c675cd04493..bdc1575f80c6a 100644 --- a/test/libelf/tset/elf_begin/Makefile +++ b/test/libelf/tset/elf_begin/Makefile @@ -1,8 +1,9 @@ -# $Id: Makefile 2933 2013-03-30 01:33:02Z jkoshy $ +# $Id: Makefile 3704 2019-03-02 20:41:12Z jkoshy $ TOP= ../../../.. TS_SRCS= begin.m4 +TS_FILES= entry-too-large.ar TS_DATA= check_elf.msb32 check_elf.lsb32 check_elf.msb64 \ check_elf.lsb64 a.ar a-bsd.ar a.o zero CLEANFILES+= a.c diff --git a/test/libelf/tset/elf_begin/begin.m4 b/test/libelf/tset/elf_begin/begin.m4 index 9a282eb3f7462..16ac34e66826a 100644 --- a/test/libelf/tset/elf_begin/begin.m4 +++ b/test/libelf/tset/elf_begin/begin.m4 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: begin.m4 2933 2013-03-30 01:33:02Z jkoshy $ + * $Id: begin.m4 3706 2019-03-02 20:57:45Z jkoshy $ */ #include <sys/stat.h> @@ -634,3 +634,44 @@ tcArMemoryFdIgnored_$1(void) ARFN(`BSD') ARFN(`SVR4') + +/* + * Verify behavior with a corrupted header containing a too-large size. + */ +void +tcArEntryTooLarge(void) +{ + Elf *ar_e, *e; + int error, fd, result; + + result = TET_UNRESOLVED; + ar_e = NULL; + e = NULL; + + TP_ANNOUNCE("elf_begin() returns ELF_E_ARCHIVE for too-large archive " + "entries."); + + TP_SET_VERSION(); + + _TS_OPEN_FILE(ar_e, "entry-too-large.ar", ELF_C_READ, fd, goto done;); + + if ((e = elf_begin(fd, ELF_C_READ, ar_e)) != NULL) { + TP_FAIL("elf_begin() succeeded."); + goto done; + } + + error = elf_errno(); + if (error != ELF_E_ARCHIVE) { + TP_FAIL("unexpected error %d", error); + goto done; + } + + result = TET_PASS; + +done: + if (e) + (void) elf_end(e); + if (ar_e) + (void) elf_end(ar_e); + tet_result(result); +} diff --git a/test/libelf/tset/elf_begin/entry-too-large.ar b/test/libelf/tset/elf_begin/entry-too-large.ar new file mode 100644 index 0000000000000..5cab1486306cf --- /dev/null +++ b/test/libelf/tset/elf_begin/entry-too-large.ar @@ -0,0 +1,3 @@ +!<arch> +a1.c/ 1551379738 1000 1000 100644 9 ` +1234567 diff --git a/test/libelf/tset/elf_getdata/getdata.m4 b/test/libelf/tset/elf_getdata/getdata.m4 index 40afc8a4ec300..332f81bcebe34 100644 --- a/test/libelf/tset/elf_getdata/getdata.m4 +++ b/test/libelf/tset/elf_getdata/getdata.m4 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: getdata.m4 2090 2011-10-27 08:07:39Z jkoshy $ + * $Id: getdata.m4 3695 2019-02-25 18:55:07Z jkoshy $ */ #include <libelf.h> @@ -68,6 +68,36 @@ findscn(Elf *e, const char *name) return (NULL); } +/* + * Check the contents of an Elf_Data descriptor. + * + * The return value from this helper is as follows: + * + * 0 - the descriptor matched the specified content. + * -1 - the descriptor size had a mismatch. + * >0 - the content of the descriptor did not match. The returned value + * is the index of the first byte that differs. + */ +static int +match_content(Elf_Data *ed, size_t nbytes, const char *content) +{ + int n; + const char *buf; + + if (ed->d_size != nbytes) + return (-1); + + buf = (const char *) ed->d_buf; + for (n = 0; n < nbytes; n++) { + if (*buf != *content) + return (n); + buf++; + content++; + } + + return (0); +} + define(`ZEROSECTION',".zerosection") undefine(`FN') define(`FN',` @@ -106,6 +136,11 @@ tcZeroSection$1$2(void) goto done; } + if ((ed = elf_getdata(scn, ed)) != NULL) { + TP_FAIL("Extra data descriptor in section."); + goto done; + } + result = TET_PASS; done: @@ -139,16 +174,17 @@ define(`_FN',` void tcNonZeroSection$1$2(void) { - Elf *e; int error, fd, result; - const size_t strsectionsize = sizeof stringsection; - size_t n, shstrndx; + int match_error; + size_t shstrndx; const char *buf; Elf_Scn *scn; Elf_Data *ed; + Elf *e; - e = NULL; fd = -1; + e = NULL; + scn = NULL; result = TET_UNRESOLVED; TP_ANNOUNCE("a data descriptor for a non-zero sized section " @@ -170,19 +206,22 @@ tcNonZeroSection$1$2(void) goto done; } - if (ed->d_size != strsectionsize) { + match_error = match_content(ed, sizeof(stringsection), + stringsection); + if (match_error == -1) { TP_FAIL("Illegal values returned: d_size %d != expected %d", - (int) ed->d_size, strsectionsize); + (int) ed->d_size, sizeof(stringsection)); goto done; - } - - if (memcmp(stringsection, ed->d_buf, strsectionsize) != 0) { + } else if (match_error > 0) { buf = (const char *) ed->d_buf; - for (n = 0; n < strsectionsize; n++) - if (buf[n] != stringsection[n]) - break; TP_FAIL("String mismatch: buf[%d] \"%c\" != \"%c\"", - n, buf[n], stringsection[n]); + match_error, buf[match_error], + stringsection[match_error]); + goto done; + } + + if ((ed = elf_getdata(scn, ed)) != NULL) { + TP_FAIL("Extra data descriptor in section."); goto done; } @@ -201,3 +240,112 @@ _FN(lsb,32) _FN(lsb,64) _FN(msb,32) _FN(msb,64) + +static const char new_content[] = { +changequote({,}) + 'n', 'e', 'w', ' ', 'c', 'o', 'n', 't', 'e', 'n', 't', '\0' +changequote +}; + +/* + * Verify that a section with multiple Elf_Data segments is handled correctly. + */ +undefine(`_FN') +define(`_FN',` +void +tcDataTraversal$1$2(void) +{ + Elf *e; + Elf_Scn *scn; + Elf_Data *ed; + size_t shstrndx; + int error, fd, match_error, result; + + e = NULL; + fd = -1; + result = TET_UNRESOLVED; + + TP_ANNOUNCE("multiple Elf_Data segments can be traversed."); + _TS_OPEN_FILE(e, "zerosection.$1$2", ELF_C_READ, fd, goto done;); + + if (elf_getshdrstrndx(e, &shstrndx) != 0 || + (scn = elf_getscn(e, shstrndx)) == NULL) { + TP_UNRESOLVED("Cannot find the string table"); + goto done; + } + + /* + * Add new data to the string section. + */ + if ((ed = elf_newdata(scn)) == NULL) { + TP_UNRESOLVED("Cannot allocate new data."); + goto done; + } + + ed->d_buf = (char *) new_content; + ed->d_size = sizeof(new_content); + + /* + * Rescan the descriptor list for the section. + */ + ed = NULL; + if ((ed = elf_getdata(scn, ed)) == NULL) { + error = elf_errno(); + TP_FAIL("elf_getdata failed %d \"%s\"", error, + elf_errmsg(error)); + goto done; + } + + match_error = match_content(ed, sizeof(stringsection), + stringsection); + if (match_error == -1) { + TP_FAIL("Unexpected size of first descriptor: " + "d_size %d != expected %d", (int) ed->d_size, + sizeof(stringsection)); + goto done; + } else if (match_error > 0) { + TP_FAIL("String content mismatch for data descriptor 1."); + goto done; + } + + if ((ed = elf_getdata(scn, ed)) == NULL) { + error = elf_errno(); + TP_FAIL("Missing second data section: %d \"%s\"", error, + elf_errmsg(error)); + goto done; + } + + match_error = match_content(ed, sizeof(new_content), + new_content); + if (match_error == -1) { + TP_FAIL("Unexpected size of second descriptor: " + "d_size %d != expected %d", (int) ed->d_size, + sizeof(new_content)); + goto done; + } else if (match_error > 0) { + TP_FAIL("String content mismatch for data descriptor 2."); + goto done; + } + + /* + * There should be no other Elf_Data descriptors. + */ + if ((ed = elf_getdata(scn, ed)) != NULL) { + TP_FAIL("Too many Elf_Data descriptors for section."); + goto done; + } + + result = TET_PASS; + +done: + if (e) + elf_end(e); + if (fd != -1) + (void) close(fd); + tet_result(result); +}') + +_FN(lsb,32) +_FN(lsb,64) +_FN(msb,32) +_FN(msb,64) diff --git a/test/libelf/tset/elf_rand/Makefile b/test/libelf/tset/elf_rand/Makefile new file mode 100644 index 0000000000000..1b17ff29796c0 --- /dev/null +++ b/test/libelf/tset/elf_rand/Makefile @@ -0,0 +1,18 @@ +# $Id$ + +TOP= ../../../.. + +TS_SRCS= rand.m4 +TS_DATA= a.ar s1 s2 +TS_FILES= empty-file.ar missing-file.ar + +s1: .SILENT + echo 'This is s1.' > ${.TARGET} +s2: .SILENT + echo 's2.' > ${.TARGET} + +a.ar: s1 s2 .SILENT + rm -f ${.TARGET} + ${AR} crv ${.TARGET} s1 s2 > /dev/null + +.include "${TOP}/mk/elftoolchain.tet.mk" diff --git a/test/libelf/tset/elf_rand/empty-file.ar b/test/libelf/tset/elf_rand/empty-file.ar new file mode 100644 index 0000000000000..f7039eda3a06d --- /dev/null +++ b/test/libelf/tset/elf_rand/empty-file.ar @@ -0,0 +1,2 @@ +!<arch> +e1/ 0 0 0 644 0 ` diff --git a/test/libelf/tset/elf_rand/missing-file.ar b/test/libelf/tset/elf_rand/missing-file.ar new file mode 100644 index 0000000000000..d62c803db28b8 --- /dev/null +++ b/test/libelf/tset/elf_rand/missing-file.ar @@ -0,0 +1,2 @@ +!<arch> +e1/ 0 0 0 644 42 ` diff --git a/test/libelf/tset/elf_rand/rand.m4 b/test/libelf/tset/elf_rand/rand.m4 new file mode 100644 index 0000000000000..bdcf436456b5e --- /dev/null +++ b/test/libelf/tset/elf_rand/rand.m4 @@ -0,0 +1,415 @@ +/*- + * Copyright (c) 2019 Joseph Koshy + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ar.h> +#include <libelf.h> +#include <limits.h> +#include <unistd.h> + +#include "elfts.h" +#include "tet_api.h" + +IC_REQUIRES_VERSION_INIT(); + +include(`elfts.m4') + +/* + * The following definitions should match those in `./Makefile'. + */ +define(`TP_ARFILE',`"a.ar"') +define(`TP_NONARCHIVE', `"s1"') + +/* + * The use of an offset less than SARMAG should fail. + */ +void +tcSeekBelowSarmag(void) +{ + Elf *ar; + off_t offset; + int error, fd, result; + + fd = -1; + ar = NULL; + result = TET_UNRESOLVED; + + TP_CHECK_INITIALIZATION(); + TP_ANNOUNCE("elf_rand() fails for an offset less than SARMAG"); + + TS_OPEN_FILE(ar, TP_ARFILE, ELF_C_READ, fd); + + result = TET_PASS; + + if ((offset = elf_rand(ar, 1)) != 0) { + TP_FAIL("elf_rand() succeeded with offset=%lld", + (unsigned long long) offset); + } else if ((error = elf_errno()) != ELF_E_ARGUMENT) { + TP_FAIL("unexpected error=%d \"%s\"", error, + elf_errmsg(error)); + } + + (void) elf_end(ar); + (void) close(fd); + + tet_result(result); +} + +/* + * The use of an offset greater than the largest valid file offset + * should fail. + */ +void +tcSeekMoreThanFileSize(void) +{ + Elf *ar; + off_t offset; + struct stat sb; + int error, fd, result; + + result = TET_UNRESOLVED; + ar = NULL; + fd = -1; + + TP_CHECK_INITIALIZATION(); + TP_ANNOUNCE("elf_rand() fails with a too-large offset"); + + TS_OPEN_FILE(ar, TP_ARFILE, ELF_C_READ, fd); + + /* Get the file size of the archive. */ + if (fstat(fd, &sb) < 0) { + TP_UNRESOLVED("cannot determine the size of \"%s\"", + TP_ARFILE); + goto done; + } + + result = TET_PASS; + + if ((offset = elf_rand(ar, sb.st_size)) != 0) { + TP_FAIL("elf_rand() succeeded with offset=%lld", + (unsigned long long) offset); + } else if ((error = elf_errno()) != ELF_E_ARGUMENT) { + TP_FAIL("unexpected error=%d \"%s\"", error, + elf_errmsg(error)); + } + +done: + if (ar) + (void) elf_end(ar); + if (fd != -1) + (void) close(fd); + + tet_result(result); +} + +/* + * An offset with value SARMAG is accepted. + */ +void +tcOffsetEqualsSARMAG(void) +{ + Elf *ar; + off_t offset; + int fd, result; + + fd = -1; + ar = NULL; + result = TET_UNRESOLVED; + + TP_CHECK_INITIALIZATION(); + TP_ANNOUNCE("elf_rand(SARMAG) succeeds."); + + TS_OPEN_FILE(ar, TP_ARFILE, ELF_C_READ, fd); + + if ((offset = elf_rand(ar, SARMAG)) != SARMAG) { + TP_FAIL("unexpected offset: %lld", + (long long) offset); + goto done; + } + + result = TET_PASS; + +done: + if (ar) + (void) elf_end(ar); + if (fd != -1) + (void) close(fd); + + tet_result(result); +} + +/* + * Invoking elf_rand() on a non-archive should fail. + */ +void +tcOnNonArchive(void) +{ + Elf *e; + off_t offset; + int error, fd, result; + + fd = -1; + e = NULL; + result = TET_UNRESOLVED; + + TP_CHECK_INITIALIZATION(); + TP_ANNOUNCE("elf_rand(non-archive) fails."); + + TS_OPEN_FILE(e, TP_NONARCHIVE, ELF_C_READ, fd); + + if ((offset = elf_rand(e, SARMAG)) != 0 || + (error = elf_errno()) != ELF_E_ARGUMENT) { + TP_FAIL("unexpected offset=%lld", + (long long) offset); + goto done; + } + + result = TET_PASS; + +done: + if (e) + (void) elf_end(e); + if (fd != -1) + (void) close(fd); + + tet_result(result); +} + +/* + * Use an offset value that could cause an overflow. + */ +void +tcOffsetOverflow(void) +{ + Elf *ar; + off_t offset; + uint64_t max_offset; + int error, fd, result; + + fd = -1; + ar = NULL; + result = TET_UNRESOLVED; + + /* A even offset that is close to overflowing. */ + max_offset = (1ULL << (sizeof(off_t) * CHAR_BIT - 1)) - 2; + + TP_CHECK_INITIALIZATION(); + TP_ANNOUNCE("offset close to overflowing an off_t"); + + TS_OPEN_FILE(ar, TP_ARFILE, ELF_C_READ, fd); + + if ((offset = elf_rand(ar, (off_t) max_offset)) != 0) { + TP_FAIL("unexpected success, offset=%lld", + (long long) offset); + goto done; + } + + result = TET_PASS; + +done: + if (ar) + (void) elf_end(ar); + if (fd != -1) + (void) close(fd); + + tet_result(result); +} + +/* + * Setting the offset to a value that does not correspond to an ar header + * should fail. + */ +void +tcOffsetNotCorrespondingToAnArchiveHeader(void) +{ + Elf *ar; + off_t offset; + int error, fd, result; + + fd = -1; + ar = NULL; + result = TET_UNRESOLVED; + + TP_CHECK_INITIALIZATION(); + TP_ANNOUNCE("elf_rand(non-header-offset) should fail."); + + TS_OPEN_FILE(ar, TP_ARFILE, ELF_C_READ, fd); + + if ((offset = elf_rand(ar, SARMAG+2)) != 0) { + TP_FAIL("unexpected success, offset=%lld", + (long long) offset); + goto done; + } else if ((error = elf_errno()) != ELF_E_ARCHIVE) { + TP_FAIL("unexpected error=%d \"%s\"", error, + elf_errmsg(error)); + goto done; + } + + result = TET_PASS; + +done: + if (ar) + (void) elf_end(ar); + if (fd != -1) + (void) close(fd); + + tet_result(result); +} + +/* + * Odd values of offsets are not legal. + */ +void +tcOddOffset(void) +{ + Elf *ar; + off_t offset; + int error, fd, result; + + fd = -1; + ar = NULL; + result = TET_UNRESOLVED; + + TP_CHECK_INITIALIZATION(); + TP_ANNOUNCE("elf_rand(odd-offset-value) should fail."); + + TS_OPEN_FILE(ar, TP_ARFILE, ELF_C_READ, fd); + + if ((offset = elf_rand(ar, SARMAG+1)) != 0) { + TP_FAIL("unexpected success, offset=%lld", + (long long) offset); + goto done; + } else if ((error = elf_errno()) != ELF_E_ARGUMENT) { + TP_FAIL("unexpected error=%d \"%s\"", error, + elf_errmsg(error)); + goto done; + } + + result = TET_PASS; + +done: + if (ar) + (void) elf_end(ar); + if (fd != -1) + (void) close(fd); + + tet_result(result); +} + +/* + * Negative offset values are not legal. + */ +void +tcNegativeOffset(void) +{ + Elf *ar; + off_t offset; + int error, fd, result; + + fd = -1; + ar = NULL; + result = TET_UNRESOLVED; + + TP_CHECK_INITIALIZATION(); + TP_ANNOUNCE("elf_rand(odd-offset-value) should fail."); + + TS_OPEN_FILE(ar, TP_ARFILE, ELF_C_READ, fd); + + if ((offset = elf_rand(ar, -SARMAG)) != 0) { + TP_FAIL("unexpected success, offset=%lld", + (long long) offset); + goto done; + } else if ((error = elf_errno()) != ELF_E_ARGUMENT) { + TP_FAIL("unexpected error=%d \"%s\"", error, + elf_errmsg(error)); + goto done; + } + + result = TET_PASS; + +done: + if (ar) + (void) elf_end(ar); + if (fd != -1) + (void) close(fd); + + tet_result(result); +} + + +/* These offsets correspond to archive TP_ARFILE. */ +static off_t valid_offsets[] = { + SARMAG, /* File 's1'. */ + 80 /* File 's2'. */ +}; + +static const int number_of_offsets = + sizeof(valid_offsets) / sizeof(valid_offsets[0]); + +/* + * Valid offsets should be usable. + */ +void +tcValidOffsets(void) +{ + Elf *ar; + off_t offset; + int i, error, fd, result; + + fd = -1; + ar = NULL; + result = TET_UNRESOLVED; + + TP_CHECK_INITIALIZATION(); + TP_ANNOUNCE("elf_rand(valid-offsets) succeeds."); + + TS_OPEN_FILE(ar, TP_ARFILE, ELF_C_READ, fd); + + for (i = 0; i < number_of_offsets; i++) { + if ((offset = elf_rand(ar, valid_offsets[i])) != + valid_offsets[i]) { + error = elf_errno(); + TP_FAIL("failed to seek to offset %lld, error=%d " + "\"%s\"", (long long) offset, error, + elf_errmsg(error)); + goto done; + } + } + + result = TET_PASS; + +done: + if (ar) + (void) elf_end(ar); + if (fd != -1) + (void) close(fd); + + tet_result(result); +} diff --git a/test/libtest/Makefile b/test/libtest/Makefile index 1dc489b9db8a7..3cf6de08f3165 100644 --- a/test/libtest/Makefile +++ b/test/libtest/Makefile @@ -9,9 +9,8 @@ SUBDIR+= lib SUBDIR+= driver SUBDIR+= examples -.if !make(install) +.if !make(install) && !make(test) .include "$(TOP)/mk/elftoolchain.subdir.mk" .else -install: .SILENT .PHONY - echo Nothing to install. +install test: .SILENT .PHONY .endif diff --git a/test/libtest/README.rst b/test/libtest/README.rst index 3f29c85f8a93d..b93dca7a69ee9 100644 --- a/test/libtest/README.rst +++ b/test/libtest/README.rst @@ -43,7 +43,7 @@ functions contained in a test case named "``helloworld``": /* File: test.c */ #include "test.h" - TESTCASE_DESCRIPTION(helloworld) = + TEST_CASE_DESCRIPTION(helloworld) = "A description of the helloworld test case."; enum test_result @@ -69,14 +69,14 @@ Test cases can define their own set up and tear down functions: tc_setup_helloworld(testcase_state *tcs) { *tcs = ..allocate a struct helloworld_test.. ; - return (TESTCASE_OK); + return (TEST_CASE_OK); } enum testcase_status tc_teardown_helloworld(testcase_state tcs) { .. deallocate test case state.. - return (TESTCASE_OK); + return (TEST_CASE_OK); } The set up function for a test case will be invoked prior to any of diff --git a/test/libtest/bin/make-test-scaffolding b/test/libtest/bin/make-test-scaffolding index c0966d34527b3..6ec78e8224e1f 100755 --- a/test/libtest/bin/make-test-scaffolding +++ b/test/libtest/bin/make-test-scaffolding @@ -79,7 +79,7 @@ cat <<EOF /* GENERATED FROM: ${@} */ #include <stddef.h> #include "test.h" -#include "test_runner.h" +#include "test_case.h" EOF if ! nm ${*} | sort -k 3 | \ @@ -108,9 +108,13 @@ if ! nm ${*} | sort -k 3 | \ function print_test_case_record(tc_name) { printf("\t{\n") printf("\t\t.tc_name = \"%s\",\n", tc_name) - printf("\t\t.tc_description = %s,\n", test_case_descriptions[tc_name]) + printf("\t\t.tc_description = %s,\n", + test_case_descriptions[tc_name]) printf("\t\t.tc_tags = %s,\n", test_case_tags[tc_name]) - printf("\t\t.tc_tests = test_functions_%s\n", tc_name) + tf_name = "test_functions_" tc_name + printf("\t\t.tc_tests = %s,\n", tf_name) + printf("\t\t.tc_count = sizeof (%s) / sizeof (%s[0]),\n", + tf_name, tf_name) printf("\t},\n") } function delete_test_functions(tc_name) { @@ -120,16 +124,19 @@ if ! nm ${*} | sort -k 3 | \ } } function print_test_functions_record(tc_name) { - printf("struct test_descriptor test_functions_%s[] = {\n", tc_name) + printf("struct test_function_descriptor test_functions_%s[]", + tc_name) + printf(" = {\n") for (tf_name in test_functions) { if (tc_name != matched_test_case(tf_name)) continue printf("\t{\n") - printf("\t\t.t_name = \"%s\",\n", tf_name) - printf("\t\t.t_description = %s,\n", + printf("\t\t.tf_name = \"%s\",\n", tf_name) + printf("\t\t.tf_description = %s,\n", test_function_descriptions[tf_name]) - printf("\t\t.t_func = %s,\n", prefix_tf tf_name) - printf("\t\t.t_tags = %s\n", test_function_tags[tf_name]) + printf("\t\t.tf_func = %s,\n", prefix_tf tf_name) + printf("\t\t.tf_tags = %s\n", + test_function_tags[tf_name]) printf("\t},\n") } printf("};\n") @@ -144,7 +151,7 @@ if ! nm ${*} | sort -k 3 | \ test_case_tags[DEFAULT] = "NULL" } ($2 == "R" || $2 == "D") && $3 ~ "^" prefix_tc_descr { - printf("extern testcase_description %s;\n", $3) + printf("extern test_case_description %s;\n", $3) tc_name = suffix($3, prefix_tc_descr) test_cases[tc_name] = 1 test_case_descriptions[tc_name] = $3 @@ -155,7 +162,7 @@ if ! nm ${*} | sort -k 3 | \ test_case_setup[tc_name] = $3 } ($2 == "R" || $2 == "D") && $3 ~ "^" prefix_tc_tags { - printf("extern testcase_tags %s;\n", $3) + printf("extern test_case_tags %s;\n", $3) tc_name = suffix($3, prefix_tc_tags) test_cases[tc_name] = 1 test_case_tags[tc_name] = $3 @@ -206,6 +213,8 @@ if ! nm ${*} | sort -k 3 | \ if (needs_default) print_test_case_record(DEFAULT) printf("};\n") + printf("const int test_case_count = sizeof(test_cases) / ") + printf("sizeof(test_cases[0]);\n") }'; then # Cleanup in case of an error. rm ${output_file} diff --git a/test/libtest/driver/Makefile b/test/libtest/driver/Makefile index d149fee159d4a..87788d3fb13ce 100644 --- a/test/libtest/driver/Makefile +++ b/test/libtest/driver/Makefile @@ -6,9 +6,12 @@ TOP= ../../.. CFLAGS+= -I${TOP}/test/libtest/lib -LIB= test_main -SRCS= test_main.c +LIB= driver +SRCS= driver.c \ + driver_main.c WARNS?= 6 +MAN= test_driver.1 + .include "$(TOP)/mk/elftoolchain.lib.mk" diff --git a/test/libtest/driver/driver.c b/test/libtest/driver/driver.c new file mode 100644 index 0000000000000..aa85c7cb4ddff --- /dev/null +++ b/test/libtest/driver/driver.c @@ -0,0 +1,216 @@ +/*- + * Copyright (c) 2018, Joseph Koshy + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * The implementation of the test driver. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <err.h> +#include <libgen.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> + +#include "driver.h" + +#if defined(ELFTC_VCSID) +ELFTC_VCSID("$Id$"); +#endif + +#define SYSTEM_TMPDIR_ENV_VAR "TMPDIR" + +bool +test_driver_add_search_path(struct test_run *tr, const char *directory_name) +{ + char *canonical_path; + struct test_search_path_entry *entry; + + if (!test_driver_is_directory(directory_name)) + return (false); + + if ((canonical_path = realpath(directory_name, NULL)) == NULL) + err(1, "Cannot determine the canonical path for \"%s\"", + directory_name); + + /* Look for, and ignore duplicates. */ + STAILQ_FOREACH(entry, &tr->tr_search_path, tsp_next) { + if (strcmp(canonical_path, entry->tsp_directory) == 0) + return (true); + } + + entry = calloc(1, sizeof(*entry)); + entry->tsp_directory = canonical_path; + + STAILQ_INSERT_TAIL(&tr->tr_search_path, entry, tsp_next); + + return (true); +} + +/* + * Return an initialized test run descriptor. + * + * The caller should use test_driver_free_run() to release the returned + * descriptor. + */ +struct test_run * +test_driver_allocate_run(void) +{ + struct test_run *tr; + + tr = calloc(sizeof(struct test_run), 1); + tr->tr_action = TEST_RUN_EXECUTE; + tr->tr_style = TR_STYLE_LIBTEST; + STAILQ_INIT(&tr->tr_test_cases); + STAILQ_INIT(&tr->tr_search_path); + + return (tr); +} + +/* + * Destroy an allocated test run descriptor. + * + * The passed in pointer should not be used after this function returns. + */ +void +test_driver_free_run(struct test_run *tr) +{ + struct test_search_path_entry *path_entry; + struct test_case_selector *test_case_entry; + struct test_function_selector *function_entry; + + free(tr->tr_runtime_base_directory); + free(tr->tr_name); + if (tr->tr_artefact_archive) + free(tr->tr_artefact_archive); + + /* Free the search path list. */ + while (!STAILQ_EMPTY(&tr->tr_search_path)) { + path_entry = STAILQ_FIRST(&tr->tr_search_path); + STAILQ_REMOVE_HEAD(&tr->tr_search_path, tsp_next); + free(path_entry); + } + + /* Free the test selector list. */ + while (!STAILQ_EMPTY(&tr->tr_test_cases)) { + test_case_entry = STAILQ_FIRST(&tr->tr_test_cases); + STAILQ_REMOVE_HEAD(&tr->tr_test_cases, tcs_next); + + /* Free the linked test functions. */ + while (!STAILQ_EMPTY(&test_case_entry->tcs_functions)) { + function_entry = + STAILQ_FIRST(&test_case_entry->tcs_functions); + STAILQ_REMOVE_HEAD(&test_case_entry->tcs_functions, + tfs_next); + + free(function_entry); + } + + free(test_case_entry); + } + + free(tr); +} + +/* + * Populate unset fields of a struct test_run with defaults. + */ +bool +test_driver_finish_run_initialization(struct test_run *tr, const char *argv0) +{ + struct timeval tv; + const char *basedir; + const char *search_path; + const char *last_component; + char *argv0_copy, *path_copy, *path_element; + char test_name[NAME_MAX]; + + if (tr->tr_name == NULL) { + /* Per POSIX, basename(3) can modify its argument. */ + argv0_copy = strdup(argv0); + last_component = basename(argv0_copy); + + if (gettimeofday(&tv, NULL)) + return (false); + + (void) snprintf(test_name, sizeof(test_name), "%s+%ld%ld", + last_component, (long) tv.tv_sec, (long) tv.tv_usec); + + tr->tr_name = strdup(test_name); + + free(argv0_copy); + } + + /* + * Select a base directory, if one was not specified. + */ + if (tr->tr_runtime_base_directory == NULL) { + basedir = getenv(TEST_TMPDIR_ENV_VAR); + if (basedir == NULL) + basedir = getenv(SYSTEM_TMPDIR_ENV_VAR); + if (basedir == NULL) + basedir = "/tmp"; + tr->tr_runtime_base_directory = realpath(basedir, NULL); + if (tr->tr_runtime_base_directory == NULL) + err(1, "realpath(%s) failed", basedir); + } + + /* + * Add the search paths specified by the environment variable + * 'TEST_PATH' to the end of the search list. + */ + if ((search_path = getenv(TEST_SEARCH_PATH_ENV_VAR)) != NULL && + *search_path != '\0') { + path_copy = strdup(search_path); + path_element = strtok(path_copy, ":"); + do { + if (!test_driver_add_search_path(tr, path_element)) + warnx("in environment variable \"%s\": path " + "\"%s\" does not name a directory.", + TEST_SEARCH_PATH_ENV_VAR, path_element); + } while ((path_element = strtok(NULL, ":")) != NULL); + } + + return (true); +} + +/* + * Helper: return true if the passed in path names a directory, or false + * otherwise. + */ +bool +test_driver_is_directory(const char *path) +{ + struct stat sb; + if (stat(path, &sb) != 0) + return false; + return S_ISDIR(sb.st_mode); +} diff --git a/test/libtest/driver/driver.h b/test/libtest/driver/driver.h new file mode 100644 index 0000000000000..5ae5cfd812466 --- /dev/null +++ b/test/libtest/driver/driver.h @@ -0,0 +1,206 @@ +/*- + * Copyright (c) 2018,2019 Joseph Koshy + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _LIBTEST_DRIVER_H_ +#define _LIBTEST_DRIVER_H_ + +#include <sys/queue.h> + +#include <limits.h> +#include <stdbool.h> + +#include "_elftc.h" + +#include "test.h" + +#define TEST_SEARCH_PATH_ENV_VAR "TEST_PATH" +#define TEST_TMPDIR_ENV_VAR "TEST_TMPDIR" + +/* + * Run time data strucrures. + */ + +/* The completion status for a test run */ +enum test_run_status { + /* + * All test cases were successfully invoked, and all their contained + * test purposes passed. + */ + TR_STATUS_PASS = 0, + + /* + * All test cases were successfully invoked but at least one test + * function reported a failure. + */ + TR_STATUS_FAIL = 1, + + /* + * At least one test case reported an error during its set up or tear + * down phase. + */ + TR_STATUS_ERROR = 2 +}; + +/* + * The 'style' of the run determines the manner in which the test + * executable reports test status. + */ +enum test_run_style { + /* Libtest semantics. */ + TR_STYLE_LIBTEST, + + /* + * Be compatible with the Test Anything Protocol + * (http://testanything.org/). + */ + TR_STYLE_TAP, + + /* Be compatible with NetBSD ATF(9). */ + TR_STYLE_ATF +}; + +/* + * Structures used for selecting tests. + */ +struct test_function_selector { + const struct test_function_descriptor *tfs_descriptor; + + STAILQ_ENTRY(test_function_selector) tfs_next; + int tfs_is_selected; +}; + +STAILQ_HEAD(test_function_selector_list, test_function_selector); + +struct test_case_selector { + const struct test_case_descriptor *tcs_descriptor; + STAILQ_ENTRY(test_case_selector) tcs_next; + struct test_function_selector_list tcs_functions; + int tcs_selected_count; +}; + +/* + * The action being requested of the test driver. + */ +enum test_run_action { + TEST_RUN_EXECUTE, /* Execute the selected tests. */ + TEST_RUN_LIST, /* Only list tests. */ +}; + +STAILQ_HEAD(test_case_selector_list, test_case_selector); + +/* + * Runtime directories to look up data files. + */ +struct test_search_path_entry { + char *tsp_directory; + STAILQ_ENTRY(test_search_path_entry) tsp_next; +}; + +STAILQ_HEAD(test_search_path_list, test_search_path_entry); + +/* + * Used to track flags that were explicity set on the command line. + */ +enum test_run_flags { + TRF_BASE_DIRECTORY = 1U << 0, + TRF_EXECUTION_TIME = 1U << 1, + TRF_ARTEFACT_ARCHIVE = 1U << 2, + TRF_NAME = 1U << 3, + TRF_SEARCH_PATH = 1U << 4, + TRF_EXECUTION_STYLE = 1U << 5, +}; + +/* + * Parameters for the run. + */ +struct test_run { + /* + * Flags tracking the options which were explicitly set. + * + * This field is a bitmask formed of 'enum test_run_flags' values. + */ + unsigned int tr_commandline_flags; + + /* What the test run should do. */ + enum test_run_action tr_action; + + /* The desired behavior of the test harness. */ + enum test_run_style tr_style; + + /* The desired verbosity level. */ + int tr_verbosity; + + /* An optional name assigned by the user for this test run. */ + char *tr_name; + + /* + * The absolute path to the directory under which the test is + * to be run. + * + * Each test case will be invoked in some subdirectory of this + * directory. + */ + char *tr_runtime_base_directory; + + /* + * The test timeout in seconds. + * + * A value of zero indicates that the test driver should wait + * indefinitely for tests. + */ + long tr_max_seconds_per_test; + + /* + * If not NULL, An absolute pathname to an archive that will hold + * the artefacts created by a test run. + */ + char *tr_artefact_archive; + + /* + * Directories to use when resolving non-absolute data file + * names. + */ + struct test_search_path_list tr_search_path; + + /* All tests selected for this run. */ + struct test_case_selector_list tr_test_cases; +}; + +#ifdef __cplusplus +extern "C" { +#endif +struct test_run *test_driver_allocate_run(void); +bool test_driver_add_search_path(struct test_run *, + const char *search_path); +void test_driver_free_run(struct test_run *); +bool test_driver_is_directory(const char *); +bool test_driver_finish_run_initialization(struct test_run *, + const char *argv0); +#ifdef __cplusplus +} +#endif + +#endif /* _LIBTEST_DRIVER_H_ */ diff --git a/test/libtest/driver/driver_main.c b/test/libtest/driver/driver_main.c new file mode 100644 index 0000000000000..c43ccf52ca15c --- /dev/null +++ b/test/libtest/driver/driver_main.c @@ -0,0 +1,726 @@ +/*- + * Copyright (c) 2018, Joseph Koshy + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file defines a "main()" that invokes (or lists) the tests that were + * linked into the current executable. + */ + +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/stat.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fnmatch.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sysexits.h> +#include <time.h> +#include <unistd.h> + +#include "_elftc.h" + +#include "test.h" +#include "test_case.h" + +#include "driver.h" + +#if defined(ELFTC_VCSID) +ELFTC_VCSID("$Id$"); +#endif + +enum selection_scope { + SCOPE_TEST_CASE = 0, /* c:STRING */ + SCOPE_TEST_FUNCTION, /* f:STRING */ + SCOPE_TAG, /* t:STRING */ +}; + +/* Selection list entry. */ +struct selection_option { + STAILQ_ENTRY(selection_option) so_next; + + /* The text to use for matching. */ + const char *so_pattern; + + /* + * Whether matched test and test cases should be selected + * (if false) or deselected (if true). + */ + bool so_select_tests; + + /* The kind of information to match. */ + enum selection_scope so_selection_scope; +}; + +/* All selection options specified. */ +STAILQ_HEAD(selection_option_list, selection_option); + +static struct selection_option * +parse_selection_option(const char *option) +{ + int scope_char; + bool select_tests; + enum selection_scope scope; + struct selection_option *so; + + scope_char = '\0'; + select_tests = true; + scope = SCOPE_TEST_CASE; + + /* Deselection patterns start with a '-'. */ + if (*option == '-') { + select_tests = false; + option++; + } + + /* + * If a scope was not specified, the selection scope defaults + * to SCOPE_TEST_CASE. + */ + if (strchr(option, ':') == NULL) + scope_char = 'c'; + else { + scope_char = *option++; + if (*option != ':') + return (NULL); + option++; /* Skip over the ':'. */ + } + + if (*option == '\0') + return (NULL); + + switch (scope_char) { + case 'c': + scope = SCOPE_TEST_CASE; + break; + case 'f': + scope = SCOPE_TEST_FUNCTION; + break; + case 't': + scope = SCOPE_TAG; + break; + default: + return (NULL); + } + + so = calloc(1, sizeof(*so)); + so->so_pattern = option; + so->so_selection_scope = scope; + so->so_select_tests = select_tests; + + return (so); +} + +/* Test execution styles. */ +struct style_entry { + enum test_run_style se_style; + const char *se_name; +}; + +static const struct style_entry known_styles[] = { + { TR_STYLE_LIBTEST, "libtest" }, + { TR_STYLE_TAP, "tap" }, + { TR_STYLE_ATF, "atf" } +}; + +/* + * Parse a test run style. + * + * This function returns true if the run style was recognized, or + * false otherwise. + */ +static bool +parse_run_style(const char *option, enum test_run_style *run_style) +{ + size_t n; + + for (n = 0; n < sizeof(known_styles) / sizeof(known_styles[0]); n++) { + if (strcasecmp(option, known_styles[n].se_name) == 0) { + *run_style = known_styles[n].se_style; + return (true); + } + } + + return (false); +} + +/* + * Return the canonical spelling of a test execution style. + */ +static const char * +to_execution_style_name(enum test_run_style run_style) +{ + size_t n; + + for (n = 0; n < sizeof(known_styles) / sizeof(known_styles[0]); n++) { + if (known_styles[n].se_style == run_style) + return (known_styles[n].se_name); + } + + return (NULL); +} + +/* + * Parse a string value containing a positive integral number. + */ +static bool +parse_execution_time(const char *option, long *execution_time) { + char *end; + long value; + + if (option == NULL || *option == '\0') + return (false); + + value = strtol(option, &end, 10); + + /* Check for parse errors. */ + if (*end != '\0') + return (false); + + /* Reject negative numbers. */ + if (value < 0) + return (false); + + /* Check for overflows during parsing. */ + if (value == LONG_MAX && errno == ERANGE) + return (false); + + *execution_time = value; + + return (true); +} + +/* + * Match the names of test cases. + * + * In the event of a match, then the selection state specifed in + * 'option' is applied to all the test functions in the test case. + */ +static void +match_test_cases(struct selection_option *option, + struct test_case_selector *tcs) +{ + const struct test_case_descriptor *tcd; + struct test_function_selector *tfs; + + tcd = tcs->tcs_descriptor; + + if (fnmatch(option->so_pattern, tcd->tc_name, 0)) + return; + + STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next) + tfs->tfs_is_selected = option->so_select_tests; +} + +/* + * Match the names of test functions. + */ +static void +match_test_functions(struct selection_option *option, + struct test_case_selector *tcs) +{ + struct test_function_selector *tfs; + const struct test_function_descriptor *tfd; + + STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next) { + tfd = tfs->tfs_descriptor; + + if (fnmatch(option->so_pattern, tfd->tf_name, 0)) + continue; + + tfs->tfs_is_selected = option->so_select_tests; + } +} + +/* + * Helper: returns true if the specified text matches any of the + * entries in the array 'tags'. + */ +static bool +match_tags_helper(const char *pattern, const char *tags[]) +{ + const char **tag; + + if (!tags) + return (false); + + for (tag = tags; *tag && **tag != '\0'; tag++) { + if (!fnmatch(pattern, *tag, 0)) + return (true); + } + + return (false); +} + +/* + * Match tags. + * + * Matches against test case tags apply to all the test + * functions in the test case. + * + * Matches against test function tags apply to the matched + * test function only. + */ +static void +match_tags(struct selection_option *option, + struct test_case_selector *tcs) +{ + const struct test_case_descriptor *tcd; + const struct test_function_descriptor *tfd; + struct test_function_selector *tfs; + + tcd = tcs->tcs_descriptor; + + /* + * If the tag in the option matches a tag associated with + * a test case, then we set all of the test case's functions + * to the specified selection state. + */ + if (match_tags_helper(option->so_pattern, tcd->tc_tags)) { + STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next) + tfs->tfs_is_selected = option->so_select_tests; + return; + } + + /* + * Otherwise, check the tag against the tags for each function + * in the test case and set the selection state of each matched + * function. + */ + STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next) { + tfd = tfs->tfs_descriptor; + if (match_tags_helper(option->so_pattern, tfd->tf_tags)) + tfs->tfs_is_selected = option->so_select_tests; + } +} + +/* + * Add the selected tests to the test run. + * + * The memory used by the options list is returned to the system when this + * function completes. + */ +static void +select_tests(struct test_run *tr, + struct selection_option_list *selections) +{ + int i, j; + struct selection_option *selection; + const struct test_case_descriptor *tcd; + struct test_case_selector *tcs; + struct test_function_selector *tfs; + bool default_selection_state; + int selected_count; + + default_selection_state = STAILQ_EMPTY(selections); + + /* + * Set up runtime descriptors. + */ + for (i = 0; i < test_case_count; i++) { + if ((tcs = calloc(1, sizeof(*tcs))) == NULL) + err(EX_OSERR, "cannot allocate a test-case selector"); + STAILQ_INSERT_TAIL(&tr->tr_test_cases, tcs, tcs_next); + STAILQ_INIT(&tcs->tcs_functions); + + tcd = &test_cases[i]; + + tcs->tcs_descriptor = tcd; + + for (j = 0; j < tcd->tc_count; j++) { + if ((tfs = calloc(1, sizeof(*tfs))) == NULL) + err(EX_OSERR, "cannot allocate a test " + "function selector"); + STAILQ_INSERT_TAIL(&tcs->tcs_functions, tfs, tfs_next); + + tfs->tfs_descriptor = tcd->tc_tests + j; + tfs->tfs_is_selected = default_selection_state; + } + } + + /* + * Set or reset the selection state based on the options. + */ + STAILQ_FOREACH(selection, selections, so_next) { + STAILQ_FOREACH(tcs, &tr->tr_test_cases, tcs_next) { + switch (selection->so_selection_scope) { + case SCOPE_TEST_CASE: + match_test_cases(selection, tcs); + break; + case SCOPE_TEST_FUNCTION: + match_test_functions(selection, tcs); + break; + case SCOPE_TAG: + match_tags(selection, tcs); + break; + } + } + } + + /* + * Determine the count of tests selected, for each test case. + */ + STAILQ_FOREACH(tcs, &tr->tr_test_cases, tcs_next) { + selected_count = 0; + STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next) + selected_count += tfs->tfs_is_selected; + tcs->tcs_selected_count = selected_count; + } + + /* Free up the selection list. */ + while (!STAILQ_EMPTY(selections)) { + selection = STAILQ_FIRST(selections); + STAILQ_REMOVE_HEAD(selections, so_next); + free(selection); + } +} + +/* + * Translate a file name to absolute form. + * + * The caller needs to free the returned pointer. + */ +static char * +to_absolute_path(const char *filename) +{ + size_t space_needed; + char *absolute_path; + char current_directory[PATH_MAX]; + + if (filename == NULL || *filename == '\0') + return (NULL); + if (*filename == '/') + return strdup(filename); + + if (getcwd(current_directory, sizeof(current_directory)) == NULL) + err(1, "getcwd failed"); + + /* Reserve space for the slash separator and the trailing NUL. */ + space_needed = strlen(current_directory) + strlen(filename) + 2; + if ((absolute_path = malloc(space_needed)) == NULL) + err(1, "malloc failed"); + if (snprintf(absolute_path, space_needed, "%s/%s", current_directory, + filename) != (int) (space_needed - 1)) + err(1, "snprintf failed"); + return (absolute_path); +} + + +/* + * Display run parameters. + */ + +#define FIELD_NAME_WIDTH 24 +#define INFOLINE(NAME, FLAG, FORMAT, ...) do { \ + printf("I %c %-*s " FORMAT, \ + (FLAG) ? '!' : '.', \ + FIELD_NAME_WIDTH, NAME, __VA_ARGS__); \ + } while (0) + +static void +show_run_header(const struct test_run *tr) +{ + time_t start_time; + struct test_search_path_entry *path_entry; + + if (tr->tr_verbosity == 0) + return; + + INFOLINE("test-run-name", tr->tr_commandline_flags & TRF_NAME, + "%s\n", tr->tr_name); + + INFOLINE("test-execution-style", + tr->tr_commandline_flags & TRF_EXECUTION_STYLE, + "%s\n", to_execution_style_name(tr->tr_style)); + + if (!STAILQ_EMPTY(&tr->tr_search_path)) { + INFOLINE("test-search-path", + tr->tr_commandline_flags & TRF_SEARCH_PATH, + "%c", '['); + STAILQ_FOREACH(path_entry, &tr->tr_search_path, tsp_next) { + printf(" %s", path_entry->tsp_directory); + } + printf(" ]\n"); + } + + INFOLINE("test-run-base-directory", + tr->tr_commandline_flags & TRF_BASE_DIRECTORY, + "%s\n", tr->tr_runtime_base_directory); + + if (tr->tr_artefact_archive) { + INFOLINE("test-artefact-archive", + tr->tr_commandline_flags & TRF_ARTEFACT_ARCHIVE, + "%s\n", tr->tr_artefact_archive); + } + + printf("I %c %-*s ", + tr->tr_commandline_flags & TRF_EXECUTION_TIME ? '=' : '.', + FIELD_NAME_WIDTH, "test-execution-time"); + if (tr->tr_max_seconds_per_test == 0) + printf("unlimited\n"); + else + printf("%lu\n", tr->tr_max_seconds_per_test); + + printf("I %% %-*s %d\n", FIELD_NAME_WIDTH, "test-case-count", + test_case_count); + + if (tr->tr_action == TEST_RUN_EXECUTE) { + start_time = time(NULL); + printf("I %% %-*s %s", FIELD_NAME_WIDTH, + "test-run-start-time", ctime(&start_time)); + } +} + +static void +show_run_trailer(const struct test_run *tr) +{ + time_t end_time; + + if (tr->tr_verbosity == 0) + return; + + if (tr->tr_action == TEST_RUN_EXECUTE) { + end_time = time(NULL); + printf("I %% %-*s %s", FIELD_NAME_WIDTH, "test-run-end-time", + asctime(localtime(&end_time))); + } +} + +#undef INFOLINE +#undef FIELD_HEADER_WIDTH + +/* + * Helper: returns a character indicating the selection status for + * a test case. This character is as follows: + * + * - "*" all test functions in the test case were selected. + * - "+" some test functions in the test case were selected. + * - "-" no test functions from the test case were selected. + */ +static int +get_test_case_status(const struct test_case_selector *tcs) +{ + if (tcs->tcs_selected_count == 0) + return '-'; + if (tcs->tcs_selected_count == tcs->tcs_descriptor->tc_count) + return '*'; + return '?'; +} + +/* + * Helper: print out a comma-separated list of tags. + */ +static void +show_tags(int indent, const char *tags[]) +{ + const char **tag; + + printf("%*c: ", indent, ' '); + for (tag = tags; *tag && **tag != '\0';) { + printf("%s", *tag++); + if (*tag && **tag != '\0') + printf(","); + } + printf("\n"); +} + +/* + * Display a test case descriptor. + */ +static void +show_test_case(struct test_run *tr, const struct test_case_selector *tcs) +{ + const struct test_case_descriptor *tcd; + int prefix_char; + + prefix_char = get_test_case_status(tcs); + tcd = tcs->tcs_descriptor; + + printf("C %c %s\n", prefix_char, tcd->tc_name); + + if (tr->tr_verbosity > 0 && tcd->tc_tags != NULL) + show_tags(2, tcd->tc_tags); + + if (tr->tr_verbosity > 1 && tcd->tc_description) + printf(" & %s\n", tcd->tc_description); +} + +static void +show_test_function(struct test_run *tr, + const struct test_function_selector *tfs) +{ + const struct test_function_descriptor *tfd; + int selection_char; + + selection_char = tfs->tfs_is_selected ? '*' : '-'; + tfd = tfs->tfs_descriptor; + + printf(" F %c %s\n", selection_char, tfd->tf_name); + + if (tr->tr_verbosity > 0 && tfd->tf_tags != NULL) + show_tags(4, tfd->tf_tags); + + if (tr->tr_verbosity > 1 && tfd->tf_description) + printf(" & %s\n", tfd->tf_description); +} + +static int +show_listing(struct test_run *tr) +{ + const struct test_case_selector *tcs; + const struct test_function_selector *tfs; + + STAILQ_FOREACH(tcs, &tr->tr_test_cases, tcs_next) { + show_test_case(tr, tcs); + STAILQ_FOREACH(tfs, &tcs->tcs_functions, tfs_next) + show_test_function(tr, tfs); + } + + return (EXIT_SUCCESS); +} + +int +main(int argc, char **argv) +{ + struct test_run *tr; + int exit_code, option; + enum test_run_style run_style; + struct selection_option *selector; + struct selection_option_list selections = + STAILQ_HEAD_INITIALIZER(selections); + + tr = test_driver_allocate_run(); + + /* Parse arguments. */ + while ((option = getopt(argc, argv, ":R:T:c:ln:p:s:t:v")) != -1) { + switch (option) { + case 'R': /* Test runtime directory. */ + if (!test_driver_is_directory(optarg)) + errx(EX_USAGE, "option -%c: argument \"%s\" " + "does not name a directory.", option, + optarg); + tr->tr_runtime_base_directory = realpath(optarg, NULL); + if (tr->tr_runtime_base_directory == NULL) + err(1, "realpath failed for \"%s\"", optarg); + tr->tr_commandline_flags |= TRF_BASE_DIRECTORY; + break; + case 'T': /* Max execution time for a test function. */ + if (!parse_execution_time( + optarg, &tr->tr_max_seconds_per_test)) + errx(EX_USAGE, "option -%c: argument \"%s\" " + "is not a valid execution time value.", + option, optarg); + tr->tr_commandline_flags |= TRF_EXECUTION_TIME; + break; + case 'c': /* The archive holding artefacts. */ + tr->tr_artefact_archive = to_absolute_path(optarg); + tr->tr_commandline_flags |= TRF_ARTEFACT_ARCHIVE; + break; + case 'l': /* List matching tests. */ + tr->tr_action = TEST_RUN_LIST; + break; + case 'n': /* Test run name. */ + if (tr->tr_name) + free(tr->tr_name); + tr->tr_name = strdup(optarg); + tr->tr_commandline_flags |= TRF_NAME; + break; + case 'p': /* Add a search path entry. */ + if (!test_driver_add_search_path(tr, optarg)) + errx(EX_USAGE, "option -%c: argument \"%s\" " + "does not name a directory.", option, + optarg); + tr->tr_commandline_flags |= TRF_SEARCH_PATH; + break; + case 's': /* Test execution style. */ + if (!parse_run_style(optarg, &run_style)) + errx(EX_USAGE, "option -%c: argument \"%s\" " + "is not a supported test execution style.", + option, optarg); + tr->tr_style = run_style; + tr->tr_commandline_flags |= TRF_EXECUTION_STYLE; + break; + case 't': /* Test selection option. */ + if ((selector = parse_selection_option(optarg)) == NULL) + errx(EX_USAGE, "option -%c: argument \"%s\" " + "is not a valid selection pattern.", + option, optarg); + STAILQ_INSERT_TAIL(&selections, selector, so_next); + break; + case 'v': + tr->tr_verbosity++; + break; + case ':': + errx(EX_USAGE, + "ERROR: option -%c requires an argument.", optopt); + break; + case '?': + errx(EX_USAGE, + "ERROR: unrecognized option -%c", optopt); + break; + default: + errx(EX_USAGE, "ERROR: unspecified error."); + break; + } + } + + /* + * Set unset fields of the test run descriptor to their + * defaults. + */ + if (!test_driver_finish_run_initialization(tr, argv[0])) + err(EX_OSERR, "cannot initialize test driver"); + + /* Choose tests and test cases to act upon. */ + select_tests(tr, &selections); + + assert(STAILQ_EMPTY(&selections)); + + show_run_header(tr); + + /* Perform the requested action. */ + switch (tr->tr_action) { + case TEST_RUN_LIST: + exit_code = show_listing(tr); + break; + + case TEST_RUN_EXECUTE: + default: + /* Not yet implemented. */ + exit_code = EX_UNAVAILABLE; + } + + show_run_trailer(tr); + + test_driver_free_run(tr); + + exit(exit_code); +} diff --git a/test/libtest/driver/test_driver.1 b/test/libtest/driver/test_driver.1 new file mode 100644 index 0000000000000..a987002e8c670 --- /dev/null +++ b/test/libtest/driver/test_driver.1 @@ -0,0 +1,308 @@ +.\" Copyright (c) 2019 Joseph Koshy. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" This software is provided by Joseph Koshy ``as is'' and +.\" any express or implied warranties, including, but not limited to, the +.\" implied warranties of merchantability and fitness for a particular purpose +.\" are disclaimed. in no event shall Joseph Koshy be liable +.\" for any direct, indirect, incidental, special, exemplary, or consequential +.\" damages (including, but not limited to, procurement of substitute goods +.\" or services; loss of use, data, or profits; or business interruption) +.\" however caused and on any theory of liability, whether in contract, strict +.\" liability, or tort (including negligence or otherwise) arising in any way +.\" out of the use of this software, even if advised of the possibility of +.\" such damage. +.\" +.\" $Id$ +.\" +.Dd April 02, 2019 +.Dt TEST-DRIVER 1 +.Os +.Sh NAME +.Nm test-driver +.Nd scaffolding for executing +.Xr test 3 +based tests from the command-line +.Sh SYNOPSIS +.Nm test-executable +.Op Fl c Ar artefact-archive-name +.Op Fl l +.Op Fl n Ar run-name +.Op Fl p Ar search-path-directory +.Op Fl R Ar runtime-base-directory +.Op Fl s Ar execution-style +.Op Fl t Ar test-selector +.Op Fl T Ar seconds +.Op Fl v +.Sh DESCRIPTION +The +.Nm +library provides a +.Fn main +function that will execute the +.Xr test 3 +based tests in an executable according to the options specified +on the command-line. +The +.Nm +library usually used in conjunction with code generated by the +.Xr make-test-scaffolding 1 +utility. +.Pp +Test executables built using +.Nm +recognize the following command-line options: +.Bl -tag -width indent +.It Fl c Ar archive-name +If this option is specified, then the +.Nm +provided scaffolding will copy test outputs and other artefacts from +the test run to the archive named by argument +.Ar archive-name . +The format of the archive is specified by the path name suffix of the +artefact name. +The supported output formats are those supported by +.Xr libarchive 3 . +.It Fl l +If this option is specified, then the +.Nm +utility will list the selected tests and exit. +.It Fl n Ar run-name +Use the specified test run name in status messages and to name +any files and directories created during the test run. +If this option is not specified, then the base name of the test +executable is used. +.It Fl p Ar search-path-directory +Add the argument +.Ar search-path-directory +to the list of directories searched for by +.Xr test 3 +utility functions. +.It Fl R Ar runtime-base-directory +Set the runtime base directory to the directory specified by the +argument +.Ar runtime-base-directory . +Tests execute with their current directory set to a subdirectory +within this directory. +The path specified by argument +.Ar runtime-base-directory +must exist, and must name a directory. +.Pp +If this option is not specified, then the +.Ev TEST_TMPDIR +environment variable will be examined. +If set to a non-empty value, then its value will be used. +Otherwise, the value of the +.Ev TMPDIR +environment variable will be used, if non-empty. +If neither of the environment variables +.Ev TEST_TMPDIR +and +.Ev TMPDIR +contain a non-empty value, then the path +.Dq Pa /tmp +will be used. +.It Fl s Ar execution-style +Set the desired execution style to that specified by argument +.Ar execution-style . +Legal values for +.Ar execution-style +are: +.Bl -tag -width indent -compact +.It Li atf +Be compatible with +.Nx +.Xr atf 9 . +.It Li tap +Be compatible with TAP +.Pq Test Anything Protocol . +.It Li test +Be compatible with libtest (this test framework). +.El +The default is to use libtest semantics. +.It Fl t Ar test-selector +Select (or deselect) tests to execute according to the argument +.Ar test-selector . +.Pp +Test selectors are specified using the following syntax: +.Bl -tag -compact -width indent +.It Xo +.Op Li - Ns +.Li c : Ns Ar pattern +.Xc +Select test cases whose names match +.Ar pattern . +Selecting a test case will cause all of its contained +test functions to be selected. +.It Xo +.Op Li - Ns +.Li f : Ns Ar pattern +.Xc +Select test functions whose names match +.Ar pattern . +.It Xo +.Op Li - Ns +.Li t : Ns Ar pattern +.Xc +Select the test cases and test functions associated with +tags matching +.Ar pattern . +.It Xo +.Op Li - Ns +.Ar pattern +.Xc +If the +.Li c , +.Li f +or +.Li t +qualifiers were not specified, then the pattern is matched +against the names of test cases. +.El +The +.Ar pattern +fields of test selectors use shell wildcard syntax, as implemented by +.Xr fnmatch 3 . +.Pp +If no test selectors are specified then all the tests present in +the test executable will be run. +Otherwise, the test selectors specified are processed in the +order specified on the command line. +.Pp +A test selector that does not start with a +.Dq Li - +will add the entries that it matches to the currently selected list +of tests. +A test selector that starts with a +.Dq Li - +will remove the entries that it matches from the currently selected list +of tests. +.Pp +If at least one test selector was specified, and if the result of +applying the specified test selectors was an empty list +of tests, then the +.Nm +library will exit with an error message. +.It Fl T Ar seconds +Set the timeout for individual tests to +.Ar seconds . +If a test function fails to return with the specified number of seconds +then it is treated as having failed. +The default is to wait indefinitely for the test function to complete. +.It Fl v +Increase verbosity level by 1. +The default verbosity level is 0. +.El +.Ss Link-time Pre-requisites +The +.Nm +library expects the following symbols to be present in the +test executable it is linked with: +.Pp +.Bl -tag -width indent -compact +.It Xo +.Vt struct test_case_descriptor +.Va test_cases Ns [] +.Xc +An array of test cases descriptors. +Test case descriptors described by +.Xr test_case 5 . +.It Xo +.Vt int +.Va test_case_count +.Xc +The number of entries in the +.Va test_cases +array. +.El +.Ss Test Execution +At start up, the +.Fn main +function provided by +.Nm +will select tests (and test cases) to execute, based on the test +selection options specified. +.Pp +For each selected test case, test execution proceeds as follows: +.Bl -enum -compact +.It +The runtime directory for the test case is created. +.It +The test process forks, with test execution continuing in the +child. +.It +.Pq Child +The current directory of the process is changed to the runtime +directory. +.It +.Pq Child +The test case set up function is then executed. +If this function returns an error then test case execution is +aborted. +.It +.Pq Child +Each selected test function in the test case is then executed and +its status is output to stdout (or stderr) according to the test +execution style selected. +.It +.Pq Child +The test case tear down function is then executed. +.It +If test artefacts need to be preserved, then these are +copied to the specified archive. +.It +The test's runtime directory is then deleted. +.El +.Pp +After all test cases have been attempted, the +.Fn main +function exits with the exit code appropriate for the +test execution style selected. +.Sh EXAMPLES +To run all tests in the binary named +.Pa tc_example , +copying test artefacts to a +.Xr cpio 1 +archive named +.Pa /tmp/tc_example.cpio , +use: +.Bd -literal -offset indent +tc_example -c /tmp/tc_example.cpio +.Ed +.Pp +To execute tests in the test case +.Dq tc1 +alone, use: +.Bd -literal -offset indent +tc_example -t 'c:tc1' +.Ed +.Pp +To execute tests in the test case +.Dq tc1 +but not the test functions associated with tag +.Li tag1 , +use: +.Bd -literal -offset indent +tc_example -t 'c:tc1' -t '-t:tag1' +.Ed +.Sh DIAGNOSTICS +Test programs built with the +.Nm +library will exit with an exit code of 0 if all of the selected tests +passed when run, and with a non-zero exit code if an error +occurred during test execution. +.Sh SEE ALSO +.Xr make-test-scaffolding 1 , +.Xr fnmatch 3 , +.Xr libarchive 3 , +.Xr test 3 , +.Xr test_case 5 diff --git a/test/libtest/examples/minimal_example.c b/test/libtest/examples/minimal_example.c index 4ad08b4bce06d..3bc76fe66ea99 100644 --- a/test/libtest/examples/minimal_example.c +++ b/test/libtest/examples/minimal_example.c @@ -41,10 +41,16 @@ #include "test.h" /* + * Function prototypes. + */ +enum test_result tf_helloworld(test_case_state); + +/* * Function names prefixed with 'tf_' name test functions. */ enum test_result -tf_helloworld(testcase_state state) +tf_helloworld(test_case_state state) { + (void) state; return (TEST_PASS); } diff --git a/test/libtest/examples/simple_example.c b/test/libtest/examples/simple_example.c index 6a4f6697eb51e..6d72f65dd4518 100644 --- a/test/libtest/examples/simple_example.c +++ b/test/libtest/examples/simple_example.c @@ -31,6 +31,14 @@ #include "test.h" /* + * Function prototypes. + */ +enum test_case_status tc_setup_helloworld(test_case_state *); +enum test_case_status tc_teardown_helloworld(test_case_state); +enum test_result tf_helloworld_sayhello(test_case_state); +enum test_result tf_helloworld_saygoodbye(test_case_state); + +/* * This source defines a single test case named 'helloworld' containing a * single test function named 'sayhello' contained in that test case. At * test execution time the test case would be selectable using the tags @@ -64,35 +72,37 @@ /* * A symbol name prefixed with 'tc_description_' contains a - * test case description. The TESTCASE_DESCRIPTION macro offers + * test case description. The TEST_CASE_DESCRIPTION macro offers * a convenient way to define such symbols. In the case of the * symbol below, the test case named is 'helloworld'. */ -TESTCASE_DESCRIPTION(helloworld) = "A description for a test case."; +TEST_CASE_DESCRIPTION(helloworld) = "A description for a test case."; /* * Function names prefixed with 'tc_setup_' are assumed to be test * case set up functions. */ -enum testcase_status -tc_setup_helloworld(testcase_state *state) +enum test_case_status +tc_setup_helloworld(test_case_state *state) { - return (TESTCASE_OK); + (void) state; + return (TEST_CASE_OK); } /* * Function names prefixed with 'tc_teardown_' are assumed to be test * case tear down functions. */ -enum testcase_status -tc_teardown_helloworld(testcase_state state) +enum test_case_status +tc_teardown_helloworld(test_case_state state) { - return (TESTCASE_OK); + (void) state; + return (TEST_CASE_OK); } /* * Names prefixed with 'tc_tags_' denote the tags associated with test - * cases. The TESTCASE_TAGS macro offers a convenient way to define such + * cases. The TESTC_ASE_TAGS macro offers a convenient way to define such * symbols. * * In the example below, all test functions belonging to the test case @@ -100,7 +110,7 @@ tc_teardown_helloworld(testcase_state state) * * Tags lists are terminated by a NULL entry. */ -TESTCASE_TAGS(helloworld) = { +TEST_CASE_TAGS(helloworld) = { "tag1", "tag2", NULL @@ -110,8 +120,16 @@ TESTCASE_TAGS(helloworld) = { * Function names prefixed with 'tf_' name test functions. */ enum test_result -tf_helloworld_sayhello(testcase_state state) +tf_helloworld_sayhello(test_case_state state) +{ + (void) state; + return (TEST_PASS); +} + +enum test_result +tf_helloworld_saygoodbye(test_case_state state) { + (void) state; return (TEST_PASS); } @@ -126,6 +144,9 @@ tf_helloworld_sayhello(testcase_state state) TEST_DESCRIPTION(helloworld_sayhello) = "A description for the test function 'tf_helloworld_sayhello'."; +TEST_DESCRIPTION(helloworld_saygoodbye) = + "A description for the test function 'tf_helloworld_saygoodbye'."; + /* * Names prefixed by 'tf_tags_' contain the tags associated with * test functions. @@ -143,3 +164,8 @@ test_tags tf_tags_helloworld_sayhello = { "tag4", NULL }; + +test_tags tf_tags_helloworld_saygoodbye = { + "tag5", + NULL +}; diff --git a/test/libtest/lib/Makefile b/test/libtest/lib/Makefile index 7359ddd76a6c7..e36d8fa072bab 100644 --- a/test/libtest/lib/Makefile +++ b/test/libtest/lib/Makefile @@ -4,10 +4,9 @@ TOP= ../../.. LIB= test -SRCS= test.c \ - test_runner.c +SRCS= test.c -INCS= test.h +INCS= test.h test_case.h WARNS?= 6 diff --git a/test/libtest/lib/test.3 b/test/libtest/lib/test.3 index c7045b4039c4f..e170c181595ac 100644 --- a/test/libtest/lib/test.3 +++ b/test/libtest/lib/test.3 @@ -1,4 +1,4 @@ -.\" Copyright (c) 2018, Joseph Koshy. +.\" Copyright (c) 2018,2019 Joseph Koshy. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -24,7 +24,7 @@ .\" .\" $Id$ .\" -.Dd December 25, 2018 +.Dd January 21, 2019 .Dt TEST 3 .Os .Sh NAME @@ -34,20 +34,20 @@ .Lb libtest .Sh SYNOPSIS .In test.h -.Ft enum testcase_status -.Fn testcase_setup "testcase_state *state" -.Ft enum testcase_status -.Fn testcase_teardown "testcase_state state" +.Ft enum test_case_status +.Fn test_case_setup "test_case_state *state" +.Ft enum test_case_status +.Fn test_case_teardown "test_case_state state" .Ft enum test_result -.Fn test_function "testcase_state state" +.Fn test_function "test_case_state state" .Vt "const char" .Va test_description [] ; .Vt "const char *" .Va test_tags [] ; .Vt "const char" -.Va testcase_description [] ; +.Va test_case_description [] ; .Vt "const char *" -.Va testcase_tags [] ; +.Va test_case_tags [] ; .Sh DESCRIPTION The .Lb libtest @@ -76,7 +76,7 @@ If specified, this set up function would be invoked prior to any test function contained in the test case. The set up function can allocate and initialize test-specific state, to be passed to test functions. -If no set up function is specified for the test case, a default no-op +If no set up function is specified for the test case, a default (no-op) function will be supplied. .It An optional test case tear down function. diff --git a/test/libtest/lib/test.h b/test/libtest/lib/test.h index 86f91463993b6..6928f867a6f4d 100644 --- a/test/libtest/lib/test.h +++ b/test/libtest/lib/test.h @@ -44,60 +44,59 @@ enum test_result { /* * The return values from test case set up and tear down functions. * - * - TESTCASE_OK : The set up or tear down function was successful. - * - TESTCASE_ERROR : Set up or tear down actions could not be completed. + * - TEST_CASE_OK : The set up or tear down function was successful. + * - TEST_CASE_ERROR : Set up or tear down actions could not be completed. * - * If a test case set up function returns TESTCASE_ERROR then: + * If a test case set up function returns TEST_CASE_ERROR then: * - The test functions in the test case will not be run. * - The test case's tear down function will not be invoked. * - The test run as a whole will be treated as being in error. * - * If a test case tear down function returns a TESTCASE_ERROR, then + * If a test case tear down function returns a TEST_CASE_ERROR, then * the test run as a whole be treated as being in error. */ -enum testcase_status { - TESTCASE_OK = 0, - TESTCASE_ERROR = 1 +enum test_case_status { + TEST_CASE_OK = 0, + TEST_CASE_ERROR = 1 }; /* - * A testcase_state denotes resources that are shared by the test - * functions that are part of a test case. A testcase_state is allocated - * by the set up function for a test case. Conversely the test case's - * tear down function is responsible for deallocating the resources - * allocated by the set up function. + * A 'test_case_state' is a handle to resources shared by the test functions + * that make up a test case. A test_case_state is allocated by the test case + * set up function and is deallocated by the test case tear down function. * - * The test(3) framework treats a testcase_state as an opaque value. + * The test(3) framework treats a 'test_case_state' as an opaque value. */ -typedef void *testcase_state; +typedef void *test_case_state; /* * Test case and test function descriptions, and convenience macros * to define these. */ -typedef const char testcase_description[]; +typedef const char test_case_description[]; -#if !defined(TEST_DESCRIPTION) -#define TEST_DESCRIPTION(NAME) test_description tf_description_##NAME +#if !defined(TEST_CASE_DESCRIPTION) +#define TEST_CASE_DESCRIPTION(NAME) test_case_description tc_description_##NAME #endif typedef const char test_description[]; -#if !defined(TESTCASE_DESCRIPTION) -#define TESTCASE_DESCRIPTION(NAME) testcase_description tc_description_##NAME +#if !defined(TEST_DESCRIPTION) +#define TEST_DESCRIPTION(NAME) test_description tf_description_##NAME #endif /* * Test case and test function tags, and convenience macros to define * these. */ -typedef const char *testcase_tags[]; +typedef const char *test_case_tags[]; -#if !defined(TESTCASE_TAGS) -#define TESTCASE_TAGS(NAME) testcase_tags tc_tags_##NAME +#if !defined(TEST_CASE_TAGS) +#define TEST_CASE_TAGS(NAME) test_case_tags tc_tags_##NAME #endif typedef const char *test_tags[]; + #if !defined(TEST_TAGS) #define TEST_TAGS(NAME) test_tags tf_tags_##NAME #endif @@ -108,7 +107,7 @@ typedef const char *test_tags[]; * If defined for a test case, this function will be called prior to * the execution of an of the test functions within the test cae. Test * case execution will be aborted if the function returns any value other - * than TESTCASE_OK. + * than TEST_CASE_OK. * * The function can set '*state' to a memory area holding test state to be * passed to test functions. @@ -116,8 +115,8 @@ typedef const char *test_tags[]; * If the test case does not define a set up function, then a default * no-op set up function will be used. */ -typedef enum testcase_status (test_case_setup_function) - (testcase_state *state); +typedef enum test_case_status test_case_setup_function( + test_case_state *state); /* * A test function. @@ -127,7 +126,7 @@ typedef enum testcase_status (test_case_setup_function) * its test succeeded or TEST_FAIL otherwise. In the event the test could * not be executed, it can return TEST_UNRESOLVED. */ -typedef enum test_result (test_function)(testcase_state state); +typedef enum test_result test_function(test_case_state state); /* * A test case tear down function. @@ -138,7 +137,8 @@ typedef enum test_result (test_function)(testcase_state state); * responsible for deallocating the resources that the set up function * had allocated. */ -typedef enum testcase_status (test_case_teardown_function)(testcase_state state); +typedef enum test_case_status test_case_teardown_function( + test_case_state state); #ifdef __cplusplus extern "C" { diff --git a/test/libtest/driver/test_main.c b/test/libtest/lib/test_case.h index 90e46d9a5fe24..c40b15fb69a1a 100644 --- a/test/libtest/driver/test_main.c +++ b/test/libtest/lib/test_case.h @@ -24,26 +24,35 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#ifndef _LIBTEST_TEST_CASE_H_ +#define _LIBTEST_TEST_CASE_H_ + +#include "test.h" + /* - * This file defines a "main" that parses command-line arguments and invokes - * the selected test cases. + * These structures describe the test cases that are linked into a + * test executable. */ -#include <sys/param.h> -#include <assert.h> -#include <stdlib.h> +/* A single test function, with its associated tags and description. */ +struct test_function_descriptor { + const char *tf_name; /* Test name. */ + const char *tf_description; /* Test description. */ + const char **tf_tags; /* The tags for the test. */ + test_function *tf_func; /* The function to invoke. */ +}; -#include "_elftc.h" -#include "test.h" -#include "test_runner.h" +/* A test case, with its associated tests. */ +struct test_case_descriptor { + const char *tc_name; /* Test case name. */ + const char *tc_description; /* Test case description. */ + const char **tc_tags; /* Any associated tags. */ + const struct test_function_descriptor *tc_tests; /* Contained tests. */ + const int tc_count; /* The number of tests. */ +}; -ELFTC_VCSID("$Id$"); +/* All test cases linked into the test binary. */ +extern struct test_case_descriptor test_cases[]; +extern const int test_case_count; -int -main(int argc, char **argv) -{ - (void) test_cases; - (void) argc; - (void) argv; - exit(0); -} +#endif /* _LIBTEST_TEST_CASE_H_ */ diff --git a/test/libtest/lib/test_runner.c b/test/libtest/lib/test_runner.c deleted file mode 100644 index 1366ff4a538e6..0000000000000 --- a/test/libtest/lib/test_runner.c +++ /dev/null @@ -1,31 +0,0 @@ -/*- - * Copyright (c) 2018, Joseph Koshy - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer - * in this position and unchanged. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * An implementation of a test driver for test(3) tests. - */ - -/* To be implemented. */ diff --git a/test/libtest/lib/test_runner.h b/test/libtest/lib/test_runner.h deleted file mode 100644 index cbf00f29b44da..0000000000000 --- a/test/libtest/lib/test_runner.h +++ /dev/null @@ -1,118 +0,0 @@ -/*- - * Copyright (c) 2018, Joseph Koshy - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer - * in this position and unchanged. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _LIBTEST_TEST_RUNNER_H_ -#define _LIBTEST_TEST_RUNNER_H_ - -#include "test.h" - -/* - * These data structures and functions are used by test driver that - * execute tests. - */ - -/* - * The completion status for a test run: - * - * - TESTRUN_PASS : All test cases were successfully invoked and all test - * purposes in the test cases passed. - * - TESTRUN_FAIL : All test cases were successfully invoked but at least - * one test purpose reported a test failure. - * - TESTRUN_ERROR : At least one test case reported an error during its - * set up or tear down phase. - */ -enum testrun_status { - TESTRUN_PASS = 0, - TESTRUN_FAIL = 1, - TESTRUN_ERROR = 2 -}; - -/* - * A single test function, with its associated tags and description. - */ -struct test_descriptor { - const char *t_name; /* Test name. */ - const char *t_description; /* Test description. */ - const char **t_tags; /* Tags associated with the test. */ - test_function *t_func; /* The function to invoke. */ -}; - -/* - * A test case. - */ -struct test_case_descriptor { - const char *tc_name; /* Test case name. */ - const char *tc_description; /* Test case description. */ - const char **tc_tags; /* Any associated tags. */ - struct test_descriptor *tc_tests; /* The tests in this test case. */ -}; - -/* - * All test cases. - */ -extern struct test_case_descriptor test_cases[]; - -enum testrun_style { - /* Libtest semantics. */ - TESTRUN_STYLE_LIBTEST, - - /* - * Be compatible with the Test Anything Protocol - * (http://testanything.org/). - */ - TESTRUN_STYLE_TAP, - - /* Be compatible with NetBSD ATF(9). */ - TESTRUN_STYLE_ATF -}; - -/* - * Parameters for the run. - */ -struct test_run { - /* - * An optional name assigned by the user for this test run. - * - * This name is reported in test logs and is not interpreted - * by the test harness. - */ - char *testrun_name; - - /* The source directory for the run. */ - char *testrun_source_directory; - - /* The directory in which the test is executing. */ - char *testrun_test_directory; -}; - -#ifdef __cplusplus -extern "C" { -#endif -#ifdef __cplusplus -} -#endif - -#endif /* _LIBTEST_TEST_RUNNER_H_ */ diff --git a/test/nm/ts/Makefile.tset b/test/nm/ts/Makefile.tset index ac5fd0ad45404..4b9ca8adb0733 100644 --- a/test/nm/ts/Makefile.tset +++ b/test/nm/ts/Makefile.tset @@ -1,4 +1,4 @@ -# $Id: Makefile.tset 2085 2011-10-27 05:06:47Z jkoshy $ +# $Id: Makefile.tset 3719 2019-03-23 08:30:55Z jkoshy $ NM_EXEC?= ${.CURDIR}/../../../../nm/nm CSTD?= iso9899:1999 @@ -16,7 +16,7 @@ CLEANFILES+= test_nm.c .endif .endfor -.if !exists(${TS_DATA:R}) +.if !empty(${TS_DATA:R}) && !exists(${TS_DATA:R}) ${TS_DATA}: uudecode ${TS_DATA}.uu .endif diff --git a/test/tet/patches/configure.patch b/test/tet/patches/configure.patch index 9fb02049c5459..c2307504cacf9 100644 --- a/test/tet/patches/configure.patch +++ b/test/tet/patches/configure.patch @@ -1,13 +1,13 @@ -: $Id: configure.patch 2204 2011-11-24 05:23:42Z jkoshy $ +: $Id: configure.patch 3721 2019-03-23 09:04:45Z jkoshy $ ---- tet3.8/configure-- 2005-12-09 16:29:17 +0530 -+++ tet3.8/configure 2011-11-24 01:42:02 +0530 -@@ -317,7 +317,7 @@ +--- tet3.8/configure-- Sat Mar 23 10:36:51 2019 ++++ tet3.8/configure Sat Mar 23 10:38:03 2019 +@@ -317,7 +317,7 @@ CRAY*) *-sgi-irix*) fname=irix.mk ;; -*-freebsd) -+*-freebsd | *-netbsd | *-dragonfly) # Use FreeBSD's configuration. ++*-freebsd | *-netbsd | *-dragonfly | *-openbsd) #Use FreeBSD's configuration. fname=freebsd.mk ;; *-bsdi) |