From b64eb60f6c1fdb12f3922e04913e137199838807 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Tue, 9 May 2023 00:32:27 +0100 Subject: [PATCH] @@@ all the mess ever Organization: Straylight/Edgeware From: Mark Wooding --- configure.ac | 14 + hash/Makefile.am | 16 +- hash/t/crc32-test.c | 91 --- hash/t/crc32.tests | 33 +- hash/t/hash-test.c | 149 +++++ hash/t/hash.bench | 26 + hash/t/unihash-test.c | 96 ---- hash/t/unihash-testgen.py | 15 +- hash/tests.at | 6 +- struct/buf.c | 56 ++ struct/buf.h | 28 + t/Makefile.am | 1 + test/Makefile.am | 18 +- test/bench.c | 487 ++++++++++++++++ test/bench.h | 94 +++ test/t/tvec-test.c | 274 +++++++++ test/tests.at | 132 +++++ test/tvec-core.c | 746 ++++++++++++++++++++++++ test/tvec-main.c | 234 ++++++++ test/tvec-output.c | 874 ++++++++++++++++++++++++++++ test/tvec-types.c | 1136 +++++++++++++++++++++++++++++++++++++ test/tvec.h | 571 +++++++++++++++++++ utils/Makefile.am | 5 + utils/control.3 | 492 ++++++++++++++++ utils/control.h | 177 ++++-- utils/linreg.c | 108 ++++ utils/linreg.h | 165 ++++++ utils/macros.h | 14 + utils/t/bits-test.c | 103 ++-- utils/t/bits-testgen.py | 114 ++-- utils/t/control-test.c | 245 ++++---- utils/t/versioncmp-test.c | 47 +- utils/t/versioncmp.tests | 68 ++- utils/tests.at | 25 +- 34 files changed, 6158 insertions(+), 502 deletions(-) delete mode 100644 hash/t/crc32-test.c create mode 100644 hash/t/hash-test.c create mode 100644 hash/t/hash.bench delete mode 100644 hash/t/unihash-test.c create mode 100644 test/bench.c create mode 100644 test/bench.h create mode 100644 test/t/tvec-test.c create mode 100644 test/tests.at create mode 100644 test/tvec-core.c create mode 100644 test/tvec-main.c create mode 100644 test/tvec-output.c create mode 100644 test/tvec-types.c create mode 100644 test/tvec.h create mode 100644 utils/control.3 create mode 100644 utils/linreg.c create mode 100644 utils/linreg.h diff --git a/configure.ac b/configure.ac index 60f82b9..d144ebf 100644 --- a/configure.ac +++ b/configure.ac @@ -58,6 +58,7 @@ AC_CHECK_HEADERS([stdint.h]) dnl Libraries. mdw_ORIG_LIBS=$LIBS LIBS=$MLIB_LIBS +AC_SEARCH_LIBS([sqrt], [m]) AC_SEARCH_LIBS([socket], [socket]) AC_SEARCH_LIBS([gethostbyname], [nsl resolv]) MLIB_LIBS=$LIBS LIBS=$mdw_ORIG_LIBS @@ -120,6 +121,19 @@ case $want_adns,$have_adns in esac AM_CONDITIONAL([WITH_ADNS], [test "$use_adns" = yes]) +dnl-------------------------------------------------------------------------- +dnl Timers. + +AC_CHECK_HEADERS([linux/perf_event.h]) + +mdw_ORIG_LIBS=$LIBS LIBS=$MLIB_LIBS +AC_SEARCH_LIBS([clock_gettime], [rt]) +MLIB_LIBS=$LIBS LIBS=$mdw_ORIG_LIBS +if test "$ac_cv_search_clock_gettime" != no; then + AC_DEFINE([HAVE_CLOCK_GETTIME], [1], + [Define if you have the \`clock_gettime' function.]) +fi + dnl-------------------------------------------------------------------------- dnl Python (used for testing). diff --git a/hash/Makefile.am b/hash/Makefile.am index 877cd3a..564be16 100644 --- a/hash/Makefile.am +++ b/hash/Makefile.am @@ -56,11 +56,6 @@ $(precomp)/crc32-tab.c: mv $@.new $@ endif -check_PROGRAMS += t/crc32.t -t_crc32_t_SOURCES = t/crc32-test.c -t_crc32_t_CPPFLAGS = $(TEST_CPPFLAGS) -t_crc32_t_LDFLAGS = -static - EXTRA_DIST += t/crc32.tests ## Universal hashing. @@ -85,11 +80,12 @@ $(precomp)/unihash-global.c: -o$@.new && mv $@.new $@ endif -check_PROGRAMS += t/unihash.t -t_unihash_t_SOURCES = t/unihash-test.c -t_unihash_t_CPPFLAGS = $(TEST_CPPFLAGS) -t_unihash_t_LDFLAGS = -static - EXTRA_DIST += t/unihash-testgen.py +## Test program. +check_PROGRAMS += t/hash.t +t_hash_t_SOURCES = t/hash-test.c +t_hash_t_CPPFLAGS = $(TEST_CPPFLAGS) +t_hash_t_LDFLAGS = -static + ###----- That's all, folks -------------------------------------------------- diff --git a/hash/t/crc32-test.c b/hash/t/crc32-test.c deleted file mode 100644 index 8e6d820..0000000 --- a/hash/t/crc32-test.c +++ /dev/null @@ -1,91 +0,0 @@ -/* -*-c-*- - * - * Test driver for CRC - * - * (c) 2009 Straylight/Edgeware - */ - -/*----- Licensing notice --------------------------------------------------* - * - * This file is part of the mLib utilities library. - * - * mLib is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * mLib is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with mLib; if not, write to the Free - * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -/*----- Header files ------------------------------------------------------*/ - -#include - -#include "crc32.h" -#include "testrig.h" - -/*----- Main code ---------------------------------------------------------*/ - -static int verify(dstr *v) -{ - uint32 h, hh; - size_t n; - int i, c; - const char *p; - int ok = 1; - - static const int step[] = { 0, 1, 5, 6, 7, 8, 23, -1 }; - - /* --- Set up for using this key --- */ - - h = *(uint32 *)v[1].buf; - - /* --- Hash the data a lot --- */ - - for (i = 0; step[i] >= 0; i++) { - c = step[i]; - if (!c) - hh = crc32(0, v[0].buf, v[0].len); - else { - hh = 0; - p = v[0].buf; - n = v[0].len; - while (n) { - if (c > n) c = n; - hh = crc32(hh, p, c); - p += c; - n -= c; - } - } - if (h != hh) { - ok = 0; - fprintf(stderr, "\ncrc32 failed\n"); - fprintf(stderr, " data = %s\n", v[0].buf); - fprintf(stderr, " step = %d\n", step[i]); - fprintf(stderr, " expected = %08lx\n", (unsigned long)h); - fprintf(stderr, " computed = %08lx\n", (unsigned long)hh); - } - } - return (ok); -} - -static const test_chunk tests[] = { - { "hash", verify, { &type_string, &type_uint32 } }, - { 0, 0, { 0 } } -}; - -int main(int argc, char *argv[]) -{ - test_run(argc, argv, tests, "crc32.in"); - return (0); -} - -/*----- That's all, folks -------------------------------------------------*/ diff --git a/hash/t/crc32.tests b/hash/t/crc32.tests index 9fb2baf..92fbee7 100644 --- a/hash/t/crc32.tests +++ b/hash/t/crc32.tests @@ -1,11 +1,22 @@ -### -*-conf-*- -### crc32 tests - -hash { - "" 0; - "foo" 0x8c736521; - "anything you like" 0x2c090211; - "an exaple test string" 0x686a05aa; - "The quick brown fox jumps over the lazy dog." 0x519025e9; - "A man, a plan, a canal: Panama!" 0x27f3faee; -} +;;; -*-conf-*- +;;; crc32 tests + +[crc32] + +m = "" +h = 0 + +m = "foo" +h = 0x8c736521 + +m = "anything you like" +h = 0x2c090211 + +m = "an exaple test string" +h = 0x686a05aa + +m = "The quick brown fox jumps over the lazy dog." +h = 0x519025e9 + +m = "A man, a plan, a canal: Panama!" +h = 0x27f3faee diff --git a/hash/t/hash-test.c b/hash/t/hash-test.c new file mode 100644 index 0000000..12d8222 --- /dev/null +++ b/hash/t/hash-test.c @@ -0,0 +1,149 @@ +/* -*-c-*- + * + * Test driver for universal hashing + * + * (c) 2009 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * mLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "crc32.h" +#include "unihash.h" +#include "tvec.h" + +/*----- Main code ---------------------------------------------------------*/ + +enum { + RH, NROUT, + RK = NROUT, RM, NREG +}; + +struct step { size_t s; }; + +static void test_crc32(const struct tvec_reg *in, struct tvec_reg *out, + void *ctx) +{ + const struct step *step = ctx; + const unsigned char *p = in[RM].v.bytes.p; size_t sz = in[RM].v.bytes.sz; + uint32 h; + + if (!step) + out[RH].v.u = crc32(0, p, sz); + else { + for (h = 0; sz > step->s; p += step->s, sz -= step->s) + h = crc32(h, p, step->s); + out[RH].v.u = crc32(h, p, sz); + } +} + +static void bench_crc32(const struct tvec_reg *in, struct tvec_reg *out, + void *ctx) + { crc32(0, in[RM].v.bytes.p, in[RM].v.bytes.sz); } + +static void test_unihash(const struct tvec_reg *in, struct tvec_reg *out, + void *ctx) +{ + const struct step *step = ctx; + unihash_info ui; + const unsigned char *p = in[RM].v.bytes.p; size_t sz = in[RM].v.bytes.sz; + uint32 h; + + unihash_setkey(&ui, in[RK].v.u); + if (!step) + out[RH].v.u = unihash(&ui, p, sz); + else { + for (h = UNIHASH_INIT(&ui); sz > step->s; p += step->s, sz -= step->s) + h = unihash_hash(&ui, h, p, step->s); + out[RH].v.u = unihash_hash(&ui, h, p, sz); + } +} + +static void bench_unihash(const struct tvec_reg *in, struct tvec_reg *out, + void *ctx) + { unihash_hash(ctx, 0, in[RM].v.bytes.p, in[RM].v.bytes.sz); } + +static int setup_unihash(const struct tvec_reg *in, struct tvec_reg *out, + const union tvec_misc *arg, void *ctx) + { unihash_setkey(ctx, 0); return (0); } + +static void run_step(struct tvec_state *tv) +{ + static const size_t steps[] = { 1, 5, 6, 7, 8, 23 }; + struct step step; + size_t i; + + tv->test->fn(tv->in, tv->out, 0); + tvec_check(tv, "whole buffer"); + + for (i = 0; i < N(steps); i++) { + step.s = steps[i]; + tv->test->fn(tv->in, tv->out, &step); + tvec_check(tv, "step = %lu", (unsigned long)steps[i]); + } +} + +static const struct tvec_regdef unihash_regs[] = { + { "k", RK, &tvty_uint, 0, { &tvrange_u32 } }, + { "m", RM, &tvty_bytes, 0 }, + { "h", RH, &tvty_uint, 0, { &tvrange_u32 } }, + { 0, 0, 0, 0 } +}; + +static const struct tvec_regdef crc32_regs[] = { + { "m", RM, &tvty_bytes, 0 }, + { "h", RH, &tvty_uint, 0, { &tvrange_u32 } }, + { 0, 0, 0, 0 } +}; + +static const struct tvec_regdef bench_regs[] = { + { "msz", RM, &tvty_buffer, TVRF_ID }, + { 0, 0, 0, 0 } +}; + +static const struct tvec_bench crc32_bench = { + 1, -1, RM, + 0, 0, 0, 0, { 0 } +}; + +static const struct tvec_bench unihash_bench = { + 1, -1, RM, + sizeof(unihash_info), setup_unihash, 0, 0, { 0 } +}; + +static const struct tvec_test tests[] = { + { "crc32",crc32_regs, 0, run_step, test_crc32 }, + { "unihash", unihash_regs, 0, run_step, test_unihash }, + { "crc32-bench", bench_regs, 0, + tvec_bench, bench_crc32, { &crc32_bench } }, + { "unihash-bench", bench_regs, 0, + tvec_bench, bench_unihash, { &unihash_bench } }, + { 0, 0, 0, 0, 0 } +}; + +static const struct tvec_info testinfo = + { tests, NROUT, NREG, sizeof(struct tvec_reg) }; + +int main(int argc, char *argv[]) + { return (tvec_main(argc, argv, &testinfo, 0)); } + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/hash/t/hash.bench b/hash/t/hash.bench new file mode 100644 index 0000000..7d877ac --- /dev/null +++ b/hash/t/hash.bench @@ -0,0 +1,26 @@ +;;; -*-conf-*- +;;; hash benchmarking + +[crc32-bench] + +msz = 16 + +msz = 256 + +msz = 1 kB + +msz = 16 kB + +msz = 1 MB + +[unihash-bench] + +msz = 16 + +msz = 256 + +msz = 1 kB + +msz = 16 kB + +msz = 1 MB diff --git a/hash/t/unihash-test.c b/hash/t/unihash-test.c deleted file mode 100644 index 3e56f10..0000000 --- a/hash/t/unihash-test.c +++ /dev/null @@ -1,96 +0,0 @@ -/* -*-c-*- - * - * Test driver for universal hashing - * - * (c) 2009 Straylight/Edgeware - */ - -/*----- Licensing notice --------------------------------------------------* - * - * This file is part of the mLib utilities library. - * - * mLib is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * mLib is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with mLib; if not, write to the Free - * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -/*----- Header files ------------------------------------------------------*/ - -#include - -#include "unihash.h" -#include "testrig.h" - -/*----- Main code ---------------------------------------------------------*/ - -static int verify(dstr *v) -{ - unihash_info ui; - uint32 k; - uint32 h, hh; - size_t n; - int i, c; - const char *p; - int ok = 1; - - static const int step[] = { 0, 1, 5, 6, 7, 8, 23, -1 }; - - /* --- Set up for using this key --- */ - - k = *(uint32 *)v[0].buf; - h = *(uint32 *)v[2].buf; - unihash_setkey(&ui, k); - - /* --- Hash the data a lot --- */ - - for (i = 0; step[i] >= 0; i++) { - c = step[i]; - if (!c) - hh = unihash(&ui, v[1].buf, v[1].len); - else { - hh = UNIHASH_INIT(&ui); - p = v[1].buf; - n = v[1].len; - while (n) { - if (c > n) c = n; - hh = unihash_hash(&ui, hh, p, c); - p += c; - n -= c; - } - } - if (h != hh) { - ok = 0; - fprintf(stderr, "\nunihash failed\n"); - fprintf(stderr, " key = %08lx\n", (unsigned long)k); - fprintf(stderr, " data = %s\n", v[1].buf); - fprintf(stderr, " step = %d\n", step[i]); - fprintf(stderr, " expected = %08lx\n", (unsigned long)h); - fprintf(stderr, " computed = %08lx\n", (unsigned long)hh); - } - } - return (ok); -} - -static const test_chunk tests[] = { - { "hash", verify, { &type_uint32, &type_string, &type_uint32 } }, - { 0, 0, { 0 } } -}; - -int main(int argc, char *argv[]) -{ - test_run(argc, argv, tests, "unihash.in"); - return (0); -} - -/*----- That's all, folks -------------------------------------------------*/ diff --git a/hash/t/unihash-testgen.py b/hash/t/unihash-testgen.py index 1fde21f..a8365ae 100644 --- a/hash/t/unihash-testgen.py +++ b/hash/t/unihash-testgen.py @@ -20,17 +20,22 @@ def gfmul(x, y): def hashtest(k, m): h = k for ch in m: h = gfmul(h ^ ord(ch), k) - print(' 0x%08x "%s" 0x%08x;' % (k, m, h)) + print('') + print('k = 0x%08x' % k) + print('m = "%s"' % m) + print('h = 0x%08x' % h) print('''\ -### Test vectors for universal hashing -### [generated] +;;; -*-conf-*- Test vectors for universal hashing +;;; [generated] -hash {''') +[unihash]''') for k, m in [(0x00000000, 'anything you like'), (0x12345678, 'an exaple test string'), (0xb8a171f0, 'The quick brown fox jumps over the lazy dog.'), + (0xcc825ed5, 'Jackdaws love my big sphinx of quartz.'), + (0x16e98e46, 'Waltz, bad nymph, for quick jigs vex!'), (0x2940521b, 'A man, a plan, a canal: Panama!')]: hashtest(k, m) @@ -38,5 +43,3 @@ k, m = 0x94b22a73, 0xbb7b1fef for i in xrange(48): hashtest(k, "If we don't succeed, we run the risk of failure.") k = gfmul(k, m) - -print('}') diff --git a/hash/tests.at b/hash/tests.at index 65456d8..45d4c13 100644 --- a/hash/tests.at +++ b/hash/tests.at @@ -31,8 +31,7 @@ AT_SETUP([hash: crc32]) AT_KEYWORDS([hash crc32]) -AT_CHECK([BUILDDIR/t/crc32.t -f SRCDIR/t/crc32.tests], - [0], [ignore], [ignore]) +AT_CHECK([BUILDDIR/t/hash.t SRCDIR/t/crc32.tests], [0], [ignore], [ignore]) AT_CLEANUP @@ -41,8 +40,7 @@ AT_SETUP([hash: unihash]) AT_KEYWORDS([hash unihash]) $PYTHON SRCDIR/t/unihash-testgen.py >unihash.tests -AT_CHECK([BUILDDIR/t/unihash.t -f unihash.tests], - [0], [ignore], [ignore]) +AT_CHECK([BUILDDIR/t/hash.t unihash.tests], [0], [ignore], [ignore]) AT_CLEANUP diff --git a/struct/buf.c b/struct/buf.c index bd16ec7..11b1425 100644 --- a/struct/buf.c +++ b/struct/buf.c @@ -187,6 +187,34 @@ int buf_putbyte(buf *b, int ch) } DOUINTCONV(BUF_GETU_) +/* --- @buf_getk64{,l,b}@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer block + * @kludge64 *w@ = where to put the word + * + * Returns: Zero if OK, or nonzero if there wasn't a word there. + * + * Use: Gets a word of appropriate size and order from a buffer. + */ + +int buf_getk64(buf *b, kludge64 *w) +{ + if (BENSURE(b, 8)) return (-1); + LOAD64_(*w, b->p); BSTEP(b, 8); return (0); +} + +int buf_getk64l(buf *b, kludge64 *w) +{ + if (BENSURE(b, 8)) return (-1); + LOAD64_L_(*w, b->p); BSTEP(b, 8); return (0); +} + +int buf_getk64b(buf *b, kludge64 *w) +{ + if (BENSURE(b, 8)) return (-1); + LOAD64_B_(*w, b->p); BSTEP(b, 8); return (0); +} + /* --- @buf_putu{8,{16,24,32,64}{,l,b}}@ --- * * * Arguments: @buf *b@ = pointer to a buffer block @@ -207,6 +235,34 @@ DOUINTCONV(BUF_GETU_) } DOUINTCONV(BUF_PUTU_) +/* --- @buf_putk64{,l,b}@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer block + * @kludge64 w@ = word to write + * + * Returns: Zero if OK, or nonzero if there wasn't enough space + * + * Use: Gets a word of appropriate size and order from a buffer. + */ + +int buf_putk64(buf *b, kludge64 w) +{ + if (BENSURE(b, 8)) return (-1); + STORE64_(b->p, w); BSTEP(b, 8); return (0); +} + +int buf_putk64l(buf *b, kludge64 w) +{ + if (BENSURE(b, 8)) return (-1); + STORE64_L_(b->p, w); BSTEP(b, 8); return (0); +} + +int buf_putk64b(buf *b, kludge64 w) +{ + if (BENSURE(b, 8)) return (-1); + STORE64_B_(b->p, w); BSTEP(b, 8); return (0); +} + /* --- @findz@ --- * * * Arguments: @buf *b@ = pointer to a buffer block diff --git a/struct/buf.h b/struct/buf.h index a3585b4..97dbd10 100644 --- a/struct/buf.h +++ b/struct/buf.h @@ -199,6 +199,20 @@ extern int buf_putbyte(buf */*b*/, int /*ch*/); extern int buf_getu##w(buf */*b*/, uint##n */*w*/); DOUINTCONV(BUF_DECL_GETU_) +/* --- @buf_getk64{,l,b}@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer block + * @kludge64 *w@ = where to put the word + * + * Returns: Zero if OK, or nonzero if there wasn't a word there. + * + * Use: Gets a word of appropriate size and order from a buffer. + */ + +extern int buf_getk64(buf */*b*/, kludge64 */*w*/); +extern int buf_getk64l(buf */*b*/, kludge64 */*w*/); +extern int buf_getk64b(buf */*b*/, kludge64 */*w*/); + /* --- @buf_putu{8,{16,24,32,64}{,l,b}}@ --- * * * Arguments: @buf *b@ = pointer to a buffer block @@ -213,6 +227,20 @@ DOUINTCONV(BUF_DECL_GETU_) extern int buf_putu##w(buf */*b*/, uint##n /*w*/); DOUINTCONV(BUF_DECL_PUTU_) +/* --- @buf_putk64{,l,b}@ --- * + * + * Arguments: @buf *b@ = pointer to a buffer block + * @kludge64 w@ = word to write + * + * Returns: Zero if OK, or nonzero if there wasn't enough space + * + * Use: Gets a word of appropriate size and order from a buffer. + */ + +extern int buf_putk64(buf */*b*/, kludge64 /*w*/); +extern int buf_putk64l(buf */*b*/, kludge64 /*w*/); +extern int buf_putk64b(buf */*b*/, kludge64 /*w*/); + /* --- @buf_getmem{8,{16,24,32,64}{,l,b},z} --- * * * Arguments: @buf *b@ = pointer to a buffer block diff --git a/t/Makefile.am b/t/Makefile.am index 617af72..8a9e105 100644 --- a/t/Makefile.am +++ b/t/Makefile.am @@ -43,6 +43,7 @@ autotest_TESTS += $(top_srcdir)/codec/tests.at autotest_TESTS += $(top_srcdir)/hash/tests.at autotest_TESTS += $(top_srcdir)/struct/tests.at autotest_TESTS += $(top_srcdir)/sys/tests.at +autotest_TESTS += $(top_srcdir)/test/tests.at autotest_TESTS += $(top_srcdir)/utils/tests.at ###----- That's all, folks -------------------------------------------------- diff --git a/test/Makefile.am b/test/Makefile.am index e81daed..cf1a198 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -32,9 +32,25 @@ libtest_la_SOURCES = ###-------------------------------------------------------------------------- ### Component files. -## Testing. +## Benchmarking. +pkginclude_HEADERS += bench.h +libtest_la_SOURCES += bench.c + +## Old `testrig' testing framework. pkginclude_HEADERS += testrig.h libtest_la_SOURCES += testrig.c LIBMANS += testrig.3 +## New `tvec' testing framework. +pkginclude_HEADERS += tvec.h +libtest_la_SOURCES += tvec-core.c +libtest_la_SOURCES += tvec-output.c +libtest_la_SOURCES += tvec-types.c +libtest_la_SOURCES += tvec-main.c + +check_PROGRAMS += t/tvec.t +t_tvec_t_SOURCES = t/tvec-test.c +t_tvec_t_CPPFLAGS = $(TEST_CPPFLAGS) +t_tvec_t_LDFLAGS = -static + ###----- That's all, folks -------------------------------------------------- diff --git a/test/bench.c b/test/bench.c new file mode 100644 index 0000000..17ed4ed --- /dev/null +++ b/test/bench.c @@ -0,0 +1,487 @@ +/* -*-c-*- + * + * Benchmarking support + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "alloc.h" +#include "bench.h" +#include "bits.h" +#include "linreg.h" +#include "macros.h" + +/*----- Data structures ---------------------------------------------------*/ + +struct timer { + struct bench_timer _t; + const struct timer_ops *clkops, *cyops; + union { int fd; } u_cy; +}; + +struct timer_ops { + void (*now)(struct bench_time *t_out, struct timer *t); + void (*teardown)(struct timer *t); +}; + +/*----- Preliminaries -----------------------------------------------------*/ + +#define NS_PER_S 1000000000 + +static void PRINTF_LIKE(1, 2) debug(const char *fmt, ...) +{ + const char *p; + va_list ap; + + p = getenv("MLIB_BENCH_DEBUG"); + if (p && *p != 'n' && *p != '0') { + va_start(ap, fmt); + fputs("mLib BENCH: ", stderr); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + va_end(ap); + } +} + +/*----- The null clock ----------------------------------------------------*/ + +static void null_now(struct bench_time *t_out, struct timer *t) { ; } +static void null_teardown(struct timer *t) { ; } +static const struct timer_ops null_ops = { null_now, null_teardown }; + +static int null_cyinit(struct timer *t) + { t->cyops = &null_ops; return (0); } + +#define NULL_CYENT { "null", null_cyinit }, + +/*----- Linux performance counters ----------------------------------------*/ + +#if defined(HAVE_LINUX_PERF_EVENT_H) && defined(HAVE_UINT64) + +#include +#include + +#include +#include + +static void perfevent_now(struct bench_time *t_out, struct timer *t) +{ + ssize_t n; + + n = read(t->u_cy.fd, &t_out->cy.i, sizeof(t_out->cy.i)); + if (n != sizeof(t_out->cy.i)) { + debug("failed to read perf-event counter: %s", strerror(errno)); + return; + } + t_out->f |= BTF_CYOK; +} + +static void perfevent_teardown(struct timer *t) + { close(t->u_cy.fd); } + +static const struct timer_ops perfevent_ops = + { perfevent_now, perfevent_teardown }; + +static int perfevent_init(struct timer *t) +{ + struct perf_event_attr attr = { 0 }; + struct bench_time tm; + + attr.type = PERF_TYPE_HARDWARE; + attr.size = sizeof(attr); + attr.config = PERF_COUNT_HW_CPU_CYCLES; + attr.disabled = 0; + attr.exclude_kernel = 1; + attr.exclude_hv = 1; + + t->u_cy.fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0); + if (t->u_cy.fd < 0) { + debug("couldn't open perf evvent: %s", strerror(errno)); + return (-1); + } + + tm.f = 0; perfevent_now(&tm, t); + if (!(tm.f&BTF_CYOK)) { close(t->u_cy.fd); return (-1); } + + t->cyops = &perfevent_ops; return (0); +} +# define PERFEVENT_CYENT { "linux-perf-event", perfevent_init }, +#else +# define PERFEVENT_CYENT +#endif + +/*----- Intel time-stamp counter ------------------------------------------*/ + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + +#define EFLAGS_ID (1u << 21) +#define CPUID_1D_TSC (1u << 4) + +static uint32 set_flags(unsigned long m, unsigned long x) +{ + unsigned long r; + +#ifdef __x86_64__ +# define TMP "%%rcx" +#else +# define TMP "%%ecx" +#endif + + __asm__ ("pushf\n\t" + "pop %0\n\t" + "mov %0, " TMP "\n\t" + "and %1, %0\n\t" + "xor %2, %0\n\t" + "push %0\n\t" + "popf\n\t" + "pushf\n\t" + "pop %0\n\t" + "push " TMP "\n\t" + "popf" + : "=r"(r) + : "g"(m), "g"(x) + : "ecx"); + return (r); +} + +struct cpuid { uint32 a, b, c, d; }; + +static void cpuid(struct cpuid *info_out, uint32 a, uint32 c) +{ + __asm__ ("movl %1, %%eax\n\t" + "movl %2, %%ecx\n\t" + "cpuid\n\t" + "movl %%eax, 0(%0)\n\t" + "movl %%ebx, 4(%0)\n\t" + "movl %%ecx, 8(%0)\n\t" + "movl %%edx, 12(%0)\n\t" + : /* no outputs */ + : "r"(info_out), "g"(a), "g"(c) + : "eax", "ebx", "ecx", "edx", "cc"); +} + +static void x86rdtsc_now(struct bench_time *t_out, struct timer *t) +{ + uint32 lo, hi; + + __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi)); + SET64(t_out->cy, hi, lo); t_out->f |= BTF_CYOK; +} + +static const struct timer_ops x86rdtsc_ops = + { x86rdtsc_now, null_teardown }; + +static int x86rdtsc_init(struct timer *t) +{ + struct cpuid info; + + if ((set_flags(~EFLAGS_ID, 0)&EFLAGS_ID) || + !(set_flags(~EFLAGS_ID, EFLAGS_ID)&EFLAGS_ID)) + { debug("no `cpuid' instruction"); return (-1); } + cpuid(&info, 0, 0); + if (info.a < 1) { debug("no `cpuid' leaf 1"); return (-1); } + cpuid(&info, 1, 0); + if (!(info.d&CPUID_1D_TSC)) + { debug("no `rdtsc' instrunction"); return (-1); } + t->cyops = &x86rdtsc_ops; return (0); +} + +# define X86RDTSC_CYENT { "x86-rdtsc", x86rdtsc_init }, +#else +# define X86RDTWC_CYENT +#endif + +/*----- POSIX `clock_gettime' ---------------------------------------------*/ + +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_THREAD_CPUTIME_ID) + +static void gettime_now(struct bench_time *t_out, struct timer *t) +{ + struct timespec now; + + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now)) + { debug("error reading POSIX clock: %s", strerror(errno)); return; } + ASSIGN64(t_out->s, now.tv_sec); t_out->ns = now.tv_nsec; + t_out->f |= BTF_TIMEOK; +} + +static const struct timer_ops gettime_ops = { gettime_now, null_teardown }; + +static int gettime_init(struct timer *t) +{ + struct bench_time tm; + + tm.f = 0; gettime_now(&tm, t); if (!tm.f&BTF_TIMEOK) return (-1); + t->clkops = &gettime_ops; return (0); +} + +# define GETTIME_CLKENT { "posix-clock_gettime", gettime_init }, +#else +# define GETTIME_CLKENT +#endif + +/*----- Standard C `clock' ------------------------------------------------*/ + +static void clock_now(struct bench_time *t_out, struct timer *t) +{ + clock_t now, x; + unsigned long s; uint32 ns; + + now = clock(); + if (now == (clock_t)-1) { + debug("error reading standard clock: %s", strerror(errno)); + return; + } + x = now/CLOCKS_PER_SEC; + if (x > ULONG_MAX) { debug("standard clock out of range"); return; } + + s = x; x = now - CLOCKS_PER_SEC*s; + if (!(NS_PER_S%CLOCKS_PER_SEC)) + ns = x*(NS_PER_S/CLOCKS_PER_SEC); + else if (NS_PER_S <= ULONG_MAX/CLOCKS_PER_SEC) + ns = (x*NS_PER_S)/CLOCKS_PER_SEC; + else + ns = x*((NS_PER_S + 0.0)/CLOCKS_PER_SEC); + ASSIGN64(t_out->s, s); t_out->ns = ns; t_out->f |= BTF_TIMEOK; +} + +static const struct timer_ops clock_ops = { clock_now, null_teardown }; + +static int clock_init(struct timer *t) +{ + struct bench_time tm; + + tm.f = 0; clock_now(&tm, t); if (!tm.f&BTF_TIMEOK) return (-1); + t->clkops = &clock_ops; return (0); +} + +#define CLOCK_CLKENT { "clock", clock_init }, + +/*----- Timing setup ------------------------------------------------------*/ + +static const struct timerent { + const char *name; + int (*init)(struct timer */*t*/); +} + clktab[] = { GETTIME_CLKENT CLOCK_CLKENT { 0, 0 } }, + cytab[] = { PERFEVENT_CYENT X86RDTSC_CYENT NULL_CYENT { 0, 0 } }; + +static const struct timerent *find_timer_n(const char *name, size_t sz, + const struct timerent *timers, + const char *what) +{ + while (timers->name) { + if (strlen(timers->name) == sz && MEMCMP(name, ==, timers->name, sz)) + return (timers); + timers++; + } + debug("%s timer `%.*s' not found", what, (int)sz, name); return (0); +} + +static int try_timer(struct timer *t, + const struct timerent *timer, const char *what) +{ + if (timer->init(t)) return (-1); + debug("selected %s timer `%s'", what, timer->name); return (0); +} + +static int select_timer(struct timer *t, const struct timerent *timers, + const char *varname, const char *what) +{ + const char *p; size_t n; + const struct timerent *timer; + + p = getenv(varname); + if (!p) { + while (timers->name) + if (!try_timer(t, timers++, what)) return (0); + } else { + for (;;) { + n = strcspn(p, ","); + timer = find_timer_n(p, n, timers, what); + if (timer && !try_timer(t, timer, what)) return (0); + if (!p[n]) break; + p += n + 1; + } + } + debug("no suitable %s timer found", what); return (-1); +} + +static void timer_now(struct bench_timer *tm, struct bench_time *t_out) +{ + struct timer *t = (struct timer *)tm; + + t->clkops->now(t_out, t); + t->cyops->now(t_out, t); +} + +static void timer_destroy(struct bench_timer *tm) +{ + struct timer *t = (struct timer *)tm; + + if (!t) return; + if (t->clkops) t->clkops->teardown(t); + if (t->cyops) t->cyops->teardown(t); + xfree(t); +} + +static const struct bench_timerops timer_ops = { timer_now, timer_destroy }; + +struct bench_timer *bench_createtimer(void) +{ + struct timer *t = 0; + struct bench_timer *ret = 0; + + t = xmalloc(sizeof(*t)); t->cyops = 0; t->clkops = 0; + if (select_timer(t, clktab, "MLIB_BENCH_CLKTIMER", "clock")) goto end; + if (select_timer(t, cytab, "MLIB_BENCH_CYCLETIMER", "cycle")) goto end; + t->_t.ops = &timer_ops; ret = &t->_t; t = 0; +end: + if (t) timer_destroy(&t->_t); + return (ret); +} + +#ifdef HAVE_UINT64 +# define FLOATK64(k) ((double)(k).i) +#else +# define FLOATK64(k) ((double)(k).lo + 4275123318.0*(double)(k).hi) +#endif + +static void timer_diff(struct bench_timing *delta_out, + const struct bench_time *t0, + const struct bench_time *t1) +{ + delta_out->f = t0->f&t1->f; + kludge64 k; + + if (!(delta_out->f&BTF_TIMEOK)) + delta_out->t = 0.0; + else { + SUB64(k, t1->s, t0->s); + delta_out->t = FLOATK64(k) - 1 + + (t1->ns + NS_PER_S - t0->ns)/(double)NS_PER_S; + } + + if (!(delta_out->f&BTF_CYOK)) + delta_out->cy = 0.0; + else { + SUB64(k, t1->cy, t0->cy); + delta_out->cy = FLOATK64(k); + } +} + +/*----- Calibration -------------------------------------------------------*/ + +void bench_init(struct bench_state *b, struct bench_timer *tm) + { b->tm = tm; b->target_s = 1.0; b->f = 0; } + +void bench_destroy(struct bench_state *b) + { b->tm->ops->destroy(b->tm); } + +static void do_nothing(unsigned long n, void *p) + { while (n--) RELAX; } + +int bench_calibrate(struct bench_state *b) +{ + struct linreg lr_clk = LINREG_INIT, lr_cy = LINREG_INIT; + unsigned long n; + unsigned i; + struct bench_timer *tm = b->tm; + struct bench_time t0, t1; + struct bench_timing delta; + bench_fn *fn = LAUNDER(&do_nothing); + unsigned f = BTF_ANY; + int rc; + + if (b->f&BTF_ANY) return (0); + + for (i = 0; i < 10; i++) + { tm->ops->now(tm, &t0); fn(1, 0); tm->ops->now(tm, &t1); } + + debug("calibrating..."); + n = 1; + for (;;) { + tm->ops->now(tm, &t0); fn(n, 0); tm->ops->now(tm, &t1); + timer_diff(&delta, &t0, &t1); f &= delta.f; + if (!(f&BTF_TIMEOK)) { rc = -1; goto end; } + linreg_update(&lr_clk, n, delta.t); + if (!(f&BTF_CYOK)) + debug(" n = %10lu; t = %12g s", n, delta.t); + else { + linreg_update(&lr_cy, n, delta.cy); + debug(" n = %10lu; t = %12g s, cy = %10.0f", n, delta.t, delta.cy); + } + if (delta.t >= b->target_s/20.0) break; + if (n >= ULONG_MAX - n/3) break; + n += n/3 + 1; + } + + linreg_fit(&lr_clk, &b->clk.m, &b->clk.c, 0); + debug("clock overhead = (%g n + %g) s", b->clk.m, b->clk.c); + if (f&BTF_CYOK) { + linreg_fit(&lr_clk, &b->clk.m, &b->clk.c, 0); + debug("cycle overhead = (%g n + %g) cy", b->cy.m, b->cy.c); + } + b->f |= f; rc = 0; +end: + return (rc); +} + +int bench_measure(struct bench_timing *t_out, struct bench_state *b, + bench_fn *fn, void *p) +{ + struct bench_timer *tm = b->tm; + struct bench_time t0, t1; + unsigned long n; + + if (bench_calibrate(b)) return (-1); + debug("measuring..."); n = 1; + for (;;) { + tm->ops->now(tm, &t0); fn(n, p); tm->ops->now(tm, &t1); + timer_diff(t_out, &t0, &t1); + if (!(t_out->f&BTF_TIMEOK)) return (-1); + if (!(t_out->f&BTF_CYOK)) debug(" n = %10lu; t = %12g", n, t_out->t); + else debug(" n = %10lu; t = %12g, cy = %10.0f", n, t_out->t, t_out->cy); + if (t_out->t >= 0.72*b->target_s) break; + n *= 1.44*b->target_s/t_out->t; + } + if (!(t_out->f&BTF_CYOK)) + debug(" %g s per op; %g ops/s", t_out->t/n, n/t_out->t); + else + debug(" %g s (%g cy) per op; %g ops/s", + t_out->t/n, t_out->cy/n, n/t_out->t); + t_out->n = n; return (0); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/test/bench.h b/test/bench.h new file mode 100644 index 0000000..f9472cd --- /dev/null +++ b/test/bench.h @@ -0,0 +1,94 @@ +/* -*-c-*- + * + * Benchmarking support + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef MLIB_BENCH_H +#define MLIB_BENCH_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#ifndef MLIB_BITS_H +# include "bits.h" +#endif + +/*----- Data structures ---------------------------------------------------*/ + +struct bench_time { + unsigned f; +#define BTF_TIMEOK 1u +#define BTF_CYOK 2u +#define BTF_ANY (BTF_TIMEOK | BTF_CYOK) + kludge64 s; uint32 ns; + kludge64 cy; +}; + +struct bench_timing { + unsigned f; + unsigned long n; + double t, cy; +}; + +struct bench_timer { const struct bench_timerops *ops; }; + +struct bench_timerops { + void (*now)(struct bench_timer */*bt*/, struct bench_time */*t_out*/); + void (*destroy)(struct bench_timer */*bt*/); +}; + +struct bench_state { + struct bench_timer *tm; + double target_s; + unsigned f; + struct { double m, c; } clk, cy; +}; + +typedef void bench_fn(unsigned long /*n*/, void */*p*/); + +/*----- Functions provided ------------------------------------------------*/ + +extern struct bench_timer *bench_createtimer(void); + +extern void bench_init(struct bench_state *b, struct bench_timer *tm); + +extern void bench_destroy(struct bench_state *b); + +extern int bench_calibrate(struct bench_state */*b*/); + +extern int bench_measure(struct bench_timing */*t_out*/, + struct bench_state */*b*/, + bench_fn */*fn*/, void */*p*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/test/t/tvec-test.c b/test/t/tvec-test.c new file mode 100644 index 0000000..6bcf025 --- /dev/null +++ b/test/t/tvec-test.c @@ -0,0 +1,274 @@ +/* -*-c-*- + * + * Test the test-vector framework + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "tvec.h" + +/*----- Register definitions ----------------------------------------------*/ + +enum { + /* Standard outputs. */ + RRC, /* return code from deserialize */ + + /* Output registers, one for each register type. */ + RI, RU, RIE, RUE, RPE, RF, RSTR, RBY, RBUF, + + /* Additional diagnostic outputs. */ + RSER, /* serialized data */ + + NROUT, + + /* Some additional inputs. */ + RSAB = NROUT, /* which register to sabotage */ + + NREG, + + /* Single register for copy tests. */ + RV = 0 +}; + +static const struct tvec_iassoc ienum_assocs[] = { + { "less", -1 }, + { "equal", 0 }, + { "greater", +1 }, + { 0 } +}; + +static const struct tvec_uassoc uenum_assocs[] = { + { "apple", 0 }, + { "banana", 1 }, + { "clementine", 2 }, + { 0 } +}; + +static const struct tvec_passoc penum_assocs[] = { + { "alice", &uenum_assocs[0] }, + { "bob", &uenum_assocs[1] }, + { "carol", &uenum_assocs[2] }, + { 0 } +}; + +#if __STDC_VERSION__ >= 199901 +# define DSGINIT(x) x +#else +# define DSGINIT(x) +#endif + +static DSGINIT(const) struct tvec_enuminfo + ienum_info = { "order", TVMISC_INT, + DSGINIT({ .i = { ienum_assocs COMMA &tvrange_i16 } }) }, + uenum_info = { "fruit", TVMISC_UINT, + DSGINIT({ .u = { uenum_assocs COMMA &tvrange_u16 } }) }, + penum_info = { "player", TVMISC_PTR, + DSGINIT({ .p = { penum_assocs } }) }; + +static const struct tvec_flag attr_flags[] = { + { "black-fg", 0x07, 0x00 }, + { "blue-fg", 0x07, 0x01 }, + { "red-fg", 0x07, 0x02 }, + { "magenta-fg", 0x07, 0x03 }, + { "green-fg", 0x07, 0x04 }, + { "cyan-fg", 0x07, 0x05 }, + { "yellow-fg", 0x07, 0x06 }, + { "white-fg", 0x07, 0x07 }, + + { "black-bg", 0x38, 0x00 }, + { "blue-bg", 0x38, 0x08 }, + { "red-bg", 0x38, 0x10 }, + { "magenta-bg", 0x38, 0x18 }, + { "green-bg", 0x38, 0x20 }, + { "cyan-bg", 0x38, 0x28 }, + { "yellow-bg", 0x38, 0x30 }, + { "white-bg", 0x38, 0x38 }, + + { "normal", 0xc0, 0x00 }, + { "bright", 0x40, 0x40 }, + { "flash", 0x80, 0x80 }, + + { 0 } +}; + +static const struct tvec_flaginfo attr_info = + { "attr", attr_flags, &tvrange_u16 }; + +static const struct tvec_urange range_32 = { 0, 31 }; + +#define TYPEREGS(_) \ + _(int, RI, int, p, &tvrange_i16) \ + _(uint, RU, uint, p, &tvrange_u16) \ + _(ienum, RIE, enum, p, &ienum_info) \ + _(uenum, RUE, enum, p, &uenum_info) \ + _(penum, RPE, enum, p, &penum_info) \ + _(flags, RF, flags, p, &attr_info) \ + _(string, RSTR, string, p, &range_32) \ + _(bytes, RBY, bytes, p, &tvrange_byte) \ + _(buffer, RBUF, buffer, p, &tvrange_u16) + +/*----- Serialization test ------------------------------------------------*/ + +struct test_context { + struct tvec_state *tv; +}; + +static void capture_state_and_run(struct tvec_state *tv) +{ + struct test_context tctx; + + tctx.tv = tv; tv->test->fn(tv->in, tv->out, &tctx); + if (!(tv->in[RRC].f&TVRF_LIVE)) { + tv->in[RRC].v.i = 0; + tv->in[RRC].f |= TVRF_LIVE; tv->out[RRC].f |= TVRF_LIVE; + } + tvec_check(tv, 0); +} + +static void test_serialization + (const struct tvec_reg *in, struct tvec_reg *out, void *ctx) +{ + struct test_context *tctx = ctx; + struct tvec_state *tv = tctx->tv; + const struct tvec_regdef *rd; + union tvec_regval *rv; + void *p; size_t sz; + + if (tvec_serialize(tv->in, tv->test->regs, + NROUT, sizeof(struct tvec_reg), &p, &sz)) + { out[RRC].v.i = -1; return; } + out[RSER].f |= TVRF_LIVE; + out[RSER].v.bytes.p = p; out[RSER].v.bytes.sz = sz; + + if (tvec_deserialize(tv->out, tv->test->regs, + NROUT, sizeof(struct tvec_reg), p, sz)) + { out[RRC].v.i = -1; return; } + + if (in[RSAB].f&TVRF_LIVE) { + for (rd = tv->test->regs; rd->name; rd++) + if (STRCMP(in[RSAB].v.str.p, ==, rd->name)) { + rv = &out[rd->i].v; + if (rd->ty == &tvty_int || + (rd->ty == &tvty_enum && + ((struct tvec_enuminfo *)rd->arg.p)->mv == TVMISC_INT)) + rv->i ^= 1; + else if (rd->ty == &tvty_uint || rd->ty == &tvty_flags || + (rd->ty == &tvty_enum && + ((struct tvec_enuminfo *)rd->arg.p)->mv == TVMISC_INT)) + rv->u ^= 1; + else if (rd->ty == &tvty_string) + { if (rv->str.sz) rv->str.p[0] ^= 1; } + else if (rd->ty == &tvty_bytes) + { if (rv->bytes.sz) rv->bytes.p[0] ^= 1; } + } + } + + out[RRC].v.i = 0; +} + +DSGINIT(static) const struct tvec_regdef test_regs[] = { +#define DEFREG(name, i, ty, argslot, argval) \ + { #name, i, &tvty_##ty, TVRF_OPT, \ + DSGINIT({ .argslot = argval }) }, + TYPEREGS(DEFREG) +#undef DEFREG + { "rc", RRC, &tvty_int, TVRF_OPT, { &tvrange_int } }, + { "serialized", RSER, &tvty_bytes, TVRF_OPT }, + { "sabotage", RSAB, &tvty_string, TVRF_OPT, { &tvrange_byte } }, + + { 0 } +}; + +/*----- Single-type copy tests --------------------------------------------*/ + +static void test_copy_simple + (const struct tvec_reg *in, struct tvec_reg *out, void *ctx) + { out->v = in->v; } + +static void test_copy_string + (const struct tvec_reg *in, struct tvec_reg *out, void *ctx) +{ + tvec_allocstring(&out->v, in->v.str.sz); + memcpy(out->v.str.p, in->v.str.p, in->v.str.sz); +} + +static void test_copy_bytes + (const struct tvec_reg *in, struct tvec_reg *out, void *ctx) +{ + tvec_allocstring(&out->v, in->v.str.sz); + memcpy(out->v.str.p, in->v.str.p, in->v.str.sz); +} + +#define test_copy_int test_copy_simple +#define test_copy_uint test_copy_simple +#define test_copy_ienum test_copy_simple +#define test_copy_uenum test_copy_simple +#define test_copy_penum test_copy_simple +#define test_copy_flags test_copy_simple +#define test_copy_buffer test_copy_bytes + +#define SINGLEREG(name, i, ty, argslot, argval) \ + DSGINIT(const) struct tvec_regdef name##_regs[] = { \ + { #name, RV, &tvty_##ty, 0, DSGINIT({ .argslot = argval }) }, \ + { 0 } \ + }; +TYPEREGS(SINGLEREG) +#undef SINGLEREG + +/*----- Front end ---------------------------------------------------------*/ + +static const struct tvec_test tests[] = { + { "types", test_regs, 0, capture_state_and_run, + test_serialization }, + +#define DEFCOPY(name, i, ty, argslot, argval) \ + { #name, name##_regs, 0, tvec_runtest, test_copy_##name }, + TYPEREGS(DEFCOPY) +#undef DEFCOPY + + { 0 } +}; + +static const struct tvec_info testinfo = { + tests, + NROUT, NREG, sizeof(struct tvec_reg) +}; + +int main(int argc, char *argv[]) +{ +#if __STDC_VERSION__ < 199901 +# define POKE(tag, ty, slot) \ + slot##enum_info.u.slot.av = slot##enum_assocs; \ + TVEC_MISCSLOTS(POKE) +# undef POKE +# define POKE(name, i, ty, argslot, argval) \ + name##_regs->arg.argslot = argval; + TYPEREGS(POKE) +# undef POKE +#endif + return (tvec_main(argc, argv, &testinfo, 0)); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tests.at b/test/tests.at new file mode 100644 index 0000000..7088199 --- /dev/null +++ b/test/tests.at @@ -0,0 +1,132 @@ +### -*-autotest-*- +### +### Test script for test machinery +### +### (c) 2023 Straylight/Edgeware +### + +###----- Licensing notice --------------------------------------------------- +### +### This file is part of the mLib utilities library. +### +### mLib is free software; you can redistribute it and/or modify +### it under the terms of the GNU Library General Public License as +### published by the Free Software Foundation; either version 2 of the +### License, or (at your option) any later version. +### +### mLib is distributed in the hope that it will be useful, +### but WITHOUT ANY WARRANTY; without even the implied warranty of +### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +### GNU Library General Public License for more details. +### +### You should have received a copy of the GNU Library General Public +### License along with mLib; if not, write to the Free +### Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, +### MA 02111-1307, USA. + +###-------------------------------------------------------------------------- +### tvec + +dnl test_filter(CMD, SED, RC, STDOUT, STDERR) +m4_define([test_filter], [ +AT_CHECK([$1], [$3], [stdout-nolog], [stderr-nolog]) +##mv stdout rawout; mv stderr rawerr +AT_CHECK([sed "AS_ESCAPE([$2])" stdout], [0], [$4]) +AT_CHECK([sed "AS_ESCAPE([$2])" stderr], [0], [$5])]) + +dnl mismatch_filter +m4_define([mismatch_filter], + [s/\(@%:@<@<:@0-9a-zA-Z_-@:>@*\) @<:@^>@:>@*>/\1 ...>/g]) + +dnl test_parse(TY, IN, OUT) +m4_define([test_parse], [ +AT_DATA([tv], +[;;; -*-conf-*- +[[$1]] +$1 = $2 +@status = ? +]) +test_filter([BUILDDIR/t/tvec.t -fh tv], [mismatch_filter], [1], +[tv:3: `$1' FAILED + actual status = `.' + expected status = `?' + matched $1 = $3 +$1: 1/1 FAILED +FAILED 1 out of 1 test in 1 out of 1 group +])]) + +dnl test_parserr(TY, IN, LNO, ERR) +m4_define([test_parserr], [ +AT_DATA([tv], +[;;; -*-conf-*- +[[$1]] +$1 = $2 +@status = ? +]) +AT_CHECK([BUILDDIR/t/tvec.t -fh tv], [2], [], +[tvec.t: tv:$3: $4 +])]) + +AT_SETUP(tvec type-int) +test_parse([int], [4], [4 ; = 0x04]) +test_parse([int], [ 17; comment], [17 ; = 0x11]) +test_parserr([int], [17 : badness], [3], + [syntax error: expected end-of-line but found `:']) +test_parserr([int], [17: badness], [3], + [syntax error: expected end-of-line but found `:']) +test_parse([int], [0x234], [564 ; = 0x0234]) +test_parse([int], [033], [27 ; = 0x1b]) +test_parse([int], [ +192], [192 ; = 0xc0]) +test_parse([int], [ -192], [-192 ; = -0xc0]) +test_parserr([int], [xyzzy], [3], + [syntax error: expected signed integer but found `x']) +test_parserr([int], [-splat], [3], + [syntax error: expected signed integer but found `s']) +test_parserr([int], [0xq], [3], + [syntax error: expected end-of-line but found `x']) +test_parserr([int], [0x], [3], + [syntax error: expected end-of-line but found `x']) +test_parserr([int], [], [3], + [syntax error: expected signed integer but found ]) +test_parserr([int], [123456], [3], + [integer 123456 out of range (must be in [[-32768 .. 32767]])]) +AT_CLEANUP + +AT_SETUP(tvec type-uint) +test_parse([uint], [4], [4 ; = 0x04]) +test_parse([uint], [ 17; comment], [17 ; = 0x11]) +test_parserr([uint], [17 : badness], [3], + [syntax error: expected end-of-line but found `:']) +test_parserr([uint], [17: badness], [3], + [syntax error: expected end-of-line but found `:']) +test_parse([uint], [0x234], [564 ; = 0x0234]) +test_parse([uint], [033], [27 ; = 0x1b]) +test_parserr([uint], [ +192], [3], + [syntax error: expected unsigned integer but found `+']) +test_parserr([uint], [ -192], [3], + [syntax error: expected unsigned integer but found `-']) +test_parserr([uint], [xyzzy], [3], + [syntax error: expected unsigned integer but found `x']) +test_parserr([uint], [0xq], [3], + [syntax error: expected end-of-line but found `x']) +test_parserr([uint], [0x], [3], + [syntax error: expected end-of-line but found `x']) +test_parserr([uint], [], [3], + [syntax error: expected unsigned integer but found ]) +test_parserr([uint], [123456], [3], + [integer 123456 out of range (must be in [[0 .. 65535]])]) +AT_CLEANUP + +AT_SETUP([tvec type-enum]) +test_parse([ienum], [less], [less ; = -1 = -0x01]) +test_parse([ienum], [+1], [greater ; = 1 = 0x01]) +test_parse([ienum], [17], [17 ; = 0x11]) +test_parse([uenum], [banana], [banana ; = 1 = 0x01]) +test_parse([uenum], [clementine], [clementine ; = 2 = 0x02]) +test_parse([uenum], [17], [17 ; = 0x11]) +test_parse([penum], [carol], [carol ; = @%:@]) +test_parse([penum], [alice], [alice ; = @%:@]) +test_parse([penum], [@%:@nil], [@%:@nil]) +AT_CLEANUP + +###----- That's all, folks -------------------------------------------------- diff --git a/test/tvec-core.c b/test/tvec-core.c new file mode 100644 index 0000000..dc2f8ab --- /dev/null +++ b/test/tvec-core.c @@ -0,0 +1,746 @@ +/* -*-c-*- + * + * Main test vector driver + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include + +#include "alloc.h" +#include "bench.h" +#include "tvec.h" + +/*----- Output ------------------------------------------------------------*/ + +void tvec_error(struct tvec_state *tv, const char *msg, ...) + { va_list ap; va_start(ap, msg); tvec_error_v(tv, msg, &ap); } +void tvec_error_v(struct tvec_state *tv, const char *msg, va_list *ap) + { tv->output->ops->error(tv->output, msg, ap); exit(2); } + +void tvec_notice(struct tvec_state *tv, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); tvec_notice_v(tv, msg, &ap); va_end(ap); +} +void tvec_notice_v(struct tvec_state *tv, const char *msg, va_list *ap) + { tv->output->ops->notice(tv->output, msg, ap); } + +void tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...) +{ + va_list ap; + va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap); +} +void tvec_syntax_v(struct tvec_state *tv, int ch, + const char *expect, va_list *ap) +{ + dstr d = DSTR_INIT; + char found[8]; + + switch (ch) { + case EOF: strcpy(found, ""); break; + case '\n': strcpy(found, ""); break; + default: + if (isprint(ch)) sprintf(found, "`%c'", ch); + else sprintf(found, "<#x%02x>", ch); + break; + } + dstr_vputf(&d, expect, ap); + tvec_error(tv, "syntax error: expected %s but found %s", expect, found); +} + +void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...) +{ + va_list ap; + va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap); +} +void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap) +{ + tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++; + tv->output->ops->skipgroup(tv->output, excuse, ap); +} + +static void set_outcome(struct tvec_state *tv, unsigned out) +{ + tv->f &= ~(TVSF_ACTIVE | TVSF_OUTMASK); + tv->f |= out << TVSF_OUTSHIFT; +} + +void tvec_skip(struct tvec_state *tv, const char *excuse, ...) +{ + va_list ap; + va_start(ap, excuse); tvec_skip_v(tv, excuse, &ap); va_end(ap); +} +void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap) +{ + assert(tv->f&TVSF_ACTIVE); + set_outcome(tv, TVOUT_SKIP); + tv->output->ops->skip(tv->output, excuse, ap); +} + +void tvec_fail(struct tvec_state *tv, const char *detail, ...) +{ + va_list ap; + va_start(ap, detail); tvec_fail_v(tv, detail, &ap); va_end(ap); +} +void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap) +{ + assert((tv->f&TVSF_ACTIVE) || + (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT)); + set_outcome(tv, TVOUT_LOSE); tv->output->ops->fail(tv->output, detail, ap); +} + +void tvec_mismatch(struct tvec_state *tv) + { tv->output->ops->mismatch(tv->output); } + +void tvec_write(struct tvec_state *tv, const char *p, ...) +{ + va_list ap; + va_start(ap, p); tvec_write_v(tv, p, &ap); va_end(ap); +} +void tvec_write_v(struct tvec_state *tv, const char *p, va_list *ap) +{ + dstr d = DSTR_INIT; + + dstr_vputf(&d, p, ap); tv->output->ops->write(tv->output, d.buf, d.len); + DDESTROY(&d); +} + +/*----- Serialization and deserialization ---------------------------------*/ + +int tvec_serialize(const struct tvec_reg *rv, + const struct tvec_regdef *regs, + unsigned nr, size_t regsz, + void **p_out, size_t *sz_out) +{ + void *p = 0; buf b; + unsigned char *bitmap; + size_t i, nbits, bitsz, sz; + const struct tvec_regdef *rd; + const struct tvec_reg *r; + int rc; + + for (rd = regs, nbits = 0, sz = 0; rd->name; rd++, nbits++) { + if (rd->i >= nr) continue; + r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue; + sz += rd->ty->measure(&r->v, rd); + } + bitsz = (nbits + 7)/8; sz += bitsz; + + p = xmalloc(sz); buf_init(&b, p, sz); + bitmap = buf_get(&b, bitsz); assert(bitmap); memset(bitmap, 0, bitsz); + for (rd = regs, i = 0; rd->name; rd++, i++) { + if (rd->i >= nr) continue; + r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue; + bitmap[rd->i/8] |= 1 << rd->i%8; + if (rd->ty->tobuf(&b, &r->v, rd)) { rc = -1; goto end; } + } + + if (BBAD(&b)) { rc = -1; goto end; } + *p_out = p; *sz_out = BLEN(&b); p = 0; rc = 0; +end: + xfree(p); + return (rc); +} + +int tvec_deserialize(struct tvec_reg *rv, + const struct tvec_regdef *regs, + unsigned nr, size_t regsz, + const void *p, size_t sz) +{ + buf b; + const unsigned char *bitmap; + size_t i, nbits, bitsz; + const struct tvec_regdef *rd; + struct tvec_reg *r; + int rc; + + for (rd = regs, nbits = 0; rd->name; rd++, nbits++); + bitsz = (nbits + 7)/8; sz += bitsz; + + buf_init(&b, (/*unconst*/ void *)p, sz); + bitmap = buf_get(&b, bitsz); if (!bitmap) { rc = -1; goto end; } + for (rd = regs, i = 0; rd->name; rd++, i++) { + if (rd->i >= nr) continue; + if (!(bitmap[rd->i/8]&(1 << rd->i%8))) continue; + r = TVEC_GREG(rv, rd->i, regsz); + if (rd->ty->frombuf(&b, &r->v, rd)) { rc = -1; goto end; } + r->f |= TVRF_LIVE; + } + + if (BBAD(&b)) { rc = -1; goto end; } + rc = 0; +end: + return (rc); +} + +/*----- Benchmarking ------------------------------------------------------*/ + +struct bench_state *tvec_benchstate; + +struct benchrun { + unsigned long *n; + tvec_testfn *fn; + const struct tvec_reg *in; struct tvec_reg *out; + void *ctx; +}; + +static void benchloop_outer(unsigned long n, void *p) + { struct benchrun *r = p; while (n--) r->fn(r->in, r->out, r->ctx); } + +static void benchloop_inner(unsigned long n, void *p) + { struct benchrun *r = p; *r->n = n; r->fn(r->in, r->out, r->ctx); } + +int tvec_ensurebench(struct tvec_state *tv, struct bench_state **b_out) +{ + const struct tvec_bench *tvb = tv->test->arg.p; + struct bench_state **bb; + struct bench_timer *bt; + + if (tvb->b) bb = tvb->b; + else bb = &tvec_benchstate; + + if (!*bb) { + bt = bench_createtimer(); + if (!bt) { tvec_skip(tv, "failed to create timer"); return (-1); } + *bb = xmalloc(sizeof(**bb)); bench_init(*bb, bt); + } else if (!(*bb)->tm) + { tvec_skip(tv, "failed to create timer"); return (-1); } + + *b_out = *bb; + return (0); +} + +void tvec_bench(struct tvec_state *tv) +{ + const struct tvec_bench *tvb = tv->test->arg.p; + struct bench_state *b; + struct bench_timing tm; + struct benchrun r; + bench_fn *loopfn; + + if (tvec_ensurebench(tv, &b)) goto end_0; + + r.in = tv->in; r.out = tv->out; r.fn = tv->test->fn; + if (tvb->ctxsz) r.ctx = xmalloc(tvb->ctxsz); + else r.ctx = 0; + if (tvb->setup && tvb->setup(tv->in, tv->out, &tvb->arg, r.ctx)) + { tvec_skip(tv, "benchmark setup failed"); goto end_1; } + + if (tvb->riter < 0) + { r.n = 0; loopfn = benchloop_outer; } + else + { r.n = &TVEC_REG(tv, in, tvb->riter)->v.u; loopfn = benchloop_inner; } + + tv->output->ops->bbench(tv->output); + if (bench_measure(&tm, b, loopfn, &r)) + { tv->output->ops->ebench(tv->output, 0); goto end_2; } + tv->output->ops->ebench(tv->output, &tm); + +end_2: + if (tvb->teardown) tvb->teardown(r.ctx); +end_1: + if (r.ctx) xfree(r.ctx); +end_0: + return; +} + +/*----- Main machinery ----------------------------------------------------*/ + +void tvec_skipspc(struct tvec_state *tv) +{ + int ch; + + for (;;) { + ch = getc(tv->fp); + if (ch == EOF) break; + else if (ch == '\n' || !isspace(ch)) { ungetc(ch, tv->fp); break; } + } +} + +void tvec_flushtoeol(struct tvec_state *tv, unsigned f) +{ + int ch; + + for (;;) { + ch = getc(tv->fp); + switch (ch) { + case '\n': tv->lno++; return; + case EOF: return; + case ';': f |= TVFF_ALLOWANY; break; + default: + if (!(f&TVFF_ALLOWANY) && !isspace(ch)) + tvec_syntax(tv, ch, "end-of-line"); + break; + } + } +} + +int tvec_nexttoken(struct tvec_state *tv) +{ + enum { TAIL, NEWLINE, INDENT, COMMENT }; + int ch; + unsigned s = TAIL; + + for (;;) { + ch = getc(tv->fp); + switch (ch) { + case EOF: + return (-1); + + case ';': + s = COMMENT; + break; + + case '\n': + if (s == NEWLINE || s == INDENT) { ungetc(ch, tv->fp); return (-1); } + else { tv->lno++; s = NEWLINE; } + break; + + default: + if (isspace(ch)) + { if (s == NEWLINE) s = INDENT; } + else if (s != COMMENT) { + ungetc(ch, tv->fp); + if (s == NEWLINE) return (-1); + else return (0); + } + break; + } + } +} + +int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims, + const char *expect, ...) +{ + va_list ap; + int rc; + + va_start(ap, expect); + rc = tvec_readword_v(tv, d, delims, expect, &ap); + va_end(ap); + return (rc); +} +int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims, + const char *expect, va_list *ap) +{ + int ch; + + ch = getc(tv->fp); + if (ch == '\n' || ch == EOF || ch == ';' || + (delims && strchr(delims, ch))) { + if (expect) tvec_syntax(tv, ch, expect, ap); + else { ungetc(ch, tv->fp); return (-1); } + } + if (d->len) DPUTC(d, ' '); + do { + DPUTC(d, ch); + ch = getc(tv->fp); + } while (ch != EOF && !isspace(ch) && (!delims || !strchr(delims, ch))); + DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp); + return (0); +} + +static void init_registers(struct tvec_state *tv) +{ + const struct tvec_regdef *rd; + struct tvec_reg *r; + + for (rd = tv->test->regs; rd->name; rd++) { + assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i); + rd->ty->init(&r->v, rd); r->f = 0; + if (rd->i < tv->nrout) + { r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; } + } + tv->expst = '.'; +} + +static void release_registers(struct tvec_state *tv) +{ + const struct tvec_regdef *rd; + struct tvec_reg *r; + + for (rd = tv->test->regs; rd->name; rd++) { + assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i); + rd->ty->release(&r->v, rd); r->f = 0; + if (rd->i < tv->nrout) + { r = TVEC_REG(tv, out, rd->i); rd->ty->release(&r->v, rd); r->f = 0; } + } +} + +void tvec_check(struct tvec_state *tv, const char *detail, ...) +{ + va_list ap; + va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap); +} +void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap) +{ + const struct tvec_regdef *rd; + const struct tvec_reg *rin, *rout; + unsigned f = 0; +#define f_mismatch 1u + + if (tv->expst != tv->st) f |= f_mismatch; + for (rd = tv->test->regs; rd->name; rd++) { + if (rd->i >= tv->nrout) continue; + rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i); + if (!rin->f&TVRF_LIVE) continue; + if (!rd->ty->eq(&rin->v, &rout->v, rd)) f |= f_mismatch; + } + if (!(f&f_mismatch)) return; + + tvec_fail_v(tv, detail, ap); + tvec_mismatch(tv); + +#undef f_mismatch +} + +void tvec_runtest(struct tvec_state *tv) +{ + tv->test->fn(tv->in, tv->out, (/*unconst*/ void *)tv->test->arg.p); + tvec_check(tv, 0); +} + +static void begin_test(struct tvec_state *tv) +{ + tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK; tv->st = '.'; + tv->output->ops->btest(tv->output); +} + +void tvec_endtest(struct tvec_state *tv) +{ + unsigned out; + + if (tv->f&TVSF_ACTIVE) out = TVOUT_WIN; + else out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT; + assert(out < TVOUT_LIMIT); tv->curr[out]++; + tv->output->ops->etest(tv->output, out); + tv->f &= ~TVSF_OPEN; +} + +static void check(struct tvec_state *tv) +{ + const struct tvec_regdef *rd; + + if (!(tv->f&TVSF_OPEN)) return; + + for (rd = tv->test->regs; rd->name; rd++) { + if (TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE) + { if (rd->i < tv->nrout) TVEC_REG(tv, out, rd->i)->f |= TVRF_LIVE; } + else if (!(rd->f&TVRF_OPT)) + tvec_error(tv, "required register `%s' not set in test `%s'", + rd->name, tv->test->name); + } + + if (!(tv->f&TVSF_SKIP)) + { begin_test(tv); tv->test->run(tv); tvec_endtest(tv); } + + tv->f &= ~TVSF_OPEN; release_registers(tv); init_registers(tv); +} + +static void begin_test_group(struct tvec_state *tv) +{ + unsigned i; + + tv->output->ops->bgroup(tv->output); + tv->f &= ~TVSF_SKIP; + init_registers(tv); + for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0; + if (tv->test->preflight) tv->test->preflight(tv); +} + +void tvec_reportgroup(struct tvec_state *tv) +{ + unsigned i, out, nrun; + + for (i = 0, nrun = 0; i < TVOUT_LIMIT; i++) + { nrun += tv->curr[i]; tv->all[i] += tv->curr[i]; } + + if (tv->curr[TVOUT_SKIP] == nrun) + { out = TVOUT_SKIP; tvec_skipgroup(tv, nrun ? 0 : "no tests to run"); } + else { + if (tv->curr[TVOUT_LOSE]) out = TVOUT_LOSE; + else out = TVOUT_WIN; + tv->grps[out]++; tv->output->ops->egroup(tv->output, out); + } +} + +static void end_test_group(struct tvec_state *tv) +{ + if (!tv->test) return; + if (tv->f&TVSF_OPEN) check(tv); + if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv); + release_registers(tv); tv->test = 0; +} + +void tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) +{ + dstr d = DSTR_INIT; + const struct tvec_test *test; + const struct tvec_regdef *rd; + struct tvec_reg *r; + int ch; + + tv->infile = infile; tv->lno = 1; tv->fp = fp; + + for (;;) { + ch = getc(tv->fp); + switch (ch) { + + case EOF: + goto end; + + case '[': + tvec_skipspc(tv); + DRESET(&d); tvec_readword(tv, &d, "];", "group name"); + for (test = tv->tests; test->name; test++) + if (STRCMP(d.buf, ==, test->name)) goto found_test; + tvec_error(tv, "unknown test group `%s'", d.buf); + found_test: + tvec_skipspc(tv); + ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'"); + tvec_flushtoeol(tv, 0); + end_test_group(tv); + tv->test = test; + begin_test_group(tv); + break; + + case '\n': + tv->lno++; + if (tv->f&TVSF_OPEN) check(tv); + break; + + default: + if (isspace(ch)) { + tvec_skipspc(tv); + ch = getc(tv->fp); + if (ch == EOF) + goto end; + else if (ch == ';') + { tvec_flushtoeol(tv, TVFF_ALLOWANY); continue; } + else + { tvec_flushtoeol(tv, 0); check(tv); } + } else if (ch == ';') + { tvec_flushtoeol(tv, TVFF_ALLOWANY); continue; } + else { + ungetc(ch, tv->fp); + DRESET(&d); tvec_readword(tv, &d, "=:;", "register name"); + tvec_skipspc(tv); ch = getc(tv->fp); + if (ch != '=' && ch != ':') tvec_syntax(tv, ch, "`=' or `:'"); + tvec_skipspc(tv); + if (d.buf[0] == '@') { + if (STRCMP(d.buf, ==, "@status")) { + if (!(tv->f&TVSF_OPEN)) + { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; } + ch = getc(tv->fp); + if (ch == EOF || ch == '\n' || ch == ';') + tvec_syntax(tv, ch, "status character"); + if (ch == '\\') { + ch = getc(tv->fp); + if (ch == EOF || ch == '\n') + tvec_syntax(tv, ch, "escaped status character"); + } + tv->expst = ch; + tvec_flushtoeol(tv, 0); + } else + tvec_error(tv, "unknown special register `%s'", d.buf); + } else { + if (!tv->test) tvec_error(tv, "no current test"); + for (rd = tv->test->regs; rd->name; rd++) + if (STRCMP(rd->name, ==, d.buf)) goto found_reg; + tvec_error(tv, "unknown register `%s' for test `%s'", + d.buf, tv->test->name); + found_reg: + if (!(tv->f&TVSF_OPEN)) + { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; } + tvec_skipspc(tv); + r = TVEC_REG(tv, in, rd->i); + if (r->f&TVRF_LIVE) + tvec_error(tv, "register `%s' already set", rd->name); + rd->ty->parse(&r->v, rd, tv); r->f |= TVRF_LIVE; + } + } + break; + } + } +end: + end_test_group(tv); + tv->infile = 0; tv->fp = 0; + dstr_destroy(&d); +} + +/*----- Ad-hoc testing ----------------------------------------------------*/ + +static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } }; + +static void fakerun(struct tvec_state *tv) + { assert(!"fake run function"); } +static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p) + { assert(!"fake test function"); } + +void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t) +{ + t->name = ""; t->regs = &no_regs; + t->preflight = 0; t->run = fakerun; t->fn = fakefn; t->arg.p = 0; + tv->tests = t; +} + +void tvec_begingroup(struct tvec_state *tv, const char *name, + const char *file, unsigned lno) +{ + struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->tests; + + t->name = name; tv->test = t; + tv->infile = file; tv->lno = tv->test_lno = lno; + begin_test_group(tv); +} + +void tvec_endgroup(struct tvec_state *tv) +{ + if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv); + tv->test = 0; +} + +void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno) +{ + tv->infile = file; tv->lno = tv->test_lno = lno; + begin_test(tv); tv->f |= TVSF_OPEN; +} + +struct adhoc_claim { + unsigned f; +#define ACF_FRESH 1u + const char *saved_file; unsigned saved_lno; +}; + +static void adhoc_claim_setup(struct tvec_state *tv, + struct adhoc_claim *ck, + const struct tvec_regdef *regs, + const char *file, unsigned lno) +{ + struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test; + + ck->f = 0; + + if (!(tv->f&TVSF_OPEN)) + { ck->f |= ACF_FRESH; tvec_begintest(tv, file, lno); } + + ck->saved_file = tv->infile; if (file) tv->infile = file; + ck->saved_lno = tv->test_lno; if (file) tv->test_lno = lno; + t->regs = regs ? regs : &no_regs; + + tv->st = '.'; +} + +static void adhoc_claim_teardown(struct tvec_state *tv, + struct adhoc_claim *ck) +{ + struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test; + + t->regs = &no_regs; + tv->infile = ck->saved_file; tv->test_lno = ck->saved_lno; + + if (ck->f&ACF_FRESH) tvec_endtest(tv); +} + +int tvec_claim(struct tvec_state *tv, int ok, + const char *file, unsigned lno, const char *expr, ...) +{ + struct adhoc_claim ck; + va_list ap; + + adhoc_claim_setup(tv, &ck, 0, file, lno); + if (!ok) + { va_start(ap, expr); tvec_fail_v(tv, expr, &ap); va_end(ap); } + adhoc_claim_teardown(tv, &ck); + return (ok); +} + +int tvec_claimeq(struct tvec_state *tv, + const struct tvec_regty *ty, const union tvec_misc *arg, + const char *file, unsigned lno, const char *expr) +{ + struct tvec_regdef regs[2]; + struct adhoc_claim ck; + int ok; + + tv->in[0].f = tv->out[0].f = TVRF_LIVE; + + regs[0].name = "value"; regs[0].i = 0; + regs[0].ty = ty; regs[0].f = 0; + if (arg) regs[0].arg = *arg; + else regs[0].arg.p = 0; + + regs[1].name = 0; + + adhoc_claim_setup(tv, &ck, regs, file, lno); + ok = ty->eq(&tv->in[0].v, &tv->out[0].v, ®s[0]); + if (!ok) { tvec_fail(tv, "%s", expr ); tvec_mismatch(tv); } + adhoc_claim_teardown(tv, &ck); + return (ok); +} + +/*----- Session lifecycle -------------------------------------------------*/ + +void tvec_begin(struct tvec_state *tv_out, + const struct tvec_info *info, + struct tvec_output *o) +{ + unsigned i; + + tv_out->f = 0; + + assert(info->nrout <= info->nreg); + tv_out->nrout = info->nrout; tv_out->nreg = info->nreg; + tv_out->regsz = info->regsz; + tv_out->in = xmalloc(tv_out->nreg*tv_out->regsz); + tv_out->out = xmalloc(tv_out->nrout*tv_out->regsz); + for (i = 0; i < tv_out->nreg; i++) { + TVEC_REG(tv_out, in, i)->f = 0; + if (i < tv_out->nrout) TVEC_REG(tv_out, out, i)->f = 0; + } + + for (i = 0; i < TVOUT_LIMIT; i++) + tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0; + + tv_out->tests = info->tests; tv_out->test = 0; + tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0; + o->tv = tv_out; tv_out->output = o; + + tv_out->output->ops->bsession(tv_out->output); +} + +int tvec_end(struct tvec_state *tv) +{ + int rc = tv->output->ops->esession(tv->output); + + tv->output->ops->destroy(tv->output); + xfree(tv->in); xfree(tv->out); + return (rc); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tvec-main.c b/test/tvec-main.c new file mode 100644 index 0000000..ea597ad --- /dev/null +++ b/test/tvec-main.c @@ -0,0 +1,234 @@ +/* -*-c-*- + * + * Main entry point for test-vector processing + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include + +#include "bench.h" +#include "dstr.h" +#include "macros.h" +#include "mdwopt.h" +#include "quis.h" +#include "report.h" +#include "tvec.h" + +/*----- Main code ---------------------------------------------------------*/ + +static const struct outform { + const char *name; + struct tvec_output *(*makefn)(FILE *fp); +} outtab[] = { + { "human", tvec_humanoutput }, + { "tap", tvec_tapoutput }, + { 0, 0 } +}; + +static struct bench_state benchstate; + +const struct tvec_info tvec_adhocinfo = + { 0, 1, 1, sizeof(struct tvec_reg) }; + +static const struct outform *find_outform(const char *p) +{ + const struct outform *best = 0, *of; + size_t n = strlen(p); + + for (of = outtab; of->name; of++) + if (!STRNCMP(p, ==, of->name, n)) + ; + else if (!of->name[n]) + return (of); + else if (best) + die(2, "ambiguous output format name (`%s' or `%s'?)", + best->name, of->name); + else + best = of; + if (best) return (best); + else die(2, "unknown output format `%s'", optarg); +} + +static void version(FILE *fp) + { pquis(fp, "$, mLib test-vector framework version " VERSION "\n"); } + +static void usage(FILE *fp) +{ + pquis(fp, "\ +usage: $ [-f FORMAT] [-o OUTPUT] [-t SECS] [TEST-FILE ...]\n\ +"); +} + +static void help(FILE *fp) +{ + version(fp); fputc('\n', fp); + usage(fp); fputs("\ +Options:\n\ + -h, --help show this help text.\n\ + -v, --version show version number.\n\ + -u, --usage show usage synopsis.\n\ +\n\ + -f, --format=FORMAT produce output in FORMAT.\n\ + -o, --output=OUTPUT write output to OUTPUT file.\n\ + -t, --target=SECS aim to run benchmarks for SECS.\n\ +", fp); +} + +void tvec_parseargs(int argc, char *argv[], struct tvec_state *tv_out, + int *argpos_out, const struct tvec_info *info) +{ + FILE *ofp = 0; + const struct outform *of = 0; + struct tvec_output *o; + struct bench_timer *tm; + const char *p; char *q; + int opt; + double t; + unsigned f = 0; +#define f_bogus 1u + + static const struct option options[] = { + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { "usage", 0, 0, 'u' }, + + { "format", OPTF_ARGREQ, 0, 'f' }, + { "output", OPTF_ARGREQ, 0, 'o' }, + { "target", OPTF_ARGREQ, 0, 't' }, + { 0, 0, 0, 0 } + }; + + tvec_benchstate = &benchstate; + tm = bench_createtimer(); bench_init(&benchstate, tm); + ego(argv[0]); + for (;;) { + opt = mdwopt(argc, argv, "hvu" "f:o:t:", options, 0, 0, 0); + if (opt < 0) break; + switch (opt) { + case 'h': help(stdout); exit(0); + case 'v': version(stdout); exit(0); + case 'u': usage(stdout); exit(0); + + case 'f': of = find_outform(optarg); break; + case 'o': + if (ofp) fclose(ofp); + ofp = fopen(optarg, "w"); + if (!ofp) + die(2, "failed to open `%s' for writing: %s", + optarg, strerror(errno)); + break; + case 't': + errno = 0; t = strtod(optarg, &q); + while (ISSPACE(*q)) q++; + if (errno || *q || t < 0) die(2, "invalid time `%s'", optarg); + benchstate.target_s = t; + break; + + default: + f |= f_bogus; + break; + } + } + if (f&f_bogus) { usage(stderr); exit(2); } + if (!ofp) ofp = stdout; + if (!of) { + p = getenv("TVEC_FORMAT"); + if (p) of = find_outform(p); + } + if (of) o = of->makefn(ofp); + else o = tvec_dfltout(ofp); + + tvec_begin(tv_out, info, o); *argpos_out = optind; +} + +void tvec_readstdin(struct tvec_state *tv) + { tvec_read(tv, "", stdin); } + +void tvec_readfile(struct tvec_state *tv, const char *file) +{ + FILE *fp; + + fp = fopen(file, "r"); + if (!fp) + tvec_error(tv, "failed to open `%s' for reading: %s", + file, strerror(errno)); + tvec_read(tv, file, fp); + fclose(fp); +} + +void tvec_readdflt(struct tvec_state *tv, const char *file) +{ + dstr d = DSTR_INIT; + const char *p; + + if (file) { + p = getenv("srcdir"); + if (p) + { dstr_putf(&d, "%s/%s", p, file); file = d.buf; } + tvec_readfile(tv, file); + } else if (isatty(0)) + tvec_error(tv, "use `-' to force reading from interactive stdin"); + else + tvec_readstdin(tv); +} + +void tvec_readarg(struct tvec_state *tv, const char *arg) +{ + if (STRCMP(arg, ==, "-")) tvec_readstdin(tv); + else tvec_readfile(tv, arg); +} + +void tvec_readargs(int argc, char *argv[], struct tvec_state *tv, + int *argpos_inout, const char *dflt) +{ + int i = *argpos_inout; + + + if (i == argc) tvec_readdflt(tv, dflt); + else while (i < argc) tvec_readarg(tv, argv[i++]); + *argpos_inout = i; +} + +int tvec_main(int argc, char *argv[], + const struct tvec_info *info, const char *dflt) +{ + struct tvec_state tv; + int argpos; + + tvec_parseargs(argc, argv, &tv, &argpos, info); + tvec_readargs(argc, argv, &tv, &argpos, dflt); + return (tvec_end(&tv)); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tvec-output.c b/test/tvec-output.c new file mode 100644 index 0000000..af120d9 --- /dev/null +++ b/test/tvec-output.c @@ -0,0 +1,874 @@ +/* -*-c-*- + * + * Test vector output management + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include +#include + +#include + +#include "alloc.h" +#include "bench.h" +#include "dstr.h" +#include "quis.h" +#include "report.h" +#include "tvec.h" + +/*----- Common machinery --------------------------------------------------*/ + +enum { INPUT, OUTPUT, MATCH, EXPECT, FOUND }; +struct mismatchfns { + void (*report_status)(unsigned /*disp*/, int /*st*/, + struct tvec_state */*tv*/); + void (*report_register)(unsigned /*disp*/, + const struct tvec_reg */*r*/, + const struct tvec_regdef */*rd*/, + struct tvec_state */*tv*/); +}; + +static const char *stdisp(unsigned disp) +{ + switch (disp) { + case MATCH: return "final"; + case EXPECT: return "expected"; + case FOUND: return "actual"; + default: abort(); + } +} + +static const char *regdisp(unsigned disp) +{ + switch (disp) { + case INPUT: return "input"; + case OUTPUT: return "output"; + case MATCH: return "matched"; + case EXPECT: return "expected"; + case FOUND: return "computed"; + default: abort(); + } +} + +static int getenv_boolean(const char *var, int dflt) +{ + const char *p; + + p = getenv(var); + if (!p) + return (dflt); + else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") || + STRCMP(p, ==, "t") || STRCMP(p, ==, "true") || + STRCMP(p, ==, "on") || STRCMP(p, ==, "force") || + STRCMP(p, ==, "1")) + return (1); + else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") || + STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") || + STRCMP(p, ==, "0")) + return (0); + else { + moan("unexpected value `%s' for boolean environment variable `%s'", + var, p); + return (dflt); + } +} + +static void basic_report_status(unsigned disp, int st, struct tvec_state *tv) + { tvec_write(tv, " %8s status = `%c'\n", stdisp(disp), st); } + +static void basic_report_register(unsigned disp, + const struct tvec_reg *r, + const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + tvec_write(tv, " %8s %s = ", regdisp(disp), rd->name); + if (r->f&TVRF_LIVE) rd->ty->dump(&r->v, rd, tv, 0); + else tvec_write(tv, "#"); + tvec_write(tv, "\n"); +} + +static const struct mismatchfns basic_mismatchfns = + { basic_report_status, basic_report_register }; + +static void dump_inreg(const struct tvec_regdef *rd, + const struct mismatchfns *fns, struct tvec_state *tv) + { fns->report_register(INPUT, TVEC_REG(tv, in, rd->i), rd, tv); } + +static void dump_outreg(const struct tvec_regdef *rd, + const struct mismatchfns *fns, struct tvec_state *tv) +{ + const struct tvec_reg + *rin = TVEC_REG(tv, in, rd->i), *rout = TVEC_REG(tv, out, rd->i); + + if (tv->st == '.') { + if (!(rout->f&TVRF_LIVE)) { + if (!(rin->f&TVRF_LIVE)) + fns->report_register(INPUT, rin, rd, tv); + else { + fns->report_register(FOUND, rout, rd, tv); + fns->report_register(EXPECT, rin, rd, tv); + } + } else { + if (!(rin->f&TVRF_LIVE)) fns->report_register(OUTPUT, rout, rd, tv); + else if (rd->ty->eq(&rin->v, &rout->v, rd)) + fns->report_register(MATCH, rin, rd, tv); + else { + fns->report_register(FOUND, rout, rd, tv); + fns->report_register(EXPECT, rin, rd, tv); + } + } + } +} + +static void mismatch(const struct mismatchfns *fns, struct tvec_state *tv) +{ + const struct tvec_regdef *rd; + + if (tv->st != tv->expst) { + fns->report_status(FOUND, tv->st, tv); + fns->report_status(EXPECT, tv->expst, tv); + } else if (tv->st != '.') + fns->report_status(MATCH, tv->st, tv); + + for (rd = tv->test->regs; rd->name; rd++) { + if (rd->i < tv->nrout) dump_outreg(rd, fns, tv); + else dump_inreg(rd, fns, tv); + } +} + +static void bench_summary(struct tvec_state *tv) +{ + const struct tvec_regdef *rd; + unsigned f = 0; +#define f_any 1u + + for (rd = tv->test->regs; rd->name; rd++) + if (rd->f&TVRF_ID) { + if (f&f_any) tvec_write(tv, ", "); + else f |= f_any; + tvec_write(tv, "%s = ", rd->name); + rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, tv, TVSF_COMPACT); + } + +#undef f_any +} + +static void normalize(double *x_inout, const char **unit_out, double scale) +{ + static const char + *const nothing = "", + *const big[] = { "k", "M", "G", "T", "P", "E", 0 }, + *const little[] = { "m", "µ", "n", "p", "f", "a", 0 }; + const char *const *u; + double x = *x_inout; + + if (x < 1) + for (u = little, x *= scale; x < 1 && u[1]; u++, x *= scale); + else if (x >= scale) + for (u = big, x /= scale; x >= scale && u[1]; u++, x /= scale); + else + u = ¬hing; + + *x_inout = x; *unit_out = *u; +} + +static void bench_report(struct tvec_state *tv, + const struct bench_timing *tm) +{ + const struct tvec_bench *b = tv->test->arg.p; + double n = (double)tm->n*b->niter; + double x, scale; + const char *u, *what, *whats; + + assert(tm->f&BTF_TIMEOK); + + if (b->rbuf == -1) { + tvec_write(tv, " -- %.0f iterations ", n); + what = "op"; whats = "ops"; scale = 1000; + } else { + n *= TVEC_REG(tv, in, b->rbuf)->v.bytes.sz; + x = n; normalize(&x, &u, 1024); tvec_write(tv, " -- %.3f %sB ", x, u); + what = whats = "B"; scale = 1024; + } + x = tm->t; normalize(&x, &u, 1000); + tvec_write(tv, "in %.3f %ss", x, u); + if (tm->f&BTF_CYOK) { + x = tm->cy; normalize(&x, &u, 1000); + tvec_write(tv, " (%.3f %scy)", x, u); + } + tvec_write(tv, ": "); + + x = n/tm->t; normalize(&x, &u, scale); + tvec_write(tv, "%.3f %s%s/s", x, u, whats); + x = tm->t/n; normalize(&x, &u, 1000); + tvec_write(tv, ", %.3f %ss/%s", x, u, what); + if (tm->f&BTF_CYOK) { + x = tm->cy/n; normalize(&x, &u, 1000); + tvec_write(tv, " (%.3f %scy/%s)", x, u, what); + } + tvec_write(tv, "\n"); +} + +/*----- Skeleton ----------------------------------------------------------*/ +/* +static void ..._error(struct tvec_output *o, const char *msg, va_list *ap) +static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap) +static void ..._write(struct tvec_output *o, const char *p, size_t sz) +static void ..._bsession(struct tvec_output *o) +static int ..._esession(struct tvec_output *o) +static void ..._bgroup(struct tvec_output *o) +static void ..._egroup(struct tvec_output *o, unsigned outcome) +static void ..._skipgroup(struct tvec_output *o, + const char *excuse, va_list *ap) +static void ..._btest(struct tvec_output *o) +static void ..._skip(struct tvec_output *o, const char *detail, va_list *ap) +static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap) +static void ..._mismatch(struct tvec_output *o) +static void ..._etest(struct tvec_output *o, unsigned outcome) +static void ..._bbench(struct tvec_output *o) +static void ..._ebench(struct tvec_output *o, const struct tvec_timing *t) +static void ..._destroy(struct tvec_output *o) + +static const struct tvec_outops ..._ops = { + ..._error, ..._notice, ..._write, + ..._bsession, ..._esession, + ..._bgroup, ..._egroup, ..._skip, + ..._btest, ..._skip, ..._fail, ..._mismatch, ..._etest, + ..._bbench, ..._ebench, + ..._destroy +}; +*/ +/*----- Human-readable output ---------------------------------------------*/ + +#define HAF_FGMASK 0x0f +#define HAF_FGSHIFT 0 +#define HAF_BGMASK 0xf0 +#define HAF_BGSHIFT 4 +#define HAF_FG 256u +#define HAF_BG 512u +#define HAF_BOLD 1024u +#define HCOL_BLACK 0u +#define HCOL_RED 1u +#define HCOL_GREEN 2u +#define HCOL_YELLOW 3u +#define HCOL_BLUE 4u +#define HCOL_MAGENTA 5u +#define HCOL_CYAN 6u +#define HCOL_WHITE 7u +#define HCF_BRIGHT 8u +#define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) +#define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) + +#define HA_WIN (HFG(GREEN)) +#define HA_LOSE (HFG(RED) | HAF_BOLD) +#define HA_SKIP (HFG(YELLOW)) + +struct human_output { + struct tvec_output _o; + FILE *fp; + dstr scoreboard; + unsigned attr; + unsigned f; +#define HOF_TTY 1u +#define HOF_DUPERR 2u +#define HOF_COLOUR 4u +#define HOF_PROGRESS 8u +}; + +static void set_colour(FILE *fp, int *sep_inout, + const char *norm, const char *bright, + unsigned colour) +{ + if (*sep_inout) putc(*sep_inout, fp); + fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7); + *sep_inout = ';'; +} + +static void setattr(struct human_output *h, unsigned attr) +{ + unsigned diff = h->attr ^ attr; + int sep = 0; + + if (!diff || !(h->f&HOF_COLOUR)) return; + fputs("\x1b[", h->fp); + + if (diff&HAF_BOLD) { + if (attr&HAF_BOLD) putc('1', h->fp); + else { putc('0', h->fp); diff = h->attr; } + sep = ';'; + } + if (diff&(HAF_FG | HAF_FGMASK)) { + if (attr&HAF_FG) + set_colour(h->fp, &sep, "3", "9", (attr&HAF_FGMASK) >> HAF_FGSHIFT); + else + { if (sep) putc(sep, h->fp); fputs("39", h->fp); sep = ';'; } + } + if (diff&(HAF_BG | HAF_BGMASK)) { + if (attr&HAF_BG) + set_colour(h->fp, &sep, "4", "10", (attr&HAF_BGMASK) >> HAF_BGSHIFT); + else + { if (sep) putc(sep, h->fp); fputs("49", h->fp); sep = ';'; } + } + + putc('m', h->fp); h->attr = attr; + +#undef f_any +} + +static void clear_progress(struct human_output *h) +{ + size_t i, n; + + if (h->f&HOF_PROGRESS) { + n = strlen(h->_o.tv->test->name) + 2 + h->scoreboard.len; + for (i = 0; i < n; i++) fputs("\b \b", h->fp); + h->f &= ~HOF_PROGRESS; + } +} + +static void write_scoreboard_char(struct human_output *h, int ch) +{ + switch (ch) { + case 'x': setattr(h, HA_LOSE); break; + case '_': setattr(h, HA_SKIP); break; + default: setattr(h, 0); break; + } + putc(ch, h->fp); setattr(h, 0); +} + +static void show_progress(struct human_output *h) +{ + const char *p, *l; + + if ((h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) { + fprintf(h->fp, "%s: ", h->_o.tv->test->name); + if (!(h->f&HOF_COLOUR)) + dstr_write(&h->scoreboard, h->fp); + else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++) + write_scoreboard_char(h, *p); + fflush(h->fp); h->f |= HOF_PROGRESS; + } +} + +static void report_location(struct human_output *h, FILE *fp, + const char *file, unsigned lno) +{ + unsigned f = 0; +#define f_flush 1u + +#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0) + + if (fp != h->fp) f |= f_flush; + + if (file) { + setattr(h, HFG(CYAN)); FLUSH(h->fp); fputs(file, fp); FLUSH(fp); + setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp); + setattr(h, HFG(CYAN)); FLUSH(h->fp); fprintf(fp, "%u", lno); FLUSH(fp); + setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp); + setattr(h, 0); FLUSH(h->fp); fputc(' ', fp); + } + +#undef f_flush +#undef FLUSH +} + +static void human_report(struct human_output *h, + const char *msg, va_list *ap) +{ + struct tvec_state *tv = h->_o.tv; + + fprintf(stderr, "%s: ", QUIS); + report_location(h, stderr, tv->infile, tv->lno); + vfprintf(stderr, msg, *ap); + fputc('\n', stderr); + + if (h->f&HOF_DUPERR) { + report_location(h, stderr, tv->infile, tv->lno); + vfprintf(h->fp, msg, *ap); + fputc('\n', h->fp); + } +} + +static void human_error(struct tvec_output *o, const char *msg, va_list *ap) +{ + struct human_output *h = (struct human_output *)o; + + if (h->f&HOF_PROGRESS) fputc('\n', h->fp); + human_report(h, msg, ap); +} + +static void human_notice(struct tvec_output *o, const char *msg, va_list *ap) +{ + struct human_output *h = (struct human_output *)o; + clear_progress(h); human_report(h, msg, ap); show_progress(h); +} + +static void human_write(struct tvec_output *o, const char *p, size_t sz) +{ + struct human_output *h = (struct human_output *)o; + fwrite(p, 1, sz, h->fp); +} + +static void human_bsession(struct tvec_output *o) { ; } + +static void report_skipped(struct human_output *h, unsigned n) +{ + if (n) { + fprintf(h->fp, " (%u ", n); + setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); + fputc(')', h->fp); + } +} + +static int human_esession(struct tvec_output *o) +{ + struct human_output *h = (struct human_output *)o; + struct tvec_state *tv = h->_o.tv; + unsigned + all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN], + all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE], + all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP], + all_run = all_win + all_lose, grps_run = grps_win + grps_lose; + + if (!all_lose) { + setattr(h, HA_WIN); fputs("PASSED", h->fp); setattr(h, 0); + fprintf(h->fp, " %s%u %s", + !(all_skip || grps_skip) ? "all " : "", + all_win, all_win == 1 ? "test" : "tests"); + report_skipped(h, all_skip); + fprintf(h->fp, " in %u %s", + grps_win, grps_win == 1 ? "group" : "groups"); + report_skipped(h, grps_skip); + } else { + setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0); + fprintf(h->fp, " %u out of %u %s", + all_lose, all_run, all_run == 1 ? "test" : "tests"); + report_skipped(h, all_skip); + fprintf(h->fp, " in %u out of %u %s", + grps_lose, grps_run, grps_run == 1 ? "group" : "groups"); + report_skipped(h, grps_skip); + } + fputc('\n', h->fp); + + return (tv->all[TVOUT_LOSE] ? 1 : 0); +} + +static void human_bgroup(struct tvec_output *o) +{ + struct human_output *h = (struct human_output *)o; + dstr_reset(&h->scoreboard); show_progress(h); +} + +static void human_grpsumm(struct human_output *h, unsigned outcome) +{ + struct tvec_state *tv = h->_o.tv; + unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE], + skip = tv->curr[TVOUT_SKIP], run = win + lose; + + if (lose) { + assert(outcome == TVOUT_LOSE); + fprintf(h->fp, " %u/%u ", lose, run); + setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0); + report_skipped(h, skip); + } else { + assert(outcome == TVOUT_WIN); + fputc(' ', h->fp); setattr(h, HA_WIN); fputs("ok", h->fp); setattr(h, 0); + report_skipped(h, skip); + } + fputc('\n', h->fp); +} + +static void human_egroup(struct tvec_output *o, unsigned outcome) +{ + struct human_output *h = (struct human_output *)o; + + if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS; + else fprintf(h->fp, "%s:", h->_o.tv->test->name); + human_grpsumm(h, outcome); +} + +static void human_skipgroup(struct tvec_output *o, + const char *excuse, va_list *ap) +{ + struct human_output *h = (struct human_output *)o; + + if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) { + h->f &= ~HOF_PROGRESS; + setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); + } else { + fprintf(h->fp, "%s: ", h->_o.tv->test->name); + setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); + } + if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); } + fputc('\n', h->fp); +} + +static void human_btest(struct tvec_output *o) + { struct human_output *h = (struct human_output *)o; show_progress(h); } + +static void human_skip(struct tvec_output *o, + const char *excuse, va_list *ap) +{ + struct human_output *h = (struct human_output *)o; + struct tvec_state *tv = h->_o.tv; + + clear_progress(h); + report_location(h, h->fp, tv->infile, tv->test_lno); + fprintf(h->fp, "`%s' ", tv->test->name); + setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); + if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); } + fputc('\n', h->fp); +} + +static void human_fail(struct tvec_output *o, + const char *detail, va_list *ap) +{ + struct human_output *h = (struct human_output *)o; + struct tvec_state *tv = h->_o.tv; + + clear_progress(h); + report_location(h, h->fp, tv->infile, tv->test_lno); + fprintf(h->fp, "`%s' ", tv->test->name); + setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0); + if (detail) { fputs(": ", h->fp); vfprintf(h->fp, detail, *ap); } + fputc('\n', h->fp); +} + +static void set_dispattr(struct human_output *h, unsigned disp) +{ + switch (disp) { + case EXPECT: setattr(h, HFG(GREEN)); break; + case FOUND: setattr(h, HFG(RED)); break; + default: setattr(h, 0); break; + } +} + +static void human_report_status(unsigned disp, int st, struct tvec_state *tv) +{ + struct human_output *h = (struct human_output *)tv->output; + + fprintf(h->fp, " %8s status = ", stdisp(disp)); + set_dispattr(h, disp); fprintf(h->fp, "`%c'", st); setattr(h, 0); + fputc('\n', h->fp); +} + +static void human_report_register(unsigned disp, + const struct tvec_reg *r, + const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + struct human_output *h = (struct human_output *)tv->output; + + fprintf(h->fp, " %8s %s = ", regdisp(disp), rd->name); + if (!(r->f&TVRF_LIVE)) + tvec_write(tv, "#"); + else { + set_dispattr(h, disp); + rd->ty->dump(&r->v, rd, tv, 0); + setattr(h, 0); + } + tvec_write(tv, "\n"); +} + +static const struct mismatchfns human_mismatchfns = + { human_report_status, human_report_register }; + +static void human_mismatch(struct tvec_output *o) +{ + struct human_output *h = (struct human_output *)o; + + if (h->f&HOF_COLOUR) mismatch(&human_mismatchfns, h->_o.tv); + else mismatch(&basic_mismatchfns, h->_o.tv); +} + +static void human_etest(struct tvec_output *o, unsigned outcome) +{ + struct human_output *h = (struct human_output *)o; + int ch; + + if (h->f&HOF_TTY) { + show_progress(h); + switch (outcome) { + case TVOUT_WIN: ch = '.'; break; + case TVOUT_LOSE: ch = 'x'; break; + case TVOUT_SKIP: ch = '_'; break; + default: abort(); + } + dstr_putc(&h->scoreboard, ch); + write_scoreboard_char(h, ch); fflush(h->fp); + } +} + +static void human_bbench(struct tvec_output *o) +{ + struct human_output *h = (struct human_output *)o; + struct tvec_state *tv = h->_o.tv; + + clear_progress(h); + fprintf(h->fp, "%s: ", tv->test->name); + bench_summary(tv); fflush(h->fp); +} + +static void human_ebench(struct tvec_output *o, + const struct bench_timing *tm) +{ + struct human_output *h = (struct human_output *)o; + bench_report(h->_o.tv, tm); +} + +static void human_destroy(struct tvec_output *o) +{ + struct human_output *h = (struct human_output *)o; + + if (h->f&HOF_DUPERR) fclose(h->fp); + dstr_destroy(&h->scoreboard); + xfree(h); +} + +static const struct tvec_outops human_ops = { + human_error, human_notice, human_write, + human_bsession, human_esession, + human_bgroup, human_egroup, human_skipgroup, + human_btest, human_skip, human_fail, human_mismatch, human_etest, + human_bbench, human_ebench, + human_destroy +}; + +struct tvec_output *tvec_humanoutput(FILE *fp) +{ + struct human_output *h; + const char *p; + + h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops; + h->f = 0; h->attr = 0; + + h->fp = fp; + if (fp != stdout && fp != stderr) h->f |= HOF_DUPERR; + + switch (getenv_boolean("TVEC_TTY", -1)) { + case 1: h->f |= HOF_TTY; break; + case 0: break; + default: + if (isatty(fileno(fp))) h->f |= HOF_TTY; + break; + } + switch (getenv_boolean("TVEC_COLOUR", -1)) { + case 1: h->f |= HOF_COLOUR; break; + case 0: break; + default: + if (h->f&HOF_TTY) { + p = getenv("TERM"); + if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR; + } + break; + } + + dstr_create(&h->scoreboard); + return (&h->_o); +} + +/*----- Perl's `Test Anything Protocol' -----------------------------------*/ + +struct tap_output { + struct tvec_output _o; + FILE *fp; + unsigned f; +#define TOF_FRESHLINE 1u +}; + +static void tap_report(struct tap_output *t, const char *msg, va_list *ap) +{ + struct tvec_state *tv = t->_o.tv; + + if (tv->infile) fprintf(t->fp, "%s:%u: ", tv->infile, tv->lno); + vfprintf(t->fp, msg, *ap); fputc('\n', t->fp); +} + +static void tap_error(struct tvec_output *o, const char *msg, va_list *ap) +{ + struct tap_output *t = (struct tap_output *)o; + fputs("Bail out! ", t->fp); tap_report(t, msg, ap); +} + +static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap) +{ + struct tap_output *t = (struct tap_output *)o; + fputs("## ", t->fp); tap_report(t, msg, ap); +} + +static void tap_write(struct tvec_output *o, const char *p, size_t sz) +{ + struct tap_output *t = (struct tap_output *)o; + const char *q, *l = p + sz; + + if (p == l) return; + if (t->f&TOF_FRESHLINE) fputs("## ", t->fp); + for (;;) { + q = memchr(p, '\n', l - p); if (!q) break; + fwrite(p, 1, q + 1 - p, t->fp); p = q + 1; + if (p == l) { t->f |= TOF_FRESHLINE; return; } + fputs("## ", t->fp); + } + fwrite(p, 1, l - p, t->fp); t->f &= ~TOF_FRESHLINE; +} + +static void tap_bsession(struct tvec_output *o) { ; } + +static unsigned tap_grpix(struct tap_output *t) +{ + struct tvec_state *tv = t->_o.tv; + + return (tv->grps[TVOUT_WIN] + + tv->grps[TVOUT_LOSE] + + tv->grps[TVOUT_SKIP]); +} + +static int tap_esession(struct tvec_output *o) +{ + struct tap_output *t = (struct tap_output *)o; + + fprintf(t->fp, "1..%u\n", tap_grpix(t)); + return (0); +} + +static void tap_bgroup(struct tvec_output *o) { ; } + +static void tap_egroup(struct tvec_output *o, unsigned outcome) +{ + struct tap_output *t = (struct tap_output *)o; + struct tvec_state *tv = t->_o.tv; + unsigned + grpix = tap_grpix(t), + win = tv->curr[TVOUT_WIN], + lose = tv->curr[TVOUT_LOSE], + skip = tv->curr[TVOUT_SKIP]; + + if (lose) { + assert(outcome == TVOUT_LOSE); + fprintf(t->fp, "not ok %u %s: FAILED %u/%u", + grpix, tv->test->name, lose, win + lose); + if (skip) fprintf(t->fp, " (skipped %u)", skip); + } else { + assert(outcome == TVOUT_WIN); + fprintf(t->fp, "ok %u %s: passed %u", grpix, tv->test->name, win); + if (skip) fprintf(t->fp, " (skipped %u)", skip); + } + fputc('\n', t->fp); +} + +static void tap_skipgroup(struct tvec_output *o, + const char *excuse, va_list *ap) +{ + struct tap_output *t = (struct tap_output *)o; + + fprintf(t->fp, "ok %u %s # SKIP", tap_grpix(t), t->_o.tv->test->name); + if (excuse) + { fputc(' ', t->fp); vfprintf(t->fp, excuse, *ap); } + fputc('\n', t->fp); +} + +static void tap_btest(struct tvec_output *o) { ; } + +static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap) +{ + struct tap_output *t = (struct tap_output *)o; + struct tvec_state *tv = t->_o.tv; + + fprintf(t->fp, "## %s:%u: `%s' skipped", + tv->infile, tv->test_lno, tv->test->name); + if (excuse) { fputs(": ", t->fp); vfprintf(t->fp, excuse, *ap); } + fputc('\n', t->fp); +} + +static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap) +{ + struct tap_output *t = (struct tap_output *)o; + struct tvec_state *tv = t->_o.tv; + + fprintf(t->fp, "## %s:%u: `%s' FAILED", + tv->infile, tv->test_lno, tv->test->name); + if (detail) { fputs(": ", t->fp); vfprintf(t->fp, detail, *ap); } + fputc('\n', t->fp); +} + +static void tap_mismatch(struct tvec_output *o) + { mismatch(&basic_mismatchfns, o->tv); } + +static void tap_etest(struct tvec_output *o, unsigned outcome) { ; } + +static void tap_bbench(struct tvec_output *o) { ; } + +static void tap_ebench(struct tvec_output *o, + const struct bench_timing *tm) +{ + struct tap_output *t = (struct tap_output *)o; + struct tvec_state *tv = t->_o.tv; + + tvec_write(tv, "%s: ", tv->test->name); bench_summary(tv); + bench_report(tv, tm); +} + +static void tap_destroy(struct tvec_output *o) +{ + struct tap_output *t = (struct tap_output *)o; + + if (t->fp != stdout && t->fp != stderr) fclose(t->fp); + xfree(t); +} + +static const struct tvec_outops tap_ops = { + tap_error, tap_notice, tap_write, + tap_bsession, tap_esession, + tap_bgroup, tap_egroup, tap_skipgroup, + tap_btest, tap_skip, tap_fail, tap_mismatch, tap_etest, + tap_bbench, tap_ebench, + tap_destroy +}; + +struct tvec_output *tvec_tapoutput(FILE *fp) +{ + struct tap_output *t; + + t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops; + t->f = TOF_FRESHLINE; + t->fp = fp; + return (&t->_o); +} + +/*----- Default output ----------------------------------------------------*/ + +struct tvec_output *tvec_dfltout(FILE *fp) +{ + int ttyp = getenv_boolean("TVEC_TTY", -1); + + if (ttyp == -1) ttyp = isatty(fileno(fp)); + if (ttyp) return (tvec_humanoutput(fp)); + else return (tvec_tapoutput(fp)); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tvec-types.c b/test/tvec-types.c new file mode 100644 index 0000000..a5d57db --- /dev/null +++ b/test/tvec-types.c @@ -0,0 +1,1136 @@ +/* -*-c-*- + * + * Types for the test-vector framework + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include + +#include "buf.h" +#include "codec.h" +# include "base32.h" +# include "base64.h" +# include "hex.h" +#include "dstr.h" +#include "tvec.h" + +/*----- Preliminary utilities ---------------------------------------------*/ + +static int signed_to_buf(buf *b, long i) +{ + kludge64 k; + unsigned long u; + + u = i; + if (i >= 0) ASSIGN64(k, u); + else { ASSIGN64(k, ~u); CPL64(k, k); } + return (buf_putk64l(b, k)); +} + +static int signed_from_buf(buf *b, long *i_out) +{ + kludge64 k, lmax, not_lmin; + + ASSIGN64(lmax, LONG_MAX); ASSIGN64(not_lmin, ~(unsigned long)LONG_MIN); + if (buf_getk64l(b, &k)) return (-1); + if (CMP64(k, <=, lmax)) *i_out = (long)GET64(unsigned long, k); + else { + CPL64(k, k); + if (CMP64(k, <=, not_lmin)) *i_out = -(long)GET64(unsigned long, k) - 1; + else return (-1); + } + return (0); +} + +static int unsigned_to_buf(buf *b, unsigned long u) + { kludge64 k; ASSIGN64(k, u); return (buf_putk64l(b, k)); } + +static int unsigned_from_buf(buf *b, unsigned long *u_out) +{ + kludge64 k, ulmax; + + ASSIGN64(ulmax, ULONG_MAX); + if (buf_getk64l(b, &k)) return (-1); + if (CMP64(k, >, ulmax)) return (-1); + *u_out = GET64(unsigned long, k); return (0); +} + +static int hex_width(unsigned long u) +{ + int wd; + unsigned long t; + + for (t = u >> 4, wd = 4; t >>= wd, wd *= 2, t; ); + return (wd/4); +} + +static void check_signed_range(long i, + const struct tvec_irange *ir, + struct tvec_state *tv) +{ + if (ir && (ir->min > i || i > ir->max)) + tvec_error(tv, "integer %ld out of range (must be in [%ld .. %ld])", + i, ir->min, ir->max); +} + +static void check_unsigned_range(unsigned long u, + const struct tvec_urange *ur, + struct tvec_state *tv) +{ + if (ur && (ur->min > u || u > ur->max)) + tvec_error(tv, "integer %lu out of range (must be in [%lu .. %lu])", + u, ur->min, ur->max); +} + +static void parse_signed(long *i_out, const char *p, + const struct tvec_irange *ir, + struct tvec_state *tv) +{ + char *q; const char *pp; + int olderr; + long i; + + olderr = errno; errno = 0; + pp = p; if (*pp == '-' || *pp == '+') pp++; + if (!ISDIGIT(*pp)) tvec_syntax(tv, *pp, "signed integer"); + i = strtol(p, &q, 0); + if (*q && !ISSPACE(*q)) tvec_syntax(tv, *q, "end-of-line"); + if (errno) tvec_error(tv, "invalid integer `%s'", p); + check_signed_range(i, ir, tv); + errno = olderr; *i_out = i; +} + +static void parse_unsigned(unsigned long *u_out, const char *p, + const struct tvec_urange *ur, + struct tvec_state *tv) +{ + char *q; + int olderr; + unsigned long u; + + olderr = errno; errno = 0; + if (!ISDIGIT(*p)) tvec_syntax(tv, *p, "unsigned integer"); + u = strtoul(p, &q, 0); + if (*q && !ISSPACE(*q)) tvec_syntax(tv, *q, "end-of-line"); + if (errno) tvec_error(tv, "invalid integer `%s'", p); + check_unsigned_range(u, ur, tv); + errno = olderr; *u_out = u; +} + +static int convert_hex(char ch, int *v_out) +{ + if ('0' <= ch && ch <= '9') { *v_out = ch - '0'; return (0); } + else if ('a' <= ch && ch <= 'f') { *v_out = ch - 'a' + 10; return (0); } + else if ('A' <= ch && ch <= 'F') { *v_out = ch - 'A' + 10; return (0); } + else return (-1); +} + +static void read_quoted_string(dstr *d, int quote, struct tvec_state *tv) +{ + char expect[4]; + int ch, i, esc; + unsigned f = 0; +#define f_brace 1u + + sprintf(expect, "`%c'", quote); + + for (;;) { + ch = getc(tv->fp); + reinsert: + switch (ch) { + case EOF: case '\n': + tvec_syntax(tv, ch, expect); + + case '\\': + if (quote == '\'') goto ordinary; + ch = getc(tv->fp); + switch (ch) { + case EOF: tvec_syntax(tv, ch, expect); + case '\n': tv->lno++; break; + case '\'': DPUTC(d, '\''); break; + case '\\': DPUTC(d, '\\'); break; + case '"': DPUTC(d, '"'); break; + case 'a': DPUTC(d, '\a'); break; + case 'b': DPUTC(d, '\b'); break; + case 'e': DPUTC(d, '\x1b'); break; + case 'f': DPUTC(d, '\f'); break; + case 'n': DPUTC(d, '\n'); break; + case 'r': DPUTC(d, '\r'); break; + case 't': DPUTC(d, '\t'); break; + case 'v': DPUTC(d, '\v'); break; + + case 'x': + ch = getc(tv->fp); + if (ch == '{') { f |= f_brace; ch = getc(tv->fp); } + else f &= ~f_brace; + if (convert_hex(ch, &esc)) tvec_syntax(tv, ch, "hex digit"); + for (;;) { + ch = getc(tv->fp); if (convert_hex(ch, &i)) break; + esc = 8*esc + i; + if (esc > UCHAR_MAX) + tvec_error(tv, "character code %d out of range", esc); + } + DPUTC(d, esc); + if (!(f&f_brace)) goto reinsert; + else if (ch != '}') tvec_syntax(tv, ch, "`}'"); + break; + + default: + if ('0' <= ch && ch < '8') { + i = 1; esc = ch - '0'; + for (;;) { + ch = getc(tv->fp); + if (i > 3 || '0' > ch || ch >= '8') break; + esc = 8*esc + ch - '0'; i++; + } + if (esc > UCHAR_MAX) + tvec_error(tv, "character code %d out of range", esc); + DPUTC(d, esc); + goto reinsert; + } + tvec_syntax(tv, ch, "string escape"); + break; + } + break; + + default: + if (ch == quote) goto end; + ordinary: + DPUTC(d, ch); + break; + } + } + +end: + DPUTZ(d); + +#undef f_brace +} + +enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 }; + +static int collect_bare(dstr *d, struct tvec_state *tv) +{ + size_t pos = d->len; + enum { WORD, SPACE, ESCAPE }; unsigned s = WORD; + int ch, rc; + + for (;;) { + ch = getc(tv->fp); + switch (ch) { + case EOF: + goto bad; + case '\n': + if (s == ESCAPE) { tv->lno++; goto addch; } + if (s == WORD) pos = d->len; + ungetc(ch, tv->fp); if (tvec_nexttoken(tv)) { rc = -1; goto done; } + DPUTC(d, ' '); s = SPACE; + break; + case '"': case '\'': case '!': + if (s == SPACE) { ungetc(ch, tv->fp); rc = 0; goto done; } + goto addch; + case '\\': + s = ESCAPE; + break; + default: + if (s != ESCAPE && isspace(ch)) { + if (s == WORD) pos = d->len; + DPUTC(d, ch); s = SPACE; + break; + } + addch: + DPUTC(d, ch); s = WORD; + } + } + +done: + if (s == SPACE) d->len = pos; + DPUTZ(d); return (rc); + +bad: + tvec_syntax(tv, ch, "bareword"); +} + +static void set_up_encoding(const codec_class **ccl_out, unsigned *f_out, + unsigned code) +{ + switch (code) { + case TVCODE_BARE: + *ccl_out = 0; *f_out = 0; + break; + case TVCODE_HEX: + *ccl_out = &hex_class; *f_out = CDCF_IGNCASE; + break; + case TVCODE_BASE32: + *ccl_out = &base32_class; *f_out = CDCF_IGNCASE | CDCF_IGNEQPAD; + break; + case TVCODE_BASE64: + *ccl_out = &base64_class; *f_out = CDCF_IGNEQPAD; + break; + default: + abort(); + } +} + +static void read_compound_string(void **p_inout, size_t *sz_inout, + unsigned code, struct tvec_state *tv) +{ + const codec_class *ccl; unsigned f; + codec *cdc; + dstr d = DSTR_INIT, w = DSTR_INIT; + char *p; + int ch, err; + + set_up_encoding(&ccl, &f, code); + if (tvec_nexttoken(tv)) tvec_syntax(tv, fgetc(tv->fp), "string"); + do { + ch = getc(tv->fp); + if (ch == '"' || ch == '\'') + read_quoted_string(&d, ch, tv); + else if (ch == '!') { + ungetc(ch, tv->fp); + DRESET(&w); tvec_readword(tv, &w, ";", "`!'-keyword"); + if (STRCMP(w.buf, ==, "!bare")) code = TVCODE_BARE; + else if (STRCMP(w.buf, ==, "!hex")) code = TVCODE_HEX; + else if (STRCMP(w.buf, ==, "!base32")) code = TVCODE_BASE32; + else if (STRCMP(w.buf, ==, "!base64")) code = TVCODE_BASE64; + else tvec_error(tv, "unknown string keyword `%s'", w.buf); + set_up_encoding(&ccl, &f, code); + } else if (ccl) { + ungetc(ch, tv->fp); + DRESET(&w); + tvec_readword(tv, &w, ";", "%s-encoded fragment", ccl->name); + cdc = ccl->decoder(f); + err = cdc->ops->code(cdc, w.buf, w.len, &d); + if (!err) err = cdc->ops->code(cdc, 0, 0, &d); + if (err) + tvec_error(tv, "invalid %s fragment `%s': %s", + ccl->name, w.buf, codec_strerror(err)); + cdc->ops->destroy(cdc); + } else switch (code) { + case TVCODE_BARE: + ungetc(ch, tv->fp); + if (collect_bare(&d, tv)) goto done; + break; + default: + abort(); + } + } while (!tvec_nexttoken(tv)); + +done: + if (*sz_inout <= d.len) + { xfree(*p_inout); *p_inout = xmalloc(d.len + 1); } + p = *p_inout; memcpy(p, d.buf, d.len); p[d.len] = 0; *sz_inout = d.len; + dstr_destroy(&d); dstr_destroy(&w); +} + +/*----- Skeleton ----------------------------------------------------------*/ +/* +static void init_...(union tvec_regval *rv, const struct tvec_regdef *rd) +static void release_...(union tvec_regval *rv, const struct tvec_regdef *rd) +static int eq_...(const union tvec_regval *rv0, const union tvec_regval *rv1, + const struct tvec_regdef *rd) +static size_t measure_...(const union tvec_regval *rv, + const struct tvec_regdef *rd) +static int tobuf_...(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) +static int frombuf_...(buf *b, union tvec_regval *rv, + const struct tvec_regdef *rd) +static void parse_...(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) +static void dump_...(const union tvec_regval *rv, + const struct tvec_regdef *rd, + struct tvec_state *tv, unsigned style) + +const struct tvec_regty tvty_... = { + init_..., release_..., eq_..., measure_..., + tobuf_..., frombuf_..., + parse_..., dump_... +}; +*/ +/*----- Signed and unsigned integer types ---------------------------------*/ + +static void init_int(union tvec_regval *rv, const struct tvec_regdef *rd) + { rv->i = 0; } + +static void init_uint(union tvec_regval *rv, const struct tvec_regdef *rd) + { rv->u = 0; } + +static void release_int(union tvec_regval *rv, const struct tvec_regdef *rd) + { ; } + +static int eq_int(const union tvec_regval *rv0, const union tvec_regval *rv1, + const struct tvec_regdef *rd) + { return (rv0->i == rv1->i); } + +static int eq_uint(const union tvec_regval *rv0, + const union tvec_regval *rv1, + const struct tvec_regdef *rd) + { return (rv0->u == rv1->u); } + +static size_t measure_int(const union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (8); } + +static int tobuf_int(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (signed_to_buf(b, rv->i)); } + +static int tobuf_uint(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (unsigned_to_buf(b, rv->u)); } + +static int frombuf_int(buf *b, union tvec_regval *rv, + const struct tvec_regdef *rd) + { return signed_from_buf(b, &rv->i); } + +static int frombuf_uint(buf *b, union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (unsigned_from_buf(b, &rv->u)); } + +static void parse_int(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + dstr d = DSTR_INIT; + + tvec_readword(tv, &d, ";", "signed integer"); + parse_signed(&rv->i, d.buf, rd->arg.p, tv); + tvec_flushtoeol(tv, 0); + dstr_destroy(&d); +} + +static void parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + dstr d = DSTR_INIT; + + tvec_readword(tv, &d, ";", "unsigned integer"); + parse_unsigned(&rv->u, d.buf, rd->arg.p, tv); + tvec_flushtoeol(tv, 0); + dstr_destroy(&d); +} + +static void dump_int(const union tvec_regval *rv, + const struct tvec_regdef *rd, + struct tvec_state *tv, unsigned style) +{ + unsigned long u; + + tvec_write(tv, "%ld", rv->i); + if (!(style&TVSF_COMPACT)) { + if (rv->i >= 0) u = rv->i; + else u = -(unsigned long)rv->i; + tvec_write(tv, " ; = %s0x%0*lx", rv->i < 0 ? "-" : "", hex_width(u), u); + } +} + +static void dump_uint(const union tvec_regval *rv, + const struct tvec_regdef *rd, + struct tvec_state *tv, unsigned style) +{ + tvec_write(tv, "%lu", rv->u); + if (!(style&TVSF_COMPACT)) + tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u); +} + +const struct tvec_regty tvty_int = { + init_int, release_int, eq_int, measure_int, + tobuf_int, frombuf_int, + parse_int, dump_int +}; + +const struct tvec_irange + tvrange_schar = { SCHAR_MIN, SCHAR_MAX }, + tvrange_short = { SHRT_MIN, SHRT_MAX }, + tvrange_int = { INT_MIN, INT_MAX }, + tvrange_long = { LONG_MIN, LONG_MAX }, + tvrange_sbyte = { -128, 127 }, + tvrange_i16 = { -32768, +32767 }, + tvrange_i32 = { -2147483648, 2147483647 }; + +const struct tvec_regty tvty_uint = { + init_uint, release_int, eq_uint, measure_int, + tobuf_uint, frombuf_uint, + parse_uint, dump_uint +}; + +const struct tvec_urange + tvrange_uchar = { 0, UCHAR_MAX }, + tvrange_ushort = { 0, USHRT_MAX }, + tvrange_uint = { 0, UINT_MAX }, + tvrange_ulong = { 0, ULONG_MAX }, + tvrange_size = { 0, (size_t)-1 }, + tvrange_byte = { 0, 255 }, + tvrange_u16 = { 0, 65535 }, + tvrange_u32 = { 0, 4294967296 }; + +int tvec_claimeq_int(struct tvec_state *tv, long i0, long i1, + const char *file, unsigned lno, const char *expr) +{ + tv->in[0].v.i = i0; tv->out[0].v.i = i1; + return (tvec_claimeq(tv, &tvty_int, 0, file, lno, expr)); +} + +int tvec_claimeq_uint(struct tvec_state *tv, + unsigned long u0, unsigned long u1, + const char *file, unsigned lno, const char *expr) +{ + tv->in[0].v.u = u0; tv->out[0].v.u = u1; + return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr)); +} + +/*----- Enumerations ------------------------------------------------------*/ + +static void init_enum(union tvec_regval *rv, const struct tvec_regdef *rd) +{ + const struct tvec_enuminfo *ei = rd->arg.p; + + switch (ei->mv) { +#define CASE(tag, ty, slot) \ + case TVMISC_##tag: rv->slot = 0; break; + TVEC_MISCSLOTS(CASE) +#undef CASE + default: abort(); + } +} + +static int eq_enum(const union tvec_regval *rv0, + const union tvec_regval *rv1, + const struct tvec_regdef *rd) +{ + const struct tvec_enuminfo *ei = rd->arg.p; + + switch (ei->mv) { +#define CASE(tag, ty, slot) \ + case TVMISC_##tag: return (rv0->slot == rv1->slot); + TVEC_MISCSLOTS(CASE) +#undef CASE + default: abort(); + } +} + +static int tobuf_enum(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) +{ + const struct tvec_enuminfo *ei = rd->arg.p; + + switch (ei->mv) { +#define CASE(tag, ty, slot) \ + case TVMISC_##tag: return (HANDLE_##tag); +#define HANDLE_INT signed_to_buf(b, rv->i) +#define HANDLE_UINT unsigned_to_buf(b, rv->u) +#define HANDLE_PTR -1 + TVEC_MISCSLOTS(CASE) +#undef CASE +#undef HANDLE_INT +#undef HANDLE_UINT +#undef HANDLE_PTR + default: abort(); + } + return (0); +} + +static int frombuf_enum(buf *b, union tvec_regval *rv, + const struct tvec_regdef *rd) +{ + const struct tvec_enuminfo *ei = rd->arg.p; + + switch (ei->mv) { +#define CASE(tag, ty, slot) \ + case TVMISC_##tag: return (HANDLE_##tag); +#define HANDLE_INT signed_from_buf(b, &rv->i) +#define HANDLE_UINT unsigned_from_buf(b, &rv->u) +#define HANDLE_PTR -1 + TVEC_MISCSLOTS(CASE) +#undef CASE +#undef HANDLE_INT +#undef HANDLE_UINT +#undef HANDLE_PTR + default: abort(); + } +} + +static void parse_enum(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + const struct tvec_enuminfo *ei = rd->arg.p; +#define DECLS(tag, ty, slot) \ + const struct tvec_##slot##assoc *slot##a; + TVEC_MISCSLOTS(DECLS) +#undef DECLS + dstr d = DSTR_INIT; + + tvec_readword(tv, &d, ";", "enumeration tag or literal integer"); + switch (ei->mv) { +#define CASE(tag_, ty, slot) \ + case TVMISC_##tag_: \ + for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++) \ + if (STRCMP(d.buf, ==, slot##a->tag)) \ + { rv->slot = FETCH_##tag_; goto end; } +#define FETCH_INT (ia->i) +#define FETCH_UINT (ua->u) +#define FETCH_PTR ((/*unconst*/ void *)(pa->p)) + TVEC_MISCSLOTS(CASE) +#undef CASE +#undef FETCH_INT +#undef FETCH_UINT +#undef FETCH_PTR + } + + switch (ei->mv) { +#define CASE(tag, ty, slot) \ + case TVMISC_##tag: HANDLE_##tag goto end; +#define HANDLE_INT parse_signed(&rv->i, d.buf, ei->u.i.ir, tv); +#define HANDLE_UINT parse_unsigned(&rv->u, d.buf, ei->u.u.ur, tv); +#define HANDLE_PTR if (STRCMP(d.buf, ==, "#nil")) rv->p = 0; \ + else goto tagonly; + TVEC_MISCSLOTS(CASE) +#undef CASE +#undef HANDLE_INT +#undef HANDLE_UINT +#undef HANDLE_PTR + default: tagonly: + tvec_error(tv, "unknown `%s' value `%s'", ei->name, d.buf); + } + +end: + tvec_flushtoeol(tv, 0); + dstr_destroy(&d); +} + +static void dump_enum(const union tvec_regval *rv, + const struct tvec_regdef *rd, + struct tvec_state *tv, unsigned style) +{ + const struct tvec_enuminfo *ei = rd->arg.p; +#define DECLS(tag, ty, slot) \ + const struct tvec_##slot##assoc *slot##a; + TVEC_MISCSLOTS(DECLS) +#undef DECLS + const char *tag; + unsigned long u; + unsigned f = 0; +#define f_known 1u + + switch (ei->mv) { +#define CASE(tag_, ty, slot) \ + case TVMISC_##tag_: \ + for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++) \ + if (rv->slot == slot##a->slot) \ + { tag = slot##a->tag; goto found; } \ + break; + TVEC_MISCSLOTS(CASE) +#undef CASE + default: abort(); + } + goto print_int; + +found: + f |= f_known; + tvec_write(tv, "%s", tag); + if (style&TVSF_COMPACT) return; + tvec_write(tv, " ; = "); + +print_int: + switch (ei->mv) { +#define CASE(tag, ty, slot) \ + case TVMISC_##tag: HANDLE_##tag break; +#define HANDLE_INT tvec_write(tv, "%ld", rv->i); +#define HANDLE_UINT tvec_write(tv, "%lu", rv->u); +#define HANDLE_PTR if (!rv->p) tvec_write(tv, "#nil"); \ + else tvec_write(tv, "#<%s %p>", ei->name, rv->p); + TVEC_MISCSLOTS(CASE) +#undef CASE +#undef HANDLE_INT +#undef HANDLE_UINT +#undef HANDLE_PTR + } + + switch (ei->mv) { + case TVMISC_INT: + if (!(f&f_known)) tvec_write(tv, " ;"); + if (rv->i >= 0) u = rv->i; + else u = -(unsigned long)rv->i; + tvec_write(tv, " = %s0x%0*lx", rv->i < 0 ? "-" : "", hex_width(u), u); + break; + case TVMISC_UINT: + if (!(f&f_known)) tvec_write(tv, " ;"); + tvec_write(tv, " = 0x%0*lx", hex_width(rv->u), rv->u); + break; + } +} + +const struct tvec_regty tvty_enum = { + init_enum, release_int, eq_enum, measure_int, + tobuf_enum, frombuf_enum, + parse_enum, dump_enum +}; + +#define DEFCLAIM(tag, ty, slot) \ + int tvec_claimeq_##slot##enum(struct tvec_state *tv, \ + const struct tvec_enuminfo *ei, \ + ty e0, ty e1, \ + const char *file, unsigned lno, \ + const char *expr) \ + { \ + union tvec_misc arg; \ + \ + assert(ei->mv == TVMISC_##tag); \ + arg.p = ei; \ + tv->in[0].v.slot = GET_##tag(e0); \ + tv->out[0].v.slot = GET_##tag(e1); \ + return (tvec_claimeq(tv, &tvty_enum, &arg, file, lno, expr)); \ + } +#define GET_INT(e) (e) +#define GET_UINT(e) (e) +#define GET_PTR(e) ((/*unconst*/ void *)(e)) +TVEC_MISCSLOTS(DEFCLAIM) +#undef DEFCLAIM +#undef GET_INT +#undef GET_UINT +#undef GET_PTR + +/*----- Flag types --------------------------------------------------------*/ + +static void parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + const struct tvec_flaginfo *fi = rd->arg.p; + const struct tvec_flag *f; + unsigned long m = 0, v = 0, t; + dstr d = DSTR_INIT; + int ch; + + for (;;) { + DRESET(&d); tvec_readword(tv, &d, "|;", "flag name or integer"); + + for (f = fi->fv; f->tag; f++) + if (STRCMP(f->tag, ==, d.buf)) { + if (m&f->m) tvec_error(tv, "colliding flag setting"); + else { m |= f->m; v |= f->v; goto next; } + } + + parse_unsigned(&t, d.buf, fi->range, tv); v |= t; + next: + if (tvec_nexttoken(tv)) break; + ch = getc(tv->fp); if (ch != '|') tvec_syntax(tv, ch, "`|'"); + if (tvec_nexttoken(tv)) tvec_syntax(tv, '\n', "flag name or integer"); + } + rv->u = v; +} + +static void dump_flags(const union tvec_regval *rv, + const struct tvec_regdef *rd, + struct tvec_state *tv, unsigned style) +{ + const struct tvec_flaginfo *fi = rd->arg.p; + const struct tvec_flag *f; + unsigned long m = ~(unsigned long)0, v = rv->u; + const char *sep; + + for (f = fi->fv, sep = ""; f->tag; f++) + if ((m&f->m) && (v&f->m) == f->v) { + tvec_write(tv, "%s%s", sep, f->tag); m &= ~f->m; + sep = style&TVSF_COMPACT ? "|" : " | "; + } + + if (v&m) tvec_write(tv, "%s0x%0*lx", sep, hex_width(v), v&m); + + if (!(style&TVSF_COMPACT)) + tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u); +} + +const struct tvec_regty tvty_flags = { + init_uint, release_int, eq_uint, measure_int, + tobuf_uint, frombuf_uint, + parse_flags, dump_flags +}; + +int tvec_claimeq_flags(struct tvec_state *tv, + const struct tvec_flaginfo *fi, + unsigned long f0, unsigned long f1, + const char *file, unsigned lno, const char *expr) +{ + union tvec_misc arg; + + arg.p = fi; tv->in[0].v.u = f0; tv->out[0].v.u = f1; + return (tvec_claimeq(tv, &tvty_flags, &arg, file, lno, expr)); +} + +/*----- Text and byte strings ---------------------------------------------*/ + +void tvec_allocstring(union tvec_regval *rv, size_t sz) +{ + if (rv->str.sz < sz) { xfree(rv->str.p); rv->str.p = xmalloc(sz); } + rv->str.sz = sz; +} + +void tvec_allocbytes(union tvec_regval *rv, size_t sz) +{ + if (rv->bytes.sz < sz) { xfree(rv->bytes.p); rv->bytes.p = xmalloc(sz); } + rv->bytes.sz = sz; +} + +static void init_string(union tvec_regval *rv, const struct tvec_regdef *rd) + { rv->str.p = 0; rv->str.sz = 0; } + +static void init_bytes(union tvec_regval *rv, const struct tvec_regdef *rd) + { rv->bytes.p = 0; rv->bytes.sz = 0; } + +static void release_string(union tvec_regval *rv, + const struct tvec_regdef *rd) + { xfree(rv->str.p); } + +static void release_bytes(union tvec_regval *rv, + const struct tvec_regdef *rd) + { xfree(rv->bytes.p); } + +static int eq_string(const union tvec_regval *rv0, + const union tvec_regval *rv1, + const struct tvec_regdef *rd) +{ + return (rv0->str.sz == rv1->str.sz && + (!rv0->bytes.sz || + MEMCMP(rv0->str.p, ==, rv1->str.p, rv1->str.sz))); +} + +static int eq_bytes(const union tvec_regval *rv0, + const union tvec_regval *rv1, + const struct tvec_regdef *rd) +{ + return (rv0->bytes.sz == rv1->bytes.sz && + (!rv0->bytes.sz || + MEMCMP(rv0->bytes.p, ==, rv1->bytes.p, rv1->bytes.sz))); +} + +static size_t measure_string(const union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (rv->str.sz + 4); } + +static size_t measure_bytes(const union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (rv->bytes.sz + 4); } + +static int tobuf_string(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (buf_putmem32l(b, rv->str.p, rv->str.sz)); } + +static int tobuf_bytes(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (buf_putmem32l(b, rv->bytes.p, rv->bytes.sz)); } + +static int frombuf_string(buf *b, union tvec_regval *rv, + const struct tvec_regdef *rd) +{ + const void *p; + size_t sz; + + p = buf_getmem32l(b, &sz); if (!p) return (-1); + tvec_allocstring(rv, sz); memcpy(rv->str.p, p, sz); + return (0); +} + +static int frombuf_bytes(buf *b, union tvec_regval *rv, + const struct tvec_regdef *rd) +{ + const void *p; + size_t sz; + + p = buf_getmem32l(b, &sz); if (!p) return (-1); + tvec_allocbytes(rv, sz); memcpy(rv->bytes.p, p, sz); + return (0); +} + +static void check_string_length(size_t sz, const struct tvec_urange *ur, + struct tvec_state *tv) +{ + if (ur && (ur->min > sz || sz > ur->max)) + tvec_error(tv, "invalid string length %lu; must be in [%lu..%lu]", + (unsigned long)sz, ur->min, ur->max); +} + +static void parse_string(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + void *p = rv->str.p; + + read_compound_string(&p, &rv->str.sz, TVCODE_BARE, tv); rv->str.p = p; + check_string_length(rv->str.sz, rd->arg.p, tv); +} + +static void parse_bytes(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + void *p = rv->bytes.p; + + read_compound_string(&p, &rv->bytes.sz, TVCODE_HEX, tv); rv->bytes.p = p; + check_string_length(rv->bytes.sz, rd->arg.p, tv); +} + +static void dump_string(const union tvec_regval *rv, + const struct tvec_regdef *rd, + struct tvec_state *tv, unsigned style) +{ + const unsigned char *p, *q, *l; + int ch; + unsigned f = 0; +#define f_nonword 1u +#define f_newline 2u + + if (!rv->str.sz) { tvec_write(tv, "\"\""); return; } + + p = (const unsigned char *)rv->str.p; l = p + rv->str.sz; + if (*p == '!' || *p == ';' || *p == '"' || *p == '\'') goto quote; + for (q = p; q < l; q++) + if (*q == '\n' && q != l - 1) f |= f_newline; + else if (!*q || !isgraph(*q) || *q == '\\') f |= f_nonword; + if (f&f_newline) { tvec_write(tv, "\n\t"); goto quote; } + else if (f&f_nonword) goto quote; + tv->output->ops->write(tv->output, (const char *)p, rv->str.sz); return; + +quote: + tvec_write(tv, "\""); + for (q = p; q < l; q++) + switch (*q) { + case '"': case '\\': ch = *q; goto escape; + case '\a': ch = 'a'; goto escape; + case '\b': ch = 'b'; goto escape; + case '\x1b': ch = 'e'; goto escape; + case '\f': ch = 'f'; goto escape; + case '\r': ch = 'r'; goto escape; + case '\t': ch = 't'; goto escape; + case '\v': ch = 'v'; goto escape; + escape: + if (p < q) + tv->output->ops->write(tv->output, (const char *)p, q - p); + tvec_write(tv, "\\%c", ch); p = q + 1; + break; + + case '\n': + if (p < q) + tv->output->ops->write(tv->output, (const char *)p, q - p); + tvec_write(tv, "\\n"); p = q + 1; + if (!(style&TVSF_COMPACT) && q < l) tvec_write(tv, "\"\t\""); + break; + + default: + if (isprint(*q)) break; + if (p < q) + tv->output->ops->write(tv->output, (const char *)p, q - p); + tvec_write(tv, "\\x{%0*x}", hex_width(UCHAR_MAX), *q); p = q + 1; + break; + } + if (p < q) tv->output->ops->write(tv->output, (const char *)p, q - p); + tvec_write(tv, "\""); + +#undef f_nonword +#undef f_newline +} + +static void dump_bytes(const union tvec_regval *rv, + const struct tvec_regdef *rd, + struct tvec_state *tv, unsigned style) +{ + const unsigned char *p = rv->bytes.p, *l = p + rv->bytes.sz; + size_t off, sz = rv->bytes.sz; + unsigned i, n; + int wd; + + if (!sz) { + tvec_write(tv, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty"); + return; + } + + if (style&TVSF_COMPACT) { + while (p < l) tvec_write(tv, "%02x", *p++); + return; + } + + if (sz > 16) tvec_write(tv, "\n\t"); + + off = 0; wd = hex_width(sz); + while (p < l) { + if (l - p < 16) n = l - p; + else n = 16; + + for (i = 0; i < 16; i++) { + if (i < n) tvec_write(tv, "%02x", p[i]); + else tvec_write(tv, " "); + if (i%4 == 3) tvec_write(tv, " "); + } + tvec_write(tv, " ; "); + if (sz > 16) tvec_write(tv, "[%0*lx] ", wd, (unsigned long)off); + for (i = 0; i < n; i++) + tvec_write(tv, "%c", isprint(p[i]) ? p[i] : '.'); + p += n; off += n; + if (p < l) tvec_write(tv, "\n\t"); + } +} + +const struct tvec_regty tvty_string = { + init_string, release_string, eq_string, measure_string, + tobuf_string, frombuf_string, + parse_string, dump_string +}; + +const struct tvec_regty tvty_bytes = { + init_bytes, release_bytes, eq_bytes, measure_bytes, + tobuf_bytes, frombuf_bytes, + parse_bytes, dump_bytes +}; + +int tvec_claimeq_string(struct tvec_state *tv, + const char *p0, size_t sz0, + const char *p1, size_t sz1, + const char *file, unsigned lno, const char *expr) +{ + tv->in[0].v.str.p = (/*unconst*/ char *)p0; tv->in[0].v.str.sz = sz0; + tv->out[0].v.str.p =(/*unconst*/ char *) p1; tv->out[0].v.str.sz = sz1; + return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr)); +} + +int tvec_claimeq_strz(struct tvec_state *tv, + const char *p0, const char *p1, + const char *file, unsigned lno, const char *expr) +{ + tv->in[0].v.str.p = (/*unconst*/ char *)p0; + tv->in[0].v.str.sz = strlen(p0); + tv->out[0].v.str.p = (/*unconst*/ char *)p1; + tv->out[0].v.str.sz = strlen(p1); + return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr)); +} + +int tvec_claimeq_bytes(struct tvec_state *tv, + const void *p0, size_t sz0, + const void *p1, size_t sz1, + const char *file, unsigned lno, const char *expr) +{ + tv->in[0].v.bytes.p = (/*unconst*/ void *)p0; + tv->in[0].v.bytes.sz = sz0; + tv->out[0].v.bytes.p = (/*unconst*/ void *)p1; + tv->out[0].v.bytes.sz = sz1; + return (tvec_claimeq(tv, &tvty_bytes, 0, file, lno, expr)); +} + +/*----- Buffer type -------------------------------------------------------*/ + +static int eq_buffer(const union tvec_regval *rv0, + const union tvec_regval *rv1, + const struct tvec_regdef *rd) + { return (rv0->bytes.sz == rv1->bytes.sz); } + +static int tobuf_buffer(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (unsigned_to_buf(b, rv->bytes.sz)); } + +static int frombuf_buffer(buf *b, union tvec_regval *rv, + const struct tvec_regdef *rd) +{ + unsigned long u; + + if (unsigned_from_buf(b, &u)) return (-1); + if (u > (size_t)-1) return (-1); + tvec_allocbytes(rv, u); memset(rv->bytes.p, '!', u); + return (0); +} + +static const char units[] = "kMGTPEZY"; + +static void parse_buffer(union tvec_regval *rv, + const struct tvec_regdef *rd, + struct tvec_state *tv) +{ + dstr d = DSTR_INIT; + char *q; const char *unit; + int olderr; + size_t pos; + unsigned long u, t; + unsigned f = 0; +#define f_range 1u + + tvec_readword(tv, &d, ";", "buffer length"); + olderr = errno; errno = 0; + u = strtoul(d.buf, &q, 0); + if (errno) goto bad; + errno = olderr; + if (!*q) { + tvec_skipspc(tv); pos = d.len; + if (!tvec_readword(tv, &d, ";", 0)) pos++; + q = d.buf + pos; + } + + if (u > (size_t)-1) goto rangerr; + for (t = u, unit = units; *unit; unit++) { + if (t > (size_t)-1/1024) f |= f_range; + else t *= 1024; + if (*q == *unit && (!q[1] || q[1] == 'B')) { + if (f&f_range) goto rangerr; + u = t; q += 2; break; + } + } + if (*q && *q != ';') goto bad; + check_string_length(u, rd->arg.p, tv); + + tvec_flushtoeol(tv, 0); + tvec_allocbytes(rv, u); memset(rv->bytes.p, 0, u); + DDESTROY(&d); return; + +bad: + tvec_error(tv, "invalid buffer length `%s'", d.buf); + +rangerr: + tvec_error(tv, "buffer length `%s' out of range", d.buf); + +#undef f_range +} + +static void dump_buffer(const union tvec_regval *rv, + const struct tvec_regdef *rd, + struct tvec_state *tv, unsigned style) +{ + const char *unit; + unsigned long u = rv->bytes.sz; + + if (!u || u%1024) + tvec_write(tv, "%lu B", u); + else { + for (unit = units, u /= 1024; !(u%1024) && unit[1]; u /= 1024, unit++); + tvec_write(tv, "%lu %cB", u, *unit); + } +} + +const struct tvec_regty tvty_buffer = { + init_bytes, release_bytes, eq_buffer, measure_int, + tobuf_buffer, frombuf_buffer, + parse_buffer, dump_buffer +}; + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tvec.h b/test/tvec.h new file mode 100644 index 0000000..f6cbcc8 --- /dev/null +++ b/test/tvec.h @@ -0,0 +1,571 @@ +/* -*-c-*- + * + * Test vector processing framework + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef MLIB_TVEC_H +#define MLIB_TVEC_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include +#include + +#ifndef MLIB_BUF_H +# include "buf.h" +#endif + +#ifndef MLIB_CONTROL_H +# include "control.h" +#endif + +#ifndef MLIB_BUF_H +# include "dstr.h" +#endif + +#ifndef MLIB_MACROS_H +# include "macros.h" +#endif + +/*----- Miscellaneous values ----------------------------------------------*/ + +/* These are attached to structures which represent extension points, as a + * way to pass an opaque parameter to whatever things are hooked onto them. + */ + +#define TVEC_MISCSLOTS(_) \ + _(PTR, const void *, p) /* arbitrary pointer */ \ + _(INT, long, i) /* signed integer */ \ + _(UINT, unsigned long, u) /* signed integer */ + +union tvec_misc { +#define TVEC_DEFSLOT(tag, ty, slot) ty slot; + TVEC_MISCSLOTS(TVEC_DEFSLOT) +#undef TVEC_DEFSLOT +}; +enum { +#define TVEC_DEFCONST(tag, ty, slot) TVMISC_##tag, + TVEC_MISCSLOTS(TVEC_DEFCONST) + TVMISC_LIMIT +}; + +/*----- Register values ---------------------------------------------------*/ + +/* The framework doesn't have a preconceived idea about what's in a register + * value: it just allocates them and accesses them through the register type + * functions. It doesn't even have a baked-in idea of how big a register + * value is: instead, it gets that via the `regsz' slot in `struct + * tvec_testinfo'. So, as far as the framework is concerned, it's safe to + * add new slots to this union, even if they make the overall union larger. + * This can be done by defining the preprocessor macro `TVEC_REGSLOTS' to be + * a `union' fragment defining any additional union members. + * + * This creates a distinction between code which does and doesn't know the + * size of a register value. Code which does, which typically means the test + * functions, benchmarking setup and teardown functions, and tightly-bound + * runner functions, is free to index the register vectors directly. Code + * which doesn't, which means the framework core itself and output formatting + * machinery, must use the `TVEC_REG' macro (or its more general `TVEC_GREG' + * companion) for indexing register vectors. (In principle, register type + * handlers also fit into this category, but they have no business peering + * into register values other than the one's they're given.) + */ + +union tvec_regval { + /* The actual register value. This is what the type handler sees. + * Additional members can be added by setting `TVEC_REGSLOTS' before + * including this file. + * + * A register value can be /initialized/, which simply means that its + * contents represent a valid value according to its type -- the + * register can be compared, dumped, serialized, parsed into, etc. + * You can't do anything safely to an uninitialized register value + * other than initialize it. + */ + + long i; /* signed integer */ + unsigned long u; /* unsigned integer */ + void *p; /* pointer */ + struct { unsigned char *p; size_t sz; } bytes; /* binary string of bytes */ + struct { char *p; size_t sz; } str; /* text string */ +#ifdef TVEC_REGSLOTS + TVEC_REGSLOTS +#endif +}; + +struct tvec_reg { + /* A register. + * + * Note that all of the registers listed as being used by a + * particular test group are initialized at all times[1] while that + * test group is being processed. (The other register slots don't + * even have types associated with them, so there's nothing useful we + * could do with them.) + * + * The `TVRF_LIVE' flag indicates that the register was assigned a + * value by the test vector file: it's the right thing to use to + * check whether an optional register is actually present. Even + * `dead' registers are still initialized, though. + * + * [1] This isn't quite true. Between individual tests, the + * registers are released and reinitialized in order to reset + * them to known values ready for the next test. But you won't + * see them at this point. + */ + + unsigned f; /* flags */ +#define TVRF_LIVE 1u /* used in current test */ + union tvec_regval v; /* register value */ +}; + +struct tvec_regdef { + /* A register definition. Register definitions list the registers + * which are used by a particular test group (see `struct tvec_test' + * below). + * + * A vector of register definitions is terminated by a definition + * whose `name' slot is null. + */ + + const char *name; /* register name (for input files) */ + unsigned i; /* register index */ + const struct tvec_regty *ty; /* register type descriptor */ + unsigned f; /* flags */ +#define TVRF_OPT 1u /* optional register */ +#define TVRF_ID 2u /* part of test identity */ + union tvec_misc arg; /* extra detail for the type */ +}; + +extern int tvec_serialize(const struct tvec_reg */*rv*/, + const struct tvec_regdef */*regs*/, + unsigned /*nr*/, size_t /*regsz*/, + void **/*p_out*/, size_t */*sz_out*/); + +extern int tvec_deserialize(struct tvec_reg */*rv*/, + const struct tvec_regdef */*regs*/, + unsigned /*nr*/, size_t /*regsz*/, + const void */*p*/, size_t /*sz*/); + +/*----- Test state --------------------------------------------------------*/ + +enum { TVOUT_LOSE, TVOUT_SKIP, TVOUT_WIN, TVOUT_LIMIT }; + +struct tvec_state { + unsigned f; /* flags */ +#define TVSF_SKIP 1u /* skip this test group */ +#define TVSF_OPEN 2u /* test is open */ +#define TVSF_ACTIVE 4u /* test is active */ +#define TVSF_OUTMASK 0xf0 /* test outcome */ +#define TVSF_OUTSHIFT 4 + unsigned nrout, nreg; /* number of output/total registers */ + size_t regsz; /* size of register entry */ + struct tvec_reg *in, *out; /* register vectors */ + char expst, st; /* progress status codes */ + const struct tvec_test *tests, *test; /* all tests and current test */ + unsigned curr[TVOUT_LIMIT], all[TVOUT_LIMIT], grps[TVOUT_LIMIT]; + struct tvec_output *output; /* output formatter */ + const char *infile; unsigned lno, test_lno; /* input file name, line */ + FILE *fp; /* input file stream */ +}; + +#define TVEC_GREG(vec, i, regsz) \ + ((struct tvec_reg *)((unsigned char *)(vec) + (i)*(regsz))) +#define TVEC_REG(tv, vec, i) TVEC_GREG((tv)->vec, (i), (tv)->regsz) + +/*----- Test descriptions -------------------------------------------------*/ + +typedef void tvec_hookfn(struct tvec_state */*tv*/); +typedef void tvec_testfn(const struct tvec_reg */*in*/, + struct tvec_reg */*out*/, + void */*ctx*/); + +struct tvec_test { + const char *name; /* name of the test */ + const struct tvec_regdef *regs; /* descriptions of the registers */ + tvec_hookfn *preflight; /* check before starting */ + tvec_hookfn *run; /* test runner */ + tvec_testfn *fn; /* test function */ + union tvec_misc arg; /* additional parameter to `run' */ +}; + +extern void PRINTF_LIKE(2, 3) + tvec_check(struct tvec_state */*tv*/, const char */*detail*/, ...); +extern void tvec_check_v(struct tvec_state */*tv*/, + const char */*detail*/, va_list */*ap*/); + +extern void tvec_runtest(struct tvec_state */*tv*/); + +/*----- Input utilities ---------------------------------------------------*/ + +extern void tvec_skipspc(struct tvec_state */*tv*/); + +#define TVFF_ALLOWANY 1u +extern void tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/); + +extern int PRINTF_LIKE(4, 5) + tvec_readword(struct tvec_state */*tv*/, dstr */*d*/, + const char */*delims*/, const char */*expect*/, ...); +extern int tvec_readword_v(struct tvec_state */*tv*/, dstr */*d*/, + const char */*delims*/, const char */*expect*/, + va_list */*ap*/); + +extern int tvec_nexttoken(struct tvec_state */*tv*/); + +/*----- Session lifecycle -------------------------------------------------*/ + +struct tvec_info { + const struct tvec_test *tests; + unsigned nrout, nreg; + size_t regsz; +}; + +extern void tvec_begin(struct tvec_state */*tv_out*/, + const struct tvec_info */*info*/, + struct tvec_output */*o*/); +extern int tvec_end(struct tvec_state */*tv*/); + +extern void tvec_read(struct tvec_state */*tv*/, + const char */*infile*/, FILE */*fp*/); + + +/*----- Benchmarking ------------------------------------------------------*/ + +struct tvec_bench { + unsigned long niter; /* iterations done per unit */ + int riter, rbuf; /* iterations and buffer registers */ + size_t ctxsz; /* size of context */ + int (*setup)(const struct tvec_reg */*in*/, struct tvec_reg */*out*/, + const union tvec_misc */*arg*/, void */*ctx*/); /* setup fn */ + void (*teardown)(void */*ctx*/); /* teardown function, or null */ + struct bench_state **b; /* benchmark state anchor or null */ + union tvec_misc arg; /* argument to setup */ +}; + +extern struct bench_state *tvec_benchstate; + +extern int tvec_ensurebench(struct tvec_state */*tv*/, + struct bench_state **/*b_out*/); +extern void tvec_bench(struct tvec_state */*tv*/); + +/*----- Ad-hoc testing ----------------------------------------------------*/ + +extern void tvec_adhoc(struct tvec_state */*tv*/, struct tvec_test */*t*/); + +extern void tvec_begingroup(struct tvec_state */*tv*/, const char */*name*/, + const char */*file*/, unsigned /*lno*/); +extern void tvec_reportgroup(struct tvec_state */*tv*/); +extern void tvec_endgroup(struct tvec_state */*tv*/); + +#define TVEC_BEGINGROUP(tv, name) \ + do tvec_begingroup(tv, name, __FILE__, __LINE__); while (0) + +#define TVEC_TESTGROUP(tag, tv, name) \ + MC_WRAP(tag##__around, \ + { TVEC_BEGINGROUP(tv, name); }, \ + { tvec_endgroup(tv); }, \ + { if (!((tv)->f&TVSF_SKIP)) tvec_skipgroup(tv, 0); \ + tvec_endgroup(tv); }) + +extern void tvec_begintest(struct tvec_state */*tv*/, + const char */*file*/, unsigned /*lno*/); +extern void tvec_endtest(struct tvec_state */*tv*/); + +#define TVEC_BEGINTEST(tv) \ + do tvec_begintest(tv, __FILE__, __LINE__); while (0) + +#define TVEC_TEST(tag, tv) \ + MC_WRAP(tag##__around, \ + { TVEC_BEGINTEST(tv); }, \ + { tvec_endtest(tv); }, \ + { if ((tv)->f&TVSF_ACTIVE) tvec_skipgroup((tv), 0); \ + tvec_endtest(tv); }) + +extern int PRINTF_LIKE(5, 6) + tvec_claim(struct tvec_state */*tv*/, int /*ok*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/, ...); + +#define TVEC_CLAIM(tv, cond) \ + (tvec_claim(tv, !!(cond), __FILE__, __LINE__, #cond " untrue")) + +extern int tvec_claimeq(struct tvec_state */*tv*/, + const struct tvec_regty */*ty*/, + const union tvec_misc */*arg*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); + +/*----- Command-line interface --------------------------------------------*/ + +extern const struct tvec_info tvec_adhocinfo; + +extern void tvec_parseargs(int /*argc*/, char */*argv*/[], + struct tvec_state */*tv_out*/, + int */*argpos_out*/, + const struct tvec_info */*info*/); + +extern void tvec_readstdin(struct tvec_state */*tv*/); +extern void tvec_readfile(struct tvec_state */*tv*/, const char */*file*/); +extern void tvec_readdflt(struct tvec_state */*tv*/, const char */*file*/); +extern void tvec_readarg(struct tvec_state */*tv*/, const char */*arg*/); + +extern void tvec_readargs(int /*argc*/, char */*argv*/[], + struct tvec_state */*tv*/, + int */*argpos_inout*/, const char */*dflt*/); + +extern int tvec_main(int /*argc*/, char */*argv*/[], + const struct tvec_info */*info*/, + const char */*dflt*/); + +/*----- Output formatting -------------------------------------------------*/ + +struct tvec_output { + const struct tvec_outops *ops; + struct tvec_state *tv; +}; + +struct bench_timing; + +struct tvec_outops { + void (*error)(struct tvec_output */*o*/, + const char */*msg*/, va_list */*ap*/); + void (*notice)(struct tvec_output */*o*/, + const char */*msg*/, va_list */*ap*/); + void (*write)(struct tvec_output */*o*/, const char */*p*/, size_t /*sz*/); + + void (*bsession)(struct tvec_output */*o*/); + int (*esession)(struct tvec_output */*o*/); + + void (*bgroup)(struct tvec_output */*o*/); + void (*egroup)(struct tvec_output */*o*/, unsigned /*outcome*/); + void (*skipgroup)(struct tvec_output */*o*/, + const char */*excuse*/, va_list */*ap*/); + + void (*btest)(struct tvec_output */*o*/); + void (*skip)(struct tvec_output */*o*/, + const char */*excuse*/, va_list */*ap*/); + void (*fail)(struct tvec_output */*o*/, + const char */*detail*/, va_list */*ap*/); + void (*mismatch)(struct tvec_output */*o*/); + void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/); + + void (*bbench)(struct tvec_output */*o*/); + void (*ebench)(struct tvec_output */*o*/, + const struct bench_timing */*tm*/); + + void (*destroy)(struct tvec_output */*o*/); +}; + +extern void PRINTF_LIKE(2, 3) NORETURN + tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...); +extern void NORETURN tvec_error_v(struct tvec_state */*tv*/, + const char */*msg*/, va_list */*ap*/); + +extern void PRINTF_LIKE(2, 3) + tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...); +extern void tvec_notice_v(struct tvec_state */*tv*/, + const char */*msg*/, va_list */*ap*/); + +extern void PRINTF_LIKE(3, 4) NORETURN + tvec_syntax(struct tvec_state */*tv*/, int /*ch*/, + const char */*expect*/, ...); +extern void NORETURN tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/, + const char */*expect*/, va_list */*ap*/); + +extern void PRINTF_LIKE(2, 3) + tvec_skipgroup(struct tvec_state */*tv*/, const char */*note*/, ...); +extern void tvec_skipgroup_v(struct tvec_state */*tv*/, + const char */*note*/, va_list */*ap*/); + +extern void PRINTF_LIKE(2, 3) + tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...); +extern void tvec_skip_v(struct tvec_state */*tv*/, + const char */*excuse*/, va_list */*ap*/); + +extern void PRINTF_LIKE(2, 3) + tvec_fail(struct tvec_state */*tv*/, const char */*detail*/, ...); +extern void tvec_fail_v(struct tvec_state */*tv*/, + const char */*detail*/, va_list */*ap*/); + +extern void tvec_mismatch(struct tvec_state */*tv*/); + +extern void PRINTF_LIKE(2, 3) + tvec_write(struct tvec_state */*tv*/, const char */*p*/, ...); +extern void tvec_write_v(struct tvec_state */*tv*/, + const char */*p*/, va_list */*ap*/); + +extern struct tvec_output *tvec_humanoutput(FILE */*fp*/); +extern struct tvec_output *tvec_tapoutput(FILE */*fp*/); +extern struct tvec_output *tvec_dfltout(FILE */*fp*/); + +/*----- Register types ----------------------------------------------------*/ + +struct tvec_regty { + void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); + void (*release)(union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + int (*eq)(const union tvec_regval */*rv0*/, + const union tvec_regval */*rv1*/, + const struct tvec_regdef */*rd*/); + size_t (*measure)(const union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + void (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/, + struct tvec_state */*tv*/); + void (*dump)(const union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/, + struct tvec_state */*tv*/, unsigned /*style*/); +#define TVSF_COMPACT 1u +}; + +extern const struct tvec_regty tvty_int, tvty_uint; +struct tvec_irange { long min, max; }; +struct tvec_urange { unsigned long min, max; }; + +extern const struct tvec_irange + tvrange_schar, tvrange_short, tvrange_int, tvrange_long, + tvrange_sbyte, tvrange_i16, tvrange_i32; +extern const struct tvec_urange + tvrange_uchar, tvrange_ushort, tvrange_uint, tvrange_ulong, tvrange_size, + tvrange_byte, tvrange_u16, tvrange_u32; + +extern int tvec_claimeq_int(struct tvec_state */*tv*/, + long /*i0*/, long /*i1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +extern int tvec_claimeq_uint(struct tvec_state */*tv*/, + unsigned long /*u0*/, unsigned long /*u1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +#define TVEC_CLAIMEQ_INT(tv, i0, i1) \ + (tvec_claimeq_int(tv, i0, i1, __FILE__, __LINE__, #i0 " /= " #i1)) +#define TVEC_CLAIMEQ_UINT(tv, u0, u1) \ + (tvec_claimeq_uint(tv, u0, u1, __FILE__, __LINE__, #u0 " /= " #u1)) + +extern const struct tvec_regty tvty_enum; + +#define DEFASSOC(tag_, ty, slot) \ + struct tvec_##slot##assoc { const char *tag; ty slot; }; +TVEC_MISCSLOTS(DEFASSOC) +#undef DEFASSOC + +struct tvec_enuminfo { + const char *name; unsigned mv; + union { +#define DEFENUMINFO(tag, ty, slot) struct { \ + const struct tvec_##slot##assoc *av; \ + RANGESLOT_##tag \ + } slot; +#define RANGESLOT_INT const struct tvec_irange *ir; +#define RANGESLOT_UINT const struct tvec_urange *ur; +#define RANGESLOT_PTR + TVEC_MISCSLOTS(DEFENUMINFO) +#undef DEFENUMINFO +#undef RANGESLOT_INT +#undef RANGESLOT_UINT +#undef RANGESLOT_PTR + } u; +}; + +#define DECLCLAIM(tag, ty, slot) \ + extern int tvec_claimeq_##slot##enum \ + (struct tvec_state */*tv*/, \ + const struct tvec_enuminfo */*ei*/, ty /*e0*/, ty /*e1*/, \ + const char */*file*/, unsigned /*lno*/, const char */*expr*/); +TVEC_MISCSLOTS(DECLCLAIM) +#undef DECLCLAIM +#define TVEC_CLAIMEQ_IENUM(tv, ei, e0, e1) \ + (tvec_claimeq_ienum(tv, ei, e0, e1, \ + __FILE__, __LINE__, #e0 " /= " #e1)) +#define TVEC_CLAIMEQ_UENUM(tv, ei, e0, e1) \ + (tvec_claimeq_uenum(tv, ei, e0, e1, \ + __FILE__, __LINE__, #e0 " /= " #e1)) +#define TVEC_CLAIMEQ_PENUM(tv, ei, e0, e1) \ + (tvec_claimeq_penum(tv, ei, e0, e1, \ + __FILE__, __LINE__, #e0 " /= " #e1)) + +extern const struct tvec_regty tvty_flags; +struct tvec_flag { const char *tag; unsigned long m, v; }; +struct tvec_flaginfo { + const char *name; + const struct tvec_flag *fv; + const struct tvec_urange *range; +}; + +extern int tvec_claimeq_flags(struct tvec_state */*tv*/, + const struct tvec_flaginfo */*fi*/, + unsigned long /*f0*/, unsigned long /*f1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +#define TVEC_CLAIMEQ_FLAGS(tv, fi, f0, f1) \ + (tvec_claimeq_flags(tv, fi, f0, f1, \ + __FILE__, __LINE__, #f0 " /= " #f1)) + +extern const struct tvec_regty tvty_string, tvty_bytes; + +extern int tvec_claimeq_string(struct tvec_state */*tv*/, + const char */*p0*/, size_t /*sz0*/, + const char */*p1*/, size_t /*sz1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +extern int tvec_claimeq_strz(struct tvec_state */*tv*/, + const char */*p0*/, const char */*p1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +extern int tvec_claimeq_bytes(struct tvec_state */*tv*/, + const void */*p0*/, size_t /*sz0*/, + const void */*p1*/, size_t /*sz1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); + +#define TVEC_CLAIMEQ_STRING(tv, p0, sz0, p1, sz1) \ + (tvec_claimeq_string(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ + #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) +#define TVEC_CLAIMEQ_STRZ(tv, p0, p1) \ + (tvec_claimeq_strz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1)) +#define TVEC_CLAIMEQ_BYTES(tv, p0, sz0, p1, sz1) \ + (tvec_claimeq(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ + #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) + +extern const struct tvec_regty tvty_buffer; + +extern void tvec_allocstring(union tvec_regval */*rv*/, size_t /*sz*/); +extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/utils/Makefile.am b/utils/Makefile.am index 70c18f0..9f4562e 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -72,6 +72,11 @@ check_PROGRAMS += t/exc.t t_exc_t_SOURCES = t/exc-test.c t_exc_t_LDFLAGS = -static +## Linear regression. +pkginclude_HEADERS += linreg.h +libutils_la_SOURCES += linreg.c +##LIBMANS += linreg.3 + ## String handling. pkginclude_HEADERS += str.h libutils_la_SOURCES += str.c diff --git a/utils/control.3 b/utils/control.3 new file mode 100644 index 0000000..79e7df9 --- /dev/null +++ b/utils/control.3 @@ -0,0 +1,492 @@ +.\" -*-nroff-*- +.TH control 3 "23 April 2023" "Straylight/Edgeware" "mLib utilities library" +.SH NAME +control \- control structure metaprogramming +.\" @MC_BEFORE +.\" @MC_AFTER +.\" @MC_WRAP +.\" @MC_FINALLY +.\" @MC_DOWHILE +.\" @MC_DECL +.\" @MC_LOOPELSE +.\" @MC_LOOPBETWEEN +.\" @MC_ALLOWELSE +.\" @MC_GOELSE +.\" @MC_TARGET +.\" @MC_GOTARGET +.\" @MC_ACT +.\" @MC_LABEL +.\" @MC_GOTO +.SH SYNOPSIS +.nf +.B "#include " + +.BI MC_BEFORE( tag ", " stmts ") " body +.BI MC_AFTER( tag ", " stmts ") " body +.BI MC_WRAP( tag ", " before_stmt ", " onend_stmt ", " onbreak_stmt ") " body +.BI MC_DOWHILE( tag ", " cond ") " body +.BI MC_DECL( tag ", " decl ") " body +.BI MC_LOOPELSE( tag ", " head ") " loop_body " \fR[\fBelse " else_body \fR] + +.BI MC_TARGET( tag ", " stmt ") " body +.BI MC_GOTARGET( tag ); +.BI MC_ALLOWELSE( tag ") " main_body " \fR[\fBelse " else_body \fR] +.BI MC_GOELSE( tag ); + +.BI MC_ACT( stmt ) +.BI MC_LABEL( tag ) +.BI MC_GOTO( tag ) +.fi +.SH DESCRIPTION +The header file +.B +defines a number of macros which are useful when defining new +control structures for C. They are inspired by Simon Tatham's article +.IR "Metaprogramming custom control structures in C", +though these macros differ from Tatham's in a few respects. +.SS "Common features" +Each of these macros takes a +.I tag +argument. A +.I tag +is lexically like an identifier, except that it may begin with a digit, +so, for example, plain integers are acceptable tags. Each use of an +action macro by a user-level macro must have a distinct +.IR tag . +If you're writing a new prefix action macro written in terms of these +existing actions, your macro should receive a +.I tag +from its caller, and pass this tag, along with a distinctive component +of its own, down to any prefix actions that it calls; the +.IR tag s +from each layer should be separated by a pair of underscores. +.PP +Some of these macros work by wrapping a loop around the +.I body +statement. This interferes with the way that `free' +.B break +and +.B continue +statements within the +.I body +behave: we say that these statements are +.I captured +by the macro. +A +.B break +or +.B continue +statement is `free' if it doesn't appear lexically within a loop or +(for +.B break) +.B switch +statement that is part of the +.IR body . +So +.IP +.B "if (!x) break;" +.PP +contains a free +.B break +statement, while +.IP +.nf +.ft B +for (i = 0; i < n; i++) +\h'4n'if (interestingp(i)) break; +.ft +.fi +.PP +does not. +.PP +Some of these macros take special care to give you control over what +happens when a captured +.B break +is executed. Alas, proper handling of +.B continue +doesn't seem possible. Free +.B break +and +.B continue +statements +.I within +arguments to these macros are never captured. +.SS "Prefix action macros" +.B MC_BEFORE +macro is the simplest to understand. Executing +.IP +.BI MC_BEFORE( tag ", " stmt ") " body +.PP +has the same effect as executing +.I stmt +followed by +.IR body , +except that the whole thing is syntactically a single statement, so, for +example, it doesn't need to be enclosed in braces to be the body of a +.B for +loop. +.B MC_BEFORE +does not capture free +.B break +or +.B continue +statements. +.PP +Executing +.IP +.BI MC_AFTER( tag ", " stmt ") " body +.PP +has +.I nearly +the same effect as executing +.I stmt +followed by +.IR body . +Again, the whole thing is syntactically a single statement. However, +.B MC_AFTER +captures free +.B break +and +.B continue +statements within the +.IR body . +A free +.B break +or +.B continue +abruptly ends execution of the +.IR body , +immediately transferring control to the +.IR stmt . +.PP +Executing +.IP +.BI MC_WRAP( tag ", " before ", " onend ", " onbreak ") " body +.PP +has +.I nearly +the same effect as executing +.IR before , +.IR body , +and then +.IR onend . +If the +.I body +executes a free +.B break +statement, then control abruptly continues with the +.I onbreak +statement, and +.I onend +is not executed. Currently, if the +.I body +executes a free +.B continue +statement, then control abruptly continues with the +.I onend +statement, but this behaviour is a bug and may be fixed in the future. +.PP +.\" @@@ mc_finally +.PP +Executing +.IP +.BI MC_DOWHILE( tag ", " cond ") " body +.PP +has exactly the same effect as +.BI "do " body " while (" cond ); \fR, +the only difference being that the +.I body +appears in tail position rather than sandwiched in the middle. +.PP +Executing +.BI MC_DECL( tag ", " decl ") " body +has the same effect as +.BI "{ " decl "; " body " }" \fR, +except that free +.B break +and +.B continue +statements are captured. Currently, a free +.B continue +statement will simply abruptly terminate execution of the +.IR body , +while a free +.B break +statement abruptly +.I restarts +executing the +.I body +without leaving the scope of the +.IR decl ; +but these behaviours are bugs and may be fixed in the future. +.PP +The +.B MC_DECL +macro makes use of the fact that a +.B for +statement can introduce a declaration into its body's scope in C99 and +C++; the macro is not available in C89. +.PP +Executing +.IP +.nf +.BI MC_LOOPELSE( head ", " tag ") " +.RI \h'4n' loop_body +.RB [ else +.RI \h'4n' else_body ] +.fi +.PP +results in Python-like loop behaviour. The +.I head +must be a valid loop head with one of the forms +.IP +.BI "while (" cond ")" +.br +.BI "for (" decl "; " cond "; " next_expr ")" +.br +.BI "MC_DOWHILE(" tag ", " cond ")" +.PP +The resulting loop executes the same as +.IP +.nf +.I head +.RI \h'4n' loop_body +.fi +.PP +If the loop ends abruptly, as a result of +.BR break , +then control is passed to the statement following the loop in the usual +way. However, if the loop completes naturally, and the optional +.B else +clause is present, then the +.I else_body +is executed. A free +.B continue +statement within the +.I loop_body +behaves normally. Free +.B break +and +.B continue +statements within the +.I else_body +are not captured. +.PP +.\" @@@ loopbetween +.SS "Lower-level machinery" +Executing +.IP +.BI MC_TARGET( tag ", " stmt ") " body +.PP +has exactly the same effect as simply executing +.IR body . +Executing +.B MC_GOTARGET +immediately transfers control to +.IR stmt , +with control continuing with the following statement, skipping the +.IR body . +Free +.B break +or +.B continue +statements in +.I body +are not captured. +.PP +This is most commonly useful in loops in order to arrange the correct +behaviour of a free +.B break +within the loop body. See the example below, which shows the definition +of +.BR MC_LOOPELSE . +.PP +Executing +.IP +.BI MC_ALLOWELSE( tag ") " main_body " \fR[\fBelse " else_body \fR] +.PP +has exactly the same effect as just +.IR main_body . +Executing +.IP +.BI MC_GOELSE( tag ); +.PP +transfers control immediately to +.I else_body +(if present); control then naturally transfers to the following +statement as usual. Free +.B break +or +.B continue +statements in either of +.I main_body +or +.I else_body +are not captured. +.PP +Note that +.B MC_ALLOWELSE +works by secretly inserting an +.B if +statement head before the +.IR main_body , +so things will likely to wrong if +.I main_body +is itself an +.B if +statement: if +.I main_body +lacks an +.B else +clause, then an +.B else +intended to match +.B MC_ALLOWELSE +will be mis-associated; and even if +.I main_body +.I does +have an +.B else +clause, the resulting program text is likely to provoke a compiler +warning about `dangling +.BR else '. +.PP +Using these tools, it's relatively straightforward to define a macro +like +.BR MC_LOOPELSE , +described above: +.IP +.nf +.ft B +#define MC_LOOPELSE(tag, head) \e +\h'4n'MC_TARGET(tag##__exit, { ; }) \e +\h'4n'MC_ALLOWELSE(tag##__else) \e +\h'4n'MC_AFTER(tag##__after, { MC_GOELSE(tag##__else); }) \e +\h'4n'head \e +\h'8n'MC_WRAP(tag##__body, { ; }, { ; }, \e +\h'8n+\w'MC_WRAP(tag##__body, ''{ MC_GOTARGET(tag##__exit); }) +.ft R +.fi +.PP +The main `trick' for these control-flow macros is +.BR MC_ACT , +which wraps up a statement as an +.IR action . +An action is a valid +.IR "statement head" , +like +.B if +or +.BR while : +i.e., it must be completed by following it with a +.I body +statement. Executing +.IP +.BI MC_ACT( stmt ") " body +.PP +has the same effect as simply executing +.IR stmt ; +the +.I body +is usually ignored. Note that +.B ; +is a valid statement which does nothing, so +.BI MC_ACT( stmt ); +is also a valid statement with the same effect as +.IR stmt . +The only way to cause +.I body +to be executed is to attach a label to it and transfer control using +.BR goto . +.PP +Executing +.IP +.BI MC_LABEL( tag ") " body +.PP +has the same effect as +.IR body . +Executing +.IP +.BI MC_GOTO( tag ) +.PP +immediately transfers control to the +.IR body . +Note that +.B MC_GOTO +is syntactically an action +(i.e., it's wrapped in +.BR MC_ACT ). +The +.IR tag s +here are scoped to the top-level source line, like all +.IR tag s +in this macro package. +.PP +All of the control-flow macros in this package are mainly constructed from +.BR MC_ACT , +.BR MC_LABEL , +and +.BR MC_GOTO , +sometimes with one or two other statement heads thrown into the mix. +For example, +.B MC_AFTER +is defined as +.IP +.nf +.ft B +#define MC_AFTER(tag, stmt) \e +\h'28n'MC_GOTO(tag##__body) \e +\h'4n'MC_LABEL(tag##__end) \e +\h'28n'MC_ACT(stmt) \e +\h'28n'for (;;) \e +\h'32n'MC_GOTO(tag##__end) \e +\h'4n'MC_LABEL(tag##__body) +.ft R +.fi +.PP +(The unusual layout is conventional, to make the overall structure of +the code clear despite visual interference from the labels.) +The +.I body +appears at the end, labelled as +.IB tag __body \fR. +Control enters at the start, and is immediately transferred to the +.I body ; +but the +.I body +is enclosed in a +.B for +loop, so when the +.I body +completes, the loop restarts, transferring control to +.IB tag __end +and the +.IR stmt . +Since it is enclosed in +.BR MC_ACT , +once +.I stmt +completes, control transfers to the following statement. +.SH BUGS +Some macros cause free +.B break +and/or +.B continue +statements to behave in unexpected ways. +.PP +The need for tagging is ugly, and the restriction on having two +user-facing control-flow macros on the same line is objectionable. The +latter could be avoided by using nonstandard features such as GCC's +.B __COUNTER__ +macro, but adopting that would do programmers a disservice by +introducing a hazard for those trying to port code to other compilers +which lack any such feature. +.SH "SEE ALSO" +.BR mLib (3), +.BR macros (3). +.PP +Simon Tatham, +.IR "Metaprogramming custom control structures in C", +.BR "https://www.chiark.greenend.org.uk/~sgtatham/mp/" . +.SH "AUTHOR" +Mark Wooding, diff --git a/utils/control.h b/utils/control.h index 8be1cf1..97be8b6 100644 --- a/utils/control.h +++ b/utils/control.h @@ -32,6 +32,16 @@ extern "C" { #endif +/*----- Notes on the control operator machinery ---------------------------* + * + * These macros owe an obvious and immense debt to Simon Tatham's article + * `Metaprogramming custom control structures in C', available from + * https://www.chiark.greenend.org.uk/~sgtatham/mp/. The basic tricks are + * all Tatham's, as are most of the provided operators. The focus on + * @MC_ACT@ as a significant primitive is probably my main original + * contribution. + */ + /*----- Header files ------------------------------------------------------*/ #ifndef MLIB_MACROS_H @@ -48,7 +58,6 @@ #define MCTRL__LABEL(tag) GLUE(_mctrl__##tag##__, __LINE__) /* @MC_ACT(stmt)@ - * @MC_PASS@ * * @MC_ACT@ is the main `trick' for constructing these flow-control * operators. It wraps up a statement as what we call an `action'. Actions @@ -65,11 +74,9 @@ * its body. */ #define MC_ACT(stmt) if (1) { stmt; } else -#define MC_PASS MC_ACT(;) - /* @MC_LABEL(tag)@ - * @MC_GOTO(tag)@ + * @MC_GOTO(tag);@ * * @MC_LABEL@ just establishes a label which can be invoked (only) from the * same top-level macro; and @MC_GOTO@ transfers control to it. @@ -81,36 +88,74 @@ #define MC_LABEL(tag) MCTRL__LABEL(tag): #define MC_GOTO(tag) MC_ACT(goto MCTRL__LABEL(tag)) -/* @BEFORE(tag, stmt_0) stmt_1@ +/* @MC_TARGET(tag, stmt) body@ + * @MC_GOTARGET(tag);@ + * + * Executing @TARGET@ statement executes the @body@, as if the @TARGET@ + * weren't there. Executing a @GOTARGET@ transfers control to the @stmt@ + * (but not normally the @body@). In either case, the normal flow of control + * is then to the following statement. + */ +#define MC_TARGET(tag, stmt) \ + MC_GOTO(tag##__body) \ + MC_LABEL(tag##__tgt) MC_ACT(stmt) \ + MC_LABEL(tag##__body) +#define MC_GOTARGET(tag) do MC_GOTO(tag##__tgt); while (0) + +/* @MC_BEFORE(tag, stmt) body@ * - * Execute @stmt_0@ and then @stmt_1@. + * Execute @stmt@ and then @body@. */ -#define BEFORE(tag, stmt) \ +#define MC_BEFORE(tag, stmt) \ MC_ACT(stmt; MC_GOTO(tag##__body)) \ MC_LABEL(tag##__body) -/* @AFTER(tag, stmt_0) stmt_1@ +/* @MC_AFTER(tag, stmt) body@ * - * Execute @stmt_1@ and then @stmt_0@. If either statement invokes @break@ - * then control immediately transfers to the statement following @AFTER@. If - * either invokes @continue@, then control returns to @stmt_0@. + * Execute @body@ and then @stmt@. If @body@ invokes @break@ then control + * immediately transfers to the statement following @MC_AFTER@. If @body + * invokes @continue@, then control returns to @stmt@. */ -#define AFTER(tag, stmt) \ +#define MC_AFTER(tag, stmt) \ MC_GOTO(tag##__body) \ MC_LABEL(tag##__end) MC_ACT(stmt) \ for (;;) \ MC_GOTO(tag##__end) \ MC_LABEL(tag##__body) -/* @WRAP(tag, before, onend, onbreak) stmt@ +/* @MC_DOWHILE(tag, cond) body@ + * + * Repeatedly execute @body@ until @cond@ evaluates to zero. The @body@ is + * executed once before @cond@ is checked the first time. The @break@ and + * @continue@ statements work within @body@ as one would expect. + */ +#define MC_DOWHILE(tag, cond) \ + MC_GOTO(tag##__body) \ + while (cond) \ + MC_LABEL(tag##__body) + +/* @MC_ALLOWELSE(tag) apodosis_body [else haeresis_body]@ + * @MC_GOELSE(tag);@ * - * Execute the @before@ statement, followed by @stmt@. If @stmt@ invokes - * @break@, then @onbreak@ is immediately executed; if @stmt@ completes + * Executing @MC_ALLOWELSE@ executes @apodosis_body@, but not + * @haeresis_body@. If @MC_GOELSE(tag)@ is executed, then control continues + * from @haeresis_body@. + */ +#define MC_ALLOWELSE(tag) \ + MC_GOTO(tag##__body) \ + MC_LABEL(tag##__else) if (0) \ + MC_LABEL(tag##__body) +#define MC_GOELSE(tag) do MC_GOTO(tag##__else); while (0) + +/* @MC_WRAP(tag, before, onend, onbreak) body@ + * + * Execute the @before@ statement, followed by @body@. If @body@ invokes + * @break@, then @onbreak@ is immediately executed; if @body@ completes * normally, or invokes @continue@ then @onend@ is immediately executed. * Any @break@ and @continue@ in the @before@, @onend@, and @onbreak@ * statements behave as one would expect from their context. */ -#define WRAP(tag, before, onend, onbreak) \ +#define MC_WRAP(tag, before, onend, onbreak) \ MC_ACT(before; MC_GOTO(tag##__body)) \ MC_LABEL(tag##__end) MC_ACT(onend) \ MC_LABEL(tag##__brk) MC_ACT(onbreak) \ @@ -120,46 +165,92 @@ MC_GOTO(tag##__end) \ MC_LABEL(tag##__body) -/* @ALLOWELSE(tag) stmt_0 [else stmt_1]@ - * @GOELSE(tag);@ +/* @MC_FINALLY(tag, cleanup) body@ * - * Executing @ALLOWELSE@ executes @stmt_0@, but not @stmt_1@. If - * @GOELSE(tag)@ is executed, then control continues from @stmt_1@. - */ -#define ALLOWELSE(tag) \ - MC_GOTO(tag##__body) \ - MC_LABEL(tag##__else) if (0) \ - MC_LABEL(tag##__body) -#define GOELSE(tag) do MC_GOTO(tag##__else); while (0) - -/* @DOWHILE(tag, cond) stmt@ + * Execute @cleanup@ when @body@ completes or ends with @break@. In the + * latter case, propagate the @break@ to the enclosing context -- for which + * it must be syntactically appropriate. * - * Repeatedly execute @stmt@ until @cond@ evaluates to zero. Execute @stmt@ - * at least once. The @break@ and @continue@ statements work within @stmt@ - * as one would expect. + * The @cleanup@ code is duplicated. If it arrange to have private long-term + * state, e.g, by declaring @static@ variables, then the two copies will not + * share the same state, so probably don't do this. */ -#define DOWHILE(tag, cond) \ - MC_GOTO(tag##__body) \ - while (cond) \ - MC_LABEL(tag##__body) +#define MC_FINALLY(tag, cleanup) \ + MP_WRAP(tag##__final, { ; } cleanup, { cleanup break; }) -/* @DECL(tag, decl) stmt@ +/* @MC_DECL(tag, decl) body@ * - * Execute @stmt@ with @decl@ in scope. If @stmt@ completes or invokes - * @break@ or @continue@ then control continues with the statement following - * @DECL@. Internally, this uses @for@, so it only works in C99 or later, or - * C++. + * Execute @body@ with @decl@ in scope. If @body@ completes or invokes + * @continue@ then control continues with the statement following @MC_DECL@; + * if it invokes @break@ then it will be restarted without leaving the scope + * of @decl@. Internally, this uses @for@, so it only works in C99 or later, + * or C++. */ #if __STDC_VERSION__ >= 199901 || defined(__cplusplus) -# define DECL(tag, decl) \ +# define MC_DECL(tag, decl) \ for (decl;;) \ MC_GOTO(tag##__body) \ - MC_LABEL(tag##__end) MC_ACT(break) \ + MC_LABEL(tag##__exit) MC_ACT(break) \ for (;;) \ - MC_GOTO(tag##__end) \ + MC_GOTO(tag##__exit) \ MC_LABEL(tag##__body) #endif +/* @MC_LOOPELSE(tag, head) loop_body [else else_body]@ + * + * Python-like looping with optional @else@ clause. @head loop_body@ must be + * a syntactically valid @for@, @while@, or @MC_DOWHILE@ loop; if the loop + * exits because of @break@ then control continues in the usual way; + * otherwise, the @else_body@ (if any) is executed. + */ +#define MC_LOOPELSE(tag, head) \ + MC_TARGET(tag##__exit, { ; }) \ + MC_ALLOWELSE(tag##__else) \ + MC_AFTER(tag##__after, { MC_GOELSE(tag##__else); }) \ + head \ + MC_WRAP(tag##__body, { ; }, { ; }, { MC_GOTARGET(tag##__exit); }) + +/* @MC_LOOPBETWEEN(tag, setup, cond, step) loop_body [else else_body]@ + * + * This is essentially a @for@ loop with a twist. The @setup@, @cond@, and + * @step@ arguments are the parts of the @for@ head clause; because of the + * limitations of the C macro syntax, they're separated by commas rather than + * semicolons. + * + * The twist is that, once the @loop_body@ has finished, the @step@ + * expression evaluated, and the @cond@ evaluated and determined to be + * nonzero, the @else_body@ (if any) is executed before re-entering the + * @loop_body@. This makes it a useful place to insert any kind of + * interstitial material, e.g., printing commas between list items. + * + * The @cond@ is textually duplicated. You'll get some code bloat if the + * condition is very complex. If it somehow arranges to have private + * long-term state (e.g., as a result of declaring static variables inside + * GCC statement expressions), then the two copies will not share this state, + * so probably don't do this. + * + * Note that by the time that the @else_body@ is executed, the decision has + * already been made that another iteration will be performed, and, in + * particular, the @step@ has occurred. The @else_body@ is therefore looking + * at the next item to be processed, not the item that has just finished + * being processed. + */ +#define MC_LOOPBETWEEN(tag, setup, cond, step) \ + for (setup;;) \ + if (!(cond)) break; else \ + MC_TARGET(tag##__exit, { break; }) \ + for (;;) \ + MC_WRAP(tag##__tailwrap, { ; }, \ + { ; }, \ + { MC_GOTARGET(tag##__exit); }) \ + MC_ALLOWELSE(tag##__tail) \ + MC_WRAP(tag##__bodywrap, { ; }, \ + { if ((step), !(cond)) \ + MC_GOTARGET(tag##__exit); \ + else \ + MC_GOELSE(tag##__tail); }, \ + { MC_GOTARGET(tag##__exit); }) + /*----- That's all, folks -------------------------------------------------*/ #ifdef __cplusplus diff --git a/utils/linreg.c b/utils/linreg.c new file mode 100644 index 0000000..8d999c0 --- /dev/null +++ b/utils/linreg.c @@ -0,0 +1,108 @@ +/* -*-c-*- + * + * Simple linear regression + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include + +#include "linreg.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @linreg_init@ --- * + * + * Arguments: @struct linreg *lr@ = linear regression state + * + * Returns: --- + * + * Use: Initializes a linear-regression state ready for use. + */ + +void linreg_init(struct linreg *lr) +{ + lr->sum_x = lr->sum_x2 = lr->sum_y = lr->sum_y2 = lr->sum_x_y = 0.0; + lr->n = 0; +} + +/* --- @linreg_update@ --- * + * + * Arguments: @struct linreg *lr@ = linear regression state + * @double x, y@ = point coordinates + * + * Returns: --- + * + * Use: Informs the linear regression machinery of a point. + * + * Note that the state size is constant, and independent of the + * number of points. + */ + +void linreg_update(struct linreg *lr, double x, double y) +{ + lr->sum_x += x; lr->sum_x2 += x*x; lr->sum_x_y += x*y; + lr->sum_y += y; lr->sum_y2 += y*y; + lr->n++; +} + +/* --- @linreg_fit@ --- * + * + * Arguments: @const struct linreg *lr@ = linear regression state + * @double *m_out, *c_out, *r_out@ = where to write outputs + * + * Returns: --- + * + * Use: Compute the best-fit line through the previously-specified + * points. The line has the equation %$y = m x + c$%; %$m$% and + * %$c$% are written to @*m_out@ and @*c_out@ respectively, and + * the correlation coefficient %$r$% is written to @*r_out@. + * Any (or all, but that would be useless) of the output + * pointers may be null to discard that result. + * + * At least one point must have been given. + */ + +void linreg_fit(const struct linreg *lr, + double *m_out, double *c_out, double *r_out) +{ + double E_X, E_X2, E2_X, E_X_Y, E_Y, E_Y2, E2_Y, n; + double cov_X_Y, var_X, var_Y, m; + assert(lr->n); + + n = lr->n; E_X_Y = lr->sum_x_y/n; + E_X = lr->sum_x/n; E_X2 = lr->sum_x2/n; E2_X = E_X*E_X; + E_Y = lr->sum_y/n; E_Y2 = lr->sum_y2/n; E2_Y = E_Y*E_Y; + + cov_X_Y = E_X_Y - E_X*E_Y; var_X = E_X2 - E2_X; var_Y = E_Y2 - E2_Y; + + m = cov_X_Y/var_X; + if (m_out) *m_out = m; + if (c_out) *c_out = E_Y - m*E_X; + if (r_out) *r_out = cov_X_Y/sqrt(var_X*var_Y); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/utils/linreg.h b/utils/linreg.h new file mode 100644 index 0000000..2adb65f --- /dev/null +++ b/utils/linreg.h @@ -0,0 +1,165 @@ +/* -*-c-*- + * + * Simple linear regression + * + * (c) 2023 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef MLIB_LINREG_H +#define MLIB_LINREG_H + +#ifdef __cplusplus + extern "C" { +#endif + +/* Theory. (This should be well-known.) + * + * We have a number of data points (x_i, y_i) for 0 <= i < n. We believe + * they lie close to a straight line y = m x + c, for unknown constants m and + * c. Let e_i = m x_i + c - y_i. We seek the parameters which minimize + * E = ∑_{0\le i> (WD - n))) & MASK +def ror(x, n): return ((x >> n) | (x << (WD - n))) & MASK -R.seed(None) -seed = arg() -if seed is None: SEED = R.randrange(0, 1 << 32) -else: SEED = int(seed, 0) -R.seed(SEED) +class BaseVector (object): + def __init__(me): + me._all = [] + def newseed(me, seed): + me._curr = [] + me._all.append((seed, me._curr)) + def _append(me, *args): + if len(args) != len(me._REGS): + raise TypeError("expected %d arguments" % len(me._REGS)) + me._curr.append(args) + def write(me): + print("") + print("[%s]" % me._NAME) + for seed, vv in me._all: + print("") + print(";;; seed = 0x%08x" % seed) + for v in vv: + print("") + for r, x in zip(me._REGS, v): print("%s = %s" % (r, me._RFMT[r] % x)) -print('### Test vectors for 64-bit arithmetic macros') -print('### [generated; seed = 0x%08x]' % SEED) +class ShiftVector (BaseVector): + _REGS = ["x", "n", "z"] + _RFMT = { "x": FMT, "n": "%d", "z": FMT } + def add(me, r): + for i in xrange(NVEC): + x = r.randrange(LIMIT) + n = r.randrange(70)%WD + z = me._op(x, n) + me._append(x, n, z) + for i in xrange(4): + x = r.randrange(LIMIT) + z = me._op(x, WD/2) + me._append(x, WD/2, z) +class LSLVector (ShiftVector): + _NAME = "lsl64" + def _op(me, x, n): return (x << n)&MASK +class LSRVector (ShiftVector): + _NAME = "lsr64" + def _op(me, x, n): return (x >> n)&MASK +class ROLVector (ShiftVector): + _NAME = "rol64" + def _op(me, x, n): return rol(x, n) +class RORVector (ShiftVector): + _NAME = "ror64" + def _op(me, x, n): return ror(x, n) -def rol(x, n): return ((x << n) | (x >> (WD - n))) & MASK -def ror(x, n): return ((x >> n) | (x << (WD - n))) & MASK -def put(x): return '%0*x' % (WD//4, x) - -for name, func in [('lsl', lambda x, n: x << n), - ('lsr', lambda x, n: x >> n), - ('rol', rol), - ('ror', ror)]: - print('\n%s64 {' % name) - for i in xrange(NVEC): - x = R.randrange(LIMIT) - sh = R.randrange(0, 70) & 63 - print(' %s %2d %s;' % (put(x), sh, put(func(x, sh) & MASK))) - for i in xrange(4): - x = R.randrange(LIMIT) - sh = 32 - print(' %s %2d %s;' % (put(x), sh, put(func(x, sh) & MASK))) - print('}') - -for name, func in [('add', lambda x, y: x + y), - ('sub', lambda x, y: x - y)]: - print('\n%s64 {' % name) - for i in xrange(NVEC): - x = R.randrange(LIMIT) - y = R.randrange(LIMIT) - print(' %s %s %s;' % (put(x), put(y), put(func(x, y) & MASK))) - print('}') +class ArithVector (BaseVector): + _REGS = ["x", "y", "z"] + _RFMT = { "x": FMT, "y": FMT, "z": FMT } + def add(me, r): + for i in xrange(NVEC): + x = r.randrange(LIMIT) + y = r.randrange(LIMIT) + z = me._op(x, y) + me._append(x, y, z) +class AddVector (ArithVector): + _NAME = "add64" + def _op(me, x, y): return (x + y)&MASK +class SubVector (ArithVector): + _NAME = "sub64" + def _op(me, x, y): return (x - y)&MASK + +VECS = [LSLVector(), LSRVector(), ROLVector(), RORVector(), + AddVector(), SubVector()] + +for arg in SYS.argv[1:]: + if arg == "-": seed = R.SystemRandom().randrange(1 << 32) + else: seed = int(arg, 0) + r = R.Random(seed) + for v in VECS: v.newseed(seed); v.add(r) + +print(";;; -*-conf-*- Test vectors for 64-bit arithmetic macros") +print(";;; [generated]") + +for v in VECS: v.write() diff --git a/utils/t/control-test.c b/utils/t/control-test.c index 0f47e26..772ddd2 100644 --- a/utils/t/control-test.c +++ b/utils/t/control-test.c @@ -29,127 +29,168 @@ #include #include +#include #include "control.h" +#include "report.h" +#include "tvec.h" /*----- Main code ---------------------------------------------------------*/ -static int step = 0; -static int rc = 0; - -#define STEP(s) check_step(s, __FILE__ ": " STR(__LINE__)) -#define MISSTEP STEP(-1) -static void check_step(int s, const char *where) +static struct tvec_state tvstate; +static int step; + +#define TESTGROUP(name) \ + TVEC_TESTGROUP(tg, &tvstate, name) \ + MC_BEFORE(init, { step = 0; }) +#define TEST TVEC_TEST(test, &tvstate) +#define STEP(s) do { \ + tvec_claim(&tvstate, s == step, __FILE__, __LINE__, \ + "found %d /= expected %d", s, step); \ + step++; \ + } while (0) +#define MISSTEP do { \ + tvec_claim(&tvstate, 0, __FILE__, __LINE__, \ + "shouldn't have reached here"); \ + step++; \ + } while (0) + +int main(int argc, char *argv[]) { - if (step != s) { - fprintf(stderr, "misstep at %s: expected %d but found %d\n", - where, step, s); - rc = 2; + struct tvec_test test; + int argpos; + int i; + + tvec_parseargs(argc, argv, &tvstate, &argpos, &tvec_adhocinfo); + if (argpos < argc) die(2, "no input files expected"); + tvec_adhoc(&tvstate, &test); + + TESTGROUP("before-after") { + MC_BEFORE(before0, STEP(0)) STEP(1); + MC_AFTER(after0, STEP(3)) STEP(2); + STEP(4); } - step++; -} -#define LASTSTEP(s) laststep(s, __FILE__ ": " STR(__LINE__)) -static void laststep(int s, const char *where) - { check_step(s, where); step = 0; } + TESTGROUP("wrap") { + MC_WRAP(wrap0, STEP(0), STEP(2), MISSTEP) + STEP(1); + MC_WRAP(wrap1, STEP(3), MISSTEP, STEP(5)) + { STEP(4); break; } + STEP(6); + } -#define FORELSE(head) \ - MC_GOTO(top) \ - MC_LABEL(out) MC_ACT(;) \ - MC_LABEL(top) ALLOWELSE(els) \ - AFTER(outer, GOELSE(els)) \ - for (head) \ - WRAP(inner, { ; }, \ - { ; }, \ - { MC_GOTO(out); }) + TESTGROUP("loop") { + for (;;) { + MC_AFTER(after1, STEP(1); break) STEP(0); + MISSTEP; break; + } + STEP(2); + } -#define FOR_FIZZBUZZ(var, base, limit) \ - MC_GOTO(top) \ - MC_LABEL(out) MC_ACT({ ; }) \ - MC_LABEL(top) DECL(bounds, \ - int _i = base COMMA _limit = limit) \ - for (; _i < _limit; _i++) \ - DECL(buf, char _buf[24]) \ - DECL(var, const char *var) \ - WRAP(wrap, { \ - switch (_i%15) { \ - case 0: \ - var = "fizzbuzz"; \ - break; \ - case 3: case 6: case 9: case 12: \ - var = "fizz"; \ - break; \ - case 5: case 10: \ - var = "buzz"; \ - break; \ - default: \ - sprintf(_buf, "%d", _i); var = _buf; \ - break; \ - } \ - }, \ - { ; }, \ - { MC_GOTO(out); }) - -int main(void) -{ - int i; +#define FORELSE(head) MC_LOOPELSE(forelse, for (head)) + + TESTGROUP("for-else") { + FORELSE (i = 0; i < 10; i++) { + STEP(i); + if (i == 7) break; + } else + MISSTEP; + STEP(8); + } + + TESTGROUP("for-else-break") { + FORELSE (i = 0; i < 10; i++) { + STEP(i); + if (i == 12) break; + } else + STEP(10); + STEP(11); + } - BEFORE(before0, STEP(0)) STEP(1); - AFTER(after0, STEP(3)) STEP(2); - LASTSTEP(4); +#undef FORELSE - WRAP(wrap0, STEP(0), STEP(2), MISSTEP) STEP(1); - WRAP(wrap1, STEP(3), MISSTEP, STEP(5)) { STEP(4); break; } - LASTSTEP(6); + TESTGROUP("loop-between") { + MC_LOOPBETWEEN(x, i = 0, i < 5, i++) STEP(2*i); + else STEP(2*i - 1); + STEP(9); + } + + TESTGROUP("loop-between-continue-break") { + MC_LOOPBETWEEN(x, i = 0, i < 5, i++) { + if (i == 1) { STEP(2); continue; } + STEP(2*i); + if (i == 3) break; + } else + STEP(2*i - 1); + STEP(7); + } - STEP(0); - for (;;) { - AFTER(after1, STEP(2); break) STEP(1); - MISSTEP; break; +#define WRAPELSE_TEST \ + MC_TARGET(done_plain, { STEP(4); MC_GOELSE(elsie); }) \ + MC_WRAP(outer_wrap, { STEP(0); }, \ + { STEP(6); }, \ + { MISSTEP; }) \ + MC_ALLOWELSE(elsie) \ + MC_WRAP(inner_wrap, { STEP(1); }, \ + { STEP(3); MC_GOTARGET(done_plain); }, \ + { MISSTEP; }) + + TESTGROUP("wrap-else") { + WRAPELSE_TEST STEP(2); + else STEP(5); + STEP(7); } - LASTSTEP(3); - - FORELSE (i = 0; i < 10; i++) { - STEP(i); - if (i == 7) break; - } else - MISSTEP; - LASTSTEP(8); - - FORELSE (i = 0; i < 10; i++) { - STEP(i); - if (i == 12) break; - } else - STEP(10); - LASTSTEP(11); - -#define TEST \ - MC_ACT(STEP(0); MC_GOTO(in_plain)) \ - MC_LABEL(done_plain) MC_ACT(STEP(5); GOELSE(elsie)) \ - MC_LABEL(in_plain) WRAP(outer_wrap, { STEP(1); }, \ - { STEP(7); }, \ - { MISSTEP; }) \ - ALLOWELSE(elsie) \ - WRAP(inner_wrap, { STEP(2); }, \ - { STEP(4); \ - MC_GOTO(done_plain); }, \ - { MISSTEP; }) \ - STEP(3); \ - else \ - STEP(6); \ - LASTSTEP(8); - TEST -#undef TEST +#undef WRAPELSE_TEST + + TESTGROUP("decl") { #if __STDC_VERSION__ >= 199901 || defined(__cplusplus) - STEP(0); - DECL(decl0, int j = 1) STEP(j); - LASTSTEP(2); + STEP(0); + MC_DECL(decl0, int j = 1) STEP(j); + STEP(2); +#else + tvec_skipgroup(&tvstate, "`MC_DECL' not supported on C89"); #endif + } + +#define FIZZBUZZ_DECLS(var) \ + int _i, _limit; \ + char _buf[24]; \ + const char *var +#define FOR_FIZZBUZZ(var, base, limit) \ + MC_TARGET(out, { ; }) \ + MC_BEFORE(bounds, { _i = base; _limit = limit; }) \ + for (; _i < _limit; _i++) \ + MC_WRAP(wrap, \ + { switch (_i%15) { \ + case 0: var = "fizzbuzz"; break; \ + case 3: case 6: case 9: case 12: var = "fizz"; break; \ + case 5: case 10: var = "buzz"; break; \ + default: sprintf(_buf, "%d", _i); var = _buf; break; \ + } }, \ + { ; }, \ + { MC_GOTARGET(out); }) + + TESTGROUP("fizzbuzz") { + FIZZBUZZ_DECLS(fb); + unsigned i; + static const char *const ref[] = { + "19", "buzz", "fizz", "22", "23", "fizz", "buzz", + "26", "fizz", "28", "29", "fizzbuzz", "31", 0 + }; + + i = 0; + FOR_FIZZBUZZ(fb, 19, 32) + TEST + if (TVEC_CLAIM(&tvstate, ref[i])) + { TVEC_CLAIMEQ_STRZ(&tvstate, fb, ref[i]); i++; } + TVEC_CLAIM(&tvstate, !ref[i]); + } - FOR_FIZZBUZZ(fb, 19, 32) printf("%s\n", fb); +#undef FIZZBUZZ_DECLS +#undef FOR_FIZZBUZZ - return (rc); + return (tvec_end(&tvstate)); } /*----- That's all, folks -------------------------------------------------*/ diff --git a/utils/t/versioncmp-test.c b/utils/t/versioncmp-test.c index 0927f46..789565d 100644 --- a/utils/t/versioncmp-test.c +++ b/utils/t/versioncmp-test.c @@ -25,27 +25,46 @@ * MA 02111-1307, USA. */ -#include "testrig.h" +#include "tvec.h" #include "versioncmp.h" -static int t_vcmp(dstr *v) +enum { + RRC, NROUT, + RV0 = NROUT, RV1, NREG +}; + +static void test_versioncmp(const struct tvec_reg *in, struct tvec_reg *out, + void *ctx) + { out[RRC].v.i = versioncmp(in[RV0].v.str.p, in[RV1].v.str.p); } + +static void swap_test(struct tvec_state *tv) { - int ok = 1; - int rc = versioncmp(v[0].buf, v[1].buf); - if (rc != *(int *)v[2].buf) { - fprintf(stderr, "\nversioncmp(%s, %s) returns %d != %d\n", - v[0].buf, v[1].buf, rc, *(int *)v[2].buf); - ok = 0; - } - return (ok); + struct tvec_reg rt; + + tv->st = '.'; + tv->test->fn(tv->in, tv->out, 0); tvec_check(tv, "vanilla"); + rt = tv->in[RV0]; tv->in[RV0] = tv->in[RV1]; tv->in[RV1] = rt; + tv->in[RRC].v.i = -tv->in[RRC].v.i; + tv->test->fn(tv->in, tv->out, 0); tvec_check(tv, "swapped"); } -static const test_chunk tests[] = { - { "versioncmp", t_vcmp, { &type_string, &type_string, &type_int } }, - { 0 } +static const struct tvec_irange cmp_range = { -1, +1 }; +static const struct tvec_regdef versioncmp_regs[] = { + { "v0", RV0, &tvty_string, 0 }, + { "v1", RV1, &tvty_string, 0 }, + { "rc", RRC, &tvty_int, 0, { &cmp_range } }, + { 0, 0, 0 } }; +static const struct tvec_test tests[] = { + { "versioncmp", versioncmp_regs, 0, swap_test, test_versioncmp }, + { 0, 0, 0, 0 } +}; + +static const struct tvec_info testinfo = + { tests, NROUT, NREG, sizeof(struct tvec_reg) }; + int main(int argc, char *argv[]) - { test_run(argc, argv, tests, SRCDIR "/versioncmp.in"); return (1); } + { return (tvec_main(argc, argv, &testinfo, 0)); } /*----- That's all, folks -------------------------------------------------*/ diff --git a/utils/t/versioncmp.tests b/utils/t/versioncmp.tests index 421974a..f892417 100644 --- a/utils/t/versioncmp.tests +++ b/utils/t/versioncmp.tests @@ -1,14 +1,54 @@ -## test for versioncmp - -versioncmp { - 1.2 1.2 0; - 1.1 1.2 -1; - 1.1 1.0 +1; - 1.0 1.a -1; - 1:2.0 2:0.4 -1; - 1.2 1.2~pre0 +1; - 1~~ 1~~a -1; - 1~~a 1~ -1; - 1~ 1 -1; - 1 1a -1; -} +;;; -*-conf-*- +;;; test for versioncmp + +[versioncmp] + +;; some softball tests + +v0 = 1.2 +v1 = 1.2 +rc = 0 + +v0 = 1.1 +v1 = 1.2 +rc = -1 + +v0 = 1.1 +v1 = 1.0 +rc = +1 + +;; compare numeric to alphabetic + +v0 = 1.0 +v1 = 1.a +rc = -1 + +;; compare epochs + +v0 = 1:2.0 +v1 = 2:0.4 +rc = -1 + +;; exercise `~' + +v0 = 1.2 +v1 = 1.2~pre0 +rc = +1 + +v0 = 1~~ +v1 = 1~~a +rc = -1 + +v0 = 1~~a +v1 = 1~ +rc = -1 + +v0 = 1~ +v1 = 1 +rc = -1 + +;; juxtaposed alphabetics + +v0 = 1 +v1 = 1a +rc = -1 diff --git a/utils/tests.at b/utils/tests.at index 41e8877..d7cd6c3 100644 --- a/utils/tests.at +++ b/utils/tests.at @@ -30,31 +30,14 @@ ## bits AT_SETUP([utilities: bits]) AT_KEYWORDS([utils bits]) -for seed in 0xaca98e08 0x0b6e95fb ""; do - $PYTHON SRCDIR/t/bits-testgen.py $seed >bits.tests - AT_CHECK([BUILDDIR/t/bits.t -f bits.tests], [0], [ignore], [ignore]) -done +$PYTHON SRCDIR/t/bits-testgen.py 0xaca98e08 0x0b6e95fb - >bits.tests +AT_CHECK([BUILDDIR/t/bits.t bits.tests], [0], [ignore], [ignore]) AT_CLEANUP ## control AT_SETUP([utilities: control]) AT_KEYWORDS([utils control]) -AT_DATA([expout], -[19 -buzz -fizz -22 -23 -fizz -buzz -26 -fizz -28 -29 -fizzbuzz -31 -]) -AT_CHECK([BUILDDIR/t/control.t], [0], [expout]) +AT_CHECK([BUILDDIR/t/control.t], [0], [ignore], [ignore]) AT_CLEANUP ## exc @@ -76,7 +59,7 @@ AT_CLEANUP ## versioncmp AT_SETUP([utilities: versioncmp]) AT_KEYWORDS([utils versioncmp]) -AT_CHECK([BUILDDIR/t/versioncmp.t -f SRCDIR/t/versioncmp.tests], +AT_CHECK([BUILDDIR/t/versioncmp.t SRCDIR/t/versioncmp.tests], [0], [ignore], [ignore]) AT_CLEANUP -- [mdw]