]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.12-20260402
authorWietse Z Venema <wietse@porcupine.org>
Thu, 2 Apr 2026 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Fri, 3 Apr 2026 03:52:31 +0000 (14:52 +1100)
155 files changed:
postfix/.indent.pro
postfix/HISTORY
postfix/Makefile.in
postfix/README_FILES/AAAREADME
postfix/README_FILES/PTEST_README [new file with mode: 0644]
postfix/conf/postfix-files
postfix/html/PTEST_README.html [new file with mode: 0644]
postfix/html/index.html
postfix/mantools/postlink
postfix/proto/Makefile.in
postfix/proto/PTEST_README.html [new file with mode: 0644]
postfix/proto/stop.double-cc
postfix/proto/stop.double-proto-html
postfix/proto/stop.spell-cc
postfix/proto/stop.spell-history
postfix/proto/stop.spell-proto-html
postfix/src/bounce/Makefile.in
postfix/src/bounce/bounce_notify_util_tester.c
postfix/src/dns/Makefile.in
postfix/src/dns/dns.h
postfix/src/dns/dns_lookup.c
postfix/src/dns/dns_lookup_types.c [new file with mode: 0644]
postfix/src/dns/dns_lookup_types_test.c [new file with mode: 0644]
postfix/src/global/Makefile.in
postfix/src/global/config_known_tcp_ports.c
postfix/src/global/config_known_tcp_ports_test.c [new file with mode: 0644]
postfix/src/global/delivered_hdr.c
postfix/src/global/delivered_hdr_test.c [new file with mode: 0644]
postfix/src/global/dict_mysql.c
postfix/src/global/fold_addr.h
postfix/src/global/haproxy_srvr.h
postfix/src/global/haproxy_srvr_test.c
postfix/src/global/hfrom_format.c
postfix/src/global/hfrom_format.ref [deleted file]
postfix/src/global/hfrom_format_test.c [new file with mode: 0644]
postfix/src/global/login_sender_match.ref [deleted file]
postfix/src/global/login_sender_match_test.c
postfix/src/global/mail_addr_find.c
postfix/src/global/mail_dict.c
postfix/src/global/mail_version.h
postfix/src/global/map_search.c
postfix/src/global/map_search.ref [deleted file]
postfix/src/global/map_search_test.c [new file with mode: 0644]
postfix/src/global/nbdb_redirect_test.c
postfix/src/global/normalize_mailhost_addr.c
postfix/src/global/normalize_mailhost_addr_test.c [new file with mode: 0644]
postfix/src/global/smtp_reply_footer.c
postfix/src/global/smtp_reply_footer.ref [deleted file]
postfix/src/global/smtp_reply_footer_test.c [new file with mode: 0644]
postfix/src/global/test_server_main.c [moved from postfix/src/global/test_main.c with 93% similarity]
postfix/src/global/test_server_main.h [moved from postfix/src/global/test_main.h with 94% similarity]
postfix/src/local/Makefile.in
postfix/src/local/biff_notify.c
postfix/src/postscreen/Makefile.in
postfix/src/postscreen/postscreen.h
postfix/src/postscreen/postscreen_dnsbl.c
postfix/src/postscreen/postscreen_dnsbl_test.c [new file with mode: 0644]
postfix/src/ptest/Makefile.in [new file with mode: 0644]
postfix/src/ptest/msg_jmp.c [new file with mode: 0644]
postfix/src/ptest/msg_jmp.h [new file with mode: 0644]
postfix/src/ptest/pmock_expect.c [new file with mode: 0644]
postfix/src/ptest/pmock_expect.h [new file with mode: 0644]
postfix/src/ptest/pmock_expect_test.c [new file with mode: 0644]
postfix/src/ptest/ptest.h [new file with mode: 0644]
postfix/src/ptest/ptest_ctx.c [new file with mode: 0644]
postfix/src/ptest/ptest_error.c [new file with mode: 0644]
postfix/src/ptest/ptest_log.c [new file with mode: 0644]
postfix/src/ptest/ptest_log_test.c [new file with mode: 0644]
postfix/src/ptest/ptest_main.h [new file with mode: 0644]
postfix/src/ptest/ptest_run.c [new file with mode: 0644]
postfix/src/smtp/smtp_addr.c
postfix/src/smtpd/Makefile.in
postfix/src/testing/Makefile.in
postfix/src/testing/addrinfo_to_string.c [new file with mode: 0644]
postfix/src/testing/addrinfo_to_string.h [new file with mode: 0644]
postfix/src/testing/make_addr.c [new file with mode: 0644]
postfix/src/testing/make_addr.h [new file with mode: 0644]
postfix/src/testing/make_attr.c [new file with mode: 0644]
postfix/src/testing/make_attr.h [new file with mode: 0644]
postfix/src/testing/match_addr.c [new file with mode: 0644]
postfix/src/testing/match_addr.h [new file with mode: 0644]
postfix/src/testing/match_addr_test.c [new file with mode: 0644]
postfix/src/testing/match_attr.c [new file with mode: 0644]
postfix/src/testing/match_attr.h [new file with mode: 0644]
postfix/src/testing/match_attr_test.c [new file with mode: 0644]
postfix/src/testing/match_basic.c [new file with mode: 0644]
postfix/src/testing/match_basic.h [new file with mode: 0644]
postfix/src/testing/match_basic_test.c [new file with mode: 0644]
postfix/src/testing/mock_dns.h [new file with mode: 0644]
postfix/src/testing/mock_dns_lookup.c [new file with mode: 0644]
postfix/src/testing/mock_dns_lookup_test.c [new file with mode: 0644]
postfix/src/testing/mock_getaddrinfo.c [new file with mode: 0644]
postfix/src/testing/mock_getaddrinfo.h [new file with mode: 0644]
postfix/src/testing/mock_getaddrinfo_test.c [new file with mode: 0644]
postfix/src/testing/mock_myaddrinfo.c [new file with mode: 0644]
postfix/src/testing/mock_myaddrinfo.h [new file with mode: 0644]
postfix/src/testing/mock_myaddrinfo_test.c [new file with mode: 0644]
postfix/src/testing/mock_servent.c [new file with mode: 0644]
postfix/src/testing/mock_servent.h [new file with mode: 0644]
postfix/src/testing/mock_servent_test.c [new file with mode: 0644]
postfix/src/testing/mock_server.c [new file with mode: 0644]
postfix/src/testing/mock_server.h [new file with mode: 0644]
postfix/src/testing/mock_server_test.c [new file with mode: 0644]
postfix/src/tlsmgr/Makefile.in
postfix/src/util/Makefile.in
postfix/src/util/argv.c
postfix/src/util/argv.h
postfix/src/util/argv_test.c [new file with mode: 0644]
postfix/src/util/dict.h
postfix/src/util/dict_cli.c [moved from postfix/src/util/dict_test.c with 97% similarity]
postfix/src/util/dict_debug_test.ref
postfix/src/util/dict_debug_test.sh
postfix/src/util/dict_open.c
postfix/src/util/dict_pipe.c
postfix/src/util/dict_pipe_test.c
postfix/src/util/dict_stream.c
postfix/src/util/dict_stream.ref [deleted file]
postfix/src/util/dict_stream_test.c [new file with mode: 0644]
postfix/src/util/dict_union.c
postfix/src/util/dict_union_test.c
postfix/src/util/find_inet_service.c [new file with mode: 0644]
postfix/src/util/find_inet_service.h [new file with mode: 0644]
postfix/src/util/find_inet_service_test.c [new file with mode: 0644]
postfix/src/util/hash_fnv_test.c
postfix/src/util/known_tcp_ports.c
postfix/src/util/known_tcp_ports.ref [deleted file]
postfix/src/util/known_tcp_ports_test.c [new file with mode: 0644]
postfix/src/util/msg.c
postfix/src/util/msg.h
postfix/src/util/msg_logger.c
postfix/src/util/msg_output.c
postfix/src/util/msg_output.h
postfix/src/util/msg_output_test.c [new file with mode: 0644]
postfix/src/util/msg_syslog.c
postfix/src/util/msg_vstream.c
postfix/src/util/msg_vstream.h
postfix/src/util/myaddrinfo.c
postfix/src/util/myaddrinfo.ref [deleted file]
postfix/src/util/myaddrinfo.ref2 [deleted file]
postfix/src/util/myaddrinfo4.ref [deleted file]
postfix/src/util/myaddrinfo4.ref2 [deleted file]
postfix/src/util/myaddrinfo_test.c [new file with mode: 0644]
postfix/src/util/mymalloc.c
postfix/src/util/mymalloc_test.c [new file with mode: 0644]
postfix/src/util/mystrtok.c
postfix/src/util/mystrtok.ref [deleted file]
postfix/src/util/mystrtok_test.c [new file with mode: 0644]
postfix/src/util/name_mask.c
postfix/src/util/name_mask.h
postfix/src/util/sunos5_stream_test.c [moved from postfix/src/util/stream_test.c with 99% similarity]
postfix/src/util/unescape.in [deleted file]
postfix/src/util/unescape.ref [deleted file]
postfix/src/util/unescape_test.c [new file with mode: 0644]
postfix/src/util/wrap_netdb.c [new file with mode: 0644]
postfix/src/util/wrap_netdb.h [new file with mode: 0644]

index 3faf372e5da740d9e972283a6e16a1d7508c2c9a..8b17072544e8c65ad99b2b05d1c7291667a566d3 100644 (file)
@@ -18,6 +18,7 @@
 -TATTR_TABLE
 -TAUTHORITY_KEYID
 -TAUTO_CLNT
+-TBASE_TEST_CASE
 -TBH_TABLE
 -TBINATTR
 -TBINATTR_INFO
 -TMKMAP_OPEN_FN
 -TMKMAP_OPEN_INFO
 -TMKMAP_SDBM
+-TMOCK_APPL
+-TMOCK_APPL_SIG
+-TMOCK_APPL_STATUS
+-TMOCK_EXPECT
 -TMOCK_OPEN_AS_REQ
 -TMOCK_SPAWN_CMD_REQ
 -TMOCK_STAT_REQ
+-TMOCK_UNIX_SERVER
 -TMSG_CAPTURE
+-TMSG_JMP_BUF
+-TMSG_OUT_INFO
 -TMSG_STATS
 -TMULTI_SERVER
 -TMVECT
 -TPSC_SMTPD_COMMAND
 -TPSC_STARTTLS
 -TPSC_STATE
+-TPTEST_CASE
+-TPTEST_CTX
 -TQMGR_ENTRY
 -TQMGR_FEEDBACK
 -TQMGR_JOB
 -TSYS_EXITS_DETAIL
 -TTEST_BASE
 -TTEST_CASE
+-TTEST_DATA
+-TTEST_JMP_BUF
 -TTLSMGR_SCACHE
 -TTLSP_STATE
 -TTLSRPT_WRAPPER
 -TXSASL_SERVER_CREATE_ARGS
 -TXSASL_SERVER_IMPL
 -TXSASL_SERVER_IMPL_INFO
+-Taddrinfo
 -Tbind_props
 -Tbson_iter_t
 -Tcipher_probe_t
index 48cdd1d0a0710cc92830cb7fa3909708557f568b..528b3363704047bcadfd0aa05545c05598f538bf 100644 (file)
@@ -26549,7 +26549,6 @@ Apologies for any names omitted.
        Documentation: inet_interfaces and proxy_interfaces
        descriptions. File: proto/postconf.proto.
 
-
 20220719
 
        Cleanup: Postfix 3.5.0 introduced debug logging noise in
@@ -30691,3 +30690,111 @@ Apologies for any names omitted.
        Cleanup: added missing smtpd_data_restrictions and
        smtpd_end_of_data_restrictions in the sample master.cf
        file. Omission reported by Viktor Dukhovni. File: conf/master.cf.
+
+20260402
+
+       Feature: 'Ptest' infrastructure for unit tests, and 'Pmock'
+       infrastructure to make tests hermetic (i.e. independent of
+       host configuration, network configuration, DNS, or installed
+       Postfix files).
+
+       Ptest was inspired by Go test, while Pmock implements a few
+       ideas from Google gmock. The PTEST_README file has some
+       information about how Postfix unit tests work.
+
+       This changes the Postfix file footprint as follows:
+
+       1) This adds source files named 'foo_test.c', that test
+       code in 'foo.c' and 'foo.h'; and this adds source files
+       mock_foo.c and mock_foo.h with functions that produce
+       prepared outputs for expected inputs. For example,
+       mock_getaddrinfo.c implements getaddrinfo() and getnameinfo()
+       functions that return prepared outputs without doing host
+       or DNS lookups. More files will be added as tests are
+       converted to the Ptest/Pmock infrastructure.
+
+       2) This removes the test data files ('foo.in*', 'foo.ref*').
+
+       3) This renames some files whose names end in test.c: for
+       example, dict_test.c is renamed to dict_cli.c.
+
+       Reality check: the Postfix source tree contains 129 .c files
+       with legacy "#ifdef TEST" support for tests, 53 .c files
+       with a separate _test.c file, and 526 .c files that have
+       no test of their own. it is likely that all .c files with
+       have a _test.c file. The plan is therefore to attack the
+       problem from both ends, and to integrate support for
+       whole-program daemon tests that was used in 2025 to test a
+       new trivial-rewrite feature.
+
+       Care was taken to minimize the changes to non-test code,
+       by making make clear which code paths rare reachable only
+       from tests.
+
+       Changes to non-test code:
+
+       Cleanup: migrate biff_notify() from gethostbyname() and
+       getservbyname() to myaddrinfo. File: local/biff_notify.c.
+
+       Cleanup: new find_inet_service(3) module that returns an
+       error status; deprecate find_inet(3) where all errors are
+       fatal. File: util/find_inet_service.c.
+
+       Cleanup: migrate the MySQL client from find_inet(3) to
+       find_inet_service(3). Files: src/global/dict_mysql.c.
+
+       Feature: new NAME_MASK_NULL flag. When is present, the
+       str_name_mask_opt() function will output a string "0" when
+       the input mask is empty. Files: util/name_mask.[hc].
+
+       Cleanup: to make dict_open() easier to test, with support
+       for graceful degradation after dictionary type or name
+       syntax errors. File: util/dict_open.c.
+
+       Cleanup: stricter parsing of TCP/UDP port numbers. File:
+       util/known_tcp_ports.c.
+
+       Bugfix: mystrndup() ignored the case that length==0, when
+       compiled without NO_SHARED_EMPTY_STRINGS (the default).
+       File: util/mymalloc.c.
+
+       Changes to make non-test code easier to test:
+
+       Testing: support to make msg_fatal() and msg_panic() calls
+       testable. Instead of terminating the process, these functions
+       may transfer control to a call-back function that calls
+       [sig]longjmp(). Files: util/msg.c.
+
+       Testing: support to make msg_xxx() calls testable. This
+       requires adding a context argument to msg_output() and to
+       msg_output() call-back functions, and making msg_output()
+       undoable. To that end, msg_output() is renamed to
+       msg_output_push(), and its companion function is called
+       msg_output_pop(). Files: util/msg_output.[hc].
+
+       Testing: support to make msg_xxx() calls testable. This
+       requires suspending output to VSTREAM_ERR while capturing
+       output for inspection. Files: util/msg_vstream.[hc].
+
+       Changes to test code:
+
+       Removed .ref* and .in* files as tests were converted to
+       _test.c style. Files: global/hfrom_format.ref,
+       global/hfrom_format_test.c, global/login_sender_match.ref,
+       global/login_sender_match_test.c, global/map_search.ref,
+       global/map_search_test.c, global/smtp_reply_footer.ref,
+       global/smtp_reply_footer_test.c, util/known_tcp_ports.ref,
+       util/known_tcp_ports_test.c, util/myaddrinfo.ref*,
+       util/myaddrinfo_test.c, util/mystrtok.ref, util/mystrtok_test.c,
+       util/unescape.in, util/unescape.ref, util/unescape_test.c.
+
+       Cleanup: renamed global/test_main.[hc] to
+       global/test_server_main.[hc]. Files: global/test_server_main.[hc],
+       bounce/bounce_tester.c, Makefiles.
+
+       Renamed dict_test.c to dict_cli.c, stream_test.c to
+       sunos5_stream_test.c.
+
+TODO
+
+       Reorganize PTEST_LIB, PMOCK_LIB, TESTLIB, TESTLIBS, etc.
index 041cf933632141f6d5f9eaf558fd025cb1f05bec..9f78020190a695935f148b790660a057d21972d4 100644 (file)
@@ -12,7 +12,7 @@ DIRS  = src/util src/global src/dns src/tls src/xsasl src/master src/milter \
        src/postsuper src/qmqpd src/spawn src/flush src/verify \
        src/virtual src/proxymap src/anvil src/scache src/discard src/tlsmgr \
        src/postmulti src/postscreen src/dnsblog src/tlsproxy \
-       src/posttls-finger src/postlogd src/nbdb_reindexd src/testing
+       src/posttls-finger src/postlogd src/nbdb_reindexd src/ptest src/testing
 MANDIRS        = proto man html
 LIBEXEC        = libexec/post-install libexec/postfix-script libexec/postfix-wrapper \
        libexec/postmulti-script libexec/postfix-non-bdb-script \
index c3c83da7d550b704fb88691b3e5ee2478b7f20da..7df74fe8b380407fbce93d3ad3cc685a061eaf1b 100644 (file)
@@ -89,3 +89,7 @@ O\bOt\bth\bhe\ber\br t\bto\bop\bpi\bic\bcs\bs
   * XCLIENT_README: XCLIENT Command
   * XFORWARD_README: XFORWARD Command
 
+F\bFo\bor\br m\bma\bai\bin\bnt\bta\bai\bin\bne\ber\brs\bs a\ban\bnd\bd c\bco\bon\bnt\btr\bri\bib\bbu\but\bto\bor\brs\bs
+
+  * PTEST_README: Writing Postfix unit tests
+
diff --git a/postfix/README_FILES/PTEST_README b/postfix/README_FILES/PTEST_README
new file mode 100644 (file)
index 0000000..522c5e8
--- /dev/null
@@ -0,0 +1,476 @@
+W\bWr\bri\bit\bti\bin\bng\bg P\bPo\bos\bst\btf\bfi\bix\bx u\bun\bni\bit\bt t\bte\bes\bst\bts\bs
+
+-------------------------------------------------------------------------------
+
+O\bOv\bve\ber\brv\bvi\bie\bew\bw
+
+This document covers Ptest, a simple unit test framework that was introduced
+with Postfix version 3.8. It is modeled after Go tests, with primitives such as
+ptest_error() and ptest_fatal() that report test failures, and PTEST_RUN() that
+supports subtests.
+
+Ptest is light-weight compared to more powerful frameworks such as Gtest, but
+it avoids the need for adding a large Postfix dependency (a dependency that
+would not affect Postfix distributors, but developers only).
+
+  * Simple example
+
+  * Testing one function with TEST_CASE data
+
+  * Testing functions with subtests
+
+  * Suggestions for writing tests
+
+  * Ptest API reference
+
+S\bSi\bim\bmp\bpl\ble\be e\bex\bxa\bam\bmp\bpl\ble\be
+
+Simple tests exercise one function under test, one scenario at a time. Each
+scenario calls the function under test with good or bad inputs, and verifies
+that the function behaves as expected. The code in Postfix mymalloc_test.c file
+is a good example.
+
+After some #include statements, the file goes like this:
+
+     27 typedef struct PTEST_CASE {
+     28     const char *testname;         /* Human-readable description */
+     29     void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+     30 } PTEST_CASE;
+     31
+     32 /* Test functions. */
+     33
+     34 static void test_mymalloc_normal(PTEST_CTX *t, const PTEST_CASE *tp)
+     35 {
+     36     void   *ptr;
+     37
+     38     ptr = mymalloc(100);
+     39     myfree(ptr);
+     40 }
+     41
+     42 static void test_mymalloc_panic_too_small(PTEST_CTX *t, const
+    PTEST_CASE *tp)
+     43 {
+     44     expect_ptest_log_event(t, "panic: mymalloc: requested length 0");
+     45     (void) mymalloc(0);
+     46     ptest_fatal(t, "mymalloc(0) returned");
+     47 }
+    ...     // Test functions for myrealloc(), mystrdup(), mymemdup().
+    260
+    261 static const PTEST_CASE ptestcases[] = {
+    262     {"mymalloc + myfree normal case", test_mymalloc_normal,
+    263     },
+    264     {"mymalloc panic for too small request",
+    test_mymalloc_panic_too_small,
+    265     },
+    ...     // Test cases for myrealloc(), mystrdup(), mymemdup().
+    306 };
+    307
+    308 #include <ptest_main.h>
+
+To run the test:
+
+    $ make test_mymalloc
+    ... compiler output...
+    LD_LIBRARY_PATH=/path/to/postfix-source/lib ./mymalloc_test
+    RUN  mymalloc + myfree normal case
+    PASS mymalloc + myfree normal case
+    RUN  mymalloc panic for too small request
+    PASS mymalloc panic for too small request
+    ... results for myrealloc(), mystrdup(), mymemdup()...
+    mymalloc_test: PASS: 22, SKIP: 0, FAIL: 0
+
+This simple example already shows several key features of the ptest framework.
+
+  * Each test is implemented as a separate function (test_mymalloc_normal(),
+    test_mymalloc_panic_too_small(), and so on). These functions take two
+    arguments: the first argument points to test infrastructure, and the second
+    argument is not used here but will feature in a later example.
+
+  * The first test verifies 'normal' behavior: it verifies that mymalloc() will
+    allocate a small amount of memory, and that myfree() will accept the result
+    from mymalloc(). When the test is run under a memory checker such as
+    Valgrind, the memory checker will report no memory leak or other error.
+
+  * The second test is more interesting.
+
+      o The test verifies that mymalloc() will call msg_panic() when the
+        requested amount of memory is too small. But in this test the msg_panic
+        () call will not terminate the process like it normally would. The
+        Ptest framework changes the control flow of msg_panic() and msg_fatal()
+        such that these functions will terminate their test, instead of their
+        process.
+
+      o The expect_ptest_log_event() call sets up an expectation that msg_panic
+        () will produce a specific error message; the test would fail if the
+        expectation remains unsatisfied.
+
+      o The ptest_fatal() call at the end of the second test is not needed;
+        this call can only be reached if mymalloc() does not call msg_panic().
+        But then the expected panic message would not be logged, and the test
+        would fail anyway.
+
+  * The ptestcases[] table near the end of the example contains for each test
+    the name and a pointer to function. As we show in a later example, the
+    ptestcases[] table can also contain test inputs and expected test outputs.
+
+  * The "#include <ptest_main.h>" at the end pulls in the code that iterates
+    over the ptestcases[] table and logs progress.
+
+  * The test run output shows that the msg_panic() output in the second test is
+    silenced; only output from unexpected msg_panic() or other unexpected msg
+    (3) calls would show up in test run output.
+
+T\bTe\bes\bst\bti\bin\bng\bg o\bon\bne\be f\bfu\bun\bnc\bct\bti\bio\bon\bn w\bwi\bit\bth\bh T\bTE\bES\bST\bT_\b_C\bCA\bAS\bSE\bE d\bda\bat\bta\ba
+
+Often, we want to test a module that contains only one function. In that case
+we can store all the test inputs and expected results in the PTEST_CASE
+structure.
+
+The examples below are taken from the dict_union_test.c file which test the
+unionmap implementation in the file. dict_union.c.
+
+Background: a unionmap creates a union of tables. For example, the lookup table
+"unionmap:{inline:{foo=one},inline:{foo=two}}" will return ("one, two",
+DICT_STAT_SUCCESS) when queried with foo, and will return (null,
+DICT_STAT_SUCCESS) otherwise.
+
+First, we present the TEST_CASE structure with additional fields for inputs and
+expected results.
+
+     29 #define MAX_PROBE       5
+     30
+     31 struct probe {
+     32     const char *query;
+     33     const char *want_value;
+     34     int     want_error;
+     35 };
+     36
+     37 typedef struct PTEST_CASE {
+     38     const char *testname;
+     39     void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+     40     const char *type_name;
+     41     const struct probe probes[MAX_PROBE];
+     42 } PTEST_CASE;
+
+In the PTEST_CASE structure above:
+
+  * The testname and action fields are required. We have seen these already in
+    the simple example above. The other PTEST_CASE fields are specific to the
+    unionmap tests.
+
+  * The type_name field will contain the name of the table, for example
+    unionmap:{static:one,inline:{foo=two}}.
+
+  * The probes field contains a list of (query, expected result value, expected
+    error code) that will be used to query the unionmap and to verify the
+    result value and error code.
+
+Next we show the test data. Every test calls the same test_dict_union()
+function with a different unionmap configuration and with a list of queries
+with expected results. The implementation of that function follows after the
+test data.
+
+    115 static const PTEST_CASE ptestcases[] = {
+    116     {
+    ...
+    120         .testname = "propagates notfound and found",
+    121         .action = test_dict_union,
+    122         .type_name = "unionmap:{static:one,inline:{foo=two}}",
+    123         .probes = {
+    124             {"foo", "one,two", DICT_STAT_SUCCESS},
+    125             {"bar", "one", DICT_STAT_SUCCESS},
+    126         },
+    127     }, {
+    128         .testname = "error propagation: static map + fail map",
+    129         .action = test_dict_union,
+    130         .type_name = "unionmap:{static:one,fail:fail}",
+    131         .probes = {
+    132             {"foo", 0, DICT_ERR_RETRY},
+    133         },
+    ...
+    151     },
+    152 };
+    153
+    154 #include <ptest_main.h>
+
+Finally, here is the test_dict_union() function that queries the unionmap
+implementation with test inputs, and verifies that the results are as expected.
+
+     84 static void test_dict_union(PTEST_CTX *t, const struct PTEST_CASE *tp)
+     85 {
+     86     DICT   *dict;
+     87     const struct probe *pp;
+     88     const char *got_value;
+     89     int     got_error;
+     90
+     91     dict = dict_open(tp->type_name, O_RDONLY, 0);
+     92
+     93     for (pp = tp->probes; pp < tp->probes + MAX_PROBE && pp->query !=
+    0; pp++) {
+     94         got_value = dict_get(dict, pp->query);
+     95         got_error = dict->error;
+     96         if (got_value == 0 && pp->want_value == 0)
+     97             continue;
+     98         if (got_value == 0 || pp->want_value == 0) {
+     99             ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want
+    '%s'",
+    100                         pp->query, STR_OR_NULL(got_value),
+    101                         STR_OR_NULL(pp->want_value));
+    102             break;
+    103         }
+    104         if (strcmp(got_value, pp->want_value) != 0) {
+    105             ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want
+    '%s'",
+    106                         pp->query, got_value, pp->want_value);
+    107         }
+    108         if (got_error != pp->want_error)
+    109             ptest_error(t, "dict_get(dict,\"%s\") error: got %d, want
+    %d",
+    110                         pp->query, got_error, pp->want_error);
+    111     }
+    112     dict_close(dict);
+    113 }
+
+A test run looks like this:
+
+    $ make test_dict_union
+    ...compiler output...
+    LD_LIBRARY_PATH=/path/to/postfix-source/lib ./dict_union_test
+    ...
+    RUN  propagates notfound and found
+    PASS propagates notfound and found
+    RUN  error propagation: static map + fail map
+    PASS error propagation: static map + fail map
+    ...
+    dict_union_test: PASS: 5, SKIP: 0, FAIL: 0
+
+T\bTe\bes\bst\bti\bin\bng\bg f\bfu\bun\bnc\bct\bti\bio\bon\bns\bs w\bwi\bit\bth\bh s\bsu\bub\bbt\bte\bes\bst\bts\bs
+
+Sometimes it is not convenient to store test data in a PTEST_CASE structure.
+This can happen when converting an existing test into Ptest, or when the module
+under test contains functions that need different kinds of test data. The
+solution is to create a _test.c file with the structure as shown below. This
+can be found in the file map_search_test.c, which was converted from an
+existing test into Ptest.
+
+  * One PTEST_CASE structure definition without test data.
+
+     50 typedef struct PTEST_CASE {
+     51     const char *testname;
+     52     void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+     53 } PTEST_CASE;
+
+  * One test function for each module function that needs to be tested, and one
+    table with test cases for that module function. In this case there is only
+    one module function (map_search()) that needs to be tested, so there is
+    only one test function (test_map_search()).
+
+     67 #define MAX_WANT_LOG    5
+     68
+     69 static void test_map_search(PTEST_CTX *t, const struct PTEST_CASE
+    *unused)
+     70 {
+     71     /* Test cases with inputs and expected outputs. */
+     72     struct test {
+     73         const char *map_spec;
+     74         int     want_return;            /* 0=fail, 1=success */
+     75         const char *want_log[MAX_WANT_LOG];
+     76         const char *want_map_type_name; /* 0 or match */
+     77         const char *want_search_order;   /* 0 or match */
+     78     };
+     79     static struct test test_cases[] = {
+     80         {                               /* 0 */
+     81             .map_spec = "type",
+     82             .want_return = 0,
+     83             .want_log = {
+     84                 "malformed map specification: 'type'",
+     85                 "expected maptype:mapname instead of 'type'",
+     86             },
+     87         },
+     88         {                               /* 1 */
+     89             .map_spec = "type:name",
+     90             .want_return = 1,
+     91             .want_map_type_name = "type:name",
+     92         },
+    .. .        // ...other test cases...
+    166     };
+
+  * In a test function, iterate over its table with test cases, using PTEST_RUN
+    () to run each test case in its own subtest.
+
+    184     for (tp = test_cases; tp->map_spec; tp++) {
+    185         vstring_sprintf(test_label, "test %d", (int) (tp -
+    test_cases));
+    186         PTEST_RUN(t, STR(test_label), {
+    187             for (cpp = tp->want_log; cpp < tp->want_log + MAX_WANT_LOG
+    && *cpp; cpp++)
+    188                 expect_ptest_log_event(t, *cpp);
+    189             map_search_from_create = map_search_create(tp->map_spec);
+    ...            // ...verify that the result is as expected...
+    ...            // ...use ptest_return() or ptest_fatal() to exit from a
+    test...
+    228         });
+    229     }
+    ...
+
+  * Create a ptestcases[] table to call each test function once, and include
+    the Ptest main program.
+
+    183 static const PTEST_CASE ptestcases[] = {
+    184     "test_map_search", test_map_search,
+    185 };
+    186
+    187 #include <ptest_main.h>
+
+See the file map_search_test.c for a complete example.
+
+This is what a test run looks like:
+
+    $ make test_map_search
+    ...compiler output...
+    LD_LIBRARY_PATH=/path/to/postfix-source/lib  ./map_search_test
+    RUN  test_map_search
+    RUN  test_map_search/test 0
+    PASS test_map_search/test 0
+    RUN  test_map_search/test 1
+    PASS test_map_search/test 1
+    ....
+    PASS test_map_search
+    map_search_test: PASS: 13, SKIP: 0, FAIL: 0
+
+This shows that the subtest name is appended to the parent test name, formatted
+as parent-name/child-name.
+
+S\bSu\bug\bgg\bge\bes\bst\bti\bio\bon\bns\bs f\bfo\bor\br w\bwr\bri\bit\bti\bin\bng\bg t\bte\bes\bst\bts\bs
+
+Ptest is loosely inspired on Go test, especially its top-level test functions
+and its methods T.run(), T.error() and T.fatal().
+
+Suggestions for test style may look familiar to Go programmers:
+
+  * Use identifiers named got_xxx and want_xxx. When a test result is
+    unexpected, log the discrepancy as "got <what you got>, want <what you
+    want>".
+
+  * Report discrepancies with ptest_error() if possible; use ptest_fatal() only
+    when continuing the test would produce nonsensical results.
+
+  * Where it makes sense use a table with testcases and use PTEST_RUN() to run
+    each testcase in its own subtest.
+
+Other suggestions:
+
+  * Consider running tests under a memory checker such as Valgrind. Use
+    ptest_defer() to avoid memory leaks when a test should terminate early.
+
+  * Always test non-error and error cases, to cover all code paths in the
+    function under test.
+
+P\bPt\bte\bes\bst\bt A\bAP\bPI\bI r\bre\bef\bfe\ber\bre\ben\bnc\bce\be
+
+  * Managing test errors
+
+  * Managing log events
+
+  * Managing test execution
+
+  * Miscellaneous test APIs
+
+M\bMa\ban\bna\bag\bgi\bin\bng\bg t\bte\bes\bst\bt e\ber\brr\bro\bor\brs\bs
+
+As one might expect, Ptest has support to flag unexpected test results as
+errors.
+
+v\bvo\boi\bid\bd p\bpt\bte\bes\bst\bt_\b_e\ber\brr\bro\bor\br(\b(P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*t\bt,\b, c\bco\bon\bns\bst\bt c\bch\bha\bar\br *\b*f\bfo\bor\brm\bma\bat\bt,\b, .\b..\b..\b.)\b)
+    Called from inside a test, to report an unexpected test result, and to flag
+    the test as failed without terminating the test. This call can be ignored
+    with expect_ptest_error().
+
+v\bvo\boi\bid\bd p\bpt\bte\bes\bst\bt_\b_f\bfa\bat\bta\bal\bl(\b(P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*t\bt,\b, c\bco\bon\bns\bst\bt c\bch\bha\bar\br *\b*f\bfo\bor\brm\bma\bat\bt,\b, .\b..\b..\b.)\b)
+    Called from inside a test, to report an unexpected test result, to flag the
+    test as failed, and to terminate the test. This call cannot be ignored with
+    expect_ptest_error().
+For convenience, Ptest can also report non-error information.
+
+v\bvo\boi\bid\bd p\bpt\bte\bes\bst\bt_\b_i\bin\bnf\bfo\bo(\b(P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*t\bt,\b, c\bco\bon\bns\bst\bt c\bch\bha\bar\br *\b*f\bfo\bor\brm\bma\bat\bt,\b, .\b..\b..\b.)\b)
+    Called from inside a test, to report a non-error condition without
+    terminating the test. This call cannot be ignored with expect_ptest_error
+    ().
+Finally, Ptest has support to test ptest_error() itself, to verify that an
+intentional error is reported as expected.
+
+v\bvo\boi\bid\bd e\bex\bxp\bpe\bec\bct\bt_\b_p\bpt\bte\bes\bst\bt_\b_e\ber\brr\bro\bor\br(\b(P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*t\bt,\b, c\bco\bon\bns\bst\bt c\bch\bha\bar\br *\b*t\bte\bex\bxt\bt)\b)
+    Called from inside a test, to expect exactly one ptest_error() call with
+    the specified text, and to ignore that ptest_error() call (i.e. don't flag
+    the test as failed). To ignore multiple calls, call expect_ptest_error()
+    multiple times. A test is flagged as failed when an expected error is not
+    reported (and of course when an error is reported that is not expected with
+    expect_ptest_error()).
+
+M\bMa\ban\bna\bag\bgi\bin\bng\bg l\blo\bog\bg e\bev\bve\ben\bnt\bts\bs
+
+Ptest integrates with Postfix msg(3) logging.
+
+  * Ptest changes the control flow of msg_fatal() and msg_panic(). When these
+    functions are called during a test, Ptest flags a test as failed and
+    terminates the test instead of the process.
+
+  * Ptest silences the output from msg_info() and other msg(3) calls, and
+    installs a log event listener to monitor Postfix logging.
+
+Ptest provides the following API to manage log events:
+
+v\bvo\boi\bid\bd e\bex\bxp\bpe\bec\bct\bt_\b_p\bpt\bte\bes\bst\bt_\b_l\blo\bog\bg_\b_e\bev\bve\ben\bnt\bt(\b(P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*t\bt,\b, c\bco\bon\bns\bst\bt c\bch\bha\bar\br *\b*t\bte\bex\bxt\bt)\b)
+    Called from inside a test, to expect exactly one msg(3) call with the
+    specified text. To expect multiple events, call expect_ptest_log_event()
+    multiple times. A test is flagged as failed when expected text is not
+    logged, or when text is logged that is not expected with
+    expect_ptest_log_event().
+
+M\bMa\ban\bna\bag\bgi\bin\bng\bg t\bte\bes\bst\bt e\bex\bxe\bec\bcu\but\bti\bio\bon\bn
+
+Ptest has a number of primitives that control test execution.
+
+v\bvo\boi\bid\bd P\bPT\bTE\bES\bST\bT_\b_R\bRU\bUN\bN(\b(P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*t\bt,\b, c\bco\bon\bns\bst\bt c\bch\bha\bar\br *\b*t\bte\bes\bst\bt_\b_n\bna\bam\bme\be,\b, {\b{ c\bco\bod\bde\be i\bin\bn b\bbr\bra\bac\bce\bes\bs }\b})\b)
+    Called from inside a test, to run the { code in braces } in it own subtest
+    environment. In the test progress report, the subtest name is appended to
+    the parent test name, formatted as parent-name/child-name.
+
+    NOTE: because PTEST_RUN() is a macro, the { code in braces } MUST NOT
+    contain a return statement; use ptest_return() instead. It is OK for { code
+    in braces } to call a function that uses return.
+
+v\bvo\boi\bid\bd P\bPT\bTE\bES\bST\bT_\b_T\bTR\bRY\bY(\b(P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*t\bt,\b, c\bco\bon\bns\bst\bt c\bch\bha\bar\br *\b*t\bte\bes\bst\bt_\b_n\bna\bam\bme\be,\b, {\b{ c\bco\bod\bde\be i\bin\bn b\bbr\bra\bac\bce\bes\bs }\b})\b)
+    Called from inside a test, to run the { code in braces } without entering a
+    new subtest environment. The purpose is to continue running the current
+    test after the { code in braces } calls msg_fatal*() or msg_panic(). The
+    { code in braces } should set a variable to indicate that PTEST_TRY()
+    executed "normally".
+
+    NOTE: because PTEST_TRY() is a macro, the { code in braces } MUST NOT
+    contain a return statement; use ptest_return() instead. It is OK for { code
+    in braces } to call a function that uses return.
+
+N\bNO\bOR\bRE\bET\bTU\bUR\bRN\bN p\bpt\bte\bes\bst\bt_\b_s\bsk\bki\bip\bp(\b(P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*t\bt)\b)
+    Called from inside a test, to flag a test as skipped, and to terminate the
+    test without terminating the process. Use this to disable tests that are
+    not applicable for a specific system type or build configuration.
+
+N\bNO\bOR\bRE\bET\bTU\bUR\bRN\bN p\bpt\bte\bes\bst\bt_\b_r\bre\bet\btu\bur\brn\bn(\b(P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*t\bt)\b)
+    Used inside a { code in braces } block to terminate a PTEST_RUN subtest.
+
+v\bvo\boi\bid\bd p\bpt\bte\bes\bst\bt_\b_d\bde\bef\bfe\ber\br(\b(P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*t\bt,\b, v\bvo\boi\bid\bd (\b(*\b*d\bde\bef\bfe\ber\br_\b_f\bfn\bn)\b)(\b(v\bvo\boi\bid\bd *\b*)\b),\b, v\bvo\boi\bid\bd *\b*d\bde\bef\bfe\ber\br_\b_c\bct\btx\bx)\b)
+    Called once from inside a test, to call defer_fn(defer_ctx) after the test
+    completes. This is typically used to eliminate a resource leak in tests
+    that terminate the test early.
+
+    NOTE: The deferred function is designed to run outside a test, and
+    therefore it must not call Ptest functions.
+
+M\bMi\bis\bsc\bce\bel\bll\bla\ban\bne\beo\bou\bus\bs t\bte\bes\bst\bt A\bAP\bPI\bIs\bs
+
+P\bPT\bTE\bES\bST\bT_\b_C\bCT\bTX\bX *\b*p\bpt\bte\bes\bst\bt_\b_c\bct\btx\bx_\b_c\bcu\bur\brr\bre\ben\bnt\bt(\b(v\bvo\boi\bid\bd)\b)
+    Returns the PTEST_CTX pointer for the current test or subtest. This can be
+    used to handle a test error in a mock function or helper function that has
+    no PTEST_CTX argument.
+
index 0d95a975d694ec78a2eccdcd0e4c248a74dad37c..7db6b3c3babd691e4c856e3801ebb53bd08e541a 100644 (file)
@@ -312,6 +312,7 @@ $readme_directory/MILTER_README:f:root:-:644
 $readme_directory/MONGODB_README:f:root:-:644
 $readme_directory/MULTI_INSTANCE_README:f:root:-:644
 $readme_directory/MYSQL_README:f:root:-:644
+$readme_directory/PTEST_README:f:root:-:644
 $readme_directory/SMTPUTF8_README:f:root:-:644
 $readme_directory/SQLITE_README:f:root:-:644
 $readme_directory/NFS_README:f:root:-:644
@@ -378,6 +379,7 @@ $html_directory/MILTER_README.html:f:root:-:644
 $html_directory/MONGODB_README.html:f:root:-:644
 $html_directory/MULTI_INSTANCE_README.html:f:root:-:644
 $html_directory/MYSQL_README.html:f:root:-:644
+$html_directory/PTEST_README.html:f:root:-:644
 $html_directory/SMTPUTF8_README.html:f:root:-:644
 $html_directory/SQLITE_README.html:f:root:-:644
 $html_directory/NFS_README.html:f:root:-:644
diff --git a/postfix/html/PTEST_README.html b/postfix/html/PTEST_README.html
new file mode 100644 (file)
index 0000000..1655906
--- /dev/null
@@ -0,0 +1,642 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Writing Postfix unit tests </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<link rel='stylesheet' type='text/css' href='postfix-doc.css'>
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Writing
+Postfix unit tests </h1>
+
+<hr>
+
+<h2> Overview </h2>
+
+<p> This document covers Ptest, a simple unit test framework that
+was introduced with Postfix version 3.8. It is modeled after Go
+tests, with primitives such as <a href="PTEST_README.html#ptest_error">ptest_error</a>() and <a href="PTEST_README.html#ptest_fatal">ptest_fatal</a>() that
+report test failures, and <a href="PTEST_README.html#PTEST_RUN">PTEST_RUN</a>() that supports subtests. </p>
+
+<p> Ptest is light-weight compared to more powerful frameworks
+such as Gtest, but it avoids the need for adding a large Postfix
+dependency (a dependency that would not affect Postfix distributors,
+but developers only). </p>
+
+<ul>
+
+<li> <p> <a href="#simple_example"> Simple example </a> </p>
+
+<li> <p> <a href="#test_case_data"> Testing one function with
+TEST_CASE data </a> </p>
+
+<li> <p> <a href="#sub_tests"> Testing functions with subtests </a> </p>
+
+<li> <p> <a href="#style"> Suggestions for writing tests </a> </p>
+
+<li> <p> <a href="#api_reference"> Ptest API reference  </a> </p>
+
+</ul>
+
+<h2> <a name="simple_example"> Simple example </a> </h2>
+
+<p> Simple tests exercise one function under test, one scenario at
+a time.  Each scenario calls the function under test with good or
+bad inputs, and verifies that the function behaves as expected. The
+code in Postfix <tt>mymalloc_test.c</tt> file is a good example. </p>
+
+<p> After some <tt>#include</tt> statements, the file goes like
+this: </p>
+
+<blockquote>
+<pre>
+ 27 typedef struct PTEST_CASE {
+ 28     const char *testname;         /* Human-readable description */
+ 29     void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+ 30 } PTEST_CASE;
+ 31 
+ 32 /* Test functions. */
+ 33 
+ 34 static void test_mymalloc_normal(PTEST_CTX *t, const PTEST_CASE *tp)
+ 35 {
+ 36     void   *ptr;
+ 37 
+ 38     ptr = mymalloc(100);
+ 39     myfree(ptr);
+ 40 }
+ 41 
+ 42 static void test_mymalloc_panic_too_small(PTEST_CTX *t, const PTEST_CASE *tp)
+ 43 {
+ 44     <a href="PTEST_README.html#expect_ptest_log_event">expect_ptest_log_event</a>(t, "panic: mymalloc: requested length 0");
+ 45     (void) mymalloc(0);
+ 46     <a href="PTEST_README.html#ptest_fatal">ptest_fatal</a>(t, "mymalloc(0) returned");
+ 47 }
+...     // Test functions for myrealloc(), mystrdup(), mymemdup().
+260
+261 static const PTEST_CASE ptestcases[] = {
+262     {"mymalloc + myfree normal case", test_mymalloc_normal,
+263     },
+264     {"mymalloc panic for too small request", test_mymalloc_panic_too_small,
+265     },
+...     // Test cases for myrealloc(), mystrdup(), mymemdup().
+306 };
+307 
+308 #include &lt;ptest_main.h&gt;
+</pre>
+</blockquote>
+
+<p> To run the test: </p>
+
+<blockquote>
+<pre>
+$ make test_mymalloc
+... compiler output...
+LD_LIBRARY_PATH=/path/to/postfix-source/lib ./mymalloc_test
+RUN  mymalloc + myfree normal case
+PASS mymalloc + myfree normal case
+RUN  mymalloc panic for too small request
+PASS mymalloc panic for too small request
+... results for myrealloc(), mystrdup(), mymemdup()...
+mymalloc_test: PASS: 22, SKIP: 0, FAIL: 0
+</pre>
+</blockquote>
+
+<p> This simple example already shows several key features of the ptest
+framework. </p>
+
+<ul>
+
+<li> <p> Each test is implemented as a separate function
+(<tt>test_mymalloc_normal()</tt>, <tt>test_mymalloc_panic_too_small()</tt>,
+and so on). These functions take two arguments: the first argument
+points to test infrastructure, and the second argument is not used
+here but will feature in a later example. </p>
+
+<li> <p> The first test verifies 'normal' behavior: it verifies that
+<tt>mymalloc()</tt> will allocate a small amount of memory, and that
+<tt>myfree()</tt> will accept the result from <tt>mymalloc()</tt>.
+When the test is run under a memory checker such as Valgrind, the
+memory checker will report no memory leak or other error. </p>
+
+<li> <p> The second test is more interesting. </p>
+
+<ul>
+
+<li> <p> The test verifies that <tt>mymalloc()</tt> will call
+<tt>msg_panic()</tt> when the requested amount of memory is too
+small. But in this test the <tt>msg_panic()</tt> call will not
+terminate the process like it normally would. The Ptest framework
+changes the control flow of <tt>msg_panic()</tt> and <tt>msg_fatal()</tt>
+such that these functions will terminate their test, instead of
+their process. </p>
+
+<li> <p> The <tt><a href="PTEST_README.html#expect_ptest_log_event">expect_ptest_log_event</a>()</tt> call sets up an
+expectation that <tt>msg_panic()</tt> will produce a specific error
+message; the test would fail if the expectation remains unsatisfied.
+</p>
+
+<li> <p> The <tt><a href="PTEST_README.html#ptest_fatal">ptest_fatal</a>()</tt> call at the end of the second
+test is not needed; this call can only be reached if <tt>mymalloc()</tt>
+does not call <tt>msg_panic()</tt>. But then the expected panic
+message would not be logged, and the test would fail anyway. </p>
+
+</ul>
+
+<li> <p> The <tt>ptestcases[]</tt> table near the end of the example
+contains for each test the name and a pointer to function. As we
+show in a later example, the <tt>ptestcases[]</tt> table can also
+contain test inputs and expected test outputs. </p>
+
+<li> <p> The "<tt>#include &lt;ptest_main.h&gt;</tt>" at the end pulls
+in the code that iterates over the <tt>ptestcases[]</tt> table and
+logs progress.
+
+<li> <p> The test run output shows that the <tt>msg_panic()</tt>
+output in the second test is silenced; only output from unexpected
+<tt>msg_panic()</tt> or other unexpected <tt>msg(3)</tt> calls would
+show up in test run output. </p>
+
+</ul>
+
+<h2> <a name="test_case_data"> Testing one function with
+TEST_CASE data </a> </h2>
+
+<p> Often, we want to test a module that contains only one function. In
+that case we can store all the test inputs and expected results in the
+PTEST_CASE structure. </p>
+
+<p> The examples below are taken from the <tt>dict_union_test.c</tt>
+file which test the <tt>unionmap</tt> implementation in the file.
+<tt>dict_union.c</tt>. </p>
+
+<p> Background: a unionmap creates a union of tables. For example,
+the lookup table "<tt><a href="DATABASE_README.html#types">unionmap</a>:{<a href="DATABASE_README.html#types">inline</a>:{foo=one},<a href="DATABASE_README.html#types">inline</a>:{foo=two}}</tt>"
+will return ("<tt>one, two</tt>", DICT_STAT_SUCCESS) when queried
+with <tt>foo</tt>, and will return (null, DICT_STAT_SUCCESS)
+otherwise. </p>
+
+<p> First, we present the TEST_CASE structure with additional fields
+for inputs and expected results. </p>
+
+<blockquote>
+<pre>
+ 29 #define MAX_PROBE       5
+ 30 
+ 31 struct probe {
+ 32     const char *query;
+ 33     const char *want_value;
+ 34     int     want_error;
+ 35 };
+ 36 
+ 37 typedef struct PTEST_CASE {
+ 38     const char *testname;
+ 39     void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+ 40     const char *type_name;
+ 41     const struct probe probes[MAX_PROBE];
+ 42 } PTEST_CASE;
+</pre>
+</blockquote>
+
+<p> In the PTEST_CASE structure above: </p>
+
+<ul>
+
+<li> <p> The <tt>testname</tt> and <tt>action</tt> fields are
+required. We have seen these already in the simple example above.
+The other PTEST_CASE fields are specific to the unionmap tests.
+<p>
+
+<li> <p> The <tt>type_name</tt> field will contain the name of the table,
+for example <tt><a href="DATABASE_README.html#types">unionmap</a>:{<a href="DATABASE_README.html#types">static</a>:one,<a href="DATABASE_README.html#types">inline</a>:{foo=two}}</tt>. </p>
+
+<li> <p> The <tt>probes</tt> field contains a list of (query, expected
+result value, expected error code) that will be used to query the unionmap
+and to verify the result value and error code.
+</p>
+
+</ul>
+
+<p> Next we show the test data. Every test calls the same
+<tt>test_dict_union()</tt> function with a different <tt>unionmap</tt>
+configuration and with a list of queries with expected results. The
+implementation of that function follows after the test data. </p>
+
+<blockquote>
+<pre>
+115 static const PTEST_CASE ptestcases[] = {
+116     {
+...
+120         .testname = "propagates notfound and found",
+121         .action = test_dict_union,
+122         .type_name = "<a href="DATABASE_README.html#types">unionmap</a>:{<a href="DATABASE_README.html#types">static</a>:one,<a href="DATABASE_README.html#types">inline</a>:{foo=two}}",
+123         .probes = {
+124             {"foo", "one,two", DICT_STAT_SUCCESS},
+125             {"bar", "one", DICT_STAT_SUCCESS},
+126         },
+127     }, {
+128         .testname = "error propagation: static map + fail map",
+129         .action = test_dict_union,
+130         .type_name = "<a href="DATABASE_README.html#types">unionmap</a>:{<a href="DATABASE_README.html#types">static</a>:one,<a href="DATABASE_README.html#types">fail</a>:fail}",
+131         .probes = {
+132             {"foo", 0, DICT_ERR_RETRY},
+133         },
+...
+151     },
+152 };
+153 
+154 #include &lt;ptest_main.h&gt;
+</pre>
+</blockquote>
+
+<p> Finally, here is the <tt>test_dict_union()</tt> function that
+queries the <tt>unionmap</tt> implementation with test inputs, and
+verifies that the results are as expected. </p>
+
+<blockquote>
+<pre>
+ 84 static void test_dict_union(PTEST_CTX *t, const struct PTEST_CASE *tp)
+ 85 {
+ 86     DICT   *dict;
+ 87     const struct probe *pp;
+ 88     const char *got_value;
+ 89     int     got_error;
+ 90 
+ 91     dict = dict_open(tp-&gt;type_name, O_RDONLY, 0);
+ 92 
+ 93     for (pp = tp-&gt;probes; pp &lt; tp-&gt;probes + MAX_PROBE && pp-&gt;query != 0; pp++) {
+ 94         got_value = dict_get(dict, pp-&gt;query);
+ 95         got_error = dict-&gt;error;
+ 96         if (got_value == 0 && pp-&gt;want_value == 0)
+ 97             continue;
+ 98         if (got_value == 0 || pp-&gt;want_value == 0) {
+ 99             <a href="PTEST_README.html#ptest_error">ptest_error</a>(t, "dict_get(dict, \"%s\"): got '%s', want '%s'",
+100                         pp-&gt;query, STR_OR_NULL(got_value),
+101                         STR_OR_NULL(pp-&gt;want_value));
+102             break;
+103         }
+104         if (strcmp(got_value, pp-&gt;want_value) != 0) {
+105             <a href="PTEST_README.html#ptest_error">ptest_error</a>(t, "dict_get(dict, \"%s\"): got '%s', want '%s'",
+106                         pp-&gt;query, got_value, pp-&gt;want_value);
+107         }
+108         if (got_error != pp-&gt;want_error)
+109             <a href="PTEST_README.html#ptest_error">ptest_error</a>(t, "dict_get(dict,\"%s\") <a href="error.8.html">error</a>: got %d, want %d",
+110                         pp-&gt;query, got_error, pp-&gt;want_error);
+111     }
+112     dict_close(dict);
+113 }
+</pre> 
+</blockquote>
+
+<p> A test run looks like this: </p>
+
+<blockquote>
+<pre>
+$ make test_dict_union
+...compiler output...
+LD_LIBRARY_PATH=/path/to/postfix-source/lib ./dict_union_test
+...
+RUN  propagates notfound and found
+PASS propagates notfound and found
+RUN  error propagation: static map + fail map
+PASS error propagation: static map + fail map
+...
+dict_union_test: PASS: 5, SKIP: 0, FAIL: 0
+</pre>
+</blockquote>
+
+<h2> <a name="sub_tests"> Testing functions with subtests </a> </h2>
+
+<p> Sometimes it is not convenient to store test data in a PTEST_CASE
+structure. This can happen when converting an existing test into
+Ptest, or when the module under test contains functions that need
+different kinds of test data. The solution is to create a
+<tt>_test.c</tt> file with the structure as shown below. This can
+be found in the file <tt>map_search_test.c</tt>, which was converted
+from an existing test into Ptest. </p>
+
+<ul>
+
+<li> <p> One PTEST_CASE structure definition without test data. </p>
+
+<pre>
+ 50 typedef struct PTEST_CASE {
+ 51     const char *testname;
+ 52     void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+ 53 } PTEST_CASE;
+</pre>
+
+<li> <p> One test function for each module function that needs to
+be tested, and one table with test cases for that module function.
+In this case there is only one module function (<tt>map_search()</tt>)
+that needs to be tested, so there is only one test function
+(<tt>test_map_search()</tt>). </p>
+
+<pre>
+ 67 #define MAX_WANT_LOG    5
+ 68 
+ 69 static void test_map_search(PTEST_CTX *t, const struct PTEST_CASE *unused)
+ 70 {
+ 71     /* Test cases with inputs and expected outputs. */
+ 72     struct test {
+ 73         const char *map_spec;
+ 74         int     want_return;            /* 0=fail, 1=success */
+ 75         const char *want_log[MAX_WANT_LOG];
+ 76         const char *want_map_type_name; /* 0 or match */
+ 77         const char *want_search_order;   /* 0 or match */
+ 78     };
+ 79     static struct test test_cases[] = {
+ 80         {                               /* 0 */
+ 81             .map_spec = "type",
+ 82             .want_return = 0,
+ 83             .want_log = {
+ 84                 "malformed map specification: 'type'",
+ 85                 "expected maptype:mapname instead of 'type'",
+ 86             },
+ 87         },
+ 88         {                               /* 1 */
+ 89             .map_spec = "type:name",
+ 90             .want_return = 1,
+ 91             .want_map_type_name = "type:name",
+ 92         },
+.. .        // ...other test cases...
+166     };
+</pre>
+
+<li> <p> In a test function, iterate over its table with test cases,
+using <tt><a href="PTEST_README.html#PTEST_RUN">PTEST_RUN</a>()</tt> to run each test case in its own subtest.
+</p>
+
+<pre>
+184     for (tp = test_cases; tp-&gt;map_spec; tp++) {
+185         vstring_sprintf(test_label, "test %d", (int) (tp - test_cases));
+186         <a href="PTEST_README.html#PTEST_RUN">PTEST_RUN</a>(t, STR(test_label), {
+187             for (cpp = tp-&gt;want_log; cpp &lt; tp-&gt;want_log + MAX_WANT_LOG && *cpp; cpp++)
+188                 <a href="PTEST_README.html#expect_ptest_log_event">expect_ptest_log_event</a>(t, *cpp);
+189             map_search_from_create = map_search_create(tp-&gt;map_spec);
+...            // ...verify that the result is as expected...
+...            // ...use <a href="PTEST_README.html#ptest_return">ptest_return</a>() or <a href="PTEST_README.html#ptest_fatal">ptest_fatal</a>() to exit from a test...
+228         });
+229     }
+...
+</pre>
+
+<li> <p> Create a <tt>ptestcases[]</tt> table to call each test
+function once, and include the Ptest main program. </p>
+
+<pre>
+183 static const PTEST_CASE ptestcases[] = {
+184     "test_map_search", test_map_search,
+185 };
+186 
+187 #include &lt;ptest_main.h&gt;
+</pre>
+
+</ul>
+
+<p> See the file <tt>map_search_test.c</tt> for a complete example.
+</p>
+
+<p> This is what a test run looks like: </p>
+
+<blockquote>
+<pre>
+$ make test_map_search
+...compiler output...
+LD_LIBRARY_PATH=/path/to/postfix-source/lib  ./map_search_test
+RUN  test_map_search
+RUN  test_map_search/test 0
+PASS test_map_search/test 0
+RUN  test_map_search/test 1
+PASS test_map_search/test 1
+....
+PASS test_map_search
+map_search_test: PASS: 13, SKIP: 0, FAIL: 0
+</pre>
+</blockquote>
+
+<p> This shows that the subtest name is appended to the parent test
+name, formatted as <i>parent-name/child-name</i>. </p>
+
+<h2> <a name="style"> Suggestions for writing tests </a> </h2>
+
+<p> Ptest is loosely inspired on Go test, especially its top-level
+test functions and its methods <tt>T.run()</tt>, <tt>T.error()</tt>
+and <tt>T.fatal()</tt>. </p>
+
+<p> Suggestions for test style may look familiar to Go programmers:
+</p>
+
+<ul>
+
+<li> <p> Use identifiers named <tt>got_xxx</tt> and <tt>want_xxx</tt>.
+When a test result is unexpected, log the discrepancy as "got
+&lt;what you got&gt;, want &lt;what you want&gt;".  </p>
+
+<li> <p> Report discrepancies with <tt><a href="PTEST_README.html#ptest_error">ptest_error</a>()</tt> if possible;
+use <tt><a href="PTEST_README.html#ptest_fatal">ptest_fatal</a>()</tt> only when continuing the test would
+produce nonsensical results. </p>
+
+<li> <p> Where it makes sense use a table with testcases and use
+<tt><a href="PTEST_README.html#PTEST_RUN">PTEST_RUN</a>()</tt> to run each testcase in its own subtest. </p>
+
+</ul>
+
+<p> Other suggestions: </p>
+
+<ul>
+
+<li> <p> Consider running tests under a memory checker such as
+Valgrind. Use <tt><a href="PTEST_README.html#ptest_defer">ptest_defer</a>()</tt> to avoid memory leaks when a
+test should terminate early. </p>
+
+<li> <p> Always test non-error and error cases, to cover all code
+paths in the function under test. </p>
+
+</ul>
+
+<h2> <a name="api_reference"> Ptest API reference </a> </h2>
+
+<ul>
+
+<li> <p> <a href="#managing_errors">Managing test errors</a>
+
+<li> <p> <a href="#managing_logs">Managing log events</a>
+
+<li> <p> <a href="#managing_flows">Managing test execution </a>
+
+<li> <p> <a href="#misc_api">Miscellaneous test APIs </a>
+
+</ul>
+
+<h2> <a name="managing_errors"> Managing test errors</a> </h2>
+
+<p> As one might expect, Ptest has support to flag unexpected test
+results as errors. </p>
+
+<dl>
+
+<dt> <b> <a name="ptest_error"> <tt>void ptest_error(PTEST_CTX *t,
+const char *format, ...)</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to report an unexpected test result,
+and to flag the test as failed without terminating the test. This
+call can be ignored with <tt><a href="PTEST_README.html#expect_ptest_error">expect_ptest_error</a>()</tt>. <br> <br> </dd>
+
+<dt> <b> <a name="ptest_fatal"> <tt>void ptest_fatal(PTEST_CTX *t,
+const char *format, ...)</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to report an unexpected test result,
+to flag the test as failed, and to terminate the test. This call
+cannot be ignored with <tt><a href="PTEST_README.html#expect_ptest_error">expect_ptest_error</a>()</tt>. </dd>
+
+</dl>
+
+<p> For convenience, Ptest can also report non-error information.
+</p>
+
+<dl>
+
+<dt> <b> <a name="ptest_info"> <tt>void ptest_info(PTEST_CTX *t,
+const char *format, ...)</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to report a non-error condition
+without terminating the test. This call cannot be ignored with
+<tt><a href="PTEST_README.html#expect_ptest_error">expect_ptest_error</a>()</tt>. </dd>
+
+</dl>
+
+<p> Finally, Ptest has support to test <tt><a href="PTEST_README.html#ptest_error">ptest_error</a>()</tt> itself,
+to verify that an intentional error is reported as expected. </p>
+
+<dl>
+
+<dt> <b> <a name="expect_ptest_error"> <tt>void
+expect_ptest_error(PTEST_CTX *t, const char *text)</tt> </a> </b>
+</dt>
+
+<dd> Called from inside a test, to expect exactly one <tt><a href="PTEST_README.html#ptest_error">ptest_error</a>()</tt>
+call with the specified text, and to ignore that <tt><a href="PTEST_README.html#ptest_error">ptest_error</a>()</tt>
+call (i.e. don't flag the test as failed). To ignore multiple calls,
+call <tt><a href="PTEST_README.html#expect_ptest_error">expect_ptest_error</a>()</tt> multiple times. A test is flagged
+as failed when an expected error is not reported (and of course
+when an error is reported that is not expected with
+<tt><a href="PTEST_README.html#expect_ptest_error">expect_ptest_error</a>()</tt>). </dd>
+
+</dl>
+
+<h2> <a name="managing_logs"> Managing log events</a> </h2>
+
+<p> Ptest integrates with Postfix <tt>msg(3)</tt> logging. </p>
+
+<ul>
+
+<li> <p> Ptest changes the control flow of <tt>msg_fatal()</tt> and
+<tt>msg_panic()</tt>. When these functions are called during a test,
+Ptest flags a test as failed and terminates the test instead of the
+process. </p>
+
+<li> <p> Ptest silences the output from <tt>msg_info()</tt> and
+other <tt>msg(3)</tt> calls, and installs a log event listener to
+monitor Postfix logging. </p>
+
+</ul>
+
+<p> Ptest provides the following API to manage log events: </p>
+
+<dl>
+
+<dt> <b> <a name="expect_ptest_log_event"> <tt>void
+expect_ptest_log_event(PTEST_CTX *t, const char *text)</tt> </a>
+</b> </dt>
+
+<dd> Called from inside a test, to expect exactly one <tt>msg(3)</tt>
+call with the specified text. To expect multiple events, call
+<tt><a href="PTEST_README.html#expect_ptest_log_event">expect_ptest_log_event</a>()</tt> multiple times. A test is flagged
+as failed when expected text is not logged, or when text is logged
+that is not expected with <tt><a href="PTEST_README.html#expect_ptest_log_event">expect_ptest_log_event</a>()</tt>. </dd>
+
+</dl>
+
+<h2> <a name="managing_flows"> Managing test execution </a> </h2>
+
+<p> Ptest has a number of primitives that control test execution.
+</p>
+
+<dl>
+
+<dt> <b> <a name="PTEST_RUN"> <tt>void PTEST_RUN(PTEST_CTX *t, const
+char *test_name, { code in braces })</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to run the <tt>{ code in braces
+}</tt> in it own subtest environment. In the test progress report,
+the subtest name is appended to the parent test name, formatted as
+<i>parent-name/child-name</i>. <br> <br> NOTE: because <tt><a href="PTEST_README.html#PTEST_RUN">PTEST_RUN</a>()
+</tt> is a macro, the <tt>{ code in braces }</tt> MUST NOT contain
+a <tt>return</tt> statement; use <tt><a href="PTEST_README.html#ptest_return">ptest_return</a>()</tt> instead.
+It is OK for <tt>{ code in braces }</tt> to call a function that
+uses <tt>return</tt>. <br> <br> </dd>
+
+<dt> <b> <a name="PTEST_TRY"> <tt>void PTEST_TRY(PTEST_CTX *t, const
+char *test_name, { code in braces })</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to run the <tt>{ code in braces
+}</tt> without entering a new subtest environment. The purpose is
+to continue running the current test after the <tt>{ code in braces
+}</tt> calls msg_fatal*() or msg_panic(). The <tt>{ code in braces
+}</tt> should set a variable to indicate that <a href="PTEST_README.html#PTEST_TRY">PTEST_TRY</a>() executed
+"normally". <br> <br> NOTE: because <a href="PTEST_README.html#PTEST_TRY">PTEST_TRY</a>() is a macro, the
+<tt>{ code in braces }</tt> MUST NOT contain a <tt>return</tt>
+statement; use <tt><a href="PTEST_README.html#ptest_return">ptest_return</a>()</tt> instead.  It is OK for <tt>{
+code in braces }</tt> to call a function that uses <tt>return</tt>.
+<br> <br> </dd>
+
+<dt> <b> <a name="ptest_skip"> <tt>NORETURN ptest_skip(PTEST_CTX
+*t)</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to flag a test as skipped, and to
+terminate the test without terminating the process. Use this to
+disable tests that are not applicable for a specific system type
+or build configuration. <br> <br> </dd>
+
+<dt> <b> <a name="ptest_return"> <tt>NORETURN ptest_return(PTEST_CTX
+*t)</tt> </a> </b> </dt>
+
+<dd> Used inside a <tt>{ code in braces }</tt> block to terminate
+a <a href="PTEST_README.html#PTEST_RUN">PTEST_RUN</a> subtest. <br> <br> </dd>
+
+<dt> <b> <a name="ptest_defer"> <tt>void ptest_defer(PTEST_CTX *t,
+void (*defer_fn)(void *), void  *defer_ctx)</tt> </a> </b> </dt>
+
+<dd> Called once from inside a test, to call <tt>defer_fn(defer_ctx)</tt>
+after the test completes. This is typically used to eliminate a
+resource leak in tests that terminate the test early. <br> <br>
+NOTE: The deferred function is designed to run outside a test, and
+therefore it must not call Ptest functions. </dd>
+
+</dl>
+
+<h2> <a name="misc_api">Miscellaneous test APIs </a> </h2>
+
+<dl>
+
+<dt> <b> <a name="ptest_ctx_current"> <tt>PTEST_CTX
+*ptest_ctx_current(void)</tt> </a> </b> </dt>
+
+<dd> Returns the PTEST_CTX pointer for the current test or subtest.
+This can be used to handle a test error in a mock function or helper
+function that has no PTEST_CTX argument. <br> <br> </dd>
+
+</dl>
+
+</body>
+
+</html>
+
index 00a7d0404a586689b87aac2aa9a872ed10fcd98d..1f7507619235cb8da24ccac5c2a23f35ceb56fb4 100644 (file)
@@ -223,6 +223,14 @@ Recipients </a>
 
 </ul>
 
+<p><strong> For maintainers and contributors </strong></p>
+
+<ul>
+
+<li> <a href="PTEST_README.html"> Writing Postfix unit tests </a>
+
+</ul>
+
 </td>
 
 </table>
index b7159c0d8d9ae679c5da117bcd72beae07a508fe..c797672c366a0245377972b78a8c4b2e4500fddb 100755 (executable)
@@ -1229,6 +1229,20 @@ while (<>) {
     s/(ftp:\/\/[^ ,"\(\)]*[^ ,"\(\):;!?.])/<a href="$1">$1<\/a>/;
     s/\bRFC\s*([1-9]\d*)/<a href="https:\/\/tools.ietf.org\/html\/rfc$1">$&<\/a>/g;
 
+    # Ptest hyperlinks
+
+    s;\bptest_error\b;<a href="PTEST_README.html#$&">$&</a>;g;
+    s;\bptest_fatal\b;<a href="PTEST_README.html#$&">$&</a>;g;
+    s;\bptest_info\b;<a href="PTEST_README.html#$&">$&</a>;g;
+    s;\bexpect_ptest_error\b;<a href="PTEST_README.html#$&">$&</a>;g;
+    s;\bexpect_ptest_log_event\b;<a href="PTEST_README.html#$&">$&</a>;g;
+    s;\bPTEST_RUN\b;<a href="PTEST_README.html#$&">$&</a>;g;
+    s;\bPTEST_TRY\b;<a href="PTEST_README.html#$&">$&</a>;g;
+    s;\bptest_skip\b;<a href="PTEST_README.html#$&">$&</a>;g;
+    s;\bptest_return\b;<a href="PTEST_README.html#$&">$&</a>;g;
+    s;\bptest_defer\b;<a href="PTEST_README.html#$&">$&</a>;g;
+    s;\bpptest_ctx_current\b;<a href="PTEST_README.html#$&">$&</a>;g;
+
     # Split README/RFC/parameter/restriction hyperlinks that span line breaks
 
     s/(<a href="[^"]*">)([-A-Za-z0-9_]*)\b([-<\/bB>]*\n *[<bB>]*)\b([-A-Za-z0-9_]*)(<\/a>)/$1$2$5$3$1$4$5/;
index c1c8764508e64f01ee03ccfce85d0d3a4a94f686..d150d11e6675aa27680c9444787399878faaea6a 100644 (file)
@@ -40,6 +40,7 @@ HTML  = ../html/ADDRESS_CLASS_README.html \
        ../html/PGSQL_README.html \
        ../html/POSTSCREEN_3_5_README.html \
        ../html/POSTSCREEN_README.html \
+       ../html/PTEST_README.html \
        ../html/QSHAPE_README.html \
        ../html/RESTRICTION_CLASS_README.html \
        ../html/REQUIRETLS_README.html \
@@ -94,6 +95,7 @@ README        = ../README_FILES/ADDRESS_CLASS_README \
        ../README_FILES/PGSQL_README \
        ../README_FILES/POSTSCREEN_3_5_README \
        ../README_FILES/POSTSCREEN_README \
+       ../README_FILES/PTEST_README \
        ../README_FILES/QSHAPE_README \
        ../README_FILES/RESTRICTION_CLASS_README \
        ../README_FILES/REQUIRETLS_README \
@@ -287,6 +289,9 @@ clobber:
 ../html/POSTSCREEN_README.html: POSTSCREEN_README.html
        $(DETAB) $? | $(POSTLINK) >$@
 
+../html/PTEST_README.html: PTEST_README.html
+       $(DETAB) $? | $(POSTLINK) >$@
+
 ../html/QMQP_README.html: QMQP_README.html
        $(DETAB) $? | $(POSTLINK) >$@
 
@@ -482,6 +487,9 @@ clobber:
 ../README_FILES/POSTSCREEN_README: POSTSCREEN_README.html
        $(DETAB) $? | $(HT2READ) >$@
 
+../README_FILES/PTEST_README: PTEST_README.html
+       $(DETAB) $? | $(HT2READ) >$@
+
 ../README_FILES/QMQP_README: QMQP_README.html
        $(DETAB) $? | $(HT2READ) >$@
 
diff --git a/postfix/proto/PTEST_README.html b/postfix/proto/PTEST_README.html
new file mode 100644 (file)
index 0000000..4d5259f
--- /dev/null
@@ -0,0 +1,642 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Writing Postfix unit tests </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<link rel='stylesheet' type='text/css' href='postfix-doc.css'>
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Writing
+Postfix unit tests </h1>
+
+<hr>
+
+<h2> Overview </h2>
+
+<p> This document covers Ptest, a simple unit test framework that
+was introduced with Postfix version 3.8. It is modeled after Go
+tests, with primitives such as ptest_error() and ptest_fatal() that
+report test failures, and PTEST_RUN() that supports subtests. </p>
+
+<p> Ptest is light-weight compared to more powerful frameworks
+such as Gtest, but it avoids the need for adding a large Postfix
+dependency (a dependency that would not affect Postfix distributors,
+but developers only). </p>
+
+<ul>
+
+<li> <p> <a href="#simple_example"> Simple example </a> </p>
+
+<li> <p> <a href="#test_case_data"> Testing one function with
+TEST_CASE data </a> </p>
+
+<li> <p> <a href="#sub_tests"> Testing functions with subtests </a> </p>
+
+<li> <p> <a href="#style"> Suggestions for writing tests </a> </p>
+
+<li> <p> <a href="#api_reference"> Ptest API reference  </a> </p>
+
+</ul>
+
+<h2> <a name="simple_example"> Simple example </a> </h2>
+
+<p> Simple tests exercise one function under test, one scenario at
+a time.  Each scenario calls the function under test with good or
+bad inputs, and verifies that the function behaves as expected. The
+code in Postfix <tt>mymalloc_test.c</tt> file is a good example. </p>
+
+<p> After some <tt>#include</tt> statements, the file goes like
+this: </p>
+
+<blockquote>
+<pre>
+ 27 typedef struct PTEST_CASE {
+ 28     const char *testname;         /* Human-readable description */
+ 29     void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+ 30 } PTEST_CASE;
+ 31 
+ 32 /* Test functions. */
+ 33 
+ 34 static void test_mymalloc_normal(PTEST_CTX *t, const PTEST_CASE *tp)
+ 35 {
+ 36     void   *ptr;
+ 37 
+ 38     ptr = mymalloc(100);
+ 39     myfree(ptr);
+ 40 }
+ 41 
+ 42 static void test_mymalloc_panic_too_small(PTEST_CTX *t, const PTEST_CASE *tp)
+ 43 {
+ 44     expect_ptest_log_event(t, "panic: mymalloc: requested length 0");
+ 45     (void) mymalloc(0);
+ 46     ptest_fatal(t, "mymalloc(0) returned");
+ 47 }
+...     // Test functions for myrealloc(), mystrdup(), mymemdup().
+260
+261 static const PTEST_CASE ptestcases[] = {
+262     {"mymalloc + myfree normal case", test_mymalloc_normal,
+263     },
+264     {"mymalloc panic for too small request", test_mymalloc_panic_too_small,
+265     },
+...     // Test cases for myrealloc(), mystrdup(), mymemdup().
+306 };
+307 
+308 #include &lt;ptest_main.h&gt;
+</pre>
+</blockquote>
+
+<p> To run the test: </p>
+
+<blockquote>
+<pre>
+$ make test_mymalloc
+... compiler output...
+LD_LIBRARY_PATH=/path/to/postfix-source/lib ./mymalloc_test
+RUN  mymalloc + myfree normal case
+PASS mymalloc + myfree normal case
+RUN  mymalloc panic for too small request
+PASS mymalloc panic for too small request
+... results for myrealloc(), mystrdup(), mymemdup()...
+mymalloc_test: PASS: 22, SKIP: 0, FAIL: 0
+</pre>
+</blockquote>
+
+<p> This simple example already shows several key features of the ptest
+framework. </p>
+
+<ul>
+
+<li> <p> Each test is implemented as a separate function
+(<tt>test_mymalloc_normal()</tt>, <tt>test_mymalloc_panic_too_small()</tt>,
+and so on). These functions take two arguments: the first argument
+points to test infrastructure, and the second argument is not used
+here but will feature in a later example. </p>
+
+<li> <p> The first test verifies 'normal' behavior: it verifies that
+<tt>mymalloc()</tt> will allocate a small amount of memory, and that
+<tt>myfree()</tt> will accept the result from <tt>mymalloc()</tt>.
+When the test is run under a memory checker such as Valgrind, the
+memory checker will report no memory leak or other error. </p>
+
+<li> <p> The second test is more interesting. </p>
+
+<ul>
+
+<li> <p> The test verifies that <tt>mymalloc()</tt> will call
+<tt>msg_panic()</tt> when the requested amount of memory is too
+small. But in this test the <tt>msg_panic()</tt> call will not
+terminate the process like it normally would. The Ptest framework
+changes the control flow of <tt>msg_panic()</tt> and <tt>msg_fatal()</tt>
+such that these functions will terminate their test, instead of
+their process. </p>
+
+<li> <p> The <tt>expect_ptest_log_event()</tt> call sets up an
+expectation that <tt>msg_panic()</tt> will produce a specific error
+message; the test would fail if the expectation remains unsatisfied.
+</p>
+
+<li> <p> The <tt>ptest_fatal()</tt> call at the end of the second
+test is not needed; this call can only be reached if <tt>mymalloc()</tt>
+does not call <tt>msg_panic()</tt>. But then the expected panic
+message would not be logged, and the test would fail anyway. </p>
+
+</ul>
+
+<li> <p> The <tt>ptestcases[]</tt> table near the end of the example
+contains for each test the name and a pointer to function. As we
+show in a later example, the <tt>ptestcases[]</tt> table can also
+contain test inputs and expected test outputs. </p>
+
+<li> <p> The "<tt>#include &lt;ptest_main.h&gt;</tt>" at the end pulls
+in the code that iterates over the <tt>ptestcases[]</tt> table and
+logs progress.
+
+<li> <p> The test run output shows that the <tt>msg_panic()</tt>
+output in the second test is silenced; only output from unexpected
+<tt>msg_panic()</tt> or other unexpected <tt>msg(3)</tt> calls would
+show up in test run output. </p>
+
+</ul>
+
+<h2> <a name="test_case_data"> Testing one function with
+TEST_CASE data </a> </h2>
+
+<p> Often, we want to test a module that contains only one function. In
+that case we can store all the test inputs and expected results in the
+PTEST_CASE structure. </p>
+
+<p> The examples below are taken from the <tt>dict_union_test.c</tt>
+file which test the <tt>unionmap</tt> implementation in the file.
+<tt>dict_union.c</tt>. </p>
+
+<p> Background: a unionmap creates a union of tables. For example,
+the lookup table "<tt>unionmap:{inline:{foo=one},inline:{foo=two}}</tt>"
+will return ("<tt>one, two</tt>", DICT_STAT_SUCCESS) when queried
+with <tt>foo</tt>, and will return (null, DICT_STAT_SUCCESS)
+otherwise. </p>
+
+<p> First, we present the TEST_CASE structure with additional fields
+for inputs and expected results. </p>
+
+<blockquote>
+<pre>
+ 29 #define MAX_PROBE       5
+ 30 
+ 31 struct probe {
+ 32     const char *query;
+ 33     const char *want_value;
+ 34     int     want_error;
+ 35 };
+ 36 
+ 37 typedef struct PTEST_CASE {
+ 38     const char *testname;
+ 39     void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+ 40     const char *type_name;
+ 41     const struct probe probes[MAX_PROBE];
+ 42 } PTEST_CASE;
+</pre>
+</blockquote>
+
+<p> In the PTEST_CASE structure above: </p>
+
+<ul>
+
+<li> <p> The <tt>testname</tt> and <tt>action</tt> fields are
+required. We have seen these already in the simple example above.
+The other PTEST_CASE fields are specific to the unionmap tests.
+<p>
+
+<li> <p> The <tt>type_name</tt> field will contain the name of the table,
+for example <tt>unionmap:{static:one,inline:{foo=two}}</tt>. </p>
+
+<li> <p> The <tt>probes</tt> field contains a list of (query, expected
+result value, expected error code) that will be used to query the unionmap
+and to verify the result value and error code.
+</p>
+
+</ul>
+
+<p> Next we show the test data. Every test calls the same
+<tt>test_dict_union()</tt> function with a different <tt>unionmap</tt>
+configuration and with a list of queries with expected results. The
+implementation of that function follows after the test data. </p>
+
+<blockquote>
+<pre>
+115 static const PTEST_CASE ptestcases[] = {
+116     {
+...
+120         .testname = "propagates notfound and found",
+121         .action = test_dict_union,
+122         .type_name = "unionmap:{static:one,inline:{foo=two}}",
+123         .probes = {
+124             {"foo", "one,two", DICT_STAT_SUCCESS},
+125             {"bar", "one", DICT_STAT_SUCCESS},
+126         },
+127     }, {
+128         .testname = "error propagation: static map + fail map",
+129         .action = test_dict_union,
+130         .type_name = "unionmap:{static:one,fail:fail}",
+131         .probes = {
+132             {"foo", 0, DICT_ERR_RETRY},
+133         },
+...
+151     },
+152 };
+153 
+154 #include &lt;ptest_main.h&gt;
+</pre>
+</blockquote>
+
+<p> Finally, here is the <tt>test_dict_union()</tt> function that
+queries the <tt>unionmap</tt> implementation with test inputs, and
+verifies that the results are as expected. </p>
+
+<blockquote>
+<pre>
+ 84 static void test_dict_union(PTEST_CTX *t, const struct PTEST_CASE *tp)
+ 85 {
+ 86     DICT   *dict;
+ 87     const struct probe *pp;
+ 88     const char *got_value;
+ 89     int     got_error;
+ 90 
+ 91     dict = dict_open(tp-&gt;type_name, O_RDONLY, 0);
+ 92 
+ 93     for (pp = tp-&gt;probes; pp &lt; tp-&gt;probes + MAX_PROBE && pp-&gt;query != 0; pp++) {
+ 94         got_value = dict_get(dict, pp-&gt;query);
+ 95         got_error = dict-&gt;error;
+ 96         if (got_value == 0 && pp-&gt;want_value == 0)
+ 97             continue;
+ 98         if (got_value == 0 || pp-&gt;want_value == 0) {
+ 99             ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want '%s'",
+100                         pp-&gt;query, STR_OR_NULL(got_value),
+101                         STR_OR_NULL(pp-&gt;want_value));
+102             break;
+103         }
+104         if (strcmp(got_value, pp-&gt;want_value) != 0) {
+105             ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want '%s'",
+106                         pp-&gt;query, got_value, pp-&gt;want_value);
+107         }
+108         if (got_error != pp-&gt;want_error)
+109             ptest_error(t, "dict_get(dict,\"%s\") error: got %d, want %d",
+110                         pp-&gt;query, got_error, pp-&gt;want_error);
+111     }
+112     dict_close(dict);
+113 }
+</pre> 
+</blockquote>
+
+<p> A test run looks like this: </p>
+
+<blockquote>
+<pre>
+$ make test_dict_union
+...compiler output...
+LD_LIBRARY_PATH=/path/to/postfix-source/lib ./dict_union_test
+...
+RUN  propagates notfound and found
+PASS propagates notfound and found
+RUN  error propagation: static map + fail map
+PASS error propagation: static map + fail map
+...
+dict_union_test: PASS: 5, SKIP: 0, FAIL: 0
+</pre>
+</blockquote>
+
+<h2> <a name="sub_tests"> Testing functions with subtests </a> </h2>
+
+<p> Sometimes it is not convenient to store test data in a PTEST_CASE
+structure. This can happen when converting an existing test into
+Ptest, or when the module under test contains functions that need
+different kinds of test data. The solution is to create a
+<tt>_test.c</tt> file with the structure as shown below. This can
+be found in the file <tt>map_search_test.c</tt>, which was converted
+from an existing test into Ptest. </p>
+
+<ul>
+
+<li> <p> One PTEST_CASE structure definition without test data. </p>
+
+<pre>
+ 50 typedef struct PTEST_CASE {
+ 51     const char *testname;
+ 52     void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+ 53 } PTEST_CASE;
+</pre>
+
+<li> <p> One test function for each module function that needs to
+be tested, and one table with test cases for that module function.
+In this case there is only one module function (<tt>map_search()</tt>)
+that needs to be tested, so there is only one test function
+(<tt>test_map_search()</tt>). </p>
+
+<pre>
+ 67 #define MAX_WANT_LOG    5
+ 68 
+ 69 static void test_map_search(PTEST_CTX *t, const struct PTEST_CASE *unused)
+ 70 {
+ 71     /* Test cases with inputs and expected outputs. */
+ 72     struct test {
+ 73         const char *map_spec;
+ 74         int     want_return;            /* 0=fail, 1=success */
+ 75         const char *want_log[MAX_WANT_LOG];
+ 76         const char *want_map_type_name; /* 0 or match */
+ 77         const char *want_search_order;   /* 0 or match */
+ 78     };
+ 79     static struct test test_cases[] = {
+ 80         {                               /* 0 */
+ 81             .map_spec = "type",
+ 82             .want_return = 0,
+ 83             .want_log = {
+ 84                 "malformed map specification: 'type'",
+ 85                 "expected maptype:mapname instead of 'type'",
+ 86             },
+ 87         },
+ 88         {                               /* 1 */
+ 89             .map_spec = "type:name",
+ 90             .want_return = 1,
+ 91             .want_map_type_name = "type:name",
+ 92         },
+.. .        // ...other test cases...
+166     };
+</pre>
+
+<li> <p> In a test function, iterate over its table with test cases,
+using <tt>PTEST_RUN()</tt> to run each test case in its own subtest.
+</p>
+
+<pre>
+184     for (tp = test_cases; tp-&gt;map_spec; tp++) {
+185         vstring_sprintf(test_label, "test %d", (int) (tp - test_cases));
+186         PTEST_RUN(t, STR(test_label), {
+187             for (cpp = tp-&gt;want_log; cpp &lt; tp-&gt;want_log + MAX_WANT_LOG && *cpp; cpp++)
+188                 expect_ptest_log_event(t, *cpp);
+189             map_search_from_create = map_search_create(tp-&gt;map_spec);
+...            // ...verify that the result is as expected...
+...            // ...use ptest_return() or ptest_fatal() to exit from a test...
+228         });
+229     }
+...
+</pre>
+
+<li> <p> Create a <tt>ptestcases[]</tt> table to call each test
+function once, and include the Ptest main program. </p>
+
+<pre>
+183 static const PTEST_CASE ptestcases[] = {
+184     "test_map_search", test_map_search,
+185 };
+186 
+187 #include &lt;ptest_main.h&gt;
+</pre>
+
+</ul>
+
+<p> See the file <tt>map_search_test.c</tt> for a complete example.
+</p>
+
+<p> This is what a test run looks like: </p>
+
+<blockquote>
+<pre>
+$ make test_map_search
+...compiler output...
+LD_LIBRARY_PATH=/path/to/postfix-source/lib  ./map_search_test
+RUN  test_map_search
+RUN  test_map_search/test 0
+PASS test_map_search/test 0
+RUN  test_map_search/test 1
+PASS test_map_search/test 1
+....
+PASS test_map_search
+map_search_test: PASS: 13, SKIP: 0, FAIL: 0
+</pre>
+</blockquote>
+
+<p> This shows that the subtest name is appended to the parent test
+name, formatted as <i>parent-name/child-name</i>. </p>
+
+<h2> <a name="style"> Suggestions for writing tests </a> </h2>
+
+<p> Ptest is loosely inspired on Go test, especially its top-level
+test functions and its methods <tt>T.run()</tt>, <tt>T.error()</tt>
+and <tt>T.fatal()</tt>. </p>
+
+<p> Suggestions for test style may look familiar to Go programmers:
+</p>
+
+<ul>
+
+<li> <p> Use identifiers named <tt>got_xxx</tt> and <tt>want_xxx</tt>.
+When a test result is unexpected, log the discrepancy as "got
+&lt;what you got&gt;, want &lt;what you want&gt;".  </p>
+
+<li> <p> Report discrepancies with <tt>ptest_error()</tt> if possible;
+use <tt>ptest_fatal()</tt> only when continuing the test would
+produce nonsensical results. </p>
+
+<li> <p> Where it makes sense use a table with testcases and use
+<tt>PTEST_RUN()</tt> to run each testcase in its own subtest. </p>
+
+</ul>
+
+<p> Other suggestions: </p>
+
+<ul>
+
+<li> <p> Consider running tests under a memory checker such as
+Valgrind. Use <tt>ptest_defer()</tt> to avoid memory leaks when a
+test should terminate early. </p>
+
+<li> <p> Always test non-error and error cases, to cover all code
+paths in the function under test. </p>
+
+</ul>
+
+<h2> <a name="api_reference"> Ptest API reference </a> </h2>
+
+<ul>
+
+<li> <p> <a href="#managing_errors">Managing test errors</a>
+
+<li> <p> <a href="#managing_logs">Managing log events</a>
+
+<li> <p> <a href="#managing_flows">Managing test execution </a>
+
+<li> <p> <a href="#misc_api">Miscellaneous test APIs </a>
+
+</ul>
+
+<h2> <a name="managing_errors"> Managing test errors</a> </h2>
+
+<p> As one might expect, Ptest has support to flag unexpected test
+results as errors. </p>
+
+<dl>
+
+<dt> <b> <a name="ptest_error"> <tt>void ptest_error(PTEST_CTX *t,
+const char *format, ...)</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to report an unexpected test result,
+and to flag the test as failed without terminating the test. This
+call can be ignored with <tt>expect_ptest_error()</tt>. <br> <br> </dd>
+
+<dt> <b> <a name="ptest_fatal"> <tt>void ptest_fatal(PTEST_CTX *t,
+const char *format, ...)</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to report an unexpected test result,
+to flag the test as failed, and to terminate the test. This call
+cannot be ignored with <tt>expect_ptest_error()</tt>. </dd>
+
+</dl>
+
+<p> For convenience, Ptest can also report non-error information.
+</p>
+
+<dl>
+
+<dt> <b> <a name="ptest_info"> <tt>void ptest_info(PTEST_CTX *t,
+const char *format, ...)</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to report a non-error condition
+without terminating the test. This call cannot be ignored with
+<tt>expect_ptest_error()</tt>. </dd>
+
+</dl>
+
+<p> Finally, Ptest has support to test <tt>ptest_error()</tt> itself,
+to verify that an intentional error is reported as expected. </p>
+
+<dl>
+
+<dt> <b> <a name="expect_ptest_error"> <tt>void
+expect_ptest_error(PTEST_CTX *t, const char *text)</tt> </a> </b>
+</dt>
+
+<dd> Called from inside a test, to expect exactly one <tt>ptest_error()</tt>
+call with the specified text, and to ignore that <tt>ptest_error()</tt>
+call (i.e. don't flag the test as failed). To ignore multiple calls,
+call <tt>expect_ptest_error()</tt> multiple times. A test is flagged
+as failed when an expected error is not reported (and of course
+when an error is reported that is not expected with
+<tt>expect_ptest_error()</tt>). </dd>
+
+</dl>
+
+<h2> <a name="managing_logs"> Managing log events</a> </h2>
+
+<p> Ptest integrates with Postfix <tt>msg(3)</tt> logging. </p>
+
+<ul>
+
+<li> <p> Ptest changes the control flow of <tt>msg_fatal()</tt> and
+<tt>msg_panic()</tt>. When these functions are called during a test,
+Ptest flags a test as failed and terminates the test instead of the
+process. </p>
+
+<li> <p> Ptest silences the output from <tt>msg_info()</tt> and
+other <tt>msg(3)</tt> calls, and installs a log event listener to
+monitor Postfix logging. </p>
+
+</ul>
+
+<p> Ptest provides the following API to manage log events: </p>
+
+<dl>
+
+<dt> <b> <a name="expect_ptest_log_event"> <tt>void
+expect_ptest_log_event(PTEST_CTX *t, const char *text)</tt> </a>
+</b> </dt>
+
+<dd> Called from inside a test, to expect exactly one <tt>msg(3)</tt>
+call with the specified text. To expect multiple events, call
+<tt>expect_ptest_log_event()</tt> multiple times. A test is flagged
+as failed when expected text is not logged, or when text is logged
+that is not expected with <tt>expect_ptest_log_event()</tt>. </dd>
+
+</dl>
+
+<h2> <a name="managing_flows"> Managing test execution </a> </h2>
+
+<p> Ptest has a number of primitives that control test execution.
+</p>
+
+<dl>
+
+<dt> <b> <a name="PTEST_RUN"> <tt>void PTEST_RUN(PTEST_CTX *t, const
+char *test_name, { code in braces })</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to run the <tt>{ code in braces
+}</tt> in it own subtest environment. In the test progress report,
+the subtest name is appended to the parent test name, formatted as
+<i>parent-name/child-name</i>. <br> <br> NOTE: because <tt>PTEST_RUN()
+</tt> is a macro, the <tt>{ code in braces }</tt> MUST NOT contain
+a <tt>return</tt> statement; use <tt>ptest_return()</tt> instead.
+It is OK for <tt>{ code in braces }</tt> to call a function that
+uses <tt>return</tt>. <br> <br> </dd>
+
+<dt> <b> <a name="PTEST_TRY"> <tt>void PTEST_TRY(PTEST_CTX *t, const
+char *test_name, { code in braces })</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to run the <tt>{ code in braces
+}</tt> without entering a new subtest environment. The purpose is
+to continue running the current test after the <tt>{ code in braces
+}</tt> calls msg_fatal*() or msg_panic(). The <tt>{ code in braces
+}</tt> should set a variable to indicate that PTEST_TRY() executed
+"normally". <br> <br> NOTE: because PTEST_TRY() is a macro, the
+<tt>{ code in braces }</tt> MUST NOT contain a <tt>return</tt>
+statement; use <tt>ptest_return()</tt> instead.  It is OK for <tt>{
+code in braces }</tt> to call a function that uses <tt>return</tt>.
+<br> <br> </dd>
+
+<dt> <b> <a name="ptest_skip"> <tt>NORETURN ptest_skip(PTEST_CTX
+*t)</tt> </a> </b> </dt>
+
+<dd> Called from inside a test, to flag a test as skipped, and to
+terminate the test without terminating the process. Use this to
+disable tests that are not applicable for a specific system type
+or build configuration. <br> <br> </dd>
+
+<dt> <b> <a name="ptest_return"> <tt>NORETURN ptest_return(PTEST_CTX
+*t)</tt> </a> </b> </dt>
+
+<dd> Used inside a <tt>{ code in braces }</tt> block to terminate
+a PTEST_RUN subtest. <br> <br> </dd>
+
+<dt> <b> <a name="ptest_defer"> <tt>void ptest_defer(PTEST_CTX *t,
+void (*defer_fn)(void *), void  *defer_ctx)</tt> </a> </b> </dt>
+
+<dd> Called once from inside a test, to call <tt>defer_fn(defer_ctx)</tt>
+after the test completes. This is typically used to eliminate a
+resource leak in tests that terminate the test early. <br> <br>
+NOTE: The deferred function is designed to run outside a test, and
+therefore it must not call Ptest functions. </dd>
+
+</dl>
+
+<h2> <a name="misc_api">Miscellaneous test APIs </a> </h2>
+
+<dl>
+
+<dt> <b> <a name="ptest_ctx_current"> <tt>PTEST_CTX
+*ptest_ctx_current(void)</tt> </a> </b> </dt>
+
+<dd> Returns the PTEST_CTX pointer for the current test or subtest.
+This can be used to handle a test error in a mock function or helper
+function that has no PTEST_CTX argument. <br> <br> </dd>
+
+</dl>
+
+</body>
+
+</html>
+
index 40fe40e68e2d2af9938d106c8e4dcf8694a49ab4..18329d08d323542065cea977ad0feea09ad0a701 100644 (file)
@@ -356,3 +356,11 @@ dict_open  dict_open looks up our DICT_OPEN_INFO with dict_fn and mkmap_fn
 mkmap_open  mkmap_open looks up our DICT_OPEN_INFO with dict_fn and mkmap_fn
  typedef struct MSG_CAPTURE MSG_CAPTURE 
 MSG_CAPTURE  MSG_CAPTURE msg_capt_create ssize_t init_size 
+copy_addrinfo  copy_addrinfo expectation helper 
+int  int eq_enum 
+int  int eq_str 
+void  void dns_set_h_errno int herrval 
+application  application specific 
+ Null name or name name 
+void  void defer_ctx 
+ To undo a ptest_defer call call the function with a
index beee8a9b47606f00fe248c24b45a4f2e47c39df1..25c2fad2ef11df505107b4c9bb37be0788ffbfc1 100644 (file)
@@ -384,3 +384,9 @@ smtp_requiretls_policy smtp_requiretls_policy inline
 in tt tt p 
 mynetworks mynetworks lmdb etc postfix network_table
  44 enable redirect redirect hash to lmdb or cdb 
+ 90 type_name unionmap static one fail fail 
+132 for cpp tp want_log cpp tp want_log MAX_WANT_LOG cpp cpp 
+184 test_map_search test_map_search 
+ void ptest_defer PTEST_CTX t void defer_fn void void defer_ctx 
+130 type_name unionmap static one fail fail 
+187 for cpp tp want_log cpp tp want_log MAX_WANT_LOG cpp cpp 
index 3a58f60aaee0b24b5591406f48f5fdb94c496c7a..b800a34e75fd76d57cbcbe75e0c220b8958437ae 100644 (file)
@@ -1916,7 +1916,59 @@ surr
 whoami
 IfChange
 ThenChange
+Aargh
+abc
+addrinfos
+clearjmp
+deserialize
+Deserialize
+deserialized
+endservent
+errval
+getservbyname
+getservbyport
+getservent
+gotlen
+herrno
+herrval
+hostlen
+ipprotocol
+JMP
+Matcher
+matchers
+MATCHERS
+MOCKABLE
+NORAMDOMIZE
+notexist
+onlinepubs
+opengroup
+pmock
+ptest
+ptestcase
+ptestcases
+resetjmp
+servent
+servlen
+setservent
+SNDBUF
+sockadddr
+sockaddrs
+stayopen
+subtest
+subtests
+Subtests
+unallocated
+wantlen
+wrapup
+xrat
+xsh
+yesno
+Ptest
+Pmock
+PMOCK
+TESTLIB
+TESTLIBS
+undoable
 EKU
 clientAuth
 serverAuth
-WebPKI
index 924d594ea79325fa0e69bd8faa6c6d4a60405f43..b9ef6afd39dbc0f6e569af3d87aaa534ed7e7d79 100644 (file)
@@ -135,3 +135,6 @@ Teodor
 IfThisThenThat
 linter
 MAXINT
+gmock
+sunos
+WebPKI
index 66414fd6372b7519a365375527997cf2e1851694..caaa74a7fd05bdc3c70ab22c72c3d7be20a42723 100644 (file)
@@ -415,17 +415,36 @@ hPP
 hPPx
 hx
 lcdb
-#!/bin/sh
-
-# Spellchecks the proto HTML files.
-
-LANG=C; export LANG
-
-mantools/dehtml proto/*html proto/*.proto | tr '+' ' ' | spell | grep -F -vxf proto/stop | grep -F -vxf proto/stop.spell-proto-html
+APIs
+const
+cpp
+fn
+Gtest
+myfree
+mymalloc
+mymemdup
+myrealloc
+mystrdup
+NORETURN
+ptest
+Ptest
+PTEST
+ptestcases
+RDONLY
+STR
+struct
+subtest
+subtests
+testcase
+testcases
+testname
+tp
+typedef
+Valgrind
+vstring
 EKU
 WebPKI
 ccerts
 clientAuth
-dehtml
+notfound
 serverAuth
-vxf
index 957ccbed40ff806af1d08c66fb96a51ebeec5382..b6d1b3a5edee62cc6cc48537cb87f2dc4454fbcf 100644 (file)
@@ -355,7 +355,7 @@ bounce_notify_util_tester.o: ../../include/rec_type.h
 bounce_notify_util_tester.o: ../../include/recipient_list.h
 bounce_notify_util_tester.o: ../../include/record.h
 bounce_notify_util_tester.o: ../../include/sys_defs.h
-bounce_notify_util_tester.o: ../../include/test_main.h
+bounce_notify_util_tester.o: ../../include/test_server_main.h
 bounce_notify_util_tester.o: ../../include/vbuf.h
 bounce_notify_util_tester.o: ../../include/vstream.h
 bounce_notify_util_tester.o: ../../include/vstring.h
index 3151ced48fb3b94d140e75645b807a0f66cd77cd..2d6c12303e47edad96369870ce7667183760d958 100644 (file)
@@ -30,7 +30,7 @@
  /*
   * Testing library.
   */
-#include <test_main.h>
+#include <test_server_main.h>
 
 #define TEST_ENCODING  "7bit"
 #define NO_SMTPUTF8    (0)
@@ -156,7 +156,7 @@ int     main(int argc, char **argv)
        0,
     };
 
-    test_main(argc, argv, test_driver,
+    test_server_main(argc, argv, test_driver,
              CA_TEST_MAIN_INT_TABLE(int_table),
              CA_TEST_MAIN_STR_TABLE(str_table),
              CA_TEST_MAIN_TIME_TABLE(time_table),
index ddcd1de349ccc33790a879441c73b443ac7b984e..59b515baa25c94bc156dd1f823688b2355e5f3c4 100644 (file)
@@ -1,10 +1,10 @@
 SHELL  = /bin/sh
 SRCS   = dns_lookup.c dns_rr.c dns_strerror.c dns_strtype.c dns_rr_to_pa.c \
        dns_sa_to_rr.c dns_rr_eq_sa.c dns_rr_to_sa.c dns_strrecord.c \
-       dns_rr_filter.c dns_str_resflags.c dns_sec.c
+       dns_rr_filter.c dns_str_resflags.c dns_sec.c dns_lookup_types.c
 OBJS   = dns_lookup.o dns_rr.o dns_strerror.o dns_strtype.o dns_rr_to_pa.o \
        dns_sa_to_rr.o dns_rr_eq_sa.o dns_rr_to_sa.o dns_strrecord.o \
-       dns_rr_filter.o dns_str_resflags.o dns_sec.o
+       dns_rr_filter.o dns_str_resflags.o dns_sec.o dns_lookup_types.o
 HDRS   = dns.h
 TESTSRC        = test_dns_lookup.c test_alias_token.c
 DEFS   = -I. -I$(INC_DIR) -D$(SYSTYPE)
@@ -12,9 +12,10 @@ CFLAGS       = $(DEBUG) $(OPT) $(DEFS)
 INCL   =
 LIB    = lib$(LIB_PREFIX)dns$(LIB_SUFFIX)
 TESTPROG= test_dns_lookup dns_rr_to_pa dns_rr_to_sa dns_sa_to_rr dns_rr_eq_sa \
-       dns_rr_test
+       dns_rr_test dns_lookup_types_test
 LIBS   = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
        ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+TEST_LIB= ../../lib/libtesting.a ../../lib/libptest.a
 LIB_DIR        = ../../lib
 INC_DIR        = ../../include
 
@@ -22,17 +23,17 @@ INC_DIR     = ../../include
 
 all: $(LIB)
 
-$(OBJS): ../../conf/makedefs.out
+$(OBJS) $(TEST_OBJ): ../../conf/makedefs.out
 
 Makefile: Makefile.in
        cat ../../conf/makedefs.out $? >$@
 
 test:  $(TESTPROG)
 
-tests: test dns_rr_to_pa_test dns_rr_to_sa_test dns_sa_to_rr_test \
+tests: update test dns_rr_to_pa_test dns_rr_to_sa_test dns_sa_to_rr_test \
        dns_rr_eq_sa_test no-a-test no-aaaa-test no-mx-test \
        error-filter-test nullmx_test nxdomain_test mxonly_test \
-       dnsbl_tests dns_rr_tests
+       dnsbl_tests test_dns_rr test_dns_lookup_types
 
 dnsbl_tests: \
        dnsbl_ttl_127.0.0.2_bind_plain_test \
@@ -99,7 +100,7 @@ dns_rr_eq_sa: $(LIB) $(LIBS)
        mv $@.o junk
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
        mv junk $@.o
-
+       
 dns_rr_to_pa_test: dns_rr_to_pa dns_rr_to_pa.in dns_rr_to_pa.ref
        $(SHLIB_ENV) $(VALGRIND) ./dns_rr_to_pa `cat dns_rr_to_pa.in` >dns_rr_to_pa.tmp
        diff dns_rr_to_pa.ref dns_rr_to_pa.tmp
@@ -161,6 +162,14 @@ mxonly_test: test_dns_lookup mxonly_test.ref
        diff mxonly_test.ref mxonly_test.tmp
        rm -f mxonly_test.tmp
 
+dns_lookup_types_test: update dns_lookup_types_test.o \
+       $(LIB_DIR)/mock_dns_lookup.o $(TEST_LIB) $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o $(LIB_DIR)/mock_dns_lookup.o \
+       $(TEST_LIB) $(LIB) $(LIBS) $(SYSLIBS)
+
+test_dns_lookup_types: dns_lookup_types_test
+       $(SHLIB_ENV) $(VALGRIND) ./dns_lookup_types_test
+
 # Non-existent record, libbind API, RFC 2308 disabled.
 
 dnsbl_ttl_127.0.0.1_bind_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_plain.ref
@@ -241,11 +250,11 @@ dnsbl_ttl_127.0.0.2_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_p
        diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_ncache.tmp
        rm -f dnsbl_ttl_127.0.0.2_priv_ncache.tmp
 
-dns_rr_tests: dns_rr_test
+test_dns_rr: dns_rr_test
        $(SHLIB_ENV) $(VALGRIND) ./dns_rr_test
 
 dns_rr_test: dns_rr_test.o $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o $(LIB) $(LIBS) $(SYSLIBS)
 
 clean:
        rm -f *.o $(LIB) *core $(TESTPROG) junk
@@ -280,6 +289,40 @@ dns_lookup.o: ../../include/vstream.h
 dns_lookup.o: ../../include/vstring.h
 dns_lookup.o: dns.h
 dns_lookup.o: dns_lookup.c
+dns_lookup_types.o: ../../include/argv.h
+dns_lookup_types.o: ../../include/check_arg.h
+dns_lookup_types.o: ../../include/dict.h
+dns_lookup_types.o: ../../include/maps.h
+dns_lookup_types.o: ../../include/msg.h
+dns_lookup_types.o: ../../include/myaddrinfo.h
+dns_lookup_types.o: ../../include/myflock.h
+dns_lookup_types.o: ../../include/sock_addr.h
+dns_lookup_types.o: ../../include/sys_defs.h
+dns_lookup_types.o: ../../include/vbuf.h
+dns_lookup_types.o: ../../include/vstream.h
+dns_lookup_types.o: ../../include/vstring.h
+dns_lookup_types.o: dns.h
+dns_lookup_types.o: dns_lookup_types.c
+dns_lookup_types_test.o: ../../include/argv.h
+dns_lookup_types_test.o: ../../include/check_arg.h
+dns_lookup_types_test.o: ../../include/mock_dns.h
+dns_lookup_types_test.o: ../../include/msg.h
+dns_lookup_types_test.o: ../../include/msg_jmp.h
+dns_lookup_types_test.o: ../../include/msg_output.h
+dns_lookup_types_test.o: ../../include/msg_vstream.h
+dns_lookup_types_test.o: ../../include/myaddrinfo.h
+dns_lookup_types_test.o: ../../include/myrand.h
+dns_lookup_types_test.o: ../../include/pmock_expect.h
+dns_lookup_types_test.o: ../../include/ptest.h
+dns_lookup_types_test.o: ../../include/ptest_main.h
+dns_lookup_types_test.o: ../../include/sock_addr.h
+dns_lookup_types_test.o: ../../include/stringops.h
+dns_lookup_types_test.o: ../../include/sys_defs.h
+dns_lookup_types_test.o: ../../include/vbuf.h
+dns_lookup_types_test.o: ../../include/vstream.h
+dns_lookup_types_test.o: ../../include/vstring.h
+dns_lookup_types_test.o: dns.h
+dns_lookup_types_test.o: dns_lookup_types_test.c
 dns_rr.o: ../../include/check_arg.h
 dns_rr.o: ../../include/msg.h
 dns_rr.o: ../../include/myaddrinfo.h
index 6d8c6b25b58a53e74764bbae5e64d8a54b2b8956..087e05e9978d438a346e84847a4c2de35a62d83d 100644 (file)
@@ -265,8 +265,9 @@ extern int dns_lookup_x(const char *, unsigned, unsigned, DNS_RR **,
 extern int dns_lookup_rl(const char *, unsigned, DNS_RR **, VSTRING *,
                                 VSTRING *, int *, int,...);
 extern int dns_lookup_rv(const char *, unsigned, DNS_RR **, VSTRING *,
-                                VSTRING *, int *, int, unsigned *);
+                                VSTRING *, int *, int, const unsigned *);
 extern int dns_get_h_errno(void);
+extern void dns_set_h_errno(int);
 
 #define dns_lookup(name, type, rflags, list, fqdn, why) \
     dns_lookup_x((name), (type), (rflags), (list), (fqdn), (why), (int *) 0, \
index 03c42e763c4dd21551d8c82fad7441b439637ba9..11115c3dada00d33836d6b4be27f5ce88ec73382 100644 (file)
@@ -33,6 +33,9 @@
 /*     unsigned *ltype;
 /*
 /*     int     dns_get_h_errno()
+/*
+/*     void    dns_set_h_errno(
+/*     int     errval)
 /* AUXILIARY FUNCTIONS
 /*     extern int var_dns_ncache_ttl_fix;
 /*
@@ -91,9 +94,9 @@
 /*     checks in the rest of Postfix, because it is not a valid
 /*     host or domain name.
 /*
-/*     dns_get_h_errno() returns the last error. This deprecates
-/*     usage of the global h_errno variable. We should not rely
-/*     on that being updated.
+/*     dns_get_h_errno() returns the last error, and dns_set_h_errno()
+/*     sets it. This deprecates usage of the global h_errno variable.
+/*     We should not rely on that being updated.
 /*
 /*     dns_lookup_l() and dns_lookup_v() allow the user to specify
 /*     a list of resource types.
@@ -1182,142 +1185,16 @@ int     dns_lookup_x(const char *name, unsigned type, unsigned flags,
     return (DNS_NOTFOUND);
 }
 
-/* dns_lookup_rl - DNS lookup interface with types list */
-
-int     dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
-                             VSTRING *fqdn, VSTRING *why, int *rcode,
-                             int lflags,...)
-{
-    va_list ap;
-    unsigned type, next;
-    int     status = DNS_NOTFOUND;
-    int     hpref_status = INT_MIN;
-    VSTRING *hpref_rtext = 0;
-    int     hpref_rcode;
-    int     hpref_h_errno;
-    DNS_RR *rr;
-
-    /* Save intermediate highest-priority result. */
-#define SAVE_HPREF_STATUS() do { \
-       hpref_status = status; \
-       if (rcode) \
-           hpref_rcode = *rcode; \
-       if (why && status != DNS_OK) \
-           vstring_strcpy(hpref_rtext ? hpref_rtext : \
-                          (hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \
-                          vstring_str(why)); \
-       hpref_h_errno = DNS_GET_H_ERRNO(&dns_res_state); \
-    } while (0)
-
-    /* Restore intermediate highest-priority result. */
-#define RESTORE_HPREF_STATUS() do { \
-       status = hpref_status; \
-       if (rcode) \
-           *rcode = hpref_rcode; \
-       if (why && status != DNS_OK) \
-           vstring_strcpy(why, vstring_str(hpref_rtext)); \
-       DNS_SET_H_ERRNO(&dns_res_state, hpref_h_errno); \
-    } while (0)
-
-    if (rrlist)
-       *rrlist = 0;
-    va_start(ap, lflags);
-    for (type = va_arg(ap, unsigned); type != 0; type = next) {
-       next = va_arg(ap, unsigned);
-       if (msg_verbose)
-           msg_info("lookup %s type %s flags %s",
-                    name, dns_strtype(type), dns_str_resflags(flags));
-       status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
-                             fqdn, why, rcode, lflags);
-       if (rrlist && rr) {
-           *rrlist = dns_rr_append(*rrlist, rr);
-           if (DNS_RR_IS_TRUNCATED(*rrlist))
-               break;
-       }
-       if (status == DNS_OK) {
-           if (lflags & DNS_REQ_FLAG_STOP_OK)
-               break;
-       } else if (status == DNS_INVAL) {
-           if (lflags & DNS_REQ_FLAG_STOP_INVAL)
-               break;
-       } else if (status == DNS_POLICY) {
-           if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
-               break;
-       } else if (status == DNS_NULLMX) {
-           if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
-               break;
-       }
-       /* XXX Stop after NXDOMAIN error. */
-       if (next == 0)
-           break;
-       if (status >= hpref_status)
-           SAVE_HPREF_STATUS();                /* save last info */
-    }
-    va_end(ap);
-    if (status < hpref_status)
-       RESTORE_HPREF_STATUS();                 /* else report last info */
-    if (hpref_rtext)
-       vstring_free(hpref_rtext);
-    return (status);
-}
-
-/* dns_lookup_rv - DNS lookup interface with types vector */
+/* dns_get_h_errno - get the last lookup status */
 
-int     dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist,
-                             VSTRING *fqdn, VSTRING *why, int *rcode,
-                             int lflags, unsigned *types)
+int     dns_get_h_errno(void)
 {
-    unsigned type, next;
-    int     status = DNS_NOTFOUND;
-    int     hpref_status = INT_MIN;
-    VSTRING *hpref_rtext = 0;
-    int     hpref_rcode;
-    int     hpref_h_errno;
-    DNS_RR *rr;
-
-    if (rrlist)
-       *rrlist = 0;
-    for (type = *types++; type != 0; type = next) {
-       next = *types++;
-       if (msg_verbose)
-           msg_info("lookup %s type %s flags %s",
-                    name, dns_strtype(type), dns_str_resflags(flags));
-       status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
-                             fqdn, why, rcode, lflags);
-       if (rrlist && rr) {
-           *rrlist = dns_rr_append(*rrlist, rr);
-           if (DNS_RR_IS_TRUNCATED(*rrlist))
-               break;
-       }
-       if (status == DNS_OK) {
-           if (lflags & DNS_REQ_FLAG_STOP_OK)
-               break;
-       } else if (status == DNS_INVAL) {
-           if (lflags & DNS_REQ_FLAG_STOP_INVAL)
-               break;
-       } else if (status == DNS_POLICY) {
-           if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
-               break;
-       } else if (status == DNS_NULLMX) {
-           if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
-               break;
-       }
-       /* XXX Stop after NXDOMAIN error. */
-       if (next == 0)
-           break;
-       if (status >= hpref_status)
-           SAVE_HPREF_STATUS();                /* save last info */
-    }
-    if (status < hpref_status)
-       RESTORE_HPREF_STATUS();                 /* else report last info */
-    if (hpref_rtext)
-       vstring_free(hpref_rtext);
-    return (status);
+    return (DNS_GET_H_ERRNO(&dns_res_state));
 }
 
-/* dns_get_h_errno - get the last lookup status */
+/* dns_set_h_errno - set the last lookup status */
 
-int     dns_get_h_errno(void)
+void    dns_set_h_errno(int errval)
 {
-    return (DNS_GET_H_ERRNO(&dns_res_state));
+    DNS_SET_H_ERRNO(&dns_res_state, errval);
 }
diff --git a/postfix/src/dns/dns_lookup_types.c b/postfix/src/dns/dns_lookup_types.c
new file mode 100644 (file)
index 0000000..bd09eda
--- /dev/null
@@ -0,0 +1,186 @@
+/*++
+/* NAME
+/*     dns_lookup_types 3
+/* SUMMARY
+/*     domain name service lookup for multiple types
+/* SYNOPSIS
+/*     #include <dns.h>
+/*
+/*     int     dns_lookup_l(name, rflags, list, fqdn, why, lflags, ...)
+/*     const char *name;
+/*     unsigned rflags;
+/*     DNS_RR  **list;
+/*     VSTRING *fqdn;
+/*     VSTRING *why;
+/*     int     lflags;
+/*
+/*     int     dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype)
+/*     const char *name;
+/*     unsigned rflags;
+/*     DNS_RR  **list;
+/*     VSTRING *fqdn;
+/*     VSTRING *why;
+/*     int     lflags;
+/*     unsigned *ltype;
+/* AUXILIARY FUNCTIONS
+/*     int     dns_lookup_rl(name, rflags, list, fqdn, why, rcode, lflags, ...)
+/*     const char *name;
+/*     unsigned rflags;
+/*     DNS_RR  **list;
+/*     VSTRING *fqdn;
+/*     VSTRING *why;
+/*     int     *rcode;
+/*     int     lflags;
+/*
+/*     int     dns_lookup_rv(name, rflags, list, fqdn, why, rcode, lflags,
+/*                             ltype)
+/*     const char *name;
+/*     unsigned rflags;
+/*     DNS_RR  **list;
+/*     VSTRING *fqdn;
+/*     VSTRING *why;
+/*     int     *rcode;
+/*     int     lflags;
+/*     unsigned *ltype;
+/* DESCRIPTION
+/*     These functions iterate over a sequence of unsigned resource
+/*     types, call dns_lookup_x() for each type, and carefully
+/*     aggregate the resulting error and non-error results.
+/*
+/*     dns_lookup_rl() and dns_lookup_l() iterate over a variadic
+/*     list of query types, while dns_lookup_rv() and dns_lookup_v()
+/*     iterate over a vector of query types.
+/* DIAGNOSTICS
+/* SEE ALSO
+/*     dns_lookup(3), domain name service lookup
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     IBM T.J. Watson Research
+/*     P.O. Box 704
+/*     Yorktown Heights, NY 10598, USA
+/*
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <msg.h>
+
+/* DNS library. */
+
+#define LIBDNS_INTERNAL
+#include <dns.h>
+
+ /*
+  * KISS memory management.
+  */
+#define MAX_TYPE       10
+
+/* dns_lookup_rl - DNS lookup interface with types list */
+
+int     dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
+                             VSTRING *fqdn, VSTRING *why, int *rcode,
+                             int lflags,...)
+{
+    va_list ap;
+    unsigned type, types[MAX_TYPE];
+    int     count = 0;
+
+    va_start(ap, lflags);
+    for (type = va_arg(ap, unsigned); type != 0; type = va_arg(ap, unsigned)) {
+       if (count >= MAX_TYPE - 1)
+           msg_panic("dns_lookup_rl: too many types");
+       types[count++] = type;
+    }
+    types[count] = 0;
+    va_end(ap);
+    return (dns_lookup_rv(name, flags, rrlist, fqdn, why, rcode, lflags, types));
+}
+
+/* dns_lookup_rv - DNS lookup interface with types vector */
+
+int     dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist,
+                             VSTRING *fqdn, VSTRING *why, int *rcode,
+                             int lflags, const unsigned *types)
+{
+    unsigned type, next;
+    int     status = DNS_NOTFOUND;
+    int     hpref_status = INT_MIN;
+    VSTRING *hpref_rtext = 0;
+    int     hpref_rcode;
+    int     hpref_h_errno;
+    DNS_RR *rr;
+
+    /* Save intermediate highest-priority result. */
+#define SAVE_HPREF_STATUS() do { \
+       hpref_status = status; \
+       if (rcode) \
+           hpref_rcode = *rcode; \
+       if (why && status != DNS_OK) \
+           vstring_strcpy(hpref_rtext ? hpref_rtext : \
+                          (hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \
+                          vstring_str(why)); \
+       hpref_h_errno = dns_get_h_errno(); \
+    } while (0)
+
+    /* Restore intermediate highest-priority result. */
+#define RESTORE_HPREF_STATUS() do { \
+       status = hpref_status; \
+       if (rcode) \
+           *rcode = hpref_rcode; \
+       if (why && status != DNS_OK) \
+           vstring_strcpy(why, vstring_str(hpref_rtext)); \
+       dns_set_h_errno(hpref_h_errno); \
+    } while (0)
+
+    if (rrlist)
+       *rrlist = 0;
+    for (type = *types++; type != 0; type = next) {
+       next = *types++;
+       if (msg_verbose)
+           msg_info("lookup %s type %s flags %s",
+                    name, dns_strtype(type), dns_str_resflags(flags));
+       status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
+                             fqdn, why, rcode, lflags);
+       if (rrlist && rr) {
+           *rrlist = dns_rr_append(*rrlist, rr);
+           if (DNS_RR_IS_TRUNCATED(*rrlist))
+               break;
+       }
+       if (status == DNS_OK) {
+           if (lflags & DNS_REQ_FLAG_STOP_OK)
+               break;
+       } else if (status == DNS_INVAL) {
+           if (lflags & DNS_REQ_FLAG_STOP_INVAL)
+               break;
+       } else if (status == DNS_POLICY) {
+           if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
+               break;
+       } else if (status == DNS_NULLMX) {
+           if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
+               break;
+       }
+       /* XXX Stop after NXDOMAIN error. */
+       if (next == 0)
+           break;
+       if (status >= hpref_status)
+           SAVE_HPREF_STATUS();                /* save last info */
+    }
+    if (status < hpref_status)
+       RESTORE_HPREF_STATUS();                 /* else report last info */
+    if (hpref_rtext)
+       vstring_free(hpref_rtext);
+    return (status);
+}
diff --git a/postfix/src/dns/dns_lookup_types_test.c b/postfix/src/dns/dns_lookup_types_test.c
new file mode 100644 (file)
index 0000000..3813f73
--- /dev/null
@@ -0,0 +1,200 @@
+ /*
+  * Test program to mocks including logging. See pmock_expect_test.c and
+  * ptest_main.h for a documented example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <vstring.h>
+
+ /*
+  * DNS library.
+  */
+#include <dns.h>
+
+ /*
+  * Test library.
+  */
+#include <mock_dns.h>
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+ /*
+  * dns_lookup_rl() forwards all calls to dns_lookup_rv(), therefore most
+  * tests will focus on dns_lookup_rl().
+  */
+#define NO_RFLAGS      0
+#define NO_LFLAGS      0
+
+static void test_dns_lookup_rl_success(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    int     got_st, want_st = DNS_OK;
+    DNS_RR *got_rr = 0, *want_rr;
+    int     got_rcode, want_rcode = NOERROR;
+    int     got_herrval, want_herrval = 0;
+
+    /*
+     * Set up expectations and prepared responses.
+     */
+    want_rr = make_dns_rr("example.com", "example.com", T_MX, C_IN,
+                         5, 0, 10, 0, 0, "m1.example.com", 14);
+    expect_dns_lookup_x(1, want_herrval, DNS_OK, "example.com", T_MX,
+                       NO_RFLAGS, want_rr, (VSTRING *) 0, (VSTRING *) 0,
+                       NOERROR, NO_LFLAGS);
+
+    got_st = dns_lookup_rl("example.com", NO_RFLAGS, &got_rr, (VSTRING *) 0,
+                          (VSTRING *) 0, &got_rcode, NO_LFLAGS, T_MX, 0);
+    if (got_st != want_st) {
+       ptest_error(t, "dns_lookup_rl: got result %d, want %d",
+                   got_st, want_st);
+    } else if (got_rcode != want_rcode) {
+       ptest_error(t, "dns_lookup_rl: got rcode %d, want %d",
+                   got_rcode, want_rcode);
+    } else {
+       (void) eq_dns_rr(t, "dns_lookup_rl", got_rr, want_rr);
+    }
+
+    got_herrval = dns_get_h_errno();
+    if (got_herrval != want_herrval)
+       ptest_error(t, "dns_get_h_errno: got %d, want %d",
+                   got_herrval, want_herrval);
+
+    /*
+     * Cleanup.
+     */
+    dns_rr_free(want_rr);
+    if (got_rr)
+       dns_rr_free(got_rr);
+}
+
+static void test_dns_lookup_rv_success(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    int     got_st, want_st = DNS_OK;
+    DNS_RR *got_rr = 0, *want_rr;
+    int     got_rcode, want_rcode = NOERROR;
+    int     got_herrval, want_herrval = 0;
+    static const unsigned rr_types[2] = {T_MX, 0};
+
+    /*
+     * Set up expectations and prepared responses,
+     */
+    want_rr = make_dns_rr("example.com", "example.com", T_MX, C_IN,
+                         5, 0, 10, 0, 0, "m1.example.com", 14);
+    expect_dns_lookup_x(1, want_herrval, DNS_OK, "example.com", T_MX,
+                       NO_RFLAGS, want_rr, (VSTRING *) 0, (VSTRING *) 0,
+                       NOERROR, NO_LFLAGS);
+
+    got_st = dns_lookup_rv("example.com", NO_RFLAGS, &got_rr, (VSTRING *) 0,
+                          (VSTRING *) 0, &got_rcode, NO_LFLAGS, rr_types);
+    if (got_st != want_st) {
+       ptest_error(t, "dns_lookup_rv: got result %d, want %d",
+                   got_st, want_st);
+    } else if (got_rcode != want_rcode) {
+       ptest_error(t, "dns_lookup_rv: got rcode %d, want %d",
+                   got_rcode, want_rcode);
+    } else {
+       (void) eq_dns_rr(t, "dns_lookup_rv", got_rr, want_rr);
+    }
+
+    got_herrval = dns_get_h_errno();
+    if (got_herrval != want_herrval)
+       ptest_error(t, "dns_get_h_errno: got %d, want %d",
+                   got_herrval, want_herrval);
+
+    /*
+     * Cleanup.
+     */
+    dns_rr_free(want_rr);
+    if (got_rr)
+       dns_rr_free(got_rr);
+}
+
+static void test_dns_lookup_rv_error_ladder(PTEST_CTX *t,
+                                                   const PTEST_CASE *unused)
+{
+    int     got_st;
+    int     got_herrval;
+    struct step {
+       int     want_st;
+       int     want_herrval;
+    };
+    struct step ladder[] = {
+       DNS_OK, 0,
+       DNS_POLICY, 0,
+       DNS_RETRY, TRY_AGAIN,
+       DNS_INVAL, 0,
+       DNS_FAIL, NO_RECOVERY,
+       DNS_NULLMX, 0,
+       DNS_NOTFOUND, NO_DATA,
+    };
+    struct step *lp;
+    VSTRING *label = vstring_alloc(100);
+
+#define LADDER_SIZE    (sizeof(ladder)/sizeof(*ladder))
+
+    for (lp = ladder; lp < ladder + LADDER_SIZE - 1; lp++) {
+
+       vstring_sprintf(label, "%s precedence over %s",
+                       dns_status_to_string(lp->want_st),
+                       dns_status_to_string(lp[1].want_st));
+
+       PTEST_RUN(t, vstring_str(label), {
+
+           /*
+            * Set up expectations and prepared responses.
+            */
+           expect_dns_lookup_x(1, lp->want_herrval, lp->want_st,
+                               "example.com", T_MX, NO_RFLAGS, (DNS_RR *) 0,
+                         (VSTRING *) 0, (VSTRING *) 0, NOERROR, NO_LFLAGS);
+           expect_dns_lookup_x(1, lp[1].want_herrval, lp[1].want_st,
+                               "example.com", T_A, NO_RFLAGS, (DNS_RR *) 0,
+                         (VSTRING *) 0, (VSTRING *) 0, NOERROR, NO_LFLAGS);
+
+           /*
+            * Call the mock and verify the results.
+            */
+           got_st = dns_lookup_rl("example.com", NO_RFLAGS, (DNS_RR **) 0,
+                                  (VSTRING *) 0, (VSTRING *) 0, (int *) 0,
+                                  NO_LFLAGS, T_MX, T_A, 0);
+           if (got_st != lp->want_st) {
+               ptest_error(t, "dns_lookup_rv: got result %d, want %d",
+                           got_st, lp->want_st);
+           }
+           got_herrval = dns_get_h_errno();
+           if (got_herrval != lp->want_herrval)
+               ptest_error(t, "dns_get_h_errno: got %d, want %d",
+                           got_herrval, lp->want_herrval);
+       });
+    }
+    vstring_free(label);
+}
+
+ /*
+  * Test cases. The "success" tests exercise the expectation match and apply
+  * helpers, and "failure" tests exercise the print helpers. All tests
+  * exercise the expectation free helpers.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+       "test_dns_lookup_rl success", test_dns_lookup_rl_success,
+    },
+    {
+       "test_dns_lookup_rv success", test_dns_lookup_rv_success,
+    },
+    {
+       "test_dns_lookup_rv error ladder", test_dns_lookup_rv_error_ladder,
+    },
+};
+
+#include <ptest_main.h>
index ad40269ce7831192213bb0360bbe0a7a1417f1fb..273e7ab6739064770c5d93c1fa0cdb636af6737e 100644 (file)
@@ -36,7 +36,7 @@ SRCS  = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \
        mail_addr_form.c quote_flags.c maillog_client.c \
        normalize_mailhost_addr.c map_search.c reject_deliver_request.c \
        info_log_addr_form.c sasl_mech_filter.c login_sender_match.c \
-       test_main.c compat_level.c config_known_tcp_ports.c \
+       test_server_main.c compat_level.c config_known_tcp_ports.c \
        hfrom_format.c rfc2047_code.c ascii_header_text.c sendopts.c \
        pol_stats.c nbdb_clnt.c nbdb_util.c allowed_prefix.c \
        nbdb_redirect.c nbdb_surrogate.c
@@ -77,7 +77,7 @@ OBJS  = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \
        $(NON_PLUGIN_MAP_OBJ) mail_addr_form.o quote_flags.o maillog_client.o \
        normalize_mailhost_addr.o map_search.o reject_deliver_request.o \
        info_log_addr_form.o sasl_mech_filter.o login_sender_match.o \
-       test_main.o compat_level.o config_known_tcp_ports.o \
+       test_server_main.o compat_level.o config_known_tcp_ports.o \
        hfrom_format.o rfc2047_code.o ascii_header_text.o sendopts.o \
        pol_stats.o nbdb_clnt.o nbdb_util.o allowed_prefix.o \
        nbdb_redirect.o nbdb_surrogate.o
@@ -85,7 +85,6 @@ OBJS  = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \
 # When hard-linking these maps, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
 # otherwise it sets the PLUGIN_* macros.
 MAP_OBJ = dict_ldap.o dict_mysql.o dict_pgsql.o dict_sqlite.o dict_mongodb.o
-
 HDRS   = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \
        canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \
        conv_time.h db_common.h debug_peer.h debug_process.h defer.h \
@@ -116,14 +115,16 @@ HDRS      = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \
        attr_override.h mail_parm_split.h midna_adomain.h mail_addr_form.h \
        maillog_client.h normalize_mailhost_addr.h map_search.h \
        info_log_addr_form.h sasl_mech_filter.h login_sender_match.h \
-       test_main.h compat_level.h config_known_tcp_ports.h \
+       test_server_main.h compat_level.h config_known_tcp_ports.h \
        hfrom_format.h rfc2047_code.h ascii_header_text.h sendopts.h \
        pol_stats.h nbdb_clnt.h nbdb_util.h allowed_prefix.h \
        nbdb_redirect.h nbdb_surrogate.h
 TESTSRC        = rec2stream.c stream2rec.c recdump.c dict_sqlite_test.c \
        ehlo_mask_test.c haproxy_srvr_test.c sendopts_test.c pol_stats_test.c \
        allowed_prefix_test.c nbdb_util_test.c nbdb_redirect_test.c \
-       nbdb_surrogate_test.c
+       nbdb_surrogate_test.c recdump.c login_sender_match_test.c \
+       normalize_mailhost_addr_test.c smtp_reply_footer_test.c \
+       map_search_test.c
 DEFS   = -I. -I$(INC_DIR) -D$(SYSTYPE)
 CFLAGS = $(DEBUG) $(OPT) $(DEFS)
 INCL   =
@@ -137,15 +138,18 @@ TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \
        valid_mailhost_addr own_inet_addr header_body_checks \
        data_redirect addr_match_list safe_ultostr verify_sender_addr \
        mail_dict server_acl uxtext mail_parm_split \
-       fold_addr smtp_reply_footer mail_addr_map normalize_mailhost_addr \
-       haproxy_srvr_test map_search delivered_hdr login_sender_match_test \
-       compat_level config_known_tcp_ports hfrom_format rfc2047_code \
+       fold_addr smtp_reply_footer_test mail_addr_map \
+       normalize_mailhost_addr_test haproxy_srvr_test map_search_test \
+       delivered_hdr_test login_sender_match_test compat_level \
+       config_known_tcp_ports_test hfrom_format_test rfc2047_code \
        ascii_header_text sendopts_test dict_sqlite_test pol_stats_test \
        allowed_prefix_test nbdb_util_test nbdb_redirect_test \
        nbdb_surrogate_test
 TESTLIB        = $(LIB_DIR)/libtesting.a
 
 LIBS   = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+PTEST_LIB= ../../lib/libptest.a
+PMOCK_LIB= ../../lib/libtesting.a
 LIB_DIR        = ../../lib
 INC_DIR        = ../../include
 PLUGIN_MAP_SO = $(LIB_PREFIX)ldap$(LIB_SUFFIX) $(LIB_PREFIX)mysql$(LIB_SUFFIX) \
@@ -156,7 +160,7 @@ MAKES       =
 
 all: $(LIB) $(PLUGIN_MAP_SO_MAKE) $(PLUGIN_MAP_OBJ)
 
-$(OBJS) $(PLUGIN_MAP_OBJ): ../../conf/makedefs.out
+$(OBJS) $(PLUGIN_MAP_OBJ) $(TESTPROG): ../../conf/makedefs.out
 
 Makefile: Makefile.in
        cat ../../conf/makedefs.out $? >$@
@@ -387,45 +391,24 @@ mail_parm_split: mail_parm_split.c $(LIB) $(LIBS)
 fold_addr: fold_addr.c $(LIB) $(LIBS)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
 
-smtp_reply_footer: smtp_reply_footer.c $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
-
-normalize_mailhost_addr: normalize_mailhost_addr.c $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
-
-haproxy_srvr_test: haproxy_srvr_test.c $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
-
-map_search: map_search.c $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
-
-delivered_hdr: delivered_hdr.c $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
-
-login_sender_match_test: login_sender_match_test.c $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
-
 compat_level: compat_level.c $(LIB) $(LIBS)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
 
-hfrom_format: hfrom_format.c $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
-
 rfc2047_code: rfc2047_code.c $(LIB) $(LIBS)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
 
 ascii_header_text: ascii_header_text.c $(LIB) $(LIBS)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
 
-sendopts_test: sendopts_test.c $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+sendopts_test: sendopts_test.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
 
-dict_sqlite_test: dict_sqlite_test.c dict_sqlite.o $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c dict_sqlite.o $(LIB) $(LIBS) \
+dict_sqlite_test: dict_sqlite_test.o dict_sqlite.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o dict_sqlite.o $(LIB) $(LIBS) \
            $(SYSLIBS) $(AUXLIBS_SQLITE)
 
-pol_stats_test: pol_stats_test.c pol_stats.o $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c pol_stats.o $(LIB) $(LIBS) \
+pol_stats_test: pol_stats_test.o pol_stats.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o pol_stats.o $(LIB) $(LIBS) \
            $(SYSLIBS)
 
 allowed_prefix_test: allowed_prefix_test.o $(TESTLIB) $(LIB) $(LIBS)
@@ -440,19 +423,16 @@ nbdb_redirect_test: nbdb_redirect_test.o $(TESTLIB) $(LIB) $(LIBS)
 nbdb_surrogate_test: nbdb_surrogate_test.o $(TESTLIB) $(LIB) $(LIBS)
        $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIB) $(LIB) $(LIBS) $(SYSLIBS)
 
-config_known_tcp_ports: config_known_tcp_ports.c $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
-
-tests: tok822_test mime_tests strip_addr_test tok822_limit_test \
+tests: update tok822_test mime_tests strip_addr_test tok822_limit_test \
        xtext_test scache_multi_test test_ehlo_mask \
        namadr_list_test mail_conf_time_test header_body_checks_tests \
        server_acl_test resolve_local_test maps_test \
        safe_ultostr_test mail_parm_split_test fold_addr_test \
-       smtp_reply_footer_test off_cvt_test mail_addr_crunch_test \
+       test_smtp_reply_footer off_cvt_test mail_addr_crunch_test \
        mail_addr_find_test mail_addr_map_test quote_822_local_test \
-       normalize_mailhost_addr_test test_haproxy_srvr map_search_test \
+       test_normalize_mailhost_addr test_haproxy_srvr test_map_search \
        delivered_hdr_test test_login_sender_match compat_level_test \
-       config_known_tcp_ports_test hfrom_format_test rfc2047_code_test \
+       test_config_known_tcp_ports test_hfrom_format rfc2047_code_test \
        ascii_header_text_test test_sendopts test_dict_sqlite test_pol_stats \
        test_allowed_prefix nbdb_tests
 
@@ -555,7 +535,9 @@ server_acl_test: server_acl server_acl.in server_acl.ref
        rm -f server_acl.tmp
 
 resolve_local_test: resolve_local resolve_local.in resolve_local.ref
-       $(SHLIB_ENV) sh resolve_local.in >resolve_local.tmp 2>&1
+       rm -f main.cf
+       touch -t 197601010000 main.cf
+       $(SHLIB_ENV) MAIL_CONFIG=. sh resolve_local.in >resolve_local.tmp 2>&1
        diff  resolve_local.ref resolve_local.tmp
        rm -f resolve_local.tmp
 
@@ -731,10 +713,11 @@ fold_addr_test: fold_addr fold_addr_test.in fold_addr_test.ref
        diff fold_addr_test.ref fold_addr_test.tmp
        rm -f fold_addr_test.tmp
 
-smtp_reply_footer_test: smtp_reply_footer smtp_reply_footer.ref
-       $(SHLIB_ENV) $(VALGRIND) ./smtp_reply_footer >smtp_reply_footer.tmp 2>&1
-       diff smtp_reply_footer.ref smtp_reply_footer.tmp
-       rm -f smtp_reply_footer.tmp
+smtp_reply_footer_test: smtp_reply_footer_test.o $(PTEST_LIB) $(LIB) $(LIBS)
+        $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS)
+
+test_smtp_reply_footer: smtp_reply_footer_test
+       $(SHLIB_ENV) $(VALGRIND) ./smtp_reply_footer_test
 
 off_cvt_test: off_cvt off_cvt.in off_cvt.ref
        $(SHLIB_ENV) $(VALGRIND) ./off_cvt <off_cvt.in >off_cvt.tmp 2>&1
@@ -742,9 +725,11 @@ off_cvt_test: off_cvt off_cvt.in off_cvt.ref
        rm -f off_cvt.tmp
 
 mail_addr_crunch_test: update mail_addr_crunch mail_addr_crunch.in mail_addr_crunch.ref
-       -$(SHLIB_ENV) sh mail_addr_crunch.in >mail_addr_crunch.tmp 2>&1
+       rm -f main.cf
+       touch -t 197601010000 main.cf
+       -$(SHLIB_ENV) MAIL_CONFIG=. sh mail_addr_crunch.in >mail_addr_crunch.tmp 2>&1
        diff mail_addr_crunch.ref mail_addr_crunch.tmp
-       rm -f mail_addr_crunch.tmp
+       rm -f mail_addr_crunch.tmp main.cf
 
 mail_addr_find_test: update mail_addr_find mail_addr_find.in mail_addr_find.ref
        -$(SHLIB_ENV) $(VALGRIND) ./mail_addr_find <mail_addr_find.in >mail_addr_find.tmp 2>&1
@@ -762,25 +747,37 @@ quote_822_local_test: update quote_822_local quote_822_local.in quote_822_local.
        diff quote_822_local.ref quote_822_local.tmp
        rm -f quote_822_local.tmp
 
-normalize_mailhost_addr_test: update normalize_mailhost_addr
-       -$(SHLIB_ENV) $(VALGRIND) ./normalize_mailhost_addr >normalize_mailhost_addr.tmp 2>&1
-       diff /dev/null normalize_mailhost_addr.tmp
-       rm -f normalize_mailhost_addr.tmp
+normalize_mailhost_addr_test: normalize_mailhost_addr_test.o \
+       $(PTEST_LIB) $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS)
+
+test_normalize_mailhost_addr: update normalize_mailhost_addr_test
+       $(SHLIB_ENV) $(VALGRIND) ./normalize_mailhost_addr_test
+
+haproxy_srvr_test: haproxy_srvr_test.o \
+       $(PTEST_LIB) $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS)
 
 test_haproxy_srvr: update haproxy_srvr_test
        $(SHLIB_ENV) $(VALGRIND) ./haproxy_srvr_test
 
-map_search_test: update map_search map_search.ref
-       -$(SHLIB_ENV) $(VALGRIND) ./map_search >map_search.tmp 2>&1
-       diff map_search.ref map_search.tmp
-       rm -f map_search.tmp
+map_search_test: map_search_test.o $(PTEST_LIB) $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS)
+
+test_map_search: update map_search_test 
+       $(SHLIB_ENV) $(VALGRIND) ./map_search_test
+
+delivered_hdr_test: delivered_hdr_test.o $(PTEST_LIB) $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS)
 
-delivered_hdr_test: update delivered_hdr delivered_hdr.ref
-       -$(SHLIB_ENV) $(VALGRIND) ./delivered_hdr >delivered_hdr.tmp 2>&1
-       diff delivered_hdr.ref delivered_hdr.tmp
-       rm -f delivered_hdr.tmp
+test_delivered_hdr: update delivered_hdr_test 
+       $(SHLIB_ENV) $(VALGRIND) ./delivered_hdr_test
 
-test_login_sender_match: update login_sender_match_test 
+login_sender_match_test: login_sender_match_test.o \
+       $(PTEST_LIB) $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS)
+
+test_login_sender_match: update login_sender_match_test
        $(SHLIB_ENV) $(VALGRIND) ./login_sender_match_test
 
 compat_level_test: compat_level_expand_test compat_level_convert_test
@@ -797,19 +794,17 @@ compat_level_convert_test: update compat_level compat_level_convert.in \
        diff compat_level_convert.ref compat_level_convert.tmp
        rm -f compat_level_convert.tmp
 
-config_known_tcp_ports_test: update config_known_tcp_ports \
-           config_known_tcp_ports.ref
-       -$(SHLIB_ENV) $(VALGRIND) ./config_known_tcp_ports \
-            >config_known_tcp_ports.tmp 2>&1
-       diff config_known_tcp_ports.ref config_known_tcp_ports.tmp
-       rm -f config_known_tcp_ports.tmp
+config_known_tcp_ports_test: config_known_tcp_ports_test.o $(PTEST_LIB) $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS)
+
+test_config_known_tcp_ports: update config_known_tcp_ports_test
+       $(SHLIB_ENV) $(VALGRIND) ./config_known_tcp_ports_test
+
+hfrom_format_test: hfrom_format_test.o $(PTEST_LIB) $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS)
 
-hfrom_format_test: update hfrom_format \
-           hfrom_format.ref
-       -$(SHLIB_ENV) $(VALGRIND) ./hfrom_format \
-            >hfrom_format.tmp 2>&1
-       diff hfrom_format.ref hfrom_format.tmp
-       rm -f hfrom_format.tmp
+test_hfrom_format: update hfrom_format_test
+       $(SHLIB_ENV) $(VALGRIND) ./hfrom_format_test
 
 rfc2047_code_test: update rfc2047_code
        $(SHLIB_ENV) $(VALGRIND) ./rfc2047_code
@@ -1087,6 +1082,24 @@ config_known_tcp_ports.o: ../../include/vbuf.h
 config_known_tcp_ports.o: ../../include/vstring.h
 config_known_tcp_ports.o: config_known_tcp_ports.c
 config_known_tcp_ports.o: config_known_tcp_ports.h
+config_known_tcp_ports_test.o: ../../include/argv.h
+config_known_tcp_ports_test.o: ../../include/check_arg.h
+config_known_tcp_ports_test.o: ../../include/known_tcp_ports.h
+config_known_tcp_ports_test.o: ../../include/msg.h
+config_known_tcp_ports_test.o: ../../include/msg_jmp.h
+config_known_tcp_ports_test.o: ../../include/msg_output.h
+config_known_tcp_ports_test.o: ../../include/msg_vstream.h
+config_known_tcp_ports_test.o: ../../include/myrand.h
+config_known_tcp_ports_test.o: ../../include/pmock_expect.h
+config_known_tcp_ports_test.o: ../../include/ptest.h
+config_known_tcp_ports_test.o: ../../include/ptest_main.h
+config_known_tcp_ports_test.o: ../../include/stringops.h
+config_known_tcp_ports_test.o: ../../include/sys_defs.h
+config_known_tcp_ports_test.o: ../../include/vbuf.h
+config_known_tcp_ports_test.o: ../../include/vstream.h
+config_known_tcp_ports_test.o: ../../include/vstring.h
+config_known_tcp_ports_test.o: config_known_tcp_ports.h
+config_known_tcp_ports_test.o: config_known_tcp_ports_test.c
 conv_time.o: ../../include/msg.h
 conv_time.o: ../../include/sys_defs.h
 conv_time.o: conv_time.c
@@ -1264,6 +1277,28 @@ delivered_hdr.o: quote_822_local.h
 delivered_hdr.o: quote_flags.h
 delivered_hdr.o: rec_type.h
 delivered_hdr.o: record.h
+delivered_hdr_test.o: ../../include/argv.h
+delivered_hdr_test.o: ../../include/check_arg.h
+delivered_hdr_test.o: ../../include/msg.h
+delivered_hdr_test.o: ../../include/msg_jmp.h
+delivered_hdr_test.o: ../../include/msg_output.h
+delivered_hdr_test.o: ../../include/msg_vstream.h
+delivered_hdr_test.o: ../../include/mymalloc.h
+delivered_hdr_test.o: ../../include/myrand.h
+delivered_hdr_test.o: ../../include/pmock_expect.h
+delivered_hdr_test.o: ../../include/ptest.h
+delivered_hdr_test.o: ../../include/ptest_main.h
+delivered_hdr_test.o: ../../include/stringops.h
+delivered_hdr_test.o: ../../include/sys_defs.h
+delivered_hdr_test.o: ../../include/vbuf.h
+delivered_hdr_test.o: ../../include/vstream.h
+delivered_hdr_test.o: ../../include/vstring.h
+delivered_hdr_test.o: delivered_hdr.h
+delivered_hdr_test.o: delivered_hdr_test.c
+delivered_hdr_test.o: fold_addr.h
+delivered_hdr_test.o: mail_params.h
+delivered_hdr_test.o: rec_type.h
+delivered_hdr_test.o: record.h
 dict_ldap.o: ../../include/argv.h
 dict_ldap.o: ../../include/binhash.h
 dict_ldap.o: ../../include/check_arg.h
@@ -1327,7 +1362,7 @@ dict_mysql.o: ../../include/argv.h
 dict_mysql.o: ../../include/check_arg.h
 dict_mysql.o: ../../include/dict.h
 dict_mysql.o: ../../include/events.h
-dict_mysql.o: ../../include/find_inet.h
+dict_mysql.o: ../../include/find_inet_service.h
 dict_mysql.o: ../../include/match_list.h
 dict_mysql.o: ../../include/msg.h
 dict_mysql.o: ../../include/myflock.h
@@ -1598,10 +1633,17 @@ haproxy_srvr.o: ../../include/vbuf.h
 haproxy_srvr.o: ../../include/vstring.h
 haproxy_srvr.o: haproxy_srvr.c
 haproxy_srvr.o: haproxy_srvr.h
+haproxy_srvr_test.o: ../../include/argv.h
 haproxy_srvr_test.o: ../../include/check_arg.h
 haproxy_srvr_test.o: ../../include/msg.h
+haproxy_srvr_test.o: ../../include/msg_jmp.h
+haproxy_srvr_test.o: ../../include/msg_output.h
 haproxy_srvr_test.o: ../../include/msg_vstream.h
 haproxy_srvr_test.o: ../../include/myaddrinfo.h
+haproxy_srvr_test.o: ../../include/myrand.h
+haproxy_srvr_test.o: ../../include/pmock_expect.h
+haproxy_srvr_test.o: ../../include/ptest.h
+haproxy_srvr_test.o: ../../include/ptest_main.h
 haproxy_srvr_test.o: ../../include/sock_addr.h
 haproxy_srvr_test.o: ../../include/stringops.h
 haproxy_srvr_test.o: ../../include/sys_defs.h
@@ -1655,6 +1697,24 @@ hfrom_format.o: ../../include/sys_defs.h
 hfrom_format.o: hfrom_format.c
 hfrom_format.o: hfrom_format.h
 hfrom_format.o: mail_params.h
+hfrom_format_test.o: ../../include/argv.h
+hfrom_format_test.o: ../../include/check_arg.h
+hfrom_format_test.o: ../../include/msg.h
+hfrom_format_test.o: ../../include/msg_jmp.h
+hfrom_format_test.o: ../../include/msg_output.h
+hfrom_format_test.o: ../../include/msg_vstream.h
+hfrom_format_test.o: ../../include/myrand.h
+hfrom_format_test.o: ../../include/pmock_expect.h
+hfrom_format_test.o: ../../include/ptest.h
+hfrom_format_test.o: ../../include/ptest_main.h
+hfrom_format_test.o: ../../include/stringops.h
+hfrom_format_test.o: ../../include/sys_defs.h
+hfrom_format_test.o: ../../include/vbuf.h
+hfrom_format_test.o: ../../include/vstream.h
+hfrom_format_test.o: ../../include/vstring.h
+hfrom_format_test.o: hfrom_format.h
+hfrom_format_test.o: hfrom_format_test.c
+hfrom_format_test.o: mail_params.h
 info_log_addr_form.o: ../../include/check_arg.h
 info_log_addr_form.o: ../../include/msg.h
 info_log_addr_form.o: ../../include/name_code.h
@@ -1739,8 +1799,14 @@ login_sender_match_test.o: ../../include/argv.h
 login_sender_match_test.o: ../../include/check_arg.h
 login_sender_match_test.o: ../../include/dict.h
 login_sender_match_test.o: ../../include/msg.h
+login_sender_match_test.o: ../../include/msg_jmp.h
+login_sender_match_test.o: ../../include/msg_output.h
 login_sender_match_test.o: ../../include/msg_vstream.h
 login_sender_match_test.o: ../../include/myflock.h
+login_sender_match_test.o: ../../include/myrand.h
+login_sender_match_test.o: ../../include/pmock_expect.h
+login_sender_match_test.o: ../../include/ptest.h
+login_sender_match_test.o: ../../include/ptest_main.h
 login_sender_match_test.o: ../../include/stringops.h
 login_sender_match_test.o: ../../include/sys_defs.h
 login_sender_match_test.o: ../../include/vbuf.h
@@ -2236,6 +2302,24 @@ map_search.o: ../../include/vbuf.h
 map_search.o: ../../include/vstring.h
 map_search.o: map_search.c
 map_search.o: map_search.h
+map_search_test.o: ../../include/argv.h
+map_search_test.o: ../../include/check_arg.h
+map_search_test.o: ../../include/msg.h
+map_search_test.o: ../../include/msg_jmp.h
+map_search_test.o: ../../include/msg_output.h
+map_search_test.o: ../../include/msg_vstream.h
+map_search_test.o: ../../include/myrand.h
+map_search_test.o: ../../include/name_code.h
+map_search_test.o: ../../include/pmock_expect.h
+map_search_test.o: ../../include/ptest.h
+map_search_test.o: ../../include/ptest_main.h
+map_search_test.o: ../../include/stringops.h
+map_search_test.o: ../../include/sys_defs.h
+map_search_test.o: ../../include/vbuf.h
+map_search_test.o: ../../include/vstream.h
+map_search_test.o: ../../include/vstring.h
+map_search_test.o: map_search.h
+map_search_test.o: map_search_test.c
 maps.o: ../../include/argv.h
 maps.o: ../../include/check_arg.h
 maps.o: ../../include/dict.h
@@ -2570,6 +2654,25 @@ normalize_mailhost_addr.o: ../../include/vstring.h
 normalize_mailhost_addr.o: normalize_mailhost_addr.c
 normalize_mailhost_addr.o: normalize_mailhost_addr.h
 normalize_mailhost_addr.o: valid_mailhost_addr.h
+normalize_mailhost_addr_test.o: ../../include/argv.h
+normalize_mailhost_addr_test.o: ../../include/check_arg.h
+normalize_mailhost_addr_test.o: ../../include/inet_proto.h
+normalize_mailhost_addr_test.o: ../../include/msg.h
+normalize_mailhost_addr_test.o: ../../include/msg_jmp.h
+normalize_mailhost_addr_test.o: ../../include/msg_output.h
+normalize_mailhost_addr_test.o: ../../include/msg_vstream.h
+normalize_mailhost_addr_test.o: ../../include/mymalloc.h
+normalize_mailhost_addr_test.o: ../../include/myrand.h
+normalize_mailhost_addr_test.o: ../../include/pmock_expect.h
+normalize_mailhost_addr_test.o: ../../include/ptest.h
+normalize_mailhost_addr_test.o: ../../include/ptest_main.h
+normalize_mailhost_addr_test.o: ../../include/stringops.h
+normalize_mailhost_addr_test.o: ../../include/sys_defs.h
+normalize_mailhost_addr_test.o: ../../include/vbuf.h
+normalize_mailhost_addr_test.o: ../../include/vstream.h
+normalize_mailhost_addr_test.o: ../../include/vstring.h
+normalize_mailhost_addr_test.o: normalize_mailhost_addr.h
+normalize_mailhost_addr_test.o: normalize_mailhost_addr_test.c
 off_cvt.o: ../../include/check_arg.h
 off_cvt.o: ../../include/msg.h
 off_cvt.o: ../../include/sys_defs.h
@@ -3013,6 +3116,26 @@ smtp_reply_footer.o: ../../include/vstring.h
 smtp_reply_footer.o: dsn_util.h
 smtp_reply_footer.o: smtp_reply_footer.c
 smtp_reply_footer.o: smtp_reply_footer.h
+smtp_reply_footer_test.o: ../../include/argv.h
+smtp_reply_footer_test.o: ../../include/check_arg.h
+smtp_reply_footer_test.o: ../../include/mac_expand.h
+smtp_reply_footer_test.o: ../../include/mac_parse.h
+smtp_reply_footer_test.o: ../../include/msg.h
+smtp_reply_footer_test.o: ../../include/msg_jmp.h
+smtp_reply_footer_test.o: ../../include/msg_output.h
+smtp_reply_footer_test.o: ../../include/msg_vstream.h
+smtp_reply_footer_test.o: ../../include/myrand.h
+smtp_reply_footer_test.o: ../../include/pmock_expect.h
+smtp_reply_footer_test.o: ../../include/ptest.h
+smtp_reply_footer_test.o: ../../include/ptest_main.h
+smtp_reply_footer_test.o: ../../include/stringops.h
+smtp_reply_footer_test.o: ../../include/sys_defs.h
+smtp_reply_footer_test.o: ../../include/vbuf.h
+smtp_reply_footer_test.o: ../../include/vstream.h
+smtp_reply_footer_test.o: ../../include/vstring.h
+smtp_reply_footer_test.o: dsn_util.h
+smtp_reply_footer_test.o: smtp_reply_footer.h
+smtp_reply_footer_test.o: smtp_reply_footer_test.c
 smtp_stream.o: ../../include/check_arg.h
 smtp_stream.o: ../../include/iostuff.h
 smtp_stream.o: ../../include/msg.h
@@ -3080,25 +3203,25 @@ sys_exits.o: ../../include/vbuf.h
 sys_exits.o: ../../include/vstring.h
 sys_exits.o: sys_exits.c
 sys_exits.o: sys_exits.h
-test_main.o: ../../include/argv.h
-test_main.o: ../../include/check_arg.h
-test_main.o: ../../include/dict.h
-test_main.o: ../../include/msg.h
-test_main.o: ../../include/msg_vstream.h
-test_main.o: ../../include/myflock.h
-test_main.o: ../../include/mymalloc.h
-test_main.o: ../../include/stringops.h
-test_main.o: ../../include/sys_defs.h
-test_main.o: ../../include/vbuf.h
-test_main.o: ../../include/vstream.h
-test_main.o: ../../include/vstring.h
-test_main.o: mail_conf.h
-test_main.o: mail_dict.h
-test_main.o: mail_params.h
-test_main.o: mail_task.h
-test_main.o: mail_version.h
-test_main.o: test_main.c
-test_main.o: test_main.h
+test_server_main.o: ../../include/argv.h
+test_server_main.o: ../../include/check_arg.h
+test_server_main.o: ../../include/dict.h
+test_server_main.o: ../../include/msg.h
+test_server_main.o: ../../include/msg_vstream.h
+test_server_main.o: ../../include/myflock.h
+test_server_main.o: ../../include/mymalloc.h
+test_server_main.o: ../../include/stringops.h
+test_server_main.o: ../../include/sys_defs.h
+test_server_main.o: ../../include/vbuf.h
+test_server_main.o: ../../include/vstream.h
+test_server_main.o: ../../include/vstring.h
+test_server_main.o: mail_conf.h
+test_server_main.o: mail_dict.h
+test_server_main.o: mail_params.h
+test_server_main.o: mail_task.h
+test_server_main.o: mail_version.h
+test_server_main.o: test_server_main.c
+test_server_main.o: test_server_main.h
 timed_ipc.o: ../../include/check_arg.h
 timed_ipc.o: ../../include/msg.h
 timed_ipc.o: ../../include/sys_defs.h
index db61f4aae5c1e9b544643b4807b497c6d4460000..15322a26418ffdc686c9385481c43934ffb32c4c 100644 (file)
@@ -123,135 +123,3 @@ void    config_known_tcp_ports(const char *source, const char *settings)
     }
     argv_free(associations);
 }
-
-#ifdef TEST
-
-#include <stdlib.h>
-#include <string.h>
-#include <msg_vstream.h>
-
-#define STR(x) vstring_str(x)
-
- /* TODO(wietse) make this a proper VSTREAM interface */
-
-/* vstream_swap - kludge to capture output for testing */
-
-static void vstream_swap(VSTREAM *one, VSTREAM *two)
-{
-    VSTREAM save;
-
-    save = *one;
-    *one = *two;
-    *two = save;
-}
-
-struct test_case {
-    const char *label;                 /* identifies test case */
-    const char *config;                        /* configuration under test */
-    const char *exp_warning;           /* expected warning or null */
-    const char *exp_export;            /* expected export or null */
-};
-
-static struct test_case test_cases[] = {
-    {"good",
-        /* config */ "smtp = 25, smtps = submissions = 465, lmtp = 24",
-        /* warning */ "",
-        /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465"
-    },
-    {"equal-equal",
-        /* config */ "smtp = 25, smtps == submissions = 465, lmtp = 24",
-        /* warning */ "config_known_tcp_ports: warning: equal-equal: "
-       "in \" smtps == submissions = 465\": missing service name before "
-       "\"=\"\n",
-        /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465"
-    },
-    {"port test 1",
-        /* config */ "smtps = submission =",
-        /* warning */ "config_known_tcp_ports: warning: port test 1: "
-       "in \"smtps = submission =\": missing port value after \"=\"\n",
-        /* export */ ""
-    },
-    {"port test 2",
-        /* config */ "smtps = submission = 4 65",
-        /* warning */ "config_known_tcp_ports: warning: port test 2: "
-       "in \"smtps = submission = 4 65\": whitespace in port number\n",
-        /* export */ ""
-    },
-    {"port test 3",
-        /* config */ "lmtp = 24, smtps = submission = foo",
-        /* warning */ "config_known_tcp_ports: warning: port test 3: "
-       "in \" smtps = submission = foo\": non-numerical service port\n",
-        /* export */ "lmtp=24"
-    },
-    {"service name test 1",
-        /* config */ "smtps = sub mission = 465",
-        /* warning */ "config_known_tcp_ports: warning: service name test 1: "
-       "in \"smtps = sub mission = 465\": whitespace in service name\n",
-        /* export */ "smtps=465"
-    },
-    {"service name test 2",
-        /* config */ "lmtp = 24, smtps = 1234 = submissions = 465",
-        /* warning */ "config_known_tcp_ports: warning: service name test 2: "
-       "in \" smtps = 1234 = submissions = 465\": numerical service name\n",
-        /* export */ "lmtp=24 smtps=465 submissions=465"
-    },
-    0,
-};
-
-int     main(int argc, char **argv)
-{
-    VSTRING *export_buf;
-    struct test_case *tp;
-    int     pass = 0;
-    int     fail = 0;
-    int     test_failed;
-    const char *export;
-    VSTRING *msg_buf;
-    VSTREAM *memory_stream;
-
-#define STRING_OR_NULL(s) ((s) ? (s) : "(null)")
-
-    msg_vstream_init("config_known_tcp_ports", VSTREAM_ERR);
-
-    export_buf = vstring_alloc(100);
-    msg_buf = vstring_alloc(100);
-    for (tp = test_cases; tp->label != 0; tp++) {
-       test_failed = 0;
-       if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
-           msg_fatal("open memory stream: %m");
-       vstream_swap(VSTREAM_ERR, memory_stream);
-       config_known_tcp_ports(tp->label, tp->config);
-       vstream_swap(memory_stream, VSTREAM_ERR);
-       if (vstream_fclose(memory_stream))
-           msg_fatal("close memory stream: %m");
-       if (strcmp(STR(msg_buf), tp->exp_warning) != 0) {
-           msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
-                    tp->label, STR(msg_buf),
-                    STRING_OR_NULL(tp->exp_warning));
-           test_failed = 1;
-       } else {
-           export = export_known_tcp_ports(export_buf);
-           if (strcmp(export, tp->exp_export) != 0) {
-               msg_warn("test case %s: got export: \"%s\", want: \"%s\"",
-                        tp->label, export, tp->exp_export);
-               test_failed = 1;
-           }
-           clear_known_tcp_ports();
-           VSTRING_RESET(msg_buf);
-           VSTRING_TERMINATE(msg_buf);
-       }
-       if (test_failed) {
-           msg_info("%s: FAIL", tp->label);
-           fail++;
-       } else {
-           msg_info("%s: PASS", tp->label);
-           pass++;
-       }
-    }
-    msg_info("PASS=%d FAIL=%d", pass, fail);
-    vstring_free(msg_buf);
-    vstring_free(export_buf);
-    exit(fail != 0);
-}
-
-#endif
diff --git a/postfix/src/global/config_known_tcp_ports_test.c b/postfix/src/global/config_known_tcp_ports_test.c
new file mode 100644 (file)
index 0000000..e1c331e
--- /dev/null
@@ -0,0 +1,97 @@
+ /*
+  * Test program to exercise config_known_tcp_ports.c. See ptest_main.h for a
+  * documented example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <known_tcp_ports.h>
+#include <vstring.h>
+
+ /*
+  * Global library.
+  */
+#include <config_known_tcp_ports.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* identifies test case */
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+    const char *config;                        /* configuration under test */
+    const char *want_warning;          /* expected warning or null */
+    const char *want_export;           /* expected export or null */
+} PTEST_CASE;
+
+static void test_config_known_tcp_ports(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    VSTRING *export_buf;
+    const char *got_export;
+
+    export_buf = vstring_alloc(100);
+    if (*tp->want_warning)
+       expect_ptest_error(t, tp->want_warning);
+    config_known_tcp_ports(tp->testname, tp->config);
+    got_export = export_known_tcp_ports(export_buf);
+    if (strcmp(got_export, tp->want_export) != 0)
+       ptest_error(t, "got export \"%s\", want \"%s\"",
+                   got_export, tp->want_export);
+    clear_known_tcp_ports();
+    vstring_free(export_buf);
+}
+
+static const PTEST_CASE ptestcases[] = {
+    {"good", test_config_known_tcp_ports,
+        /* config */ "smtp = 25, smtps = submissions = 465, lmtp = 24",
+        /* warning */ "",
+        /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465"
+    },
+    {"equal-equal", test_config_known_tcp_ports,
+        /* config */ "smtp = 25, smtps == submissions = 465, lmtp = 24",
+        /* warning */ "equal-equal: in \" smtps == submissions = 465\": "
+       "missing service name before \"=\"",
+        /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465"
+    },
+    {"port test 1", test_config_known_tcp_ports,
+        /* config */ "smtps = submission =",
+        /* warning */ "port test 1: in \"smtps = submission =\": "
+       "missing port value after \"=\"",
+        /* export */ ""
+    },
+    {"port test 2", test_config_known_tcp_ports,
+        /* config */ "smtps = submission = 4 65",
+        /* warning */ "port test 2: in \"smtps = submission = 4 65\": "
+       "whitespace in port number",
+        /* export */ ""
+    },
+    {"port test 3", test_config_known_tcp_ports,
+        /* config */ "lmtp = 24, smtps = submission = foo",
+        /* warning */ "port test 3: in \" smtps = submission = foo\": "
+       "non-numerical service port",
+        /* export */ "lmtp=24"
+    },
+    {"service name test 1", test_config_known_tcp_ports,
+        /* config */ "smtps = sub mission = 465",
+        /* warning */ "service name test 1: in \"smtps = sub mission = 465\": "
+       "whitespace in service name",
+        /* export */ "smtps=465"
+    },
+    {"service name test 2", test_config_known_tcp_ports,
+        /* config */ "lmtp = 24, smtps = 1234 = submissions = 465",
+        /* warning */ "service name test 2: in \" smtps = 1234 = submissions "
+       "= 465\": numerical service name",
+        /* export */ "lmtp=24 smtps=465 submissions=465"
+    },
+};
+
+#include <ptest_main.h>
index 0aea1cc1dbca814bc6734b302c710bcbafe282aa..1b20b38565a13bcd92e39a0b4cf155b5b1a20b39 100644 (file)
@@ -198,69 +198,3 @@ void    delivered_hdr_free(DELIVERED_HDR_INFO *info)
     htable_free(info->table, (void (*) (void *)) 0);
     myfree((void *) info);
 }
-
-#ifdef TEST
-
-#include <msg_vstream.h>
-#include <mail_params.h>
-
-char   *var_drop_hdrs;
-
-int     main(int arc, char **argv)
-{
-
-    /*
-     * We write test records to a VSTRING, then read with delivered_hdr_init.
-     */
-    VSTRING *mem_bp;
-    VSTREAM *mem_fp;
-    DELIVERED_HDR_INFO *dp;
-    struct test_case {
-       int     rec_type;
-       const char *content;
-       int     expect_find;
-    };
-    const struct test_case test_cases[] = {
-       REC_TYPE_CONT, "Delivered-To: one", 1,
-       REC_TYPE_NORM, "Delivered-To: two", 0,
-       REC_TYPE_NORM, "Delivered-To: three", 1,
-       0,
-    };
-    const struct test_case *tp;
-    int     actual_find;
-    int     errors;
-
-    msg_vstream_init(argv[0], VSTREAM_ERR);
-
-    var_drop_hdrs = mystrdup(DEF_DROP_HDRS);
-
-    mem_bp = vstring_alloc(VSTREAM_BUFSIZE);
-    if ((mem_fp = vstream_memopen(mem_bp, O_WRONLY)) == 0)
-       msg_panic("vstream_memopen O_WRONLY failed: %m");
-
-#define REC_PUT_LIT(fp, type, lit) rec_put((fp), (type), (lit), strlen(lit))
-
-    for (tp = test_cases; tp->content != 0; tp++)
-       REC_PUT_LIT(mem_fp, tp->rec_type, tp->content);
-
-    if (vstream_fclose(mem_fp))
-       msg_panic("vstream_fclose fail: %m");
-
-    if ((mem_fp = vstream_memopen(mem_bp, O_RDONLY)) == 0)
-       msg_panic("vstream_memopen O_RDONLY failed: %m");
-
-    dp = delivered_hdr_init(mem_fp, 0, FOLD_ADDR_ALL);
-
-    for (errors = 0, tp = test_cases; tp->content != 0; tp++) {
-       actual_find =
-           delivered_hdr_find(dp, tp->content + sizeof("Delivered-To:"));
-       msg_info("test case: %c >%s<: %s (expected: %s)",
-                tp->rec_type, tp->content,
-                actual_find == tp->expect_find ? "PASS" : "FAIL",
-                tp->expect_find ? "MATCH" : "NO MATCH");
-       errors += (actual_find != tp->expect_find);;
-    }
-    exit(errors);
-}
-
-#endif
diff --git a/postfix/src/global/delivered_hdr_test.c b/postfix/src/global/delivered_hdr_test.c
new file mode 100644 (file)
index 0000000..d470867
--- /dev/null
@@ -0,0 +1,117 @@
+ /*
+  * Test program to exercise delivered_hdr.c. See ptest_main.h for a
+  * documented example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg_vstream.h>
+#include <mymalloc.h>
+
+ /*
+  * Global library.
+  */
+#include <mail_params.h>
+#include <delivered_hdr.h>
+#include <rec_type.h>
+#include <record.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * Satisfy a configuration dependency.
+  */
+char   *var_drop_hdrs;
+
+ /*
+  * Test library adaptor.
+  */
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+#define FOUND          1
+#define NOT_FOUND      0
+
+static void test_delivered_hdr_find(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *mem_bp;
+    VSTREAM *mem_fp;
+    DELIVERED_HDR_INFO *dp;
+    struct test_case {
+       int     rec_type;
+       const char *rec_content;
+       int     want_found;
+    };
+
+    /*
+     * This structure specifies the order of records that will be written to
+     * a test queue file, and what we expect that delivered_hdr() will find.
+     * It should not find the record that immediately follows REC_TYPE_CONT.
+     */
+    static const struct test_case test_cases[] = {
+       REC_TYPE_CONT, "Delivered-To: one", FOUND,
+       REC_TYPE_NORM, "Delivered-To: two", NOT_FOUND,
+       REC_TYPE_NORM, "Delivered-To: three", FOUND,
+       0,
+    };
+    const struct test_case *tp;
+    int     got_found;
+
+    var_drop_hdrs = mystrdup(DEF_DROP_HDRS);
+
+    /*
+     * Write queue file records to memory stream.
+     */
+#define REC_PUT_LIT(fp, type, lit) rec_put((fp), (type), (lit), strlen(lit))
+
+    mem_bp = vstring_alloc(VSTREAM_BUFSIZE);
+    if ((mem_fp = vstream_memopen(mem_bp, O_WRONLY)) == 0)
+       ptest_fatal(t, "vstream_memopen O_WRONLY failed: %m");
+    for (tp = test_cases; tp->rec_content != 0; tp++)
+       REC_PUT_LIT(mem_fp, tp->rec_type, tp->rec_content);
+    if (vstream_fclose(mem_fp))
+       ptest_fatal(t, "vstream_fclose fail: %m");
+
+    /*
+     * Reopen the memory stream and populate the Delivered-To: cache.
+     */
+    if ((mem_fp = vstream_memopen(mem_bp, O_RDONLY)) == 0)
+       ptest_fatal(t, "vstream_memopen O_RDONLY failed: %m");
+    dp = delivered_hdr_init(mem_fp, 0, FOLD_ADDR_ALL);
+
+    /*
+     * Verify that what should be found will be found. XXX delivered_hdr()
+     * assumes that Delivered-To: records fit in one queue file record.
+     */
+#define FOUND_OR_NOT(x) ((x) ? "found" : "not found")
+
+    for (tp = test_cases; tp->rec_content != 0; tp++) {
+       got_found =
+           delivered_hdr_find(dp, tp->rec_content + sizeof("Delivered-To:"));
+       if (!got_found != !tp->want_found)
+           ptest_error(t, "delivered_hdr_find '%s': got '%s', want '%s'",
+                       tp->rec_content, FOUND_OR_NOT(got_found),
+                       FOUND_OR_NOT(tp->want_found));
+    }
+}
+
+ /*
+  * Test library adaptor.
+  */
+static PTEST_CASE ptestcases[] = {
+    "test_delivered_hdr_find", test_delivered_hdr_find,
+};
+
+#include <ptest_main.h>
index 89ec3c1ff086c16eadd80a1b4de3916b5e94cf39..c0affafec6d47a741e63563ec0bfeffcaa3df687 100644 (file)
@@ -99,7 +99,7 @@
 #include "argv.h"
 #include "vstring.h"
 #include "split_at.h"
-#include "find_inet.h"
+#include "find_inet_service.h"
 #include "myrand.h"
 #include "events.h"
 #include "stringops.h"
@@ -850,7 +850,9 @@ static HOST *host_init(const char *hostname)
     }
     host->name = mystrdup(d);
     if ((s = split_at_right(host->name, ':')) != 0)
-       host->port = ntohs(find_inet_port(s, "tcp"));
+       if ((host->port = find_inet_service(s, "tcp")) < 0)
+           /* TODO: return null and create a surrogate dictionary. */
+           msg_fatal("unknown service: %s/tcp", s);
     if (strcasecmp(host->name, "localhost") == 0) {
        /* The MySQL way: this will actually connect over the UNIX socket */
        myfree(host->name);
index ba8021df22526c6c38f59c7f7bba66ef0c854c04..67008154b0b7681119dd5334c220f5a6b6c808ec 100644 (file)
 /* DESCRIPTION
 /* .nf
 
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+
  /*
   * External interface.
   */
index 5536c6bb16edbe09f00b1abe8a8f295ae37d6fd2..9467b058eac223691f621594b4cb631d72ceefef 100644 (file)
@@ -133,6 +133,79 @@ struct proxy_hdr_v2 {
 
 #endif                                 /* _HAPROXY_SRVR_INTERNAL_ */
 
+ /*
+  * Binary V2 protocol structure, also needed to create test data.
+  */
+#ifdef HAPROXY_SRVR_INTERNAL
+
+ /*
+  * Begin protocol v2 definitions from haproxy/include/types/connection.h.
+  */
+#define PP2_SIGNATURE          "\r\n\r\n\0\r\nQUIT\n"
+#define PP2_SIGNATURE_LEN      12
+#define PP2_HEADER_LEN         16
+
+/* ver_cmd byte */
+#define PP2_CMD_LOCAL          0x00
+#define PP2_CMD_PROXY          0x01
+#define PP2_CMD_MASK           0x0F
+
+#define PP2_VERSION            0x20
+#define PP2_VERSION_MASK       0xF0
+
+/* fam byte */
+#define PP2_TRANS_UNSPEC       0x00
+#define PP2_TRANS_STREAM       0x01
+#define PP2_TRANS_DGRAM                0x02
+#define PP2_TRANS_MASK         0x0F
+
+#define PP2_FAM_UNSPEC         0x00
+#define PP2_FAM_INET           0x10
+#define PP2_FAM_INET6          0x20
+#define PP2_FAM_UNIX           0x30
+#define PP2_FAM_MASK           0xF0
+
+/* len field (2 bytes) */
+#define PP2_ADDR_LEN_UNSPEC    (0)
+#define PP2_ADDR_LEN_INET      (4 + 4 + 2 + 2)
+#define PP2_ADDR_LEN_INET6     (16 + 16 + 2 + 2)
+#define PP2_ADDR_LEN_UNIX      (108 + 108)
+
+#define PP2_HDR_LEN_UNSPEC     (PP2_HEADER_LEN + PP2_ADDR_LEN_UNSPEC)
+#define PP2_HDR_LEN_INET       (PP2_HEADER_LEN + PP2_ADDR_LEN_INET)
+#define PP2_HDR_LEN_INET6      (PP2_HEADER_LEN + PP2_ADDR_LEN_INET6)
+#define PP2_HDR_LEN_UNIX       (PP2_HEADER_LEN + PP2_ADDR_LEN_UNIX)
+
+struct proxy_hdr_v2 {
+    uint8_t sig[PP2_SIGNATURE_LEN];    /* PP2_SIGNATURE */
+    uint8_t ver_cmd;                   /* protocol version | command */
+    uint8_t fam;                       /* protocol family and transport */
+    uint16_t len;                      /* length of remainder */
+    union {
+       struct {                        /* for TCP/UDP over IPv4, len = 12 */
+           uint32_t src_addr;
+           uint32_t dst_addr;
+           uint16_t src_port;
+           uint16_t dst_port;
+       }       ip4;
+       struct {                        /* for TCP/UDP over IPv6, len = 36 */
+           uint8_t src_addr[16];
+           uint8_t dst_addr[16];
+           uint16_t src_port;
+           uint16_t dst_port;
+       }       ip6;
+       struct {                        /* for AF_UNIX sockets, len = 216 */
+           uint8_t src_addr[108];
+           uint8_t dst_addr[108];
+       }       unx;
+    }       addr;
+};
+
+ /*
+  * End protocol v2 definitions from haproxy/include/types/connection.h.
+  */
+#endif /* HAPROXY_SRVR_INTERNAL */
+
 /* LICENSE
 /* .ad
 /* .fi
index 8dcdd8b58ac8782be989fe89c9d5fdd10093dec6..7f959fdaa3aaac5d1cf01a5117b7141782f52789 100644 (file)
+ /*
+  * Test program to exercise haproxy_srvr.c. See ptest_main.h for a
+  * documented example.
+  */
+
  /*
   * System library.
   */
 #include <sys_defs.h>
-#include <stdlib.h>
+#include <string.h>
 
  /*
   * Utility library.
   */
 #include <msg.h>
-#include <msg_vstream.h>
-#include <myaddrinfo.h>
 #include <sock_addr.h>
-#include <stringops.h>
 #include <vstring.h>
 
  /*
   * Global library.
   */
-#define _HAPROXY_SRVR_INTERNAL_
+#define HAPROXY_SRVR_INTERNAL
 #include <haproxy_srvr.h>
 
  /*
-  * Application-specific.
+  * Test library.
   */
-#define STR_OR_NULL(str) ((str) ? (str) : "(null)")
+#include <ptest.h>
 
  /*
   * Test cases with inputs and expected outputs. A request may contain
   * trailing garbage, and it may be too short. A v1 request may also contain
-  * malformed address or port information. TODO(wietse) add unit test with
-  * different inet_protocols setting. See normalize_v4mapped_addr_test.c.
+  * malformed address or port information.
   */
-typedef struct TEST_CASE {
+typedef struct BASE_TEST_CASE {
     const char *haproxy_request;       /* v1 or v2 request including thrash */
     ssize_t haproxy_req_len;           /* request length including thrash */
-    ssize_t exp_req_len;               /* parsed request length */
-    int     exp_non_proxy;             /* request is not proxied */
-    const char *exp_return;            /* expected error string */
-    const char *exp_client_addr;       /* expected client address string */
-    const char *exp_server_addr;       /* expected client port string */
-    const char *exp_client_port;       /* expected client address string */
-    const char *exp_server_port;       /* expected server port string */
-} TEST_CASE;
-
-static TEST_CASE v1_test_cases[] = {
+    ssize_t want_req_len;              /* parsed request length */
+    int     want_non_proxy;            /* request is not proxied */
+    const char *want_return;           /* expected error string */
+    const char *want_client_addr;      /* expected client address string */
+    const char *want_server_addr;      /* expected client port string */
+    const char *want_client_port;      /* expected client address string */
+    const char *want_server_port;      /* expected server port string */
+} BASE_TEST_CASE;
+
+ /*
+  * Initialize the haproxy_request, haproxy_req_len, and want_req_len fields.
+  */
+#define STRING_AND_LENS(s) (s), (sizeof(s) - 1), (sizeof(s) - 1)
+
+ /*
+  * This table contains V1 protocol test cases that may also be converted
+  * into V2 protocol test cases.
+  */
+static BASE_TEST_CASE v1_test_cases[] = {
     /* IPv6. */
-    {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
-    {"PROXY TCP6 FC:00:00:00:1:2:3:4 FC:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
-    {"PROXY TCP6 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"},
-    {"PROXY TCP6 fc:00:00:00:1:2:3:4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"},
+    {STRING_AND_LENS("PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123 321\n"), 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
+    {STRING_AND_LENS("PROXY TCP6 FC:00:00:00:1:2:3:4 FC:00:00:00:4:3:2:1 123 321\n"), 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
+    {STRING_AND_LENS("PROXY TCP6 1.2.3.4 4.3.2.1 123 321\n"), 0, "bad or missing client address"},
+    {STRING_AND_LENS("PROXY TCP6 fc:00:00:00:1:2:3:4 4.3.2.1 123 321\n"), 0, "bad or missing server address"},
     /* IPv4 in IPv6. */
-    {"PROXY TCP6 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
-    {"PROXY TCP6 ::FFFF:1.2.3.4 ::FFFF:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
-    {"PROXY TCP4 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"},
-    {"PROXY TCP4 1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"},
+    {STRING_AND_LENS("PROXY TCP6 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n"), 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+    {STRING_AND_LENS("PROXY TCP6 ::FFFF:1.2.3.4 ::FFFF:4.3.2.1 123 321\n"), 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+    {STRING_AND_LENS("PROXY TCP4 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n"), 0, "bad or missing client address"},
+    {STRING_AND_LENS("PROXY TCP4 1.2.3.4 ::ffff:4.3.2.1 123 321\n"), 0, "bad or missing server address"},
     /* IPv4. */
-    {"PROXY TCP4 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
-    {"PROXY TCP4 01.02.03.04 04.03.02.01 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
-    {"PROXY TCP4 1.2.3.4 4.3.2.1 123456 321\n", 0, 0, 0, "bad or missing client port"},
-    {"PROXY TCP4 1.2.3.4 4.3.2.1 123 654321\n", 0, 0, 0, "bad or missing server port"},
-    {"PROXY TCP4 1.2.3.4 4.3.2.1 0123 321\n", 0, 0, 0, "bad or missing client port"},
-    {"PROXY TCP4 1.2.3.4 4.3.2.1 123 0321\n", 0, 0, 0, "bad or missing server port"},
+    {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 123 321\n"), 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+    {STRING_AND_LENS("PROXY TCP4 01.02.03.04 04.03.02.01 123 321\n"), 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+    {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 123456 321\n"), 0, "bad or missing client port"},
+    {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 123 654321\n"), 0, "bad or missing server port"},
+    {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 0123 321\n"), 0, "bad or missing client port"},
+    {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 123 0321\n"), 0, "bad or missing server port"},
     /* Missing fields. */
-    {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123\n", 0, 0, 0, "bad or missing server port"},
-    {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1\n", 0, 0, 0, "bad or missing client port"},
-    {"PROXY TCP6 fc:00:00:00:1:2:3:4\n", 0, 0, 0, "bad or missing server address"},
-    {"PROXY TCP6\n", 0, 0, 0, "bad or missing client address"},
-    {"PROXY TCP4 1.2.3.4 4.3.2.1 123\n", 0, 0, 0, "bad or missing server port"},
-    {"PROXY TCP4 1.2.3.4 4.3.2.1\n", 0, 0, 0, "bad or missing client port"},
-    {"PROXY TCP4 1.2.3.4\n", 0, 0, 0, "bad or missing server address"},
-    {"PROXY TCP4\n", 0, 0, 0, "bad or missing client address"},
+    {STRING_AND_LENS("PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123\n"), 0, "bad or missing server port"},
+    {STRING_AND_LENS("PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1\n"), 0, "bad or missing client port"},
+    {STRING_AND_LENS("PROXY TCP6 fc:00:00:00:1:2:3:4\n"), 0, "bad or missing server address"},
+    {STRING_AND_LENS("PROXY TCP6\n"), 0, "bad or missing client address"},
+    {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 123\n"), 0, "bad or missing server port"},
+    {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1\n"), 0, "bad or missing client port"},
+    {STRING_AND_LENS("PROXY TCP4 1.2.3.4\n"), 0, "bad or missing server address"},
+    {STRING_AND_LENS("PROXY TCP4\n"), 0, "bad or missing client address"},
     /* Other. */
-    {"PROXY BLAH\n", 0, 0, 0, "bad or missing protocol type"},
-    {"PROXY\n", 0, 0, 0, "short protocol header"},
+    {STRING_AND_LENS("PROXY BLAH\n"), 0, "bad or missing protocol type"},
+    {STRING_AND_LENS("PROXY\n"), 0, "short protocol header"},
     {"BLAH\n", 0, 0, 0, "short protocol header"},
     {"\n", 0, 0, 0, "short protocol header"},
     {"", 0, 0, 0, "short protocol header"},
     0,
 };
 
+ /*
+  * Limited V2-only tests. XXX Should we add error cases? Several errors are
+  * already tested with mutations of V2 handshakes that were generated from
+  * V1 handshakes.
+  */
 static struct proxy_hdr_v2 v2_local_request = {
     PP2_SIGNATURE, PP2_VERSION | PP2_CMD_LOCAL,
 };
-static TEST_CASE v2_non_proxy_test = {
+static BASE_TEST_CASE v2_non_proxy_test = {
     (char *) &v2_local_request, PP2_HEADER_LEN, PP2_HEADER_LEN, 1,
 };
 
-#define STR(x) vstring_str(x)
-#define LEN(x) VSTRING_LEN(x)
+#define STR_OR_NULL(s) ((s) ? (s) : "(null)")
+#define STR(x)         vstring_str(x)
+#define LEN(x)         VSTRING_LEN(x)
 
-#define DO_SOCKADDR_OUTPUT     1
-#define NO_SOCKADDR_OUTPUT     0
+#define DO_SOCKADDR_OUTPUT      1
+#define NO_SOCKADDR_OUTPUT      0
 
-static int evaluate_sockaddr(const char *which, struct sockaddr *sa,
-                               SOCKADDR_SIZE sa_len, const char *want_addr,
-                                    const char *want_port)
+static void evaluate_sockaddr(PTEST_CTX *t, const char *which,
+                                     struct sockaddr *sa,
+                                     SOCKADDR_SIZE sa_len,
+                                     const char *want_addr,
+                                     const char *want_port)
 {
-    MAI_HOSTADDR_STR act_addr;
-    MAI_SERVPORT_STR act_port;
+    MAI_HOSTADDR_STR got_addr;
+    MAI_SERVPORT_STR got_port;
     int     err;
-    int     failed = 0;
 
-    if ((err = sockaddr_to_hostaddr(sa, sa_len, &act_addr, &act_port, 0)) != 0) {
-       msg_warn("sockaddr_to_hostaddr: %s", MAI_STRERROR(err));
-       return (1);
+    if ((err = sockaddr_to_hostaddr(sa, sa_len, &got_addr, &got_port, 0)) != 0) {
+       ptest_error(t, "sockaddr_to_hostaddr: %s", MAI_STRERROR(err));
+       return;
     }
-    if (strcmp(act_addr.buf, want_addr) != 0) {
-       msg_warn("got %s address '%s', want '%s'",
-                which, act_addr.buf, want_addr);
-       failed = 1;
+    if (strcmp(got_addr.buf, want_addr) != 0) {
+       ptest_error(t, "%s address got='%s', want='%s'",
+                   which, got_addr.buf, want_addr);
     }
-    if (strcmp(act_port.buf, want_port) != 0) {
-       msg_warn("got %s port '%s', want '%s'",
-                which, act_port.buf, want_port);
-       failed = 1;
+    if (strcmp(got_port.buf, want_port) != 0) {
+       ptest_error(t, "%s port got='%s', want='%s'",
+                   which, got_port.buf, want_port);
     }
-    return (failed);
 }
 
-/* evaluate_test_case - evaluate one base or mutated test case */
+/* evaluate_test_case - evaluate one test case (base or mutation) */
 
-static int evaluate_test_case(const char *test_label,
-                                     const TEST_CASE *test_case,
-                                     int want_sockaddr_output)
+static void evaluate_test_case(PTEST_CTX *t, const char *test_label,
+                                      const BASE_TEST_CASE *test_case,
+                                      int want_sockaddr_output)
 {
-    /* Actual results. */
-    const char *act_return;
-    ssize_t act_req_len;
-    int     act_non_proxy;
-    MAI_HOSTADDR_STR act_smtp_client_addr;
-    MAI_HOSTADDR_STR act_smtp_server_addr;
-    MAI_SERVPORT_STR act_smtp_client_port;
-    MAI_SERVPORT_STR act_smtp_server_port;
-    int     test_failed;
-
-    struct sockaddr_storage client_ss;
-    struct sockaddr *client_sa;
-    SOCKADDR_SIZE client_sa_len;
-
-    struct sockaddr_storage server_ss;
-    struct sockaddr *server_sa;
-    SOCKADDR_SIZE server_sa_len;
-
-    if (msg_verbose)
-       msg_info("test case=%s exp_client_addr=%s exp_server_addr=%s "
-                "exp_client_port=%s exp_server_port=%s",
-                test_label, STR_OR_NULL(test_case->exp_client_addr),
-                STR_OR_NULL(test_case->exp_server_addr),
-                STR_OR_NULL(test_case->exp_client_port),
-                STR_OR_NULL(test_case->exp_server_port));
-
-    if (want_sockaddr_output) {
-       client_sa = (struct sockaddr *) &client_ss;
-       client_sa_len = sizeof(client_ss);
-       server_sa = (struct sockaddr *) &server_ss;
-       server_sa_len = sizeof(server_ss);
-    } else {
-       client_sa = 0;
-       client_sa_len = 0;
-       server_sa = 0;
-       server_sa_len = 0;
-    }
+    PTEST_RUN(t, test_label, {
+       const char *got_return;
+       ssize_t got_req_len;
+       int     got_non_proxy;
+       MAI_HOSTADDR_STR got_smtp_client_addr;
+       MAI_HOSTADDR_STR got_smtp_server_addr;
+       MAI_SERVPORT_STR got_smtp_client_port;
+       MAI_SERVPORT_STR got_smtp_server_port;
+
+       struct sockaddr_storage client_ss;
+       struct sockaddr *client_sa;
+       SOCKADDR_SIZE client_sa_len;
+
+       struct sockaddr_storage server_ss;
+       struct sockaddr *server_sa;
+       SOCKADDR_SIZE server_sa_len;
+
+       if (want_sockaddr_output) {
+           client_sa = (struct sockaddr *) &client_ss;
+           client_sa_len = sizeof(client_ss);
+           server_sa = (struct sockaddr *) &server_ss;
+           server_sa_len = sizeof(server_ss);
+       } else {
+           client_sa = 0;
+           client_sa_len = 0;
+           server_sa = 0;
+           server_sa_len = 0;
+       }
 
-    /*
-     * Start the test.
-     */
-    test_failed = 0;
-    act_req_len = test_case->haproxy_req_len;
-    act_return =
-       haproxy_srvr_parse_sa(test_case->haproxy_request, &act_req_len,
-                             &act_non_proxy,
-                             &act_smtp_client_addr, &act_smtp_client_port,
-                             &act_smtp_server_addr, &act_smtp_server_port,
-                             client_sa, &client_sa_len,
-                             server_sa, &server_sa_len);
-    if (act_return != test_case->exp_return
-    && strcmp(STR_OR_NULL(act_return), STR_OR_NULL(test_case->exp_return))) {
-       msg_warn("test case %s return expected=>%s< actual=>%s<",
-                test_label, STR_OR_NULL(test_case->exp_return),
-                STR_OR_NULL(act_return));
-       test_failed = 1;
-       return (test_failed);
-    }
-    if (act_req_len != test_case->exp_req_len) {
-       msg_warn("test case %s str_len expected=%ld actual=%ld",
-                test_label,
-                (long) test_case->exp_req_len, (long) act_req_len);
-       test_failed = 1;
-       return (test_failed);
-    }
-    if (act_non_proxy != test_case->exp_non_proxy) {
-       msg_warn("test case %s non_proxy expected=%d actual=%d",
-                test_label,
-                test_case->exp_non_proxy, act_non_proxy);
-       test_failed = 1;
-       return (test_failed);
-    }
-    if (test_case->exp_non_proxy || test_case->exp_return != 0)
-       /* No expected address/port results. */
-       return (test_failed);
+       if (msg_verbose)
+           msg_info("test case=%s want_client_addr=%s want_server_addr=%s "
+                    "want_client_port=%s want_server_port=%s",
+                    test_label, STR_OR_NULL(test_case->want_client_addr),
+                    STR_OR_NULL(test_case->want_server_addr),
+                    STR_OR_NULL(test_case->want_client_port),
+                    STR_OR_NULL(test_case->want_server_port));
+       /* TODO(wietse) add client_sa/server_sa args */
 
-    /*
-     * Compare address/port results against expected results.
-     */
-    if (strcmp(test_case->exp_client_addr, act_smtp_client_addr.buf)) {
-       msg_warn("test case %s client_addr  expected=%s actual=%s",
-                test_label,
-                test_case->exp_client_addr, act_smtp_client_addr.buf);
-       test_failed = 1;
-    }
-    if (strcmp(test_case->exp_server_addr, act_smtp_server_addr.buf)) {
-       msg_warn("test case %s server_addr  expected=%s actual=%s",
-                test_label,
-                test_case->exp_server_addr, act_smtp_server_addr.buf);
-       test_failed = 1;
-    }
-    if (strcmp(test_case->exp_client_port, act_smtp_client_port.buf)) {
-       msg_warn("test case %s client_port  expected=%s actual=%s",
-                test_label,
-                test_case->exp_client_port, act_smtp_client_port.buf);
-       test_failed = 1;
-    }
-    if (strcmp(test_case->exp_server_port, act_smtp_server_port.buf)) {
-       msg_warn("test case %s server_port  expected=%s actual=%s",
-                test_label,
-                test_case->exp_server_port, act_smtp_server_port.buf);
-       test_failed = 1;
-    }
-    if (want_sockaddr_output) {
-       if (evaluate_sockaddr("client", client_sa, client_sa_len,
-                             test_case->exp_client_addr,
-                             test_case->exp_client_port) != 0
-           || evaluate_sockaddr("server", server_sa, server_sa_len,
-                                test_case->exp_server_addr,
-                                test_case->exp_server_port) != 0)
-           test_failed = 1;
-    }
-    return (test_failed);
+       /*
+        * Start the test.
+        */
+       got_req_len = test_case->haproxy_req_len;
+       got_return =
+           haproxy_srvr_parse_sa(test_case->haproxy_request, &got_req_len,
+                                 &got_non_proxy,
+                              &got_smtp_client_addr, &got_smtp_client_port,
+                              &got_smtp_server_addr, &got_smtp_server_port,
+                                 client_sa, &client_sa_len,
+                                 server_sa, &server_sa_len);
+       if (strcmp(STR_OR_NULL(got_return), STR_OR_NULL(test_case->want_return))) {
+           ptest_error(t, "haproxy_srvr_parse return got=%s want=%s",
+                       STR_OR_NULL(got_return),
+                       STR_OR_NULL(test_case->want_return));
+           ptest_return(t);
+       }
+       if (got_req_len != test_case->want_req_len) {
+           ptest_error(t, "haproxy_srvr_parse str_len got=%ld want=%ld",
+                       (long) got_req_len,
+                       (long) test_case->want_req_len);
+           ptest_return(t);
+       }
+       if (got_non_proxy != test_case->want_non_proxy) {
+           ptest_error(t, "haproxy_srvr_parse non_proxy got=%d want=%d",
+                       got_non_proxy,
+                       test_case->want_non_proxy);
+           ptest_return(t);
+       }
+       if (test_case->want_non_proxy || test_case->want_return != 0)
+           /* No expected address/port results. */
+           ptest_return(t);
+
+       /*
+        * Compare address/port results against expected results.
+        */
+       if (strcmp(test_case->want_client_addr, got_smtp_client_addr.buf)) {
+           ptest_error(t, "haproxy_srvr_parse client_addr got=%s want=%s",
+                       got_smtp_client_addr.buf,
+                       test_case->want_client_addr);
+       }
+       if (strcmp(test_case->want_server_addr, got_smtp_server_addr.buf)) {
+           ptest_error(t, "haproxy_srvr_parse server_addr got=%s want=%s",
+                       got_smtp_server_addr.buf,
+                       test_case->want_server_addr);
+       }
+       if (strcmp(test_case->want_client_port, got_smtp_client_port.buf)) {
+           ptest_error(t, "haproxy_srvr_parse client_port got=%s want=%s",
+                       got_smtp_client_port.buf,
+                       test_case->want_client_port);
+       }
+       if (strcmp(test_case->want_server_port, got_smtp_server_port.buf)) {
+           ptest_error(t, "haproxy_srvr_parse server_port got=%s want=%s",
+                       got_smtp_server_port.buf,
+                       test_case->want_server_port);
+       }
+       if (want_sockaddr_output) {
+           evaluate_sockaddr(t, "client", client_sa, client_sa_len,
+                             test_case->want_client_addr,
+                             test_case->want_client_port);
+           evaluate_sockaddr(t, "server", server_sa, server_sa_len,
+                             test_case->want_server_addr,
+                             test_case->want_server_port);
+       }
+    });
 }
 
 /* convert_v1_proxy_req_to_v2 - convert well-formed v1 proxy request to v2 */
 
-static void convert_v1_proxy_req_to_v2(VSTRING *buf, const char *req,
-                                              ssize_t req_len)
+static void convert_v1_proxy_req_to_v2(PTEST_CTX *t, VSTRING *buf,
+                                          const char *req, ssize_t req_len)
 {
     const char myname[] = "convert_v1_proxy_req_to_v2";
     const char *err;
@@ -271,19 +275,19 @@ static void convert_v1_proxy_req_to_v2(VSTRING *buf, const char *req,
     if ((err = haproxy_srvr_parse(req, &req_len, &non_proxy, &smtp_client_addr,
                                  &smtp_client_port, &smtp_server_addr,
                                  &smtp_server_port)) != 0 || non_proxy)
-       msg_fatal("%s: malformed or non-proxy request: %s",
-                 myname, req);
+       ptest_fatal(t, "%s: malformed or non-proxy request: %s",
+                   myname, req);
 
     if (hostaddr_to_sockaddr(smtp_client_addr.buf, smtp_client_port.buf, 0,
                             &src_res) != 0)
-       msg_fatal("%s: unable to convert source address %s port %s",
-                 myname, smtp_client_addr.buf, smtp_client_port.buf);
+       ptest_fatal(t, "%s: unable to convert source address %s port %s",
+                   myname, smtp_client_addr.buf, smtp_client_port.buf);
     if (hostaddr_to_sockaddr(smtp_server_addr.buf, smtp_server_port.buf, 0,
                             &dst_res) != 0)
-       msg_fatal("%s: unable to convert destination address %s port %s",
-                 myname, smtp_server_addr.buf, smtp_server_port.buf);
+       ptest_fatal(t, "%s: unable to convert destination address %s port %s",
+                   myname, smtp_server_addr.buf, smtp_server_port.buf);
     if (src_res->ai_family != dst_res->ai_family)
-       msg_fatal("%s: mixed source/destination address families", myname);
+       ptest_fatal(t, "%s: mixed source/destination address families", myname);
 #ifdef AF_INET6
     if (src_res->ai_family == PF_INET6) {
        hdr_v2->fam = PP2_FAM_INET6 | PP2_TRANS_STREAM;
@@ -306,229 +310,173 @@ static void convert_v1_proxy_req_to_v2(VSTRING *buf, const char *req,
        hdr_v2->addr.ip4.dst_addr = SOCK_ADDR_IN_ADDR(dst_res->ai_addr).s_addr;
        hdr_v2->addr.ip4.dst_port = SOCK_ADDR_IN_PORT(dst_res->ai_addr);
     } else {
-       msg_panic("unknown address family 0x%x", src_res->ai_family);
+       ptest_fatal(t, "unknown address family 0x%x", src_res->ai_family);
     }
     vstring_set_payload_size(buf, PP2_SIGNATURE_LEN + ntohs(hdr_v2->len));
     freeaddrinfo(src_res);
     freeaddrinfo(dst_res);
 }
 
-int     main(int argc, char **argv)
+static void run_test_variants(PTEST_CTX *t, BASE_TEST_CASE *v1_test_case)
 {
     VSTRING *test_label;
-    TEST_CASE *v1_test_case;
-    TEST_CASE v2_test_case;
-    TEST_CASE mutated_test_case;
+    BASE_TEST_CASE v2_test_case;
+    BASE_TEST_CASE mutated_test_case;
     VSTRING *v2_request_buf;
     VSTRING *mutated_request_buf;
 
-    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
-
-    /* Findings. */
-    int     tests_failed = 0;
-    int     tests_passed = 0;
-
     test_label = vstring_alloc(100);
     v2_request_buf = vstring_alloc(100);
     mutated_request_buf = vstring_alloc(100);
 
     /*
-     * Evaluate each case in the test case list. If the test input is
-     * well-formed, also test a number of mutations based on that test,
-     * before proceeding with the next test case in the list.
+     * Evaluate each v1 test case.
      */
-    for (tests_failed = 0, tests_passed = 0, v1_test_case = v1_test_cases;
-        v1_test_case->haproxy_request != 0; v1_test_case++) {
+    vstring_sprintf(test_label, "v1 baseline (%sformed)",
+                   v1_test_case->want_return ? "mal" : "well");
+    evaluate_test_case(t, STR(test_label), v1_test_case,
+                      NO_SOCKADDR_OUTPUT);
 
-       /*
-        * Fill in missing string length info in v1 test data.
-        */
-       if (v1_test_case->haproxy_req_len == 0)
-           v1_test_case->haproxy_req_len =
-               strlen(v1_test_case->haproxy_request);
-       if (v1_test_case->exp_req_len == 0)
-           v1_test_case->exp_req_len = v1_test_case->haproxy_req_len;
+    /*
+     * If the v1 test input is malformed, skip the mutation tests.
+     */
+    if (v1_test_case->want_return != 0) {
+       vstring_free(v2_request_buf);
+       vstring_free(mutated_request_buf);
+       vstring_free(test_label);
+       ptest_return(t);
+    }
 
-       /*
-        * Evaluate each v1 test case.
-        */
-       vstring_sprintf(test_label, "%d (%s%.5s input)",
-                       (int) (v1_test_case - v1_test_cases),
-                    v1_test_case->exp_return ? "malformed" : "well-formed",
-        v1_test_case->exp_return ? "" : v1_test_case->haproxy_request + 5);
-       msg_info("RUN  %s", STR(test_label));
-       if (evaluate_test_case(STR(test_label), v1_test_case,
-                              NO_SOCKADDR_OUTPUT)) {
-           msg_info("FAIL %s", STR(test_label));
-           tests_failed += 1;
-       } else {
-           msg_info("PASS %s", STR(test_label));
-           tests_passed += 1;
-       }
+    /*
+     * Mutation test: a well-formed v1 test case should also pass with output
+     * to sockaddr arguments.
+     */
+    vstring_strcat(test_label, "(with sockaddr output)");
+    evaluate_test_case(t, STR(test_label), v1_test_case,
+                      DO_SOCKADDR_OUTPUT);
 
-       /*
-        * If the v1 test input is malformed, skip the mutation tests.
-        */
-       if (v1_test_case->exp_return != 0)
-           continue;
+    /*
+     * Mutation test: a well-formed v1 test case should still pass after
+     * appending a byte, and should return the actual parsed header length.
+     * The test uses the implicit VSTRING null safety byte.
+     */
+    vstring_sprintf(test_label, "v1 mutated (one byte appended)");
+    mutated_test_case = *v1_test_case;
+    mutated_test_case.haproxy_req_len += 1;
+    /* reuse v1_test_case->want_req_len */
+    evaluate_test_case(t, STR(test_label), &mutated_test_case,
+                      NO_SOCKADDR_OUTPUT);
 
-       /*
-        * Mutation test: a well-formed v1 test case should also pass with
-        * output to sockaddr arguments.
-        */
-       vstring_sprintf(test_label, "%d (with sockaddr output)",
-                       (int) (v1_test_case - v1_test_cases));
-       msg_info("RUN  %s", STR(test_label));
-       if (evaluate_test_case(STR(test_label), v1_test_case,
-                              DO_SOCKADDR_OUTPUT)) {
-           msg_info("FAIL %s", STR(test_label));
-           tests_failed += 1;
-       } else {
-           msg_info("PASS %s", STR(test_label));
-           tests_passed += 1;
-       }
+    /*
+     * Mutation test: a well-formed v1 test case should fail after stripping
+     * the terminator.
+     */
+    vstring_sprintf(test_label, "v1 mutated (last byte stripped)");
+    mutated_test_case = *v1_test_case;
+    mutated_test_case.want_return = "missing protocol header terminator";
+    mutated_test_case.haproxy_req_len -= 1;
+    mutated_test_case.want_req_len = mutated_test_case.haproxy_req_len;
+    evaluate_test_case(t, STR(test_label), &mutated_test_case,
+                      NO_SOCKADDR_OUTPUT);
 
-       /*
-        * Mutation test: a well-formed v1 test case should still pass after
-        * appending a byte, and should return the actual parsed header
-        * length. The test uses the implicit VSTRING null safety byte.
-        */
-       vstring_sprintf(test_label, "%d (one byte appended)",
-                       (int) (v1_test_case - v1_test_cases));
-       mutated_test_case = *v1_test_case;
-       mutated_test_case.haproxy_req_len += 1;
-       msg_info("RUN  %s", STR(test_label));
-       /* reuse v1_test_case->exp_req_len */
-       if (evaluate_test_case(STR(test_label), &mutated_test_case,
-                              NO_SOCKADDR_OUTPUT)) {
-           msg_info("FAIL %s", STR(test_label));
-           tests_failed += 1;
-       } else {
-           msg_info("PASS %s", STR(test_label));
-           tests_passed += 1;
-       }
+    /*
+     * A 'well-formed' v1 test case should pass after conversion to v2.
+     */
+    vstring_sprintf(test_label, "v2 baseline (converted from v1)");
+    v2_test_case = *v1_test_case;
+    convert_v1_proxy_req_to_v2(t, v2_request_buf,
+                              v1_test_case->haproxy_request,
+                              v1_test_case->haproxy_req_len);
+    v2_test_case.haproxy_request = STR(v2_request_buf);
+    v2_test_case.haproxy_req_len = PP2_HEADER_LEN
+       + ntohs(((struct proxy_hdr_v2 *) STR(v2_request_buf))->len);
+    v2_test_case.want_req_len = v2_test_case.haproxy_req_len;
+    evaluate_test_case(t, STR(test_label), &v2_test_case,
+                      NO_SOCKADDR_OUTPUT);
 
-       /*
-        * Mutation test: a well-formed v1 test case should fail after
-        * stripping the terminator.
-        */
-       vstring_sprintf(test_label, "%d (last byte stripped)",
-                       (int) (v1_test_case - v1_test_cases));
-       mutated_test_case = *v1_test_case;
-       mutated_test_case.exp_return = "missing protocol header terminator";
-       mutated_test_case.haproxy_req_len -= 1;
-       mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len;
-       msg_info("RUN  %s", STR(test_label));
-       if (evaluate_test_case(STR(test_label), &mutated_test_case,
-                              NO_SOCKADDR_OUTPUT)) {
-           msg_info("FAIL %s", STR(test_label));
-           tests_failed += 1;
-       } else {
-           msg_info("PASS %s", STR(test_label));
-           tests_passed += 1;
-       }
+    /*
+     * Mutation test: a well-formed v2 test case should also pass with output
+     * to sockaddr arguments.
+     */
+    vstring_strcat(test_label, " (with sockaddr output)");
+    evaluate_test_case(t, STR(test_label), &v2_test_case,
+                      DO_SOCKADDR_OUTPUT);
 
-       /*
-        * A 'well-formed' v1 test case should pass after conversion to v2.
-        */
-       vstring_sprintf(test_label, "%d (converted to v2)",
-                       (int) (v1_test_case - v1_test_cases));
-       v2_test_case = *v1_test_case;
-       convert_v1_proxy_req_to_v2(v2_request_buf,
-                                  v1_test_case->haproxy_request,
-                                  v1_test_case->haproxy_req_len);
-       v2_test_case.haproxy_request = STR(v2_request_buf);
-       v2_test_case.haproxy_req_len = PP2_HEADER_LEN
-           + ntohs(((struct proxy_hdr_v2 *) STR(v2_request_buf))->len);
-       v2_test_case.exp_req_len = v2_test_case.haproxy_req_len;
-       msg_info("RUN  %s", STR(test_label));
-       if (evaluate_test_case(STR(test_label), &v2_test_case,
-                              NO_SOCKADDR_OUTPUT)) {
-           msg_info("FAIL %s", STR(test_label));
-           tests_failed += 1;
-       } else {
-           msg_info("PASS %s", STR(test_label));
-           tests_passed += 1;
-       }
+    /*
+     * Mutation test: a well-formed v2 test case should still pass after
+     * appending a byte, and should return the actual parsed header length.
+     * The test uses the implicit VSTRING null safety byte.
+     */
+    vstring_sprintf(test_label, "v2 mutated (one byte appended)");
+    mutated_test_case = v2_test_case;
+    mutated_test_case.haproxy_req_len += 1;
+    /* reuse v2_test_case->want_req_len */
+    evaluate_test_case(t, STR(test_label), &mutated_test_case,
+                      NO_SOCKADDR_OUTPUT);
 
-       /*
-        * Mutation test: a well-formed v2 test case should also pass with
-        * output to sockaddr arguments.
-        */
-       vstring_sprintf(test_label,
-                       "%d (converted to v2, with sockaddr output)",
-                       (int) (v1_test_case - v1_test_cases));
-       msg_info("RUN  %s", STR(test_label));
-       if (evaluate_test_case(STR(test_label), &v2_test_case,
-                              DO_SOCKADDR_OUTPUT)) {
-           msg_info("FAIL %s", STR(test_label));
-           tests_failed += 1;
-       } else {
-           msg_info("PASS %s", STR(test_label));
-           tests_passed += 1;
-       }
+    /*
+     * Mutation test: a well-formed v2 test case should fail after stripping
+     * one byte
+     */
+    vstring_sprintf(test_label, "v2 mutated (last byte stripped)");
+    mutated_test_case = v2_test_case;
+    mutated_test_case.haproxy_req_len -= 1;
+    mutated_test_case.want_req_len = mutated_test_case.haproxy_req_len;
+    mutated_test_case.want_return = "short version 2 protocol header";
+    evaluate_test_case(t, STR(test_label), &mutated_test_case,
+                      NO_SOCKADDR_OUTPUT);
 
-       /*
-        * Mutation test: a well-formed v2 test case should still pass after
-        * appending a byte, and should return the actual parsed header
-        * length. The test uses the implicit VSTRING null safety byte.
-        */
-       vstring_sprintf(test_label, "%d (converted to v2, one byte appended)",
-                       (int) (v1_test_case - v1_test_cases));
-       mutated_test_case = v2_test_case;
-       mutated_test_case.haproxy_req_len += 1;
-       /* reuse v2_test_case->exp_req_len */
-       msg_info("RUN  %s", STR(test_label));
-       if (evaluate_test_case(STR(test_label), &mutated_test_case,
-                              NO_SOCKADDR_OUTPUT)) {
-           msg_info("FAIL %s", STR(test_label));
-           tests_failed += 1;
-       } else {
-           msg_info("PASS %s", STR(test_label));
-           tests_passed += 1;
-       }
+    /*
+     * Clean up.
+     */
+    vstring_free(v2_request_buf);
+    vstring_free(mutated_request_buf);
+    vstring_free(test_label);
+}
 
-       /*
-        * Mutation test: a well-formed v2 test case should fail after
-        * stripping one byte
-        */
-       vstring_sprintf(test_label, "%d (converted to v2, last byte stripped)",
-                       (int) (v1_test_case - v1_test_cases));
-       mutated_test_case = v2_test_case;
-       mutated_test_case.haproxy_req_len -= 1;
-       mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len;
-       mutated_test_case.exp_return = "short version 2 protocol header";
-       msg_info("RUN  %s", STR(test_label));
-       if (evaluate_test_case(STR(test_label), &mutated_test_case,
-                              NO_SOCKADDR_OUTPUT)) {
-           msg_info("FAIL %s", STR(test_label));
-           tests_failed += 1;
-       } else {
-           msg_info("PASS %s", STR(test_label));
-           tests_passed += 1;
-       }
-    }
+ /*
+  * PTEST adapter.
+  */
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+static void run_proxy_tests(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    BASE_TEST_CASE *v1_test_case;
+    VSTRING *test_label;
 
     /*
-     * Additional V2-only tests.
+     * Run all variants of one base case test together in a subtest.
      */
-    vstring_strcpy(test_label, "v2 non-proxy request");
-    msg_info("RUN  %s", STR(test_label));
-    if (evaluate_test_case("v2 non-proxy request", &v2_non_proxy_test,
-                          NO_SOCKADDR_OUTPUT)) {
-       msg_info("FAIL %s", STR(test_label));
-       tests_failed += 1;
-    } else {
-       msg_info("PASS %s", STR(test_label));
-       tests_passed += 1;
+    test_label = vstring_alloc(100);
+    for (v1_test_case = v1_test_cases;
+        v1_test_case->haproxy_request != 0; v1_test_case++) {
+       vstring_sprintf(test_label, "%d",
+                       1 + (int) (v1_test_case - v1_test_cases));
+       PTEST_RUN(t, STR(test_label), {
+           run_test_variants(t, v1_test_case);
+       });
     }
 
     /*
-     * Clean up.
+     * Additional V2-only test.
      */
-    vstring_free(v2_request_buf);
-    vstring_free(mutated_request_buf);
+    vstring_sprintf(test_label, "%d",
+                   1 + (int) (v1_test_case - v1_test_cases));
+    PTEST_RUN(t, STR(test_label), {
+       evaluate_test_case(t, "v2 non-proxy request", &v2_non_proxy_test,
+                          DO_SOCKADDR_OUTPUT);
+    });
     vstring_free(test_label);
-    msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed);
-    exit(tests_failed != 0);
 }
+
+ /*
+  * PTEST adapter.
+  */
+static const PTEST_CASE ptestcases[] = {
+    "haproxy_srvr_test", run_proxy_tests,
+};
+
+#include <ptest_main.h>
index f0f850a3e5e9fc718abedf5e00294e8cc879f571..a0122fa9627c936e833ee87bf64ac4261fefe989 100644 (file)
@@ -91,191 +91,3 @@ const char *str_hfrom_format_code(int code)
        msg_fatal("invalid header format code: %d", code);
     return (name);
 }
-
-#ifdef TEST
-#include <stdlib.h>
-#include <setjmp.h>
-#include <string.h>
-
-#include <vstream.h>
-#include <vstring.h>
-#include <msg_vstream.h>
-
-#define STR(x) vstring_str(x)
-
- /*
-  * TODO(wietse) make this a proper VSTREAM interface. Instead of temporarily
-  * swapping streams, we could temporarily swap the stream's write function.
-  */
-
-/* vstream_swap - kludge to capture output for testing */
-
-static void vstream_swap(VSTREAM *one, VSTREAM *two)
-{
-    VSTREAM save;
-
-    save = *one;
-    *one = *two;
-    *two = save;
-}
-
-jmp_buf test_fatal_jbuf;
-
-#undef msg_fatal
-
-/* test_msg_fatal - does not return, and does not terminate */
-
-void    test_msg_fatal(const char *fmt,...)
-{
-    va_list ap;
-
-    va_start(ap, fmt);
-    vmsg_warn(fmt, ap);
-    va_end(ap);
-    longjmp(test_fatal_jbuf, 1);
-}
-
-struct name_test_case {
-    const char *label;                 /* identifies test case */
-    const char *config;                        /* configuration under test */
-    const char *exp_warning;           /* expected warning or empty */
-    const int exp_code;                        /* expected code */
-};
-
-static struct name_test_case name_test_cases[] = {
-    {"hfrom_format_parse good-standard",
-        /* config */ HFROM_FORMAT_NAME_STD,
-        /* warning */ "",
-        /* exp_code */ HFROM_FORMAT_CODE_STD
-    },
-    {"hfrom_format_parse good-obsolete",
-        /* config */ HFROM_FORMAT_NAME_OBS,
-        /* warning */ "",
-        /* exp_code */ HFROM_FORMAT_CODE_OBS
-    },
-    {"hfrom_format_parse bad",
-        /* config */ "does-not-exist",
-        /* warning */ "hfrom_format: warning: invalid setting: \"hfrom_format_parse bad = does-not-exist\"\n",
-        /* code */ 0,
-    },
-    {"hfrom_format_parse empty",
-        /* config */ "",
-        /* warning */ "hfrom_format: warning: invalid setting: \"hfrom_format_parse empty = \"\n",
-        /* code */ 0,
-    },
-    0,
-};
-
-struct code_test_case {
-    const char *label;                 /* identifies test case */
-    int     code;                      /* code under test */
-    const char *exp_warning;           /* expected warning or empty */
-    const char *exp_name;              /* expected namme */
-};
-
-static struct code_test_case code_test_cases[] = {
-    {"str_hfrom_format_code good-standard",
-        /* code */ HFROM_FORMAT_CODE_STD,
-        /* warning */ "",
-        /* exp_name */ HFROM_FORMAT_NAME_STD
-    },
-    {"str_hfrom_format_code good-obsolete",
-        /* code */ HFROM_FORMAT_CODE_OBS,
-        /* warning */ "",
-        /* exp_name */ HFROM_FORMAT_NAME_OBS
-    },
-    {"str_hfrom_format_code bad",
-        /* config */ 12345,
-        /* warning */ "hfrom_format: warning: invalid header format code: 12345\n",
-        /* exp_name */ 0
-    },
-    0,
-};
-
-int     main(int argc, char **argv)
-{
-    struct name_test_case *np;
-    int     code;
-    struct code_test_case *cp;
-    const char *name;
-    int     pass = 0;
-    int     fail = 0;
-    int     test_failed;
-    VSTRING *msg_buf;
-    VSTREAM *memory_stream;
-
-    msg_vstream_init("hfrom_format", VSTREAM_ERR);
-    msg_buf = vstring_alloc(100);
-
-    for (np = name_test_cases; np->label != 0; np++) {
-       VSTRING_RESET(msg_buf);
-       VSTRING_TERMINATE(msg_buf);
-       test_failed = 0;
-       if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
-           msg_fatal("open memory stream: %m");
-       vstream_swap(VSTREAM_ERR, memory_stream);
-       if (setjmp(test_fatal_jbuf) == 0)
-           code = hfrom_format_parse(np->label, np->config);
-       vstream_swap(memory_stream, VSTREAM_ERR);
-       if (vstream_fclose(memory_stream))
-           msg_fatal("close memory stream: %m");
-       if (strcmp(STR(msg_buf), np->exp_warning) != 0) {
-           msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
-                    np->label, STR(msg_buf), np->exp_warning);
-           test_failed = 1;
-       }
-       if (*np->exp_warning == 0) {
-           if (code != np->exp_code) {
-               msg_warn("test case %s: got code: \"%d\", want: \"%d\"(%s)",
-                        np->label, code, np->exp_code,
-                        str_hfrom_format_code(np->exp_code));
-               test_failed = 1;
-           }
-       }
-       if (test_failed) {
-           msg_info("%s: FAIL", np->label);
-           fail++;
-       } else {
-           msg_info("%s: PASS", np->label);
-           pass++;
-       }
-    }
-
-    for (cp = code_test_cases; cp->label != 0; cp++) {
-       VSTRING_RESET(msg_buf);
-       VSTRING_TERMINATE(msg_buf);
-       test_failed = 0;
-       if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
-           msg_fatal("open memory stream: %m");
-       vstream_swap(VSTREAM_ERR, memory_stream);
-       if (setjmp(test_fatal_jbuf) == 0)
-           name = str_hfrom_format_code(cp->code);
-       vstream_swap(memory_stream, VSTREAM_ERR);
-       if (vstream_fclose(memory_stream))
-           msg_fatal("close memory stream: %m");
-       if (strcmp(STR(msg_buf), cp->exp_warning) != 0) {
-           msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
-                    cp->label, STR(msg_buf), cp->exp_warning);
-           test_failed = 1;
-       } else if (*cp->exp_warning == 0) {
-           if (strcmp(name, cp->exp_name)) {
-               msg_warn("test case %s: got name: \"%s\", want: \"%s\"",
-                        cp->label, name, cp->exp_name);
-               test_failed = 1;
-           }
-       }
-       if (test_failed) {
-           msg_info("%s: FAIL", cp->label);
-           fail++;
-       } else {
-           msg_info("%s: PASS", cp->label);
-           pass++;
-       }
-    }
-
-    msg_info("PASS=%d FAIL=%d", pass, fail);
-    vstring_free(msg_buf);
-    exit(fail != 0);
-}
-
-#endif
diff --git a/postfix/src/global/hfrom_format.ref b/postfix/src/global/hfrom_format.ref
deleted file mode 100644 (file)
index 28ba870..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-hfrom_format: hfrom_format_parse good-standard: PASS
-hfrom_format: hfrom_format_parse good-obsolete: PASS
-hfrom_format: hfrom_format_parse bad: PASS
-hfrom_format: hfrom_format_parse empty: PASS
-hfrom_format: str_hfrom_format_code good-standard: PASS
-hfrom_format: str_hfrom_format_code good-obsolete: PASS
-hfrom_format: str_hfrom_format_code bad: PASS
-hfrom_format: PASS=7 FAIL=0
diff --git a/postfix/src/global/hfrom_format_test.c b/postfix/src/global/hfrom_format_test.c
new file mode 100644 (file)
index 0000000..9a6fe2e
--- /dev/null
@@ -0,0 +1,139 @@
+ /*
+  * Test program to exercise hfrom_format.c. See ptest_main.h for a
+  * documented example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Global library.
+  */
+#include <hfrom_format.h>
+#include <mail_params.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * Test adapter.
+  */
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+struct name_test_case {
+    const char *label;                 /* identifies test case */
+    const char *config;                        /* configuration under test */
+    const char *want_warning;          /* expected warning or empty */
+    const int want_code;               /* expected code */
+};
+
+static struct name_test_case name_test_cases[] = {
+    {"hfrom_format_parse good-standard",
+        /* config */ HFROM_FORMAT_NAME_STD,
+        /* warning */ "",
+        /* want_code */ HFROM_FORMAT_CODE_STD
+    },
+    {"hfrom_format_parse good-obsolete",
+        /* config */ HFROM_FORMAT_NAME_OBS,
+        /* warning */ "",
+        /* want_code */ HFROM_FORMAT_CODE_OBS
+    },
+    {"hfrom_format_parse bad",
+        /* config */ "does-not-exist,",
+        /* warning */ "invalid setting: \"hfrom_format_parse bad = does-not-exist,\"",
+        /* code */ 0,
+    },
+    {"hfrom_format_parse empty",
+        /* config */ "",
+        /* warning */ "invalid setting: \"hfrom_format_parse empty = \"",
+        /* code */ 0,
+    },
+    0,
+};
+
+static void test_hfrom_format_parse(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    struct name_test_case *np;
+
+    for (np = name_test_cases; np->label != 0; np++) {
+       PTEST_RUN(t, np->label, {
+           int     got_code;
+
+           if (*np->want_warning)
+               expect_ptest_error(t, np->want_warning);
+           got_code = hfrom_format_parse(np->label, np->config);
+           if (*np->want_warning == 0) {
+               if (got_code != np->want_code) {
+                   ptest_error(t, "got code \"%d\", want \"%d\"(%s)",
+                              got_code, np->want_code,
+                              str_hfrom_format_code(np->want_code));
+               }
+           }
+       });
+    }
+}
+
+struct code_test_case {
+    const char *label;                 /* identifies test case */
+    int     code;                      /* code under test */
+    const char *want_warning;          /* expected warning or empty */
+    const char *want_name;             /* expected namme */
+};
+
+static struct code_test_case code_test_cases[] = {
+    {"str_hfrom_format_code good-standard",
+        /* code */ HFROM_FORMAT_CODE_STD,
+        /* warning */ "",
+        /* want_name */ HFROM_FORMAT_NAME_STD
+    },
+    {"str_hfrom_format_code good-obsolete",
+        /* code */ HFROM_FORMAT_CODE_OBS,
+        /* warning */ "",
+        /* want_name */ HFROM_FORMAT_NAME_OBS
+    },
+    {"str_hfrom_format_code bad",
+        /* config */ 12345,
+        /* warning */ "invalid header format code: 12345",
+        /* want_name */ 0
+    },
+    0,
+};
+
+static void test_str_hfrom_format_code(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    struct code_test_case *cp;
+
+    for (cp = code_test_cases; cp->label != 0; cp++) {
+       PTEST_RUN(t, cp->label, {
+           const char *got_name;
+
+           if (*cp->want_warning)
+               expect_ptest_error(t, cp->want_warning);
+           got_name = str_hfrom_format_code(cp->code);
+           if (*cp->want_warning == 0) {
+               if (strcmp(got_name, cp->want_name) != 0) {
+                   ptest_error(t, "got name: \"%s\", want: \"%s\"",
+                              got_name, cp->want_name);
+               }
+           }
+       });
+    }
+}
+
+ /*
+  * Test adapter.
+  */
+static const PTEST_CASE ptestcases[] = {
+    {"test hfrom_format_parse", test_hfrom_format_parse,},
+    {"test str_hfrom_format_code", test_str_hfrom_format_code,},
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/global/login_sender_match.ref b/postfix/src/global/login_sender_match.ref
deleted file mode 100644 (file)
index 20ea483..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-unknown: RUN test case 0 wildcard works
-unknown: PASS test 0
-unknown: RUN test case 1 unknown user
-unknown: PASS test 1
-unknown: RUN test case 2 bare user
-unknown: PASS test 2
-unknown: RUN test case 3 user@domain
-unknown: PASS test 3
-unknown: RUN test case 4 user+ext@domain
-unknown: PASS test 4
-unknown: RUN test case 5 wrong sender
-unknown: PASS test 5
-unknown: RUN test case 6 @domain
-unknown: PASS test 6
-unknown: RUN test case 7 wrong @domain
-unknown: PASS test 7
-unknown: RUN test case 8 null sender
-unknown: PASS test 8
-unknown: RUN test case 9 wrong null sender
-unknown: PASS test 9
-unknown: RUN test case 10 error
-unknown: warning: fail:sorry lookup error for "baz"
-unknown: PASS test 10
-unknown: RUN test case 11 no error
-unknown: PASS test 11
-unknown: RUN test case 12 unknown uid:number
-unknown: PASS test 12
-unknown: RUN test case 13 known uid:number
-unknown: PASS test 13
-unknown: RUN test case 14 unknown "other last"
-unknown: PASS test 14
-unknown: RUN test case 15 bare "first last"
-unknown: PASS test 15
-unknown: RUN test case 16 "first last"@domain
-unknown: PASS test 16
index 36d806d52ed3e26c60d6e1f983bc5931fff79bca..3f99802d229fba01c31fd7da7e2b640f8a0a32db 100644 (file)
  /*
-  * System library.
+  * Test program to exercise login_sender_match.c. See and ptest_main.h for a
+  * documented example.
   */
-#include <sys_defs.h>
 
  /*
-  * Utility library.
+  * System library.
   */
-#include <msg.h>
-#include <msg_vstream.h>
-#include <stringops.h>
+#include <sys_defs.h>
 
  /*
   * Global library.
   */
-#include <login_sender_match.h>
 #include <mail_params.h>
+#include <login_sender_match.h>
 
-int     main(int argc, char **argv)
-{
-    struct testcase {
-       const char *title;
-       const char *map_names;
-       const char *ext_delimiters;
-       const char *null_sender;
-       const char *wildcard;
-       const char *login_name;
-       const char *sender_addr;
-       int     exp_return;
-    };
-    struct testcase testcases[] = {
-       {"wildcard works",
-           "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
-           "+-", "<>", "*", "root", "anything", LSM_STAT_FOUND
-       },
-       {"unknown user",
-           "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
-           "+-", "<>", "*", "toor", "anything", LSM_STAT_NOTFOUND
-       },
-       {"bare user",
-           "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
-           "+-", "<>", "*", "foo", "foo", LSM_STAT_FOUND
-       },
-       {"user@domain",
-           "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
-           "+-", "<>", "*", "foo", "foo@example.com", LSM_STAT_FOUND
-       },
-       {"user+ext@domain",
-           "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
-           "+-", "<>", "*", "foo", "foo+bar@example.com", LSM_STAT_FOUND
-       },
-       {"wrong sender",
-           "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
-           "+-", "<>", "*", "foo", "bar@example.com", LSM_STAT_NOTFOUND
-       },
-       {"@domain",
-           "inline:{root=*, {foo = @example.com}, bar=<>}",
-           "+-", "<>", "*", "foo", "anyone@example.com", LSM_STAT_FOUND
-       },
-       {"wrong @domain",
-           "inline:{root=*, {foo = @example.com}, bar=<>}",
-           "+-", "<>", "*", "foo", "anyone@example.org", LSM_STAT_NOTFOUND
-       },
-       {"null sender",
-           "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
-           "+-", "<>", "*", "bar", "", LSM_STAT_FOUND
-       },
-       {"wrong null sender",
-           "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
-           "+-", "<>", "*", "baz", "", LSM_STAT_NOTFOUND
-       },
-       {"error",
-           "inline:{root=*}, fail:sorry",
-           "+-", "<>", "*", "baz", "whatever", LSM_STAT_RETRY
-       },
-       {"no error",
-           "inline:{root=*}, fail:sorry",
-           "+-", "<>", "*", "root", "whatever", LSM_STAT_FOUND
-       },
-       {"unknown uid:number",
-           "inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}",
-           "+-", "<>", "*", "uid:54321", "foo", LSM_STAT_NOTFOUND
-       },
-       {"known uid:number",
-           "inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}",
-           "+-", "<>", "*", "uid:12345", "foo", LSM_STAT_FOUND
-       },
-       {"unknown \"other last\"",
-           "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
-           "+-", "<>", "*", "foo", "other last", LSM_STAT_NOTFOUND
-       },
-       {"bare \"first last\"",
-           "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
-           "+-", "<>", "*", "foo", "first last", LSM_STAT_FOUND
-       },
-       {"\"first last\"@domain",
-           "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
-           "+-", "<>", "*", "foo", "first last@example.com", LSM_STAT_FOUND
-       },
-    };
-    struct testcase *tp;
-    int     act_return;
-    int     pass;
-    int     fail;
-    LOGIN_SENDER_MATCH *lsm;
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *tp);
+} PTEST_CASE;
 
-    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+typedef struct TEST_DATA {
+    const char *test_label;
+    const char *map_names;
+    const char *ext_delimiters;
+    const char *null_sender;
+    const char *wildcard;
+    const char *login_name;
+    const char *sender_addr;
+    int     want_return;
+    const char *want_logging;
+} TEST_DATA;
 
-    /*
-     * Fake variable settings.
-     */
-    var_double_bounce_sender = DEF_DOUBLE_BOUNCE;
-    var_ownreq_special = DEF_OWNREQ_SPECIAL;
+static const TEST_DATA test_data[] = {
+    {"wildcard works",
+       "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "root",
+       .sender_addr = "anything",
+       .want_return = LSM_STAT_FOUND,
+    },
+    {"unknown user",
+       "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "toor",
+       .sender_addr = "anything",
+       .want_return = LSM_STAT_NOTFOUND,
+    },
+    {"unknown user -> error",
+       "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}, fail:sorry",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "toor",
+       .sender_addr = "anything",
+       .want_return = LSM_STAT_RETRY,
+       .want_logging = "fail:sorry lookup error"
+    },
+    {"bare user",
+       "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "foo",
+       .sender_addr = "foo",
+       .want_return = LSM_STAT_FOUND,
+    },
+    {"user@domain",
+       "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "foo",
+       .sender_addr = "foo@example.com",
+       .want_return = LSM_STAT_FOUND,
+    },
+    {"user+ext@domain",
+       "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "foo",
+       .sender_addr = "foo+bar@example.com",
+       .want_return = LSM_STAT_FOUND,
+    },
+    {"wrong sender",
+       "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "foo",
+       .sender_addr = "bar@example.com",
+       .want_return = LSM_STAT_NOTFOUND,
+    },
+    {"@domain",
+       "inline:{root=*, {foo = @example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "foo",
+       .sender_addr = "anyone@example.com",
+       .want_return = LSM_STAT_FOUND,
+    },
+    {"wrong @domain",
+       "inline:{root=*, {foo = @example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "foo",
+       .sender_addr = "anyone@example.org",
+       .want_return = LSM_STAT_NOTFOUND,
+    },
+    {"null sender",
+       "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "bar",
+       .sender_addr = "",
+       .want_return = LSM_STAT_FOUND,
+    },
+    {"wrong null sender",
+       "inline:{root=*, {foo = foo,foo@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       "baz",
+       .sender_addr = "",
+       .want_return = LSM_STAT_NOTFOUND,
+    },
+    {"error",
+       "inline:{root=*}, fail:sorry",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "baz",
+       .sender_addr = "whatever",
+       .want_return = LSM_STAT_RETRY,
+       .want_logging = "fail:sorry lookup error"
+    },
+    {"no error",
+       "inline:{root=*}, fail:sorry",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "root",
+       .sender_addr = "whatever",
+       .want_return = LSM_STAT_FOUND,
+    },
+    {"unknown uid:number",
+       "inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "uid:54321",
+       .sender_addr = "foo",
+       .want_return = LSM_STAT_NOTFOUND,
+    },
+    {"known uid:number",
+       "inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "uid:12345",
+       .sender_addr = "foo",
+       .want_return = LSM_STAT_FOUND,
+    },
+    {"unknown \"other last\"",
+       "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "foo",
+       .sender_addr = "other last",
+       .want_return = LSM_STAT_NOTFOUND,
+    },
+    {"bare \"first last\"",
+       "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "foo",
+       .sender_addr = "first last",
+       .want_return = LSM_STAT_FOUND,
+    },
+    {"\"first last\"@domain",
+       "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+       .ext_delimiters = "+-",
+       .null_sender = "<>",
+       .wildcard = "*",
+       .login_name = "foo",
+       .sender_addr = "first last@example.com",
+       .want_return = LSM_STAT_FOUND,
+    },
+};
+
+static void test_login_sender_match(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    const TEST_DATA *tp;
 
-#define NUM_TESTS      sizeof(testcases)/sizeof(testcases[0])
+    for (tp = test_data; tp < test_data + PTEST_NROF(test_data); tp++) {
+       PTEST_RUN(t, tp->test_label, {
+           int     got_return;
+           LOGIN_SENDER_MATCH *lsm;
+
+           /*
+            * Fake variable settings.
+            */
+           var_double_bounce_sender = DEF_DOUBLE_BOUNCE;
+           var_ownreq_special = DEF_OWNREQ_SPECIAL;
 
-    for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
-       msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title);
 #if 0
-       msg_info("title=%s", tp->title);
-       msg_info("map_names=%s", tp->map_names);
-       msg_info("ext_delimiters=%s", tp->ext_delimiters);
-       msg_info("null_sender=%s", tp->null_sender);
-       msg_info("wildcard=%s", tp->wildcard);
-       msg_info("login_name=%s", tp->login_name);
-       msg_info("sender_addr=%s", tp->sender_addr);
-       msg_info("exp_return=%d", tp->exp_return);
+           pest_info(t, "name=%s", tp->title);
+           pest_info(t, "map_names=%s", tp->map_names);
+           pest_info(t, "ext_delimiters=%s", tp->ext_delimiters);
+           pest_info(t, "null_sender=%s", tp->null_sender);
+           pest_info(t, "wildcard=%s", tp->wildcard);
+           pest_info(t, "login_name=%s", tp->login_name);
+           pest_info(t, "sender_addr=%s", tp->sender_addr);
+           pest_info(t, "want_return=%d", tp->exp_return);
 #endif
-       lsm = login_sender_create("test map", tp->map_names,
-                                 tp->ext_delimiters, tp->null_sender,
-                                 tp->wildcard);
-       act_return = login_sender_match(lsm, tp->login_name, tp->sender_addr);
-       if (act_return == tp->exp_return) {
-           msg_info("PASS test %ld", (long) (tp - testcases));
-           pass++;
-       } else {
-           msg_info("FAIL test %ld", (long) (tp - testcases));
-           fail++;
-       }
-       login_sender_free(lsm);
+           lsm = login_sender_create("test map", tp->map_names,
+                                     tp->ext_delimiters, tp->null_sender,
+                                     tp->wildcard);
+           if (tp->want_logging)
+               expect_ptest_log_event(t, tp->want_logging);
+           got_return = login_sender_match(lsm, tp->login_name, tp->sender_addr);
+           if (got_return != tp->want_return)
+               ptest_error(t, "login_sender_match() got %d, want %d",
+                           got_return, tp->want_return);
+           login_sender_free(lsm);
+       });
     }
-    msg_info("PASS=%d FAIL=%d", pass, fail);
-    return (fail > 0);
 }
+
+static const PTEST_CASE ptestcases[] = {
+    {"test_login_sender_match", test_login_sender_match,},
+};
+
+#include <ptest_main.h>
index c7e55455c03685ffcd18e214cdff1846842e7035..68efdcaa4daa05889bd0fead5ad8977571249e2b 100644 (file)
@@ -208,6 +208,8 @@ static int strategy_from_string(const char *strategy_string)
 
 /* strategy_to_string - internal form to symbolic strategy flags */
 
+#if 0
+
 static const char *strategy_to_string(VSTRING *res_buf, int strategy_mask)
 {
     static VSTRING *my_buf;
@@ -219,6 +221,8 @@ static const char *strategy_to_string(VSTRING *res_buf, int strategy_mask)
                              NAME_MASK_WARN | NAME_MASK_PIPE));
 }
 
+#endif
+
 #endif
 
  /*
index 55ac5dc2266906500da0e8a90dcfe3fa9c98a497..94ed47b9a5080cbe191729a71d4458f6c8005994 100644 (file)
@@ -118,7 +118,7 @@ int     main(int argc, char **argv)
     var_proxywrite_service = DEF_PROXYWRITE_SERVICE;
     var_ipc_timeout = 3600;
     mail_dict_init();
-    dict_test(argc, argv);
+    dict_cli(argc, argv);
     return (0);
 }
 
index f60f3b45341248eff81fbdfcafd9b9ecf519ef0a..4b7939447524faff1bbc777f7bab883423f8bcda 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20260401"
+#define MAIL_RELEASE_DATE      "20260402"
 #define MAIL_VERSION_NUMBER    "3.12"
 
 #ifdef SNAPSHOT
index b10f7d516829f802527ecf4f46ec627702d34b83..2cead144a0b6314500b5a21c3b896032af48854e 100644 (file)
@@ -252,146 +252,3 @@ const MAP_SEARCH *map_search_lookup(const char *map_spec)
 
     return ((MAP_SEARCH *) htable_find(map_search_table, map_spec));
 }
-
- /*
-  * Test driver.
-  */
-#ifdef TEST
-#include <stdlib.h>
-
- /*
-  * Test search actions.
-  */
-#define TEST_NAME_1    "one"
-#define TEST_NAME_2    "two"
-#define TEST_CODE_1    1
-#define TEST_CODE_2    2
-
-#define BAD_NAME       "bad"
-
-static const NAME_CODE search_actions[] = {
-    TEST_NAME_1, TEST_CODE_1,
-    TEST_NAME_2, TEST_CODE_2,
-    0, MAP_SEARCH_CODE_UNKNOWN,
-};
-
-/* Helpers to simplify tests. */
-
-static const char *string_or_null(const char *s)
-{
-    return (s ? s : "(null)");
-}
-
-static char *escape_order(VSTRING *buf, const char *search_order)
-{
-    return (STR(escape(buf, search_order, strlen(search_order))));
-}
-
-int     main(int argc, char **argv)
-{
-    /* Test cases with inputs and expected outputs. */
-    typedef struct TEST_CASE {
-       const char *map_spec;
-       int     exp_return;             /* 0=fail, 1=success */
-       const char *exp_map_type_name;  /* 0 or match */
-       const char *exp_search_order;   /* 0 or match */
-    } TEST_CASE;
-    static TEST_CASE test_cases[] = {
-       {"type", 0, 0, 0},
-       {"type:name", 1, "type:name", 0},
-       {"{type:name}", 1, "type:name", 0},
-       {"{type:name", 0, 0, 0},        /* } */
-       {"{type}", 0, 0, 0},
-       {"{type:name foo}", 0, 0, 0},
-       {"{type:name foo=bar}", 0, 0, 0},
-       {"{type:name search_order=}", 1, "type:name", ""},
-       {"{type:name search_order=one, two}", 0, 0, 0},
-       {"{type:name {search_order=one, two}}", 1, "type:name", "\01\02"},
-       {"{type:name {search_order=one, two, bad}}", 0, 0, 0},
-       {"{inline:{a=b} {search_order=one, two}}", 1, "inline:{a=b}", "\01\02"},
-       {"{inline:{a=b, c=d} {search_order=one, two}}", 1, "inline:{a=b, c=d}", "\01\02"},
-       {0},
-    };
-    TEST_CASE *test_case;
-
-    /* Actual results. */
-    const MAP_SEARCH *map_search_from_create;
-    const MAP_SEARCH *map_search_from_create_2nd;
-    const MAP_SEARCH *map_search_from_lookup;
-
-    /* Findings. */
-    int     tests_failed = 0;
-    int     test_failed;
-
-    /* Scratch */
-    VSTRING *expect_escaped = vstring_alloc(100);
-    VSTRING *actual_escaped = vstring_alloc(100);
-
-    map_search_init(search_actions);
-
-    for (tests_failed = 0, test_case = test_cases; test_case->map_spec;
-        tests_failed += test_failed, test_case++) {
-       test_failed = 0;
-       msg_info("test case %d: '%s'",
-                (int) (test_case - test_cases), test_case->map_spec);
-       map_search_from_create = map_search_create(test_case->map_spec);
-       if (!test_case->exp_return != !map_search_from_create) {
-           if (map_search_from_create)
-               msg_warn("test case %d return expected %s actual {%s, %s}",
-                        (int) (test_case - test_cases),
-                        test_case->exp_return ? "success" : "fail",
-                        map_search_from_create->map_type_name,
-                        escape_order(actual_escaped,
-                                     map_search_from_create->search_order));
-           else
-               msg_warn("test case %d return expected %s actual %s",
-                        (int) (test_case - test_cases), "success",
-                        map_search_from_create ? "success" : "fail");
-           test_failed = 1;
-           continue;
-       }
-       if (test_case->exp_return == 0)
-           continue;
-       map_search_from_lookup = map_search_lookup(test_case->map_spec);
-       if (map_search_from_create != map_search_from_lookup) {
-           msg_warn("test case %d map_search_lookup expected=%p actual=%p",
-                    (int) (test_case - test_cases),
-                    map_search_from_create, map_search_from_lookup);
-           test_failed = 1;
-       }
-       map_search_from_create_2nd = map_search_create(test_case->map_spec);
-       if (map_search_from_create != map_search_from_create_2nd) {
-           msg_warn("test case %d repeated map_search_create "
-                    "expected=%p actual=%p",
-                    (int) (test_case - test_cases),
-                    map_search_from_create, map_search_from_create_2nd);
-           test_failed = 1;
-       }
-       if (strcmp(string_or_null(test_case->exp_map_type_name),
-                  string_or_null(map_search_from_create->map_type_name))) {
-           msg_warn("test case %d map_type_name expected=%s actual=%s",
-                    (int) (test_case - test_cases),
-                    string_or_null(test_case->exp_map_type_name),
-                    string_or_null(map_search_from_create->map_type_name));
-           test_failed = 1;
-       }
-       if (strcmp(string_or_null(test_case->exp_search_order),
-                  string_or_null(map_search_from_create->search_order))) {
-           msg_warn("test case %d search_order expected=%s actual=%s",
-                    (int) (test_case - test_cases),
-                    escape_order(expect_escaped,
-                              string_or_null(test_case->exp_search_order)),
-                    escape_order(actual_escaped,
-                    string_or_null(map_search_from_create->search_order)));
-           test_failed = 1;
-       }
-    }
-    vstring_free(expect_escaped);
-    vstring_free(actual_escaped);
-
-    if (tests_failed)
-       msg_info("tests failed: %d", tests_failed);
-    exit(tests_failed != 0);
-}
-
-#endif
diff --git a/postfix/src/global/map_search.ref b/postfix/src/global/map_search.ref
deleted file mode 100644 (file)
index a296e4e..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-unknown: test case 0: 'type'
-unknown: warning: malformed map specification: 'type'
-unknown: warning: expected maptype:mapname instead of 'type'
-unknown: test case 1: 'type:name'
-unknown: test case 2: '{type:name}'
-unknown: test case 3: '{type:name'
-unknown: warning: malformed map specification: 'missing '}' in "{type:name"'
-unknown: test case 4: '{type}'
-unknown: warning: malformed map specification: '{type}'
-unknown: warning: expected maptype:mapname instead of 'type'
-unknown: test case 5: '{type:name foo}'
-unknown: warning: malformed map attribute in '{type:name foo}': 'missing '=' after attribute name'
-unknown: test case 6: '{type:name foo=bar}'
-unknown: warning: unknown map attribute in '{type:name foo=bar}': 'foo'
-unknown: test case 7: '{type:name search_order=}'
-unknown: test case 8: '{type:name search_order=one, two}'
-unknown: warning: malformed map attribute in '{type:name search_order=one, two}': 'missing '=' after attribute name'
-unknown: test case 9: '{type:name {search_order=one, two}}'
-unknown: test case 10: '{type:name {search_order=one, two, bad}}'
-unknown: warning: unknown search type 'bad' in '{type:name {search_order=one, two, bad}}'
-unknown: test case 11: '{inline:{a=b} {search_order=one, two}}'
-unknown: test case 12: '{inline:{a=b, c=d} {search_order=one, two}}'
diff --git a/postfix/src/global/map_search_test.c b/postfix/src/global/map_search_test.c
new file mode 100644 (file)
index 0000000..8eb6b91
--- /dev/null
@@ -0,0 +1,242 @@
+ /*
+  * Test program to exercise map_search.c. See ptest_main.h for a documented
+  * example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <stringops.h>
+#include <vstring.h>
+
+ /*
+  * Global library.
+  */
+#include <map_search.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * Test search actions.
+  */
+#define TEST_NAME_1    "one"
+#define TEST_NAME_2    "two"
+#define TEST_CODE_1    1
+#define TEST_CODE_2    2
+
+#define BAD_NAME       "bad"
+
+#define STR(x) vstring_str(x)
+
+static const NAME_CODE search_actions[] = {
+    TEST_NAME_1, TEST_CODE_1,
+    TEST_NAME_2, TEST_CODE_2,
+    0, MAP_SEARCH_CODE_UNKNOWN,
+};
+
+ /*
+  * Test library adaptor.
+  */
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+/* Helpers to simplify tests. */
+
+static const char *string_or_null(const char *s)
+{
+    return (s ? s : "(null)");
+}
+
+static char *escape_order(VSTRING *buf, const char *search_order)
+{
+    return (STR(escape(buf, search_order, strlen(search_order))));
+}
+
+#define MAX_WANT_LOG   5
+
+static void test_map_search(PTEST_CTX *t, const struct PTEST_CASE *unused)
+{
+    /* Test cases with inputs and expected outputs. */
+    struct test {
+       const char *map_spec;
+       int     want_return;            /* 0=fail, 1=success */
+       const char *want_log[MAX_WANT_LOG];
+       const char *want_map_type_name; /* 0 or match */
+       const char *want_search_order;  /* 0 or match */
+    };
+    static struct test test_cases[] = {
+       {                               /* 0 */
+           .map_spec = "type",
+           .want_return = 0,
+           .want_log = {
+               "malformed map specification: 'type'",
+               "expected maptype:mapname instead of 'type'",
+           },
+       },
+       {                               /* 1 */
+           .map_spec = "type:name",
+           .want_return = 1,
+           .want_map_type_name = "type:name",
+       },
+       {                               /* 2 */
+           .map_spec = "{type:name}",
+           .want_return = 1,
+           .want_map_type_name = "type:name",
+       },
+       {                               /* 3 */
+           .map_spec = "{type:name",
+           .want_return = 0,
+           .want_log = {
+               "missing '}' in \"{type:name\""
+           },
+       },                              /* } */
+       {                               /* 4 */
+           .map_spec = "{type}",
+           .want_return = 0,
+           .want_log = {
+               "malformed map specification: '{type}'",
+               "expected maptype:mapname instead of 'type'",
+           },
+       },
+       {                               /* 5 */
+           .map_spec = "{type:name foo}",
+           .want_return = 0,
+           .want_log = {
+               "missing '=' after attribute name",
+           },
+       },
+       {                               /* 6 */
+           .map_spec = "{type:name foo=bar}",
+           .want_return = 0,
+           .want_log = {
+               "warning: unknown map attribute in '{type:name foo=bar}': 'foo'",
+           },
+       },
+       {                               /* 7 */
+           .map_spec = "{type:name search_order=}",
+           .want_return = 1,
+           .want_map_type_name = "type:name",
+           .want_search_order = "",
+       },
+       {                               /* 8 */
+           .map_spec = "{type:name search_order=one, two}",
+           .want_return = 0,
+           .want_log = {
+               "missing '=' after attribute name'",
+           },
+       },
+       {                               /* 9 */
+           .map_spec = "{type:name {search_order=one, two}}",
+           .want_return = 1,
+           .want_map_type_name = "type:name",
+           .want_search_order = "\01\02",
+       },
+       {                               /* 10 */
+           .map_spec = "{type:name {search_order=one, two, bad}}",
+           .want_return = 0,
+           .want_log = {
+               "'bad' in '{type:name {search_order=one, two, bad}}'",
+           },
+       },
+       {                               /* 11 */
+           .map_spec = "{inline:{a=b} {search_order=one, two}}",
+           .want_return = 1,
+           .want_map_type_name = "inline:{a=b}",
+           .want_search_order = "\01\02",
+       },
+       {                               /* 12 */
+           .map_spec = "{inline:{a=b, c=d} {search_order=one, two}}",
+           .want_return = 1,
+           .want_map_type_name = "inline:{a=b, c=d}",
+           .want_search_order = "\01\02",
+       },
+       {0},
+    };
+    struct test *tp;
+    const char *const * cpp;
+
+    /* Actual results. */
+    const MAP_SEARCH *map_search_from_create;
+    const MAP_SEARCH *map_search_from_create_2nd;
+    const MAP_SEARCH *map_search_from_lookup;
+
+    /* Scratch */
+    VSTRING *want_escaped = vstring_alloc(100);
+    VSTRING *got_escaped = vstring_alloc(100);
+
+    /* Other */
+    VSTRING *test_label = vstring_alloc(100);
+
+    map_search_init(search_actions);
+
+    for (tp = test_cases; tp->map_spec; tp++) {
+       vstring_sprintf(test_label, "test %d", (int) (tp - test_cases));
+       PTEST_RUN(t, STR(test_label), {
+           for (cpp = tp->want_log; cpp < tp->want_log + MAX_WANT_LOG && *cpp; cpp++)
+               expect_ptest_log_event(t, *cpp);
+           map_search_from_create = map_search_create(tp->map_spec);
+           if (!tp->want_return != !map_search_from_create) {
+               if (map_search_from_create)
+                   ptest_fatal(t, "return: got {%s, %s}, want '%s'",
+                               map_search_from_create->map_type_name,
+                               escape_order(got_escaped,
+                                     map_search_from_create->search_order),
+                               tp->want_return ? "success" : "fail");
+               else
+                   ptest_fatal(t, "return: got '%s', want '%s'",
+                               map_search_from_create ? "success" : "fail",
+                               "success");
+           }
+           if (tp->want_return == 0)
+               ptest_return(t);
+           map_search_from_lookup = map_search_lookup(tp->map_spec);
+           if (map_search_from_create != map_search_from_lookup) {
+               ptest_error(t, "map_search_lookup: got %p, want %p",
+                           map_search_from_lookup, map_search_from_create);
+           }
+           map_search_from_create_2nd = map_search_create(tp->map_spec);
+           if (map_search_from_create != map_search_from_create_2nd) {
+               ptest_error(t, "repeated map_search_create: got %p want %p",
+                       map_search_from_create_2nd, map_search_from_create);
+           }
+           if (strcmp(string_or_null(tp->want_map_type_name),
+                  string_or_null(map_search_from_create->map_type_name))) {
+               ptest_error(t, "map_type_name: got '%s', want '%s'",
+                     string_or_null(map_search_from_create->map_type_name),
+                           string_or_null(tp->want_map_type_name));
+           }
+           if (strcmp(string_or_null(tp->want_search_order),
+                   string_or_null(map_search_from_create->search_order))) {
+               ptest_error(t, "search_order: got '%s', want '%s'",
+                           escape_order(got_escaped,
+                     string_or_null(map_search_from_create->search_order)),
+                           escape_order(want_escaped,
+                                    string_or_null(tp->want_search_order)));
+           }
+       });
+    }
+    vstring_free(want_escaped);
+    vstring_free(got_escaped);
+    vstring_free(test_label);
+}
+
+ /*
+  * Test library adaptor.
+  */
+static const PTEST_CASE ptestcases[] = {
+    "test_map_search", test_map_search,
+};
+
+#include <ptest_main.h>
index 81b737c1977f863f021452c0a46aa58cf9b955fc..728b5d707d282b322fa9d5f1cbda36df9415f0b3 100644 (file)
@@ -138,6 +138,7 @@ static int redirects_dict_open_hash_to_def_db_type(const TEST_CASE *tp)
            status = FAIL;
        }
        dict_close(dict);
+       msg_capt_free(capture);
     }
 
     return (status);
@@ -195,6 +196,7 @@ static int redirects_dict_open_btree_to_def_cache_db_type(const TEST_CASE *tp)
            status = FAIL;
        }
        dict_close(dict);
+       msg_capt_free(capture);
     }
 
     return (status);
@@ -252,6 +254,7 @@ static int redirects_mkmap_open_hash_to_def_db_type(const TEST_CASE *tp)
            status = FAIL;
        }
        mkmap_close(mkmap);
+       msg_capt_free(capture);
     }
 
     return (status);
@@ -310,6 +313,7 @@ static int redirects_mkmap_open_btree_to_def_cache_db_type(const TEST_CASE *tp)
            status = FAIL;
        }
        mkmap_close(mkmap);
+       msg_capt_free(capture);
     }
 
     return (status);
index 0502b5025cee5dae33ad1ec876e01150963d23a0..70dadcef422301682df6e7f1a709e6a2b59bf759 100644 (file)
@@ -149,111 +149,3 @@ int     normalize_mailhost_addr(const char *string, char **mailhost_addr,
     }
     return (0);
 }
-
- /*
-  * Test program.
-  */
-#ifdef TEST
-#include <stdlib.h>
-#include <mymalloc.h>
-#include <msg.h>
-
- /*
-  * Main test program.
-  */
-int     main(int argc, char **argv)
-{
-    /* Test cases with inputs and expected outputs. */
-    typedef struct TEST_CASE {
-       const char *inet_protocols;
-       const char *mailhost_addr;
-       int     exp_return;
-       const char *exp_mailhost_addr;
-       char   *exp_bare_addr;
-       int     exp_addr_family;
-    } TEST_CASE;
-    static TEST_CASE test_cases[] = {
-       /* IPv4 in IPv6. */
-       {"ipv4, ipv6", "ipv6:::ffff:1.2.3.4", 0, "1.2.3.4", "1.2.3.4", AF_INET},
-       {"ipv6", "ipv6:::ffff:1.2.3.4", 0, "IPv6:::ffff:1.2.3.4", "::ffff:1.2.3.4", AF_INET6},
-       /* Pass IPv4 or IPV6. */
-       {"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", "fc00::1", AF_INET6},
-       {"ipv4, ipv6", "1.2.3.4", 0, "1.2.3.4", "1.2.3.4", AF_INET},
-       /* Normalize IPv4 or IPV6. */
-       {"ipv4, ipv6", "ipv6:fc00::0", 0, "IPv6:fc00::", "fc00::", AF_INET6},
-       {"ipv4, ipv6", "01.02.03.04", 0, "1.2.3.4", "1.2.3.4", AF_INET},
-       /* Suppress specific outputs. */
-       {"ipv4, ipv6", "ipv6:fc00::1", 0, 0, "fc00::1", AF_INET6},
-       {"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", 0, AF_INET6},
-       {"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", "fc00::1", -1},
-       /* Address type mismatch. */
-       {"ipv4, ipv6", "::ffff:1.2.3.4", -1},
-       {"ipv4", "ipv6:fc00::1", -1},
-       {"ipv6", "1.2.3.4", -1},
-       0,
-    };
-    TEST_CASE *test_case;
-
-    /* Actual results. */
-    int     act_return;
-    char   *act_mailhost_addr = mystrdup("initial_mailhost_addr");
-    char   *act_bare_addr = mystrdup("initial_bare_addr");
-    int     act_addr_family = 0xdeadbeef;
-
-    /* Findings. */
-    int     tests_failed = 0;
-    int     test_failed;
-
-    for (tests_failed = 0, test_case = test_cases; test_case->inet_protocols;
-        tests_failed += test_failed, test_case++) {
-       test_failed = 0;
-       inet_proto_init(argv[0], test_case->inet_protocols);
-       act_return =
-           normalize_mailhost_addr(test_case->mailhost_addr,
-                                   test_case->exp_mailhost_addr ?
-                                   &act_mailhost_addr : (char **) 0,
-                                   test_case->exp_bare_addr ?
-                                   &act_bare_addr : (char **) 0,
-                                   test_case->exp_addr_family >= 0 ?
-                                   &act_addr_family : (int *) 0);
-       if (act_return != test_case->exp_return) {
-           msg_warn("test case %d return expected=%d actual=%d",
-                    (int) (test_case - test_cases),
-                    test_case->exp_return, act_return);
-           test_failed = 1;
-           continue;
-       }
-       if (test_case->exp_return != 0)
-           continue;
-       if (test_case->exp_mailhost_addr
-           && strcmp(test_case->exp_mailhost_addr, act_mailhost_addr)) {
-           msg_warn("test case %d mailhost_addr expected=%s actual=%s",
-                    (int) (test_case - test_cases),
-                    test_case->exp_mailhost_addr, act_mailhost_addr);
-           test_failed = 1;
-       }
-       if (test_case->exp_bare_addr
-           && strcmp(test_case->exp_bare_addr, act_bare_addr)) {
-           msg_warn("test case %d bare_addr expected=%s actual=%s",
-                    (int) (test_case - test_cases),
-                    test_case->exp_bare_addr, act_bare_addr);
-           test_failed = 1;
-       }
-       if (test_case->exp_addr_family >= 0
-           && test_case->exp_addr_family != act_addr_family) {
-           msg_warn("test case %d addr_family expected=0x%x actual=0x%x",
-                    (int) (test_case - test_cases),
-                    test_case->exp_addr_family, act_addr_family);
-           test_failed = 1;
-       }
-    }
-    if (act_mailhost_addr)
-       myfree(act_mailhost_addr);
-    if (act_bare_addr)
-       myfree(act_bare_addr);
-    if (tests_failed)
-       msg_info("tests failed: %d", tests_failed);
-    exit(tests_failed != 0);
-}
-
-#endif
diff --git a/postfix/src/global/normalize_mailhost_addr_test.c b/postfix/src/global/normalize_mailhost_addr_test.c
new file mode 100644 (file)
index 0000000..5dcf58b
--- /dev/null
@@ -0,0 +1,179 @@
+ /*
+  * Test program to exercise normalize_mailhost_addr.c. See ptest_main.h for
+  * a documented example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+ /*
+  * Utility library.
+  */
+#include <mymalloc.h>
+#include <inet_proto.h>
+
+ /*
+  * Global library.
+  */
+#include <normalize_mailhost_addr.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+    const char *inet_protocols;
+    const char *mailhost_addr;
+    int     want_return;
+    const char *want_mailhost_addr;
+    char   *want_bare_addr;
+    int     want_addr_family;
+} PTEST_CASE;
+
+static void test_normalize_mailhost_addr(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    /* Actual results. */
+    int     got_return;
+    char   *got_mailhost_addr = mystrdup("initial_mailhost_addr");
+    char   *got_bare_addr = mystrdup("initial_bare_addr");
+    int     got_addr_family = 0xdeadbeef;
+
+#define CLEANUP_AND_RETURN() do { \
+       if (got_mailhost_addr) \
+           myfree(got_mailhost_addr); \
+       got_mailhost_addr = 0; \
+       if (got_bare_addr) \
+           myfree(got_bare_addr); \
+       got_bare_addr = 0; \
+    } while (0)
+
+    inet_proto_init(tp->testname, tp->inet_protocols);
+    got_return = normalize_mailhost_addr(tp->mailhost_addr,
+                                        tp->want_mailhost_addr ?
+                                        &got_mailhost_addr : (char **) 0,
+                                        tp->want_bare_addr ?
+                                        &got_bare_addr : (char **) 0,
+                                        tp->want_addr_family >= 0 ?
+                                        &got_addr_family : (int *) 0);
+    if (got_return != tp->want_return) {
+       ptest_error(t, "return value: got %d, want %d",
+                   got_return, tp->want_return);
+       CLEANUP_AND_RETURN();
+    }
+    if (tp->want_return != 0)
+       CLEANUP_AND_RETURN();
+    if (tp->want_mailhost_addr
+       && strcmp(tp->want_mailhost_addr, got_mailhost_addr)) {
+       ptest_error(t, "mailhost_addr value: got '%s', want '%s'",
+                   got_mailhost_addr, tp->want_mailhost_addr);
+    }
+    if (tp->want_bare_addr && strcmp(tp->want_bare_addr, got_bare_addr)) {
+       ptest_error(t, "bare_addr value: got '%s', want '%s'",
+                   got_bare_addr, tp->want_bare_addr);
+    }
+    if (tp->want_addr_family > 0 && tp->want_addr_family != got_addr_family) {
+       ptest_error(t, "addr_family: got 0x%x, want 0x%x",
+                   got_addr_family, tp->want_addr_family);
+    }
+    CLEANUP_AND_RETURN();
+}
+
+static PTEST_CASE ptestcases[] = {
+    {
+       "IPv4 in IPv6 #1", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv4, ipv6",
+        /* mailhost_addr */ "ipv6:::ffff:1.2.3.4",
+        /* want_return */ 0,
+        /* want_mailhost_addr */ "1.2.3.4",
+        /* want_bare_addr */ "1.2.3.4",
+        /* want_addr_family */ AF_INET
+    }, {
+       "IPv4 in IPv6 #2", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv6",
+        /* mailhost_addr */ "ipv6:::ffff:1.2.3.4",
+        /* want_return */ 0,
+        /* want_mailhost_addr */ "IPv6:::ffff:1.2.3.4",
+        /* want_bare_addr */ "::ffff:1.2.3.4",
+        /* want_addr_family */ AF_INET6
+    }, {
+       "Pass IPv4 or IPV6 #1", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv4, ipv6",
+        /* mailhost_addr */ "ipv6:fc00::1",
+        /* want_return */ 0,
+        /* want_mailhost_addr */ "IPv6:fc00::1",
+        /* want_bare_addr */ "fc00::1",
+        /* want_addr_family */ AF_INET6
+    }, {
+       "Pass IPv4 or IPV6 #2", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv4, ipv6",
+        /* mailhost_addr */ "1.2.3.4",
+        /* want_return */ 0,
+        /* want_mailhost_addr */ "1.2.3.4",
+        /* want_bare_addr */ "1.2.3.4",
+        /* want_addr_family */ AF_INET
+    }, {
+       "Normalize IPv4 or IPV6 #1", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv4, ipv6",
+        /* mailhost_addr */ "ipv6:fc00::0",
+        /* want_return */ 0,
+        /* want_mailhost_addr */ "IPv6:fc00::",
+        /* want_bare_addr */ "fc00::",
+        /* want_addr_family */ AF_INET6
+    }, {
+       "Normalize IPv4 or IPV6 #2", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv4, ipv6",
+        /* mailhost_addr */ "01.02.03.04",
+        /* want_return */ 0,
+        /* want_mailhost_addr */ "1.2.3.4",
+        /* want_bare_addr */ "1.2.3.4",
+        /* want_addr_family */ AF_INET
+    }, {
+       "Suppress specific outputs #1", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv4, ipv6",
+        /* mailhost_addr */ "ipv6:fc00::1",
+        /* want_return */ 0,
+        /* want_mailhost_addr */ 0,
+        /* want_bare_addr */ "fc00::1",
+        /* want_addr_family */ AF_INET6
+    }, {
+       "Suppress specific outputs #2", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv4, ipv6",
+        /* mailhost_addr */ "ipv6:fc00::1",
+        /* want_return */ 0,
+        /* want_mailhost_addr */ "IPv6:fc00::1",
+        /* want_bare_addr */ 0,
+        /* want_addr_family */ AF_INET6
+    }, {
+       "Suppress specific outputs #3", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv4, ipv6",
+        /* mailhost_addr */ "ipv6:fc00::1",
+        /* want_return */ 0,
+        /* want_mailhost_addr */ "IPv6:fc00::1",
+        /* want_bare_addr */ "fc00::1", -1
+    }, {
+       "Address type mismatch #1", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv4, ipv6",
+        /* mailhost_addr */ "::ffff:1.2.3.4",
+        /* want_return */ -1
+    }, {
+       "Address type mismatch #2", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv4",
+        /* mailhost_addr */ "ipv6:fc00::1",
+        /* want_return */ -1
+    }, {
+       "Address type mismatch #3", test_normalize_mailhost_addr,
+        /* inet_protocols */ "ipv6",
+        /* mailhost_addr */ "1.2.3.4",
+        /* want_return */ -1
+    },
+};
+
+#include <ptest_main.h>
index 6e5bb75d96310aa442e771e18cb19d9655d6fb44..ef1e40d39e073ba8d7a399695611698bcca713d1 100644 (file)
@@ -207,82 +207,3 @@ int     smtp_reply_footer(VSTRING *buffer, ssize_t start,
        vstring_strcat(buffer, "\r\n");
     return (mac_expand_error ? -2 : 0);
 }
-
-#ifdef TEST
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <msg.h>
-#include <vstream.h>
-#include <vstring_vstream.h>
-#include <msg_vstream.h>
-
-struct test_case {
-    const char *title;
-    const char *orig_reply;
-    const char *template;
-    const char *filter;
-    int     expected_status;
-    const char *expected_reply;
-};
-
-#define NO_FILTER      ((char *) 0)
-#define NO_TEMPLATE    "NO_TEMPLATE"
-#define NO_ERROR       (0)
-#define BAD_SMTP       (-1)
-#define BAD_MACRO      (-2)
-
-static const struct test_case test_cases[] = {
-    {"missing reply", "", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
-    {"long smtp_code", "1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
-    {"short smtp_code", "12 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
-    {"good+bad smtp_code", "321 foo\r\n1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
-    {"1-line no dsn", "550 Foo", "\\c footer", NO_FILTER, NO_ERROR, "550 Foo footer"},
-    {"1-line no dsn", "550 Foo", "Bar", NO_FILTER, NO_ERROR, "550-Foo\r\n550 Bar"},
-    {"2-line no dsn", "550-Foo\r\n550 Bar", "Baz", NO_FILTER, NO_ERROR, "550-Foo\r\n550-Bar\r\n550 Baz"},
-    {"1-line with dsn", "550 5.1.1 Foo", "Bar", NO_FILTER, NO_ERROR, "550-5.1.1 Foo\r\n550 5.1.1 Bar"},
-    {"2-line with dsn", "550-5.1.1 Foo\r\n450 4.1.1 Bar", "Baz", NO_FILTER, NO_ERROR, "550-5.1.1 Foo\r\n450-4.1.1 Bar\r\n450 4.1.1 Baz"},
-    {"bad macro", "220 myhostname", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0},
-    {"bad macroCRLF", "220 myhostname\r\n", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0},
-    {"good macro", "220 myhostname", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY"},
-    {"good macroCRLF", "220 myhostname\r\n", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY\r\n"},
-    0,
-};
-
-static const char *lookup(const char *name, int unused_mode, void *context)
-{
-    return "DUMMY";
-}
-
-int     main(int argc, char **argv)
-{
-    const struct test_case *tp;
-    int     status;
-    VSTRING *buf = vstring_alloc(10);
-    void   *context = 0;
-
-    msg_vstream_init(argv[0], VSTREAM_ERR);
-
-    for (tp = test_cases; tp->title != 0; tp++) {
-       vstring_strcpy(buf, tp->orig_reply);
-       status = smtp_reply_footer(buf, 0, tp->template, tp->filter,
-                                  lookup, context);
-       if (status != tp->expected_status) {
-           msg_warn("test \"%s\": status %d, expected %d",
-                    tp->title, status, tp->expected_status);
-       } else if (status < 0 && strcmp(STR(buf), tp->orig_reply) != 0) {
-           msg_warn("test \"%s\": result \"%s\", expected \"%s\"",
-                    tp->title, STR(buf), tp->orig_reply);
-       } else if (status == 0 && strcmp(STR(buf), tp->expected_reply) != 0) {
-           msg_warn("test \"%s\": result \"%s\", expected \"%s\"",
-                    tp->title, STR(buf), tp->expected_reply);
-       } else {
-           msg_info("test \"%s\": pass", tp->title);
-       }
-    }
-    vstring_free(buf);
-    exit(0);
-}
-
-#endif
diff --git a/postfix/src/global/smtp_reply_footer.ref b/postfix/src/global/smtp_reply_footer.ref
deleted file mode 100644 (file)
index d7eb5a7..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-./smtp_reply_footer: test "missing reply": pass
-./smtp_reply_footer: test "long smtp_code": pass
-./smtp_reply_footer: test "short smtp_code": pass
-./smtp_reply_footer: test "good+bad smtp_code": pass
-./smtp_reply_footer: test "1-line no dsn": pass
-./smtp_reply_footer: test "1-line no dsn": pass
-./smtp_reply_footer: test "2-line no dsn": pass
-./smtp_reply_footer: test "1-line with dsn": pass
-./smtp_reply_footer: test "2-line with dsn": pass
-./smtp_reply_footer: warning: truncated macro reference: " ${whatever"
-./smtp_reply_footer: test "bad macro": pass
-./smtp_reply_footer: warning: truncated macro reference: " ${whatever"
-./smtp_reply_footer: test "bad macroCRLF": pass
-./smtp_reply_footer: test "good macro": pass
-./smtp_reply_footer: test "good macroCRLF": pass
diff --git a/postfix/src/global/smtp_reply_footer_test.c b/postfix/src/global/smtp_reply_footer_test.c
new file mode 100644 (file)
index 0000000..13aeaab
--- /dev/null
@@ -0,0 +1,90 @@
+ /*
+  * Test program to exercise smtp_reply_footer.c. See ptest_main.h for a
+  * documented example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Global library.
+  */
+#include <dsn_util.h>
+#include <smtp_reply_footer.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * SLMs.
+  */
+#define STR    vstring_str
+
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+    const char *orig_reply;
+    const char *template;
+    const char *filter;
+    int     want_status;
+    const char *new_reply;
+    const char *ignore_warning;
+} PTEST_CASE;
+
+#define NO_FILTER      ((char *) 0)
+#define NO_TEMPLATE    "NO_TEMPLATE"
+#define NO_ERROR       (0)
+#define BAD_SMTP       (-1)
+#define BAD_MACRO      (-2)
+
+static const char *lookup(const char *testname, int unused_mode, void *context)
+{
+    return "DUMMY";
+}
+
+static void test_footer(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    VSTRING *buf = vstring_alloc(10);
+    int     got_status;
+    void   *context = 0;
+
+    if (tp->ignore_warning)
+       expect_ptest_log_event(t, tp->ignore_warning);
+    vstring_strcpy(buf, tp->orig_reply);
+    got_status = smtp_reply_footer(buf, 0, tp->template, tp->filter,
+                                  lookup, context);
+    if (got_status != tp->want_status) {
+       ptest_error(t, "smtp_reply_footer status: got %d, want %d",
+                   got_status, tp->want_status);
+    } else if (got_status < 0 && strcmp(STR(buf), tp->orig_reply) != 0) {
+       ptest_error(t, "smtp_reply_footer result: got \"%s\", want \"%s\"",
+                   STR(buf), tp->orig_reply);
+    } else if (got_status == 0 && strcmp(STR(buf), tp->new_reply) != 0) {
+       ptest_error(t, "smtp_reply_footer result: got \"%s\", want \"%s\"",
+                   STR(buf), tp->new_reply);
+    }
+    vstring_free(buf);
+}
+
+const PTEST_CASE ptestcases[] = {
+    {"missing reply", test_footer, "", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
+    {"long smtp_code", test_footer, "1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
+    {"short smtp_code", test_footer, "12 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
+    {"good+bad smtp_code", test_footer, "321 foo\r\n1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0},
+    {"1-line no dsn", test_footer, "550 Foo", "\\c footer", NO_FILTER, NO_ERROR, "550 Foo footer"},
+    {"1-line no dsn", test_footer, "550 Foo", "Bar", NO_FILTER, NO_ERROR, "550-Foo\r\n550 Bar"},
+    {"2-line no dsn", test_footer, "550-Foo\r\n550 Bar", "Baz", NO_FILTER, NO_ERROR, "550-Foo\r\n550-Bar\r\n550 Baz"},
+    {"1-line with dsn", test_footer, "550 5.1.1 Foo", "Bar", NO_FILTER, NO_ERROR, "550-5.1.1 Foo\r\n550 5.1.1 Bar"},
+    {"2-line with dsn", test_footer, "550-5.1.1 Foo\r\n450 4.1.1 Bar", "Baz", NO_FILTER, NO_ERROR, "550-5.1.1 Foo\r\n450-4.1.1 Bar\r\n450 4.1.1 Baz"},
+    {"bad macro", test_footer, "220 myhostname", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0, "truncated macro reference"},
+    {"bad macroCRLF", test_footer, "220 myhostname\r\n", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0, "truncated macro reference"},
+    {"good macro", test_footer, "220 myhostname", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY"},
+    {"good macroCRLF", test_footer, "220 myhostname\r\n", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY\r\n"},
+};
+
+#include <ptest_main.h>
similarity index 93%
rename from postfix/src/global/test_main.c
rename to postfix/src/global/test_server_main.c
index a783ce354618dc79feb498f07fbda23b6987d663..936d897d9e44cdd85c7612c78d75f2a35fcf6747 100644 (file)
@@ -1,12 +1,12 @@
 /*++
 /* NAME
-/*     test_main 3
+/*     test_server_main 3
 /* SUMMARY
 /*     test main program
 /* SYNOPSIS
-/*     #include <test_main.h>
+/*     #include <test_server_main.h>
 /*
-/*     NORETURN test_main(argc, argv, test_driver, key, value, ...)
+/*     NORETURN test_server_main(argc, argv, test_driver, key, value, ...)
 /*     int     argc;
 /*     char    **argv;
 /*     void    (*test_driver)(int argc, char **argv);
 /*     This module implements a test main program for stand-alone
 /*     module tests.
 /*
-/*     test_main() should be called from a main program. It does
+/*     test_server_main() should be called from a main program. It does
 /*     generic command-line options processing, and initializes
 /*     configurable parameters. After calling the test_driver()
-/*     function, the test_main() function terminates.
+/*     function, the test_server_main() function terminates.
 /*
 /*     Arguments:
 /* .IP "void (*test_driver)(int argc, char **argv)"
@@ -27,7 +27,7 @@
 /*     The argc and argv specify the process name and non-option
 /*     command-line arguments.
 /* .PP
-/*     Optional test_main() arguments are specified as a null-terminated
+/*     Optional test_server_main() arguments are specified as a null-terminated
 /*     list with macros that have zero or more arguments:
 /* .IP "CA_TEST_MAIN_INT_TABLE(CONFIG_INT_TABLE *)"
 /*     A table with configurable parameters, to be loaded from the
  /*
   * Test library.
   */
-#include <test_main.h>
+#include <test_server_main.h>
 
 /* test_driver_main - the real main program */
 
-NORETURN test_main(int argc, char **argv, TEST_DRIVER_FN test_driver,...)
+NORETURN test_server_main(int argc, char **argv, TEST_DRIVER_FN test_driver,...)
 {
     const char *myname = "test_driver_main";
     va_list ap;
similarity index 94%
rename from postfix/src/global/test_main.h
rename to postfix/src/global/test_server_main.h
index aea605a1f3cbfdc58e1878718308a1c2ade5d4f4..c2c24719153fc0274c6a730785759aa9e816e027 100644 (file)
@@ -1,10 +1,10 @@
 /*++
 /* NAME
-/*     test_main       3h
+/*     test_server_main        3h
 /* SUMMARY
 /*     test    main program
 /* SYNOPSIS
-/*     #include <test_main.h>
+/*     #include <test_server_main.h>
 /* DESCRIPTION
 /* .nf
 
@@ -45,7 +45,7 @@ CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_NBOOL_TABLE);
 CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_LONG_TABLE);
 
 typedef void (*TEST_DRIVER_FN) (int, char **);
-extern NORETURN test_main(int, char **, TEST_DRIVER_FN,...);
+extern NORETURN test_server_main(int, char **, TEST_DRIVER_FN,...);
 
 /* LICENSE
 /* .ad
index afdbab46d8e3c9857fe54d6586d6a451dade74e8..c21e4ea574ff0dcb92bbdbd59d211277956e0c97 100644 (file)
@@ -93,8 +93,10 @@ alias.o: ../../include/vstream.h
 alias.o: ../../include/vstring.h
 alias.o: alias.c
 alias.o: local.h
+biff_notify.o: ../../include/inet_proto.h
 biff_notify.o: ../../include/iostuff.h
 biff_notify.o: ../../include/msg.h
+biff_notify.o: ../../include/myaddrinfo.h
 biff_notify.o: ../../include/sys_defs.h
 biff_notify.o: biff_notify.c
 biff_notify.o: biff_notify.h
index a6a49258ee65e39fd02d5f3a3828e4052ccb63b6..6aa75a2205ee8dc1f3327cff3863c961993d82be 100644 (file)
 /*     IBM T.J. Watson Research
 /*     P.O. Box 704
 /*     Yorktown Heights, NY 10598, USA
+/*
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
 /*--*/
 
 /* System library. */
 #include "sys_defs.h"
 #include <sys/socket.h>
 #include <netinet/in.h>
-#include <netdb.h>
 #include <string.h>
 
 /* Utility library. */
 
 #include <msg.h>
 #include <iostuff.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
 
 /* Application-specific. */
 
 
 void    biff_notify(const char *text, ssize_t len)
 {
-    static struct sockaddr_in sin;
+    const char myname[] = "biff_notify";
+    const char *hostname = "localhost";
+    const char *servname = "biff";
+    int     sock_type = SOCK_DGRAM;
+    const INET_PROTO_INFO *proto_info;
+    static struct sockaddr_storage sa;
+    static SOCKADDR_SIZE sa_len;
+    static int sa_family;
     static int sock = -1;
-    struct hostent *hp;
-    struct servent *sp;
+    struct addrinfo *res0, *res;
+    int     aierr;
+    int     found;
 
     /*
      * Initialize a socket address structure, or re-use an existing one.
      */
-    if (sin.sin_family == 0) {
-       if ((sp = getservbyname("biff", "udp")) == 0) {
-           msg_warn("service not found: biff/udp");
+    if (sa_len == 0) {
+       if ((aierr = hostname_to_sockaddr(hostname, servname, sock_type,
+                                         &res0)) != 0) {
+           msg_warn("lookup failed for host '%s' or service '%s': %s",
+                    hostname, servname, MAI_STRERROR(aierr));
            return;
        }
-       if ((hp = gethostbyname("localhost")) == 0) {
-           msg_warn("host not found: localhost");
-           return;
+       proto_info = inet_proto_info();
+       for (found = 0, res = res0; !found && res != 0; res = res->ai_next) {
+           if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+               msg_info("skipping address family %d for host '%s' service '%s'",
+                        res->ai_family, hostname, servname);
+               continue;
+           }
+           if (res->ai_addrlen > sizeof(sa)) {
+               msg_warn("skipping address size %d for host '%s' service '%s'",
+                        res->ai_addrlen, hostname, servname);
+               continue;
+           }
+           found++;
+           memcpy(&sa, res->ai_addr, res->ai_addrlen);
+           sa_len = res->ai_addrlen;
+           sa_family = res->ai_family;
+           if (msg_verbose) {
+               MAI_HOSTADDR_STR hostaddr_str;
+               MAI_SERVPORT_STR servport_str;
+
+               SOCKADDR_TO_HOSTADDR((struct sockaddr *) &sa, sa_len,
+                                    &hostaddr_str, &servport_str, 0);
+               msg_info("%s: sending to: {%s, %s}",
+                        myname, hostaddr_str.buf, servport_str.buf);
+           }
        }
-       if ((int) hp->h_length > (int) sizeof(sin.sin_addr)) {
-           msg_warn("bad address size %d for localhost", hp->h_length);
+       freeaddrinfo(res0);
+       if (!found)
            return;
-       }
-       sin.sin_family = hp->h_addrtype;
-       sin.sin_port = sp->s_port;
-       memcpy((void *) &sin.sin_addr, hp->h_addr_list[0], hp->h_length);
     }
 
     /*
      * Open a socket, or re-use an existing one.
      */
     if (sock < 0) {
-       if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+       if ((sock = socket(sa_family, sock_type, 0)) < 0) {
            msg_warn("socket: %m");
            return;
        }
@@ -93,6 +127,6 @@ void    biff_notify(const char *text, ssize_t len)
     /*
      * Biff!
      */
-    if (sendto(sock, text, len, 0, (struct sockaddr *) &sin, sizeof(sin)) != len)
+    if (sendto(sock, text, len, 0, (struct sockaddr *) &sa, sa_len) != len)
        msg_warn("biff_notify: %m");
 }
index 24f26c905410ae53ca9eb0f07130b7dc6b2c2e66..8f34f7db9cc8a9006a6b5e0c3e27890303d4af07 100644 (file)
@@ -10,11 +10,12 @@ OBJS        = postscreen.o postscreen_dict.o postscreen_dnsbl.o \
        postscreen_starttls.o postscreen_expand.o postscreen_endpt.o \
        postscreen_haproxy.o
 HDRS   = 
-TESTSRC        =
+TESTSRC        = postscreen_dnsbl_test.c
 DEFS   = -I. -I$(INC_DIR) -D$(SYSTYPE)
 CFLAGS = $(DEBUG) $(OPT) $(DEFS)
-TESTPROG=
+TESTPROG= postscreen_dnsbl_test
 PROG   = postscreen
+TEST_LIB= ../../lib/libtesting.a ../../lib/libptest.a
 INC_DIR = ../../include
 LIBS   = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
        ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \
@@ -27,14 +28,14 @@ LIBS        = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
 $(PROG): $(OBJS) $(LIBS)
        $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
 
-$(OBJS): ../../conf/makedefs.out
+$(OBJS) $(TESTPROG): ../../conf/makedefs.out
 
 Makefile: Makefile.in
        cat ../../conf/makedefs.out $? >$@
 
 test:  $(TESTPROG)
 
-tests: test
+tests: test_postscreen_dnsbl
 
 root_tests:
 
@@ -48,6 +49,14 @@ clean:
 
 tidy:  clean
 
+postscreen_dnsbl_test: postscreen_dnsbl_test.o postscreen_dnsbl.o \
+       ../../lib/mock_server.o $(TEST_LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o postscreen_dnsbl.o \
+       ../../lib/mock_server.o $(TEST_LIB) $(LIBS) $(SYSLIBS)
+       
+test_postscreen_dnsbl: update postscreen_dnsbl_test 
+       $(SHLIB_ENV) ${VALGRIND} ./postscreen_dnsbl_test
+
 depend: $(MAKES)
        (sed '1,/^# do not edit/!d' Makefile.in; \
        set -e; for i in [a-z][a-z0-9]*.c; do \
@@ -142,6 +151,43 @@ postscreen_dnsbl.o: ../../include/vstream.h
 postscreen_dnsbl.o: ../../include/vstring.h
 postscreen_dnsbl.o: postscreen.h
 postscreen_dnsbl.o: postscreen_dnsbl.c
+postscreen_dnsbl_test.o: ../../include/addr_match_list.h
+postscreen_dnsbl_test.o: ../../include/argv.h
+postscreen_dnsbl_test.o: ../../include/attr.h
+postscreen_dnsbl_test.o: ../../include/check_arg.h
+postscreen_dnsbl_test.o: ../../include/connect.h
+postscreen_dnsbl_test.o: ../../include/dict.h
+postscreen_dnsbl_test.o: ../../include/dict_cache.h
+postscreen_dnsbl_test.o: ../../include/events.h
+postscreen_dnsbl_test.o: ../../include/htable.h
+postscreen_dnsbl_test.o: ../../include/iostuff.h
+postscreen_dnsbl_test.o: ../../include/mail_params.h
+postscreen_dnsbl_test.o: ../../include/mail_proto.h
+postscreen_dnsbl_test.o: ../../include/make_attr.h
+postscreen_dnsbl_test.o: ../../include/maps.h
+postscreen_dnsbl_test.o: ../../include/match_list.h
+postscreen_dnsbl_test.o: ../../include/mock_server.h
+postscreen_dnsbl_test.o: ../../include/msg.h
+postscreen_dnsbl_test.o: ../../include/msg_jmp.h
+postscreen_dnsbl_test.o: ../../include/msg_output.h
+postscreen_dnsbl_test.o: ../../include/msg_vstream.h
+postscreen_dnsbl_test.o: ../../include/myaddrinfo.h
+postscreen_dnsbl_test.o: ../../include/myflock.h
+postscreen_dnsbl_test.o: ../../include/mymalloc.h
+postscreen_dnsbl_test.o: ../../include/myrand.h
+postscreen_dnsbl_test.o: ../../include/nvtable.h
+postscreen_dnsbl_test.o: ../../include/pmock_expect.h
+postscreen_dnsbl_test.o: ../../include/ptest.h
+postscreen_dnsbl_test.o: ../../include/ptest_main.h
+postscreen_dnsbl_test.o: ../../include/server_acl.h
+postscreen_dnsbl_test.o: ../../include/string_list.h
+postscreen_dnsbl_test.o: ../../include/stringops.h
+postscreen_dnsbl_test.o: ../../include/sys_defs.h
+postscreen_dnsbl_test.o: ../../include/vbuf.h
+postscreen_dnsbl_test.o: ../../include/vstream.h
+postscreen_dnsbl_test.o: ../../include/vstring.h
+postscreen_dnsbl_test.o: postscreen.h
+postscreen_dnsbl_test.o: postscreen_dnsbl_test.c
 postscreen_early.o: ../../include/addr_match_list.h
 postscreen_early.o: ../../include/argv.h
 postscreen_early.o: ../../include/check_arg.h
index 69a5e17503dc644ae27d828670b3c7178780cb53..b9ceeeb99b2b3c9809b0b8da27f6fe18b108b674 100644 (file)
@@ -485,6 +485,7 @@ const char *psc_maps_find(MAPS *, const char *, int);
 extern void psc_dnsbl_init(void);
 extern int psc_dnsbl_retrieve(const char *, const char **, int, int *);
 extern int psc_dnsbl_request(const char *, void (*) (int, void *), void *);
+extern void psc_dnsbl_deinit(void);
 
  /*
   * postscreen_tests.c
index 4be962221fa7567a324c275e526fe751aba87ce0..d95e0b4ee651c43f00d40a1c4d7730e53f72c23f 100644 (file)
 /*
 /*     int     psc_dnsbl_retrieve(client_addr, dnsbl_name, dnsbl_index,
 /*                                     dnsbl_ttl)
-/*     char    *client_addr;
+/*     const char *client_addr;
 /*     const char **dnsbl_name;
 /*     int     dnsbl_index;
 /*     int     *dnsbl_ttl;
+/* AUXILIARY FUNCTIONS
+/*     void    psc_dnsbl_deinit(void)
 /* DESCRIPTION
 /*     This module implements preliminary support for DNSBL lookups.
 /*     Multiple requests for the same information are handled with
@@ -44,6 +46,9 @@
 /*     reference count. The reply TTL value is clamped to
 /*     postscreen_dnsbl_min_ttl and postscreen_dnsbl_max_ttl.  It
 /*     is an error to retrieve a score without requesting it first.
+/*
+/*     psc_dnsbl_deinit() tries to reset state so that psc_dnsbl_init()
+/*     can be called again. This is to support tests only.
 /* LICENSE
 /* .ad
 /* .fi
@@ -115,7 +120,7 @@ static HTABLE *dnsbl_site_cache;    /* indexed by DNSBNL domain */
 static HTABLE_INFO **dnsbl_site_list;  /* flattened cache */
 
 typedef struct {
-    const char *safe_dnsbl;            /* from postscreen_dnsbl_reply_map */
+    char   *safe_dnsbl;                        /* from postscreen_dnsbl_reply_map */
     struct PSC_DNSBL_SITE *first;      /* list of (filter, weight) tuples */
 } PSC_DNSBL_HEAD;
 
@@ -480,6 +485,8 @@ static void psc_dnsbl_receive(int event, void *context)
     vstream_fclose(stream);
 }
 
+static int request_count;
+
 /* psc_dnsbl_request  - send dnsbl query, increment reference count */
 
 int     psc_dnsbl_request(const char *client_addr,
@@ -492,7 +499,6 @@ int     psc_dnsbl_request(const char *client_addr,
     HTABLE_INFO **ht;
     PSC_DNSBL_SCORE *score;
     HTABLE_INFO *hash_node;
-    static int request_count;
 
     /*
      * Some spambots make several connections at nearly the same time,
@@ -622,3 +628,72 @@ void    psc_dnsbl_init(void)
     reply_dnsbl = vstring_alloc(100);
     reply_addr = vstring_alloc(100);
 }
+
+ /* Begin code reachable only by tests. */
+
+static void psc_dnsbl_site_free(void *ptr)
+{
+    PSC_DNSBL_SITE *site = (PSC_DNSBL_SITE *) ptr;
+
+    if (site->filter)
+       myfree(site->filter);
+    if (site->byte_codes)
+       myfree(site->byte_codes);
+    if (site->next)
+       psc_dnsbl_site_free(site->next);
+    myfree(site);
+}
+
+static void psc_dnsbl_head_free(void *ptr)
+{
+    PSC_DNSBL_HEAD *head = (PSC_DNSBL_HEAD *) ptr;
+
+    if (head->safe_dnsbl)
+       myfree(head->safe_dnsbl);
+    if (head->first)
+       psc_dnsbl_site_free(head->first);
+    myfree(head);
+};
+
+static void psc_dnsbl_score_free(void *ptr)
+{
+    PSC_DNSBL_SCORE *score = (PSC_DNSBL_SCORE *) ptr;
+
+    myfree(score);
+}
+
+/* psc_dnsbl_deinit - helper for tests only */
+
+void    psc_dnsbl_deinit(void)
+{
+    if (psc_dnsbl_service) {
+       myfree(psc_dnsbl_service);
+       psc_dnsbl_service = 0;
+    }
+    if (dnsbl_site_cache) {
+       htable_free(dnsbl_site_cache, psc_dnsbl_head_free);
+       dnsbl_site_cache = 0;
+    }
+    if (dnsbl_site_list) {
+       myfree(dnsbl_site_list);
+       dnsbl_site_list = 0;
+    }
+    if (dnsbl_score_cache) {
+       htable_free(dnsbl_score_cache, psc_dnsbl_score_free);
+       dnsbl_score_cache = 0;
+    }
+    if (reply_client) {
+       vstring_free(reply_client);
+       reply_client = 0;
+    }
+    if (reply_dnsbl) {
+       vstring_free(reply_dnsbl), reply_dnsbl = 0;
+    }
+    if (reply_addr) {
+       vstring_free(reply_addr);
+       reply_addr = 0;
+    }
+    request_count = 0;
+}
+
+ /* End code reachable only by tests. */
diff --git a/postfix/src/postscreen/postscreen_dnsbl_test.c b/postfix/src/postscreen/postscreen_dnsbl_test.c
new file mode 100644 (file)
index 0000000..5cbcd4d
--- /dev/null
@@ -0,0 +1,569 @@
+ /*
+  * Test program to exercise postscreen_dnsbl.c. See comments in
+  * mock_server.c, and PTEST_README for documented examples of unit tests.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <limits.h>
+
+ /*
+  * Utility library.
+  */
+#include <attr.h>
+#include <events.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+  * Global library.
+  */
+#include <mail_proto.h>
+#include <mail_params.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+#include <make_attr.h>
+#include <mock_server.h>
+
+ /*
+  * Application-specific.
+  */
+#include <postscreen.h>
+
+ /*
+  * Generic case structure. Some scenarios have different test data
+  * structures.
+  */
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+ /*
+  * Structure to capture postscreen_dnsbl_retrieve() inputs and outputs.
+  */
+struct session_state {
+    /* postscreen_dnsbl_retrieve() inputs. */
+    const char *req_addr;              /* Client IP address */
+    int     req_idx;                   /* Request index */
+    /* postscreen_dnsbl_retrieve()  outputs. */
+    const char *got_dnsbl;             /* Null, or biggest contributor */
+    int     got_ttl;                   /* TTL from A or SOA record */
+    int     got_score;                 /* Combined score */
+};
+
+ /*
+  * Surrogates for global variables used, but not defined, by
+  * postscreen_dnsbl.c.
+  */
+int     var_psc_dnsbl_min_ttl;         /* postscreen_dnsbl_min_ttl */
+int     var_psc_dnsbl_max_ttl;         /* postscreen_dnsbl_max_ttl */
+int     var_psc_dnsbl_tmout;           /* postscreen_dnsbl_timeout */
+char   *var_psc_dnsbl_sites;           /* postscreen_dnsbl_sites */
+char   *var_dnsblog_service;           /* dnsblog_service_name */
+DICT   *psc_dnsbl_reply;               /* postscreen_dnsbl_reply_map */
+
+/* deinit_psc_globals - reset globals that this test file depends on */
+
+static void deinit_psc_globals(void)
+{
+
+    /*
+     * deinit_psc_globals() must be idempotent, so that it can be called
+     * safely at the start of a test (to avoid cross talk) and at the end (to
+     * avoid memory leaks).
+     */
+    if (var_psc_dnsbl_sites) {
+       myfree(var_psc_dnsbl_sites);
+       var_psc_dnsbl_sites = 0;
+    }
+    if (psc_dnsbl_reply) {
+       dict_close(psc_dnsbl_reply);
+       psc_dnsbl_reply = 0;
+    }
+
+    /*
+     * Reset postscreen_dnsbl.c internals.
+     */
+    psc_dnsbl_deinit();
+}
+
+/* init_psc_globals - initialize globals that this file depends on */
+
+static void init_psc_globals(const char *dnsbl_sites)
+{
+
+    /*
+     * We call deinit_psc_globals() first, because it may not be called at
+     * the end of a failed test. A failed test should not affect later tests.
+     */
+    deinit_psc_globals();
+
+    /*
+     * Set parameters that postscreen_dnsbl.c depends on.
+     */
+    var_psc_dnsbl_min_ttl = 60;
+    var_psc_dnsbl_max_ttl = 3600;
+    var_psc_dnsbl_tmout = atoi(DEF_PSC_DNSBL_TMOUT);
+    var_psc_dnsbl_sites = mystrdup(dnsbl_sites);
+    var_dnsblog_service = DEF_DNSBLOG_SERVICE;
+
+    /*
+     * postscreen_dnsbl.c mandatory initialization.
+     */
+    psc_dnsbl_init();
+}
+
+/* psc_dnsbl_callback - event handler to retrieve score and ttl */
+
+static void psc_dnsbl_callback(int event, void *context)
+{
+    struct session_state *sp = (struct session_state *) context;
+
+    sp->got_score = psc_dnsbl_retrieve(sp->req_addr, &sp->got_dnsbl,
+                                      sp->req_idx, &sp->got_ttl);
+}
+
+ /*
+  * Test inputs and expected results for a single reputation provider.
+  */
+struct single_dnsbl_data {
+    const char *label;                 /* test label */
+    const char *dnsbl_sites;           /* postscreen_dnsbl_sites */
+    const char *req_dnsbl;             /* in dnsblog request */
+    const char *req_addr;              /* in dnsblog request */
+    const char *res_addr;              /* in dnsblog response */
+    int     res_ttl;                   /* in dnsblog response */
+    int     want_score;                        /* sum of weights */
+};
+
+static const struct single_dnsbl_data single_dnsbl_tests[] = {
+    {
+       "single site listed address",
+        /* dnsbl_sites */ "zen.spamhaus.org",
+        /* req_dnsbl */ "zen.spamhaus.org",
+        /* req_addr */ "127.0.0.2",
+        /* res_addr */ "127.0.0.2 127.0.0.4 127.0.0.10",
+        /* res_ttl */ 60,
+        /* want_score */ 1,
+    },
+    {
+       "repeated site 1x rpc 2x score",
+        /* dnsbl_sites */ "zen.spamhaus.org, zen.spamhaus.org",
+        /* req_dnsbl */ "zen.spamhaus.org",
+        /* req_addr */ "127.0.0.2",
+        /* res_addr */ "127.0.0.2 127.0.0.4 127.0.0.10",
+        /* res_ttl */ 60,
+        /* want_score */ 2,
+    },
+    {
+       "unlisted address zero score",
+        /* dnsbl_sites */ "zen.spamhaus.org",
+        /* req_dnsbl */ "zen.spamhaus.org",
+        /* req_addr */ "127.0.0.1",
+        /* res_addr */ "",
+        /* res_ttl */ 60,
+        /* want_score */ 0,
+    },
+    {
+       "site with weight first",
+        /* dnsbl_sites */ "zen.spamhaus.org*3, zen.spamhaus.org",
+        /* req_dnsbl */ "zen.spamhaus.org",
+        /* req_addr */ "127.0.0.2",
+        /* res_addr */ "127.0.0.2 127.0.0.4 127.0.0.10",
+        /* res_ttl */ 60,
+        /* want_score */ 4,
+    },
+    {
+       "site with weight last",
+        /* dnsbl_sites */ "zen.spamhaus.org, zen.spamhaus.org*3",
+        /* req_dnsbl */ "zen.spamhaus.org",
+        /* req_addr */ "127.0.0.2",
+        /* res_addr */ "127.0.0.2 127.0.0.4 127.0.0.10",
+        /* res_ttl */ 60,
+        /* want_score */ 4,
+    },
+    {
+       "site with filter+weight first",
+        /* dnsbl_sites */ "zen.spamhaus.org=127.0.0.10*3, zen.spamhaus.org",
+        /* req_dnsbl */ "zen.spamhaus.org",
+        /* req_addr */ "127.0.0.2",
+        /* res_addr */ "127.0.0.2 127.0.0.4 127.0.0.10",
+        /* res_ttl */ 60,
+        /* want_score */ 4,
+    },
+    {
+       "site with filter+weight last",
+        /* dnsbl_sites */ "zen.spamhaus.org, zen.spamhaus.org=127.0.0.10*3",
+        /* req_dnsbl */ "zen.spamhaus.org",
+        /* req_addr */ "127.0.0.2",
+        /* res_addr */ "127.0.0.2 127.0.0.4 127.0.0.10",
+        /* res_ttl */ 60,
+        /* want_score */ 4,
+    },
+    {
+       "filter+weight add and subtract",
+        /* dnsbl_sites */ "zen.spamhaus.org=127.0.0.[1..255]*3, zen.spamhaus.org=127.0.0.3*-1",
+        /* req_dnsbl */ "zen.spamhaus.org",
+        /* req_addr */ "10.2.3.4",
+        /* res_addr */ "127.0.0.3 127.0.0.10",
+        /* res_ttl */ 60,
+        /* want_score */ 2,
+    },
+    {
+       "filter+weight add and not subtract",
+        /* dnsbl_sites */ "zen.spamhaus.org=127.0.0.[1..255]*3, zen.spamhaus.org=127.0.0.3*-1",
+        /* req_dnsbl */ "zen.spamhaus.org",
+        /* req_addr */ "10.2.3.4",
+        /* res_addr */ "127.0.0.10",
+        /* res_ttl */ 60,
+        /* want_score */ 3,
+    },
+};
+
+static void test_single_dnsbl(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    MOCK_SERVER *mp;
+    struct session_state session_state;
+    const char *dnsblog_path = "private/dnsblog";
+    VSTRING *serialized_req;
+    VSTRING *serialized_resp;
+    const int request_id = 0;
+    const struct single_dnsbl_data *tt;
+
+    for (tt = single_dnsbl_tests; tt < single_dnsbl_tests
+        + PTEST_NROF(single_dnsbl_tests); tt++) {
+       if (tt->label == 0)
+           ptest_fatal(t, "Null test label in single_dnsbl_tests array!");
+       PTEST_RUN(t, tt->label, {
+
+           /*
+            * Reset global state and parameters used by postscreen_dnsbl.c.
+            */
+           init_psc_globals(tt->dnsbl_sites);
+
+           /*
+            * Instantiate a mock server.
+            */
+           mp = mock_unix_server_create(dnsblog_path);
+
+           /*
+            * Set up the expected dnsblog request, and the corresponding
+            * response. The mock dnsblog server immediately generates a read
+            * event request, so we should send something soon.
+            */
+           serialized_req =
+               make_attr(ATTR_FLAG_NONE,
+                         SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, tt->req_dnsbl),
+                         SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR,
+                                       tt->req_addr),
+                         SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id),
+                         ATTR_TYPE_END);
+           serialized_resp =
+               make_attr(ATTR_FLAG_NONE,
+                         SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, tt->req_dnsbl),
+                         SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR,
+                                       tt->req_addr),
+                         SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id),
+                         SEND_ATTR_STR(MAIL_ATTR_RBL_ADDR, tt->res_addr),
+                         SEND_ATTR_INT(MAIL_ATTR_TTL, tt->res_ttl),
+                         ATTR_TYPE_END);
+           mock_server_interact(mp, serialized_req, serialized_resp);
+
+           /*
+            * Send a request by calling psc_dnsbl_request(), and run the
+            * event loop once to notify the mock dnsblog server that a
+            * request is pending. The mock dnsblog server will receive the
+            * request, and if it matches the expected request, the mock
+            * dnsblog server will immediately send the prepared response.
+            */
+           session_state.req_addr = tt->req_addr;
+           session_state.got_dnsbl = 0;
+           session_state.got_ttl = INT_MAX;
+           session_state.got_score = INT_MAX;
+           session_state.req_idx = psc_dnsbl_request(tt->req_addr,
+                                                     psc_dnsbl_callback,
+                                                     &session_state);
+           event_loop(2);
+
+           /*
+            * Run the event loop another time to wake up
+            * psc_dnsbl_receive(). That function will deserialize the mock
+            * dnsblog server's response, and will immediately call our
+            * psc_dnsbl_callback() function to store the result into the
+            * session_state object.
+            */
+           event_loop(2);
+
+           /*
+            * Validate the response.
+            */
+           if (session_state.got_ttl == INT_MAX) {
+               ptest_error(t, "psc_dnsbl_callback() was not called, "
+                           "or did not update the session_state");
+           } else {
+               if (session_state.got_ttl != tt->res_ttl)
+                   ptest_error(t, "unexpected ttl: got %d, want %d",
+                               session_state.got_ttl, tt->res_ttl);
+               if (session_state.got_score != tt->want_score)
+                   ptest_error(t, "unexpected score: got %d, want %d",
+                               session_state.got_score, tt->want_score);
+           }
+
+           /*
+            * Clean up.
+            */
+           vstring_free(serialized_req);
+           vstring_free(serialized_resp);
+           mock_server_free(mp);
+           deinit_psc_globals();
+       });
+    }
+}
+
+ /*
+  * Test inputs and expected results for multiple reputation providers.
+  */
+struct dnsbl_data {
+    const char *req_dnsbl;             /* in dnsblog request */
+    const char *res_addr;              /* in dnsblog response */
+    int     res_ttl;                   /* in dnsblog response */
+};
+
+#define MAX_DNSBL_SITES        3
+
+struct multi_dnsbl_data {
+    const char *label;                 /* test label */
+    const char *dnsbl_sites;           /* postscreen_dnsbl_sites */
+    const char *req_addr;              /* in dnsblog request */
+    struct dnsbl_data dnsbl_data[MAX_DNSBL_SITES];
+    int            want_ttl;                   /* effective TTL */
+    int     want_score;                        /* sum of weights */
+};
+
+static const struct multi_dnsbl_data multi_dnsbl_tests[] = {
+    {
+       "dual dnsbl, client listed by both",
+        /* dnsbl_sites */ "zen.spamhaus.org, foo.example.org",
+        /* req_addr */ "10.2.3.4",
+        /* dnsbl_data */ {
+           {
+                /* req_dnsbl */ "foo.example.org",
+                /* res_addr */ "127.0.0.10",
+                /* res_ttl */ 60,
+           },
+           {
+                /* req_dnsbl */ "zen.spamhaus.org",
+                /* res_addr */ "127.0.0.10",
+                /* res_ttl */ 60,
+           },
+       },
+        /* want_ttl */ 60,
+        /* want_score */ 2,
+    }, {
+       "dual dnsbl, client listed by first",
+        /* dnsbl_sites */ "zen.spamhaus.org, foo.example.org",
+        /* req_addr */ "10.2.3.4",
+        /* dnsbl_data */ {
+           {
+                /* req_dnsbl */ "foo.example.org",
+                /* res_addr */ "",
+                /* res_ttl */ 62,
+           },
+           {
+                /* req_dnsbl */ "zen.spamhaus.org",
+                /* res_addr */ "127.0.0.10",
+                /* res_ttl */ 61,
+           },
+       },
+        /* want_ttl */ 61,
+        /* want_score */ 1,
+    }, {
+       "dual dnsbl, client listed by last",
+        /* dnsbl_sites */ "zen.spamhaus.org, foo.example.org",
+        /* req_addr */ "10.2.3.4",
+        /* dnsbl_data */ {
+           {
+                /* req_dnsbl */ "foo.example.org",
+                /* res_addr */ "127.0.0.10",
+                /* res_ttl */ 62,
+           },
+           {
+                /* req_dnsbl */ "zen.spamhaus.org",
+                /* res_addr */ "",
+                /* res_ttl */ 61,
+           },
+       },
+        /* want_ttl */ 62,
+        /* want_score */ 1,
+    }, {
+       "dual dnsbl, unlisted address zero score",
+        /* dnsbl_sites */ "zen.spamhaus.org, foo.example.org",
+        /* req_addr */ "10.2.3.4",
+        /* dnsbl_data */ {
+           {
+                /* req_dnsbl */ "foo.example.org",
+                /* res_addr */ "",
+                /* res_ttl */ 62,
+           },
+           {
+                /* req_dnsbl */ "zen.spamhaus.org",
+                /* res_addr */ "",
+                /* res_ttl */ 61,
+           },
+       },
+        /* want_ttl */ 61,
+        /* want_score */ 0,
+    }, {
+       "dual dnsbl, allowlist wins",
+        /* dnsbl_sites */ "list.dnswl.org=127.0.[0..255].[1..3]*-2, foo.example.org",
+        /* req_addr */ "10.2.3.4",
+        /* dnsbl_data */ {
+           {
+                /* req_dnsbl */ "foo.example.org",
+                /* res_addr */ "127.0.0.10",
+                /* res_ttl */ 62,
+           },
+           {
+                /* req_dnsbl */ "list.dnswl.org",
+                /* res_addr */ "127.0.5.2",
+                /* res_ttl */ 61,
+           },
+       },
+        /* want_ttl */ 61,
+        /* want_score */ -1,
+    }
+};
+
+static void test_multi_dnsbl(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    MOCK_SERVER *mp[MAX_DNSBL_SITES];
+    struct session_state session_state;
+    const char *dnsblog_path = "private/dnsblog";
+    const int request_id = 0;
+    const struct multi_dnsbl_data *tt;
+    const struct dnsbl_data *dp;
+    int     n;
+
+    for (tt = multi_dnsbl_tests; tt < multi_dnsbl_tests
+        + PTEST_NROF(multi_dnsbl_tests); tt++) {
+       if (tt->label == 0)
+           ptest_fatal(t, "Null test label in multi_dnsbl_tests array!");
+       PTEST_RUN(t, tt->label, {
+
+           /*
+            * Reset global state and parameters used by postscreen_dnsbl.c.
+            */
+           init_psc_globals(tt->dnsbl_sites);
+
+           for (n = 0, dp = tt->dnsbl_data; n < MAX_DNSBL_SITES
+                && dp[n].req_dnsbl != 0; n++) {
+               VSTRING *serialized_req;
+               VSTRING *serialized_resp;
+
+               /*
+                * Instantiate a mock server.
+                */
+               if ((mp[n] = mock_unix_server_create(dnsblog_path)) == 0)
+                   ptest_fatal(t, "mock_unix_server_create: %m");
+
+               /*
+                * Set up the expected dnsblog requests, and the
+                * corresponding responses. The mock dnsblog server
+                * immediately generates read event requests, so we should
+                * send something soon.
+                */
+               serialized_req =
+                   make_attr(ATTR_FLAG_NONE,
+                             SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN,
+                                           dp[n].req_dnsbl),
+                             SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR,
+                                           tt->req_addr),
+                             SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id),
+                             ATTR_TYPE_END);
+               serialized_resp =
+                   make_attr(ATTR_FLAG_NONE,
+                             SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN,
+                                           dp[n].req_dnsbl),
+                             SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR,
+                                           tt->req_addr),
+                             SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id),
+                             SEND_ATTR_STR(MAIL_ATTR_RBL_ADDR,
+                                           dp[n].res_addr),
+                             SEND_ATTR_INT(MAIL_ATTR_TTL,
+                                           dp[n].res_ttl),
+                             ATTR_TYPE_END);
+               mock_server_interact(mp[n], serialized_req,
+                                         serialized_resp);
+               vstring_free(serialized_req);
+               vstring_free(serialized_resp);
+           }
+
+           /*
+            * Send a request by calling psc_dnsbl_request(), and run the
+            * event loop once to notify the mock dnsblog servers that a
+            * request is pending. Each mock dnsblog server will receive a
+            * request, and if it matches the expected request, the mock
+            * dnsblog server will immediately send the prepared response.
+            */
+           session_state.req_addr = tt->req_addr;
+           session_state.got_dnsbl = 0;
+           session_state.got_ttl = INT_MAX;
+           session_state.got_score = INT_MAX;
+           session_state.req_idx = psc_dnsbl_request(tt->req_addr,
+                                                     psc_dnsbl_callback,
+                                                     &session_state);
+           event_loop(2);
+
+           /*
+            * Run the event loop again, to wake up psc_dnsbl_receive(). That
+            * function will deserialize the mock dnsblog server's response,
+            * and will immediately call our psc_dnsbl_callback() function to
+            * store the result into the session_state object.
+            */
+           event_loop(2);
+
+           /*
+            * Validate the response.
+            */
+           if (session_state.got_ttl == INT_MAX) {
+               ptest_error(t, "psc_dnsbl_callback() was not called, "
+                           "or did not update the session_state");
+           } else {
+               if (session_state.got_ttl != tt->want_ttl)
+                   ptest_error(t, "unexpected ttl: got %d, want %d",
+                               session_state.got_ttl, tt->want_ttl);
+               if (session_state.got_score != tt->want_score)
+                   ptest_error(t, "unexpected score: got %d, want %d",
+                               session_state.got_score, tt->want_score);
+           }
+
+           /*
+            * Clean up.
+            */
+           for (n = 0, dp = tt->dnsbl_data; n < MAX_DNSBL_SITES
+                && dp[n].req_dnsbl != 0; n++)
+               mock_server_free(mp[n]);
+           deinit_psc_globals();
+       });
+    }
+}
+
+ /*
+  * Test cases.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+       "single dnsbl", test_single_dnsbl,
+    },
+    {
+       "multi dnsbl", test_multi_dnsbl,
+    },
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/ptest/Makefile.in b/postfix/src/ptest/Makefile.in
new file mode 100644 (file)
index 0000000..102010b
--- /dev/null
@@ -0,0 +1,174 @@
+SHELL  = /bin/sh
+SRCS   = ptest_ctx.c ptest_error.c ptest_log.c pmock_expect.c ptest_run.c \
+       msg_jmp.c
+LIB_OBJ        = ptest_ctx.o ptest_error.o ptest_log.o pmock_expect.o ptest_run.o \
+       msg_jmp.o
+HDRS   = ptest.h pmock_expect.h ptest_main.h msg_jmp.h
+TESTSRC        =
+DEFS   = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL   =
+LIB    = libptest.a
+TESTPROG= pmock_expect_test ptest_log_test
+
+LIBS    = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+       ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR        = ../../lib
+INC_DIR        = ../../include
+MAKES  =
+
+.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
+
+all: $(LIB_OBJ) $(MOCK_OBJ)
+
+$(LIB_OBJ) $(TESTPROG): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+       cat ../../conf/makedefs.out $? >$@
+
+test:  $(TESTPROG)
+
+$(LIB): $(LIB_OBJ)
+       $(_AR) $(ARFL) $(LIB) $?
+       $(_RANLIB) $(LIB)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+       cp $(LIB) $(LIB_DIR)
+       $(_RANLIB) $(LIB_DIR)/$(LIB)
+
+update: $(LIB_DIR)/$(LIB) $(HDRS) $(MOCK_OBJ)
+       -for i in $(HDRS); \
+       do \
+         cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+       done
+       (cd $(INC_DIR); chmod 644 $(HDRS))
+       -for i in $(MOCK_OBJ); \
+       do \
+         cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \
+       done
+
+clean:
+       rm -f *.o $(LIB) *core $(TESTPROG) junk $(MAKES) *.tmp
+
+tidy:   clean
+
+tests: update test_pmock_expect test_ptest_log
+
+pmock_expect_test: pmock_expect_test.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o $(LIB) $(LIBS) $(SYSLIBS)
+
+test_pmock_expect: update pmock_expect_test
+       $(SHLIB_ENV) ${VALGRIND} ./pmock_expect_test
+
+ptest_log_test: ptest_log_test.o ptest_log.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o ptest_log.o $(LIB) $(LIBS) $(SYSLIBS)
+
+test_ptest_log: update ptest_log_test
+       $(SHLIB_ENV) ${VALGRIND} ./ptest_log_test
+
+depend: $(MAKES)
+       (sed '1,/^# do not edit/!d' Makefile.in; \
+       set -e; for i in [a-z][a-z0-9]*.c; do \
+           $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+           -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \
+           -e 's/o: \.\//o: /' -e p -e '}' ; \
+       done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+       @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+# do not edit below this line - it is generated by 'make depend'
+msg_jmp.o: ../../include/msg.h
+msg_jmp.o: ../../include/sys_defs.h
+msg_jmp.o: msg_jmp.c
+msg_jmp.o: msg_jmp.h
+pmock_expect.o: ../../include/argv.h
+pmock_expect.o: ../../include/check_arg.h
+pmock_expect.o: ../../include/htable.h
+pmock_expect.o: ../../include/msg.h
+pmock_expect.o: ../../include/mymalloc.h
+pmock_expect.o: ../../include/sys_defs.h
+pmock_expect.o: ../../include/vbuf.h
+pmock_expect.o: ../../include/vstream.h
+pmock_expect.o: ../../include/vstring.h
+pmock_expect.o: msg_jmp.h
+pmock_expect.o: pmock_expect.c
+pmock_expect.o: pmock_expect.h
+pmock_expect.o: ptest.h
+pmock_expect_test.o: ../../include/argv.h
+pmock_expect_test.o: ../../include/check_arg.h
+pmock_expect_test.o: ../../include/msg.h
+pmock_expect_test.o: ../../include/msg_output.h
+pmock_expect_test.o: ../../include/msg_vstream.h
+pmock_expect_test.o: ../../include/mymalloc.h
+pmock_expect_test.o: ../../include/myrand.h
+pmock_expect_test.o: ../../include/stringops.h
+pmock_expect_test.o: ../../include/sys_defs.h
+pmock_expect_test.o: ../../include/vbuf.h
+pmock_expect_test.o: ../../include/vstream.h
+pmock_expect_test.o: ../../include/vstring.h
+pmock_expect_test.o: msg_jmp.h
+pmock_expect_test.o: pmock_expect.h
+pmock_expect_test.o: pmock_expect_test.c
+pmock_expect_test.o: ptest.h
+pmock_expect_test.o: ptest_main.h
+ptest_ctx.o: ../../include/argv.h
+ptest_ctx.o: ../../include/check_arg.h
+ptest_ctx.o: ../../include/msg.h
+ptest_ctx.o: ../../include/mymalloc.h
+ptest_ctx.o: ../../include/stringops.h
+ptest_ctx.o: ../../include/sys_defs.h
+ptest_ctx.o: ../../include/vbuf.h
+ptest_ctx.o: ../../include/vstream.h
+ptest_ctx.o: ../../include/vstring.h
+ptest_ctx.o: msg_jmp.h
+ptest_ctx.o: ptest.h
+ptest_ctx.o: ptest_ctx.c
+ptest_error.o: ../../include/argv.h
+ptest_error.o: ../../include/check_arg.h
+ptest_error.o: ../../include/msg.h
+ptest_error.o: ../../include/mymalloc.h
+ptest_error.o: ../../include/sys_defs.h
+ptest_error.o: ../../include/vbuf.h
+ptest_error.o: ../../include/vstream.h
+ptest_error.o: ../../include/vstring.h
+ptest_error.o: msg_jmp.h
+ptest_error.o: ptest.h
+ptest_error.o: ptest_error.c
+ptest_log.o: ../../include/argv.h
+ptest_log.o: ../../include/check_arg.h
+ptest_log.o: ../../include/msg.h
+ptest_log.o: ../../include/msg_output.h
+ptest_log.o: ../../include/sys_defs.h
+ptest_log.o: ../../include/vbuf.h
+ptest_log.o: ../../include/vstream.h
+ptest_log.o: ../../include/vstring.h
+ptest_log.o: msg_jmp.h
+ptest_log.o: ptest.h
+ptest_log.o: ptest_log.c
+ptest_log_test.o: ../../include/argv.h
+ptest_log_test.o: ../../include/check_arg.h
+ptest_log_test.o: ../../include/msg.h
+ptest_log_test.o: ../../include/msg_output.h
+ptest_log_test.o: ../../include/msg_vstream.h
+ptest_log_test.o: ../../include/myrand.h
+ptest_log_test.o: ../../include/stringops.h
+ptest_log_test.o: ../../include/sys_defs.h
+ptest_log_test.o: ../../include/vbuf.h
+ptest_log_test.o: ../../include/vstream.h
+ptest_log_test.o: ../../include/vstring.h
+ptest_log_test.o: msg_jmp.h
+ptest_log_test.o: pmock_expect.h
+ptest_log_test.o: ptest.h
+ptest_log_test.o: ptest_log_test.c
+ptest_log_test.o: ptest_main.h
+ptest_run.o: ../../include/argv.h
+ptest_run.o: ../../include/check_arg.h
+ptest_run.o: ../../include/msg.h
+ptest_run.o: ../../include/msg_vstream.h
+ptest_run.o: ../../include/sys_defs.h
+ptest_run.o: ../../include/vbuf.h
+ptest_run.o: ../../include/vstream.h
+ptest_run.o: ../../include/vstring.h
+ptest_run.o: msg_jmp.h
+ptest_run.o: pmock_expect.h
+ptest_run.o: ptest.h
+ptest_run.o: ptest_run.c
diff --git a/postfix/src/ptest/msg_jmp.c b/postfix/src/ptest/msg_jmp.c
new file mode 100644 (file)
index 0000000..0535ea5
--- /dev/null
@@ -0,0 +1,77 @@
+/*++
+/* NAME
+/*     msg_jmp 3
+/* SUMMARY
+/*     msg plugin for tests
+/* SYNOPSIS
+/*     #include <msg_jmp.h>
+/*
+/*     int     msg_setjmp(MSG_JMP_BUF *bufp)
+/*
+/*     void    msg_resetjmp(MSG_JMP_BUF *bufp)
+/*
+/*     void    msg_clearjmp(void)
+/* DESCRIPTION
+/*     The default action for msg_fatal*() and msg_panic() is to terminate
+/*     the program. To support non-production tests that must verify
+/*     that these calls are actually made, the following functions
+/*     implement support for long jumps instead of process termination.
+/*     This code uses the msg_bailout_action() call-back feature.
+/*
+/*     msg_setjmp() specifies a caller-specified buffer and saves state
+/*     for a future long jump. The buffer lifetime must extend to the
+/*     next msg_resetjmp() or msg_clearjmp() call.
+/*
+/*     In-between the msg_setjmp() and msg_clearjmp() calls, msg_fatal*()
+/*     and msg_panic() will perform a long jump instead of terminating
+/*     the program.
+/*
+/*     msg_resetjmp() restores state that was previously saved with
+/*     msg_setjmp(). The buffer lifetime must extend to the next
+/*     msg_resetjmp() or msg_clearjmp() call.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+ /*
+  * System libraries.
+  */
+#include <sys_defs.h>
+#include <signal.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+
+ /*
+  * Ptest library.
+  */
+#include <msg_jmp.h>
+
+ /*
+  * Private state. But it has to be caller-visible
+because msg_setjmp() cannot be a function.
+  */
+MSG_JMP_BUF *msg_jmp_bufp;
+
+/* msg_longjmp - restore setjmp() previously-saved run-time state */
+
+NORETURN msg_longjmp(int value)
+{
+#ifdef NO_SIGSETJMP
+    longjmp(msg_jmp_bufp[0], value);
+#else
+    siglongjmp(msg_jmp_bufp[0], value);
+#endif
+}
diff --git a/postfix/src/ptest/msg_jmp.h b/postfix/src/ptest/msg_jmp.h
new file mode 100644 (file)
index 0000000..92e1a19
--- /dev/null
@@ -0,0 +1,63 @@
+#ifndef _MSG_JMP_H_INCLUDED_
+#define _MSG_JMP_H_INCLUDED_
+
+/*++
+/* NAME
+/*     msg_jmp 3h
+/* SUMMARY
+/*     msg plugin for tests
+/* SYNOPSIS
+/*     #include <msg_jmp.h>
+/* DESCRIPTION
+/*     .nf
+
+/*
+ * System library.
+ */
+#include <setjmp.h>
+
+/*
+ * Utility library.
+ */
+#include <msg.h>
+
+/*
+ * External interface.
+ */
+
+ /*
+  * Only for tests: make a long jump instead of terminating.
+  */
+#ifdef NO_SIGSETJMP
+#define MSG_JMP_BUF jmp_buf
+#define msg_setjmp(bufp)        (msg_set_longjmp_action(msg_longjmp), \
+                                   setjmp((msg_jmp_bufp = (bufp))[0]))
+#else
+#define MSG_JMP_BUF sigjmp_buf
+#define msg_setjmp(bufp)        (msg_set_longjmp_action(msg_longjmp), \
+                                   sigsetjmp((msg_jmp_bufp = (bufp))[0], 1))
+#endif
+#define msg_resetjmp(bufp)      do { msg_jmp_bufp = (bufp); } while (0)
+#define msg_clearjmp()          do { \
+                                   msg_set_longjmp_action(0); \
+                                   msg_jmp_bufp = 0; \
+                               } while (0)
+
+extern MSG_JMP_BUF *msg_jmp_bufp;
+extern NORETURN msg_longjmp(int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+#endif
diff --git a/postfix/src/ptest/pmock_expect.c b/postfix/src/ptest/pmock_expect.c
new file mode 100644 (file)
index 0000000..e6dd225
--- /dev/null
@@ -0,0 +1,286 @@
+/*++
+/* NAME
+/*     pmock_expect 3h
+/* SUMMARY
+/*     mock support for hermetic tests
+/* SYNOPSIS
+/*     #include <pmock_expect.h>
+/*
+/*     MOCK_EXPECT *pmock_expect_create(
+/*     const MOCK_APPL_SIG *sig,
+/*     const char *file,
+/*     int     line,
+/*     int     calls_expected,
+/*     ssize_t size)
+/*
+/*     int     pmock_expect_apply(
+/*     const MOCK_APPL_SIG *sig,
+/*     const MOCK_EXPECT *inputs,
+/*     void *targets)
+/*
+/*     void    pmock_expect_free(MOCK_EXPECT *me)
+/*
+/*     void    pmock_expect_wrapup(PTEST_CTX *t)
+/* DESCRIPTION
+/*     This module provides support to implement mock functions
+/*     that emulate real functions with the same name, but that
+/*     respond to calls with prepared outputs. This requires that
+/*     the real function has the "MOCKABLE" annotation.
+/*
+/*     For a simple example, see the pmock_expect_test.c file.
+/*
+/*     pmock_expect_create() creates an expectation for calls into
+/*     a mock function (whose details are given with the MOCK_APPL_SIG
+/*     argument). pmock_expect_create() initializes the generic
+/*     expectation fields (file name, line number, and number of
+/*     calls), and appends the resulting object to a dedicated
+/*     list for the user-defined mock function. The pmock_expect_create()
+/*     caller must save deep copies of the expected inputs and
+/*     prepared outputs.
+/*
+/*     pmock_expect_apply() takes an inputs argument with mock call
+/*     inputs, and looks up a matching expectation. If a match is
+/*     found, and if its call count isn't already saturated,
+/*     pmock_expect_apply() uses the targets argument to update the
+/*     mock call outputs.
+/*
+/*     pmock_expect_wrapup() reports unused expectations, and
+/*     destroys all expectations. Subsequent calls of this function
+/*     do nothing.
+/* DIAGNOSTICS
+/*     pmock_expect_apply() returns 'true' when a match is found
+/*     and the match is not saturated. Otherwise, it returns 'false'
+/*     after generating a "too many calls" or "unexpected call"
+/*     error. If that error is expected (with expect_ptest_error()),
+/*     then it is ignored (not reported) and it will not count as
+/*     a test failure.
+/*
+/*     pmock_expect_wrapup() logs a warning when some expectation
+/*     has not been used.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+
+ /*
+  * Utility library.
+  */
+#include <mymalloc.h>
+#include <htable.h>
+
+ /*
+  * Testing library.
+  */
+#include <pmock_expect.h>
+#include <ptest.h>
+
+ /*
+  * Private structure with all expectations for a single mock application.
+  */
+typedef struct MOCK_APPL {
+    const MOCK_APPL_SIG *sig;          /* application-specific */
+    MOCK_EXPECT *head;                 /* first expectation */
+    MOCK_EXPECT *tail;                 /* last expectation */
+} MOCK_APPL;
+
+ /*
+  * Collection of MOCK_APPL instances indexed by application name.
+  */
+static HTABLE *mock_appl_list;
+
+/* mock_appl_create - create empty list for same-type expectations */
+
+static MOCK_APPL *mock_appl_create(const MOCK_APPL_SIG *sig)
+{
+    MOCK_APPL *ma;
+
+    /*
+     * Initialize self.
+     */
+    ma = (MOCK_APPL *) mymalloc(sizeof(*ma));
+    ma->sig = sig;
+    ma->head = ma->tail = 0;
+    return (ma);
+}
+
+/* mock_appl_list_free - destroy application node */
+
+static void mock_appl_list_free(MOCK_APPL *ma)
+{
+    myfree(ma);
+}
+
+/* pmock_expect_create - create one mock expectation */
+
+MOCK_EXPECT *pmock_expect_create(const MOCK_APPL_SIG *sig, const char *file,
+                                        int line, int calls_expected,
+                                        ssize_t size)
+{
+    MOCK_APPL *ma;
+    MOCK_EXPECT *me;
+
+    /*
+     * Look up or instantiate the expectation for this mock application.
+     */
+    if (mock_appl_list == 0)
+       mock_appl_list = htable_create(13);
+    if ((ma = (MOCK_APPL *) htable_find(mock_appl_list, sig->name)) == 0) {
+       ma = mock_appl_create(sig);
+       (void) htable_enter(mock_appl_list, sig->name, (void *) ma);
+    }
+
+    /*
+     * Initialize the generic expectation fields.
+     */
+    me = (MOCK_EXPECT *) mymalloc(size);
+    me->file = mystrdup(file);
+    me->line = line;
+    me->calls_expected = calls_expected;
+    me->calls_made = 0;
+    me->next = 0;
+
+    /*
+     * Append the new expectation to this mock application list.
+     */
+    if (ma->head == 0)
+       ma->head = me;
+    else
+       ma->tail->next = me;
+    ma->tail = me;
+
+    /*
+     * Let the caller fill in their application-specific fields.
+     */
+    return (me);
+}
+
+/* pmock_expect_free - destroy one expectation node */
+
+void    pmock_expect_free(MOCK_EXPECT *me)
+{
+    myfree(me->file);
+    myfree(me);
+}
+
+/* pmock_expect_apply - match inputs and apply outputs */
+
+int     pmock_expect_apply(const MOCK_APPL_SIG *sig,
+                                  const MOCK_EXPECT *inputs,
+                                  void *targets)
+{
+    MOCK_APPL *ma;
+    MOCK_EXPECT *me;
+    MOCK_EXPECT *saturated = 0;                /* saturated expectation */
+    VSTRING *buf;
+    PTEST_CTX *t;
+
+    /*
+     * Look up the mock application list.
+     */
+    if (mock_appl_list != 0 && (ma = (MOCK_APPL *)
+                            htable_find(mock_appl_list, sig->name)) != 0) {
+
+       /*
+        * Look for an expectation match that is not saturated. Remember the
+        * last saturated match.
+        */
+       for (me = ma->head; me != 0; me = me->next) {
+           const MOCK_APPL_SIG *sig = ma->sig;
+
+           if (sig->match_expect == 0 || sig->match_expect(me, inputs)) {
+               if (me->calls_expected == 0
+                   || me->calls_made < me->calls_expected) {
+                   if (sig->assign_expect)
+                       sig->assign_expect(me, targets);
+                   me->calls_made += 1;
+                   return (1);
+               } else {
+                   saturated = me;
+               }
+           }
+       }
+    }
+
+    /*
+     * Report a saturated or unmatched expectation.
+     */
+    buf = vstring_alloc(100);
+    t = ptest_ctx_current();
+    if (saturated != 0) {
+       ptest_error(t, "%s:%d too many calls: %s(%s)",
+                   saturated->file, saturated->line, sig->name,
+                   sig->print_expect(saturated, buf));
+    } else {
+       ptest_error(t, "unexpected call: %s(%s)", sig->name,
+                   sig->print_expect(inputs, buf));
+    }
+    vstring_free(buf);
+    return (0);
+}
+
+/* pmock_expect_wrapup - report unused expectations and clean up */
+
+void    pmock_expect_wrapup(PTEST_CTX *t)
+{
+    HTABLE_INFO **info, **ht;
+    MOCK_APPL *ma;
+    MOCK_EXPECT *me, *next_me;
+    VSTRING *buf = 0;
+    const char *plural[] = {"", "s"};
+
+    /*
+     * Iterate over each mock application.
+     * 
+     * NOTE: do not call ptest_fatal(). This code runs after the test has
+     * completed.
+     */
+    if (mock_appl_list != 0) {
+       info = htable_list(mock_appl_list);
+       for (ht = info; *ht; ht++) {
+           ma = (MOCK_APPL *) ht[0]->value;
+
+           /*
+            * Iterate over each expectation.
+            */
+           for (me = ma->head; me != 0; me = next_me) {
+               next_me = me->next;
+               if (me->calls_expected > 0
+                   && me->calls_expected > me->calls_made) {
+                   ma->sig->print_expect(me, buf ? buf :
+                                         (buf = vstring_alloc(100)));
+                   ptest_error(t, "%s:%d got %d call%s for %s(%s), want %d",
+                               me->file, me->line, me->calls_made,
+                               plural[me->calls_made != 1],
+                               ma->sig->name, vstring_str(buf),
+                               me->calls_expected);
+               } else if (me->calls_made == 0) {
+                   ma->sig->print_expect(me, buf ? buf :
+                                         (buf = vstring_alloc(100)));
+                   ptest_error(t, "%s:%d got 0 calls for %s(%s), want 1 or more",
+                               me->file, me->line, ma->sig->name,
+                               vstring_str(buf));
+               }
+               ma->sig->free_expect(me);
+           }
+           htable_delete(mock_appl_list, ma->sig->name, (void (*) (void *)) 0);
+           mock_appl_list_free(ma);
+       }
+       if (buf)
+           vstring_free(buf);
+       myfree(info);
+    }
+    if (mock_appl_list != 0 && mock_appl_list->used != 0)
+       ptest_error(t, "pmock_expect_wrapup: mock_appl_list->used is %ld",
+                   (long) mock_appl_list->used);
+}
diff --git a/postfix/src/ptest/pmock_expect.h b/postfix/src/ptest/pmock_expect.h
new file mode 100644 (file)
index 0000000..d267c93
--- /dev/null
@@ -0,0 +1,78 @@
+#ifndef _PMOCK_EXPECT_H_INCLUDED_
+#define _PMOCK_EXPECT_H_INCLUDED_
+
+/*++
+/* NAME
+/*     pmock_expect 3h
+/* SUMMARY
+/*     mock test support
+/* SYNOPSIS
+/*     #include <pmock_expect.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * MOCK expectation parent class instance. Real mock expectations will
+  * subclass this, and add their own application-specific fields with
+  * expected inputs and prepared outputs.
+  */
+typedef struct MOCK_EXPECT {
+    char   *file;                      /* __FILE__ */
+    int     line;                      /* __LINE__ */
+    int     calls_expected;            /* expected count */
+    int     calls_made;                        /* actual count */
+    struct MOCK_EXPECT *next;          /* linkage */
+} MOCK_EXPECT;
+
+ /*
+  * Mock class signature and (subclass) methods.
+  */
+typedef int (*MOCK_EXPECT_MATCH_FN) (const MOCK_EXPECT *, const MOCK_EXPECT *);
+typedef void (*MOCK_EXPECT_ASSIGN_FN) (const MOCK_EXPECT *, void *);
+typedef char *(*MOCK_EXPECT_PRNT_FN) (const MOCK_EXPECT *, VSTRING *);
+typedef void (*MOCK_EXPECT_FREE_FN) (MOCK_EXPECT *);
+
+typedef struct MOCK_APPL_SIG {
+    const char *name;                  /* application sans mock_ prefix */
+    MOCK_EXPECT_MATCH_FN match_expect; /* match expectation inputs */
+    MOCK_EXPECT_ASSIGN_FN assign_expect;/* assign expectation outputs */
+    MOCK_EXPECT_PRNT_FN print_expect;  /* print call or expectation */
+    MOCK_EXPECT_FREE_FN free_expect;   /* destruct expectation */
+} MOCK_APPL_SIG;
+
+ /*
+  * Mock expectation constructor, called by expect_foo().
+  */
+extern MOCK_EXPECT *pmock_expect_create(const MOCK_APPL_SIG *, const char *file,
+                                               int line, int calls_expected,
+                                               ssize_t);
+extern int pmock_expect_apply(const MOCK_APPL_SIG *, const MOCK_EXPECT *, void *);
+extern void pmock_expect_free(MOCK_EXPECT *);
+
+ /*
+  * Report unused expectations and destroy all evidence and expectations.
+  */
+extern void pmock_expect_wrapup(PTEST_CTX *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/postfix/src/ptest/pmock_expect_test.c b/postfix/src/ptest/pmock_expect_test.c
new file mode 100644 (file)
index 0000000..ef440dc
--- /dev/null
@@ -0,0 +1,245 @@
+ /*
+  * This file contains two parts.
+  * 
+  * 1 - A trivial mock function, including code to set up expectations and to
+  * respond to calls.
+  * 
+  * 2 - Test cases that exercise this mock function and the mock support
+  * infrastructure.
+  */
+
+ /*
+  * Part 1: This emulates a trivial function:
+  * 
+  * int foo(const char *arg_in, char **arg_out)
+  * 
+  * When the mock foo() function is called with an arg_in value that matches an
+  * expected input (see below) then the mock foo() function stores a prepared
+  * value through the arg_out argument, and returns a prepared function
+  * result value.
+  * 
+  * The prepared response an result are set up with:
+  * 
+  * void expect_foo(const char *file, int line, int calls_expected, int retval,
+  * const char *arg_in, const char *arg_out)
+  * 
+  * This saves deep copies of arg_in and arg_out, and the result value in
+  * retval. The file name and line number are used to improve warning
+  * messages; typically these are specified at the call site with __FILE__
+  * and __LINE__.
+  * 
+  * The code below provides mock-specific helpers that match inputs against an
+  * expectation and that output prepared responses. These are called by the
+  * mock support infrastructure as needed.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+
+ /*
+  * Test library.
+  */
+#include <mymalloc.h>
+#include <pmock_expect.h>
+#include <ptest.h>
+
+ /*
+  * Deep copies of expected inputs and prepared outputs specified in an
+  * 'expect_foo' call. This structure will also be used to capture shallow
+  * copies of inputs for a 'foo' call.
+  */
+struct foo_expectation {
+    MOCK_EXPECT mock_expect;           /* generic fields */
+    char   *arg_in;                    /* input arguments */
+    int     retval;                    /* result value */
+    char   *arg_out;                   /* output argument */
+};
+
+ /*
+  * Pointers to the outputs for a 'foo' call.
+  */
+struct foo_targets {
+    char  **arg_out;                   /* output argument pointer */
+    int    *retval;                    /* result value pointer */
+};
+
+/* match_foo - match inputs against expectation */
+
+static int match_foo(const MOCK_EXPECT *expect, const MOCK_EXPECT *inputs)
+{
+    struct foo_expectation *pe = (struct foo_expectation *) expect;
+    struct foo_expectation *pi = (struct foo_expectation *) inputs;
+
+    return (strcmp(pe->arg_in, pi->arg_in) == 0);
+}
+
+/* assign_foo - assign expected output */
+
+static void assign_foo(const MOCK_EXPECT *expect, void *targets)
+{
+    struct foo_expectation *pe = (struct foo_expectation *) expect;
+    struct foo_targets *pt = (struct foo_targets *) targets;
+
+    *(pt->arg_out) = mystrdup(pe->arg_out);
+    *(pt->retval) = pe->retval;
+}
+
+/* print_foo - print expected inputs */
+
+static char *print_foo(const MOCK_EXPECT *expect, VSTRING *buf)
+{
+    struct foo_expectation *pe = (struct foo_expectation *) expect;
+
+    vstring_sprintf(buf, "%s", pe->arg_in);
+    return (vstring_str(buf));
+}
+
+/* free_foo - destructor */
+
+static void free_foo(MOCK_EXPECT *expect)
+{
+    struct foo_expectation *pe = (struct foo_expectation *) expect;
+
+    if (pe->arg_in)
+       myfree(pe->arg_in);
+    if (pe->arg_out)
+       myfree(pe->arg_out);
+    pmock_expect_free(expect);
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG foo_sig = {
+    "foo",
+    match_foo,
+    assign_foo,
+    print_foo,
+    free_foo,
+};
+
+/* expect_foo - set up expectation */
+
+static void expect_foo(const char *file, int line, int calls_expected,
+                              int retval, const char *arg_in,
+                              const char *arg_out)
+{
+    struct foo_expectation *pe;
+
+    pe = (struct foo_expectation *)
+       pmock_expect_create(&foo_sig, file, line, calls_expected, sizeof(*pe));
+    pe->arg_in = mystrdup(arg_in);
+    pe->retval = retval;
+    pe->arg_out = mystrdup(arg_out);
+}
+
+/* foo - mock foo */
+
+static int foo(const char *arg_in, char **arg_out)
+{
+    struct foo_expectation inputs;
+    struct foo_targets targets;
+    int     retval = -1;
+
+    /*
+     * Bundle the arguments to simplify handling.
+     */
+    inputs.arg_in = (char *) arg_in;
+    targets.arg_out = arg_out;
+    targets.retval = &retval;
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&foo_sig, &inputs.mock_expect, (void *) &targets);
+
+    return (retval);
+}
+
+ /*
+  * Part 2: Test cases. See ptest_main.h for a documented example.
+  */
+
+ /*
+  * The ptestcase structure.
+  */
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+static void test_unused_expectation_1_of_2(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    const char *want_arg_out = "output";
+    char   *got_arg_out = 0;
+    int     got_retval, want_retval = 42;
+
+    /*
+     * Set up an expectation for two calls, but intentionally make only one.
+     */
+    expect_foo(__FILE__, __LINE__, 2, want_retval, "input", want_arg_out);
+    got_retval = foo("input", &got_arg_out);
+    if (got_arg_out == 0 || strcmp(got_arg_out, want_arg_out) != 0) {
+       ptest_error(t, "foo: got '%s', want '%s'",
+                   got_arg_out ? got_arg_out : "(null)", want_arg_out);
+    } else if (got_retval != want_retval) {
+       ptest_error(t, "foo: got retval %d, want %d", got_retval, want_retval);
+    }
+
+    /*
+     * This error is intentional. Do not count as a failure. The error will
+     * be logged after this test terminates.
+     */
+    expect_ptest_error(t, " got 1 call for foo(input), want 2");
+
+    /*
+     * Cleanup.
+     */
+    if (got_arg_out)
+       myfree(got_arg_out);
+}
+
+static void test_unused_expectation_0_of_0_1(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    int     want_retval = 42;
+
+    /*
+     * Give each expectation a unique line number. Here, we make zero calls
+     * while expecting exactly one call, or one or more calls.
+     */
+    expect_foo(__FILE__, __LINE__, 1, want_retval, "input", "output");
+    expect_foo(__FILE__, __LINE__, 0, want_retval, "input", "output");
+
+    /*
+     * These errors are intentional. Do not count as a failure.
+     */
+    expect_ptest_error(t, " got 0 calls for foo(input), want 1 or more");
+    expect_ptest_error(t, " got 0 calls for foo(input), want 1");
+}
+
+ /*
+  * Test cases. The "success" calls exercise the expectation match and apply
+  * helpers, and "missing" tests exercise the print helpers. All tests
+  * exercise the expectation free helpers.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+       "unused expectation 1 of 2", test_unused_expectation_1_of_2,
+    },
+    {
+       "unused expectation 0 of 0-1", test_unused_expectation_0_of_0_1,
+    },
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/ptest/ptest.h b/postfix/src/ptest/ptest.h
new file mode 100644 (file)
index 0000000..c53cf68
--- /dev/null
@@ -0,0 +1,147 @@
+#ifndef _PTEST_H_INCLUDED_
+#define _PTEST_H_INCLUDED_
+
+/*++
+/* NAME
+/*     ptest 3h
+/* SUMMARY
+/*     run-time test support
+/* SYNOPSIS
+/*     #include <ptest.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <setjmp.h>
+
+ /*
+  * Utility library.
+  */
+#include <argv.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+  * Ptest library.
+  */
+#include <msg_jmp.h>
+
+ /*
+  * TODO: factor out, and merge with DICT_JMP_BUF, MSG_JMP_BUF,
+  * SLMDB_JMP_BUF, VSTREAM_JMP_BUF.
+  */
+#ifdef NO_SIGSETJMP
+#define TEST_JMP_BUF jmp_buf
+#define ptest_longjmp(bufp, val)       longjmp((bufp)[0], (val))
+#else
+#define TEST_JMP_BUF sigjmp_buf
+#define ptest_longjmp(bufp, val)       siglongjmp((bufp)[0], (val))
+#endif
+
+ /*
+  * All run-time test info in one place.
+  */
+typedef void (*PTEST_DEFER_FN) (void *);
+
+#define PTEST_CTX_FLAG_SKIP    (1<<0)  /* This test is skipped */
+#define PTEST_CTX_FLAG_FAIL    (1<<1)  /* This test has failed */
+
+typedef struct PTEST_CTX {
+    /* ptest_ctx.c */
+    char   *name;                      /* Null, name, or name/name/... */
+    TEST_JMP_BUF *jbuf;                        /* Used by ptest_fatal(), msg(3) */
+    struct PTEST_CTX *parent;          /* In case tests are nested */
+    int     flags;                     /* See above */
+    /* ptest_run.c */
+    int     sub_pass;                  /* Subtests that passed */
+    int     sub_fail;                  /* Subtests that failed */
+    int     sub_skip;                  /* Subtests that were skipped */
+    PTEST_DEFER_FN defer_fn;           /* To be called after test... */
+    void   *defer_ctx;                 /* ...with this argument */
+    /* ptest_error.c */
+    VSTREAM *err_stream;               /* Output stream */
+    VSTRING *err_buf;                  /* Formatting buffer */
+    ARGV   *allow_errors;              /* Allowed errors */
+    /* ptest_log.c */
+    VSTRING *log_buf;                  /* Formatting buffer */
+    ARGV   *allow_logs;                        /* Allowed logs */
+} PTEST_CTX;
+
+ /*
+  * ptest_ctx.c
+  */
+extern PTEST_CTX *ptest_ctx_create(const char *, TEST_JMP_BUF *);
+extern PTEST_CTX *ptest_ctx_current(void);
+extern void ptest_ctx_free(PTEST_CTX *);
+
+ /*
+  * ptest_error.c
+  */
+extern void ptest_error_setup(PTEST_CTX *, VSTREAM *);
+extern void expect_ptest_error(PTEST_CTX *, const char *);
+extern void PRINTFLIKE(2, 3) ptest_info(PTEST_CTX *, const char *,...);
+extern void PRINTFLIKE(2, 3) ptest_error(PTEST_CTX *, const char *,...);
+extern NORETURN PRINTFLIKE(2, 3) ptest_fatal(PTEST_CTX *, const char *,...);
+extern int ptest_error_wrapup(PTEST_CTX *);
+
+ /*
+  * ptest_log.c
+  */
+extern void ptest_log_setup(PTEST_CTX *);
+extern void expect_ptest_log_event(PTEST_CTX *, const char *);
+extern void ptest_log_wrapup(PTEST_CTX *);
+
+ /*
+  * ptest_run.c
+  */
+extern void ptest_run_prolog(PTEST_CTX *);
+extern void ptest_run_epilog(PTEST_CTX *, PTEST_CTX *);
+extern NORETURN ptest_skip(PTEST_CTX *);
+extern NORETURN ptest_return(PTEST_CTX *);
+extern void ptest_defer(PTEST_CTX *, PTEST_DEFER_FN, void *);
+
+#define PTEST_RUN(t, _test_name, _body_in_braces) do { \
+    MSG_JMP_BUF _new_buf; \
+    PTEST_CTX *_parent = t; \
+    t = ptest_ctx_create((_test_name), &_new_buf); \
+    ptest_run_prolog(t); \
+    if (msg_setjmp(&_new_buf) == 0) { \
+        _body_in_braces \
+    } \
+    msg_resetjmp(_parent->jbuf); \
+    ptest_run_epilog(t, _parent); \
+    ptest_ctx_free(t); \
+    t = _parent; \
+} while (0)
+
+#define PTEST_TRY(t, _body_in_braces) do { \
+    MSG_JMP_BUF _new_buf; \
+    if (msg_setjmp(&_new_buf) == 0) { \
+        _body_in_braces \
+    } \
+    msg_resetjmp(t->jbuf); \
+} while (0)
+
+ /*
+  * How many elements in a test case array.
+  */
+#define PTEST_NROF(x) (sizeof(x)/sizeof((x)[0]))
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+#endif
diff --git a/postfix/src/ptest/ptest_ctx.c b/postfix/src/ptest/ptest_ctx.c
new file mode 100644 (file)
index 0000000..71b7c56
--- /dev/null
@@ -0,0 +1,131 @@
+/*++
+/* NAME
+/*     ptest_ctx 3
+/* SUMMARY
+/*     test context support
+/* SYNOPSIS
+/*     #include <ptest.h>
+/*
+/*     PTEST_CTX ptest_ctx_create(
+/*     const char *name,
+/*     TEST_JMP_BUF *jbuf)
+/*
+/*     PTEST_CTX *ptest_ctx_current(void)
+/*
+/*     int     ptest_ctx_free(PTEST_CTX *t)
+/* DESCRIPTION
+/*     This module manages a stack of contexts that are used by
+/*     tests.
+/*
+/*     ptest_ctx_create() is called by test infrastructure before
+/*     a test is run. It returns an initialized PTEST_CTX object
+/*     after making it the current test context. The jbuf argument
+/*     references jump buffer that will be used by ptest_fatal(),
+/*     msg_fatal() and msg_panic().
+/*
+/*     ptest_ctx_current() returns the current test context.  This
+/*     function exists because mocked functions must be called
+/*     without an argument that specifies a test context.
+/*
+/*     ptest_ctx_free() is called by test infrastructure after a
+/*     test terminates and all error reporting has completed.
+/*     It destroys the PTEST_CTX object.
+/* DIAGNOSTICS
+/*     ptest_ctx_current() will panic if the test context stack is
+/*     empty.
+/*
+/*     ptest_ctx_free() will panic if the argument does not specify
+/*     the current test context.
+/* SEE ALSO
+/*     pmock_expect(3), mock test support
+/*     ptest_error(3), test error reporter
+/*     ptest_log(3), log receiver
+/*     ptest_main(3), test driver
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <setjmp.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstream.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+static PTEST_CTX *ptest_ctx_head;
+
+/* ptest_ctx_create - create initialized PTEST_CTX object */
+
+PTEST_CTX *ptest_ctx_create(const char *name, TEST_JMP_BUF *jbuf)
+{
+    PTEST_CTX *parent = ptest_ctx_head;
+    PTEST_CTX *t;
+
+    t = mymalloc(sizeof(*t));
+    if (name == 0)                             /* main-level context */
+       t->name = 0;
+    else if (parent->name == 0)                        /* top-level test context */
+       t->name = mystrdup(name);
+    else                                       /* sub test */
+       t->name = concatenate(parent->name, "/", name, (char *) 0);
+    t->jbuf = jbuf;
+    t->parent = parent;
+    t->flags = 0;
+    /* ptest_run.c specific */
+    t->sub_pass = t->sub_fail = t->sub_skip = 0;
+    /* ptest_error.c specific */
+    t->err_stream = 0;
+    t->err_buf = 0;
+    t->allow_errors = 0;
+    /* ptest_log.c specific */
+    t->log_buf = 0;
+    t->allow_logs = 0;
+    /* ptest_defer.c specific */
+    t->defer_fn = 0;
+    t->defer_ctx = 0;
+
+    ptest_ctx_head = t;
+
+    return (t);
+}
+
+/* ptest_ctx_current - return current context or die */
+
+PTEST_CTX *ptest_ctx_current()
+{
+    if (ptest_ctx_head == 0)
+       msg_panic("ptest_ctx_current: no test context");
+    return (ptest_ctx_head);
+}
+
+/* ptest_ctx_free - destroy PTEST_CTX or die */
+
+void    ptest_ctx_free(PTEST_CTX *t)
+{
+    if (t != ptest_ctx_head)
+       msg_panic("ptest_ctx_free: wrong test context - "
+                 "should you use ptest_return()?");
+    ptest_ctx_head = t->parent;
+    if (t->name)
+       myfree(t->name);
+    myfree((void *) t);
+}
diff --git a/postfix/src/ptest/ptest_error.c b/postfix/src/ptest/ptest_error.c
new file mode 100644 (file)
index 0000000..f71e123
--- /dev/null
@@ -0,0 +1,222 @@
+/*++
+/* NAME
+/*     ptest_error 3
+/* SUMMARY
+/*     test error and non-error support
+/* SYNOPSIS
+/*     #include <ptest.h>
+/*
+/*     void    expect_ptest_error(
+/*     PTEST_CTX *t,
+/*     const char *text)
+/*
+/*     void    PRINTFLIKE(2, 3) ptest_info(
+/*     PTEST_CTX *t,
+/*     const char *, ...)
+/*
+/*     void    PRINTFLIKE(2, 3) ptest_error(
+/*     PTEST_CTX *t,
+/*     const char *, ...)
+/*
+/*     NORETURN PRINTFLIKE(2, 3) ptest_fatal(
+/*     PTEST_CTX *t,
+/*     const char *, ...)
+/* TEST INFRASTRUCTURE SUPPORT
+/*     PTEST_CTX ptest_error_setup(VSTREAM *err_stream)
+/*
+/*     int     ptest_error_wrapup(PTEST_CTX *t)
+/* DESCRIPTION
+/*     This module manages errors and non-errors that are reported
+/*     by tests.
+/*
+/*     ptest_info() is called from inside a test, to report a
+/*     non-error condition, for example, to report progress.
+/*
+/*     ptest_error() is called from inside a test, to report a
+/*     non-fatal test error (after the call is finished, the test
+/*     will continue). If the error text matches a pattern given
+/*     to an earlier expect_ptest_error() call (see below), then
+/*     this ptest_error() call will be ignored once, and treated
+/*     as a non-error.  Otherwise, ptest_error() logs the error and
+/*     increments an error count.
+/*
+/*     expect_ptest_error() is called from inside a test. It requires
+/*     that a ptest_error() call will be made whose formatted text
+/*     contains a substring that matches the text argument. For
+/*     robustness, do not include file line number information in
+/*     the expected text. If the expected ptest_error() call is
+/*     made, then that call will be ignored once, and treated as
+/*     a non-error (call expect_ptest_error() multiple times to
+/*     ignore an error multiple times). If the expected ptest_error()
+/*     call is not made, then ptest_error_wrapup() will report an
+/*     error and the test will fail.
+/*
+/*     ptest_fatal() is called from inside a test. It reports a
+/*     fatal test error and increments an error count. A ptest_fatal()
+/*     call does not return, instead it terminates the test.
+/*     ptest_fatal() calls cannot be expected and ignored with
+/*     expect_ptest_error().
+/*
+/*     ptest_error_setup() is called by test infrastructure before
+/*     a test is run. It updates a PTEST_CTX object. The err_stream
+/*     argument specifies the output stream for error reporting.
+/*
+/*     ptest_error_wrapup() is called by test infrastructure after
+/*     a test terminates. It calls ptest_error() to report any
+/*     missing ptest_error() calls, destroys the PTEST_CTX information
+/*     that was allocated with ptest_error_setup(), and returns the
+/*     final error count.
+/* DIAGNOSTICS
+/*     The above functions write to the VSTREAM specified in the
+/*     ptest_error_setup() call.
+/* SEE ALSO
+/*     pmock_expect(3), mock test support
+/*     ptest_main(3h), test driver
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <setjmp.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <argv.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * SLMs.
+  */
+#define STR(x) vstring_str(x)
+
+/* ptest_error_setup - populate PTEST_CTX object */
+
+void    ptest_error_setup(PTEST_CTX *t, VSTREAM *err_stream)
+{
+    t->flags &= ~PTEST_CTX_FLAG_FAIL;
+    t->err_stream = err_stream;
+    t->err_buf = vstring_alloc(100);
+    t->allow_errors = argv_alloc(1);
+}
+
+/* expect_ptest_error - require and bless a non-fatal error */
+
+void    expect_ptest_error(PTEST_CTX *t, const char *text)
+{
+    argv_add(t->allow_errors, text, (char *) 0);
+}
+
+/* ptest_info - report non-error condition */
+
+void    ptest_info(PTEST_CTX *t, const char *fmt,...)
+{
+    va_list ap;
+
+    /*
+     * Format the message.
+     */
+    va_start(ap, fmt);
+    vstream_vfprintf(t->err_stream, fmt, ap);
+    vstream_fprintf(t->err_stream, "\n");
+    va_end(ap);
+    vstream_fflush(t->err_stream);
+}
+
+/* ptest_error - report non-fatal error */
+
+void    ptest_error(PTEST_CTX *t, const char *fmt,...)
+{
+    va_list ap;
+    char  **cpp;
+
+    /*
+     * Format the message.
+     */
+    va_start(ap, fmt);
+    vstring_vsprintf(t->err_buf, fmt, ap);
+    va_end(ap);
+
+    /*
+     * Skip this error if it was expected.
+     */
+    for (cpp = t->allow_errors->argv; *cpp; cpp++) {
+       if (strstr(STR(t->err_buf), *cpp) != 0) {
+           argv_delete(t->allow_errors, cpp - t->allow_errors->argv, 1);
+           return;
+       }
+    }
+
+    /*
+     * Report the message.
+     */
+    vstream_fprintf(t->err_stream, "error: %s\n", STR(t->err_buf));
+    vstream_fflush(t->err_stream);
+    t->flags |= PTEST_CTX_FLAG_FAIL;
+}
+
+/* ptest_fatal - report fatal error */
+
+NORETURN ptest_fatal(PTEST_CTX *t, const char *fmt,...)
+{
+    va_list ap;
+
+    /*
+     * This has no code in common with ptest_error().
+     */
+    vstream_fprintf(t->err_stream, "fatal: ");
+    va_start(ap, fmt);
+    vstream_vfprintf(t->err_stream, fmt, ap);
+    va_end(ap);
+    vstream_fprintf(t->err_stream, "\n");
+    vstream_fflush(t->err_stream);
+    t->flags |= PTEST_CTX_FLAG_FAIL;
+    ptest_longjmp(t->jbuf, 1);
+}
+
+/* ptest_error_wrapup - enforce error expectations and clean up */
+
+int     ptest_error_wrapup(PTEST_CTX *t)
+{
+    char  **cpp;
+    int     fail_flag;
+
+    /*
+     * Report a new error if an expected error did not happen.
+     */
+    for (cpp = t->allow_errors->argv; *cpp; cpp++) {
+       vstream_fprintf(t->err_stream, "Missing error: want '%s'\n", *cpp);
+       t->flags |= PTEST_CTX_FLAG_FAIL;
+       vstream_fflush(t->err_stream);
+    }
+    fail_flag = (t->flags & PTEST_CTX_FLAG_FAIL);
+
+    /*
+     * Clean up the PTEST_CTX fields that we created.
+     */
+    vstring_free(t->err_buf);
+    t->err_buf = 0;
+    argv_free(t->allow_errors);
+    t->allow_errors = 0;
+    t->flags &= ~PTEST_CTX_FLAG_FAIL;
+    return (fail_flag);
+}
diff --git a/postfix/src/ptest/ptest_log.c b/postfix/src/ptest/ptest_log.c
new file mode 100644 (file)
index 0000000..c54112c
--- /dev/null
@@ -0,0 +1,148 @@
+/*++
+/* NAME
+/*     ptest_log 3
+/* SUMMARY
+/*     log event receiver support
+/* SYNOPSIS
+/*     #include <ptest.h>
+/*
+/*     void    expect_ptest_log_event(
+/*     PTEST_CTX *t,
+/*     const char *text)
+/* INFRASTRUCTURE SUPPORT
+/*     void    ptest_log_setup(
+/*     PTEST_CTX *t)
+/*
+/*     void    ptest_log_wrapup(
+/*     PTEST_CTX *t)
+/* DESCRIPTION
+/*     This module inspects msg(3) logging.
+/*
+/*     expect_ptest_log_event() is called from a test. It requires
+/*     that an msg(3) call will be made whose formatted text
+/*     contains a substring that matches the text argument. For
+/*     robustness, do not include file name or line number
+/*     information. If an msg(3) call fails to match a log event
+/*     expectation, then the log event receiver will call ptest_error()
+/*     to report an unexpected msg(3) call. See ptest_log_wrapup()
+/*     below for the handling of an expected but missing msg(3) call.
+/*
+/*     ptest_log_setup() is called by testing infrastructure before
+/*     a test is started. It updates the PTEST_CTX structure, and
+/*     installs an msg(3) log event receiver.
+/*
+/*     ptest_log_wrapup() is called by test infrastructure after
+/*     a test terminates. It calls ptest_error() to report any
+/*     unmatched expect_ptest_log_event() expectations, and destroys
+/*     buffers that were created by ptest_log_setup().
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <msg_output.h>
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * SLMs.
+  */
+#define STR(x) vstring_str(x)
+
+/* ptest_log_event - receive log event */
+
+static void ptest_log_event(int level, const char *text, void *context)
+{
+    static const char *level_text[] = {
+       "info", "warning", "error", "fatal", "panic",
+    };
+    PTEST_CTX *t = (PTEST_CTX *) context;
+    char  **cpp;
+
+    /*
+     * Silence events for parent handlers.
+     */
+    if (t != ptest_ctx_current())
+       return;
+
+    /*
+     * Sanity checks.
+     */
+    if (level < 0 || level >= (int) (sizeof(level_text) / sizeof(level_text[0])))
+       msg_panic("ptest_log_event: invalid severity level: %d", level);
+
+    /*
+     * Format the text.
+     */
+    if (level == MSG_INFO) {
+       vstring_sprintf(t->log_buf, "%s", text);
+    } else {
+       vstring_sprintf(t->log_buf, "%s: %s", level_text[level], text);
+    }
+
+    /*
+     * Handle expected versus unexpected text.
+     */
+    for (cpp = t->allow_logs->argv; *cpp; cpp++) {
+       if (strstr(STR(t->log_buf), *cpp) != 0) {
+           argv_delete(t->allow_logs, cpp - t->allow_logs->argv, 1);
+           return;
+       }
+    }
+    ptest_error(t, "Unexpected log event: got '%s'", STR(t->log_buf));
+}
+
+/* ptest_log_setup - install logging receiver */
+
+void    ptest_log_setup(PTEST_CTX *t)
+{
+    if (t != ptest_ctx_current())
+       msg_panic("ptest_log_setup: not current context");
+    t->log_buf = vstring_alloc(100);
+    t->allow_logs = argv_alloc(1);
+    msg_output_push(ptest_log_event, (void *) t);
+}
+
+/* expect_ptest_log_event - add log event expectation */
+
+void    expect_ptest_log_event(PTEST_CTX *t, const char *text)
+{
+    if (t != ptest_ctx_current())
+       msg_panic("expect_ptest_log_event: not current context");
+    argv_add(t->allow_logs, text, (char *) 0);
+}
+
+/* ptest_log_wrapup - enforce logging expectations */
+
+void    ptest_log_wrapup(PTEST_CTX *t)
+{
+    char  **cpp;
+
+    msg_output_pop(ptest_log_event, (void *) t);
+    for (cpp = t->allow_logs->argv; *cpp; cpp++)
+       ptest_error(t, "Missing log event: want '%s'", *cpp);
+    vstring_free(t->log_buf);
+    t->log_buf = 0;
+    argv_free(t->allow_logs);
+    t->allow_logs = 0;
+}
diff --git a/postfix/src/ptest/ptest_log_test.c b/postfix/src/ptest/ptest_log_test.c
new file mode 100644 (file)
index 0000000..275c700
--- /dev/null
@@ -0,0 +1,78 @@
+ /*
+  * Test program to exercise ptest_log functions including logging. See
+  * comments in ptest_main.h and pmock_expect_test.c for a documented
+  * example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+
+ /*
+  * Ptest library.
+  */
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+static void ptest_log_non_error(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    /* This test passes if there is no error. */
+    expect_ptest_log_event(t, "this is a non-error");
+    msg_info("this is a non-error");
+}
+
+static void ptest_log_flags_unexpected_message(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    expect_ptest_error(t, "this is a forced 'Unexpected log event' error");
+    msg_info("this is a forced 'Unexpected log event' error");
+}
+
+static void ptest_log_flags_missing_message(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    expect_ptest_error(t, "this is a forced 'Missing log event' error");
+    expect_ptest_log_event(t, "this is a forced 'Missing log event' error");
+}
+
+static void ptest_nested_logging(PTEST_CTX * t, const PTEST_CASE * unused)
+{
+    expect_ptest_log_event(t, "top-level");
+    msg_info("this is a top-level event");
+    PTEST_RUN(t, "top-1 level", {
+       expect_ptest_log_event(t, "top-1 level event");
+       msg_info("this is a top-1 level event");
+       PTEST_RUN(t, "top-2 level", {
+           expect_ptest_log_event(t, "top-2 level event");
+           msg_info("this is a top-2 level event");
+       });
+    });
+}
+
+ /*
+  * Test cases.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+       "ptest_log_non_error", ptest_log_non_error,
+    },
+    {
+       "ptest_log_flags_unexpected_message", ptest_log_flags_unexpected_message,
+    },
+    {
+       "ptest_log_flags_missing_message", ptest_log_flags_missing_message,
+    },
+    {
+       "ptest_nested_logging", ptest_nested_logging,
+    },
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/ptest/ptest_main.h b/postfix/src/ptest/ptest_main.h
new file mode 100644 (file)
index 0000000..085a434
--- /dev/null
@@ -0,0 +1,179 @@
+/*++
+/* NAME
+/*     ptest_main 3h
+/* SUMMARY
+/*     test driver
+/* DESCRIPTION
+/*     This file should be included at the end of a *_test.c file.
+/*     It contains a main program and test driver, and supports
+/*     programs whether or not they use mocks as defined in
+/*     <pmock_expect.h>.
+/*
+/*     Before including this file, a *_test.c file should define
+/*     the structure and content of its test cases, and the functions
+/*     that implement those tests:
+/*
+/* .nf
+/*     /* Begin example. */
+/*
+/*     #include <ptest.h>
+/*     #include <pmock_expect.h>
+/*
+/*      /*
+/*       * Test case structure. If multiple test functions cannot
+/*       * have their test data in a shared PTEST_CASE structure, then
+/*       * each test function can define its own test data, and run
+/*       * multiple tests with PTEST_RUN(). See documentation in
+/*       * ptest_run.c.
+/*       */
+/*     typedef struct PTEST_CASE {
+/*         const char *testname;       /* Human-readable description */
+/*         void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+/*         /* Optionally, your test data fields... */
+/*     } PTEST_CASE;
+/*
+/*      /*
+/*       * Test functions. These should not use msg_xxx() functions.
+/*       *
+/*       * To report a test error use ptest_error(t, ...) and to abort a
+/*       * test use ptest_fatal(t, ...). Tests with errors will not PASS.
+/*       *
+/*       * To "expect" a non-fatal error (and not count it as a failure)
+/*       * use expect_ptest_error(t, text) where the text is a substring
+/*       * of the expected error message.
+/*       */
+/*
+/*     static void test_abc(PTEST_CTX *t, const PTEST_CASE *tp)
+/*     {
+/*         int  ret;
+/*
+/*         want_abc = 42;
+/*         got_abc = abc(1, 2, 3);
+/*         if (got_abc != want_abc)
+/*             ptest_error(t, "abc: got %d, want %d", got_abc, want_abc);
+/*     }
+/*
+/*     /* More test functions... */
+/*
+/*     /* Test cases. Do not terminate with null. */
+/*
+/*     const PTEST_CASE ptestcases[] = {
+/*         { "test abc", test_abc, ...},
+/*         /* More test cases... */
+/*     };
+/*
+/*     #include <ptest_main.h>
+/*
+/*     /* End example. */
+/* .fi
+/*
+/*     The <ptest_main.h> test driver iterates over each test case
+/*     and invokes the test case's action function with a pointer
+/*     to its test case. The test driver captures all logging that
+/*     is generated while the test case runs, including logging
+/*     from test functions, library functions and from the mock
+/*     infrastructure.
+/*
+/*     The action function should call ptest_error() or ptest_fatal()
+/*     when a test fails. ptest_fatal() terminates a test but does
+/*     not terminate the process.
+/*
+/*     If the tests use mocks, the mock infrastructure will log
+/*     unexpected mock calls, and unused mock call expectations.
+/*
+/*     The ptest_log module will log a warning if the captured
+/*     logging differs from the expected logging.
+/* SEE ALSO
+/*     msg(3) diagnostics
+/*     ptest_error(3) test error and non-error handling
+/*     ptest_log(3) log event receiver support
+/* BUGS
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+#include <stdlib.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <msg_output.h>
+#include <msg_vstream.h>
+#include <myrand.h>
+#include <stringops.h>
+#include <vstream.h>
+
+ /*
+  * Test library.
+  */
+#include <pmock_expect.h>
+#include <ptest.h>
+
+/* main - test driver */
+
+int     main(int argc, char **argv)
+{
+    PTEST_CTX *t;
+    const PTEST_CASE *tp;
+    int     fail;
+
+#ifndef DORANDOMIZE
+
+    /*
+     * This must be set BEFORE the first hash table call.
+     */
+    if (putenv("NORANDOMIZE=") != 0)
+       msg_fatal("putenv() failed: %m");
+
+    /*
+     * This is used by DNS client code.
+     */
+    mysrand(0);
+#endif
+
+    /*
+     * Send msg(3) logging to stderr by default.
+     */
+    msg_vstream_init(basename(argv[0]), VSTREAM_ERR);
+
+    /*
+     * The main-level PTEST_CTX context has no name and no long jump context.
+     * It's sole purpose is to run tests and to aggregate pass/skip/fail
+     * counts.
+     */
+    t = ptest_ctx_create((char *) 0, (MSG_JMP_BUF *) 0);
+
+    /*
+     * Run each test in its own PTEST_CTX context with its own log
+     * interceptor and long jump context. Each test can invoke PTEST_RUN() to
+     * run one or more of subtests in their own context with their own test
+     * data, instead of having to store all test data in a PTEST_CASE
+     * structure.
+     */
+    for (tp = ptestcases; tp < ptestcases + PTEST_NROF(ptestcases); tp++) {
+       if (tp->testname == 0)
+           msg_fatal("Null testname in ptestcases array!");
+       PTEST_RUN(t, tp->testname, {
+           tp->action(t, tp);
+       });
+    }
+    msg_info("PASS: %d, SKIP: %d, FAIL: %d", t->sub_pass, t->sub_skip, fail = t->sub_fail);
+    ptest_ctx_free(t);
+    exit(fail > 0);
+}
diff --git a/postfix/src/ptest/ptest_run.c b/postfix/src/ptest/ptest_run.c
new file mode 100644 (file)
index 0000000..52622bc
--- /dev/null
@@ -0,0 +1,159 @@
+/*++
+/* NAME
+/*     ptest_run 3h
+/* SUMMARY
+/*     test runner
+/* SYNOPSIS
+/*     #include <ptest.h>
+/*
+/*     void    PTEST_RUN(
+/*     PTEST_CTX *t,
+/*     const char *test_name,
+/*     { body_in_braces })
+/*
+/*     NORETURN ptest_skip(PTEST_CTX *t)
+/*
+/*     NORETURN ptest_return(PTEST_CTX *t)
+/*
+/*     void    ptest_defer(
+/*     PTEST_CTX *t,
+/*     void    (*defer_fn)(void *),
+/*     void    *defer_ctx)
+/* DESCRIPTION
+/*     PTEST_RUN() is called from inside a test to run a subtest.
+/*
+/*     PTEST_RUN() is a macro that runs the { body_in_braces }
+/*     with msg(3) logging temporarily redirected to a listener, and
+/*     with panic, fatal, error, and non-error functions that
+/*     terminate a test without terminating the process.
+/*
+/*     To use this as a subtest inside a PTEST_CASE action:
+/*
+/* .na
+/*     static void action(PTEST_CTX *t, const PTEST_CASE *tp)
+/*     {
+/*         struct subtest {
+/*             // ...
+/*         };
+/*         static const struct subtest tests[] = {
+/*             // ...subtest data and expectations...
+/*         };
+/*         struct subtest *sp;
+/*         for (sp = tests; sp < tests + sizeof(tests) / sizeof(tests[0]); sp++) {
+/*             PTEST_RUN(t, sp->name, {
+/*                 // Test code that uses sp->mumble here.
+/*                 // Use ptest_error(), ptest_fatal(), or ptest_return()
+/*                 // to report an error or terminate a test, or
+/*                 // ptest_skip() to skip a test.
+/*             });
+/*         }
+/*     }
+/*
+/*     ptest_skip() is called from inside a test. It flags a test
+/*     as skipped, and terminates the test without terminating the
+/*     process.
+/*
+/*     ptest_return() is called from inside a test. It terminates
+/*     the test without terminating the process.
+/*
+/*     ptest_defer() may be called once from a test, to defer some
+/*     processing until after the test completes. This is typically
+/*     used to eliminate a resource leak in tests that terminate
+/*     the test early (i.e. that return with a long jump).
+/*
+/*     To "undo" a ptest_defer() call, call the function with a
+/*     null defer_fn argument. Then, it may be called again to
+/*     set up deferred execution.
+/* SEE ALSO
+/*     pmock_expect(3), mock for hermetic tests
+/*     ptest_error(3), test error support
+/*     ptest_log(3), logging receiver support
+/* BUGS
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstream.h>
+
+ /*
+  * Test library.
+  */
+#include <pmock_expect.h>
+#include <ptest.h>
+
+/* ptest_run_prolog - encapsulate PTEST_RUN() dependencies */
+
+void    ptest_run_prolog(PTEST_CTX *t)
+{
+    ptest_error_setup(t, VSTREAM_ERR);
+    ptest_info(t, "RUN  %s", t->name);
+    ptest_log_setup(t);
+    msg_vstream_enable(0);
+}
+
+/* ptest_run_epilog - encapsulate PTEST_RUN() dependencies */
+
+void    ptest_run_epilog(PTEST_CTX *t, PTEST_CTX *parent)
+{
+    msg_vstream_enable(1);
+    ptest_log_wrapup(t);
+    pmock_expect_wrapup(t);
+    if (ptest_error_wrapup(t) != 0 || t->sub_fail != 0) {
+       ptest_info(t, "FAIL %s", t->name);
+       parent->sub_fail += 1;
+    } else if (t->flags & PTEST_CTX_FLAG_SKIP) {
+       ptest_info(t, "SKIP %s", t->name);
+       parent->sub_skip += 1;
+    } else {
+       ptest_info(t, "PASS %s", t->name);
+       parent->sub_pass += 1;
+    }
+    parent->sub_pass += t->sub_pass;
+    parent->sub_fail += t->sub_fail;
+    parent->sub_skip += t->sub_skip;
+    if (t->defer_fn)
+       t->defer_fn(t->defer_ctx);
+}
+
+/* ptest_skip - skip a test and return from test */
+
+NORETURN ptest_skip(PTEST_CTX *t)
+{
+    t->flags |= PTEST_CTX_FLAG_SKIP;
+    ptest_longjmp(t->jbuf, 1);
+}
+
+/* ptest_return - early return from test */
+
+NORETURN ptest_return(PTEST_CTX *t)
+{
+    ptest_longjmp(t->jbuf, 1);
+}
+
+/* ptest_defer - post-test processing */
+
+void    ptest_defer(PTEST_CTX *t, PTEST_DEFER_FN defer_fn,
+                           void *defer_ctx)
+{
+    if (t->defer_fn && defer_fn)
+       msg_panic("ptest_defer: multiple calls for this test context");
+    t->defer_fn = defer_fn;
+    t->defer_ctx = defer_ctx;
+}
index 476ac87c42bb757e7d3e507292648d767e1e5d5c..5a0f6df14ae02fbad001e9bab615e31c62e3677d 100644 (file)
@@ -31,7 +31,7 @@
 /*     lookups are done via the Internet domain name service (DNS).
 /*     A reasonable number of CNAME indirections is permitted. When
 /*     DNS lookups are disabled, host address lookup is done with
-/*     getnameinfo() or gethostbyname().
+/*     myaddrinfo(3).
 /*
 /*     smtp_domain_addr() looks up the network addresses for mail
 /*     exchanger hosts listed for the named domain. Addresses are
index 9ca64dc5ead783da27767e27d43195fe67f7ecf1..7d130a822ff74389aa2a0103b31133baec4dd4b1 100644 (file)
@@ -10,7 +10,7 @@ OBJS  = smtpd.o smtpd_token.o smtpd_check.o smtpd_chat.o smtpd_state.o \
 HDRS   = smtpd_token.h smtpd_check.h smtpd_chat.h smtpd_sasl_proto.h \
        smtpd_sasl_glue.h smtpd_proxy.h smtpd_dsn_fix.h smtpd_milter.h \
        smtpd_resolve.h smtpd_expand.h
-TESTSRC        = smtpd_token_test.c
+TESTSRC        =
 DEFS   = -I. -I$(INC_DIR) -D$(SYSTYPE)
 CFLAGS = $(DEBUG) $(OPT) $(DEFS)
 TESTPROG= smtpd_token smtpd_check smtpd_peer_test
index 4534831c4747abbbdf55d751617d9b2a637f4f77..3a77e6b34f035cf5f2602b270c26d38dcf7e253c 100644 (file)
@@ -1,11 +1,22 @@
 SHELL  = /bin/sh
-SRCS   = nosleep.c dict_test_helper.c mock_open_as.c mock_spawn_command.c \
+SRCS   = mock_myaddrinfo.c mock_dns_lookup.c mock_servent.c \
+       mock_getaddrinfo.c match_basic.c match_addr.c make_addr.c \
+       addrinfo_to_string.c make_attr.c match_attr.c \
+       nosleep.c dict_test_helper.c mock_open_as.c mock_spawn_command.c \
        mock_stat.c msg_capture.c mock_dict.c
-LIB_OBJ        = dict_test_helper.o mock_open_as.o mock_spawn_command.o \
+LIB_OBJ        = match_basic.o match_addr.o make_addr.o addrinfo_to_string.o \
+       make_attr.o match_attr.o \
+       dict_test_helper.o mock_open_as.o mock_spawn_command.o \
        mock_stat.o msg_capture.o mock_dict.o
-MOCK_OBJ=
-TEST_OBJ=
-HDRS   = dict_test_helper.h mock_open_as.h mock_spawn_command.h \
+MOCK_OBJ= mock_myaddrinfo.o mock_dns_lookup.o mock_servent.o \
+       mock_getaddrinfo.o mock_server.o
+TEST_OBJ = mock_dns_lookup_test.o mock_getaddrinfo_test.o \
+       mock_myaddrinfo_test.o mock_servent_test.o match_addr_test.o \
+       mock_server_test.o match_attr_test.o match_basic.o
+HDRS   = mock_myaddrinfo.h mock_dns.h mock_servent.h mock_getaddrinfo.h \
+       match_basic.h match_addr.h make_addr.h addrinfo_to_string.h \
+       mock_server.h make_attr.h match_attr.h \
+       dict_test_helper.h mock_open_as.h mock_spawn_command.h \
        mock_stat.h msg_capture.h mock_dict.h
 TESTSRC        =
 DEFS   = -I. -I$(INC_DIR) -D$(SYSTYPE)
@@ -13,9 +24,13 @@ CFLAGS       = $(DEBUG) $(OPT) $(DEFS)
 INCL   =
 LIB    = libtesting.a
 LIB_SO = nosleep.so
-TESTPROG=
+TESTPROG= mock_dns_lookup_test mock_getaddrinfo_test \
+       mock_myaddrinfo_test mock_servent_test match_addr_test \
+       match_attr_test match_basic_test mock_server_test
 
-LIBS   = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+LIBS   = ../../lib/libptest.a \
+       ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \
+       ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
        ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
 LIB_DIR        = ../../lib
 INC_DIR        = ../../include
@@ -23,7 +38,7 @@ MAKES =
 
 .c.o:;  $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
 
-all: $(LIB_SO) $(LIB) $(MOCK_OBJ)
+all: $(LIB_SO) $(LIB_OBJ) $(MOCK_OBJ)
 
 $(LIB_OBJ) $(MOCK_OBJ) $(TEST_OBJ): ../../conf/makedefs.out
 
@@ -61,13 +76,62 @@ lib_update: $(LIB_DIR)/$(LIB) $(HDRS) $(MOCK_OBJ)
          cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \
        done
 
-tests:
-
 clean:
-       rm -f $(LIB_SO) *.o
+       rm -f $(LIB_SO) *.o $(LIB) *core $(TESTPROG) junk $(MAKES) *.tmp
 
 tidy: clean
 
+tests: update test_mock_dns_lookup test_mock_getaddrinfo test_mock_myaddrinfo \
+       test_mock_servent test_match_addr test_match_basic test_mock_server
+
+mock_myaddrinfo_test: mock_myaddrinfo_test.o mock_myaddrinfo.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o mock_myaddrinfo.o $(LIB) $(LIBS) $(SYSLIBS)
+
+test_mock_myaddrinfo: update mock_myaddrinfo_test
+       $(SHLIB_ENV) ${VALGRIND} ./mock_myaddrinfo_test
+
+mock_dns_lookup_test: mock_dns_lookup_test.o mock_dns_lookup.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o mock_dns_lookup.o $(LIB) $(LIBS) $(SYSLIBS)
+
+test_mock_dns_lookup: update mock_dns_lookup_test
+       $(SHLIB_ENV) ${VALGRIND} ./mock_dns_lookup_test
+
+mock_servent_test: mock_servent_test.o mock_servent.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o mock_servent.o $(LIB) $(LIBS) $(SYSLIBS)
+
+test_mock_servent: update mock_servent_test
+       $(SHLIB_ENV) ${VALGRIND} ./mock_servent_test
+
+mock_getaddrinfo_test: mock_getaddrinfo_test.o mock_getaddrinfo.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o mock_getaddrinfo.o $(LIB) $(LIBS) $(SYSLIBS)
+
+test_mock_getaddrinfo: update mock_getaddrinfo_test
+       $(SHLIB_ENV) ${VALGRIND} ./mock_getaddrinfo_test
+
+match_addr_test: match_addr_test.o match_addr.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o match_addr.o $(LIB) $(LIBS) $(SYSLIBS)
+
+test_match_addr: update match_addr_test
+       $(SHLIB_ENV) ${VALGRIND} ./match_addr_test
+
+match_basic_test: match_basic_test.o match_basic.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o match_basic.o $(LIB) $(LIBS) $(SYSLIBS)
+
+test_match_basic: update match_basic_test
+       $(SHLIB_ENV) ${VALGRIND} ./match_basic_test
+
+mock_server_test: mock_server_test.o mock_server.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o mock_server.o $(LIB) $(LIBS) $(SYSLIBS)
+
+test_mock_server: update mock_server_test
+       $(SHLIB_ENV) ${VALGRIND} ./mock_server_test
+
+match_attr_test: match_attr_test.o match_attr.o $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o match_attr.o $(LIB) $(LIBS) $(SYSLIBS)
+
+test_match_attr: update match_attr_test
+       $(SHLIB_ENV) ${VALGRIND} ./match_attr_test
+
 depend: $(MAKES)
        (sed '1,/^# do not edit/!d' Makefile.in; \
        set -e; for i in [a-z][a-z0-9]*.c; do \
@@ -78,6 +142,17 @@ depend: $(MAKES)
        @$(EXPORT) make -f Makefile.in Makefile 1>&2
 
 # do not edit below this line - it is generated by 'make depend'
+addrinfo_to_string.o: ../../include/check_arg.h
+addrinfo_to_string.o: ../../include/msg.h
+addrinfo_to_string.o: ../../include/myaddrinfo.h
+addrinfo_to_string.o: ../../include/name_code.h
+addrinfo_to_string.o: ../../include/name_mask.h
+addrinfo_to_string.o: ../../include/sys_defs.h
+addrinfo_to_string.o: ../../include/vbuf.h
+addrinfo_to_string.o: ../../include/vstring.h
+addrinfo_to_string.o: ../../include/wrap_netdb.h
+addrinfo_to_string.o: addrinfo_to_string.c
+addrinfo_to_string.o: addrinfo_to_string.h
 dict_test_helper.o: ../../include/argv.h
 dict_test_helper.o: ../../include/check_arg.h
 dict_test_helper.o: ../../include/dict.h
@@ -90,6 +165,127 @@ dict_test_helper.o: ../../include/vstream.h
 dict_test_helper.o: ../../include/vstring.h
 dict_test_helper.o: dict_test_helper.c
 dict_test_helper.o: dict_test_helper.h
+make_addr.o: ../../include/msg.h
+make_addr.o: ../../include/mymalloc.h
+make_addr.o: ../../include/sys_defs.h
+make_addr.o: ../../include/wrap_netdb.h
+make_addr.o: make_addr.c
+make_addr.o: make_addr.h
+make_attr.o: ../../include/argv.h
+make_attr.o: ../../include/attr.h
+make_attr.o: ../../include/check_arg.h
+make_attr.o: ../../include/htable.h
+make_attr.o: ../../include/msg.h
+make_attr.o: ../../include/msg_jmp.h
+make_attr.o: ../../include/mymalloc.h
+make_attr.o: ../../include/nvtable.h
+make_attr.o: ../../include/ptest.h
+make_attr.o: ../../include/sys_defs.h
+make_attr.o: ../../include/vbuf.h
+make_attr.o: ../../include/vstream.h
+make_attr.o: ../../include/vstring.h
+make_attr.o: make_attr.c
+make_attr.o: make_attr.h
+match_addr.o: ../../include/argv.h
+match_addr.o: ../../include/check_arg.h
+match_addr.o: ../../include/msg.h
+match_addr.o: ../../include/msg_jmp.h
+match_addr.o: ../../include/ptest.h
+match_addr.o: ../../include/sys_defs.h
+match_addr.o: ../../include/vbuf.h
+match_addr.o: ../../include/vstream.h
+match_addr.o: ../../include/vstring.h
+match_addr.o: ../../include/wrap_netdb.h
+match_addr.o: addrinfo_to_string.h
+match_addr.o: match_addr.c
+match_addr.o: match_addr.h
+match_addr.o: match_basic.h
+match_addr_test.o: ../../include/argv.h
+match_addr_test.o: ../../include/check_arg.h
+match_addr_test.o: ../../include/msg.h
+match_addr_test.o: ../../include/msg_jmp.h
+match_addr_test.o: ../../include/msg_output.h
+match_addr_test.o: ../../include/msg_vstream.h
+match_addr_test.o: ../../include/myrand.h
+match_addr_test.o: ../../include/pmock_expect.h
+match_addr_test.o: ../../include/ptest.h
+match_addr_test.o: ../../include/ptest_main.h
+match_addr_test.o: ../../include/stringops.h
+match_addr_test.o: ../../include/sys_defs.h
+match_addr_test.o: ../../include/vbuf.h
+match_addr_test.o: ../../include/vstream.h
+match_addr_test.o: ../../include/vstring.h
+match_addr_test.o: ../../include/wrap_netdb.h
+match_addr_test.o: make_addr.h
+match_addr_test.o: match_addr.h
+match_addr_test.o: match_addr_test.c
+match_attr.o: ../../include/argv.h
+match_attr.o: ../../include/attr.h
+match_attr.o: ../../include/check_arg.h
+match_attr.o: ../../include/htable.h
+match_attr.o: ../../include/msg.h
+match_attr.o: ../../include/msg_jmp.h
+match_attr.o: ../../include/mymalloc.h
+match_attr.o: ../../include/nvtable.h
+match_attr.o: ../../include/ptest.h
+match_attr.o: ../../include/sys_defs.h
+match_attr.o: ../../include/vbuf.h
+match_attr.o: ../../include/vstream.h
+match_attr.o: ../../include/vstring.h
+match_attr.o: match_attr.c
+match_attr.o: match_attr.h
+match_attr_test.o: ../../include/argv.h
+match_attr_test.o: ../../include/attr.h
+match_attr_test.o: ../../include/check_arg.h
+match_attr_test.o: ../../include/htable.h
+match_attr_test.o: ../../include/msg.h
+match_attr_test.o: ../../include/msg_jmp.h
+match_attr_test.o: ../../include/msg_output.h
+match_attr_test.o: ../../include/msg_vstream.h
+match_attr_test.o: ../../include/mymalloc.h
+match_attr_test.o: ../../include/myrand.h
+match_attr_test.o: ../../include/nvtable.h
+match_attr_test.o: ../../include/pmock_expect.h
+match_attr_test.o: ../../include/ptest.h
+match_attr_test.o: ../../include/ptest_main.h
+match_attr_test.o: ../../include/stringops.h
+match_attr_test.o: ../../include/sys_defs.h
+match_attr_test.o: ../../include/vbuf.h
+match_attr_test.o: ../../include/vstream.h
+match_attr_test.o: ../../include/vstring.h
+match_attr_test.o: make_attr.h
+match_attr_test.o: match_attr.h
+match_attr_test.o: match_attr_test.c
+match_basic.o: ../../include/argv.h
+match_basic.o: ../../include/check_arg.h
+match_basic.o: ../../include/msg.h
+match_basic.o: ../../include/msg_jmp.h
+match_basic.o: ../../include/ptest.h
+match_basic.o: ../../include/sys_defs.h
+match_basic.o: ../../include/vbuf.h
+match_basic.o: ../../include/vstream.h
+match_basic.o: ../../include/vstring.h
+match_basic.o: match_basic.c
+match_basic.o: match_basic.h
+match_basic_test.o: ../../include/argv.h
+match_basic_test.o: ../../include/check_arg.h
+match_basic_test.o: ../../include/msg.h
+match_basic_test.o: ../../include/msg_jmp.h
+match_basic_test.o: ../../include/msg_output.h
+match_basic_test.o: ../../include/msg_vstream.h
+match_basic_test.o: ../../include/myrand.h
+match_basic_test.o: ../../include/name_code.h
+match_basic_test.o: ../../include/name_mask.h
+match_basic_test.o: ../../include/pmock_expect.h
+match_basic_test.o: ../../include/ptest.h
+match_basic_test.o: ../../include/ptest_main.h
+match_basic_test.o: ../../include/stringops.h
+match_basic_test.o: ../../include/sys_defs.h
+match_basic_test.o: ../../include/vbuf.h
+match_basic_test.o: ../../include/vstream.h
+match_basic_test.o: ../../include/vstring.h
+match_basic_test.o: match_basic.h
+match_basic_test.o: match_basic_test.c
 mock_dict.o: ../../include/argv.h
 mock_dict.o: ../../include/check_arg.h
 mock_dict.o: ../../include/dict.h
@@ -105,6 +301,129 @@ mock_dict.o: ../../include/vstream.h
 mock_dict.o: ../../include/vstring.h
 mock_dict.o: mock_dict.c
 mock_dict.o: mock_dict.h
+mock_dns_lookup.o: ../../include/argv.h
+mock_dns_lookup.o: ../../include/check_arg.h
+mock_dns_lookup.o: ../../include/dns.h
+mock_dns_lookup.o: ../../include/hex_code.h
+mock_dns_lookup.o: ../../include/msg.h
+mock_dns_lookup.o: ../../include/msg_jmp.h
+mock_dns_lookup.o: ../../include/myaddrinfo.h
+mock_dns_lookup.o: ../../include/mymalloc.h
+mock_dns_lookup.o: ../../include/name_code.h
+mock_dns_lookup.o: ../../include/pmock_expect.h
+mock_dns_lookup.o: ../../include/ptest.h
+mock_dns_lookup.o: ../../include/sock_addr.h
+mock_dns_lookup.o: ../../include/stringops.h
+mock_dns_lookup.o: ../../include/sys_defs.h
+mock_dns_lookup.o: ../../include/vbuf.h
+mock_dns_lookup.o: ../../include/vstream.h
+mock_dns_lookup.o: ../../include/vstring.h
+mock_dns_lookup.o: mock_dns.h
+mock_dns_lookup.o: mock_dns_lookup.c
+mock_dns_lookup_test.o: ../../include/argv.h
+mock_dns_lookup_test.o: ../../include/check_arg.h
+mock_dns_lookup_test.o: ../../include/dns.h
+mock_dns_lookup_test.o: ../../include/msg.h
+mock_dns_lookup_test.o: ../../include/msg_jmp.h
+mock_dns_lookup_test.o: ../../include/msg_output.h
+mock_dns_lookup_test.o: ../../include/msg_vstream.h
+mock_dns_lookup_test.o: ../../include/myaddrinfo.h
+mock_dns_lookup_test.o: ../../include/myrand.h
+mock_dns_lookup_test.o: ../../include/pmock_expect.h
+mock_dns_lookup_test.o: ../../include/ptest.h
+mock_dns_lookup_test.o: ../../include/ptest_main.h
+mock_dns_lookup_test.o: ../../include/sock_addr.h
+mock_dns_lookup_test.o: ../../include/stringops.h
+mock_dns_lookup_test.o: ../../include/sys_defs.h
+mock_dns_lookup_test.o: ../../include/vbuf.h
+mock_dns_lookup_test.o: ../../include/vstream.h
+mock_dns_lookup_test.o: ../../include/vstring.h
+mock_dns_lookup_test.o: mock_dns.h
+mock_dns_lookup_test.o: mock_dns_lookup_test.c
+mock_getaddrinfo.o: ../../include/argv.h
+mock_getaddrinfo.o: ../../include/check_arg.h
+mock_getaddrinfo.o: ../../include/msg.h
+mock_getaddrinfo.o: ../../include/msg_jmp.h
+mock_getaddrinfo.o: ../../include/myaddrinfo.h
+mock_getaddrinfo.o: ../../include/mymalloc.h
+mock_getaddrinfo.o: ../../include/pmock_expect.h
+mock_getaddrinfo.o: ../../include/ptest.h
+mock_getaddrinfo.o: ../../include/sys_defs.h
+mock_getaddrinfo.o: ../../include/vbuf.h
+mock_getaddrinfo.o: ../../include/vstream.h
+mock_getaddrinfo.o: ../../include/vstring.h
+mock_getaddrinfo.o: ../../include/wrap_netdb.h
+mock_getaddrinfo.o: addrinfo_to_string.h
+mock_getaddrinfo.o: make_addr.h
+mock_getaddrinfo.o: match_addr.h
+mock_getaddrinfo.o: match_basic.h
+mock_getaddrinfo.o: mock_getaddrinfo.c
+mock_getaddrinfo.o: mock_getaddrinfo.h
+mock_getaddrinfo_test.o: ../../include/argv.h
+mock_getaddrinfo_test.o: ../../include/check_arg.h
+mock_getaddrinfo_test.o: ../../include/msg.h
+mock_getaddrinfo_test.o: ../../include/msg_jmp.h
+mock_getaddrinfo_test.o: ../../include/msg_output.h
+mock_getaddrinfo_test.o: ../../include/msg_vstream.h
+mock_getaddrinfo_test.o: ../../include/myaddrinfo.h
+mock_getaddrinfo_test.o: ../../include/myrand.h
+mock_getaddrinfo_test.o: ../../include/pmock_expect.h
+mock_getaddrinfo_test.o: ../../include/ptest.h
+mock_getaddrinfo_test.o: ../../include/ptest_main.h
+mock_getaddrinfo_test.o: ../../include/stringops.h
+mock_getaddrinfo_test.o: ../../include/sys_defs.h
+mock_getaddrinfo_test.o: ../../include/vbuf.h
+mock_getaddrinfo_test.o: ../../include/vstream.h
+mock_getaddrinfo_test.o: ../../include/vstring.h
+mock_getaddrinfo_test.o: ../../include/wrap_netdb.h
+mock_getaddrinfo_test.o: addrinfo_to_string.h
+mock_getaddrinfo_test.o: make_addr.h
+mock_getaddrinfo_test.o: match_addr.h
+mock_getaddrinfo_test.o: match_basic.h
+mock_getaddrinfo_test.o: mock_getaddrinfo.h
+mock_getaddrinfo_test.o: mock_getaddrinfo_test.c
+mock_myaddrinfo.o: ../../include/argv.h
+mock_myaddrinfo.o: ../../include/check_arg.h
+mock_myaddrinfo.o: ../../include/msg.h
+mock_myaddrinfo.o: ../../include/msg_jmp.h
+mock_myaddrinfo.o: ../../include/myaddrinfo.h
+mock_myaddrinfo.o: ../../include/mymalloc.h
+mock_myaddrinfo.o: ../../include/pmock_expect.h
+mock_myaddrinfo.o: ../../include/ptest.h
+mock_myaddrinfo.o: ../../include/sys_defs.h
+mock_myaddrinfo.o: ../../include/vbuf.h
+mock_myaddrinfo.o: ../../include/vstream.h
+mock_myaddrinfo.o: ../../include/vstring.h
+mock_myaddrinfo.o: ../../include/wrap_netdb.h
+mock_myaddrinfo.o: addrinfo_to_string.h
+mock_myaddrinfo.o: make_addr.h
+mock_myaddrinfo.o: match_addr.h
+mock_myaddrinfo.o: match_basic.h
+mock_myaddrinfo.o: mock_myaddrinfo.c
+mock_myaddrinfo.o: mock_myaddrinfo.h
+mock_myaddrinfo_test.o: ../../include/argv.h
+mock_myaddrinfo_test.o: ../../include/check_arg.h
+mock_myaddrinfo_test.o: ../../include/msg.h
+mock_myaddrinfo_test.o: ../../include/msg_jmp.h
+mock_myaddrinfo_test.o: ../../include/msg_output.h
+mock_myaddrinfo_test.o: ../../include/msg_vstream.h
+mock_myaddrinfo_test.o: ../../include/myaddrinfo.h
+mock_myaddrinfo_test.o: ../../include/myrand.h
+mock_myaddrinfo_test.o: ../../include/pmock_expect.h
+mock_myaddrinfo_test.o: ../../include/ptest.h
+mock_myaddrinfo_test.o: ../../include/ptest_main.h
+mock_myaddrinfo_test.o: ../../include/stringops.h
+mock_myaddrinfo_test.o: ../../include/sys_defs.h
+mock_myaddrinfo_test.o: ../../include/vbuf.h
+mock_myaddrinfo_test.o: ../../include/vstream.h
+mock_myaddrinfo_test.o: ../../include/vstring.h
+mock_myaddrinfo_test.o: ../../include/wrap_netdb.h
+mock_myaddrinfo_test.o: addrinfo_to_string.h
+mock_myaddrinfo_test.o: make_addr.h
+mock_myaddrinfo_test.o: match_addr.h
+mock_myaddrinfo_test.o: match_basic.h
+mock_myaddrinfo_test.o: mock_myaddrinfo.h
+mock_myaddrinfo_test.o: mock_myaddrinfo_test.c
 mock_open_as.o: ../../include/check_arg.h
 mock_open_as.o: ../../include/msg.h
 mock_open_as.o: ../../include/open_as.h
@@ -114,6 +433,80 @@ mock_open_as.o: ../../include/vstream.h
 mock_open_as.o: ../../include/vstring.h
 mock_open_as.o: mock_open_as.c
 mock_open_as.o: mock_open_as.h
+mock_servent.o: ../../include/argv.h
+mock_servent.o: ../../include/check_arg.h
+mock_servent.o: ../../include/msg.h
+mock_servent.o: ../../include/msg_jmp.h
+mock_servent.o: ../../include/mymalloc.h
+mock_servent.o: ../../include/pmock_expect.h
+mock_servent.o: ../../include/ptest.h
+mock_servent.o: ../../include/sys_defs.h
+mock_servent.o: ../../include/vbuf.h
+mock_servent.o: ../../include/vstream.h
+mock_servent.o: ../../include/vstring.h
+mock_servent.o: ../../include/wrap_netdb.h
+mock_servent.o: mock_servent.c
+mock_servent.o: mock_servent.h
+mock_servent_test.o: ../../include/argv.h
+mock_servent_test.o: ../../include/check_arg.h
+mock_servent_test.o: ../../include/msg.h
+mock_servent_test.o: ../../include/msg_jmp.h
+mock_servent_test.o: ../../include/msg_output.h
+mock_servent_test.o: ../../include/msg_vstream.h
+mock_servent_test.o: ../../include/myrand.h
+mock_servent_test.o: ../../include/pmock_expect.h
+mock_servent_test.o: ../../include/ptest.h
+mock_servent_test.o: ../../include/ptest_main.h
+mock_servent_test.o: ../../include/stringops.h
+mock_servent_test.o: ../../include/sys_defs.h
+mock_servent_test.o: ../../include/vbuf.h
+mock_servent_test.o: ../../include/vstream.h
+mock_servent_test.o: ../../include/vstring.h
+mock_servent_test.o: ../../include/wrap_netdb.h
+mock_servent_test.o: mock_servent.h
+mock_servent_test.o: mock_servent_test.c
+mock_server.o: ../../include/argv.h
+mock_server.o: ../../include/check_arg.h
+mock_server.o: ../../include/connect.h
+mock_server.o: ../../include/events.h
+mock_server.o: ../../include/iostuff.h
+mock_server.o: ../../include/msg.h
+mock_server.o: ../../include/msg_jmp.h
+mock_server.o: ../../include/mymalloc.h
+mock_server.o: ../../include/ptest.h
+mock_server.o: ../../include/sys_defs.h
+mock_server.o: ../../include/vbuf.h
+mock_server.o: ../../include/vstream.h
+mock_server.o: ../../include/vstring.h
+mock_server.o: match_attr.h
+mock_server.o: mock_server.c
+mock_server.o: mock_server.h
+mock_server_test.o: ../../include/argv.h
+mock_server_test.o: ../../include/attr.h
+mock_server_test.o: ../../include/check_arg.h
+mock_server_test.o: ../../include/connect.h
+mock_server_test.o: ../../include/events.h
+mock_server_test.o: ../../include/htable.h
+mock_server_test.o: ../../include/iostuff.h
+mock_server_test.o: ../../include/mail_proto.h
+mock_server_test.o: ../../include/msg.h
+mock_server_test.o: ../../include/msg_jmp.h
+mock_server_test.o: ../../include/msg_output.h
+mock_server_test.o: ../../include/msg_vstream.h
+mock_server_test.o: ../../include/mymalloc.h
+mock_server_test.o: ../../include/myrand.h
+mock_server_test.o: ../../include/nvtable.h
+mock_server_test.o: ../../include/pmock_expect.h
+mock_server_test.o: ../../include/ptest.h
+mock_server_test.o: ../../include/ptest_main.h
+mock_server_test.o: ../../include/stringops.h
+mock_server_test.o: ../../include/sys_defs.h
+mock_server_test.o: ../../include/vbuf.h
+mock_server_test.o: ../../include/vstream.h
+mock_server_test.o: ../../include/vstring.h
+mock_server_test.o: make_attr.h
+mock_server_test.o: mock_server.h
+mock_server_test.o: mock_server_test.c
 mock_spawn_command.o: ../../include/check_arg.h
 mock_spawn_command.o: ../../include/msg.h
 mock_spawn_command.o: ../../include/spawn_command.h
diff --git a/postfix/src/testing/addrinfo_to_string.c b/postfix/src/testing/addrinfo_to_string.c
new file mode 100644 (file)
index 0000000..c08d75a
--- /dev/null
@@ -0,0 +1,304 @@
+/*++
+/* NAME
+/*      addrinfo_to_string 3
+/* SUMMARY
+/*     address info to string conversion
+/* SYNOPSIS
+/*      #include <addrinfo_to_string.h>
+/*
+/*      const char *pf_to_string(int);
+/*
+/*      const char *af_to_string(int);
+/*
+/*      const char *socktype_to_string(int);
+/*
+/*      const char *ipprotocol_to_string(int);
+/*
+/*      const char *ai_flags_to_string(
+/*      VSTRING *buf,
+/*      int     flags);
+/*
+/*      const char *ni_flags_to_string(
+/*      VSTRING *buf,
+/*      int     flags);
+/*      char    *append_addrinfo_to_string(
+/*      VSTRING *buf,
+/*      const struct addrinfo *ai)
+/*
+/*      char    *addrinfo_hints_to_string(
+/*      VSTRING *buf,
+/*      const struct addrinfo *hints)
+/*
+/*      char    *sockaddr_to_string(
+/*      VSTRING *buf,
+/*      const struct sockaddr *sa,
+/*     size_t  sa_len);
+/* DESCRIPTION
+/*     The functions in this module convert address information
+/*     to textual form, for use in test error messages. They
+/*     implement only the necessary subsets.
+/*
+/*      pf_to_string(), af_to_string(), socktype_to_string,
+/*      ipprotocol_to_string, ai_flags_to_string(), ni_flags_to_string()
+/*      produce a textual representation of addrinfo properties or
+/*      getnameinfo() flags.
+/*
+/*      append_addrinfo_to_string() appends a textual representation
+/*      of the referenced addrinfo object (only one) to the specified
+/*      buffer.
+/*
+/*      addrinfo_hints_to_string() writes a textual representation of
+/*      the referenced getaddrinfo() hints object.
+/*
+/*      sockaddr_to_string() writes a textual representation of the
+/*      referenced sockaddr object.
+/* LICENSE
+/* .ad
+/* .fi
+/*      The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*      Wietse Venema
+/*      Google, Inc.
+/*      111 8th Avenue
+/*      New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <wrap_netdb.h>
+#include <stdio.h>                     /* sprintf/snprintf */
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <myaddrinfo.h>
+#include <name_code.h>
+#include <name_mask.h>
+
+ /*
+  * Test library.
+  */
+#include <addrinfo_to_string.h>
+
+#define STR_OR_NULL(s)          ((s) ? (s) : "(null)")
+
+#ifdef NO_SNPRINTF
+#define MY_SNPRINTF(bp, sz, fmt, ...) \
+        sprintf((bp), (fmt), __VA_ARGS__)
+#else
+#define MY_SNPRINTF(bp, sz, fmt, ...) \
+        snprintf((bp), (sz), (fmt), __VA_ARGS__)
+#endif
+
+#define STR     vstring_str
+
+/* pf_to_string - convert protocol family to human-readable form */
+
+const char *pf_to_string(int af)
+{
+    static const NAME_CODE pf_codes[] = {
+       "PF_INET", PF_INET,
+       "PF_INET6", PF_INET6,
+       0,
+    };
+    const char *name;
+
+    name = str_name_code(pf_codes, af);
+    return (name ? name : "unknown-protocol-family");
+}
+
+/* af_to_string - convert address family to human-readable form */
+
+const char *af_to_string(int af)
+{
+    static const NAME_CODE af_codes[] = {
+       "AF_INET", AF_INET,
+       "AF_INET6", AF_INET6,
+       0,
+    };
+    const char *name;
+
+    name = str_name_code(af_codes, af);
+    return (name ? name : "unknown-address-family");
+}
+
+/* socktype_to_string - convert socket type to human-readable form */
+
+const char *socktype_to_string(int socktype)
+{
+    static const NAME_CODE socktypes[] = {
+       "SOCK_STREAM", SOCK_STREAM,
+       "SOCK_DGRAM", SOCK_DGRAM,
+       "SOCK_RAW", SOCK_RAW,
+       "0", 0,
+       0,
+    };
+    const char *name;
+
+    name = str_name_code(socktypes, socktype);
+    return (name ? name : "unknown-socket-type");
+}
+
+/* ipprotocol_to_string - convert protocol to human-readable form */
+
+const char *ipprotocol_to_string(int proto)
+{
+    static const NAME_CODE protocols[] = {
+       "IPPROTO_UDP", IPPROTO_UDP,
+       "IPPROTO_TCP", IPPROTO_TCP,
+       "0", 0,
+       0,
+    };
+    const char *name;
+
+    name = str_name_code(protocols, proto);
+    return (name ? name : "unknown-protocol");
+}
+
+/* ai_flags_to_string - convert addrinfo flags to human-readable form */
+
+const char *ai_flags_to_string(VSTRING *buf, int flags)
+{
+    static const NAME_MASK ai_flags[] = {
+#ifdef AI_IDN
+       "AI_IDN", AI_IDN,
+#endif
+#ifdef AI_CANONIDN
+       "AI_CANONIDN", AI_CANONIDN,
+#endif
+#ifdef AI_IDN_ALLOW_UNASSIGNED
+       "AI_IDN_ALLOW_UNASSIGNED", AI_IDN_ALLOW_UNASSIGNED,
+#endif
+#ifdef AI_IDN_USE_STD3_ASCII_RULES
+       "AI_IDN_USE_STD3_ASCII_RULES", AI_IDN_USE_STD3_ASCII_RULES,
+#endif
+       "AI_ADDRCONFIG", AI_ADDRCONFIG,
+       "AI_CANONNAME", AI_CANONNAME,
+       "AI_NUMERICHOST", AI_NUMERICHOST,
+       "AI_NUMERICSERV", AI_NUMERICSERV,
+       "AI_PASSIVE", AI_PASSIVE,
+       0,
+    };
+
+    return (str_name_mask_opt(buf, "ai_flags_to_string", ai_flags, flags,
+                      NAME_MASK_NUMBER | NAME_MASK_PIPE | NAME_MASK_NULL));
+}
+
+/* ni_flags_to_string - convert getnameinfo flags to human-readable form */
+
+const char *ni_flags_to_string(VSTRING *buf, int flags)
+{
+    static const NAME_MASK ni_flags[] = {
+       "NI_NAMEREQD", NI_NAMEREQD,
+       "NI_DGRAM", NI_DGRAM,
+       "NI_NOFQDN", NI_NOFQDN,
+       "NI_NUMERICHOST", NI_NUMERICHOST,
+       "NI_NUMERICSERV", NI_NUMERICSERV,
+       0,
+    };
+
+    return (str_name_mask_opt(buf, "ni_flags_to_string", ni_flags, flags,
+                      NAME_MASK_NUMBER | NAME_MASK_PIPE | NAME_MASK_NULL));
+}
+
+/* append_addrinfo_to_string - print human-readable addrinfo */
+
+char   *append_addrinfo_to_string(VSTRING *buf, const struct addrinfo *ai)
+{
+    if (ai == 0) {
+       vstring_sprintf_append(buf, "(null)");
+    } else {
+       VSTRING *sockaddr_buf = vstring_alloc(100);
+       VSTRING *flags_buf = vstring_alloc(100);
+
+       vstring_sprintf_append(buf, "{%s, %s, %s, %s, %d, %s, %s}",
+                              ai_flags_to_string(flags_buf, ai->ai_flags),
+                              pf_to_string(ai->ai_family),
+                              socktype_to_string(ai->ai_socktype),
+                              ipprotocol_to_string(ai->ai_protocol),
+                              ai->ai_addrlen,
+                              sockaddr_to_string(sockaddr_buf, ai->ai_addr,
+                                                 ai->ai_addrlen),
+                              STR_OR_NULL(ai->ai_canonname));
+       vstring_free(sockaddr_buf);
+       vstring_free(flags_buf);
+    }
+    return (STR(buf));
+}
+
+/* addrinfo_hints_to_string - append human-readable hints */
+
+char   *addrinfo_hints_to_string(VSTRING *buf, const struct addrinfo *ai)
+{
+    if (ai == 0) {
+       vstring_sprintf(buf, "(null)");
+    } else {
+       VSTRING *flags_buf = vstring_alloc(100);
+
+       vstring_sprintf(buf, "{%s, %s, %s, %s}",
+                       ai_flags_to_string(flags_buf, ai->ai_flags),
+                       pf_to_string(ai->ai_family),
+                       socktype_to_string(ai->ai_socktype),
+                       ipprotocol_to_string(ai->ai_protocol));
+       vstring_free(flags_buf);
+    }
+    return (STR(buf));
+}
+
+/* sockaddr_to_strings - convert to printable host address and port */
+
+static void sockaddr_to_strings(const struct sockaddr *sa, size_t salen,
+                                       MAI_HOSTADDR_STR *hostaddr,
+                                       MAI_SERVPORT_STR *portnum)
+{
+    if (sa->sa_family == AF_INET) {
+       struct sockaddr_in *sin = (struct sockaddr_in *) sa;
+
+       if (salen < sizeof(*sin))
+           msg_fatal("sockaddr_to_strings: bad sockaddr length %ld",
+                     (long) salen);
+       MY_SNPRINTF(portnum->buf, sizeof(*portnum), "%d", ntohs(sin->sin_port));
+       if (inet_ntop(AF_INET, &sin->sin_addr, hostaddr->buf,
+                     sizeof(*hostaddr)) == 0)
+           msg_fatal("inet_ntop(AF_INET,...): %m");
+#ifdef HAS_IPV6
+    } else if (sa->sa_family == AF_INET6) {
+       struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
+
+       if (salen < sizeof(*sin6))
+           msg_fatal("sockaddr_to_strings: bad sockaddr length %ld",
+                     (long) salen);
+       MY_SNPRINTF(portnum->buf, sizeof(*portnum), "%d",
+                   ntohs(sin6->sin6_port));
+       if (inet_ntop(AF_INET6, &sin6->sin6_addr, hostaddr->buf,
+                     sizeof(*hostaddr)) == 0)
+           msg_fatal("inet_ntop(AF_INET,...): %m");
+#endif
+    } else {
+       errno = EAFNOSUPPORT;
+       msg_panic("sockaddr_to_strings: protocol familiy %d: %m",
+                 sa->sa_family);
+    }
+}
+
+/* sockaddr_to_string - render human-readable sockaddr */
+
+char   *sockaddr_to_string(VSTRING *buf, const struct sockaddr *sa,
+                                  size_t salen)
+{
+    MAI_HOSTADDR_STR hostaddr;
+    MAI_SERVPORT_STR portnum;
+
+    sockaddr_to_strings(sa, salen, &hostaddr, &portnum);
+    vstring_sprintf(buf, "{%s, %s, %s}",
+                   af_to_string(sa->sa_family),
+                   hostaddr.buf, portnum.buf);
+    return (STR(buf));
+}
diff --git a/postfix/src/testing/addrinfo_to_string.h b/postfix/src/testing/addrinfo_to_string.h
new file mode 100644 (file)
index 0000000..758cc2d
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef _ADDRINFO_TO_STRING_H_INCLUDED_
+#define _ADDRINFO_TO_STRING_H_INCLUDED_
+
+/*++
+/* NAME
+/*     addrinfo_to_string 3h
+/* SUMMARY
+/*     address info to string conversion
+/* SYNOPSIS
+/*     #include <addrinfo_to_string.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <wrap_netdb.h>
+
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+
+ /*
+  * External interface.
+  */
+extern const char *pf_to_string(int);
+extern const char *af_to_string(int);
+extern const char *socktype_to_string(int);
+extern const char *ipprotocol_to_string(int);
+extern const char *ai_flags_to_string(VSTRING *, int);
+extern const char *ni_flags_to_string(VSTRING *, int);
+extern char *append_addrinfo_to_string(VSTRING *, const struct addrinfo *);
+extern char *addrinfo_hints_to_string(VSTRING *, const struct addrinfo *);
+extern char *sockaddr_to_string(VSTRING *, const struct sockaddr *, size_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/make_addr.c b/postfix/src/testing/make_addr.c
new file mode 100644 (file)
index 0000000..8b6fcfe
--- /dev/null
@@ -0,0 +1,208 @@
+/*++
+/* NAME
+/*     make_addr 3
+/* SUMMARY
+/*     make_addrinfo(), freeaddrinfo(), make_sockaddr() for hermetic tests
+/* SYNOPSIS
+/*     #include <make_addrinfo.h>
+/*
+/*     struct addrinfo *make_addrinfo(
+/*     const struct addrinfo *hints,
+/*     const char *name,
+/*     const char *addr,
+/*     int     port)
+/*
+/*     struct addrinfo *copy_addrinfo(const struct addrinfo *ai)
+/*
+/*     void    freeaddrinfo(struct addrinfo *ai)
+/*
+/*     struct sockaddr *make_sockaddr(
+/*     int     family,
+/*     const char *addr,
+/*     int     port)
+/*
+/*     void    free_sockaddr(struct sockaddr *sa)
+/* DESCRIPTION
+/*     This module contains helper functions to set up mock
+/*     expectations.
+/*
+/*     make_addrinfo() creates one addrinfo structure. The hints
+/*     argument must specify the protocol family for the addr
+/*     argument (i.e. not PF_UNSPEC). To create a linked list,
+/*     manually append make_addrinfo() results. The port is in
+/*     host byte order.
+/*
+/*     copy_addrinfo() makes a deep copy of a linked list of
+/*     addrinfo structures.
+/*
+/*     freeaddrinfo() deletes a linked list of addrinfo structures.
+/*     This function must be used for addrinfo structures created
+/*     with make_addrinfo() and copy_addrinfo().
+/*
+/*     make_sockaddr() creates a sockaddr structure from the string
+/*     representation of an IP address, and a port in host byte
+/*     order.
+/*
+/*     free_sockaddr() exists to make program code more explicit.
+/* DIAGNOSTICS
+/*     make_sockaddr() and make_addrinfo() terminate with a fatal
+/*     error when an unknown address family is specified, or when
+/*     the string representation of an IP address does not match
+/*     the address family.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <wrap_netdb.h>
+#include <string.h>
+#include <errno.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <mymalloc.h>
+
+ /*
+  * Test library.
+  */
+#include <make_addr.h>
+
+#define MYSTRDUP_OR_NULL(x)    ((x) ? mystrdup(x) : 0)
+
+/* make_sockaddr - helper to create mock sockaddr instances */
+
+struct sockaddr *make_sockaddr(int family, const char *addr, int port)
+{
+    if (family == AF_INET) {
+       struct sockaddr_in *sa;
+
+       sa = (struct sockaddr_in *) mymalloc(sizeof(*sa));
+       memset(sa, 0, sizeof(*sa));
+       switch (inet_pton(AF_INET, addr, (void *) &sa->sin_addr)) {
+       case 1:
+           break;
+       case 0:
+           msg_fatal("bad address syntax: '%s'", addr);
+       case -1:
+           msg_fatal("inet_pton(AF_INET, %s, (ptr)): %m", addr);
+       }
+       sa->sin_family = AF_INET;
+       sa->sin_port = htons(port);
+       return ((struct sockaddr *) sa);
+#ifdef HAS_IPV6
+    } else if (family == AF_INET6) {
+       struct sockaddr_in6 *sa;
+
+       sa = (struct sockaddr_in6 *) mymalloc(sizeof(*sa));
+       memset(sa, 0, sizeof(*sa));
+       switch (inet_pton(AF_INET6, addr, (void *) &sa->sin6_addr)) {
+       case 1:
+           break;
+       case 0:
+           msg_fatal("bad address syntax: '%s'", addr);
+       case -1:
+           msg_fatal("inet_pton(AF_INET6, %s, (ptr)): %m", addr);
+       }
+       sa->sin6_family = AF_INET6;
+       sa->sin6_port = htons(port);
+       return ((struct sockaddr *) sa);
+#endif
+    } else {
+       errno = EAFNOSUPPORT;
+       msg_panic("make_sockaddr: address familiy %d: %m", family);
+    }
+}
+
+/* free_sockaddr - destructor for make_sockaddr()  and copy_addrinfo() */
+
+void    free_sockaddr(struct sockaddr *sa)
+{
+    myfree(sa);
+}
+
+/* copy_addrinfo - expectation helper */
+
+struct addrinfo *copy_addrinfo(const struct addrinfo *in)
+{
+    struct addrinfo *out;
+
+    /*
+     * First a shallow copy, followed by deep copies for pointer fields.
+     */
+    if (in == 0)
+       return (0);
+    out = (struct addrinfo *) mymalloc(sizeof(*out));
+    *out = *in;
+    if (in->ai_addr != 0) {
+       out->ai_addr = (struct sockaddr *) mymalloc(in->ai_addrlen);
+       memcpy(out->ai_addr, in->ai_addr, in->ai_addrlen);
+    }
+    if (in->ai_canonname != 0)
+       out->ai_canonname = mystrdup(in->ai_canonname);
+    if (in->ai_next)
+       out->ai_next = copy_addrinfo(in->ai_next);
+    return (out);
+}
+
+/* make_addrinfo - helper to create mock addrinfo input or result */
+
+struct addrinfo *make_addrinfo(const struct addrinfo *hints,
+                                      const char *name, const char *addr,
+                                      int port)
+{
+    struct addrinfo *out;
+
+    out = (struct addrinfo *) mymalloc(sizeof(*out));
+    memset(out, 0, sizeof(*out));
+    out->ai_canonname = MYSTRDUP_OR_NULL(name);
+    switch (hints->ai_family) {
+    case PF_INET6:
+       out->ai_addr = make_sockaddr(AF_INET6, addr, port);
+       out->ai_addrlen = sizeof(struct sockaddr_in6);
+       out->ai_family = hints->ai_family;
+       out->ai_socktype = hints->ai_socktype;
+       out->ai_protocol = hints->ai_protocol;
+       break;
+    case PF_INET:
+       out->ai_addr = make_sockaddr(AF_INET, addr, port);
+       out->ai_addrlen = sizeof(struct sockaddr_in);
+       out->ai_family = hints->ai_family;
+       out->ai_socktype = hints->ai_socktype;
+       out->ai_protocol = hints->ai_protocol;
+       break;
+    default:
+       msg_fatal("make_addrinfo: hints->ai_family: %d", hints->ai_family);
+    }
+    out->ai_next = 0;
+    return (out);
+}
+
+/* freeaddrinfo - free the mock-generated addrinfo structures */
+
+void    freeaddrinfo(struct addrinfo *res)
+{
+    if (res) {
+       if (res->ai_next)
+           freeaddrinfo(res->ai_next);
+       if (res->ai_addr)
+           myfree(res->ai_addr);
+       if (res->ai_canonname)
+           myfree(res->ai_canonname);
+       myfree(res);
+    }
+}
diff --git a/postfix/src/testing/make_addr.h b/postfix/src/testing/make_addr.h
new file mode 100644 (file)
index 0000000..ebe4a6d
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef _MAKE_ADDR_H_INCLUDED_
+#define _MAKE_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/*     make_addr 3h
+/* SUMMARY
+/*     make_addrinfo(), freeaddrinfo(), make_sockaddr() for  hermetic tests
+/* SYNOPSIS
+/*     #include <mock_getaddrinfo.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <wrap_netdb.h>
+
+ /*
+  * External interface.
+  */
+extern struct addrinfo *make_addrinfo(const struct addrinfo *, const char *,
+                                             const char *, int);
+extern struct addrinfo *copy_addrinfo(const struct addrinfo *);
+extern struct sockaddr *make_sockaddr(int, const char *, int);
+extern void free_sockaddr(struct sockaddr *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/make_attr.c b/postfix/src/testing/make_attr.c
new file mode 100644 (file)
index 0000000..1fd944b
--- /dev/null
@@ -0,0 +1,60 @@
+/*++
+/* NAME
+/*     make_attr 3
+/* SUMMARY
+/*     create serialized attribute request or response
+/* SYNOPSIS
+/*     #include <make_attr.h>
+/*
+/*     VSTRING *make_attr(int flags, ...)
+/* DESCRIPTION
+/*     make_attr() creates a serialized request or response attribute
+/*     list. The arguments are like attr_print().
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdarg.h>
+
+ /*
+  * Utility library.
+  */
+#include <attr.h>
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+#include <make_attr.h>
+
+/* make_attr - serialize attribute list */
+
+VSTRING *make_attr(int flags,...)
+{
+    static const char myname[] = "make_attr";
+    VSTRING *res = vstring_alloc(100);
+    VSTREAM *stream;
+    va_list ap;
+    int     err;
+
+    if ((stream = vstream_memopen(res, O_WRONLY)) == 0)
+       ptest_fatal(ptest_ctx_current(), "%s: vstream_memopen: %m", myname);;
+    va_start(ap, flags);
+    err = attr_vprint(stream, flags, ap);
+    va_end(ap);
+    if (vstream_fclose(stream) != 0 || err)
+       ptest_fatal(ptest_ctx_current(), "%s: write attributes: %m", myname);
+    return (res);
+}
diff --git a/postfix/src/testing/make_attr.h b/postfix/src/testing/make_attr.h
new file mode 100644 (file)
index 0000000..97659d9
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef _MAKE_ATTR_H_INCLUDED_
+#define _MAKE_ATTR_H_INCLUDED_
+
+/*++
+/* NAME
+/*     make_attr 3h
+/* SUMMARY
+/*     create serialized attributes
+/* SYNOPSIS
+/*     #include <make_attr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+
+ /*
+  * External interface.
+  */
+extern VSTRING *make_attr(int,...);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/match_addr.c b/postfix/src/testing/match_addr.c
new file mode 100644 (file)
index 0000000..bce07b7
--- /dev/null
@@ -0,0 +1,142 @@
+/*++
+/* NAME
+/*     match_addr 3
+/* SUMMARY
+/*     matchers for network address information
+/* SYNOPSIS
+/*     #include <match_addr.h>
+/*
+/*     int     eq_addrinfo(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     struct addrinfo got,
+/*     struct addrinfo want)
+/*
+/*     int     eq_sockaddr(PTEST_CTX *t,
+/*     const char *what,
+/*     const struct sockaddr *got,
+/*     size_t  gotlen,
+/*     const struct sockaddr *want,
+/*     size_t wantlen)
+/* DESCRIPTION
+/*     The functions described here are safe macros that include
+/*     call-site information (file name, line number) that may be
+/*     used in error messages.
+/*
+/*     eq_addrinfo() compares two struct addrinfo linked lists,
+/*     and eq_sockaddr() compares two struct sockaddr instances.
+/*     Both functions return whether their arguments contain the
+/*     same values.  If the t argument is not null, both functions
+/*     will report values that differ with ptest_error()).
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+#include <match_basic.h>
+#include <match_addr.h>
+#include <addrinfo_to_string.h>
+
+#define STR    vstring_str
+
+/* _eq_addrinfo - match struct addrinfo instances */
+
+int     _eq_addrinfo(const char *file, int line,
+                            PTEST_CTX *t, const char *what,
+                            const struct addrinfo *got,
+                            const struct addrinfo *want)
+{
+    if (got == 0 && want == 0)
+       return (1);
+    if (got == 0 || want == 0) {
+       if (t) {
+           VSTRING *buf = vstring_alloc(100);
+
+           ptest_error(t, "%s:%d %s: got %s, want %s",
+                       file, line, what, got ?
+                       append_addrinfo_to_string(buf, got) : "(null)",
+                       want ?
+                       append_addrinfo_to_string(buf, want) : "(null)");
+           vstring_free(buf);
+       }
+       return (0);
+    }
+    return (_eq_flags(file, line, t, "ai_flags", got->ai_flags, want->ai_flags,
+                     ai_flags_to_string)
+           && _eq_enum(file, line, t, "ai_family", got->ai_family,
+                       want->ai_family, af_to_string)
+           && _eq_enum(file, line, t, "ai_socktype", got->ai_socktype,
+                       want->ai_socktype, socktype_to_string)
+           && _eq_enum(file, line, t, "ai_protocol",
+                       got->ai_protocol, want->ai_protocol,
+                       ipprotocol_to_string)
+           && _eq_size_t(file, line, t, "ai_addrlen",
+                         got->ai_addrlen, want->ai_addrlen)
+           && _eq_sockaddr(file, line, t, "ai_addr", got->ai_addr,
+                           got->ai_addrlen, want->ai_addr, want->ai_addrlen)
+           && _eq_addrinfo(file, line, t, what, got->ai_next,
+                           want->ai_next));
+}
+
+/* _eq_sockaddr - sockaddr matcher */
+
+int     _eq_sockaddr(const char *file, int line,
+                            PTEST_CTX *t, const char *what,
+                            const struct sockaddr *got, size_t gotlen,
+                            const struct sockaddr *want, size_t wantlen)
+{
+    if (got == 0 && want == 0)
+       return (1);
+    if (got == 0 || want == 0) {
+       if (t) {
+           VSTRING *got_buf = vstring_alloc(100);
+           VSTRING *want_buf = vstring_alloc(100);
+
+           ptest_error(t, "%s:%d %s: got %s, want %s",
+                       file, line, what,
+                       got ? sockaddr_to_string(got_buf, got, gotlen)
+                       : "(null)",
+                       want ? sockaddr_to_string(want_buf, want, wantlen)
+                       : "(null)");
+           vstring_free(want_buf);
+           vstring_free(got_buf);
+       }
+       return (0);
+    }
+    if (!_eq_enum(file, line, (PTEST_CTX *) 0, "struct sockaddr address family",
+                 got->sa_family, want->sa_family, af_to_string)
+       || !_eq_size_t(file, line, (PTEST_CTX *) 0, "struct sockaddr length",
+                      gotlen, wantlen)
+       || memcmp(got, want, gotlen) != 0) {
+       VSTRING *got_buf = vstring_alloc(100);
+       VSTRING *want_buf = vstring_alloc(100);
+
+       ptest_error(t, "%s:%d %s: got %s, want %s",
+                   file, line, what,
+                   sockaddr_to_string(got_buf, got, gotlen),
+                   sockaddr_to_string(want_buf, want, wantlen));
+       vstring_free(want_buf);
+       vstring_free(got_buf);
+       return (0);
+    }
+    return (1);
+}
diff --git a/postfix/src/testing/match_addr.h b/postfix/src/testing/match_addr.h
new file mode 100644 (file)
index 0000000..b53978a
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef _MATCH_ADDR_H_INCLUDED_
+#define _MATCH_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/*     match_addr 3h
+/* SUMMARY
+/*     network address matcher
+/* SYNOPSIS
+/*     #include <match_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <wrap_netdb.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * Matchers.
+  */
+#define eq_addrinfo(...)       _eq_addrinfo(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_addrinfo(const char *, int, PTEST_CTX *, const char *,
+                               const struct addrinfo *,
+                               const struct addrinfo *);
+
+#define eq_sockaddr(...)       _eq_sockaddr(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_sockaddr(const char *, int, PTEST_CTX *, const char *,
+                               const struct sockaddr *, size_t,
+                               const struct sockaddr *, size_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/match_addr_test.c b/postfix/src/testing/match_addr_test.c
new file mode 100644 (file)
index 0000000..12f8b5c
--- /dev/null
@@ -0,0 +1,163 @@
+ /*
+  * Test program to exercise make_addr functions including logging. See
+  * comments in ptest_main.h and pmock_expect_test.c for a documented
+  * example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <wrap_netdb.h>
+#include <string.h>
+
+ /*
+  * Test library.
+  */
+#include <make_addr.h>
+#include <match_addr.h>
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+static void test_eq_addrinfo_equal(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    struct addrinfo hints;
+    struct addrinfo *want_addrinfo;
+
+    /*
+     * Set up expectations.
+     */
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = PF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+    want_addrinfo = make_addrinfo(&hints, "localhost", "127.0.0.1", 25);
+
+    /*
+     * Verify that this addrinfo matches itself.
+     */
+    if (!eq_addrinfo(t, "addrinfo", want_addrinfo, want_addrinfo))
+       ptest_error(t, "eq_addrinfo() returned false for identical objects");
+
+    /*
+     * Clean up.
+     */
+    freeaddrinfo(want_addrinfo);
+}
+
+static void test_eq_addrinfo_diff(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    struct addrinfo hints;
+    struct addrinfo *want_addrinfo;
+    struct addrinfo *other_addrinfo;
+
+    /*
+     * Set up expectations.
+     */
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = PF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+    want_addrinfo = make_addrinfo(&hints, "localhost", "127.0.0.1", 25);
+    other_addrinfo = make_addrinfo(&hints, "localhost", "127.0.0.2", 25);
+
+    /*
+     * Verify that the two addrinfos don't match. Do not count this mismatch
+     * as an error. Instead, count the absence of the mismatch as an error.
+     */
+    expect_ptest_error(t, " ai_addr: "
+                      "got {AF_INET, 127.0.0.2, 25}, "
+                      "want {AF_INET, 127.0.0.1, 25}");
+    if (eq_addrinfo(t, "test_eq_addrinfo", other_addrinfo, want_addrinfo))
+       ptest_error(t, "eq_addrinfo() returned true for different objects");
+
+    /*
+     * Clean up.
+     */
+    freeaddrinfo(want_addrinfo);
+    freeaddrinfo(other_addrinfo);
+}
+
+static void test_eq_addrinfo_null(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    struct addrinfo hints;
+    struct addrinfo *want_addrinfo;
+    struct addrinfo *other_addrinfo = 0;
+
+    /*
+     * Set up expectations.
+     */
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = PF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+    want_addrinfo = make_addrinfo(&hints, "localhost", "127.0.0.1", 25);
+
+    /*
+     * Verify that the two addrinfos don't match. Do not count this mismatch
+     * as an error. Instead, count the absence of the mismatch as an error.
+     */
+    expect_ptest_error(t, "test_eq_addrinfo_null: got (null), "
+                      "want {0, PF_INET, SOCK_STREAM, 0, 16, "
+                      "{AF_INET, 127.0.0.1, 25}, localhost}");
+    if (eq_addrinfo(t, "test_eq_addrinfo_null", other_addrinfo, want_addrinfo))
+       ptest_error(t, "eq_addrinfo() returned true for different objects");
+
+    /*
+     * Clean up.
+     */
+    freeaddrinfo(want_addrinfo);
+}
+
+static void test_eq_sockaddr_diff(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    struct sockaddr *want_sockaddr;
+    struct sockaddr *other_sockaddr;
+
+    /*
+     * Set up expectations.
+     */
+    want_sockaddr = make_sockaddr(AF_INET, "127.0.0.1", 25);
+    other_sockaddr = make_sockaddr(AF_INET, "127.0.0.2", 25);
+
+    /*
+     * Verify that the two sockaddrs don't match. Do not count this mismatch
+     * as an error. Instead, count the absence of the mismatch as an error.
+     */
+    expect_ptest_error(t, "test_eq_sockaddr_diff: "
+                      "got {AF_INET, 127.0.0.2, 25}, "
+                      "want {AF_INET, 127.0.0.1, 25}");
+    if (eq_sockaddr(t, "test_eq_sockaddr_diff",
+                   other_sockaddr, sizeof(struct sockaddr_in),
+                   want_sockaddr, sizeof(struct sockaddr_in)))
+       ptest_error(t, "eq_sockaddr() returned true for different objects");
+
+    /*
+     * Clean up.
+     */
+    free_sockaddr(want_sockaddr);
+    free_sockaddr(other_sockaddr);
+}
+
+ /*
+  * Test cases.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+       "Compare equal IPv4 addrinfos", test_eq_addrinfo_equal,
+    },
+    {
+       "Compare different IPv4 addrinfos", test_eq_addrinfo_diff,
+    },
+    {
+       "Compare null and non-null IPv4 addrinfos", test_eq_addrinfo_null,
+    },
+    {
+       "Compare different IPv4 sockaddrs", test_eq_sockaddr_diff,
+    },
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/testing/match_attr.c b/postfix/src/testing/match_attr.c
new file mode 100644 (file)
index 0000000..5829263
--- /dev/null
@@ -0,0 +1,160 @@
+/*++
+/* NAME
+/*     match_attr 3
+/* SUMMARY
+/*     matchers for network address information
+/* SYNOPSIS
+/*     #include <match_attr.h>
+/*
+/*     int     eq_attr(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     VSTRING *got,
+/*     VSTRING *want)
+/* DESCRIPTION
+/*     The functions described here are safe macros that include
+/*     call-site information (file name, line number) that may be
+/*     used in error messages.
+/*
+/*     eq_attr() compares two serialized attribute lists and returns
+/*     whether their arguments contain the same values. If the t
+/*     argument is not null, eq_attr() will report details with
+/*     ptest_error()).
+/* BUGS
+/*     An attribute name can appear only once in an attribute list.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+#include <stdlib.h>
+
+ /*
+  * Utility library.
+  */
+#include <attr.h>
+#include <htable.h>
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+#include <match_attr.h>
+
+/* compar -qsort callback */
+
+static int compar(const void *a, const void *b)
+{
+    return (strcmp((*(HTABLE_INFO **) a)->key, (*(HTABLE_INFO **) b)->key));
+}
+
+/* _eq_attr - match serialized attributes */
+
+int     _eq_attr(const char *file, int line, PTEST_CTX *t,
+                     const char *what, VSTRING *got_buf, VSTRING *want_buf)
+{
+    static const char myname[] = "eq_attr";
+    HTABLE *got_hash;
+    HTABLE *want_hash;
+    int     count;
+    VSTREAM *mp;
+    HTABLE_INFO **ht_list;
+    HTABLE_INFO **ht;
+    char   *ht_value;
+
+    if (VSTRING_LEN(got_buf) == VSTRING_LEN(want_buf)
+       && memcmp(vstring_str(got_buf), vstring_str(want_buf),
+                 VSTRING_LEN(got_buf)) == 0)
+       return (1);
+
+    if (t != 0) {
+
+       /*
+        * Deserialize the actual attributes into a hash. This loses order
+        * information. TODO(wietse) maybe preserve order in two parallel
+        * ARGV objects, and use _eq_argv() under the covers.
+        */
+       got_hash = htable_create(13);
+       if ((mp = vstream_memopen(got_buf, O_RDONLY)) == 0)
+           ptest_fatal(t, "%s: vstream_memopen: %m", myname);
+       count = attr_scan(mp, ATTR_FLAG_NONE,
+                         ATTR_TYPE_HASH, got_hash,
+                         ATTR_TYPE_END);
+       if (vstream_fclose(mp) != 0 || count <= 0)
+           ptest_fatal(t, "%s: vstream_fclose: %m", myname);
+
+       /*
+        * Deserialize the wanted attributes into a hash. This loses order
+        * information.
+        */
+       want_hash = htable_create(13);
+       if ((mp = vstream_memopen(want_buf, O_RDONLY)) == 0)
+           ptest_fatal(t, "%s: vstream_memopen: %m", myname);
+       count = attr_scan(mp, ATTR_FLAG_NONE,
+                         ATTR_TYPE_HASH, want_hash,
+                         ATTR_TYPE_END);
+       if (vstream_fclose(mp) != 0 || count <= 0)
+           ptest_fatal(t, "%s: vstream_fclose: %m", myname);
+
+       /*
+        * Delete the intersection of the deserialized attribute lists.
+        */
+       ht_list = htable_list(got_hash);
+       for (ht = ht_list; *ht; ht++) {
+           if ((ht_value = htable_find(want_hash, ht[0]->key)) != 0
+               && strcmp(ht_value, ht[0]->value) == 0) {
+               htable_delete(want_hash, ht[0]->key, myfree);
+               /* At this point, ht_value is a dangling pointer. */
+               htable_delete(got_hash, ht[0]->key, myfree);
+               /* At this point, ht is a dangling pointer. */
+           }
+       }
+       myfree(ht_list);
+
+       /*
+        * If the attributes differ only in order, then say so. We have no
+        * order information. This should never happen with real requests and
+        * responses.
+        */
+       if (got_hash->used == 0 && want_hash->used == 0) {
+           ptest_error(t, "%s: attribute order differs", what);
+       }
+
+       /*
+        * List differences in name or value.
+        */
+       else {
+           ptest_error(t, "%s: attributes differ, +got/-want follows", what);
+
+           /*
+            * Enumerate the unique attributes.
+            */
+           ht_list = htable_list(got_hash);
+           qsort(ht_list, got_hash->used, sizeof(*ht_list), compar);
+           for (ht = ht_list; *ht; ht++)
+               ptest_error(t, "+%s = %s", ht[0]->key, (char *) ht[0]->value);
+           myfree(ht_list);
+
+           ht_list = htable_list(want_hash);
+           qsort(ht_list, want_hash->used, sizeof(*ht_list), compar);
+           for (ht = ht_list; *ht; ht++)
+               ptest_error(t, "-%s = %s", ht[0]->key, (char *) ht[0]->value);
+           myfree(ht_list);
+       }
+       htable_free(got_hash, myfree);
+       htable_free(want_hash, myfree);
+    }
+    return (0);
+}
diff --git a/postfix/src/testing/match_attr.h b/postfix/src/testing/match_attr.h
new file mode 100644 (file)
index 0000000..8efbb13
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef _MATCH_ATTR_H_INCLUDED_
+#define _MATCH_ATTR_H_INCLUDED_
+
+/*++
+/* NAME
+/*     match_attr 3h
+/* SUMMARY
+/*     attribute matching
+/* SYNOPSIS
+/*     #include <match_attr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * External interface.
+  */
+extern int _eq_attr(const char *, int, PTEST_CTX *, const char *,
+                           VSTRING *, VSTRING *);
+
+#define eq_attr(...)   _eq_attr(__FILE__, __LINE__, __VA_ARGS__)
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/match_attr_test.c b/postfix/src/testing/match_attr_test.c
new file mode 100644 (file)
index 0000000..c9fb540
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+  * Test program to exercise match_attr functions including logging. See
+  * documentation in PTEST_README for the structure of this file.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+
+ /*
+  * Utility library.
+  */
+#include <attr.h>
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <make_attr.h>
+#include <match_attr.h>
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+static void test_eq_attr_equal(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *want_attr;
+
+    /*
+     * Serialize some attributes.
+     */
+    want_attr = make_attr(ATTR_FLAG_NONE,
+                         SEND_ATTR_STR("this-key", "this-value"),
+                         SEND_ATTR_STR("that-key", "that-value"),
+                         ATTR_TYPE_END);
+
+    /*
+     * VERIFY that this serialized attribute list matches ifself.
+     */
+    if (!eq_attr(t, "want_attr", want_attr, want_attr))
+       ptest_fatal(t, "eq_attr() returned false for identical objects");
+
+    /*
+     * Clean up.
+     */
+    vstring_free(want_attr);
+}
+
+static void test_eq_attr_swapped(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *want_attr;
+    VSTRING *swapped_attr;
+
+    /*
+     * Serialize some attributes.
+     */
+    want_attr = make_attr(ATTR_FLAG_NONE,
+                         SEND_ATTR_STR("this-key", "this-value"),
+                         SEND_ATTR_STR("that-key", "that-value"),
+                         ATTR_TYPE_END);
+    swapped_attr = make_attr(ATTR_FLAG_NONE,
+                            SEND_ATTR_STR("that-key", "that-value"),
+                            SEND_ATTR_STR("this-key", "this-value"),
+                            ATTR_TYPE_END);
+
+    /*
+     * VERIFY that eq_attr() report attributes that differ only in order.
+     */
+    expect_ptest_error(t, "attribute order differs");
+    if (eq_attr(t, "want_attr", swapped_attr, want_attr))
+       ptest_fatal(t, "eq_attr() returned true for swapped objects");
+
+    /*
+     * Clean up.
+     */
+    vstring_free(want_attr);
+}
+
+static void test_eq_attr_diff(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *want_attr;
+    VSTRING *swapped_attr;
+
+    /*
+     * Serialize some attributes.
+     */
+    want_attr = make_attr(ATTR_FLAG_NONE,
+                         SEND_ATTR_STR("this-key", "this-value"),
+                         SEND_ATTR_STR("that-key", "that-value"),
+                         SEND_ATTR_STR("same-key", "same-value"),
+                         ATTR_TYPE_END);
+    swapped_attr = make_attr(ATTR_FLAG_NONE,
+                            SEND_ATTR_STR("not-this-key", "this-value"),
+                            SEND_ATTR_STR("that-key", "not-that-value"),
+                            SEND_ATTR_STR("same-key", "same-value"),
+                            ATTR_TYPE_END);
+
+    /*
+     * Verify that match_attr reports the expected differences.
+     */
+    expect_ptest_error(t, "attributes differ");
+    expect_ptest_error(t, "+not-this-key = this-value");
+    expect_ptest_error(t, "+that-key = not-that-value");
+    expect_ptest_error(t, "-that-key = that-value");
+    expect_ptest_error(t, "-this-key = this-value");
+    if (eq_attr(t, "want_attr", swapped_attr, want_attr))
+       ptest_fatal(t, "eq_attr() returned true for different objects");
+
+    /*
+     * Clean up.
+     */
+    vstring_free(want_attr);
+    vstring_free(swapped_attr);
+}
+
+ /*
+  * Test cases.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+       "Compare identical attribute lists", test_eq_attr_equal,
+    },
+    {
+       "Compare swapped attribute lists", test_eq_attr_swapped,
+    },
+    {
+       "Compare different attribute lists", test_eq_attr_diff,
+    },
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/testing/match_basic.c b/postfix/src/testing/match_basic.c
new file mode 100644 (file)
index 0000000..ee401df
--- /dev/null
@@ -0,0 +1,238 @@
+/*++
+/* NAME
+/*     match_basic 3
+/* SUMMARY
+/*     basic matchers
+/* SYNOPSIS
+/*     #include <match_basic.h>
+/*
+/*     int     eq_int(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     int     got,
+/*     int     want)
+/*
+/*     int     eq_size_t(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     size_t  got,
+/*     size_t  want)
+/*
+/*     int     eq_ssize_t(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     ssize_t got,
+/*     ssize_t want)
+/*
+/*     int     eq_flags(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     int     got,
+/*     int     want,
+/*     const char *(*flags_to_str) (VSTRING *, int))
+/*
+/*     int     eq_enum(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     int     got,
+/*     int     want,
+/*     const char *(*enum_to_str) (int))
+/*
+/*     int     eq_str(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     const char *got,
+/*     const char *want)
+/*
+/*     int     eq_argv(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     const ARGV *got,
+/*     const ARGV *want)
+/* DESCRIPTION
+/*     The functions described here are actually safe macros that
+/*     include call-site information (file name, line number) in
+/*     error messages.
+/*
+/*     eq_int() compares two integers, and if t is not null, reports
+/*     values that differ with ptest_error());
+/*
+/*     eq_flags() compares two integer bitmasks, and if t is not
+/*     null, reports values that differ with ptest_error());
+/*
+/*     eq_enum() compares two integer enum values, and if t is not
+/*     null, reports values that differ with ptest_error());
+/*
+/*     eq_str() compares two null-terminated strings, and if t is
+/*     not null, reports values that differ with ptest_error());
+/*
+/*     eq_argv() compares the lengths and values of two null-terminated
+/*     string arrays, and if t is not null, reports differences with
+/*     ptest_error());
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+#include <match_basic.h>
+
+#define STR_OR_NULL(s)         ((s) ? (s) : "(null)")
+#define ARGV_OR_NULL(s)                ((s) ? "(ARGV)" : "(null)")
+
+/* _eq_int - match integers */
+
+int     _eq_int(const char *file, int line, PTEST_CTX *t,
+                       const char *what, int got, int want)
+{
+    if (got != want) {
+       if (t)
+           ptest_error(t, "%s:%d: %s: got %d, want %d", file, line, what, got, want);
+       return (0);
+    }
+    return (1);
+}
+
+/* _eq_size_t - match size_t */
+
+int     _eq_size_t(const char *file, int line, PTEST_CTX *t,
+                          const char *what, size_t got, size_t want)
+{
+    if (got != want) {
+       if (t)
+           ptest_error(t, "%s:%d: %s: got %lu, want %lu",
+                       file, line, what, (long) got, (long) want);
+       return (0);
+    }
+    return (1);
+}
+
+/* _eq_ssize_t - match ssize_t */
+
+int     _eq_ssize_t(const char *file, int line, PTEST_CTX *t,
+                           const char *what, ssize_t got, ssize_t want)
+{
+    if (got != want) {
+       if (t)
+           ptest_error(t, "%s:%d: %s: got %ld, want %ld",
+                       file, line, what, (long) got, (long) want);
+       return (0);
+    }
+    return (1);
+}
+
+/* _eq_flags - match flags */
+
+int     _eq_flags(const char *file, int line, PTEST_CTX *t,
+                         const char *what, int got, int want,
+                         const char *(flags_to_string) (VSTRING *, int))
+{
+    if (got != want) {
+       if (t) {
+           VSTRING *got_buf = vstring_alloc(100);
+           VSTRING *want_buf = vstring_alloc(100);
+
+           ptest_error(t, "%s:%d: %s: got '%s', want '%s'", file, line, what,
+                       flags_to_string(got_buf, got),
+                       flags_to_string(want_buf, want));
+           vstring_free(got_buf);
+           vstring_free(want_buf);
+       }
+       return (0);
+    }
+    return (1);
+}
+
+/* _eq_enum - match enum */
+
+int     _eq_enum(const char *file, int line, PTEST_CTX *t,
+                        const char *what, int got, int want,
+                        const char *(enum_to_string) (int))
+{
+    if (got != want) {
+       if (t)
+           ptest_error(t, "%s:%d: %s: got '%s', want '%s'", file, line, what,
+                       enum_to_string(got), enum_to_string(want));
+       return (0);
+    }
+    return (1);
+}
+
+/* _eq_str - match null-terminated strings */
+
+int     _eq_str(const char *file, int line, PTEST_CTX *t,
+                       const char *what, const char *got, const char *want)
+{
+    if (got == 0 && want == 0) {
+        return (1);
+    } else if (got == 0 || want == 0) {
+        if (t)
+            ptest_error(t, "%s:%d: %s: got '%s', want '%s'",
+                        file, line, what, STR_OR_NULL(got),
+                        STR_OR_NULL(want));
+        return (0);
+    }
+    if (strcmp(got, want) != 0) {
+       if (t)
+           ptest_error(t, "%s:%d: %s: got '%s', want '%s'",
+                       file, line, what, got, want);
+       return (0);
+    }
+    return (1);
+}
+
+/* _eq_argv - match ARGV instances */
+
+int     _eq_argv(const char *file, int line, PTEST_CTX *t,
+                        const char *what, const ARGV *got, const ARGV *want)
+{
+    char  **gpp, **wpp;
+
+    if (got == 0 && want == 0) {
+       return (1);
+    } else if (got == 0 || want == 0) {
+       if (t)
+           ptest_error(t, "%s:%d: %s: got '%s', want '%s'",
+                       file, line, what, ARGV_OR_NULL(got),
+                       ARGV_OR_NULL(want));
+       return (0);
+    }
+    (void) _eq_int(file, line, t, what, got->argc, want->argc);
+
+    for (gpp = got->argv, wpp = want->argv; /* see below */ ; gpp++, wpp++) {
+       if (*gpp == 0 && *wpp == 0) {
+           return (1);
+       } else if (*gpp == 0 || *wpp == 0) {
+           if (t)
+               ptest_error(t, "%s:%d: %s: got '%s', want '%s'",
+                           file, line, what, STR_OR_NULL(*gpp),
+                           STR_OR_NULL(*wpp));
+           return (0);
+       } else if (!_eq_str(file, line, t, what, *gpp, *wpp)) {
+           return (0);
+       }
+    }
+}
diff --git a/postfix/src/testing/match_basic.h b/postfix/src/testing/match_basic.h
new file mode 100644 (file)
index 0000000..91d2cd2
--- /dev/null
@@ -0,0 +1,78 @@
+#ifndef _MATCH_BASIC_H_INCLUDED_
+#define _MATCH_BASIC_H_INCLUDED_
+
+/*++
+/* NAME
+/*     match_basic 3h
+/* SUMMARY
+/*     basic matchers
+/* SYNOPSIS
+/*     #include <matchers.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <setjmp.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * Matchers.
+  */
+#define eq_int(...)    _eq_int(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_int(const char *, int, PTEST_CTX *,
+                          const char *, int, int);
+
+#define eq_size_t(...) _eq_int(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_size_t(const char *, int, PTEST_CTX *,
+                             const char *, size_t, size_t);
+
+#define eq_ssize_t(...)        _eq_int(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_ssize_t(const char *, int, PTEST_CTX *,
+                              const char *, ssize_t, ssize_t);
+
+#define eq_flags(...)  _eq_flags(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_flags(const char *, int, PTEST_CTX *,
+                            const char *, int, int,
+                            const char *(*flags_to_str) (VSTRING *, int));
+
+#define eq_enum(...)   _eq_enum(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_enum(const char *, int, PTEST_CTX *,
+                 const char *, int, int, const char *(*enum_to_str) (int));
+
+#define eq_str(...)    _eq_str(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_str(const char *, int, PTEST_CTX *,
+                          const char *, const char *, const char *);
+
+#define eq_argv(...)   _eq_argv(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_argv(const char *, int, PTEST_CTX *,
+                           const char *, const ARGV *, const ARGV *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/match_basic_test.c b/postfix/src/testing/match_basic_test.c
new file mode 100644 (file)
index 0000000..042b880
--- /dev/null
@@ -0,0 +1,160 @@
+ /*
+  * Test program to exercise make_addr functions including logging. See
+  * comments in ptest_main.h and pmock_expect_test.c for a documented
+  * example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+
+ /*
+  * Utility library.
+  */
+#include <name_code.h>
+#include <name_mask.h>
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <match_basic.h>
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;               /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+static void test_eq_int(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    expect_ptest_error(t, "got 3, want 5");
+    if (eq_int(t, "int", 3, 5)
+       || eq_int((PTEST_CTX *) 0, "int", 3, 5))
+       ptest_error(t, "unexpected int match: 3 == 5");
+}
+
+static void test_eq_size_t(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    expect_ptest_error(t, "got 3, want 5");
+    if (eq_size_t(t, "size_t", 3, 5)
+       || eq_size_t((PTEST_CTX *) 0, "size_t", 3, 5))
+       ptest_error(t, "unexpected size_t match: 3 == 5");
+}
+
+static void test_eq_ssize_t(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    expect_ptest_error(t, "got 3, want 5");
+    if (eq_ssize_t(t, "ssize_t", 3, 5)
+       || eq_ssize_t((PTEST_CTX *) 0, "ssize_t", 3, 5))
+       ptest_error(t, "unexpected ssize_t match: 3 == 5");
+}
+
+#define FLAG_ONE       (1<<0)
+#define FLAG_TWO       (1<<1)
+
+static const NAME_MASK test_flags[] = {
+    "one", FLAG_ONE,
+    "two", FLAG_TWO,
+    0,
+};
+
+static const char *flags_to_string(VSTRING *buf, int flags)
+{
+    return (str_name_mask_opt(buf, "flags_to_string", test_flags, flags,
+                      NAME_MASK_NUMBER | NAME_MASK_PIPE | NAME_MASK_NULL));
+}
+
+static void test_eq_flags(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+int got = FLAG_ONE;
+int want = FLAG_ONE | FLAG_TWO;
+
+    expect_ptest_error(t, "got 'one', want 'one|two'");
+    if (eq_flags(t, "flags", got, want, flags_to_string)
+       || eq_flags((PTEST_CTX *) 0, "flags", got, want, flags_to_string))
+       ptest_error(t, "unexpected flags match: 'one' == 'one|two'");
+}
+
+#define CODE_ONE        (1)
+#define CODE_TWO        (2)
+
+static const NAME_CODE test_codes[] = {
+    "one", CODE_ONE,
+    "two", CODE_TWO,
+    0,
+};
+
+static const char *enum_to_string(int code)
+{
+    const char *result;
+
+    if ((result = str_name_code(test_codes, code)) == 0)
+       result = "unknown";
+    return (result);
+}
+
+static void test_eq_enum(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    int     got = CODE_ONE;
+    int     want = CODE_TWO;
+
+    expect_ptest_error(t, "got 'one', want 'two'");
+
+    if (eq_enum(t, "flags", got, want, enum_to_string)
+       || eq_enum((PTEST_CTX *) 0, "flags", got, want, enum_to_string))
+       ptest_error(t, "unexpected flags match: 'one' == 'two'");
+}
+
+static void test_eq_str(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    const char *got = "bar";
+    const char *want = "foo";
+
+    (void) eq_str(t, "str", (char *) 0, (char *) 0);
+
+    expect_ptest_error(t, "got '(null)', want 'foo'");
+    if (eq_str(t, "str", (char *) 0, want)
+       || eq_str((PTEST_CTX *) 0, "str", (char *) 0, want))
+       ptest_error(t, "null str matches non-null str");
+
+    expect_ptest_error(t, "got 'bar', want 'foo'");
+    if (eq_str(t, "str", got, want)
+       || eq_str((PTEST_CTX *) 0, "str", got, want))
+       ptest_error(t, "unexpected str match: 'bar' == 'foo'");
+}
+
+static void test_eq_argv(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    ARGV    got = {.argc = 1,.argv = (char *[]) {"one", 0}};
+    ARGV    want = {.argc = 2,.argv = (char *[]) {"one", "two", 0}};
+
+    (void) eq_argv(t, "argv", (ARGV *) 0, (ARGV *) 0);
+
+    expect_ptest_error(t, "got '(null)', want '(ARGV)'");
+    if (eq_argv(t, "argv", (ARGV *) 0, &want)
+       || eq_argv((PTEST_CTX *) 0, "argv", (ARGV *) 0, &want))
+       ptest_error(t, "null ARGV matches non-null ARGV");
+
+    expect_ptest_error(t, "got 1, want 2");
+    expect_ptest_error(t, "got '(null)', want 'two'");
+    if (eq_argv(t, "argv", &got, &want)
+       || eq_argv((PTEST_CTX *) 0, "argv", &got, &want))
+       ptest_error(t, "unexpected argv match: 'got' == 'want'");
+}
+
+ /*
+  * Test cases.
+  */
+const PTEST_CASE ptestcases[] = {
+    {"Compare int", test_eq_int,},
+    {"Compare size_t", test_eq_size_t,},
+    {"Compare ssize_t", test_eq_ssize_t,},
+    {"Compare flags", test_eq_flags,},
+    {"Compare enum", test_eq_enum,},
+    {"Compare str", test_eq_str,},
+    {"Compare argv", test_eq_argv,},
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/testing/mock_dns.h b/postfix/src/testing/mock_dns.h
new file mode 100644 (file)
index 0000000..5df2e86
--- /dev/null
@@ -0,0 +1,67 @@
+#ifndef _MOCK_DNS_H_INCLUDED_
+#define _MOCK_DNS_H_INCLUDED_
+
+/*++
+/* NAME
+/*     mock_dns 3h
+/* SUMMARY
+/*     emulate DNS support for hermetic tests
+/* SYNOPSIS
+/*     #include <mock_dns.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * DNS library.
+  */
+#include <dns.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * Manage expectations and responses. Capture the source file name and line
+  * number for better diagnostics.
+  */
+#define expect_dns_lookup_x(...) \
+       _expect_dns_lookup_x(__FILE__, __LINE__, __VA_ARGS__)
+
+extern void _expect_dns_lookup_x(const char *, int, int, int, int,
+                                const char *, unsigned, unsigned, DNS_RR *,
+                                      VSTRING *, VSTRING *, int, unsigned);
+
+ /*
+  * Matcher predicates. Capture the source file name and line number for
+  * better diagnostics.
+  */
+#define eq_dns_rr(...) _eq_dns_rr(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_dns_rr(const char *, int, PTEST_CTX *, const char *, DNS_RR *,
+                             DNS_RR *);
+
+ /*
+  * Helper to create test data.
+  */
+extern DNS_RR *make_dns_rr(const char *, const char *, unsigned, unsigned,
+                                  unsigned, unsigned, unsigned, unsigned,
+                                  unsigned, void *, size_t);
+
+ /*
+  * Other helper.
+  */
+extern const char *dns_status_to_string(int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/mock_dns_lookup.c b/postfix/src/testing/mock_dns_lookup.c
new file mode 100644 (file)
index 0000000..a7500b2
--- /dev/null
@@ -0,0 +1,501 @@
+/*++
+/* NAME
+/*     mock_dns_lookup 3
+/* SUMMARY
+/*     dns_lookup mock for hermetic tests
+/* SYNOPSIS
+/*     #include <mock_dns_lookup.h>
+/*
+/*     int     dns_lookup_x(
+/*     const char *name,
+/*     unsigned type,
+/*     unsigned rflags,
+/*     DNS_RR  **list,
+/*     VSTRING *fqdn,
+/*     VSTRING *why,
+/*     int     *rcode,
+/*     unsigned lflags)
+/*
+/*     int      dns_get_h_errno(void)
+/*
+/*     void     dns_set_h_errno(int herrval)
+/* EXPECTATION SETUP
+/*     void    expect_dns_lookup_x(
+/*     int     calls_expected,
+/*     int     herrval,
+/*     int     retval,
+/*     const char *name,
+/*     unsigned type,
+/*     unsigned flags,
+/*     DNS_RR  *rrlist,
+/*     VSTRING *fqdn,
+/*     VSTRING *why,
+/*     int     rcode,
+/*     unsigned lflags)
+/*
+/*     DNS_RR  *make_dns_rr(
+/*     const char *qname,
+/*     const char *rname,
+/*     unsigned type,
+/*     unsigned class,
+/*     unsigned ttl,
+/*     unsigned dnssec_valid,
+/*     unsigned pref,
+/*     unsigned weight,
+/*     unsigned port,
+/*     void    *data,
+/*     size_t  data_len)
+/* MATCHERS
+/*     int     eq_dns_rr(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     DNS_RR  *got,
+/*     DNS_RR  *want)
+/* OTHER HELPERS
+/*     const char *dns_status_to_string(int status)
+/* DESCRIPTION
+/*     This module implements a mock dns_lookup_x() lookup function
+/*     that produces prepared outputs in response to expected
+/*     inputs. This supports hermetic tests, i.e. tests that do
+/*     not depend on host configuration or on network access.
+/*
+/*     expect_dns_lookup_x() makes deep copies of its input
+/*     arguments, and of the arguments that specify prepared
+/*     outputs. The herrval argument specifies a prepared
+/*     dns_get_h_errno() result value, and the retval argument
+/*     specifies a prepared dns_lookup_x() result value. The
+/*     calls_expected argument specifies the expected number of
+/*     dns_lookup_x() calls (zero means one or more calls, not:
+/*     zero calls).
+/*
+/*     dns_get_h_errno() returns an error value that is configured
+/*     with expect_dns_lookup_x(), and that is assigned when
+/*     dns_lookup_x() or dns_set_h_errno() are called.
+/*
+/*     dns_set_h_errno() assigns the dns_get_h_errno() result
+/*     value.
+/*
+/*     make_dns_rr() is a wrapper around dns_rr_create() that also
+/*     controls the dnssec_valid flag.
+/*
+/*     eq_dns_rr() is a predicate that compares its arguments
+/*     (linked lists) for equality. If t is not null, the what
+/*     argument is used in logging when the inputs differ.
+/* DIAGNOSTICS
+/*     If a mock is called unexpectedly (the call arguments do not
+/*     match an expectation, or more calls are made than expected),
+/*     a warning is logged, and the test will be flagged as failed.
+/*     For now the mock returns an error result to the caller.
+/*     TODO: consider aborting the test.
+/* SEE ALSO
+/*     dns_lookup(3), domain name service lookup
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+
+ /*
+  * Utility library.
+  */
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <hex_code.h>
+#include <name_code.h>
+
+ /*
+  * DNS library.
+  */
+#include <dns.h>
+
+ /*
+  * Test library.
+  */
+#include <mock_dns.h>
+#include <pmock_expect.h>
+#include <ptest.h>
+
+ /*
+  * Helpers.
+  */
+#define MYSTRDUP_OR_NULL(x)            ((x) ? mystrdup(x) : 0)
+#define VSTRDUP_OR_NULL(x) \
+    ((x) ? vstring_strcpy(vstring_alloc(VSTRING_LEN(x)), vstring_str(x)) : 0)
+
+#define STR(x)                         vstring_str(x)
+
+#define STR_OR_NULL(s)                 ((s) ? (s) : "(null)")
+#define VSTR_STR_OR_NULL(s)            ((s) ? STR(s) : "(null)")
+
+ /*
+  * Local state for the mock functions dns_get_herrno() and dns_set_herrno(),
+  * also updated when the mock function dns_lookup_x() is called.
+  * 
+  * XXX This could leak information when tests are run successively in the same
+  * process. Should the test infrastructure fork() the process before each
+  * test? Leakage will not happen when each test calls a function that resets
+  * global_herrval.
+  */
+static int global_herrval = ~0;
+
+/* dns_status_to_string - convert status code to string */
+
+const char *dns_status_to_string(int status)
+{
+    static const NAME_CODE status_string[] = {
+       "DNS_OK", DNS_OK,
+       "DNS_POLICY", DNS_POLICY,
+       "DNS_RETRY", DNS_RETRY,
+       "DNS_INVAL", DNS_INVAL,
+       "DNS_FAIL", DNS_FAIL,
+       "DNS_NULLMX", DNS_NULLMX,
+       "DNS_NOTFOUND", DNS_NOTFOUND,
+       0,
+    };
+
+    return (str_name_code(status_string, status));
+}
+
+/* copy_dns_rrlist - deep copy */
+
+static DNS_RR *copy_dns_rrlist(DNS_RR *list)
+{
+    DNS_RR *rr;
+
+    if (list == 0)
+       return (0);
+    rr = dns_rr_copy(list);
+    rr->next = copy_dns_rrlist(list->next);
+    return (rr);
+}
+
+/* make_dns_rr - dns_rr_create() wrapper */
+
+DNS_RR *make_dns_rr(const char *qname, const char *rname, unsigned type,
+                           unsigned class, unsigned ttl,
+                           unsigned dnssec_valid, unsigned pref,
+                           unsigned weight, unsigned port,
+                           void *data, size_t data_len)
+{
+    DNS_RR *rr;
+
+    rr = dns_rr_create(qname, rname, type, class, ttl, pref, weight, port,
+                      data, data_len);
+    rr->dnssec_valid = dnssec_valid;
+    return (rr);
+}
+
+/* _eq_dns_rr - equality predicate */
+
+int     _eq_dns_rr(const char *file, int line, PTEST_CTX *t,
+                          const char *what,
+                          DNS_RR *got, DNS_RR *want)
+{
+    if (got == 0 && want == 0) {
+       return (1);
+    }
+    if (got == 0 || want == 0) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got %s, want %s",
+                       file, line, what, got ? "(DNS_RR *)" : "(null)",
+                       want ? "(DNS_RR *)" : "(null)");
+       return (0);
+    }
+    if (strcmp(got->qname, want->qname) != 0) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got qname '%s', want '%s'",
+                       file, line, what, got->qname, want->qname);
+       return (0);
+    }
+    if (strcmp(got->rname, want->rname) != 0) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got rname '%s', want '%s'",
+                       file, line, what, got->rname, want->rname);
+       return (0);
+    }
+    if (got->type != want->type) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got type %d, want %d",
+                       file, line, what, got->type, want->type);
+       return (0);
+    }
+    if (got->class != want->class) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got class %d, want %d",
+                       file, line, what, got->class, want->class);
+       return (0);
+    }
+    if (got->ttl != want->ttl) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got ttl %d, want %d",
+                       file, line, what, got->ttl, want->ttl);
+       return (0);
+    }
+    if (got->dnssec_valid != want->dnssec_valid) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got dnssec_valid %d, want %d",
+                  file, line, what, got->dnssec_valid, want->dnssec_valid);
+       return (0);
+    }
+    if (got->pref != want->pref) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got pref %d, want %d",
+                       file, line, what, got->pref, want->pref);
+       return (0);
+    }
+    if (got->weight != want->weight) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got weight %d, want %d",
+                       file, line, what, got->weight, want->weight);
+       return (0);
+    }
+    if (got->port != want->port) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got port %d, want %d",
+                       file, line, what, got->port, want->port);
+       return (0);
+    }
+    if (got->data_len != want->data_len) {
+       if (t)
+           ptest_error(t, "%s:%d %s: got data_len %d, want %d",
+              file, line, what, (int) got->data_len, (int) want->data_len);
+       return (0);
+    }
+    if (memcmp(got->data, want->data, got->data_len) != 0) {
+       VSTRING *got_data_hex = vstring_alloc(100);
+       VSTRING *want_data_hex = vstring_alloc(100);
+
+       if (t)
+           ptest_error(t, "%s:%d %s: got data %s, want %s",
+              file, line, what, STR(hex_encode_opt(got_data_hex, got->data,
+                                got->data_len, HEX_ENCODE_FLAG_USE_COLON)),
+                       STR(hex_encode_opt(want_data_hex, want->data,
+                              want->data_len, HEX_ENCODE_FLAG_USE_COLON)));
+       vstring_free(got_data_hex);
+       vstring_free(want_data_hex);
+       return (0);
+    }
+    return (_eq_dns_rr(file, line, t, what, got->next, want->next));
+}
+
+ /*
+  * Manage dns_lookup_x() expectations and responses. We use this structure
+  * for deep copies of expect_dns_lookup_x() expected inputs and prepared
+  * responses, and for shallow copies of dns_lookup_x() inputs.
+  */
+struct dns_lookup_x_expectation {
+    MOCK_EXPECT mock_expect;           /* generic fields */
+    int     herrval;                   /* h_errno value */
+    int     retval;                    /* result value */
+    char   *name;                      /* inputs */
+    unsigned type;
+    unsigned flags;
+    unsigned lflags;
+    DNS_RR *rrlist;                    /* outputs */
+    VSTRING *fqdn;
+    VSTRING *why;
+    int     rcode;
+};
+
+ /*
+  * Pointers to dns_lookup_x() outputs.
+  */
+struct dns_lookup_x_targets {
+    int    *herrval;
+    int    *retval;
+    DNS_RR **rrlist;
+    VSTRING *fqdn;
+    VSTRING *why;
+    int    *rcode;
+};
+
+/* match_dns_lookup_x - match inputs against expectation */
+
+static int match_dns_lookup_x(const MOCK_EXPECT *expect,
+                                     const MOCK_EXPECT *inputs)
+{
+    struct dns_lookup_x_expectation *pe =
+    (struct dns_lookup_x_expectation *) expect;
+    struct dns_lookup_x_expectation *pi =
+    (struct dns_lookup_x_expectation *) inputs;
+
+    return (strcmp(STR_OR_NULL(pe->name),
+                  STR_OR_NULL(pi->name)) == 0
+           && pe->type == pi->type
+           && pe->flags == pi->flags
+           && pe->lflags == pi->lflags);
+}
+
+/* assign_dns_lookup_x - assign expected output */
+
+static void assign_dns_lookup_x(const MOCK_EXPECT *expect,
+                                       void *targets)
+{
+    struct dns_lookup_x_expectation *pe =
+    (struct dns_lookup_x_expectation *) expect;
+    struct dns_lookup_x_targets *pt =
+    (struct dns_lookup_x_targets *) targets;
+
+    if (pe->retval == DNS_OK) {
+       if (pt->rrlist)
+           *(pt->rrlist) = copy_dns_rrlist(pe->rrlist);
+       if (pt->fqdn && pe->fqdn)
+           vstring_strcpy(pt->fqdn, STR(pe->fqdn));
+    } else {
+       if (pt->why && pe->why)
+           vstring_strcpy(pt->why, STR(pe->why));
+    }
+    if (pt->rcode)
+       *pt->rcode = pe->rcode;
+    *pt->retval = pe->retval;
+    *pt->herrval = pe->herrval;
+}
+
+/* print_dns_lookup_x - print expected inputs */
+
+static char *print_dns_lookup_x(const MOCK_EXPECT *expect,
+                                       VSTRING *buf)
+{
+    struct dns_lookup_x_expectation *pe =
+    (struct dns_lookup_x_expectation *) expect;
+
+    vstring_sprintf(buf, "\"%s\", %s, %d, (ptr), (ptr), (ptr), (ptr), %d",
+                   STR_OR_NULL(pe->name), dns_strtype(pe->type),
+                   pe->flags, pe->lflags);
+    return (STR(buf));
+}
+
+/* free_dns_lookup_x - destructor */
+
+static void free_dns_lookup_x(MOCK_EXPECT *expect)
+{
+    struct dns_lookup_x_expectation *pe =
+    (struct dns_lookup_x_expectation *) expect;
+
+    myfree(pe->name);
+    if (pe->retval == DNS_OK) {
+       if (pe->rrlist)
+           dns_rr_free(pe->rrlist);
+       if (pe->fqdn)
+           vstring_free(pe->fqdn);
+    } else {
+       if (pe->why)
+           vstring_free(pe->why);
+    }
+    pmock_expect_free(expect);
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG dns_lookup_x_sig = {
+    "dns_lookup_x",
+    match_dns_lookup_x,
+    assign_dns_lookup_x,
+    print_dns_lookup_x,
+    free_dns_lookup_x,
+};
+
+/* _expect_dns_lookup_x - set up expectation */
+
+void    _expect_dns_lookup_x(const char *file, int line, int calls_expected,
+                                    int herrval, int retval,
+                           const char *name, unsigned type, unsigned flags,
+                               DNS_RR *rrlist, VSTRING *fqdn, VSTRING *why,
+                                    int rcode, unsigned lflags)
+{
+    struct dns_lookup_x_expectation *pe;
+
+    pe = (struct dns_lookup_x_expectation *)
+       pmock_expect_create(&dns_lookup_x_sig,
+                           file, line, calls_expected, sizeof(*pe));
+
+    /*
+     * Inputs.
+     */
+    pe->name = MYSTRDUP_OR_NULL(name);
+    pe->type = type;
+    pe->flags = flags;
+    pe->lflags = lflags;
+
+    /*
+     * Outputs.
+     */
+    pe->herrval = herrval;
+    pe->retval = retval;
+    if (pe->retval == DNS_OK) {
+       pe->rrlist = copy_dns_rrlist(rrlist);
+       pe->fqdn = VSTRDUP_OR_NULL(fqdn);
+    } else {
+       pe->why = VSTRDUP_OR_NULL(why);
+    }
+    pe->rcode = rcode;
+}
+
+/* dns_lookup_x - answer the call with prepared responses */
+
+int     dns_lookup_x(const char *name, unsigned type, unsigned flags,
+                            DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why,
+                            int *rcode, unsigned lflags)
+{
+    struct dns_lookup_x_expectation inputs;
+    struct dns_lookup_x_targets targets;
+    int     retval = DNS_FAIL;
+
+    /*
+     * Bundle the arguments to simplify handling.
+     */
+    inputs.name = (char *) name;
+    inputs.type = type;
+    inputs.flags = flags;
+    inputs.lflags = lflags;
+
+    targets.herrval = &global_herrval;
+    targets.retval = &retval;
+    targets.rrlist = rrlist;
+    targets.fqdn = fqdn;
+    targets.why = why;
+    targets.rcode = rcode;
+
+    /*
+     * Aargh.
+     */
+    if (rrlist)
+       *rrlist = 0;
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&dns_lookup_x_sig,
+                             &inputs.mock_expect, (void *) &targets);
+    return (retval);
+}
+
+/* dns_get_h_errno - return prepared answer */
+
+int     dns_get_h_errno(void)
+{
+    return (global_herrval);
+}
+
+/* dns_set_h_errno - return prepared answer */
+
+void    dns_set_h_errno(int herrval)
+{
+    global_herrval = herrval;
+}
diff --git a/postfix/src/testing/mock_dns_lookup_test.c b/postfix/src/testing/mock_dns_lookup_test.c
new file mode 100644 (file)
index 0000000..661777b
--- /dev/null
@@ -0,0 +1,206 @@
+ /*
+  * Test program to exercise mocks including logging. See comments in
+  * ptest_main.h and pmock_expect_test.c for a documented example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <arpa/inet.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <mock_dns.h>
+#include <pmock_expect.h>
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+#define NO_RES_FLAGS   0
+
+static void test_dns_lookup_x_success(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *got_fqdn = vstring_alloc(100), *want_fqdn = vstring_alloc(100);
+    int     got_st, want_st = DNS_OK;
+    int     got_herrval, want_herrval = 0;
+    DNS_RR *got_rr = 0, *want_rr;
+    int     got_rcode, want_rcode = NOERROR;
+    struct in_addr sin_addr;
+    const char *localhost = "localhost";
+
+    /*
+     * Set up expectations.
+     */
+    vstring_strcpy(want_fqdn, localhost);
+    if (inet_pton(AF_INET, "127.0.0.1", &sin_addr) != 1)
+       ptest_fatal(t, "inet_pton(AF_INET, \"127.0.0.1\", (ptr)): bad address");
+    want_rr = make_dns_rr(localhost, localhost, T_A, C_IN,
+                         10, 0, 0, 0, 0, &sin_addr, sizeof(sin_addr));
+    expect_dns_lookup_x(1, want_herrval, want_st, localhost, T_A, NO_RES_FLAGS,
+                       want_rr, want_fqdn, (VSTRING *) 0,
+                       want_rcode, DNS_REQ_FLAG_NONE);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = dns_lookup_x("localhost", T_A, NO_RES_FLAGS, &got_rr,
+                         got_fqdn, (VSTRING *) 0, &got_rcode,
+                         DNS_REQ_FLAG_NONE);
+    if (got_st != want_st) {
+       ptest_error(t, "dns_lookup_x: got result %d, want %d", got_st, want_st);
+    } else if (eq_dns_rr(t, "dns_lookup_x", got_rr, want_rr) == 0) {
+        /* warning is already logged */ ;
+    } else if (strcmp(vstring_str(got_fqdn), vstring_str(want_fqdn)) != 0) {
+       ptest_error(t, "dns_lookup_x: got fqdn '%s', want '%s'",
+                   vstring_str(got_fqdn), vstring_str(want_fqdn));
+    } else if (got_rcode != want_rcode) {
+       ptest_error(t, "dns_lookup_x: got rcode %d, want %d", got_rcode, want_rcode);
+    }
+    got_herrval = dns_get_h_errno();
+    if (got_herrval != want_herrval)
+       ptest_error(t, "dns_get_h_errno: got %d, want %d",
+                   got_herrval, want_herrval);
+
+    /*
+     * Clean up.
+     */
+    vstring_free(got_fqdn);
+    vstring_free(want_fqdn);
+    dns_rr_free(want_rr);
+    if (got_rr)
+       dns_rr_free(got_rr);
+}
+
+static void test_dns_lookup_x_notexist(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *got_why = vstring_alloc(100), *want_why = vstring_alloc(100);
+    int     got_st, want_st = DNS_NOTFOUND;
+    int     got_herrval, want_herrval = HOST_NOT_FOUND;
+    int     got_rcode, want_rcode = NXDOMAIN;
+
+    /*
+     * Set up expectations.
+     */
+    vstring_strcpy(want_why, "Host or domain name not found."
+           " Name service error for name=notexist type=A: Host not found");
+    expect_dns_lookup_x(1, want_herrval, want_st, "notexist", T_A, NO_RES_FLAGS,
+                       (DNS_RR *) 0, (VSTRING *) 0, want_why, want_rcode,
+                       DNS_REQ_FLAG_NONE);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = dns_lookup_x("notexist", T_A, NO_RES_FLAGS, (DNS_RR **) 0,
+                         (VSTRING *) 0, got_why, &got_rcode,
+                         DNS_REQ_FLAG_NONE);
+    if (got_st != want_st) {
+       ptest_error(t, "dns_lookup_x: got result %d, want %d", got_st, want_st);
+    } else if (got_rcode != want_rcode) {
+       ptest_error(t, "dns_lookup_x: got rcode %d, want %d", got_rcode, want_rcode);
+    } else if (strcmp(vstring_str(got_why), vstring_str(want_why)) != 0) {
+       ptest_error(t, "dns_lookup_x: got why '%s', want '%s'",
+                   vstring_str(got_why), vstring_str(want_why));
+    }
+    got_herrval = dns_get_h_errno();
+    if (got_herrval != want_herrval)
+       ptest_error(t, "dns_get_h_errno: got %d, want %d",
+                   got_herrval, want_herrval);
+
+    /*
+     * Clean up.
+     */
+    vstring_free(got_why);
+    vstring_free(want_why);
+}
+
+static void test_dns_lookup_x_unused(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+
+    /*
+     * Create an expectation, without calling it. I does not matter what the
+     * expectation is, so we use the one from test_dns_lookup_x_notexist().
+     */
+    expect_dns_lookup_x(1, HOST_NOT_FOUND, NXDOMAIN, "notexist", T_A, NO_RES_FLAGS,
+                       (DNS_RR *) 0, (VSTRING *) 0, (VSTRING *) 0, 0,
+                       DNS_REQ_FLAG_NONE);
+
+    /*
+     * We expect that there will be a 'missing call' error. If the error does
+     * not happen then the test fails.
+     */
+    expect_ptest_error(t, "got 0 calls for dns_lookup_x(\"notexist\", A, "
+                      "0, (ptr), (ptr), (ptr), (ptr), 0), want 1");
+}
+
+static void test_dns_set_h_errno_success(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static int want_herrval[] = {12345, 54321};
+    int     got_herrval;
+    int     n;
+
+    for (n = 0; n < 2; n++) {
+       dns_set_h_errno(want_herrval[n]);
+       got_herrval = dns_get_h_errno();
+       if (got_herrval != want_herrval[n])
+           ptest_error(t, "dns_get_h_errno: got %d, want %d",
+                       got_herrval, want_herrval[n]);
+    }
+}
+
+static void test_eq_dns_rr_differ(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    DNS_RR *got_rr, *want_rr;
+    struct in_addr sin_addr;
+    const char *localhost = "localhost";
+
+    if (inet_pton(AF_INET, "127.0.0.1", &sin_addr) != 1)
+       ptest_fatal(t, "inet_pton(AF_INET, \"127.0.0.1\", (ptr)): bad address");
+    want_rr = make_dns_rr(localhost, localhost, T_A, C_IN,
+                         10, 0, 0, 0, 0, &sin_addr, sizeof(sin_addr));
+
+    if (inet_pton(AF_INET, "127.0.0.2", &sin_addr) != 1)
+       ptest_fatal(t, "inet_pton(AF_INET, \"127.0.0.2\", (ptr)): bad address");
+    got_rr = make_dns_rr(localhost, localhost, T_A, C_IN,
+                        10, 0, 0, 0, 0, &sin_addr, sizeof(sin_addr));
+
+    expect_ptest_error(t, "eq_dns_rr: got data 7F:00:00:02, want 7F:00:00:01");
+    if (eq_dns_rr(t, "eq_dns_rr", got_rr, want_rr))
+       ptest_error(t, "eq_dns_rr: Unexpected match");
+    dns_rr_free(got_rr);
+    dns_rr_free(want_rr);
+}
+
+ /*
+  * Test cases. The "success" tests exercise the expectation match and apply
+  * helpers, and "unused" tests exercise the print helpers.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+       "test_dns_lookup_x success", test_dns_lookup_x_success,
+    },
+    {
+       "test_dns_lookup_x notexist", test_dns_lookup_x_notexist,
+    },
+    {
+       "test_dns_lookup_x unused", test_dns_lookup_x_unused,
+    },
+    {
+       "dns_set_h_errno success", test_dns_set_h_errno_success,
+    },
+    {
+       "test_eq_dns_rr differ", test_eq_dns_rr_differ,
+    },
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/testing/mock_getaddrinfo.c b/postfix/src/testing/mock_getaddrinfo.c
new file mode 100644 (file)
index 0000000..41fc941
--- /dev/null
@@ -0,0 +1,487 @@
+/*++
+/* NAME
+/*     mock_getaddrinfo 3
+/* SUMMARY
+/*     mock getaddrinfo/getnameinfo for hermetic tests
+/* SYNOPSIS
+/*     #include <mock_getaddrinfo.h>
+/*
+/*     int     getaddrinfo(
+/*     const char *hostname,
+/*     const char *servname,
+/*     const struct addrinfo *hints,
+/*     struct addrinfo **result,
+/*
+/*     int     getnameinfo(
+/*     const struct sockaddr *sa,
+/*     size_t salen,
+/*     char    *host,
+/*     size_t  hostlen,
+/*     char    *serv,
+/*     size_t  servlen
+/*     int     flags)
+/* EXPECTATION SETUP
+/*     void    expect_getaddrinfo(
+/*     int     calls_expected,
+/*     int     retval,
+/*     const char *hostname,
+/*     const char *servname,
+/*     const struct addrinfo *hints,
+/*     struct addrinfo *result)
+/*
+/*     void    expect_getnameinfo(
+/*     int     calls_expected,
+/*     int     retval,
+/*     const struct sockaddr *sa,
+/*     size_t salen,
+/*     char    *host,
+/*     size_t  hostlen,
+/*     char    *serv,
+/*     size_t  servlen
+/*     int     flags)
+/* TEST DATA
+/*     struct addrinfo *make_addrinfo(
+/*     const struct addrinfo *hints,
+/*     const char *name,
+/*     const char *addr,
+/*     int     port)
+/*
+/*     struct addrinfo *copy_addrinfo(const struct addrinfo *ai)
+/*
+/*     void    freeaddrinfo(struct addrinfo *ai)
+/*
+/*     struct sockaddr *make_sockaddr(
+/*     int     family,
+/*     const char *addr,
+/*     int     port)
+/*
+/*     void    free_sockaddr(struct sockaddr *sa)
+/* MATCHERS
+/*     int     eq_addrinfo(
+/*     PTEST_CTX * t,
+/*     const char *what,
+/*     struct addrinfo got,
+/*     struct addrinfo want)
+/*
+/*     int     eq_sockaddr
+/*     PTEST_CTX * t,
+/*     const char *what,
+/*     const struct sockaddr *got,
+/*     size_t  gotlen,
+/*     const struct sockaddr *want,
+/*     size_t  wantlen)
+/* DESCRIPTION
+/*     This module implements mock system library functions that
+/*     produce prepared outputs in response to expected inputs.
+/*     This supports hermetic tests, i.e. tests that do not depend
+/*     on host configuration or on network access.
+/*
+/*     The "expect_" functions take expected inputs and corresponding
+/*     outputs. They make deep copies of their arguments, including
+/*     "struct addrinfo *" linked lists. The retval argument
+/*     specifies a prepared result value. The calls_expected
+/*     argument specifies the expected number of calls (zero means
+/*     one or more calls, not: zero calls).
+/*
+/*     make_addrinfo() creates one addrinfo structure. To create
+/*     linked list, manually append make_addrinfo() results.
+/*
+/*     copy_addrinfo() makes a deep copy of a linked list of
+/*     addrinfo structures.
+/*
+/*     freeaddrinfo() deletes a linked list of addrinfo structures.
+/*     This function must be used for addrinfo structures created
+/*     with make_addrinfo() and copy_addrinfo().
+/*
+/*     make_sockaddr() creates a sockaddr structure from the string
+/*     representation of an IP address.
+/*
+/*     free_sockaddr() exists to make program code more explicit.
+/*
+/*     eq_addrinfo() compares addrinfo linked lists and reports
+/*     differences with ptest_error(). The what argument provides
+/*     context. Specify a null test context for silent operation.
+/*
+/*     eq_sockaddr() compares sockaddr instances and reports
+/*     differences with ptest_error(). The what argument provides
+/*     context. Specify a null test context for silent operation.
+/*
+/*     append_addrinfo_to_string() appends a textual representation
+/*     of the referenced addrinfo to the specified buffer.
+/*
+/*     addrinfo_hints_to_string() writes a textual representation of
+/*     the referenced getaddrinfo() hints object.
+/*
+/*     sockaddr_to_string() writes a textual representation of the
+/*     referenced sockaddr object.
+/*
+/*     pf_to_string(), af_to_string(), socktype_to_string,
+/*     ipprotocol_to_string, ai_flags_to_string(), ni_flags_to_string()
+/*     produce a textual representation of addrinfo properties or
+/*     getnameinfo() flags.
+/* DIAGNOSTICS
+/*     If a mock is called unexpectedly (the call arguments do not
+/*     match any expectation, or they do match, but more calls are
+/*     made than were expected), a warning is logged, and the test
+/*     will be flagged as failed. For now the mock returns an error
+/*     result to the caller.  TODO: consider aborting the test.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <wrap_netdb.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>                     /* sprintf/snprintf */
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <myaddrinfo.h>
+#include <mymalloc.h>
+
+ /*
+  * Test library.
+  */
+#include <mock_getaddrinfo.h>
+#include <pmock_expect.h>
+#include <ptest.h>
+
+#define MYSTRDUP_OR_NULL(x)    ((x) ? mystrdup(x) : 0)
+#define STR_OR_NULL(s)         ((s) ? (s) : "(null)")
+
+#define STR    vstring_str
+
+ /*
+  * Manage getaddrinfo() expectations and responses. We use this structure
+  * for deep copies of expect_getaddrinfo() expected inputs and prepared
+  * responses, and for shallow copies of getaddrinfo() inputs, so that we can
+  * reuse the print_getaddrinfo() helper.
+  */
+struct getaddrinfo_expectation {
+    MOCK_EXPECT mock_expect;           /* generic fields */
+    int     retval;                    /* result value */
+    char   *node;                      /* inputs */
+    char   *service;
+    struct addrinfo *hints;
+    struct addrinfo *res;              /* outputs */
+};
+
+ /*
+  * Pointers to getaddrinfo() outputs.
+  */
+struct getaddrinfo_targets {
+    struct addrinfo **res;
+    int    *retval;
+};
+
+/* match_getaddrinfo - match inputs against expectation */
+
+static int match_getaddrinfo(const MOCK_EXPECT *expect,
+                                    const MOCK_EXPECT *inputs)
+{
+    struct getaddrinfo_expectation *pe =
+    (struct getaddrinfo_expectation *) expect;
+    struct getaddrinfo_expectation *pi =
+    (struct getaddrinfo_expectation *) inputs;
+
+    return (strcmp(STR_OR_NULL(pe->node), STR_OR_NULL(pi->node)) == 0
+         && strcmp(STR_OR_NULL(pe->service), STR_OR_NULL(pi->service)) == 0
+        && eq_addrinfo((PTEST_CTX *) 0, (char *) 0, pe->hints, pi->hints));
+}
+
+/* assign_getaddrinfo - assign expected output */
+
+static void assign_getaddrinfo(const MOCK_EXPECT *expect, void *targets)
+{
+    struct getaddrinfo_expectation *pe =
+    (struct getaddrinfo_expectation *) expect;
+    struct getaddrinfo_targets *pt =
+    (struct getaddrinfo_targets *) targets;
+
+    if (pe->retval == 0)
+       *(pt->res) = copy_addrinfo(pe->res);
+    *pt->retval = pe->retval;
+}
+
+/* print_getaddrinfo - print expected inputs */
+
+static char *print_getaddrinfo(const MOCK_EXPECT *expect, VSTRING *buf)
+{
+    struct getaddrinfo_expectation *pe =
+    (struct getaddrinfo_expectation *) expect;
+    VSTRING *hints_buf = vstring_alloc(100);
+
+    vstring_sprintf(buf, "\"%s\", \"%s\", %s, (ptr)",
+                   STR_OR_NULL(pe->node),
+                   STR_OR_NULL(pe->service),
+                   addrinfo_hints_to_string(hints_buf, pe->hints));
+    vstring_free(hints_buf);
+    return (vstring_str(buf));
+}
+
+/* free_getaddrinfo - destructor */
+
+static void free_getaddrinfo(MOCK_EXPECT *expect)
+{
+    struct getaddrinfo_expectation *pe =
+    (struct getaddrinfo_expectation *) expect;
+
+    if (pe->node)
+       myfree(pe->node);
+    if (pe->service)
+       myfree(pe->service);
+    if (pe->hints)
+       myfree(pe->hints);
+    if (pe->retval == 0)
+       freeaddrinfo(pe->res);
+    pmock_expect_free(expect);
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG getaddrinfo_sig = {
+    "getaddrinfo",
+    match_getaddrinfo,
+    assign_getaddrinfo,
+    print_getaddrinfo,
+    free_getaddrinfo,
+};
+
+/* _expect_getaddrinfo - set up expectation */
+
+void    _expect_getaddrinfo(const char *file, int line,
+                                   int calls_expected, int retval,
+                                   const char *node,
+                                   const char *service,
+                                   const struct addrinfo *hints,
+                                   struct addrinfo *res)
+{
+    struct getaddrinfo_expectation *pe;
+
+    pe = (struct getaddrinfo_expectation *)
+       pmock_expect_create(&getaddrinfo_sig,
+                           file, line, calls_expected, sizeof(*pe));
+    pe->retval = retval;
+    pe->node = MYSTRDUP_OR_NULL(node);
+    pe->service = MYSTRDUP_OR_NULL(service);
+    pe->hints = copy_addrinfo(hints);
+    if (pe->retval == 0)
+       pe->res = copy_addrinfo(res);
+}
+
+/* getaddrinfo - mock getaddrinfo */
+
+int     getaddrinfo(const char *node,
+                           const char *service,
+                           const struct addrinfo *hints,
+                           struct addrinfo **res)
+{
+    struct getaddrinfo_expectation inputs;
+    struct getaddrinfo_targets targets;
+    int     retval = EAI_FAIL;
+
+    /*
+     * Bundle the arguments to simplify handling.
+     */
+    inputs.node = (char *) node;
+    inputs.service = (char *) service;
+    inputs.hints = (struct addrinfo *) hints;
+
+    targets.res = res;
+    targets.retval = &retval;
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&getaddrinfo_sig,
+                             &inputs.mock_expect, (void *) &targets);
+    return (retval);
+}
+
+ /*
+  * Manage getnameinfo() expectations and responses. We use this structure
+  * for deep copies of expect_getnameinfo() expected inputs and prepared
+  * responses, and for shallow copies of getnameinfo() inputs, so that we can
+  * reuse the print_getnameinfo() helper.
+  */
+struct getnameinfo_expectation {
+    MOCK_EXPECT mock_expect;           /* generic fields */
+    int     retval;                    /* result value */
+    struct sockaddr *sa;               /* inputs */
+    size_t  salen;
+    char   *host;                      /* outputs */
+    size_t  hostlen;
+    char   *serv;
+    size_t  servlen;
+    int     flags;                     /* other input */
+};
+
+ /*
+  * Pointers to getnameinfo() outputs.
+  */
+struct getnameinfo_targets {
+    char   *host;
+    size_t  hostlen;
+    char   *serv;
+    size_t  servlen;
+    int    *retval;
+};
+
+/* match_getnameinfo - match inputs against expectation */
+
+static int match_getnameinfo(const MOCK_EXPECT *expect,
+                                    const MOCK_EXPECT *inputs)
+{
+    struct getnameinfo_expectation *pe =
+    (struct getnameinfo_expectation *) expect;
+    struct getnameinfo_expectation *pi =
+    (struct getnameinfo_expectation *) inputs;
+
+    return (eq_sockaddr((PTEST_CTX *) 0, (char *) 0,
+                       pe->sa, pe->salen, pi->sa, pi->salen)
+           && pe->flags == pi->flags);
+}
+
+/* assign_getnameinfo - assign expected output */
+
+static void assign_getnameinfo(const MOCK_EXPECT *expect, void *targets)
+{
+    struct getnameinfo_expectation *pe =
+    (struct getnameinfo_expectation *) expect;
+    struct getnameinfo_targets *pt =
+    (struct getnameinfo_targets *) targets;
+
+#define MIN_OF(x,y) ((x) < (y) ? (x) : (y))
+
+    if (pe->retval == 0) {
+       if (pt->host && pe->host) {
+           strncpy(pt->host, pe->host, MIN_OF(pt->hostlen, pe->hostlen));
+           pt->host[pt->hostlen - 1] = 0;
+       }
+       if (pt->serv && pe->serv) {
+           strncpy(pt->serv, pe->serv, MIN_OF(pt->servlen, pe->servlen));
+           pt->serv[pt->servlen - 1] = 0;
+       }
+    }
+    *pt->retval = pe->retval;
+}
+
+/* print_getnameinfo - print inputs */
+
+static char *print_getnameinfo(const MOCK_EXPECT *expect, VSTRING *buf)
+{
+    struct getnameinfo_expectation *pe =
+    (struct getnameinfo_expectation *) expect;
+    VSTRING *sockaddr_buf = vstring_alloc(100);
+    VSTRING *flags_buf = vstring_alloc(100);
+
+    vstring_sprintf(buf, "%s, %ld, (ptr), (len), (ptr), (len), %s",
+                   sockaddr_to_string(sockaddr_buf, pe->sa, pe->salen),
+                   (long) pe->salen,
+                   ni_flags_to_string(flags_buf, pe->flags));
+    vstring_free(sockaddr_buf);
+    vstring_free(flags_buf);
+    return (STR(buf));
+}
+
+/* free_getnameinfo - destructor */
+
+static void free_getnameinfo(MOCK_EXPECT *expect)
+{
+    struct getnameinfo_expectation *pe =
+    (struct getnameinfo_expectation *) expect;
+
+    if (pe->sa)
+       myfree(pe->sa);
+    if (pe->host)
+       myfree(pe->host);
+    if (pe->serv)
+       myfree(pe->serv);
+    pmock_expect_free(expect);
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG getnameinfo_sig = {
+    "getnameinfo",
+    match_getnameinfo,
+    assign_getnameinfo,
+    print_getnameinfo,
+    free_getnameinfo,
+};
+
+/* _expect_getnameinfo - set up expectation */
+
+void    _expect_getnameinfo(const char *file, int line,
+                                   int calls_expected, int retval,
+                                   const struct sockaddr *sa, size_t salen,
+                                   const char *host, size_t hostlen,
+                                   const char *serv, size_t servlen,
+                                   int flags)
+{
+    struct getnameinfo_expectation *pe;
+
+    pe = (struct getnameinfo_expectation *)
+       pmock_expect_create(&getnameinfo_sig,
+                           file, line, calls_expected, sizeof(*pe));
+    pe->retval = retval;
+    pe->sa = (struct sockaddr *) mymalloc(salen);
+    memcpy(pe->sa, sa, salen);
+    pe->salen = salen;
+    pe->host = MYSTRDUP_OR_NULL(host);
+    pe->hostlen = hostlen;
+    pe->serv = MYSTRDUP_OR_NULL(serv);
+    pe->servlen = servlen;
+    pe->flags = flags;
+}
+
+/* getnameinfo - mock getnameinfo */
+
+int     getnameinfo(const struct sockaddr *sa, socklen_t salen,
+                           char *host, size_t hostlen,
+                           char *serv, size_t servlen, int flags)
+{
+    struct getnameinfo_expectation inputs;
+    struct getnameinfo_targets targets;
+    int     retval = EAI_FAIL;
+
+    /*
+     * Bundle the arguments to simplify handling.
+     */
+    inputs.sa = (struct sockaddr *) sa;
+    inputs.salen = salen;
+    inputs.flags = flags;
+
+    targets.host = host;
+    targets.hostlen = hostlen;
+    targets.serv = serv;
+    targets.servlen = servlen;
+    targets.retval = &retval;
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&getnameinfo_sig,
+                             &inputs.mock_expect, (void *) &targets);
+    return (retval);
+}
diff --git a/postfix/src/testing/mock_getaddrinfo.h b/postfix/src/testing/mock_getaddrinfo.h
new file mode 100644 (file)
index 0000000..0f89b68
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef _MOCK_GETADDRINFO_H_INCLUDED_
+#define _MOCK_GETADDRINFO_H_INCLUDED_
+
+/*++
+/* NAME
+/*     mock_getaddrinfo 3h
+/* SUMMARY
+/*     getaddrinfo/getnameinfo mock for hermetic tests
+/* SYNOPSIS
+/*     #include <mock_getaddrinfo.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <wrap_netdb.h>
+
+ /*
+  * Utility library.
+  */
+#include <myaddrinfo.h>                        /* MAI_HOSTNAME_STR, etc. */
+
+ /*
+  * Test library.
+  */
+#include <addrinfo_to_string.h>
+#include <make_addr.h>
+#include <match_addr.h>
+#include <match_basic.h>
+#include <ptest.h>
+
+ /*
+  * Manage expectations and responses. Capture the source file name and line
+  * number for better diagnostics.
+  */
+#define expect_getaddrinfo(...) \
+       _expect_getaddrinfo(__FILE__, __LINE__, __VA_ARGS__)
+
+extern void _expect_getaddrinfo(const char *, int, int, int,
+                                       const char *, const char *,
+                                       const struct addrinfo *,
+                                       struct addrinfo *);
+
+#define expect_getnameinfo(...) \
+       _expect_getnameinfo(__FILE__, __LINE__, __VA_ARGS__)
+
+extern void _expect_getnameinfo(const char *, int, int, int,
+                                       const struct sockaddr *, size_t,
+                                       const char *, size_t,
+                                       const char *, size_t, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/mock_getaddrinfo_test.c b/postfix/src/testing/mock_getaddrinfo_test.c
new file mode 100644 (file)
index 0000000..23c1b56
--- /dev/null
@@ -0,0 +1,220 @@
+ /*
+  * Test program for the mock_getaddrinfo module. See comments in
+  * ptest_main.h and pmock_expect_test.c for a documented example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <wrap_netdb.h>
+
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <mock_getaddrinfo.h>
+#include <pmock_expect.h>
+#include <ptest.h>
+
+#define STR    vstring_str
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+static void test_getaddrinfo_success(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    struct addrinfo hints;
+    struct addrinfo *got_addrinfo;
+    struct addrinfo *want_addrinfo;
+    int     got_st, want_st = 0;
+
+    /*
+     * Set up expectations.
+     */
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = PF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+    want_addrinfo = make_addrinfo(&hints, "localhost", "127.0.0.1", 25);
+    expect_getaddrinfo(1, want_st, "localhost", "smtp", &hints, want_addrinfo);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = getaddrinfo("localhost", "smtp", &hints, &got_addrinfo);
+    if (got_st != want_st) {
+       ptest_error(t, "getaddrinfo: got %d, want %d", got_st, want_st);
+    } else if (eq_addrinfo(t, "getaddrinfo", got_addrinfo,
+                          want_addrinfo) == 0) {
+       VSTRING *got_buf = vstring_alloc(100);
+       VSTRING *want_buf = vstring_alloc(100);
+
+       ptest_error(t, "getaddrinfo: got %s, want %s",
+                   append_addrinfo_to_string(got_buf, got_addrinfo),
+                   append_addrinfo_to_string(want_buf, want_addrinfo));
+       vstring_free(got_buf);
+       vstring_free(want_buf);
+    }
+
+    /*
+     * Clean up.
+     */
+    freeaddrinfo(want_addrinfo);
+    if (got_addrinfo)
+       freeaddrinfo(got_addrinfo);
+}
+
+static void test_getaddrinfo_failure(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    struct addrinfo hints;
+    struct addrinfo *got_addrinfo = 0;
+    struct addrinfo *want_addrinfo = 0;
+    int     got_st, want_st = EAI_FAIL;
+    VSTRING *event_buf = vstring_alloc(100);
+    VSTRING *hints_buf = vstring_alloc(100);
+
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = PF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+
+    /*
+     * The missing expectation is intentional. Do not count this as an error.
+     */
+    vstring_sprintf(event_buf, "unexpected call: "
+                   "getaddrinfo(\"notexist\", \"smtp\", %s, (ptr))",
+                   addrinfo_hints_to_string(hints_buf, &hints));
+    expect_ptest_error(t, STR(event_buf));
+    vstring_free(event_buf);
+    vstring_free(hints_buf);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = getaddrinfo("notexist", "smtp", &hints, &got_addrinfo);
+    if (got_st != want_st) {
+       ptest_error(t, "getaddrinfo: got %d, want %d", got_st, want_st);
+    } else if (eq_addrinfo(t, "getaddrinfo", got_addrinfo,
+                          want_addrinfo) == 0) {
+       VSTRING *got_buf = vstring_alloc(100);
+
+       ptest_error(t, "getaddrinfo: got %s, want (null)",
+                   append_addrinfo_to_string(got_buf, got_addrinfo));
+       vstring_free(got_buf);
+    }
+
+    /*
+     * Clean up.
+     */
+    if (got_addrinfo)
+       freeaddrinfo(got_addrinfo);
+}
+
+static void test_getnameinfo_numeric_success(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    struct sockaddr *req_sockaddr = make_sockaddr(AF_INET, "127.0.0.1", 25);
+    size_t  req_sockaddrlen = sizeof(struct sockaddr_in);
+    int     got_st, want_st = 0;
+    MAI_HOSTADDR_STR want_hostaddr = {"127.0.0.1"};
+    MAI_SERVPORT_STR want_servport = {"25"};
+    MAI_HOSTADDR_STR got_hostaddr;
+    MAI_SERVPORT_STR got_servport;
+    int     req_flags = NI_NUMERICHOST | NI_NUMERICSERV;
+
+    /*
+     * Set up expectations.
+     */
+    expect_getnameinfo(1, want_st, req_sockaddr, req_sockaddrlen,
+                      want_hostaddr.buf, sizeof(want_hostaddr),
+                      want_servport.buf, sizeof(want_servport),
+                      req_flags);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = getnameinfo(req_sockaddr, req_sockaddrlen,
+                        got_hostaddr.buf, sizeof(got_hostaddr),
+                        got_servport.buf, sizeof(got_servport),
+                        req_flags);
+
+    if (got_st != want_st) {
+       ptest_error(t, "getnameinfo: got %d, want %d", got_st, want_st);
+    } else if (strcmp(got_hostaddr.buf, want_hostaddr.buf) != 0) {
+       ptest_error(t, "getnameinfo hostaddr: got '%s', want '%s'",
+                   got_hostaddr.buf, want_hostaddr.buf);
+    } else if (strcmp(got_servport.buf, want_servport.buf) != 0) {
+       ptest_error(t, "getnameinfo servport: got '%s', want '%s'",
+                   got_servport.buf, want_servport.buf);
+    }
+
+    /*
+     * Clean up.
+     */
+    free_sockaddr(req_sockaddr);
+}
+
+static void test_getnameinfo_numeric_failure(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    struct sockaddr *req_sockaddr = make_sockaddr(AF_INET, "127.0.0.1", 25);
+    size_t  req_sockaddrlen = sizeof(struct sockaddr_in);
+    int     req_flags = NI_NUMERICHOST | NI_NUMERICSERV;
+    int     got_st, want_st = EAI_FAIL;
+    VSTRING *event_buf = vstring_alloc(100);
+    VSTRING *ni_flags_buf = vstring_alloc(100);
+
+    /*
+     * The missing expectation is intentional. Do not count this as an error.
+     */
+    vstring_sprintf(event_buf, "unexpected call: "
+                   "getnameinfo({AF_INET, 127.0.0.1, 25}, %ld, "
+                   "(ptr), (len), (ptr), (len), %s",
+                   (long) req_sockaddrlen,
+                   ni_flags_to_string(ni_flags_buf, req_flags));
+    expect_ptest_error(t, STR(event_buf));
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = getnameinfo(req_sockaddr, req_sockaddrlen,
+                        (char *) 0, (size_t) 0,
+                        (char *) 0, (size_t) 0,
+                        req_flags);
+    if (got_st != want_st)
+       ptest_error(t, "getnameinfo return: got %d, want %d", got_st, want_st);
+
+    /*
+     * Clean up.
+     */
+    vstring_free(event_buf);
+    vstring_free(ni_flags_buf);
+    free_sockaddr(req_sockaddr);
+}
+
+ /*
+  * Test cases. The "success" tests exercise the expectation match and apply
+  * helpers, and "failure" tests exercise the print helpers. All tests
+  * exercise the expectation free helpers.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+       "getaddrinfo success", test_getaddrinfo_success,
+    },
+    {
+       "getaddrinfo failure", test_getaddrinfo_failure,
+    },
+    {
+       "getnameinfo_numeric success", test_getnameinfo_numeric_success,
+    },
+    {
+       "getnameinfo_numeric failure", test_getnameinfo_numeric_failure,
+    },
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/testing/mock_myaddrinfo.c b/postfix/src/testing/mock_myaddrinfo.c
new file mode 100644 (file)
index 0000000..aeae3cd
--- /dev/null
@@ -0,0 +1,776 @@
+/*++
+/* NAME
+/*     mock_myaddrinfo 3
+/* SUMMARY
+/*     myaddrinfo mock for hermetic tests
+/* SYNOPSIS
+/*     #include <mock_myaddrinfo.h>
+/*
+/*     int     hostname_to_sockaddr_pf(
+/*     const char *hostname,
+/*     int     pf,
+/*     const char *service,
+/*     int     socktype,
+/*     struct addrinfo **result,
+/*
+/*     int     hostaddr_to_sockaddr(
+/*     const char *hostaddr,
+/*     const char *service,
+/*     int     socktype,
+/*     struct addrinfo **result)
+/*
+/*     int     sockaddr_to_hostaddr(
+/*     const struct sockaddr *sa,
+/*     SOCKADDR_SIZE salen,
+/*     MAI_HOSTADDR_STR *hostaddr,
+/*     MAI_SERVPORT_STR *portnum,
+/*     int     socktype)
+/*
+/*     int     sockaddr_to_hostname(
+/*     const struct sockaddr *sa,
+/*     SOCKADDR_SIZE salen,
+/*     MAI_HOSTNAME_STR *hostname,
+/*     MAI_SERVNAME_STR *service,
+/*     int     socktype)
+/* EXPECTATION SETUP
+/*     void    expect_hostname_to_sockaddr_pf(
+/*     int     calls_expected,
+/*     int     retval,
+/*     const char *hostname,
+/*     int     pf,
+/*     const char *service,
+/*     int     socktype,
+/*     struct addrinfo *result)
+/*
+/*     void    expect_hostaddr_to_sockaddr(
+/*     int     calls_expected,
+/*     int     retval,
+/*     const char *hostaddr,
+/*     const char *service,
+/*     int     socktype,
+/*     struct addrinfo *result)
+/*
+/*     void    expect_sockaddr_to_hostaddr(
+/*     int     calls_expected,
+/*     int     retval,
+/*     const struct sockaddr *sa,
+/*     SOCKADDR_SIZE salen,
+/*     MAI_HOSTADDR_STR *hostaddr,
+/*     MAI_SERVPORT_STR *portnum,
+/*     int     socktype)
+/*
+/*     void    expect_sockaddr_to_hostname(
+/*     int     calls_expected,
+/*     int     retval,
+/*     const struct sockaddr *sa,
+/*     SOCKADDR_SIZE salen,
+/*     MAI_HOSTNAME_STR *hostname,
+/*     MAI_SERVNAME_STR *service,
+/*     int     socktype)
+/* TEST DATA
+/*     struct addrinfo *make_addrinfo(
+/*     const struct addrinfo *hints,
+/*     const char *name,
+/*     const char *addr)
+/*
+/*     struct addrinfo *copy_addrinfo(const struct addrinfo *ai)
+/*
+/*     void    freeaddrinfo(struct addrinfo *ai)
+/*
+/*     struct sockaddr *make_sockaddr(
+/*     const char *addr,
+/*     int     port)
+/*
+/*     void    free_sockaddr(struct sockaddr *sa)
+/* MATCHERS
+/*     int     eq_addrinfo(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     struct addrinfo *got,
+/*     struct addrinfo *want)
+/* DESCRIPTION
+/*     This module implements mock myaddrinfo() lookup and conversion
+/*     functions that produce prepared outputs in response to
+/*     expected inputs. This supports hermetic tests, i.e. tests
+/*     that do not depend on host configuration or on network
+/*     access.
+/*
+/*     This module also provides a mock freeaddrinfo() function.
+/*     This is needed because the mock_myaddrinfo library and the
+/*     system library may use different memory allocation strategies.
+/*
+/*     The "expect_" functions take expected inputs and corresponding
+/*     outputs. They make deep copies of their arguments, including
+/*     the "struct addrinfo *" linked lists. The retval argument
+/*     specifies a prepared result value. The calls_expected argument
+/*     specifies the expected number of calls (zero means one or
+/*     more calls, not: zero calls).
+/*
+/*     make_addrinfo() creates one addrinfo structure. To create
+/*     linked list, manually append make_addrinfo() results.
+/*
+/*     copy_addrinfo() makes a deep copy of a linked list of
+/*     addrinfo structures.
+/*
+/*     freeaddrinfo() deletes a linked list of addrinfo structures.
+/*     This function must be used for addrinfo structures created
+/*     with make_addrinfo() and copy_addrinfo().
+/*
+/*     make_sockaddr() creates a sockaddr structure from the string
+/*     representation of an IP address.
+/*
+/*     free_sockaddr() exists to make program code more explicit.
+/*
+/*     eq_addrinfo() compares addrinfo linked lists and reports
+/*     differences with ptest_error().  The what argument provides
+/*     context. Specify a null test context for silent operation.
+/* DIAGNOSTICS
+/*     If a mock is called unexpectedly (the call arguments do not
+/*     match the expectation, or more calls are made than expected),
+/*     a warning is logged, and the test will be flagged as failed.
+/*     For now the mock returns an error result to the caller.
+/*     TODO: consider aborting the test.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>                     /* sprintf */
+
+ /*
+  * Utility library.
+  */
+#include <mymalloc.h>
+#include <msg.h>
+#include <myaddrinfo.h>
+
+ /*
+  * Test library.
+  */
+#include <mock_myaddrinfo.h>
+#include <pmock_expect.h>
+#include <make_addr.h>
+#include <ptest.h>
+
+#define MYSTRDUP_OR_NULL(x)            ((x) ? mystrdup(x) : 0)
+#define STR_OR_NULL(s)                 ((s) ? (s) : "(null)")
+
+ /*
+  * Manage hostname_to_sockaddr_pf() expectations and responses. We use this
+  * structure for deep copies pf expect_hostname_to_sockaddr_pf() expected
+  * inputs and prepared responses, and for shallow copies of
+  * hostname_to_sockaddr_pf() inputs, so that we can reuse the
+  * print_hostname_to_sockaddr_pf() helper.
+  */
+struct hostname_to_sockaddr_pf_expectation {
+    MOCK_EXPECT mock_expect;           /* generic fields */
+    int     retval;                    /* result value */
+    char   *hostname;                  /* inputs */
+    int     pf;
+    char   *service;
+    int     socktype;
+    struct addrinfo *res;              /* other outputs */
+};
+
+ /*
+  * Pointers to hostname_to_sockaddr_pf() outputs.
+  */
+struct hostname_to_sockaddr_pf_targets {
+    struct addrinfo **res;
+    int    *retval;
+};
+
+/* match_hostname_to_sockaddr_pf - match inputs against expectation */
+
+static int match_hostname_to_sockaddr_pf(const MOCK_EXPECT *expect,
+                                                const MOCK_EXPECT *inputs)
+{
+    struct hostname_to_sockaddr_pf_expectation *pe =
+    (struct hostname_to_sockaddr_pf_expectation *) expect;
+    struct hostname_to_sockaddr_pf_expectation *pi =
+    (struct hostname_to_sockaddr_pf_expectation *) inputs;
+
+    return (strcmp(STR_OR_NULL(pe->hostname),
+                  STR_OR_NULL(pi->hostname)) == 0
+           && pe->pf == pi->pf
+           && strcmp(STR_OR_NULL(pe->service),
+                     STR_OR_NULL(pi->service)) == 0
+           && pe->socktype == pi->socktype);
+}
+
+/* assign_hostname_to_sockaddr_pf - assign expected output */
+
+static void assign_hostname_to_sockaddr_pf(const MOCK_EXPECT *expect,
+                                                  void *targets)
+{
+    struct hostname_to_sockaddr_pf_expectation *pe =
+    (struct hostname_to_sockaddr_pf_expectation *) expect;
+    struct hostname_to_sockaddr_pf_targets *pt =
+    (struct hostname_to_sockaddr_pf_targets *) targets;
+
+    if (pe->retval == 0)
+       *(pt->res) = copy_addrinfo(pe->res);
+    *pt->retval = pe->retval;
+}
+
+/* print_hostname_to_sockaddr_pf - print expected inputs */
+
+static char *print_hostname_to_sockaddr_pf(const MOCK_EXPECT *expect,
+                                                  VSTRING *buf)
+{
+    struct hostname_to_sockaddr_pf_expectation *pe =
+    (struct hostname_to_sockaddr_pf_expectation *) expect;
+
+    vstring_sprintf(buf, "\"%s\", %d, \"%s\", %d, (ptr)",
+                   STR_OR_NULL(pe->hostname), pe->pf,
+                   STR_OR_NULL(pe->service), pe->socktype);
+    return (vstring_str(buf));
+}
+
+/* free_hostname_to_sockaddr_pf - destructor */
+
+static void free_hostname_to_sockaddr_pf(MOCK_EXPECT *expect)
+{
+    struct hostname_to_sockaddr_pf_expectation *pe =
+    (struct hostname_to_sockaddr_pf_expectation *) expect;
+
+    if (pe->hostname)
+       myfree(pe->hostname);
+    if (pe->service)
+       myfree(pe->service);
+    if (pe->retval == 0)
+       freeaddrinfo(pe->res);
+    pmock_expect_free(expect);
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG hostname_to_sockaddr_pf_sig = {
+    "hostname_to_sockaddr_pf",
+    match_hostname_to_sockaddr_pf,
+    assign_hostname_to_sockaddr_pf,
+    print_hostname_to_sockaddr_pf,
+    free_hostname_to_sockaddr_pf,
+};
+
+/* _expect_hostname_to_sockaddr_pf - set up expectation */
+
+void    _expect_hostname_to_sockaddr_pf(const char *file, int line,
+                                            int calls_expected, int retval,
+                                               const char *hostname, int pf,
+                                               const char *service,
+                                               int socktype,
+                                               struct addrinfo *res)
+{
+    struct hostname_to_sockaddr_pf_expectation *pe;
+
+    pe = (struct hostname_to_sockaddr_pf_expectation *)
+       pmock_expect_create(&hostname_to_sockaddr_pf_sig,
+                           file, line, calls_expected, sizeof(*pe));
+    pe->retval = retval;
+    pe->hostname = MYSTRDUP_OR_NULL(hostname);
+    pe->pf = pf;
+    pe->service = MYSTRDUP_OR_NULL(service);
+    pe->socktype = socktype;
+    if (pe->retval == 0)
+       pe->res = copy_addrinfo(res);
+}
+
+/* hostname_to_sockaddr_pf - mock hostname_to_sockaddr_pf */
+
+int     hostname_to_sockaddr_pf(const char *hostname, int pf,
+                                       const char *service, int socktype,
+                                       struct addrinfo **res)
+{
+    struct hostname_to_sockaddr_pf_expectation inputs;
+    struct hostname_to_sockaddr_pf_targets targets;
+    int     retval = EAI_FAIL;
+
+    /*
+     * Bundle the arguments to simplify handling.
+     */
+    inputs.hostname = (char *) hostname;
+    inputs.pf = pf;
+    inputs.service = (char *) service;
+    inputs.socktype = socktype;
+
+    targets.res = res;
+    targets.retval = &retval;
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&hostname_to_sockaddr_pf_sig,
+                             &inputs.mock_expect, (void *) &targets);
+    return (retval);
+}
+
+ /*
+  * Manage hostaddr_to_sockaddr() expectations and responses. We use this
+  * structure for deep copies of expect_hostaddr_to_sockaddr() expected
+  * inputs and prepared responses, and for shallow copies
+  * hostaddr_to_sockaddr() inputs, so that we can reuse the
+  * print_hostaddr_to_sockaddr() helper.
+  */
+struct hostaddr_to_sockaddr_expectation {
+    MOCK_EXPECT mock_expect;           /* generic fields */
+    int     retval;                    /* result value */
+    char   *hostaddr;                  /* inputs */
+    char   *service;
+    int     socktype;
+    struct addrinfo *res;              /* other outputs */
+};
+
+ /*
+  * Pointers to hostaddr_to_sockaddr() outputs.
+  */
+struct hostaddr_to_sockaddr_targets {
+    struct addrinfo **res;
+    int    *retval;
+};
+
+/* match_hostaddr_to_sockaddr - match inputs against expectation */
+
+static int match_hostaddr_to_sockaddr(const MOCK_EXPECT *expect,
+                                             const MOCK_EXPECT *inputs)
+{
+    struct hostaddr_to_sockaddr_expectation *pe =
+    (struct hostaddr_to_sockaddr_expectation *) expect;
+    struct hostaddr_to_sockaddr_expectation *pi =
+    (struct hostaddr_to_sockaddr_expectation *) inputs;
+
+    return (strcmp(STR_OR_NULL(pe->hostaddr),
+                  STR_OR_NULL(pi->hostaddr)) == 0
+           && strcmp(STR_OR_NULL(pe->service),
+                     STR_OR_NULL(pi->service)) == 0
+           && pe->socktype == pi->socktype);
+}
+
+/* assign_hostaddr_to_sockaddr - assign expected output */
+
+static void assign_hostaddr_to_sockaddr(const MOCK_EXPECT *expect,
+                                               void *targets)
+{
+    struct hostaddr_to_sockaddr_expectation *pe =
+    (struct hostaddr_to_sockaddr_expectation *) expect;
+    struct hostaddr_to_sockaddr_targets *pt =
+    (struct hostaddr_to_sockaddr_targets *) targets;
+
+    if (pe->retval == 0)
+       *(pt->res) = copy_addrinfo(pe->res);
+    *pt->retval = pe->retval;
+}
+
+/* print_hostaddr_to_sockaddr - print expected inputs */
+
+static char *print_hostaddr_to_sockaddr(const MOCK_EXPECT *expect,
+                                               VSTRING *buf)
+{
+    struct hostaddr_to_sockaddr_expectation *pe =
+    (struct hostaddr_to_sockaddr_expectation *) expect;
+
+    vstring_sprintf(buf, "\"%s\", \"%s\", %d, (ptr)",
+                   STR_OR_NULL(pe->hostaddr),
+                   STR_OR_NULL(pe->service), pe->socktype);
+    return (vstring_str(buf));
+}
+
+/* free_hostname_to_sockaddr_pf - destructor */
+
+static void free_hostaddr_to_sockaddr(MOCK_EXPECT *expect)
+{
+    struct hostaddr_to_sockaddr_expectation *pe =
+    (struct hostaddr_to_sockaddr_expectation *) expect;
+
+    if (pe->hostaddr)
+       myfree(pe->hostaddr);
+    if (pe->service)
+       myfree(pe->service);
+    if (pe->retval == 0)
+       freeaddrinfo(pe->res);
+    pmock_expect_free(expect);
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG hostaddr_to_sockaddr_sig = {
+    "hostaddr_to_sockaddr",
+    match_hostaddr_to_sockaddr,
+    assign_hostaddr_to_sockaddr,
+    print_hostaddr_to_sockaddr,
+    free_hostaddr_to_sockaddr,
+};
+
+/* _expect_hostaddr_to_sockaddr - set up expectation */
+
+void    _expect_hostaddr_to_sockaddr(const char *file, int line,
+                                            int calls_expected, int retval,
+                                            const char *hostaddr,
+                                         const char *service, int socktype,
+                                            struct addrinfo *res)
+{
+    struct hostaddr_to_sockaddr_expectation *pe;
+
+    pe = (struct hostaddr_to_sockaddr_expectation *)
+       pmock_expect_create(&hostaddr_to_sockaddr_sig,
+                           file, line, calls_expected, sizeof(*pe));
+    pe->retval = retval;
+    pe->hostaddr = MYSTRDUP_OR_NULL(hostaddr);
+    pe->service = MYSTRDUP_OR_NULL(service);
+    pe->socktype = socktype;
+    if (pe->retval == 0)
+       pe->res = copy_addrinfo(res);
+}
+
+/* hostaddr_to_sockaddr - mock hostaddr_to_sockaddr */
+
+int     hostaddr_to_sockaddr(const char *hostaddr, const char *service,
+                                    int socktype, struct addrinfo **res)
+{
+    struct hostaddr_to_sockaddr_expectation inputs;
+    struct hostaddr_to_sockaddr_targets targets;
+    int     retval = EAI_FAIL;
+
+    /*
+     * Bundle the arguments to simplify handling.
+     */
+    inputs.hostaddr = (char *) hostaddr;
+    inputs.service = (char *) service;
+    inputs.socktype = socktype;
+
+    targets.res = res;
+    targets.retval = &retval;
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&hostaddr_to_sockaddr_sig,
+                             &inputs.mock_expect, (void *) &targets);
+    return (retval);
+}
+
+ /*
+  * Manage sockaddr_to_hostaddr() expectations and responses. We use this
+  * structure for deep copies of expect_sockaddr_to_hostaddr() expected
+  * inputs and prepared responses, and for shallow copies of
+  * sockaddr_to_hostaddr() inputs, so that we can reuse the
+  * print_sockaddr_to_hostaddr() helper.
+  */
+struct sockaddr_to_hostaddr_expectation {
+    MOCK_EXPECT mock_expect;           /* generic fields */
+    int     retval;                    /* result value */
+    struct sockaddr_storage sa;                /* inputs */
+    SOCKADDR_SIZE salen;
+    int     socktype;
+    MAI_HOSTADDR_STR *hostaddr;                /* other outputs */
+    MAI_SERVPORT_STR *portnum;
+    MAI_HOSTADDR_STR hostaddr_storage;
+    MAI_SERVPORT_STR portnum_storage;
+};
+
+ /*
+  * Pointers to sockaddr_to_hostaddr() outputs.
+  */
+struct sockaddr_to_hostaddr_targets {
+    MAI_HOSTADDR_STR *hostaddr;
+    MAI_SERVPORT_STR *portnum;
+    int    *retval;
+};
+
+/* match_sockaddr_to_hostaddr - match inputs against expectation */
+
+static int match_sockaddr_to_hostaddr(const MOCK_EXPECT *expect,
+                                             const MOCK_EXPECT *inputs)
+{
+    struct sockaddr_to_hostaddr_expectation *pe =
+    (struct sockaddr_to_hostaddr_expectation *) expect;
+    struct sockaddr_to_hostaddr_expectation *pi =
+    (struct sockaddr_to_hostaddr_expectation *) inputs;
+
+    return (pe->salen == pi->salen
+           && memcmp(&pe->sa, &pi->sa, pe->salen) == 0
+           && pe->socktype == pi->socktype);
+}
+
+/* assign_sockaddr_to_hostaddr - assign expected output */
+
+static void assign_sockaddr_to_hostaddr(const MOCK_EXPECT *expect,
+                                               void *targets)
+{
+    struct sockaddr_to_hostaddr_expectation *pe =
+    (struct sockaddr_to_hostaddr_expectation *) expect;
+    struct sockaddr_to_hostaddr_targets *pt =
+    (struct sockaddr_to_hostaddr_targets *) targets;
+
+    if (pe->retval == 0) {
+       if (pe->hostaddr && pt->hostaddr)
+           *pt->hostaddr = *pe->hostaddr;
+       if (pe->portnum && pt->portnum)
+           *pt->portnum = *pe->portnum;
+    }
+    *pt->retval = pe->retval;
+}
+
+/* print_sockaddr_to_hostaddr - print expected inputs */
+
+static char *print_sockaddr_to_hostaddr(const MOCK_EXPECT *expect,
+                                               VSTRING *buf)
+{
+    struct sockaddr_to_hostaddr_expectation *pe =
+    (struct sockaddr_to_hostaddr_expectation *) expect;
+    VSTRING *sockaddr_buf = vstring_alloc(100);
+
+    vstring_sprintf(buf, "%s, %ld, (ptr), (ptr)",
+                   sockaddr_to_string(sockaddr_buf,
+                                      (struct sockaddr *) &pe->sa,
+                                      pe->salen),
+                   (long) pe->salen);
+    vstring_free(sockaddr_buf);
+    return (vstring_str(buf));
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG sockaddr_to_hostaddr_sig = {
+    "sockaddr_to_hostaddr",
+    match_sockaddr_to_hostaddr,
+    assign_sockaddr_to_hostaddr,
+    print_sockaddr_to_hostaddr,
+    pmock_expect_free,
+};
+
+/* _expect_sockaddr_to_hostaddr - binary address to printable address form */
+
+void    _expect_sockaddr_to_hostaddr(const char *file, int line,
+                                            int calls_expected, int retval,
+                                            const struct sockaddr *sa,
+                                            SOCKADDR_SIZE salen,
+                                            MAI_HOSTADDR_STR *hostaddr,
+                                            MAI_SERVPORT_STR *portnum,
+                                            int socktype)
+{
+    struct sockaddr_to_hostaddr_expectation *pe;
+
+    pe = (struct sockaddr_to_hostaddr_expectation *)
+       pmock_expect_create(&sockaddr_to_hostaddr_sig,
+                           file, line, calls_expected, sizeof(*pe));
+    pe->retval = retval;
+    memcpy((void *) &pe->sa, (void *) sa, salen);
+    pe->salen = salen;
+    if (pe->retval == 0 && hostaddr) {
+       *(pe->hostaddr = &pe->hostaddr_storage) = *hostaddr;
+    } else {
+       pe->hostaddr = 0;
+    }
+    if (pe->retval == 0 && portnum) {
+       *(pe->portnum = &pe->portnum_storage) = *portnum;
+    } else {
+       pe->portnum = 0;
+    }
+    pe->socktype = socktype;
+}
+
+/* sockaddr_to_hostaddr - mock sockaddr_to_hostaddr */
+
+int     sockaddr_to_hostaddr(const struct sockaddr *sa,
+                                    SOCKADDR_SIZE salen,
+                                    MAI_HOSTADDR_STR *hostaddr,
+                                    MAI_SERVPORT_STR *portnum,
+                                    int socktype)
+{
+    struct sockaddr_to_hostaddr_expectation inputs;
+    struct sockaddr_to_hostaddr_targets targets;
+    int     retval = EAI_FAIL;
+
+    /*
+     * Bundle the arguments to simplify handling.
+     */
+    memcpy((void *) &inputs.sa, (void *) sa, salen);
+    inputs.salen = salen;
+    inputs.socktype = socktype;
+
+    targets.hostaddr = hostaddr;
+    targets.portnum = portnum;
+    targets.retval = &retval;
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&sockaddr_to_hostaddr_sig,
+                             &inputs.mock_expect, (void *) &targets);
+    return (retval);
+}
+
+ /*
+  * Manage sockaddr_to_hostname() expectations and responses. We use this
+  * structure for deep copies of expect_sockaddr_to_hostname() expected
+  * inputs and prepared responses, and for shallow copies of
+  * sockaddr_to_hostname() inputs, so that we can reuse the
+  * print_sockaddr_to_hostname() helper.
+  */
+struct sockaddr_to_hostname_expectation {
+    MOCK_EXPECT mock_expect;           /* generic fields */
+    int     retval;                    /* result value */
+    struct sockaddr_storage sa;                /* inputs */
+    SOCKADDR_SIZE salen;
+    int     socktype;
+    MAI_HOSTNAME_STR *hostname;                /* other outputs */
+    MAI_SERVNAME_STR *service;
+    MAI_HOSTNAME_STR hostname_storage;
+    MAI_SERVNAME_STR service_storage;
+};
+
+ /*
+  * Pointers to sockaddr_to_hostname() outputs.
+  */
+struct sockaddr_to_hostname_targets {
+    MAI_HOSTNAME_STR *hostname;
+    MAI_SERVNAME_STR *service;
+    int    *retval;
+};
+
+/* match_sockaddr_to_hostname - match inputs against expectation */
+
+static int match_sockaddr_to_hostname(const MOCK_EXPECT *expect,
+                                             const MOCK_EXPECT *inputs)
+{
+    struct sockaddr_to_hostname_expectation *pe =
+    (struct sockaddr_to_hostname_expectation *) expect;
+    struct sockaddr_to_hostname_expectation *pi =
+    (struct sockaddr_to_hostname_expectation *) inputs;
+
+    return (pe->salen == pi->salen
+           && memcmp(&pe->sa, &pi->sa, pe->salen) == 0
+           && pe->socktype == pi->socktype);
+}
+
+/* assign_sockaddr_to_hostname - assign expected output */
+
+static void assign_sockaddr_to_hostname(const MOCK_EXPECT *expect,
+                                               void *targets)
+{
+    struct sockaddr_to_hostname_expectation *pe =
+    (struct sockaddr_to_hostname_expectation *) expect;
+    struct sockaddr_to_hostname_targets *pt =
+    (struct sockaddr_to_hostname_targets *) targets;
+
+    if (pe->retval == 0) {
+       if (pe->hostname && pt->hostname)
+           *pt->hostname = *pe->hostname;
+       if (pe->service && pt->service)
+           *pt->service = *pe->service;
+    }
+    *pt->retval = pe->retval;
+}
+
+/* print_sockaddr_to_hostname - print expected inputs */
+
+static char *print_sockaddr_to_hostname(const MOCK_EXPECT *expect,
+                                               VSTRING *buf)
+{
+    struct sockaddr_to_hostname_expectation *pe =
+    (struct sockaddr_to_hostname_expectation *) expect;
+    VSTRING *sockaddr_buf = vstring_alloc(100);
+
+    vstring_sprintf(buf, "%s, %ld, (ptr), (ptr)",
+                   sockaddr_to_string(sockaddr_buf,
+                                      (struct sockaddr *) &pe->sa,
+                                      pe->salen),
+                   (long) pe->salen);
+    vstring_free(sockaddr_buf);
+    return (vstring_str(buf));
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG sockaddr_to_hostname_sig = {
+    "sockaddr_to_hostname",
+    match_sockaddr_to_hostname,
+    assign_sockaddr_to_hostname,
+    print_sockaddr_to_hostname,
+    pmock_expect_free,
+};
+
+/* _expect_sockaddr_to_hostname - set up expectations */
+
+void    _expect_sockaddr_to_hostname(const char *file, int line,
+                                            int calls_expected, int retval,
+                                            const struct sockaddr *sa,
+                                            SOCKADDR_SIZE salen,
+                                            MAI_HOSTNAME_STR *hostname,
+                                            MAI_SERVNAME_STR *service,
+                                            int socktype)
+{
+    struct sockaddr_to_hostname_expectation *pe;
+
+    pe = (struct sockaddr_to_hostname_expectation *)
+       pmock_expect_create(&sockaddr_to_hostname_sig,
+                           file, line, calls_expected, sizeof(*pe));
+    pe->retval = retval;
+    memcpy((void *) &pe->sa, (void *) sa, salen);
+    pe->salen = salen;
+    if (retval == 0 && hostname) {
+       *(pe->hostname = &pe->hostname_storage) = *hostname;
+    } else {
+       pe->hostname = 0;
+    }
+    if (retval == 0 && service) {
+       *(pe->service = &pe->service_storage) = *service;
+    } else {
+       pe->service = 0;
+    }
+    pe->socktype = socktype;
+}
+
+/* sockaddr_to_hostname - mock sockaddr_to_hostname */
+
+int     sockaddr_to_hostname(const struct sockaddr *sa,
+                                    SOCKADDR_SIZE salen,
+                                    MAI_HOSTNAME_STR *hostname,
+                                    MAI_SERVNAME_STR *service,
+                                    int socktype)
+{
+    struct sockaddr_to_hostname_expectation inputs;
+    struct sockaddr_to_hostname_targets targets;
+    int     retval = EAI_FAIL;
+
+    /*
+     * Bundle the arguments to simplify handling.
+     */
+    memcpy((void *) &inputs.sa, (void *) sa, salen);
+    inputs.salen = salen;
+    inputs.socktype = socktype;
+
+    targets.hostname = hostname;
+    targets.service = service;
+    targets.retval = &retval;
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&sockaddr_to_hostname_sig,
+                             &inputs.mock_expect, (void *) &targets);
+    return (retval);
+}
diff --git a/postfix/src/testing/mock_myaddrinfo.h b/postfix/src/testing/mock_myaddrinfo.h
new file mode 100644 (file)
index 0000000..6f2461d
--- /dev/null
@@ -0,0 +1,76 @@
+#ifndef _MOCK_MYADDRINFO_H_INCLUDED_
+#define _MOCK_MYADDRINFO_H_INCLUDED_
+
+/*++
+/* NAME
+/*     mock_myaddrinfo 3h
+/* SUMMARY
+/*     myaddrinfo mock for hermetic tests
+/* SYNOPSIS
+/*     #include <mock_myaddrinfo.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * Utility library.
+  */
+#include <myaddrinfo.h>
+
+ /*
+  * Test library.
+  */
+#include <addrinfo_to_string.h>
+#include <make_addr.h>
+#include <match_addr.h>
+#include <match_basic.h>
+#include <ptest.h>
+
+ /*
+  * Manage expectations and responses. Capture the source file name and line
+  * number for better diagnostics.
+  */
+#define expect_hostname_to_sockaddr_pf(...) \
+       _expect_hostname_to_sockaddr_pf(__FILE__, __LINE__, __VA_ARGS__)
+#define expect_hostaddr_to_sockaddr(...) \
+       _expect_hostaddr_to_sockaddr(__FILE__, __LINE__, __VA_ARGS__)
+#define expect_sockaddr_to_hostaddr(...) \
+       _expect_sockaddr_to_hostaddr(__FILE__, __LINE__, __VA_ARGS__)
+#define expect_sockaddr_to_hostname(...) \
+       _expect_sockaddr_to_hostname(__FILE__, __LINE__, __VA_ARGS__)
+
+extern void _expect_hostname_to_sockaddr_pf(const char *, int, int, int,
+                                           const char *, int, const char *,
+                                                   int, struct addrinfo *);
+extern void _expect_hostaddr_to_sockaddr(const char *, int, int, int,
+                                                const char *, const char *,
+                                                int, struct addrinfo *);
+extern void _expect_sockaddr_to_hostaddr(const char *, int, int, int,
+                                                const struct sockaddr *,
+                                                SOCKADDR_SIZE,
+                                                MAI_HOSTADDR_STR *,
+                                                MAI_SERVPORT_STR *, int);
+extern void _expect_sockaddr_to_hostname(const char *, int, int, int,
+                                                const struct sockaddr *,
+                                                SOCKADDR_SIZE,
+                                                MAI_HOSTNAME_STR *,
+                                                MAI_SERVNAME_STR *, int);
+
+#define expect_hostname_to_sockaddr(count, ret, host, serv, sock, res) \
+       expect_hostname_to_sockaddr_pf((count), (ret), (host), PF_UNSPEC, \
+                                       (serv), (sock), (res))
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/mock_myaddrinfo_test.c b/postfix/src/testing/mock_myaddrinfo_test.c
new file mode 100644 (file)
index 0000000..4d41310
--- /dev/null
@@ -0,0 +1,343 @@
+ /*
+  * Test program to exercise mocks including logging. See comments in
+  * ptest_main.h and pmock_expect_test.c for a documented example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <mock_myaddrinfo.h>
+#include <pmock_expect.h>
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+static void test_hostname_to_sockaddr_success(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    struct addrinfo hints;
+    struct addrinfo *got_addrinfo;
+    struct addrinfo *want_addrinfo;
+    int     got_st, want_st = 0;
+
+    /*
+     * Set up expectations.
+     */
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = PF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+    want_addrinfo = make_addrinfo(&hints, "localhost", "127.0.0.1", 25);
+    expect_hostname_to_sockaddr_pf(1, want_st, "localhost", PF_UNSPEC, "smtp",
+                                  SOCK_STREAM, want_addrinfo);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = hostname_to_sockaddr_pf("localhost", PF_UNSPEC, "smtp",
+                                    SOCK_STREAM, &got_addrinfo);
+    if (got_st != want_st) {
+       ptest_error(t, "hostname_to_sockaddr: got %d, want %d", got_st, want_st);
+    } else if (eq_addrinfo(t, "hostname_to_sockaddr", got_addrinfo,
+                          want_addrinfo) == 0) {
+       ptest_error(t, "hostname_to_sockaddr: unexpected result mismatch");
+    }
+
+    /*
+     * Clean up.
+     */
+    freeaddrinfo(want_addrinfo);
+    if (got_addrinfo)
+       freeaddrinfo(got_addrinfo);
+}
+
+static void test_hostname_to_sockaddr_failure(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    struct addrinfo *got_addrinfo = 0;
+    struct addrinfo *want_addrinfo = 0;
+    int     got_st, want_st = EAI_FAIL;
+
+    /*
+     * The missing expectation is intentional. Do not count this as an error.
+     */
+    expect_ptest_error(t, "unexpected call: "
+           "hostname_to_sockaddr_pf(\"notexist\", 0, \"smtp\", 1, (ptr))");
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = hostname_to_sockaddr_pf("notexist", PF_UNSPEC, "smtp",
+                                    SOCK_STREAM, &got_addrinfo);
+    if (got_st != want_st) {
+       ptest_error(t, "hostname_to_sockaddr: got %d, want %d", got_st, want_st);
+    } else if (eq_addrinfo(t, "hostname_to_sockaddr", got_addrinfo,
+                          want_addrinfo) == 0) {
+       ptest_error(t, "hostname_to_sockaddr: unexpected result mismatch");
+    }
+
+    /*
+     * Clean up.
+     */
+    if (got_addrinfo)
+       freeaddrinfo(got_addrinfo);
+}
+
+static void test_hostaddr_to_sockaddr_success(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    struct addrinfo hints;
+    struct addrinfo *got_addrinfo = 0;
+    struct addrinfo *want_addrinfo;
+    int     got_st, want_st = 0;
+
+    /*
+     * Set up expectations.
+     */
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = PF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+    want_addrinfo = make_addrinfo(&hints, (char *) 0, "127.0.0.1", 25);
+    expect_hostaddr_to_sockaddr(1, want_st, "127.0.0.1", "25", SOCK_STREAM,
+                               want_addrinfo);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = hostaddr_to_sockaddr("127.0.0.1", "25", SOCK_STREAM,
+                                 &got_addrinfo);
+    if (got_st != want_st) {
+       ptest_error(t, "hostaddr_to_sockaddr: got %d, want %d", got_st, want_st);
+    } else if (eq_addrinfo(t, "hostaddr_to_sockaddr", got_addrinfo,
+                          want_addrinfo) == 0) {
+       ptest_error(t, "hostname_to_sockaddr: unexpected result mismatch");
+    }
+
+    /*
+     * Clean up.
+     */
+    freeaddrinfo(want_addrinfo);
+    if (got_addrinfo)
+       freeaddrinfo(got_addrinfo);
+}
+
+static void test_hostaddr_to_sockaddr_failure(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    struct addrinfo *got_addrinfo = 0;
+    struct addrinfo *want_addrinfo = 0;
+    int     got_st, want_st = EAI_FAIL;
+
+    /*
+     * The missing expectation is intentional. Do not count this as an error.
+     */
+    expect_ptest_error(t, "unexpected call: "
+                      "hostaddr_to_sockaddr(\"127.0.0.1\", \"25\", "
+                      "1, (ptr))");
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = hostaddr_to_sockaddr("127.0.0.1", "25", SOCK_STREAM,
+                                 &got_addrinfo);
+    if (got_st != want_st) {
+       ptest_error(t, "hostaddr_to_sockaddr: got %d, want %d", got_st, want_st);
+    } else if (eq_addrinfo(t, "hostaddr_to_sockaddr", got_addrinfo,
+                          want_addrinfo) == 0) {
+       ptest_error(t, "hostname_to_sockaddr: unexpected result mismatch");
+    }
+
+    /*
+     * Clean up.
+     */
+    if (got_addrinfo)
+       freeaddrinfo(got_addrinfo);
+}
+
+static void test_sockaddr_to_hostaddr_success(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    struct sockaddr *sa;
+    SOCKADDR_SIZE salen;
+    int     got_st, want_st = 0;
+    MAI_HOSTADDR_STR want_hostaddr;
+    MAI_SERVPORT_STR want_portnum;
+    MAI_HOSTADDR_STR got_hostaddr;
+    MAI_SERVPORT_STR got_portnum;
+
+    /*
+     * Set up expectations.
+     */
+    sa = make_sockaddr(AF_INET, "127.0.0.1", 25);
+    salen = sizeof(struct sockaddr_in);
+    strncpy(want_hostaddr.buf, "127.0.0.1", sizeof(want_hostaddr.buf));
+    strncpy(want_portnum.buf, "25", sizeof(want_portnum.buf));
+    expect_sockaddr_to_hostaddr(1, want_st, sa, salen,
+                               &want_hostaddr, &want_portnum, 0);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = sockaddr_to_hostaddr(sa, salen, &got_hostaddr, &got_portnum, 0);
+    if (got_st != want_st) {
+       ptest_error(t, "sockaddr_to_hostaddr ret: got %d, want %d", got_st, want_st);
+    } else if (strcmp(got_hostaddr.buf, want_hostaddr.buf) != 0) {
+       ptest_error(t, "sockaddr_to_hostaddr hostaddr.buf: got %s, want %s",
+                   got_hostaddr.buf, want_hostaddr.buf);
+    } else if (strcmp(got_portnum.buf, want_portnum.buf) != 0) {
+       ptest_error(t, "sockaddr_to_hostaddr portnum.buf: got %s, want %s",
+                   got_portnum.buf, want_portnum.buf);
+    }
+
+    /*
+     * Clean up.
+     */
+    free_sockaddr(sa);
+}
+
+static void test_sockaddr_to_hostaddr_failure(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    struct sockaddr *sa;
+    SOCKADDR_SIZE salen;
+    int     got_st, want_st = EAI_FAIL;
+
+    /*
+     * The missing expectation is intentional. Do not count this as an error.
+     */
+    expect_ptest_error(t, "unexpected call: "
+                      "sockaddr_to_hostaddr({AF_INET, 127.0.0.1, 25}, 16, "
+                      "(ptr), (ptr))");
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    sa = make_sockaddr(AF_INET, "127.0.0.1", 25);
+    salen = sizeof(struct sockaddr_in);
+    got_st = sockaddr_to_hostaddr(sa, salen, (MAI_HOSTADDR_STR *) 0,
+                                 (MAI_SERVPORT_STR *) 0, 0);
+    if (got_st != want_st)
+       ptest_error(t, "sockaddr_to_hostaddr ret: got %d, want %d", got_st, want_st);
+
+    /*
+     * Clean up.
+     */
+    free_sockaddr(sa);
+}
+
+static void test_sockaddr_to_hostname_success(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    struct sockaddr *sa;
+    SOCKADDR_SIZE salen;
+    int     got_st, want_st = 0;
+    MAI_HOSTNAME_STR want_hostname;
+    MAI_SERVNAME_STR want_service;
+    MAI_HOSTNAME_STR got_hostname;
+    MAI_SERVNAME_STR got_service;
+
+    /*
+     * Set up expectations.
+     */
+    sa = make_sockaddr(AF_INET, "127.0.0.1", 25);
+    salen = sizeof(struct sockaddr_in);
+    strncpy(want_hostname.buf, "localhost", sizeof(want_hostname.buf));
+    strncpy(want_service.buf, "smtp", sizeof(want_service.buf));
+    expect_sockaddr_to_hostname(1, want_st, sa, salen, &want_hostname, &want_service, 0);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_st = sockaddr_to_hostname(sa, salen, &got_hostname, &got_service, 0);
+    if (got_st != want_st) {
+       ptest_error(t, "sockaddr_to_hostname ret: got %d, want %d", got_st, want_st);
+    } else if (strcmp(got_hostname.buf, want_hostname.buf) != 0) {
+       ptest_error(t, "sockaddr_to_hostname hostname.buf: got %s, want %s",
+                   got_hostname.buf, want_hostname.buf);
+    } else if (strcmp(got_service.buf, want_service.buf) != 0) {
+       ptest_error(t, "sockaddr_to_hostname service.buf: got %s, want %s",
+                   got_service.buf, want_service.buf);
+    }
+
+    /*
+     * Clean up.
+     */
+    free_sockaddr(sa);
+}
+
+static void test_sockaddr_to_hostname_failure(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    struct sockaddr *sa;
+    SOCKADDR_SIZE salen;
+    int     got_st, want_st = EAI_FAIL;
+
+    /*
+     * The missing expectation is intentional. Do not count this as an error.
+     */
+    expect_ptest_error(t, "unexpected call: "
+                      "sockaddr_to_hostname({AF_INET, 127.0.0.1, 0}, 16, "
+                      "(ptr), (ptr))");
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    sa = make_sockaddr(AF_INET, "127.0.0.1", 65536);
+    salen = sizeof(struct sockaddr_in);
+    got_st = sockaddr_to_hostname(sa, salen, (MAI_HOSTNAME_STR *) 0,
+                                 (MAI_SERVNAME_STR *) 0, 0);
+    if (got_st != want_st)
+       ptest_error(t, "sockaddr_to_hostname ret: got %d, want %d", got_st, want_st);
+
+    /*
+     * Clean up.
+     */
+    free_sockaddr(sa);
+}
+
+ /*
+  * Test cases. The "success" tests exercise the expectation match and apply
+  * helpers, and "failure" tests exercise the print helpers. All tests
+  * exercise the expectation free helpers.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+       "hostname_to_sockaddr success", test_hostname_to_sockaddr_success,
+    },
+    {
+       "hostname_to_sockaddr failure", test_hostname_to_sockaddr_failure,
+    },
+    {
+       "hostaddr_to_sockaddr success", test_hostaddr_to_sockaddr_success,
+    },
+    {
+       "hostaddr_to_sockaddr failure", test_hostaddr_to_sockaddr_failure,
+    },
+    {
+       "sockaddr_to_hostaddr success", test_sockaddr_to_hostaddr_success,
+    },
+    {
+       "sockaddr_to_hostaddr failure", test_sockaddr_to_hostaddr_failure,
+    },
+    {
+       "sockaddr_to_hostname success", test_sockaddr_to_hostname_success,
+    },
+    {
+       "sockaddr_to_hostname failure", test_sockaddr_to_hostname_failure,
+    },
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/testing/mock_servent.c b/postfix/src/testing/mock_servent.c
new file mode 100644 (file)
index 0000000..bd3d4c0
--- /dev/null
@@ -0,0 +1,474 @@
+/*++
+/* NAME
+/*     mock_servent 3
+/* SUMMARY
+/*     getservbyname mock for hermetic tests
+/* SYNOPSIS
+/*     #include <mock_servent.h>
+/*
+/*     struct servent *getservbyname(
+/*     const char *name,
+/*     const char *proto)
+/*
+/*     void    setservent(
+/*     int     stayopen)
+/*
+/*     void    endservent(void)
+/* EXPECTATION SETUP
+/*     void    expect_getservbyname(
+/*     int     calls_expected,
+/*     int     retval,
+/*     const char *name,
+/*     const char *proto)
+/*
+/*     void    expect_setservent(
+/*     int     calls_expected,
+/*     int     stayopen)
+/*
+/*     void    expect_endservent(
+/*     int     calls_expected)
+/*
+/*     struct servent *make_servent(
+/*     const char *name,
+/*     int     port,
+/*     const char *proto)
+/*
+/*     void    free_servent(
+/*     struct servent *ent)
+/* MATCHERS
+/*     int     eq_servent(
+/*     PTEST_CTX *t,
+/*     const char *what,
+/*     struct servent *got,
+/*     struct servent *want)
+/* DESCRIPTION
+/*     This module implements a partial mock getservent(3) module
+/*     that produces prepared outputs in response to expected
+/*     inputs. This supports hermetic tests, i.e. tests that do
+/*     not depend on host configuration or on network access.
+/*
+/*     expect_getservbyname() makes deep copies of its input
+/*     arguments. The retval argument specifies a prepared result
+/*     value. The calls_expected argument specifies the expected
+/*     number of calls (zero means one or more calls, not: zero
+/*     calls).
+/*
+/*     Note: getservbyname() maintains ownership of the struct
+/*     servent result that is returned by the mock getservbyname()
+/*     function. This is for consistency with the real getservbyname()
+/*     which also maintains ownership of the result.
+/*
+/*     expect_setservent() copies its stayopen argument. The
+/*     calls_expected argument specifies the expected number of
+/*     calls (zero means one or more calls, not: zero calls).
+/*
+/*     expect_endservent() has no expected inputs. The calls_expected
+/*     argument specifies the expected number of calls (zero means
+/*     one or more calls, not: zero calls).
+/*
+/*     make_servent() returns a pointer to a minimal struct servent
+/*     instance. Use free_servent() to destroy it.
+/*
+/*     eq_servent() is a predicate that compares its arguments for
+/*     equality. The what argument is used in logging when the
+/*     inputs differ. Specify a null test context for silent
+/*     operation.
+/* DIAGNOSTICS
+/*     If a mock is called unexpectedly (the call arguments do not
+/*     match an expectation, or more calls are made than expected),
+/*     a warning is logged, and the test will be flagged as failed.
+/*     For now the mock returns an error result to the caller.
+/*     TODO: consider aborting the test.
+/* SEE ALSO
+/*     dns_lookup(3), domain name service lookup
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <wrap_netdb.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <mymalloc.h>
+#include <msg.h>
+
+ /*
+  * Test library.
+  */
+#include <mock_servent.h>
+#include <pmock_expect.h>
+
+ /*
+  * Helpers.
+  */
+#define MYSTRDUP_OR_NULL(x)             ((x) ? mystrdup(x) : 0)
+#define STR_OR_NULL(s)                  ((s) ? (s) : "(null)")
+
+#define STR(x)                         vstring_str(x)
+
+/* copy_servent - deep copy */
+
+static struct servent *copy_servent(struct servent * src)
+{
+    struct servent *dst;
+
+    dst = (struct servent *) mymalloc(sizeof(*dst));
+    dst->s_name = MYSTRDUP_OR_NULL(src->s_name);
+    dst->s_aliases = mymalloc(sizeof(*dst->s_aliases));
+    dst->s_aliases[0] = 0;
+    dst->s_port = src->s_port;
+    dst->s_proto = MYSTRDUP_OR_NULL(src->s_proto);
+    return (dst);
+}
+
+/* make_servent - create mock servent instance */
+
+struct servent *make_servent(const char *name, int port, const char *proto)
+{
+    struct servent *dst;
+
+    dst = (struct servent *) mymalloc(sizeof(*dst));
+    dst->s_name = MYSTRDUP_OR_NULL(name);
+    dst->s_aliases = mymalloc(sizeof(*dst->s_aliases));
+    dst->s_aliases[0] = 0;
+    dst->s_port = htons(port);
+    dst->s_proto = MYSTRDUP_OR_NULL(proto);
+    return (dst);
+}
+
+/* free_servent - destructor */
+
+void    free_servent(struct servent * ent)
+{
+    if (ent->s_name)
+       myfree(ent->s_name);
+    if (ent->s_aliases)
+       myfree((char *) ent->s_aliases);
+    if (ent->s_proto)
+       myfree(ent->s_proto);
+    myfree(ent);
+}
+
+/* eq_aliases - equality predicate */
+
+static int eq_aliases(PTEST_CTX *t, const char *file, int line, const char *what,
+                             char **got, char **want)
+{
+    if (got[0] == 0 && want[0] == 0)
+       return (1);
+    if (got[0] == 0 || want[0] == 0) {
+       if (t)
+           ptest_error(t, "%s: got alias %s, want %s",
+                       what, got[0] ? got[0] : "(null)",
+                       want[0] ? want[0] : "(null)");
+       return (0);
+    }
+    if (strcmp(got[0], want[0]) != 0) {
+       if (t)
+           ptest_error(t, "%s: got alias '%s', want '%s'",
+                       what, got[0], want[0]);
+       return (0);
+    }
+    return (1);
+}
+
+/* _eq_servent - equality predicate */
+
+int     _eq_servent(const char *file, int line, PTEST_CTX *t,
+                           const char *what,
+                           struct servent * got, struct servent * want)
+{
+    if (got == 0 && want == 0)
+       return (1);
+    if (got == 0 || want == 0) {
+       if (t)
+           ptest_error(t, "%s: got %s, want %s",
+                       what, got ? "(struct servent *)" : "(null)",
+                       want ? "(struct servent *)" : "(null)");
+       return (0);
+    }
+    if (strcmp(got->s_name, want->s_name) != 0) {
+       if (t)
+           ptest_error(t, "%s: got name '%s', want '%s'",
+                       what, got->s_name, want->s_name);
+       return (0);
+    }
+    if (!eq_aliases(t, file, line, what, got->s_aliases, want->s_aliases))
+       return (0);
+    if (got->s_port != want->s_port) {
+       if (t)
+           ptest_error(t, "%s: got port %d, want %d",
+                       what, ntohs(got->s_port), ntohs(want->s_port));
+       return (0);
+    }
+    if (strcmp(got->s_proto, want->s_proto) != 0) {
+       if (t)
+           ptest_error(t, "%s: got proto '%s', want '%s'",
+                       what, got->s_proto, want->s_proto);
+       return (0);
+    }
+    return (1);
+}
+
+ /*
+  * Manage getservbyname() expectations and responses. We use this structure
+  * for deep copies of expect_getservbyname() expected inputs and prepared
+  * responses, and for shallow copies of getservbyname() inputs.
+  */
+struct getservbyname_expectation {
+    MOCK_EXPECT mock_expect;           /* generic fields */
+    char   *name;                      /* inputs */
+    char   *proto;
+    struct servent *retval;            /* outputs */
+};
+
+ /*
+  * Pointers to getservbyname() outputs.
+  */
+struct getservbyname_targets {
+    struct servent **retval;
+};
+
+/* match_getservbyname - match inputs against expectation */
+
+static int match_getservbyname(const MOCK_EXPECT *expect,
+                                      const MOCK_EXPECT *inputs)
+{
+    struct getservbyname_expectation *pe =
+    (struct getservbyname_expectation *) expect;
+    struct getservbyname_expectation *pi =
+    (struct getservbyname_expectation *) inputs;
+
+    return (strcmp(STR_OR_NULL(pe->name),
+                  STR_OR_NULL(pi->name)) == 0
+           && strcmp(STR_OR_NULL(pe->proto),
+                     STR_OR_NULL(pi->proto)) == 0);
+}
+
+/* assign_getservbyname - assign expected output */
+
+static void assign_getservbyname(const MOCK_EXPECT *expect,
+                                        void *targets)
+{
+    struct getservbyname_expectation *pe =
+    (struct getservbyname_expectation *) expect;
+    struct getservbyname_targets *pt =
+    (struct getservbyname_targets *) targets;
+
+    /* Not a deep copy. */
+    *pt->retval = pe->retval;
+}
+
+/* print_getservbyname - print expected inputs */
+
+static char *print_getservbyname(const MOCK_EXPECT *expect,
+                                        VSTRING *buf)
+{
+    struct getservbyname_expectation *pe =
+    (struct getservbyname_expectation *) expect;
+
+    vstring_sprintf(buf, "\"%s\", \"%s\"",
+                   STR_OR_NULL(pe->name), STR_OR_NULL(pe->proto));
+    return (STR(buf));
+}
+
+/* free_getservbyname - destructor */
+
+static void free_getservbyname(MOCK_EXPECT *expect)
+{
+    struct getservbyname_expectation *pe =
+    (struct getservbyname_expectation *) expect;
+
+    if (pe->name)
+       myfree(pe->name);
+    if (pe->proto)
+       myfree(pe->proto);
+    if (pe->retval)
+       free_servent(pe->retval);
+    pmock_expect_free(expect);
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG getservbyname_sig = {
+    "getservbyname",
+    match_getservbyname,
+    assign_getservbyname,
+    print_getservbyname,
+    free_getservbyname,
+};
+
+/* _expect_getservbyname - set up expectation */
+
+void    _expect_getservbyname(const char *file, int line,
+                               int calls_expected, struct servent * retval,
+                                     const char *name, const char *proto)
+{
+    struct getservbyname_expectation *pe;
+
+    pe = (struct getservbyname_expectation *)
+       pmock_expect_create(&getservbyname_sig,
+                           file, line, calls_expected, sizeof(*pe));
+    /* Inputs. */
+    pe->name = MYSTRDUP_OR_NULL(name);
+    pe->proto = MYSTRDUP_OR_NULL(proto);
+    /* Outputs. */
+    pe->retval = retval ? copy_servent(retval) : retval;
+}
+
+/* getservbyname - answer the call with prepared responses */
+
+struct servent *getservbyname(const char *name, const char *proto)
+{
+    struct getservbyname_expectation inputs;
+    struct getservbyname_targets targets;
+    struct servent *retval = 0;
+
+    /*
+     * Bundle the arguments to simplify handling.
+     */
+    inputs.name = (char *) name;
+    inputs.proto = (char *) proto;
+
+    targets.retval = &retval;
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&getservbyname_sig,
+                             &inputs.mock_expect, (void *) &targets);
+    return (retval);
+}
+
+ /*
+  * Manage setservent() expectations. This function has no outputs.
+  */
+struct setservent_expectation {
+    MOCK_EXPECT mock_expect;           /* generic fields */
+    int     stayopen;                  /* input */
+};
+
+static int match_setservent(const MOCK_EXPECT *expect,
+                                   const MOCK_EXPECT *inputs)
+{
+    struct setservent_expectation *pe =
+    (struct setservent_expectation *) expect;
+    struct setservent_expectation *pi =
+    (struct setservent_expectation *) inputs;
+
+    return (pe->stayopen == pi->stayopen);
+}
+
+/* print_setservent - print expected inputs */
+
+static char *print_setservent(const MOCK_EXPECT *expect,
+                                     VSTRING *buf)
+{
+    struct setservent_expectation *pe =
+    (struct setservent_expectation *) expect;
+
+    vstring_sprintf(buf, "%d", pe->stayopen);
+    return (STR(buf));
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG setservent_sig = {
+    "setservent",
+    match_setservent,
+    0,                                 /* no outputs to assign */
+    print_setservent,
+    pmock_expect_free,
+};
+
+/* _expect_setservent - set up expectation */
+
+void    _expect_setservent(const char *file, int line,
+                                  int calls_expected, int stayopen)
+{
+    struct setservent_expectation *pe;
+
+    pe = (struct setservent_expectation *)
+       pmock_expect_create(&setservent_sig,
+                           file, line, calls_expected, sizeof(*pe));
+    /* Inputs. */
+    pe->stayopen = stayopen;
+}
+
+/* setservent - answer the call */
+
+void    setservent(int stayopen)
+{
+    struct setservent_expectation inputs;
+
+    /*
+     * Bundle the arguments to simplify handling.
+     */
+    inputs.stayopen = stayopen;
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&setservent_sig,
+                             &inputs.mock_expect, (void *) 0);
+}
+
+ /*
+  * Manage endservent() expectations. This function has no arguments (all
+  * calls will match), and no outputs.
+  */
+
+/* print_endservent - print expected inputs */
+
+static char *print_endservent(const MOCK_EXPECT *expect,
+                                     VSTRING *buf)
+{
+    VSTRING_RESET(buf);
+    VSTRING_TERMINATE(buf);
+    return (STR(buf));
+}
+
+ /*
+  * The mock name and its helper callbacks in one place.
+  */
+static const MOCK_APPL_SIG endservent_sig = {
+    "endservent",
+    0,                                 /* no inputs to match */
+    0,                                 /* no outputs to assign */
+    print_endservent,
+    pmock_expect_free,
+};
+
+/* _expect_endservent - set up expectation */
+
+void    _expect_endservent(const char *file, int line, int calls_expected)
+{
+    (void) pmock_expect_create(&endservent_sig,
+                          file, line, calls_expected, sizeof(MOCK_EXPECT));
+}
+
+/* endservent - return no answer */
+
+void    endservent(void)
+{
+
+    /*
+     * TODO: bail out if there was no match?
+     */
+    (void) pmock_expect_apply(&endservent_sig, (MOCK_EXPECT *) 0, (void *) 0);
+}
diff --git a/postfix/src/testing/mock_servent.h b/postfix/src/testing/mock_servent.h
new file mode 100644 (file)
index 0000000..4860c6d
--- /dev/null
@@ -0,0 +1,76 @@
+#ifndef _MOCK_SERVENT_H_INCLUDED_
+#define _MOCK_SERVENT_H_INCLUDED_
+
+/*++
+/* NAME
+/*     mock_servent 3h
+/* SUMMARY
+/*     getservbyname mock for hermetic tests
+/* SYNOPSIS
+/*     #include <mock_servent.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <wrap_netdb.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * Manage expectations and responses. Capture the source file name and line
+  * number for better diagnostics.
+  */
+#define expect_getservbyname(...) \
+       _expect_getservbyname(__FILE__, __LINE__, __VA_ARGS__)
+
+extern void _expect_getservbyname(const char *, int, int, struct servent *,
+                                         const char *, const char *);
+
+#define expect_setservent(...) \
+       _expect_setservent(__FILE__, __LINE__, __VA_ARGS__)
+
+extern void _expect_setservent(const char *, int, int, int);
+
+#define expect_endservent(...) \
+       _expect_endservent(__FILE__, __LINE__, __VA_ARGS__)
+
+extern void _expect_endservent(const char *, int, int);
+
+ /*
+  * Matcher predicates. Capture the source file name and line number for
+  * better diagnostics.
+  */
+#define eq_servent(...) _eq_servent(__FILE__, __LINE__, __VA_ARGS__)
+
+extern int _eq_servent(const char *, int,PTEST_CTX *, 
+                              const char *, struct servent *,
+                              struct servent *);
+
+ /*
+  * Helper to create test data.
+  */
+extern struct servent *make_servent(const char *, int, const char *);
+
+extern void free_servent(struct servent *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/mock_servent_test.c b/postfix/src/testing/mock_servent_test.c
new file mode 100644 (file)
index 0000000..29ffde8
--- /dev/null
@@ -0,0 +1,241 @@
+ /*
+  * Test program to exercise mocks including logging. See pmock_expect_test.c
+  * and ptest_main.h for a documented example.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <wrap_netdb.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+
+ /*
+  * Test library.
+  */
+#include <mock_servent.h>
+#include <pmock_expect.h>
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+static void test_getservbyname_success(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    struct servent *got_ent = 0, *want_ent;
+
+    /*
+     * Set up expectations.
+     */
+    want_ent = make_servent("smtp", 25, "tcp");
+    expect_getservbyname(1, want_ent, "smtp", "tcp");
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_ent = getservbyname("smtp", "tcp");
+    if (eq_servent(t, "getservbyname", got_ent, want_ent) == 0)
+       ptest_error(t, "getservbyname: unexpected result mismatch");
+
+    /*
+     * Clean up.
+     */
+    free_servent(want_ent);
+}
+
+static void test_getservbyname_notexist(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    struct servent *got_ent, *want_ent = 0;
+
+    /*
+     * Set up expectations.
+     */
+    expect_getservbyname(1, want_ent, "noservice", "noproto");
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    got_ent = getservbyname("noservice", "noproto");
+    if (eq_servent(t, "getservbyname", got_ent, want_ent) == 0)
+       ptest_error(t, "getservbyname: unexpected result mismatch");
+
+    /*
+     * Clean up.
+     */
+    if (got_ent)
+       free_servent(got_ent);
+}
+
+static void test_getservbyname_unused(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    struct servent *want_ent = 0;
+
+    /*
+     * Create an expectation, without calling it. I does not matter what the
+     * expectation is, so we use the one from test_getservbyname_notexist().
+     */
+    expect_getservbyname(1, want_ent, "noservice", "noproto");
+
+    /*
+     * We expect that there will be a 'missing call' error. If there is none
+     * then the test will fail.
+     */
+    expect_ptest_error(t, "got 0 calls for getservbyname(\"noservice\", "
+                      "\"noproto\"), want 1");
+}
+
+static void test_eq_servent_differ(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+
+    struct servent *got_ent, *want_ent = 0;
+    struct probes {
+       const char *name;
+       int     port;
+       const char *proto;
+       const char *want_error;
+    };
+    static struct probes probes[4] = {
+       {"abc", 42, "def"},
+       {"cba", 42, "def", "eq_servent: got name 'cba', want 'abc'"},
+       {"abc", 24, "def", "eq_servent: got port 24, want 42"},
+       {"abc", 42, "fed", "eq_servent: got proto 'fed', want 'def'"},
+    };
+    struct probes *pp;
+    int     want_eq;
+
+    pp = probes;
+    want_ent = make_servent(pp->name, pp->port, pp->proto);
+    for (pp = probes; pp < probes + sizeof(probes) / sizeof(probes[0]); pp++) {
+       got_ent = make_servent(pp->name, pp->port, pp->proto);
+       want_eq = !pp->want_error;
+       if (pp->want_error)
+           expect_ptest_error(t, pp->want_error);
+       if (eq_servent(t, "eq_servent", got_ent, want_ent) != want_eq)
+           ptest_error(t, "unexpected eq_servent result mismatch");
+       free_servent(got_ent);
+    }
+    free_servent(want_ent);
+}
+
+static void test_setservent_match(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+
+    /*
+     * Set up expectations.
+     */
+    expect_setservent(1, 1);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    setservent(1);
+}
+
+static void test_setservent_nomatch(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+
+    /*
+     * Set up expectations.
+     */
+    expect_setservent(1, 1);
+
+    /*
+     * These errors are intentional. If they don't happen then the test
+     * fails.
+     */
+    expect_ptest_error(t, "unexpected call: setservent(2)");
+    expect_ptest_error(t, "got 0 calls for setservent(1), want 1");
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    setservent(2);
+}
+
+static void test_endservent_match(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+
+    /*
+     * Set up expectations.
+     */
+    expect_endservent(1);
+
+    /*
+     * Invoke the mock and verify results.
+     */
+    endservent();
+}
+
+static void test_endservent_unused(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+
+    /*
+     * Set up expectations without making a call.
+     */
+    expect_endservent(1);
+
+    /*
+     * This error is intentional. If it does not happen the test fails.
+     */
+    expect_ptest_error(t, "got 0 calls for endservent(), want 1");
+
+    /*
+     * Verify results (this happens in the test infrastructure).
+     */
+}
+
+ /*
+  * Test cases. The "success" tests exercise the expectation match and apply
+  * helpers, and "unused" tests exercise the print helpers.
+  */
+const PTEST_CASE ptestcases[] = {
+
+    /*
+     * getservbyname()
+     */
+    {
+       "test getservbyname success", test_getservbyname_success,
+    },
+    {
+       "test getservbyname notexist", test_getservbyname_notexist,
+    },
+    {
+       "test getservbyname unused", test_getservbyname_unused,
+    },
+
+    /*
+     * eq_servent()
+     */
+    {
+       "test eq_servent differ", test_eq_servent_differ,
+    },
+
+    /*
+     * setservent()
+     */
+    {
+       "test setservent match", test_setservent_match,
+    },
+    {
+       "test setservent nomatch", test_setservent_nomatch,
+    },
+
+    /*
+     * endservent()
+     */
+    {
+       "test endservent match", test_endservent_match,
+    },
+    {
+       "test endservent unused", test_endservent_unused,
+    },
+
+};
+
+#include <ptest_main.h>
diff --git a/postfix/src/testing/mock_server.c b/postfix/src/testing/mock_server.c
new file mode 100644 (file)
index 0000000..4586744
--- /dev/null
@@ -0,0 +1,332 @@
+/*++
+/* NAME
+/*     mock_server 3
+/* SUMMARY
+/*     Mock server for hermetic tests
+/* SYNOPSIS
+/*     #include <mock_server.h>
+/*
+/*     MOCK_SERVER *mock_unix_server_create(
+/*     const char *destination)
+/*
+/*     void    mock_server_interact(
+/*     MOCK_SERVER *server,
+/*     const VSTRING *request,
+/*     const VSTRING *response)
+/*
+/*     void    mock_server_free(MOCK_SERVER *server)
+/*
+/*     int     unix_connect(
+/*     const char *destination,
+/*     int     block_mode,
+/*     int     timeout)
+/* AUXILIARY FUNCTIONS
+/*     void    mock_server_free_void_ptr(void *ptr)
+/* DESCRIPTION
+/*     The purpose of this code is to make tests hermetic, i.e.
+/*     independent from a real server.
+/*
+/*     This module overrides the client function unix_connect()
+/*     with a function that connects to a mock server instance.
+/*     The mock server must be instantiated in advance with
+/*     mock_unix_server_create(). The connection destination name
+/*     is not associated with out-of-process resources.
+/*
+/*     mock_unix_server_create() creates a mock in-process server
+/*     that will "accept" one unix_connect() request with the
+/*     specified destination. To accept multiple connections, use
+/*     multiple mock_unix_server_create() calls.
+/*
+/*     mock_server_interact() configures one expected request
+/*     and/or prepared response. Specify a null request to configure
+/*     an unconditional server response such as an initial handshake,
+/*     and specify a null response to specify a final request. If
+/*     an expected request is configured, the client should send
+/*     a request to the mock server and call event_loop() once to
+/*     allow the server to receive the request. If a prepared
+/*     response is configured, the client should call event_loop()
+/*     once to receive the response from the server. After an
+/*     interaction is completed, mock_server_interact() may be
+/*     called to configure the next interaction.
+/*
+/*     mock_server_free() destroys the specified server instance.
+/*     mock_server_free_void_ptr() provides an alternative API to
+/*     allow for systems where (struct *) and (void *) differ.
+/* BUGS
+/*     Each connection supports no more than one expected request
+/*     and one prepared response at a time. Each request and each
+/*     response must fit in a VSTREAM buffer (4096 bytes as of
+/*     Postfix version 3.8), and must not be larger than SO_SNDBUF
+/*     for AF_LOCAL stream sockets (8192 bytes or more on
+/*     contemporaneous systems when this comment was written).
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <events.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+ /*
+  * Test libraries
+  */
+#include <match_attr.h>
+#include <mock_server.h>
+#include <ptest.h>
+
+ /*
+  * Macros to make obscure code more readable.
+  */
+#define COPY_VSTRING_OR_NULL(dst, src) do { \
+       if ((src) != 0) { \
+           if ((dst) == 0) \
+               (dst) = vstring_alloc(LEN(src)); \
+           vstring_memcpy((dst), STR(src), LEN(src)); \
+       } else if ((dst) != 0) { \
+           vstring_free(dst); \
+           (dst) = 0; \
+       } \
+    } while (0)
+
+#define MOCK_SERVER_REQUEST_READ_EVENT(fd, action, context, timeout) do { \
+        if (msg_verbose > 1) \
+            msg_info("%s: read-request fd=%d", myname, (fd)); \
+        event_enable_read((fd), (action), (context)); \
+        event_request_timer((action), (context), (timeout)); \
+    } while (0)
+
+#define MOCK_SERVER_CLEAR_EVENT_REQUEST(fd, action, context) do { \
+        if (msg_verbose > 1) \
+            msg_info("%s: clear-request fd=%d", myname, (fd)); \
+        event_disable_readwrite(fd); \
+        event_cancel_timer((action), (context)); \
+    } while (0)
+
+#define MOCK_SERVER_TIMEOUT    10
+
+#define MOCK_SERVER_SIDE       (0)
+#define MOCK_CLIENT_SIDE       (1)
+
+ /*
+  * Other macros.
+  */
+#define STR    vstring_str
+#define LEN    VSTRING_LEN
+
+ /*
+  * List head. We could use a RING object and save a few bits in data space,
+  * at the cost of more complex code.
+  */
+static MOCK_SERVER mock_unix_server_head;
+
+/* mock_unix_server_create - instantiate an unconnected mock server */
+
+MOCK_SERVER *mock_unix_server_create(const char *dest)
+{
+    MOCK_SERVER *mp;
+
+    mp = (MOCK_SERVER *) mymalloc(sizeof(*mp));
+    if (socketpair(AF_LOCAL, SOCK_STREAM, 0, mp->fds) < 0) {
+       myfree(mp);
+       ptest_error(ptest_ctx_current(), "mock_unix_server_create(%s): "
+                   "socketpair(AF_LOCAL, SOCK_STREAM, 0, fds): %m", dest);
+       return (0);
+    }
+    mp->flags = 0;
+    mp->want_dest = mystrdup(dest);
+    mp->want_req = 0;
+    mp->resp = 0;
+    mp->iobuf = 0;
+
+    /*
+     * Link the mock server into the waiting list.
+     */
+    mp->next = mock_unix_server_head.next;
+    mock_unix_server_head.next = mp;
+    mp->prev = &mock_unix_server_head;
+    if (mp->next)
+       mp->next->prev = mp;
+    return (mp);
+}
+
+/* mock_server_respond - send a prepared response */
+
+static void mock_server_respond(MOCK_SERVER *mp)
+{
+    const char myname[] = "mock_server_respond";
+    ssize_t got_len;
+
+    got_len = write(mp->fds[MOCK_SERVER_SIDE], STR(mp->resp), LEN(mp->resp));
+    if (got_len < 0)
+       ptest_fatal(ptest_ctx_current(), "%s: write: %m", myname);
+    if (got_len != LEN(mp->resp))
+       ptest_fatal(ptest_ctx_current(), "%s: wrote %ld of %ld bytes",
+                   myname, (long) got_len, (long) LEN(mp->resp));
+}
+
+/* mock_server_read_event - receive request and respond */
+
+static void mock_server_read_event(int event, void *context)
+{
+    const char myname[] = "mock_server_read_event";
+    MOCK_SERVER *mp = (MOCK_SERVER *) context;
+    ssize_t peek_len;
+    ssize_t got_len;
+
+    /*
+     * Disarm this file descriptor.
+     */
+    MOCK_SERVER_CLEAR_EVENT_REQUEST(mp->fds[MOCK_SERVER_SIDE],
+                                   mock_server_read_event,
+                                   context);
+
+    /*
+     * Handle the event.
+     */
+    switch (event) {
+    case EVENT_READ:
+       break;
+    case EVENT_TIME:
+       ptest_error(ptest_ctx_current(), "%s: timeout", myname);
+       return;
+    default:
+       ptest_fatal(ptest_ctx_current(), "%s: unexpected event: %d",
+                   myname, event);
+    }
+
+    /*
+     * Receive a request, if one is expected.
+     */
+    switch (peek_len = peekfd(mp->fds[MOCK_SERVER_SIDE])) {
+    case -1:
+       ptest_error(ptest_ctx_current(), "%s: read: %m", myname);
+       return;
+    case 0:
+       ptest_error(ptest_ctx_current(), "%s: read EOF", myname);
+       return;
+    default:
+       break;
+    }
+    if (mp->iobuf == 0)
+       mp->iobuf = vstring_alloc(1000);
+    else
+       VSTRING_RESET(mp->iobuf);
+    VSTRING_SPACE(mp->iobuf, peek_len);
+    got_len = read(mp->fds[MOCK_SERVER_SIDE], STR(mp->iobuf), peek_len);
+    if (got_len != peek_len) {
+       ptest_fatal(ptest_ctx_current(), "%s: read %ld of %ld bytes",
+                   myname, (long) got_len, (long) peek_len);
+       return;
+    }
+    vstring_set_payload_size(mp->iobuf, got_len);
+    if (!eq_attr(ptest_ctx_current(), "request", mp->iobuf, mp->want_req))
+       return;
+
+    /*
+     * Send the response, if available.
+     */
+    else if (mp->resp)
+       mock_server_respond(mp);
+}
+
+/* mock_server_interact - set up request and/or response */
+
+void    mock_server_interact(MOCK_SERVER *mp,
+                                         const VSTRING *req,
+                                         const VSTRING *resp)
+{
+    const char myname[] = "mock_server_interact";
+
+    if (req == 0 && resp == 0)
+       ptest_fatal(ptest_ctx_current(), "%s: null request and null response",
+                   myname);
+    COPY_VSTRING_OR_NULL(mp->want_req, req);
+    COPY_VSTRING_OR_NULL(mp->resp, resp);
+    if (req != 0) {
+       MOCK_SERVER_REQUEST_READ_EVENT(mp->fds[MOCK_SERVER_SIDE],
+                                      mock_server_read_event,
+                                      (void *) mp, MOCK_SERVER_TIMEOUT);
+    } else {
+       mock_server_respond(mp);
+    }
+}
+
+/* mock_server_unlink - detach one instance from its waiting list */
+
+static void mock_server_unlink(MOCK_SERVER *mp)
+{
+    if (mp->next)
+       mp->next->prev = mp->prev;
+    if (mp->prev)
+       mp->prev->next = mp->next;
+    mp->prev = mp->next = 0;
+}
+
+/* mock_server_free - destroy mock server */
+
+void    mock_server_free(MOCK_SERVER *mp)
+{
+    const char myname[] = "mock_server_free";
+
+    myfree(mp->want_dest);
+    if ((mp->flags & MOCK_SERVER_FLAG_CONNECTED) == 0)
+       (void) close(mp->fds[MOCK_CLIENT_SIDE]);
+    MOCK_SERVER_CLEAR_EVENT_REQUEST(mp->fds[MOCK_SERVER_SIDE],
+                                   mock_server_read_event, mp);
+    (void) close(mp->fds[MOCK_SERVER_SIDE]);
+    if (mp->want_req)
+       vstring_free(mp->want_req);
+    if (mp->resp)
+       vstring_free(mp->resp);
+    if (mp->iobuf)
+       vstring_free(mp->iobuf);
+    mock_server_unlink(mp);
+    myfree(mp);
+}
+
+/* mock_server_free_void_ptr - destroy mock server */
+
+void    mock_server_free_void_ptr(void *ptr)
+{
+    mock_server_free(ptr);
+}
+
+/* unix_connect - mock helper */
+
+int     unix_connect(const char *dest, int block_mode, int unused_timeout)
+{
+    MOCK_SERVER *mp;
+
+    for (mp = mock_unix_server_head.next; /* see below */ ; mp = mp->next) {
+       if (mp == 0) {
+           errno = ENOENT;
+           return (-1);
+       }
+       if (strcmp(dest, mp->want_dest) == 0) {
+           mock_server_unlink(mp);
+           if (block_mode == NON_BLOCKING)
+               non_blocking(mp->fds[MOCK_CLIENT_SIDE], block_mode);
+           mp->flags |= MOCK_SERVER_FLAG_CONNECTED;
+           return (mp->fds[MOCK_CLIENT_SIDE]);
+       }
+    }
+}
diff --git a/postfix/src/testing/mock_server.h b/postfix/src/testing/mock_server.h
new file mode 100644 (file)
index 0000000..c97d634
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef _MOCK_SERVER_H_INCLUDED_
+#define _MOCK_SERVER_H_INCLUDED_
+
+/*++
+/* NAME
+/*     mock_server 3h
+/* SUMMARY
+/*     Mock server support
+/* SYNOPSIS
+/*     #include <mock_server.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+#include <connect.h>
+
+ /*
+  * External interface.
+  */
+typedef struct MOCK_SERVER {
+    int     flags;
+    int     fds[2];                    /* pipe(2) result */
+    char   *want_dest;
+    VSTRING *want_req;                 /* serialized request, may be null */
+    VSTRING *resp;                     /* serialized response, may be null */
+    VSTRING *iobuf;                    /* I/O buffer */
+    struct MOCK_SERVER *next;  /* chain of unconnected servers */
+    struct MOCK_SERVER *prev;  /* chain of unconnected servers */
+} MOCK_SERVER;
+
+#define MOCK_SERVER_FLAG_CONNECTED     (1<<0)
+
+extern MOCK_SERVER *mock_unix_server_create(const char *);
+extern void mock_server_interact(MOCK_SERVER *, const VSTRING *,
+                                             const VSTRING *);
+extern void mock_server_free(MOCK_SERVER *);
+extern void mock_server_free_void_ptr(void *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/postfix/src/testing/mock_server_test.c b/postfix/src/testing/mock_server_test.c
new file mode 100644 (file)
index 0000000..e334d05
--- /dev/null
@@ -0,0 +1,426 @@
+ /*
+  * Test program to exercise mock_server.c. See PTEST_README for
+  * documentation for how this file is structured.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <errno.h>
+#include <stdarg.h>
+
+ /*
+  * Utility library.
+  */
+#include <attr.h>
+#include <events.h>
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+  * Global library.
+  */
+#include <mail_proto.h>
+
+ /*
+  * Test library.
+  */
+#include <make_attr.h>
+#include <mock_server.h>
+#include <ptest.h>
+
+ /*
+  * Generic case structure.
+  */
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+ /*
+  * Structure to capture a client-server conversation state.
+  */
+struct session_state {
+    VSTRING *resp_buf;                 /* request echoed by server */
+    int     resp_len;                  /* request length from server */
+    int     fd;
+    VSTREAM *stream;
+    int     error;
+};
+
+#define REQUEST_READ_EVENT(fd, action, context, timeout) do { \
+        if (msg_verbose > 1) \
+            msg_info("%s: read-request fd=%d", myname, (fd)); \
+        event_enable_read((fd), (action), (context)); \
+        event_request_timer((action), (context), (timeout)); \
+    } while (0)
+
+#define CLEAR_EVENT_REQUEST(fd, time_act, context) do { \
+        if (msg_verbose > 1) \
+            msg_info("%s: clear-request fd=%d", myname, (fd)); \
+        event_disable_readwrite(fd); \
+        event_cancel_timer((time_act), (context)); \
+    } while (0)
+
+/* read_event - event handler to receive server response */
+
+static void read_event(int event, void *context)
+{
+    static const char myname[] = "read_event";
+    struct session_state *session_state = (struct session_state *) context;
+
+    CLEAR_EVENT_REQUEST(session_state->fd, read_event, context);
+
+    switch (event) {
+    case EVENT_READ:
+       if (attr_scan(session_state->stream, ATTR_FLAG_NONE,
+                     RECV_ATTR_STR(MAIL_ATTR_REQ, session_state->resp_buf),
+                   RECV_ATTR_INT(MAIL_ATTR_SIZE, &session_state->resp_len),
+                     ATTR_TYPE_END) != 2) {
+           ptest_error(ptest_ctx_current(), "%s failed: %m", myname);
+           session_state->error = EINVAL;
+       }
+       break;
+    case EVENT_TIME:
+       ptest_error(ptest_ctx_current(), "%s: timeout", myname);
+       session_state->error = ETIMEDOUT;
+       break;
+    default:
+       ptest_fatal(ptest_ctx_current(), "%s: unknown event: %d",
+                   myname, event);
+    }
+}
+
+static void test_single_server(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    static const char myname[] = "test_single_server";
+    MOCK_SERVER *mp;
+    struct session_state session_state;
+    VSTRING *serialized_req;
+    VSTRING *serialized_resp;
+
+#define REQUEST_VAL    "abcdef"
+#define SERVER_NAME    "testing..."
+
+    /*
+     * Instantiate a mock server, and connect to it.
+     */
+    mp = mock_unix_server_create(SERVER_NAME);
+    session_state.resp_buf = vstring_alloc(100);
+    session_state.resp_len = 0;
+    session_state.error = 0;
+    if ((session_state.fd = unix_connect(SERVER_NAME, 0, 0)) < 0)
+       ptest_fatal(t, "unix_connect: %s: %m", SERVER_NAME);
+    session_state.stream = vstream_fdopen(session_state.fd, O_RDWR);
+
+    /*
+     * Set up a server request expectation, and response.
+     */
+    serialized_req =
+       make_attr(ATTR_FLAG_NONE,
+                 SEND_ATTR_STR(MAIL_ATTR_REQ, REQUEST_VAL),
+                 ATTR_TYPE_END);
+    serialized_resp =
+       make_attr(ATTR_FLAG_NONE,
+                 SEND_ATTR_STR(MAIL_ATTR_REQ, REQUEST_VAL),
+                 SEND_ATTR_INT(MAIL_ATTR_SIZE, strlen(REQUEST_VAL)),
+                 ATTR_TYPE_END);
+    mock_server_interact(mp, serialized_req, serialized_resp);
+
+    /*
+     * Send a request, and run the event loop once to notify the server side
+     * that the request is pending.
+     */
+    if (attr_print(session_state.stream, ATTR_FLAG_NONE,
+                  SEND_ATTR_STR(MAIL_ATTR_REQ, REQUEST_VAL),
+                  ATTR_TYPE_END) != 0
+       || vstream_fflush(session_state.stream) != 0)
+       ptest_fatal(t, "send request: %m");
+    event_loop(1);
+
+    /*
+     * Receive the response, and validate.
+     */
+    REQUEST_READ_EVENT(session_state.fd, read_event, &session_state, 1);
+    event_loop(1);
+    if (session_state.error != 0) {
+       /* already reported */
+    } else if (VSTRING_LEN(session_state.resp_buf) != strlen(REQUEST_VAL)) {
+       ptest_error(t, "got resp_buf length %ld, want %ld",
+                   (long) VSTRING_LEN(session_state.resp_buf),
+                   (long) strlen(REQUEST_VAL));
+    } else if (session_state.resp_len != strlen(REQUEST_VAL)) {
+       ptest_error(t, "got resp_len %d, want %ld",
+                   session_state.resp_len, (long) strlen(REQUEST_VAL));
+    } else if (strcmp(vstring_str(session_state.resp_buf), REQUEST_VAL) != 0) {
+       ptest_error(t, "got resp_buf '%s', want '%s'",
+                   vstring_str(session_state.resp_buf), REQUEST_VAL);
+    }
+
+    /*
+     * Clean up.
+     */
+    if (vstream_fclose(session_state.stream) != 0)
+       ptest_fatal(t, "close stream: %m");
+    vstring_free(session_state.resp_buf);
+    vstring_free(serialized_req);
+    vstring_free(serialized_resp);
+    mock_server_free(mp);
+}
+
+static void test_request_mismatch(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    static const char myname[] = "test_single_server";
+    MOCK_SERVER *mp;
+    struct session_state session_state;
+    VSTRING *serialized_req;
+    VSTRING *serialized_resp;
+
+#define REQUEST_VAL    "abcdef"
+#define SERVER_NAME    "testing..."
+
+    /*
+     * Instantiate a mock server, and connect to it.
+     */
+    mp = mock_unix_server_create(SERVER_NAME);
+    session_state.resp_buf = vstring_alloc(100);
+    session_state.resp_len = 0;
+    if ((session_state.fd = unix_connect(SERVER_NAME, 0, 0)) < 0)
+       ptest_fatal(t, "unix_connect: %s: %m", SERVER_NAME);
+    session_state.stream = vstream_fdopen(session_state.fd, O_RDWR);
+
+    /*
+     * Set up a server request expectation, and response.
+     */
+    serialized_req =
+       make_attr(ATTR_FLAG_NONE,
+                 SEND_ATTR_STR(MAIL_ATTR_REQ, REQUEST_VAL "g"),
+                 ATTR_TYPE_END);
+    serialized_resp =
+       make_attr(ATTR_FLAG_NONE,
+                 SEND_ATTR_STR(MAIL_ATTR_REQ, REQUEST_VAL),
+                 SEND_ATTR_INT(MAIL_ATTR_SIZE, strlen(REQUEST_VAL)),
+                 ATTR_TYPE_END);
+    mock_server_interact(mp, serialized_req, serialized_resp);
+
+    /*
+     * Send a request, and run the event loop once to notify the server side
+     * that the request is pending.
+     */
+    if (attr_print(session_state.stream, ATTR_FLAG_NONE,
+                  SEND_ATTR_STR(MAIL_ATTR_REQ, REQUEST_VAL),
+                  ATTR_TYPE_END) != 0
+       || vstream_fflush(session_state.stream) != 0)
+       ptest_fatal(t, "send request: %m");
+    expect_ptest_error(t, "attributes differ");
+    expect_ptest_error(t, "+request = abcdef");
+    expect_ptest_error(t, "-request = abcdefg");
+    expect_ptest_error(t, "timeout");
+    event_loop(1);
+
+    /*
+     * Receive the response, and validate.
+     */
+    REQUEST_READ_EVENT(session_state.fd, read_event, &session_state, 1);
+    event_loop(1);
+    if (session_state.error != 0) {
+        /* already reported */
+    } else if (VSTRING_LEN(session_state.resp_buf) != strlen(REQUEST_VAL)) {
+        ptest_error(t, "got resp_buf length %ld, want %ld",
+                    (long) VSTRING_LEN(session_state.resp_buf),
+                    (long) strlen(REQUEST_VAL));
+    } else if (session_state.resp_len != strlen(REQUEST_VAL)) {
+        ptest_error(t, "got resp_len %d, want %ld",
+                    session_state.resp_len, (long) strlen(REQUEST_VAL));
+    } else if (strcmp(vstring_str(session_state.resp_buf), REQUEST_VAL) != 0) {
+        ptest_error(t, "got resp_buf '%s', want '%s'",
+                    vstring_str(session_state.resp_buf), REQUEST_VAL);
+    }
+
+    /*
+     * Clean up.
+     */
+    if (vstream_fclose(session_state.stream) != 0)
+       ptest_fatal(t, "close stream: %m");
+    vstring_free(session_state.resp_buf);
+    vstring_free(serialized_req);
+    vstring_free(serialized_resp);
+    mock_server_free(mp);
+}
+
+static void test_missing_server(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    int     fd;
+
+#define SERVER_NAME "testing..."
+
+    /*
+     * Connect to a non-existent server, and require a failure.
+     */
+    if ((fd = unix_connect(SERVER_NAME, 0, 0)) >= 0) {
+       (void) close(fd);
+       ptest_fatal(t,
+                   "unix_connect(%s) did NOT fail", SERVER_NAME);
+    }
+}
+
+static void test_unused_server(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    MOCK_SERVER *mp;
+
+    /*
+     * Instantiate a mock server, and destroy it without using it.
+     */
+    mp = mock_unix_server_create(SERVER_NAME);
+    mock_server_free(mp);
+}
+
+static void test_server_speaks_only(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    static const char myname[] = "test_server_speaks_only";
+    MOCK_SERVER *mp;
+    struct session_state session_state;
+    VSTRING *serialized_resp;
+
+    /*
+     * This is the same test as "test_single_server", but without sending a
+     * request.
+     */
+#define REQUEST_VAL    "abcdef"
+#define SERVER_NAME    "testing..."
+#define NO_REQUEST     ((VSTRING *) 0)
+
+    /*
+     * Instantiate a mock server, and connect to it.
+     */
+    mp = mock_unix_server_create(SERVER_NAME);
+    session_state.resp_buf = vstring_alloc(100);
+    session_state.resp_len = 0;
+    if ((session_state.fd = unix_connect(SERVER_NAME, 0, 0)) < 0)
+       ptest_fatal(t, "unix_connect: %s: %m", SERVER_NAME);
+    session_state.stream = vstream_fdopen(session_state.fd, O_RDWR);
+
+    /*
+     * Set up a server response, without request expectation.
+     */
+    serialized_resp =
+       make_attr(ATTR_FLAG_NONE,
+                 SEND_ATTR_STR(MAIL_ATTR_REQ, REQUEST_VAL),
+                 SEND_ATTR_INT(MAIL_ATTR_SIZE, strlen(REQUEST_VAL)),
+                 ATTR_TYPE_END);
+    mock_server_interact(mp, NO_REQUEST, serialized_resp);
+
+    /*
+     * Receive the response, and validate.
+     */
+    REQUEST_READ_EVENT(session_state.fd, read_event, &session_state, 1);
+    event_loop(1);
+    if (session_state.error != 0) {
+        /* already reported */
+    } else if (VSTRING_LEN(session_state.resp_buf) != strlen(REQUEST_VAL)) {
+        ptest_error(t, "got resp_buf length %ld, want %ld",
+                    (long) VSTRING_LEN(session_state.resp_buf),
+                    (long) strlen(REQUEST_VAL));
+    } else if (session_state.resp_len != strlen(REQUEST_VAL)) {
+        ptest_error(t, "got resp_len %d, want %ld",
+                    session_state.resp_len, (long) strlen(REQUEST_VAL));
+    } else if (strcmp(vstring_str(session_state.resp_buf), REQUEST_VAL) != 0) {
+        ptest_error(t, "got resp_buf '%s', wamt '%s'",
+                    vstring_str(session_state.resp_buf), REQUEST_VAL);
+    }
+
+    /*
+     * Clean up.
+     */
+    if (vstream_fclose(session_state.stream) != 0)
+       ptest_fatal(t, "close stream: %m");
+    vstring_free(session_state.resp_buf);
+    vstring_free(serialized_resp);
+    mock_server_free(mp);
+}
+
+static void test_client_speaks_only(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    MOCK_SERVER *mp;
+    struct session_state session_state;
+    VSTRING *serialized_req;
+
+    /*
+     * This is the same test as "test_single_server", but without receiving a
+     * response.
+     */
+#define REQUEST_VAL    "abcdef"
+#define SERVER_NAME    "testing..."
+#define NO_RESPONSE    ((VSTRING *) 0)
+
+    /*
+     * Instantiate a mock server, and connect to it.
+     */
+    mp = mock_unix_server_create(SERVER_NAME);
+    if ((session_state.fd = unix_connect(SERVER_NAME, 0, 0)) < 0)
+       ptest_fatal(t, "unix_connect: %s: %m", SERVER_NAME);
+    session_state.stream = vstream_fdopen(session_state.fd, O_RDWR);
+
+    /*
+     * Set up a server request expectation, and response.
+     */
+    serialized_req =
+       make_attr(ATTR_FLAG_NONE,
+                 SEND_ATTR_STR(MAIL_ATTR_REQ, REQUEST_VAL),
+                 ATTR_TYPE_END);
+    mock_server_interact(mp, serialized_req, NO_RESPONSE);
+
+    /*
+     * Send a request, and run the event loop once to notify the server side
+     * that the request is pending.
+     */
+    if (attr_print(session_state.stream, ATTR_FLAG_NONE,
+                  SEND_ATTR_STR(MAIL_ATTR_REQ, REQUEST_VAL),
+                  ATTR_TYPE_END) != 0
+       || vstream_fflush(session_state.stream) != 0)
+       ptest_fatal(t, "send request: %m");
+    event_loop(1);
+
+    /*
+     * Clean up.
+     */
+    if (vstream_fclose(session_state.stream) != 0)
+       ptest_fatal(t, "close stream: %m");
+    vstring_free(serialized_req);
+    mock_server_free(mp);
+}
+
+ /*
+  * Test cases.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+       "test single server", test_single_server,
+    },
+    {
+       "test request mismatch", test_request_mismatch,
+    },
+    {
+       "test missing server", test_missing_server,
+    },
+    {
+       "test unused server", test_unused_server,
+    },
+    {
+       "test server speaks only", test_server_speaks_only,
+    },
+    {
+       "test client speaks only", test_client_speaks_only,
+    },
+
+    /*
+     * TODO: test multiple servers with the same endpoint name but with
+     * different expectations. See postscreen_dnsbl_test.c for an example.
+     * This requires that the environment variable "NORAMDOMIZE" is set
+     * before this program is run.
+     */
+};
+
+#include <ptest_main.h>
index 0ccf3e9645506d60d17f611fd3803a652985da92..6e610cf77e0a06705592286ab9ea41aec6a65cbf 100644 (file)
@@ -2,7 +2,7 @@ SHELL   = /bin/sh
 SRCS   = tlsmgr.c
 OBJS   = tlsmgr.o
 HDRS   = 
-TESTSRC        = smtpd_token_test.c
+TESTSRC        =
 DEFS   = -I. -I$(INC_DIR) -D$(SYSTYPE)
 CFLAGS = $(DEBUG) $(OPT) $(DEFS)
 TESTPROG= 
index b7157beb21fa9a126eb9657bd2f4c4ef82f0515e..0a0b023af9eb485ef52ac73f4be39f3522bceb18 100644 (file)
@@ -34,7 +34,7 @@ SRCS  = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
        allascii.c load_file.c killme_after.c vstream_tweak.c \
        pass_trigger.c edit_file.c inet_windowsize.c \
        unix_pass_fd_fix.c dict_cache.c valid_utf8_string.c dict_thash.c \
-       ip_match.c nbbio.c base32_code.c dict_test.c \
+       ip_match.c nbbio.c base32_code.c dict_cli.c \
        dict_fail.c msg_rate_delay.c dict_surrogate.c warn_stat.c \
        dict_sockmap.c line_number.c recv_pass_attr.c pass_accept.c \
        poll_fd.c timecmp.c slmdb.c dict_pipe.c dict_random.c \
@@ -48,7 +48,7 @@ SRCS  = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
        inet_addr_sizes.c quote_for_json.c mystrerror.c \
        sane_sockaddr_to_hostaddr.c normalize_ws.c valid_uri_scheme.c \
        clean_ascii_cntrl_space.c normalize_v4mapped_addr.c ossl_digest.c \
-       mac_midna.c wrap_stat.c dynamicmaps.c
+       mac_midna.c wrap_stat.c dynamicmaps.c find_inet_service.c wrap_netdb.c
 OBJS   = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
        attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \
        attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
@@ -84,7 +84,7 @@ OBJS  = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
        allascii.o load_file.o killme_after.o vstream_tweak.o \
        pass_trigger.o edit_file.o inet_windowsize.o \
        unix_pass_fd_fix.o dict_cache.o valid_utf8_string.o dict_thash.o \
-       ip_match.o nbbio.o base32_code.o dict_test.o \
+       ip_match.o nbbio.o base32_code.o dict_cli.o \
        dict_fail.o msg_rate_delay.o dict_surrogate.o warn_stat.o \
        dict_sockmap.o line_number.o recv_pass_attr.o pass_accept.o \
        poll_fd.o timecmp.o $(NON_PLUGIN_MAP_OBJ) dict_pipe.o dict_random.o \
@@ -98,7 +98,7 @@ OBJS  = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
        quote_for_json.o mystrerror.o sane_sockaddr_to_hostaddr.o \
        normalize_ws.o valid_uri_scheme.o clean_ascii_cntrl_space.o \
        normalize_v4mapped_addr.o ossl_digest.o mac_midna.o wrap_stat.o \
-       dynamicmaps.o
+       dynamicmaps.o find_inet_service.o wrap_netdb.o
 # MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
 # When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
 # otherwise it sets the PLUGIN_* macros.
@@ -133,9 +133,13 @@ HDRS       = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \
        known_tcp_ports.h sane_strtol.h hash_fnv.h ldseed.h mkmap.h \
        inet_prefix_top.h inet_addr_sizes.h valid_uri_scheme.h \
        clean_ascii_cntrl_space.h normalize_v4mapped_addr.h ossl_digest.h \
-       mac_midna.h wrap_stat.h dynamicmaps.h
+       mac_midna.h wrap_stat.h dynamicmaps.h find_inet_service.h wrap_netdb.h
 TESTSRC        = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \
-       stream_test.c dup2_pass_on_exec.c
+       sunos5_stream_test.c dup2_pass_on_exec.c argv_test.c dict_pipe_test.c \
+       dict_stream_test.c dict_cli.c dict_union_test.c \
+       find_inet_service_test.c hash_fnv_test.c known_tcp_ports_test.c \
+       msg_output_test.c myaddrinfo_test.c mymalloc_test.c mystrtok_test.c \
+       unescape_test.c
 DEFS   = -I. -D$(SYSTYPE)
 CFLAGS = $(DEBUG) $(OPT) $(DEFS)
 FILES  = Makefile $(SRCS) $(HDRS)
@@ -144,9 +148,9 @@ LIB = lib$(LIB_PREFIX)util$(LIB_SUFFIX)
 TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
        fifo_rdonly_bug fifo_rdwr_bug fifo_trigger fsspace fullname \
        inet_addr_host inet_addr_local mac_parse make_dirs msg_syslog \
-       mystrtok sigdelay translit valid_hostname vstream_popen \
-       vstring vstring_vstream doze select_bug stream_test mac_expand \
-       watchdog unescape hex_quote name_mask rand_sleep sane_time ctable \
+       mystrtok_test sigdelay translit valid_hostname vstream_popen \
+       vstring vstring_vstream doze select_bug sunos5_stream_test mac_expand \
+       watchdog unescape_test hex_quote name_mask rand_sleep sane_time ctable \
        inet_addr_list attr_print64 attr_scan64 base64_code attr_print0 \
        attr_scan0 host_port attr_scan_plain attr_print_plain htable \
        unix_recv_fd unix_send_fd stream_recv_fd stream_send_fd hex_code \
@@ -154,24 +158,25 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
        valid_utf8_string ip_match base32_code msg_rate_delay netstring \
        vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \
        vbuf_print split_qnameval vstream msg_logger byte_mask \
-       known_tcp_ports dict_stream find_inet binhash hash_fnv_test argv \
+       known_tcp_ports_test dict_stream_test binhash hash_fnv_test \
+       argv_test find_inet_service_test mymalloc_test msg_output_test \
+       dict_union_test dict_pipe_test myaddrinfo_test \
        clean_env inet_prefix_top printable readlline quote_for_json \
        normalize_ws valid_uri_scheme clean_ascii_cntrl_space \
-       normalize_v4mapped_addr_test ossl_digest_test dict_pipe_test \
-       dict_union_test
+       normalize_v4mapped_addr_test ossl_digest_test
 PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) $(LIB_PREFIX)lmdb$(LIB_SUFFIX) \
        $(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(LIB_SUFFIX) \
        $(LIB_PREFIX)db$(LIB_SUFFIX)
 HTABLE_FIX = NORANDOMIZE=1
 LIB_DIR        = ../../lib
 INC_DIR        = ../../include
-TESTLIB        = $(LIB_DIR)/libtesting.a
+TESTLIBS= $(LIB_DIR)/libtesting.a $(LIB_DIR)/libptest.a
 
 .c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
 
 all: $(LIB) $(PLUGIN_MAP_SO_MAKE) $(PLUGIN_MAP_OBJ)
 
-$(OBJS) $(PLUGIN_MAP_OBJ): ../../conf/makedefs.out
+$(OBJS) $(PLUGIN_MAP_OBJ) $(TESTPROG): ../../conf/makedefs.out
 
 Makefile: Makefile.in
        cat ../../conf/makedefs.out $? >$@
@@ -300,11 +305,6 @@ sigdelay: $(LIB)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
        mv junk $@.o
 
-mystrtok: $(LIB)
-       mv $@.o junk
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
-       mv junk $@.o
-
 fifo_rdwr_bug: fifo_rdwr_bug.c $(LIB)
        $(CC) $(CFLAGS)  -o $@ $@.c $(LIB) $(SYSLIBS)
 
@@ -364,11 +364,6 @@ watchdog: $(LIB)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
        mv junk $@.o
 
-unescape: $(LIB)
-       mv $@.o junk
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
-       mv junk $@.o
-
 printable: $(LIB)
        mv $@.o junk
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
@@ -464,8 +459,8 @@ binhash: $(LIB)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
        mv junk $@.o
 
-hash_fnv_test: $(LIB) update
-       $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
+hash_fnv_test: $(LIB) $(TESTLIBS) update
+       $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(TESTLIBS) $(SYSLIBS)
 
 unix_recv_fd:  $(LIB)
        mv $@.o junk
@@ -522,8 +517,8 @@ clean_env: $(LIB)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
        mv junk $@.o
 
-stream_test: stream_test.c $(LIB)
-       $(CC) $(CFLAGS)  -o $@ $@.c $(LIB) $(SYSLIBS)
+sunos5_stream_test: sunos5_stream_test.o $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(LIB) $(SYSLIBS)
 
 gcctest: gccw.c gccw.ref
        rm -f gccw.o
@@ -601,10 +596,11 @@ split_qnameval: $(LIB)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
        mv junk $@.o
 
-known_tcp_ports: $(LIB)
-       mv $@.o junk
-       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
-       mv junk $@.o
+known_tcp_ports_test: known_tcp_ports_test.o $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS)
+
+test_known_tcp_ports: update known_tcp_ports_test
+       $(SHLIB_ENV) ${VALGRIND} ./known_tcp_ports_test
 
 dict_stream: $(LIB)
        mv $@.o junk
@@ -641,22 +637,22 @@ clean_ascii_cntrl_space: $(LIB)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
        mv junk $@.o
 
-normalize_v4mapped_addr_test: normalize_v4mapped_addr_test.c $(LIB)
-       $(CC) $(CFLAGS) -o $@ normalize_v4mapped_addr_test.c $(LIB) $(SYSLIBS)
+normalize_v4mapped_addr_test: normalize_v4mapped_addr_test.o $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(LIB) $(SYSLIBS)
 
-ossl_digest_test: ossl_digest_test.c $(LIB)
-       $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
+ossl_digest_test: ossl_digest_test.o $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(LIB) $(SYSLIBS)
 
-dict_pipe_test: dict_pipe_test.c $(TESTLIB) $(LIB)
-       $(CC) $(CFLAGS) -o $@ $@.c $(TESTLIB) $(LIB) $(SYSLIBS)
+dict_pipe_test: dict_pipe_test.o $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS)
 
-dict_union_test: dict_union_test.c $(TESTLIB) $(LIB)
-       $(CC) $(CFLAGS) -o $@ $@.c $(TESTLIB) $(LIB) $(SYSLIBS)
+dict_union_test: dict_union_test.o $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS)
 
-tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
+tests: update valid_hostname_test mac_expand_test dict_test test_unescape \
        hex_quote_test ctable_test inet_addr_list_test base64_code_test \
        attr_scan64_test attr_scan0_test host_port_test dict_tests \
-       attr_scan_plain_test htable_test hex_code_test myaddrinfo_test \
+       attr_scan_plain_test htable_test hex_code_test test_myaddrinfo \
        format_tv_test ip_match_test name_mask_tests base32_code_test \
        surrogate_test timecmp_test midna_domain_test casefold_test \
        strcasecmp_utf8_test vbuf_print_test miss_endif_cidr_test \
@@ -668,7 +664,7 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
        test_normalize_v4mapped_addr test_ossl_digest test_dict_pipe \
        test_dict_union test_hash_fnv
  
-dict_tests: all dict_test \
+dict_tests: dict_test \
        dict_pcre_tests dict_cidr_test dict_thash_test dict_static_test \
        dict_inline_test dict_utf8_test dict_regexp_test \
        dict_regexp_file_test dict_cidr_file_test dict_seq_test \
@@ -691,12 +687,11 @@ mac_expand_test: mac_expand mac_expand.in mac_expand.ref
        diff mac_expand.ref mac_expand.tmp
        rm -f mac_expand.tmp
 
-unescape_test: unescape unescape.in unescape.ref
-       $(SHLIB_ENV) ${VALGRIND} ./unescape <unescape.in | LANG=C od -cb >unescape.tmp
-       diff -b unescape.ref unescape.tmp
-#      $(SHLIB_ENV) ${VALGRIND} ./unescape <unescape.in | $(SHLIB_ENV) ${VALGRIND} ./unescape -e >unescape.tmp
-#      diff unescape.in unescape.tmp
-       rm -f unescape.tmp
+unescape_test: unescape_test.o $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS)
+       
+test_unescape: update unescape_test 
+       $(SHLIB_ENV) ${VALGRIND} ./unescape_test
 
 printable_test: printable
        $(SHLIB_ENV) ${VALGRIND} ./printable 
@@ -744,14 +739,15 @@ attr_scan0_test: attr_print0 attr_scan0 attr_scan0.ref
        rm -f attr_scan0.tmp
 
 dict_test: dict_open testdb dict_test.in dict_test.ref
-       rm -f testdb.db testdb.dir testdb.pag
-       $(SHLIB_ENV) ../postmap/postmap -N hash:testdb
+       rm -f testdb.db testdb.dir testdb.pag main.cf
+       touch -t 197601010000 main.cf
+       $(SHLIB_ENV) ../postmap/postmap -Nc. hash:testdb
        $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp
        diff dict_test.ref dict_test.tmp
-       $(SHLIB_ENV) ../postmap/postmap -n hash:testdb
+       $(SHLIB_ENV) ../postmap/postmap -nc. hash:testdb
        $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp
        diff dict_test.ref dict_test.tmp
-       rm -f testdb.db testdb.dir testdb.pag dict_test.tmp
+       rm -f testdb.db testdb.dir testdb.pag dict_test.tmp main.cf
 
 dict_pcre_test: dict_open dict_pcre.in dict_pcre.map dict_pcre.ref
        $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:dict_pcre.map read \
@@ -846,20 +842,13 @@ hex_code_test: hex_code
 timecmp_test: timecmp
        $(SHLIB_ENV) ${VALGRIND} ./timecmp
 
-myaddrinfo_test: myaddrinfo myaddrinfo.ref myaddrinfo.ref2
-       $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all belly.porcupine.org 168.100.3.2 >myaddrinfo.tmp 2>&1
-       diff myaddrinfo.ref myaddrinfo.tmp
-       rm -f myaddrinfo.tmp
-       $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all null.porcupine.org 10.0.0.0 >myaddrinfo.tmp 2>&1
-       diff myaddrinfo.ref2 myaddrinfo.tmp
-       rm -f myaddrinfo.tmp
-
-myaddrinfo4_test: myaddrinfo4 myaddrinfo4.ref myaddrinfo4.ref2
-       $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all belly.porcupine.org 168.100.3.2 >myaddrinfo4.tmp 2>&1
-       diff myaddrinfo4.ref myaddrinfo4.tmp
-       $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all null.porcupine.org 10.0.0.0 >myaddrinfo4.tmp 2>&1
-       diff myaddrinfo4.ref2 myaddrinfo4.tmp
-       rm -f myaddrinfo4.tmp
+myaddrinfo_test: myaddrinfo_test.o $(LIB_DIR)/mock_getaddrinfo.o \
+       $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(LIB_DIR)/mock_getaddrinfo.o \
+       $(TESTLIBS) $(LIB) $(SYSLIBS)
+       
+test_myaddrinfo: update myaddrinfo_test 
+       $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo_test
 
 format_tv_test: format_tv format_tv.in format_tv.ref
        $(SHLIB_ENV) ${VALGRIND} ./format_tv <format_tv.in >format_tv.tmp
@@ -952,11 +941,13 @@ base32_code_test: base32_code
        $(SHLIB_ENV) ${VALGRIND} ./base32_code
 
 dict_thash_test: ../postmap/postmap dict_thash.map dict_thash.in dict_thash.ref
-       $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.map 2>&1 | \
+       rm -f main.cf
+       touch -t 197601010000 main.cf
+       $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fsc. texthash:dict_thash.map 2>&1 | \
            LANG=C sort | diff dict_thash.map -
-       NORANDOMIZE=1 $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.in >dict_thash.tmp 2>&1
+       NORANDOMIZE=1 $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fsc. texthash:dict_thash.in >dict_thash.tmp 2>&1
        diff dict_thash.ref dict_thash.tmp
-       rm -f dict_thash.tmp
+       rm -f dict_thash.tmp main.cf
 
 surrogate_test: dict_open surrogate.ref
        cp /dev/null surrogate.tmp
@@ -1082,20 +1073,17 @@ vstream_test: vstream vstream_test.in vstream_test.ref
        diff vstream_test.ref vstream_test.tmp
        rm -f vstream_test.tmp
 
-mystrtok_test: mystrtok mystrtok.ref
-       $(SHLIB_ENV) ${VALGRIND} ./mystrtok >mystrtok.tmp 2>&1
-       diff mystrtok.ref mystrtok.tmp
-       rm -f mystrtok.tmp
+mystrtok_test: mystrtok_test.o $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS)
 
-known_tcp_ports_test: known_tcp_ports known_tcp_ports.ref
-       $(SHLIB_ENV) ${VALGRIND} ./known_tcp_ports >known_tcp_ports.tmp 2>&1
-       diff known_tcp_ports.ref known_tcp_ports.tmp
-       rm -f known_tcp_ports.tmp
+test_mystrtok: update mystrtok_test
+       $(SHLIB_ENV) ${VALGRIND} ./mystrtok_test
 
-dict_stream_test: dict_stream dict_stream.ref
-       $(SHLIB_ENV) ${VALGRIND} ./dict_stream >dict_stream.tmp 2>&1
-       diff dict_stream.ref dict_stream.tmp
-       rm -f dict_stream.tmp
+dict_stream_test: dict_stream_test.o $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS)
+
+test_dict_stream: update dict_stream_test
+       $(SHLIB_ENV) ${VALGRIND} ./dict_stream_test
 
 dict_inline_pcre_test: dict_open dict_inline_pcre.ref
        (echo get foo; echo get bar) | \
@@ -1124,13 +1112,31 @@ dict_debug_test: dict_open dict_debug_test.sh dict_debug_test.ref
        diff dict_debug_test.ref dict_debug_test.tmp
        rm -f dict_debug_test.tmp
 
-find_inet_test: find_inet find_inet.ref
-       $(SHLIB_ENV) ${VALGRIND} ./find_inet >find_inet.tmp 2>&1
-       diff find_inet.ref find_inet.tmp
-       rm -f find_inet.tmp
-
-argv_test: argv
-       $(SHLIB_ENV) ${VALGRIND} ./argv
+find_inet_service_test: find_inet_service_test.o $(LIB_DIR)/mock_servent.o \
+       $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o find_inet_service.o \
+       $(LIB_DIR)/mock_servent.o $(TESTLIBS) $(LIB) $(SYSLIBS)
+       
+test_find_inet_service: update find_inet_service_test 
+       $(SHLIB_ENV) ${VALGRIND} ./find_inet_service_test
+
+mymalloc_test: mymalloc_test.o $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS)
+       
+test_mymalloc: update mymalloc_test 
+       $(SHLIB_ENV) ${VALGRIND} ./mymalloc_test
+
+msg_output_test: msg_output_test.o $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS)
+       
+test_msg_output: update msg_output_test 
+       $(SHLIB_ENV) ${VALGRIND} ./msg_output_test
+
+argv_test: argv_test.o $(TESTLIBS) $(LIB)
+       $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS)
+       
+test_argv: update argv_test 
+       $(SHLIB_ENV) ${VALGRIND} ./argv_test
 
 inet_prefix_top_test: inet_prefix_top
        $(SHLIB_ENV) ${VALGRIND} ./inet_prefix_top
@@ -1255,6 +1261,22 @@ argv_splitq.o: stringops.h
 argv_splitq.o: sys_defs.h
 argv_splitq.o: vbuf.h
 argv_splitq.o: vstring.h
+argv_test.o: ../../include/msg_jmp.h
+argv_test.o: ../../include/pmock_expect.h
+argv_test.o: ../../include/ptest.h
+argv_test.o: ../../include/ptest_main.h
+argv_test.o: argv.h
+argv_test.o: argv_test.c
+argv_test.o: check_arg.h
+argv_test.o: msg.h
+argv_test.o: msg_output.h
+argv_test.o: msg_vstream.h
+argv_test.o: myrand.h
+argv_test.o: stringops.h
+argv_test.o: sys_defs.h
+argv_test.o: vbuf.h
+argv_test.o: vstream.h
+argv_test.o: vstring.h
 attr_clnt.o: attr.h
 attr_clnt.o: attr_clnt.c
 attr_clnt.o: attr_clnt.h
@@ -1540,6 +1562,22 @@ dict_cidr.o: vbuf.h
 dict_cidr.o: vstream.h
 dict_cidr.o: vstring.h
 dict_cidr.o: warn_stat.h
+dict_cli.o: argv.h
+dict_cli.o: check_arg.h
+dict_cli.o: dict.h
+dict_cli.o: dict_cli.c
+dict_cli.o: dict_db.h
+dict_cli.o: dict_lmdb.h
+dict_cli.o: mkmap.h
+dict_cli.o: msg.h
+dict_cli.o: msg_vstream.h
+dict_cli.o: myflock.h
+dict_cli.o: stringops.h
+dict_cli.o: sys_defs.h
+dict_cli.o: vbuf.h
+dict_cli.o: vstream.h
+dict_cli.o: vstring.h
+dict_cli.o: vstring_vstream.h
 dict_db.o: argv.h
 dict_db.o: check_arg.h
 dict_db.o: dict.h
@@ -1755,14 +1793,20 @@ dict_pipe.o: vbuf.h
 dict_pipe.o: vstream.h
 dict_pipe.o: vstring.h
 dict_pipe_test.o: ../../include/dict_test_helper.h
+dict_pipe_test.o: ../../include/msg_jmp.h
+dict_pipe_test.o: ../../include/pmock_expect.h
+dict_pipe_test.o: ../../include/ptest.h
+dict_pipe_test.o: ../../include/ptest_main.h
 dict_pipe_test.o: argv.h
 dict_pipe_test.o: check_arg.h
 dict_pipe_test.o: dict.h
 dict_pipe_test.o: dict_pipe.h
 dict_pipe_test.o: dict_pipe_test.c
 dict_pipe_test.o: msg.h
+dict_pipe_test.o: msg_output.h
 dict_pipe_test.o: msg_vstream.h
 dict_pipe_test.o: myflock.h
+dict_pipe_test.o: myrand.h
 dict_pipe_test.o: stringops.h
 dict_pipe_test.o: sys_defs.h
 dict_pipe_test.o: vbuf.h
@@ -1859,6 +1903,24 @@ dict_stream.o: sys_defs.h
 dict_stream.o: vbuf.h
 dict_stream.o: vstream.h
 dict_stream.o: vstring.h
+dict_stream_test.o: ../../include/msg_jmp.h
+dict_stream_test.o: ../../include/pmock_expect.h
+dict_stream_test.o: ../../include/ptest.h
+dict_stream_test.o: ../../include/ptest_main.h
+dict_stream_test.o: argv.h
+dict_stream_test.o: check_arg.h
+dict_stream_test.o: dict.h
+dict_stream_test.o: dict_stream_test.c
+dict_stream_test.o: msg.h
+dict_stream_test.o: msg_output.h
+dict_stream_test.o: msg_vstream.h
+dict_stream_test.o: myflock.h
+dict_stream_test.o: myrand.h
+dict_stream_test.o: stringops.h
+dict_stream_test.o: sys_defs.h
+dict_stream_test.o: vbuf.h
+dict_stream_test.o: vstream.h
+dict_stream_test.o: vstring.h
 dict_surrogate.o: argv.h
 dict_surrogate.o: check_arg.h
 dict_surrogate.o: compat_va_copy.h
@@ -1888,22 +1950,6 @@ dict_tcp.o: vbuf.h
 dict_tcp.o: vstream.h
 dict_tcp.o: vstring.h
 dict_tcp.o: vstring_vstream.h
-dict_test.o: argv.h
-dict_test.o: check_arg.h
-dict_test.o: dict.h
-dict_test.o: dict_db.h
-dict_test.o: dict_lmdb.h
-dict_test.o: dict_test.c
-dict_test.o: mkmap.h
-dict_test.o: msg.h
-dict_test.o: msg_vstream.h
-dict_test.o: myflock.h
-dict_test.o: stringops.h
-dict_test.o: sys_defs.h
-dict_test.o: vbuf.h
-dict_test.o: vstream.h
-dict_test.o: vstring.h
-dict_test.o: vstring_vstream.h
 dict_thash.o: argv.h
 dict_thash.o: check_arg.h
 dict_thash.o: dict.h
@@ -1936,14 +1982,20 @@ dict_union.o: vbuf.h
 dict_union.o: vstream.h
 dict_union.o: vstring.h
 dict_union_test.o: ../../include/dict_test_helper.h
+dict_union_test.o: ../../include/msg_jmp.h
+dict_union_test.o: ../../include/pmock_expect.h
+dict_union_test.o: ../../include/ptest.h
+dict_union_test.o: ../../include/ptest_main.h
 dict_union_test.o: argv.h
 dict_union_test.o: check_arg.h
 dict_union_test.o: dict.h
 dict_union_test.o: dict_union.h
 dict_union_test.o: dict_union_test.c
 dict_union_test.o: msg.h
+dict_union_test.o: msg_output.h
 dict_union_test.o: msg_vstream.h
 dict_union_test.o: myflock.h
+dict_union_test.o: myrand.h
 dict_union_test.o: stringops.h
 dict_union_test.o: sys_defs.h
 dict_union_test.o: vbuf.h
@@ -2084,6 +2136,37 @@ find_inet.o: stringops.h
 find_inet.o: sys_defs.h
 find_inet.o: vbuf.h
 find_inet.o: vstring.h
+find_inet_service.o: check_arg.h
+find_inet_service.o: find_inet_service.c
+find_inet_service.o: find_inet_service.h
+find_inet_service.o: known_tcp_ports.h
+find_inet_service.o: msg.h
+find_inet_service.o: sane_strtol.h
+find_inet_service.o: stringops.h
+find_inet_service.o: sys_defs.h
+find_inet_service.o: vbuf.h
+find_inet_service.o: vstring.h
+find_inet_service.o: wrap_netdb.h
+find_inet_service_test.o: ../../include/mock_servent.h
+find_inet_service_test.o: ../../include/msg_jmp.h
+find_inet_service_test.o: ../../include/pmock_expect.h
+find_inet_service_test.o: ../../include/ptest.h
+find_inet_service_test.o: ../../include/ptest_main.h
+find_inet_service_test.o: argv.h
+find_inet_service_test.o: check_arg.h
+find_inet_service_test.o: find_inet_service.h
+find_inet_service_test.o: find_inet_service_test.c
+find_inet_service_test.o: known_tcp_ports.h
+find_inet_service_test.o: msg.h
+find_inet_service_test.o: msg_output.h
+find_inet_service_test.o: msg_vstream.h
+find_inet_service_test.o: myrand.h
+find_inet_service_test.o: stringops.h
+find_inet_service_test.o: sys_defs.h
+find_inet_service_test.o: vbuf.h
+find_inet_service_test.o: vstream.h
+find_inet_service_test.o: vstring.h
+find_inet_service_test.o: wrap_netdb.h
 format_tv.o: check_arg.h
 format_tv.o: format_tv.c
 format_tv.o: format_tv.h
@@ -2119,11 +2202,18 @@ hash_fnv.o: hash_fnv.h
 hash_fnv.o: ldseed.h
 hash_fnv.o: msg.h
 hash_fnv.o: sys_defs.h
+hash_fnv_test.o: ../../include/msg_jmp.h
+hash_fnv_test.o: ../../include/pmock_expect.h
+hash_fnv_test.o: ../../include/ptest.h
+hash_fnv_test.o: ../../include/ptest_main.h
+hash_fnv_test.o: argv.h
 hash_fnv_test.o: check_arg.h
 hash_fnv_test.o: hash_fnv.h
 hash_fnv_test.o: hash_fnv_test.c
 hash_fnv_test.o: msg.h
+hash_fnv_test.o: msg_output.h
 hash_fnv_test.o: msg_vstream.h
+hash_fnv_test.o: myrand.h
 hash_fnv_test.o: stringops.h
 hash_fnv_test.o: sys_defs.h
 hash_fnv_test.o: vbuf.h
@@ -2270,6 +2360,23 @@ known_tcp_ports.o: stringops.h
 known_tcp_ports.o: sys_defs.h
 known_tcp_ports.o: vbuf.h
 known_tcp_ports.o: vstring.h
+known_tcp_ports_test.o: ../../include/msg_jmp.h
+known_tcp_ports_test.o: ../../include/pmock_expect.h
+known_tcp_ports_test.o: ../../include/ptest.h
+known_tcp_ports_test.o: ../../include/ptest_main.h
+known_tcp_ports_test.o: argv.h
+known_tcp_ports_test.o: check_arg.h
+known_tcp_ports_test.o: known_tcp_ports.h
+known_tcp_ports_test.o: known_tcp_ports_test.c
+known_tcp_ports_test.o: msg.h
+known_tcp_ports_test.o: msg_output.h
+known_tcp_ports_test.o: msg_vstream.h
+known_tcp_ports_test.o: myrand.h
+known_tcp_ports_test.o: stringops.h
+known_tcp_ports_test.o: sys_defs.h
+known_tcp_ports_test.o: vbuf.h
+known_tcp_ports_test.o: vstream.h
+known_tcp_ports_test.o: vstring.h
 ldseed.o: iostuff.h
 ldseed.o: ldseed.c
 ldseed.o: ldseed.h
@@ -2294,8 +2401,6 @@ load_file.o: vbuf.h
 load_file.o: vstream.h
 load_file.o: warn_stat.h
 load_lib.o: load_lib.c
-load_lib.o: load_lib.h
-load_lib.o: msg.h
 load_lib.o: sys_defs.h
 logwriter.o: check_arg.h
 logwriter.o: iostuff.h
@@ -2520,6 +2625,7 @@ msg_logger.o: vbuf.h
 msg_logger.o: vstream.h
 msg_logger.o: vstring.h
 msg_output.o: check_arg.h
+msg_output.o: msg.h
 msg_output.o: msg_output.c
 msg_output.o: msg_output.h
 msg_output.o: msg_vstream.h
@@ -2529,6 +2635,24 @@ msg_output.o: sys_defs.h
 msg_output.o: vbuf.h
 msg_output.o: vstream.h
 msg_output.o: vstring.h
+msg_output_test.o: ../../include/match_basic.h
+msg_output_test.o: ../../include/msg_jmp.h
+msg_output_test.o: ../../include/pmock_expect.h
+msg_output_test.o: ../../include/ptest.h
+msg_output_test.o: ../../include/ptest_main.h
+msg_output_test.o: argv.h
+msg_output_test.o: check_arg.h
+msg_output_test.o: msg.h
+msg_output_test.o: msg_output.h
+msg_output_test.o: msg_output_test.c
+msg_output_test.o: msg_vstream.h
+msg_output_test.o: mymalloc.h
+msg_output_test.o: myrand.h
+msg_output_test.o: stringops.h
+msg_output_test.o: sys_defs.h
+msg_output_test.o: vbuf.h
+msg_output_test.o: vstream.h
+msg_output_test.o: vstring.h
 msg_rate_delay.o: check_arg.h
 msg_rate_delay.o: events.h
 msg_rate_delay.o: msg.h
@@ -2573,6 +2697,31 @@ myaddrinfo.o: sys_defs.h
 myaddrinfo.o: valid_hostname.h
 myaddrinfo.o: vbuf.h
 myaddrinfo.o: vstring.h
+myaddrinfo.o: wrap_netdb.h
+myaddrinfo_test.o: ../../include/addrinfo_to_string.h
+myaddrinfo_test.o: ../../include/make_addr.h
+myaddrinfo_test.o: ../../include/match_addr.h
+myaddrinfo_test.o: ../../include/match_basic.h
+myaddrinfo_test.o: ../../include/mock_getaddrinfo.h
+myaddrinfo_test.o: ../../include/msg_jmp.h
+myaddrinfo_test.o: ../../include/pmock_expect.h
+myaddrinfo_test.o: ../../include/ptest.h
+myaddrinfo_test.o: ../../include/ptest_main.h
+myaddrinfo_test.o: argv.h
+myaddrinfo_test.o: check_arg.h
+myaddrinfo_test.o: inet_proto.h
+myaddrinfo_test.o: msg.h
+myaddrinfo_test.o: msg_output.h
+myaddrinfo_test.o: msg_vstream.h
+myaddrinfo_test.o: myaddrinfo.h
+myaddrinfo_test.o: myaddrinfo_test.c
+myaddrinfo_test.o: myrand.h
+myaddrinfo_test.o: stringops.h
+myaddrinfo_test.o: sys_defs.h
+myaddrinfo_test.o: vbuf.h
+myaddrinfo_test.o: vstream.h
+myaddrinfo_test.o: vstring.h
+myaddrinfo_test.o: wrap_netdb.h
 myflock.o: check_arg.h
 myflock.o: msg.h
 myflock.o: myflock.c
@@ -2585,6 +2734,23 @@ mymalloc.o: msg.h
 mymalloc.o: mymalloc.c
 mymalloc.o: mymalloc.h
 mymalloc.o: sys_defs.h
+mymalloc_test.o: ../../include/msg_jmp.h
+mymalloc_test.o: ../../include/pmock_expect.h
+mymalloc_test.o: ../../include/ptest.h
+mymalloc_test.o: ../../include/ptest_main.h
+mymalloc_test.o: argv.h
+mymalloc_test.o: check_arg.h
+mymalloc_test.o: msg.h
+mymalloc_test.o: msg_output.h
+mymalloc_test.o: msg_vstream.h
+mymalloc_test.o: mymalloc.h
+mymalloc_test.o: mymalloc_test.c
+mymalloc_test.o: myrand.h
+mymalloc_test.o: stringops.h
+mymalloc_test.o: sys_defs.h
+mymalloc_test.o: vbuf.h
+mymalloc_test.o: vstream.h
+mymalloc_test.o: vstring.h
 myrand.o: myrand.c
 myrand.o: myrand.h
 myrand.o: sys_defs.h
@@ -2601,6 +2767,23 @@ mystrtok.o: stringops.h
 mystrtok.o: sys_defs.h
 mystrtok.o: vbuf.h
 mystrtok.o: vstring.h
+mystrtok_test.o: ../../include/msg_jmp.h
+mystrtok_test.o: ../../include/pmock_expect.h
+mystrtok_test.o: ../../include/ptest.h
+mystrtok_test.o: ../../include/ptest_main.h
+mystrtok_test.o: argv.h
+mystrtok_test.o: check_arg.h
+mystrtok_test.o: msg.h
+mystrtok_test.o: msg_output.h
+mystrtok_test.o: msg_vstream.h
+mystrtok_test.o: mymalloc.h
+mystrtok_test.o: myrand.h
+mystrtok_test.o: mystrtok_test.c
+mystrtok_test.o: stringops.h
+mystrtok_test.o: sys_defs.h
+mystrtok_test.o: vbuf.h
+mystrtok_test.o: vstream.h
+mystrtok_test.o: vstring.h
 name_code.o: name_code.c
 name_code.o: name_code.h
 name_code.o: sys_defs.h
@@ -2938,17 +3121,6 @@ stream_send_fd.o: iostuff.h
 stream_send_fd.o: msg.h
 stream_send_fd.o: stream_send_fd.c
 stream_send_fd.o: sys_defs.h
-stream_test.o: check_arg.h
-stream_test.o: connect.h
-stream_test.o: htable.h
-stream_test.o: iostuff.h
-stream_test.o: listen.h
-stream_test.o: msg.h
-stream_test.o: msg_vstream.h
-stream_test.o: stream_test.c
-stream_test.o: sys_defs.h
-stream_test.o: vbuf.h
-stream_test.o: vstream.h
 stream_trigger.o: connect.h
 stream_trigger.o: events.h
 stream_trigger.o: iostuff.h
@@ -2957,6 +3129,17 @@ stream_trigger.o: mymalloc.h
 stream_trigger.o: stream_trigger.c
 stream_trigger.o: sys_defs.h
 stream_trigger.o: trigger.h
+sunos5_stream_test.o: check_arg.h
+sunos5_stream_test.o: connect.h
+sunos5_stream_test.o: htable.h
+sunos5_stream_test.o: iostuff.h
+sunos5_stream_test.o: listen.h
+sunos5_stream_test.o: msg.h
+sunos5_stream_test.o: msg_vstream.h
+sunos5_stream_test.o: sunos5_stream_test.c
+sunos5_stream_test.o: sys_defs.h
+sunos5_stream_test.o: vbuf.h
+sunos5_stream_test.o: vstream.h
 sys_compat.o: sys_compat.c
 sys_compat.o: sys_defs.h
 timecmp.o: timecmp.c
@@ -2998,6 +3181,22 @@ unescape.o: sys_defs.h
 unescape.o: unescape.c
 unescape.o: vbuf.h
 unescape.o: vstring.h
+unescape_test.o: ../../include/msg_jmp.h
+unescape_test.o: ../../include/pmock_expect.h
+unescape_test.o: ../../include/ptest.h
+unescape_test.o: ../../include/ptest_main.h
+unescape_test.o: argv.h
+unescape_test.o: check_arg.h
+unescape_test.o: msg.h
+unescape_test.o: msg_output.h
+unescape_test.o: msg_vstream.h
+unescape_test.o: myrand.h
+unescape_test.o: stringops.h
+unescape_test.o: sys_defs.h
+unescape_test.o: unescape_test.c
+unescape_test.o: vbuf.h
+unescape_test.o: vstream.h
+unescape_test.o: vstring.h
 unix_connect.o: connect.h
 unix_connect.o: iostuff.h
 unix_connect.o: msg.h
@@ -3163,6 +3362,9 @@ watchdog.o: posix_signals.h
 watchdog.o: sys_defs.h
 watchdog.o: watchdog.c
 watchdog.o: watchdog.h
+wrap_netdb.o: sys_defs.h
+wrap_netdb.o: wrap_netdb.c
+wrap_netdb.o: wrap_netdb.h
 wrap_stat.o: sys_defs.h
 wrap_stat.o: wrap_stat.c
 wrap_stat.o: wrap_stat.h
index a91b33bfb9371b375676e56bf088300d639468b3..ea76b5152a8deb2226b49bb8a6a857da4a37c6cd 100644 (file)
 #include "vstring.h"
 #include "argv.h"
 
-#ifdef TEST
-extern NORETURN PRINTFLIKE(1, 2) test_msg_panic(const char *,...);
-
-#define msg_panic test_msg_panic
-#endif
-
 /* argv_free - destroy string array */
 
 ARGV   *argv_free(ARGV *argvp)
@@ -296,6 +290,8 @@ void    argv_add(ARGV *argvp,...)
     argvp->argv[argvp->argc] = 0;
 }
 
+#if 0
+
 /* argv_addn - add string to vector */
 
 void    argv_addn(ARGV *argvp,...)
@@ -319,6 +315,8 @@ void    argv_addn(ARGV *argvp,...)
     argvp->argv[argvp->argc] = 0;
 }
 
+#endif
+
 /* argv_addv - optionally create ARGV, append string vector */
 
 ARGV   *argv_addv(ARGV *argvp, const char *const * argv)
@@ -437,400 +435,3 @@ char   *argv_join(VSTRING *buf, ARGV *argv, int delim)
     }
     return (vstring_str(buf));
 }
-
-#ifdef TEST
-
- /*
-  * System library.
-  */
-#include <setjmp.h>
-
- /*
-  * Utility library.
-  */
-#include <msg_vstream.h>
-#include <stringops.h>
-
-#define ARRAY_LEN      (10)
-
-typedef struct TEST_CASE {
-    const char *label;                 /* identifies test case */
-    const char *inputs[ARRAY_LEN];     /* input strings */
-    int     terminate;                 /* terminate result */
-    ARGV   *(*populate_fn) (const struct TEST_CASE *, ARGV *);
-    const char *exp_panic_msg;         /* expected panic */
-    int     exp_argc;                  /* expected array length */
-    const char *exp_argv[ARRAY_LEN];   /* expected array content */
-    int     join_delim;                        /* argv_join() delimiter */
-} TEST_CASE;
-
-#define TERMINATE_ARRAY        (1)
-
-#define        PASS    (0)
-#define FAIL   (1)
-
-VSTRING *test_panic_str;
-jmp_buf test_panic_jbuf;
-
-/* test_msg_panic - does not return, and does not terminate */
-
-void    test_msg_panic(const char *fmt,...)
-{
-    va_list ap;
-
-    va_start(ap, fmt);
-    test_panic_str = vstring_alloc(100);
-    vstring_vsprintf(test_panic_str, fmt, ap);
-    va_end(ap);
-    longjmp(test_panic_jbuf, 1);
-}
-
-/* test_argv_populate - populate result, optionally terminate */
-
-static ARGV *test_argv_populate(const TEST_CASE *tp, ARGV *argvp)
-{
-    const char *const * cpp;
-
-    for (cpp = tp->inputs; *cpp; cpp++)
-       argv_add(argvp, *cpp, (char *) 0);
-    if (tp->terminate)
-       argv_terminate(argvp);
-    return (argvp);
-}
-
-/* test_argv_sort - populate and sort result */
-
-static ARGV *test_argv_sort(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_qsort(argvp, (ARGV_COMPAR_FN) 0);
-    return (argvp);
-}
-
-/* test_argv_sort_uniq - populate, sort, uniq result */
-
-static ARGV *test_argv_sort_uniq(const TEST_CASE *tp, ARGV *argvp)
-{
-
-    /*
-     * This also tests argv_delete().
-     */
-    test_argv_sort(tp, argvp);
-    argv_uniq(argvp, (ARGV_COMPAR_FN) 0);
-    return (argvp);
-}
-
-/* test_argv_good_truncate - populate and truncate to good size */
-
-static ARGV *test_argv_good_truncate(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_truncate(argvp, tp->exp_argc);
-    return (argvp);
-}
-
-/* test_argv_bad_truncate - populate and truncate to bad size */
-
-static ARGV *test_argv_bad_truncate(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_truncate(argvp, -1);
-    return (argvp);
-}
-
-/* test_argv_good_insert - populate and insert at good position */
-
-static ARGV *test_argv_good_insert(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_insert_one(argvp, 1, "new");
-    return (argvp);
-}
-
-/* test_argv_bad_insert1 - populate and insert at bad position */
-
-static ARGV *test_argv_bad_insert1(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_insert_one(argvp, -1, "new");
-    return (argvp);
-}
-
-/* test_argv_bad_insert2 - populate and insert at bad position */
-
-static ARGV *test_argv_bad_insert2(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_insert_one(argvp, 100, "new");
-    return (argvp);
-}
-
-/* test_argv_good_replace - populate and replace at good position */
-
-static ARGV *test_argv_good_replace(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_replace_one(argvp, 1, "new");
-    return (argvp);
-}
-
-/* test_argv_bad_replace1 - populate and replace at bad position */
-
-static ARGV *test_argv_bad_replace1(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_replace_one(argvp, -1, "new");
-    return (argvp);
-}
-
-/* test_argv_bad_replace2 - populate and replace at bad position */
-
-static ARGV *test_argv_bad_replace2(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_replace_one(argvp, 100, "new");
-    return (argvp);
-}
-
-/* test_argv_bad_delete1 - populate and delete at bad position */
-
-static ARGV *test_argv_bad_delete1(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_delete(argvp, -1, 1);
-    return (argvp);
-}
-
-/* test_argv_bad_delete2 - populate and delete at bad position */
-
-static ARGV *test_argv_bad_delete2(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_delete(argvp, 0, -1);
-    return (argvp);
-}
-
-/* test_argv_bad_delete3 - populate and delete at bad position */
-
-static ARGV *test_argv_bad_delete3(const TEST_CASE *tp, ARGV *argvp)
-{
-    test_argv_populate(tp, argvp);
-    argv_delete(argvp, 100, 1);
-    return (argvp);
-}
-
-/* test_argv_join - populate, join, and overwrite */
-
-static ARGV *test_argv_join(const TEST_CASE *tp, ARGV *argvp)
-{
-    VSTRING *buf = vstring_alloc(100);
-
-    /*
-     * Impedance mismatch: argv_join() produces output to VSTRING, but the
-     * test fixture wants output to ARGV.
-     */
-    test_argv_populate(tp, argvp);
-    argv_join(buf, argvp, tp->join_delim);
-    argv_delete(argvp, 0, argvp->argc);
-    argv_add(argvp, vstring_str(buf), ARGV_END);
-    vstring_free(buf);
-    return (argvp);
-}
-
-/* test_argv_addv_appends - populate result */
-
-static ARGV *test_argv_addv_appends(const TEST_CASE *tp, ARGV *argvp)
-{
-    argvp = argv_addv(argvp, tp->inputs);
-    return (argvp);
-}
-
-/* test_argv_addv_creates_appends - populate result */
-
-static ARGV *test_argv_addv_creates(const TEST_CASE *tp, ARGV *argvp)
-{
-    argv_free(argvp);
-    argvp = argv_addv((ARGV *) 0, tp->inputs);
-    return (argvp);
-}
-
-/* test_argv_verify - verify result */
-
-static int test_argv_verify(const TEST_CASE *tp, ARGV *argvp)
-{
-    int     idx;
-
-    if (tp->exp_panic_msg != 0) {
-       if (test_panic_str == 0) {
-           msg_warn("test case '%s': got no panic, want: '%s'",
-                    tp->label, tp->exp_panic_msg);
-           return (FAIL);
-       }
-       if (strcmp(vstring_str(test_panic_str), tp->exp_panic_msg) != 0) {
-           msg_warn("test case '%s': got '%s', want: '%s'",
-                tp->label, vstring_str(test_panic_str), tp->exp_panic_msg);
-           return (FAIL);
-       }
-       return (PASS);
-    }
-    if (test_panic_str != 0) {
-       msg_warn("test case '%s': got '%s', want: no panic",
-                tp->label, vstring_str(test_panic_str));
-       return (FAIL);
-    }
-    if (argvp->argc != tp->exp_argc) {
-       msg_warn("test case '%s': got argc: %ld, want: %d",
-                tp->label, (long) argvp->argc, tp->exp_argc);
-       return (FAIL);
-    }
-    if (argvp->argv[argvp->argc] != 0 && tp->terminate) {
-       msg_warn("test case '%s': got unterminated, want: terminated", tp->label);
-       return (FAIL);
-    }
-    for (idx = 0; idx < argvp->argc; idx++) {
-       if (strcmp(argvp->argv[idx], tp->exp_argv[idx]) != 0) {
-           msg_warn("test case '%s': index %d: got '%s', want: '%s'",
-                    tp->label, idx, argvp->argv[idx], tp->exp_argv[idx]);
-           return (FAIL);
-       }
-    }
-    return (PASS);
-}
-
- /*
-  * The test cases. TODO: argv_addn with good and bad string length.
-  */
-static const TEST_CASE test_cases[] = {
-    {"multiple strings, unterminated array",
-       {"foo", "baz", "bar", 0}, 0, test_argv_populate,
-       0, 3, {"foo", "baz", "bar", 0}
-    },
-    {"multiple strings, terminated array",
-       {"foo", "baz", "bar", 0}, TERMINATE_ARRAY, test_argv_populate,
-       0, 3, {"foo", "baz", "bar", 0}
-    },
-    {"distinct strings, sorted array",
-       {"foo", "baz", "bar", 0}, 0, test_argv_sort,
-       0, 3, {"bar", "baz", "foo", 0}
-    },
-    {"duplicate strings, sorted array",
-       {"foo", "baz", "baz", "bar", 0}, 0, test_argv_sort,
-       0, 4, {"bar", "baz", "baz", "foo", 0}
-    },
-    {"duplicate strings, sorted, uniqued-middle elements",
-       {"foo", "baz", "baz", "bar", 0}, 0, test_argv_sort_uniq,
-       0, 3, {"bar", "baz", "foo", 0}
-    },
-    {"duplicate strings, sorted, uniqued-first elements",
-       {"foo", "bar", "baz", "bar", 0}, 0, test_argv_sort_uniq,
-       0, 3, {"bar", "baz", "foo", 0}
-    },
-    {"duplicate strings, sorted, uniqued-last elements",
-       {"foo", "foo", "baz", "bar", 0}, 0, test_argv_sort_uniq,
-       0, 3, {"bar", "baz", "foo", 0}
-    },
-    {"multiple strings, truncate array by one",
-       {"foo", "baz", "bar", 0}, 0, test_argv_good_truncate,
-       0, 2, {"foo", "baz", 0}
-    },
-    {"multiple strings, truncate whole array",
-       {"foo", "baz", "bar", 0}, 0, test_argv_good_truncate,
-       0, 0, {"foo", "baz", 0}
-    },
-    {"multiple strings, bad truncate",
-       {"foo", "baz", "bar", 0}, 0, test_argv_bad_truncate,
-       "argv_truncate: bad length -1"
-    },
-    {"multiple strings, insert one at good position",
-       {"foo", "baz", "bar", 0}, 0, test_argv_good_insert,
-       0, 4, {"foo", "new", "baz", "bar", 0}
-    },
-    {"multiple strings, insert one at bad position",
-       {"foo", "baz", "bar", 0}, 0, test_argv_bad_insert1,
-       "argv_insert_one bad position: -1"
-    },
-    {"multiple strings, insert one at bad position",
-       {"foo", "baz", "bar", 0}, 0, test_argv_bad_insert2,
-       "argv_insert_one bad position: 100"
-    },
-    {"multiple strings, replace one at good position",
-       {"foo", "baz", "bar", 0}, 0, test_argv_good_replace,
-       0, 3, {"foo", "new", "bar", 0}
-    },
-    {"multiple strings, replace one at bad position",
-       {"foo", "baz", "bar", 0}, 0, test_argv_bad_replace1,
-       "argv_replace_one bad position: -1"
-    },
-    {"multiple strings, replace one at bad position",
-       {"foo", "baz", "bar", 0}, 0, test_argv_bad_replace2,
-       "argv_replace_one bad position: 100"
-    },
-    {"multiple strings, delete one at negative position",
-       {"foo", "baz", "bar", 0}, 0, test_argv_bad_delete1,
-       "argv_delete bad range: (start=-1 count=1)"
-    },
-    {"multiple strings, delete with bad range end",
-       {"foo", "baz", "bar", 0}, 0, test_argv_bad_delete2,
-       "argv_delete bad range: (start=0 count=-1)"
-    },
-    {"multiple strings, delete at too large position",
-       {"foo", "baz", "bar", 0}, 0, test_argv_bad_delete3,
-       "argv_delete bad range: (start=100 count=1)"
-    },
-    {"argv_join, multiple strings",
-       {"foo", "baz", "bar", 0}, 0, test_argv_join,
-       0, 1, {"foo:baz:bar", 0}, ':'
-    },
-    {"argv_join, one string",
-       {"foo", 0}, 0, test_argv_join,
-       0, 1, {"foo", 0}, ':'
-    },
-    {"argv_join, empty",
-       {0}, 0, test_argv_join,
-       0, 1, {"", 0}, ':'
-    },
-    {"argv_addv appends to ARGV",
-       {"foo", "baz", "bar", 0}, /* ignored */ 0, test_argv_addv_appends,
-       0, 3, {"foo", "baz", "bar", 0}
-    },
-    {"argv_addv creates ARGV",
-       {"foo", "baz", "bar", 0}, /* ignored */ 0, test_argv_addv_creates,
-       0, 3, {"foo", "baz", "bar", 0}
-    },
-    0,
-};
-
-int     main(int argc, char **argv)
-{
-    const TEST_CASE *tp;
-    int     pass = 0;
-    int     fail = 0;
-
-    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
-
-    for (tp = test_cases; tp->label != 0; tp++) {
-       int     test_failed;
-       ARGV   *argvp;
-
-       argvp = argv_alloc(1);
-       if (setjmp(test_panic_jbuf) == 0)
-           argvp = tp->populate_fn(tp, argvp);
-       test_failed = test_argv_verify(tp, argvp);
-       if (test_failed) {
-           msg_info("%s: FAIL", tp->label);
-           fail++;
-       } else {
-           msg_info("%s: PASS", tp->label);
-           pass++;
-       }
-       argv_free(argvp);
-       if (test_panic_str) {
-           vstring_free(test_panic_str);
-           test_panic_str = 0;
-       }
-    }
-    msg_info("PASS=%d FAIL=%d", pass, fail);
-    exit(fail != 0);
-}
-
-#endif
index 7331d398056f0787c59c7a4a3db77ee31a605279..0de769daf8016382ace2c544a68f52f361a3c123 100644 (file)
@@ -27,7 +27,9 @@ extern ARGV *argv_sort(ARGV *);               /* backwards compatibility */
 extern ARGV *argv_qsort(ARGV *, ARGV_COMPAR_FN);
 extern ARGV *argv_uniq(ARGV *, ARGV_COMPAR_FN);
 extern void argv_add(ARGV *,...);
+#if 0
 extern void argv_addn(ARGV *,...);
+#endif
 extern ARGV *argv_addv(ARGV *, const char *const *);
 extern void argv_terminate(ARGV *);
 extern void argv_truncate(ARGV *, ssize_t);
diff --git a/postfix/src/util/argv_test.c b/postfix/src/util/argv_test.c
new file mode 100644 (file)
index 0000000..5f510e0
--- /dev/null
@@ -0,0 +1,558 @@
+ /*
+  * Test program to exercise argv.c. See PTEST_README for documentation.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <argv.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+#define MAX_ARR_LEN    (10)
+
+ /*
+  * All test data can fit in the same TEST_DATA structure.
+  */
+typedef struct PTEST_CASE {
+    const char *testname;              /* identifies test case */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *tp);
+} PTEST_CASE;
+
+typedef struct TEST_DATA {
+    const char *inputs[MAX_ARR_LEN];   /* input strings */
+    int     flags;                     /* see below */
+    const char *want_panic_msg;                /* expected panic */
+    ssize_t want_argc;                 /* expected array length */
+    const char *want_argv[MAX_ARR_LEN];        /* expected array content */
+    int     join_delim;                        /* argv_join() delimiter */
+} TEST_DATA;
+
+#define ARGV_TEST_FLAG_TERMINATE       (1<<0)
+
+/* verify_argv - verify result */
+
+static void verify_argv(PTEST_CTX *t, const TEST_DATA *tp, ARGV *argvp)
+{
+    int     idx;
+
+    /*
+     * We don't use eq_argv() here. We must at least once compare argv.c
+     * output against data that was created without using any argv.c code.
+     */
+    if (argvp->argc != tp->want_argc)
+       ptest_fatal(t, "got argc %ld, want %ld",
+                   (long) argvp->argc, (long) tp->want_argc);
+    if (argvp->argv[argvp->argc] != 0 && (tp->flags & ARGV_TEST_FLAG_TERMINATE))
+       ptest_error(t, "got unterminated, want terminated");
+    for (idx = 0; idx < argvp->argc; idx++) {
+       if (strcmp(argvp->argv[idx], tp->want_argv[idx]) != 0) {
+           ptest_error(t, "index %d: got '%s', want '%s'",
+                       idx, argvp->argv[idx], tp->want_argv[idx]);
+       }
+    }
+}
+
+/* wrap_argv_free - in case (void *) != (struct *) */
+
+static void wrap_argv_free(void *context)
+{
+    argv_free((ARGV *) context);
+}
+
+/* populate_argv - populate result, optionally terminate */
+
+static ARGV *populate_argv(PTEST_CTX *t, const TEST_DATA *tp)
+{
+    ARGV   *argvp = argv_alloc(1);
+    const char *const * cpp;
+
+    /*
+     * Some tests use longjmp instead of returning normally, so we use
+     * deferred execution to clean up allocated memory.
+     */
+    ptest_defer(t, wrap_argv_free, (void *) argvp);
+    if (tp->want_panic_msg != 0)
+       expect_ptest_log_event(t, tp->want_panic_msg);
+    for (cpp = tp->inputs; *cpp; cpp++)
+       argv_add(argvp, *cpp, (char *) 0);
+    if (tp->flags & ARGV_TEST_FLAG_TERMINATE)
+       argv_terminate(argvp);
+    return (argvp);
+}
+
+/* test_argv_basic - populate and verify */
+
+static void test_argv_basic(PTEST_CTX *t, const TEST_DATA *tp)
+{
+    ARGV   *argvp = populate_argv(t, tp);
+
+    verify_argv(t, tp, argvp);
+}
+
+static void test_argv_multiple_unterminated(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_argc = 3,
+       .want_argv = {"foo", "baz", "bar", 0},
+    };
+
+    test_argv_basic(t, &test_data);
+}
+
+static void test_argv_multiple_terminated(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .flags = ARGV_TEST_FLAG_TERMINATE,
+       .want_argc = 3,
+       .want_argv = {"foo", "baz", "bar", 0},
+    };
+
+    test_argv_basic(t, &test_data);
+}
+
+/* test_argv_sort - populate and sort result */
+
+static void test_argv_sort(PTEST_CTX *t, const TEST_DATA *tp)
+{
+    ARGV   *argvp = populate_argv(t, tp);
+
+    argv_qsort(argvp, (ARGV_COMPAR_FN) 0);
+    verify_argv(t, tp, argvp);
+}
+
+/* test_argv_sort_distinct - populate and sort distinct strings */
+
+static void test_argv_sort_distinct(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_argc = 3,
+       .want_argv = {"bar", "baz", "foo", 0},
+    };
+
+    test_argv_sort(t, &test_data);
+}
+
+/* test_argv_sort_duplicate - populate and sort duplicate strings      */
+
+static void test_argv_sort_duplicate(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "baz", "bar", 0},
+       .want_argc = 4,
+       .want_argv = {"bar", "baz", "baz", "foo", 0},
+    };
+
+    test_argv_sort(t, &test_data);
+}
+
+/* test_argv_sort_uniq - populate, sort, uniq result */
+
+static void test_argv_sort_uniq(PTEST_CTX *t, const TEST_DATA *tp)
+{
+
+    /*
+     * This also tests argv_delete().
+     */
+    ARGV   *argvp = populate_argv(t, tp);
+
+    argv_qsort(argvp, (ARGV_COMPAR_FN) 0);
+    argv_uniq(argvp, (ARGV_COMPAR_FN) 0);
+    verify_argv(t, tp, argvp);
+}
+
+static void test_argv_sort_uniq_middle(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "baz", "bar", 0},
+       .want_argc = 3,
+       .want_argv = {"bar", "baz", "foo", 0},
+    };
+
+    test_argv_sort_uniq(t, &test_data);
+}
+
+static void test_argv_sort_uniq_first(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "bar", "baz", "bar", 0},
+       .want_argc = 3,
+       .want_argv = {"bar", "baz", "foo", 0},
+    };
+
+    test_argv_sort_uniq(t, &test_data);
+}
+
+static void test_argv_sort_uniq_last(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "foo", "baz", "bar", 0},
+       .want_argc = 3,
+       .want_argv = {"bar", "baz", "foo", 0},
+    };
+
+    test_argv_sort_uniq(t, &test_data);
+}
+
+/* test_argv_truncate - populate and truncate to good size */
+
+static void test_argv_truncate(PTEST_CTX *t, const TEST_DATA *tp)
+{
+    ARGV   *argvp = populate_argv(t, tp);
+
+    argv_truncate(argvp, tp->want_argc);
+    verify_argv(t, tp, argvp);
+}
+
+/* test_argv_good_truncate_one - populate and truncate one */
+
+static void test_argv_good_truncate_one(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_argc = 2,
+       .want_argv = {"foo", "baz", 0},
+    };
+
+    test_argv_truncate(t, &test_data);
+}
+
+/* test_argv_good_truncate_whole_array - populate and truncate all */
+
+static void test_argv_good_truncate_all(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_argc = 0,
+    };
+
+    test_argv_truncate(t, &test_data);
+}
+
+/* test_argv_bad_truncate - populate and truncate to bad size */
+
+static void test_argv_bad_truncate(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_panic_msg = "argv_truncate: bad length -1",
+    };
+    ARGV   *argvp = populate_argv(t, &test_data);
+
+    argv_truncate(argvp, -1);
+    ptest_fatal(t, "argv_delete() returned");
+}
+
+/* test_argv_good_insert - populate and insert at good position */
+
+static void test_argv_good_insert(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_argc = 4,
+       .want_argv = {"foo", "new", "baz", "bar", 0},
+    };
+    ARGV   *argvp = populate_argv(t, &test_data);
+
+    argv_insert_one(argvp, 1, "new");
+    verify_argv(t, &test_data, argvp);
+}
+
+/* test_argv_bad_insert1 - populate and insert at bad position */
+
+static void test_argv_bad_insert1(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_panic_msg = "argv_insert_one bad position: -1",
+    };
+    ARGV   *argvp = populate_argv(t, &test_data);
+
+    argv_insert_one(argvp, -1, "new");
+    ptest_fatal(t, "argv_delete() returned");
+}
+
+/* test_argv_bad_insert2 - populate and insert at bad position */
+
+static void test_argv_bad_insert2(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_panic_msg = "argv_insert_one bad position: 100",
+    };
+    ARGV   *argvp = populate_argv(t, &test_data);
+
+    argv_insert_one(argvp, 100, "new");
+    ptest_fatal(t, "argv_delete() returned");
+}
+
+/* test_argv_good_replace - populate and replace at good position */
+
+static void test_argv_good_replace(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_argc = 3,
+       .want_argv = {"foo", "new", "bar", 0},
+    };
+    ARGV   *argvp = populate_argv(t, &test_data);
+
+    argv_replace_one(argvp, 1, "new");
+    verify_argv(t, &test_data, argvp);
+}
+
+/* test_argv_bad_replace1 - populate and replace at bad position */
+
+static void test_argv_bad_replace1(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_panic_msg = "argv_replace_one bad position: -1",
+    };
+    ARGV   *argvp = populate_argv(t, &test_data);
+
+    argv_replace_one(argvp, -1, "new");
+    ptest_fatal(t, "argv_delete() returned");
+}
+
+/* test_argv_bad_replace2 - populate and replace at bad position */
+
+static void test_argv_bad_replace2(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_panic_msg = "argv_replace_one bad position: 100",
+    };
+    ARGV   *argvp = populate_argv(t, &test_data);
+
+    argv_replace_one(argvp, 100, "new");
+    ptest_fatal(t, "argv_delete() returned");
+}
+
+/* test_argv_bad_delete1 - populate and delete at bad position */
+
+static void test_argv_bad_delete1(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_panic_msg = "argv_delete bad range: (start=-1 count=1)",
+    };
+    ARGV   *argvp = populate_argv(t, &test_data);
+
+    argv_delete(argvp, -1, 1);
+    ptest_fatal(t, "argv_delete() returned");
+}
+
+/* test_argv_bad_delete2 - populate and delete at bad position */
+
+static void test_argv_bad_delete2(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_panic_msg = "argv_delete bad range: (start=0 count=-1)",
+    };
+    ARGV   *argvp = populate_argv(t, &test_data);
+
+    argv_delete(argvp, 0, -1);
+    ptest_fatal(t, "argv_delete() returned");
+}
+
+/* test_argv_bad_delete3 - populate and delete at bad position */
+
+static void test_argv_bad_delete3(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_panic_msg = "argv_delete bad range: (start=100 count=1)",
+    };
+    ARGV   *argvp = populate_argv(t, &test_data);
+
+    argv_delete(argvp, 100, 1);
+    ptest_fatal(t, "argv_delete() returned");
+}
+
+/* test_argv_join - populate, join, and overwrite */
+
+static void test_argv_join(PTEST_CTX *t, const TEST_DATA *tp)
+{
+    ARGV   *argvp = populate_argv(t, tp);
+    VSTRING *buf = vstring_alloc(100);
+
+    /*
+     * Impedance mismatch: argv_join() produces output to VSTRING, but the
+     * test fixture wants output to ARGV.
+     */
+    argv_join(buf, argvp, tp->join_delim);
+    argv_delete(argvp, 0, argvp->argc);
+    argv_add(argvp, vstring_str(buf), ARGV_END);
+    vstring_free(buf);
+    verify_argv(t, tp, argvp);
+}
+
+/* test_argv_join_multiple - join multiple strings */
+
+static void test_argv_join_multiple(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_argc = 1,
+       .want_argv = {"foo:baz:bar", 0},
+       .join_delim = ':',
+    };
+
+    test_argv_join(t, &test_data);
+}
+
+/* test_argv_join_single - handle single-string case */
+
+static void test_argv_join_single(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", 0},
+       .want_argc = 1,
+       .want_argv = {"foo", 0},
+       .join_delim = ':',
+    };
+
+    test_argv_join(t, &test_data);
+}
+
+/* test_argv_join_empty - handle empty-string case */
+
+static void test_argv_join_empty(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {0},
+       .want_argc = 1,
+       .want_argv = {"", 0},
+       .join_delim = ':',
+    };
+
+    test_argv_join(t, &test_data);
+}
+
+/* test_argv_addv_appends - populate result */
+
+static void test_argv_addv_appends(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_argc = 3,
+       .want_argv = {"foo", "baz", "bar", 0},
+    };
+    const TEST_DATA *tp = &test_data;
+    ARGV   *argvp = argv_alloc(1);
+
+    argvp = argv_addv(argvp, tp->inputs);
+    ptest_defer(t, wrap_argv_free, (void *) argvp);
+    verify_argv(t, tp, argvp);
+}
+
+/* test_argv_addv_creates_appends - populate result */
+
+static void test_argv_addv_creates(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    static const TEST_DATA test_data = {
+       .inputs = {"foo", "baz", "bar", 0},
+       .want_argc = 3,
+       .want_argv = {"foo", "baz", "bar", 0},
+    };
+    const TEST_DATA *tp = &test_data;
+    ARGV   *argvp;
+
+    argvp = argv_addv((ARGV *) 0, tp->inputs);
+    ptest_defer(t, wrap_argv_free, (void *) argvp);
+    verify_argv(t, tp, argvp);
+}
+
+ /*
+  * The test cases. TODO: argv_addn with good and bad string length.
+  */
+static const PTEST_CASE ptestcases[] = {
+    {.testname = "multiple strings, unterminated array",
+       .action = test_argv_multiple_unterminated,
+    },
+    {.testname = "multiple strings, terminated array",
+       .action = test_argv_multiple_terminated,
+    },
+    {.testname = "distinct strings, sorted array",
+       .action = test_argv_sort_distinct,
+    },
+    {.testname = "duplicate strings, sorted array",
+       .action = test_argv_sort_duplicate,
+    },
+    {.testname = "duplicate strings, sorted, uniqued-middle elements",
+       .action = test_argv_sort_uniq_middle,
+    },
+    {.testname = "duplicate strings, sorted, uniqued-first elements",
+       .action = test_argv_sort_uniq_first,
+    },
+    {.testname = "duplicate strings, sorted, uniqued-last elements",
+       .action = test_argv_sort_uniq_last,
+    },
+    {.testname = "multiple strings, truncate array by one",
+       .action = test_argv_good_truncate_one,
+    },
+    {.testname = "multiple strings, truncate whole array",
+       .action = test_argv_good_truncate_all,
+    },
+    {.testname = "multiple strings, bad truncate",
+       .action = test_argv_bad_truncate,
+    },
+    {.testname = "multiple strings, insert one at good position",
+       .action = test_argv_good_insert,
+    },
+    {.testname = "multiple strings, insert one at bad position",
+       .action = test_argv_bad_insert1,
+    },
+    {.testname = "multiple strings, insert one at bad position",
+       .action = test_argv_bad_insert2,
+    },
+    {.testname = "multiple strings, replace one at good position",
+       .action = test_argv_good_replace,
+    },
+    {.testname = "multiple strings, replace one at bad position",
+       .action = test_argv_bad_replace1,
+    },
+    {.testname = "multiple strings, replace one at bad position",
+       .action = test_argv_bad_replace2,
+    },
+    {.testname = "multiple strings, delete one at negative position",
+       .action = test_argv_bad_delete1,
+    },
+    {.testname = "multiple strings, delete with bad range end",
+       .action = test_argv_bad_delete2,
+    },
+    {.testname = "multiple strings, delete at too large position",
+       .action = test_argv_bad_delete3,
+    },
+    {.testname = "argv_join, multiple strings",
+       .action = test_argv_join_multiple,
+    },
+    {.testname = "argv_join, one string",
+       .action = test_argv_join_single,
+    },
+    {.testname = "argv_join, empty",
+       .action = test_argv_join_empty,
+    },
+    {.testname = "argv_addv appends to ARGV",
+       .action = test_argv_addv_appends,
+    },
+    {.testname = "argv_addv creates ARGV",
+       .action = test_argv_addv_creates,
+    },
+};
+
+#include <ptest_main.h>
index 0e689192ed142c70248b19f421b268f1da558f7e..efefb885dabbf3ba3f5f9327054cbaf94e6fac46 100644 (file)
@@ -251,7 +251,7 @@ extern DICT *dict_utf8_activate(DICT *);
  /*
   * Driver for interactive or scripted tests.
   */
-void    dict_test(int, char **);
+void    dict_cli(int, char **);
 
  /*
   * Behind-the-scenes support to continue execution with reduced
similarity index 97%
rename from postfix/src/util/dict_test.c
rename to postfix/src/util/dict_cli.c
index 26fcd097580db741391028c1762340b1591a23ad..6c5da1d5ea405c7ab57f5b52e26fe878a58c9199 100644 (file)
@@ -33,7 +33,7 @@ static NORETURN usage(char *myname)
     msg_fatal("usage: %s type:file read|write|create [flags...]", myname);
 }
 
-void    dict_test(int argc, char **argv)
+void    dict_cli(int argc, char **argv)
 {
     VSTRING *keybuf = vstring_alloc(1);
     VSTRING *inbuf = vstring_alloc(1);
@@ -148,6 +148,8 @@ void    dict_test(int argc, char **argv)
        } else if (strcmp(cmd, "masks") == 0 && !key && !value) {
            vstream_printf("DICT_FLAG_PARANOID %s\n",
                           dict_flags_str(DICT_FLAG_PARANOID));
+           vstream_printf("DICT_FLAG_UTF8_MASK %s\n",
+                          dict_flags_str(DICT_FLAG_UTF8_MASK));
        } else {
            vstream_printf("usage: %s\n", USAGE);
        }
index 04724960a12823167e77a370ce263bc81eb5bd30..6e8c361e364110cfd159fb1b6814631e01a6c603 100644 (file)
@@ -1,7 +1,9 @@
 >>> dict_open 'debug:' read
-./dict_open: fatal: open dictionary: expecting "type:name" form instead of "debug:"
+./dict_open: error: open dictionary: expecting "type:name" form instead of "debug:"
+owner=trusted (uid=2147483647)
 >>> dict_open 'debug:missing_colon_and_name' read
-./dict_open: fatal: open dictionary: expecting "type:name" form instead of "missing_colon_and_name"
+./dict_open: error: open dictionary: expecting "type:name" form instead of "missing_colon_and_name"
+owner=trusted (uid=2147483647)
 >>> dict_open 'debug:static:{space in name}' read
 owner=trusted (uid=2147483647)
 > get k
index a5c12a2396433131c7b801e309bab23470aeb99a..e860044371dca57b0dcbef61a77a8457e08959c8 100755 (executable)
@@ -2,10 +2,10 @@
 set -e
 
 echo ">>> dict_open 'debug:' read"
-! ${VALGRIND} ./dict_open 'debug:' read </dev/null || exit 1
+! ${VALGRIND} ./dict_open 'debug:' read </dev/null
 
 echo ">>> dict_open 'debug:missing_colon_and_name' read"
-! ${VALGRIND} ./dict_open 'debug:missing_colon_and_name' read </dev/null || exit 1
+! ${VALGRIND} ./dict_open 'debug:missing_colon_and_name' read </dev/null
 
 echo ">>> dict_open 'debug:static:{space in name}' read"
 ${VALGRIND} ./dict_open 'debug:static:{space in name}' read <<EOF
index d7fab9141280a12fe1181f0d82778a10a75f1b2f..0790bb0f21e5b6720d9126ed1404413d7492d19b 100644 (file)
@@ -485,11 +485,14 @@ DICT   *dict_open(const char *dict_spec, int open_flags, int dict_flags)
     char   *dict_name;
     DICT   *dict;
 
-    if ((dict_name = split_at(saved_dict_spec, ':')) == 0)
-       msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s\"",
-                 dict_spec);
-
-    dict = dict_open3(saved_dict_spec, dict_name, open_flags, dict_flags);
+    if ((dict_name = split_at(saved_dict_spec, ':')) == 0 
+       || *saved_dict_spec == 0 || *dict_name == 0) {
+       dict = dict_surrogate(dict_spec, "", open_flags, dict_flags,
+         "open dictionary: expecting \"type:name\" form instead of \"%s\"",
+                             dict_spec);
+    } else {
+       dict = dict_open3(saved_dict_spec, dict_name, open_flags, dict_flags);
+    }
     myfree(saved_dict_spec);
     return (dict);
 }
@@ -676,7 +679,7 @@ void    dict_type_override(DICT *dict, const char *type)
   */
 int     main(int argc, char **argv)
 {
-    dict_test(argc, argv);
+    dict_cli(argc, argv);
     return (0);
 }
 
index d564b6f1ca540fdb2551a509181352dcabd6c650..6bb0463c018d7e4cc9d3e657644c1dd7aedc5e78 100644 (file)
@@ -37,6 +37,9 @@
 /*     IBM T.J. Watson Research
 /*     P.O. Box 704
 /*     Yorktown Heights, NY 10598, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
 /*--*/
 
 /* System library. */
index 79bf3fa55f3a4af83eefd7f4547aa9e05d853773..ce849777ccaa8894d9b252216ccfabcccafe4faf 100644 (file)
@@ -1,14 +1,11 @@
-/*++
-/* AUTHOR(S)
-/*     Wietse Venema
-/*     porcupine.org
-/*--*/
+ /*
+  * Test program to exercise dict_pipe.c. See PTEST_README for documentation.
+  */
 
  /*
   * System library.
   */
 #include <sys_defs.h>
-#include <stdlib.h>
 #include <string.h>
 
  /*
   */
 #include <argv.h>
 #include <dict_pipe.h>
-#include <msg.h>
-#include <msg_vstream.h>
-#include <stringops.h>
 #include <vstring.h>
 
  /*
-  * Testing library.
+  * Test library.
   */
+#include <ptest.h>
 #include <dict_test_helper.h>
 
-#define LEN(x) VSTRING_LEN(x)
-#define STR(x) vstring_str(x)
-
  /*
-  * TODO(wietse) move these to common testing header file.
+  * The following needs to be large enough to include a null terminator in
+  * every ptestcase.want field.
   */
-#define PASS   1
-#define FAIL   0
+#define MAX_PROBE      5
+
+struct probe {
+    const char *query;
+    const char *want_value;
+    int     want_error;
+};
+
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+    const char *type_name;
+    const struct probe probes[MAX_PROBE];
+} PTEST_CASE;
 
-static VSTRING *msg_buf;
+#define STR_OR_NULL(s) ((s) ? (s) : "null")
+#define STR(x)  vstring_str(x)
 
-static int valid_refcounts_for_good_composite_syntax(void)
+static void valid_refcounts_for_good_composite_syntax(PTEST_CTX *t,
+                                               const struct PTEST_CASE *tp)
 {
     DICT   *dict;
     int     open_flags = O_RDONLY;
@@ -54,173 +61,86 @@ static int valid_refcounts_for_good_composite_syntax(void)
 
     dict_compose_spec(DICT_TYPE_PIPE, component_specs, open_flags, dict_flags,
                      composite_spec, reg_component_specs);
-    dict = dict_open_and_capture_msg(STR(composite_spec), open_flags,
-                                    dict_flags, msg_buf);
+    dict = dict_open(STR(composite_spec), open_flags, dict_flags);
 
-#undef RETURN
-#define RETURN(x) do { \
-       if (dict) dict_close(dict); \
-       vstring_free(composite_spec); \
-       argv_free(reg_component_specs); \
-       return (x); \
-    } while (0);
-
-    if (LEN(msg_buf) > 0) {
-       msg_warn("unexpected dict_open() warning: got %s", STR(msg_buf));
-       RETURN(FAIL);
-    }
     for (cpp = reg_component_specs->argv; *cpp; cpp++) {
        if (dict_handle(*cpp) == 0) {
-           msg_warn("table '%s' is not registered after dict_open()", *cpp);
-           RETURN(FAIL);
+           ptest_fatal(t, "table '%s' is not registered after dict_open()",
+                       *cpp);
        }
     }
     dict_close(dict);
-    dict = 0;
     for (cpp = reg_component_specs->argv; *cpp; cpp++) {
        if (dict_handle(*cpp) != 0) {
-           msg_warn("table '%s' is still registered after dict_close()", *cpp);
-           RETURN(FAIL);
+           ptest_fatal(t, "table '%s' is still registered after dict_close()",
+                       *cpp);
        }
     }
-    RETURN(PASS);
+    vstring_free(composite_spec);
+    argv_free(reg_component_specs);
 }
 
-static int valid_refcounts_for_bad_composite_syntax(void)
+static void test_dict_pipe(PTEST_CTX *t, const struct PTEST_CASE *tp)
 {
     DICT   *dict;
-    int     open_flags = O_RDONLY;
-    int     dict_flags = DICT_FLAG_LOCK;
-    VSTRING *composite_spec = vstring_alloc(100);
-    ARGV   *reg_component_specs = argv_alloc(3);
-    const char *component_specs[] = {
-       "static:foo",
-       "inline{xx=yy}",
-       "unix:passwd.byname",
-       0,
-    };
-    char  **cpp;
-    const char *want_msg = "bad syntax:";
-
-    dict_compose_spec(DICT_TYPE_PIPE, component_specs, open_flags, dict_flags,
-                     composite_spec, reg_component_specs);
-    dict = dict_open_and_capture_msg(STR(composite_spec), open_flags, dict_flags,
-                                    msg_buf);
-
-#undef RETURN
-#define RETURN(x) do { \
-       dict_close(dict); \
-       vstring_free(composite_spec); \
-       argv_free(reg_component_specs); \
-       return (x); \
-    } while (0);
-
-    if (LEN(msg_buf) == 0) {
-       msg_warn("missing dict_open() warning: want '%s'", want_msg);
-       RETURN(FAIL);
-    }
-    if (strstr(STR(msg_buf), want_msg) == 0) {
-       msg_warn("unexpected warning message: got '%s', want '%s'",
-                STR(msg_buf), want_msg);
-       RETURN(FAIL);
-    }
-    for (cpp = reg_component_specs->argv; *cpp; cpp++) {
-       if (dict_handle(*cpp) != 0) {
-           msg_warn("table '%s' is registered after failed dict_open()",
-                    *cpp);
-           RETURN(FAIL);
+    const struct probe *pp;
+    const char *got_value;
+    int     got_error;
+
+    if ((dict = dict_open(tp->type_name, O_RDONLY, 0)) == 0)
+       ptest_fatal(t, "dict_open(\"%s\", O_RDONLY, 0) failed: %m",
+                   tp->type_name);
+    for (pp = tp->probes; pp < tp->probes + MAX_PROBE && pp->query != 0; pp++) {
+       got_value = dict_get(dict, pp->query);
+       got_error = dict->error;
+       if (got_value == 0 && pp->want_value == 0)
+           continue;
+       if (got_value == 0 || pp->want_value == 0) {
+           ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want '%s'",
+                       pp->query, STR_OR_NULL(got_value),
+                       STR_OR_NULL(pp->want_value));
+           break;
        }
+       if (strcmp(got_value, pp->want_value) != 0) {
+           ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want '%s'",
+                       pp->query, got_value, pp->want_value);
+       }
+       if (got_error != pp->want_error)
+           ptest_error(t, "dict_get(dict,\"%s\") error: got %d, want %d",
+                       pp->query, got_error, pp->want_error);
     }
-    RETURN(PASS);
-}
-
-static int propagates_notfound_and_found(void)
-{
-    DICT   *dict;
-    int     open_flags = O_RDONLY;
-    int     dict_flags = DICT_FLAG_LOCK;
-    const char *dict_spec = "pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}";
-    static struct dict_get_verify_data expectations[] = {
-       {.key = "k0",.want_value = 0},
-       {.key = "k1",.want_value = 0},
-       {.key = "k2",.want_value = "v3"},
-       {0},
-    };
-    int     ret;
-
-    dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
-                                    msg_buf);
-    if (LEN(msg_buf) > 0) {
-       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
-       ret = FAIL;
-    } else {
-       ret = dict_get_and_verify_bulk(dict, expectations);
-    }
-    dict_close(dict);
-    return (ret);
-}
-
-static int propagates_notfound_and_error(void)
-{
-    DICT   *dict;
-    int     open_flags = O_RDONLY;
-    int     dict_flags = DICT_FLAG_LOCK;
-    const char *dict_spec = "pipemap:{inline:{k1=v1},fail:fail}";
-    static struct dict_get_verify_data expectations[] = {
-       {.key = "k0",.want_value = 0,.want_error = DICT_ERR_NONE},
-       {.key = "k1",.want_value = 0,.want_error = DICT_ERR_RETRY},
-       {0},
-    };
-    int     ret;
-
-    dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
-                                    msg_buf);
-    if (LEN(msg_buf) > 0) {
-       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
-       ret = FAIL;
-    } else {
-       ret = dict_get_and_verify_bulk(dict, expectations);
-    }
-    dict_close(dict);
-    return (ret);
+    dict_free(dict);
 }
 
-struct TEST_CASE {
-    const char *label;
-    int     (*action) (void);
+static const PTEST_CASE ptestcases[] = {
+    {
+       .testname = "valid refcounts for good composite syntax",
+       .action = valid_refcounts_for_good_composite_syntax,
+    }, {
+       .testname = "successful lookup: inline map + inline map",
+       .action = test_dict_pipe,
+       .type_name = "pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}",
+       .probes = {
+           {"k0", 0},
+           {"k1", 0,},
+           {"k2", "v3"},
+       },
+    }, {
+       .testname = "error propagation: inline map + fail map",
+       .action = test_dict_pipe,
+       .type_name = "pipemap:{inline:{k1=v1},fail:fail}",
+       .probes = {
+           {"k0", 0, 0},
+           {"k1", 0, DICT_ERR_RETRY},
+       },
+    }, {
+       .testname = "error propagation: fail map + inline map",
+       .action = test_dict_pipe,
+       .type_name = "pipemap:{fail:fail,inline:{k1=v1}}",
+       .probes = {
+           {"k1", 0, DICT_ERR_RETRY},
+       },
+    },
 };
 
-static const struct TEST_CASE test_cases[] = {
-    {"valid_refcounts_for_good_composite_syntax", valid_refcounts_for_good_composite_syntax,},
-    {"valid_refcounts_for_bad_composite_syntax", valid_refcounts_for_bad_composite_syntax,},
-    {"propagates_notfound_and_found", propagates_notfound_and_found,},
-    {"propagates_notfound_and_error", propagates_notfound_and_error,},
-    {0},
-};
-
-int     main(int argc, char **argv)
-{
-    static int tests_passed = 0;
-    static int tests_failed = 0;
-    const struct TEST_CASE *tp;
-
-    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
-
-    msg_buf = vstring_alloc(100);
-    dict_allow_surrogate = 1;
-
-    for (tp = test_cases; tp->label; tp++) {
-       msg_info("RUN  %s", tp->label);
-       if (tp->action() == PASS) {
-           msg_info("PASS %s", tp->label);
-           tests_passed += 1;
-       } else {
-           msg_info("FAIL %s", tp->label);
-           tests_failed += 1;
-       }
-    }
-    vstring_free(msg_buf);
-
-    msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed);
-    exit(tests_failed != 0);
-}
+#include <ptest_main.h>
index 446d8f85998b794ceb7d01bb84cc9cfdd3589327..304a2e26f51563d248957d8966d4d21ab6efad94 100644 (file)
@@ -145,130 +145,3 @@ VSTREAM *dict_stream_open(const char *dict_type, const char *mapname,
        return (map_fp);
     }
 }
-
-#ifdef TEST
-
-#include <string.h>
-
-int     main(int argc, char **argv)
-{
-    struct testcase {
-       const char *title;
-       const char *mapname;            /* starts with brace */
-       const char *expect_err;         /* null or message */
-       const char *expect_cont;        /* null or content */
-    };
-
-#define EXP_NOERR      0
-#define EXP_NOCONT     0
-
-#define STRING_OR(s, text_if_null) ((s) ? (s) : (text_if_null))
-#define DICT_TYPE_TEST "test"
-
-    const char rule_spec_error[] = DICT_TYPE_TEST " map: "
-    "syntax error after '}' in \"{blah blah}x\"";
-    const char inline_config_error[] = DICT_TYPE_TEST " map: "
-    "syntax error after '}' in \"{{foo bar}, {blah blah}}x\"";
-    struct testcase testcases[] = {
-       {"normal",
-           "{{foo bar}, {blah blah}}", EXP_NOERR, "foo bar\nblah blah\n"
-       },
-       {"trims leading/trailing wsp around rule-text",
-           "{{ foo bar }, { blah blah }}", EXP_NOERR, "foo bar\nblah blah\n"
-       },
-       {"trims leading/trailing comma-wsp around rule-spec",
-           "{, ,{foo bar}, {blah blah}, ,}", EXP_NOERR, "foo bar\nblah blah\n"
-       },
-       {"empty inline-file",
-           "{, }", EXP_NOERR, ""
-       },
-       {"propagates extpar error for inline-file",
-           "{{foo bar}, {blah blah}}x", inline_config_error, EXP_NOCONT
-       },
-       {"propagates extpar error for rule-spec",
-           "{{foo bar}, {blah blah}x}", rule_spec_error, EXP_NOCONT
-       },
-       0,
-    };
-    struct testcase *tp;
-    VSTRING *act_err = 0;
-    VSTRING *act_cont = vstring_alloc(100);
-    VSTREAM *fp;
-    struct stat st;
-    ssize_t exp_len;
-    ssize_t act_len;
-    int     pass;
-    int     fail;
-
-    for (pass = fail = 0, tp = testcases; tp->title; tp++) {
-       int     test_passed = 0;
-
-       msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title);
-
-#if 0
-       msg_info("title=%s", tp->title);
-       msg_info("mapname=%s", tp->mapname);
-       msg_info("expect_err=%s", STRING_OR_NULL(tp->expect_err));
-       msg_info("expect_cont=%s", STRINGOR_NULL(tp->expect_cont));
-#endif
-
-       if (act_err)
-           VSTRING_RESET(act_err);
-       fp = dict_stream_open(DICT_TYPE_TEST, tp->mapname, O_RDONLY,
-                             0, &st, &act_err);
-       if (fp) {
-           if (tp->expect_err) {
-               msg_warn("test case %s: got stream, expected error", tp->title);
-           } else if (!tp->expect_err && act_err && LEN(act_err) > 0) {
-               msg_warn("test case %s: got error '%s', expected noerror",
-                        tp->title, STR(act_err));
-           } else if (!tp->expect_cont) {
-               msg_warn("test case %s: got stream, expected nostream",
-                        tp->title);
-           } else {
-               exp_len = strlen(tp->expect_cont);
-               if ((act_len = vstream_fread_buf(fp, act_cont, 2 * exp_len)) < 0) {
-                   msg_warn("test case %s: content read error", tp->title);
-               } else {
-                   VSTRING_TERMINATE(act_cont);
-                   if (strcmp(tp->expect_cont, STR(act_cont)) != 0) {
-                       msg_warn("test case %s: got content '%s', expected '%s'",
-                                tp->title, STR(act_cont), tp->expect_cont);
-                   } else {
-                       test_passed = 1;
-                   }
-               }
-           }
-       } else {
-           if (!tp->expect_err) {
-               msg_warn("test case %s: got nostream, expected noerror",
-                        tp->title);
-           } else if (tp->expect_cont) {
-               msg_warn("test case %s: got nostream, expected stream",
-                        tp->title);
-           } else if (strcmp(STR(act_err), tp->expect_err) != 0) {
-               msg_warn("test case %s: got error '%s', expected '%s'",
-                        tp->title, STR(act_err), tp->expect_err);
-           } else {
-               test_passed = 1;
-           }
-
-       }
-       if (test_passed) {
-           msg_info("PASS test %ld", (long) (tp - testcases));
-           pass++;
-       } else {
-           msg_info("FAIL test %ld", (long) (tp - testcases));
-           fail++;
-       }
-       if (fp)
-           vstream_fclose(fp);
-    }
-    if (act_err)
-       vstring_free(act_err);
-    vstring_free(act_cont);
-    msg_info("PASS=%d FAIL=%d", pass, fail);
-    return (fail > 0);
-}
-
-#endif                                 /* TEST */
diff --git a/postfix/src/util/dict_stream.ref b/postfix/src/util/dict_stream.ref
deleted file mode 100644 (file)
index 87c30e5..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-unknown: RUN test case 0 normal
-unknown: PASS test 0
-unknown: RUN test case 1 trims leading/trailing wsp around rule-text
-unknown: PASS test 1
-unknown: RUN test case 2 trims leading/trailing comma-wsp around rule-spec
-unknown: PASS test 2
-unknown: RUN test case 3 empty inline-file
-unknown: PASS test 3
-unknown: RUN test case 4 propagates extpar error for inline-file
-unknown: PASS test 4
-unknown: RUN test case 5 propagates extpar error for rule-spec
-unknown: PASS test 5
-unknown: PASS=6 FAIL=0
diff --git a/postfix/src/util/dict_stream_test.c b/postfix/src/util/dict_stream_test.c
new file mode 100644 (file)
index 0000000..5cbb548
--- /dev/null
@@ -0,0 +1,114 @@
+ /*
+  * Test program to exercise dict_stream.c. See PTEST_README for
+  * documentation.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <vstream.h>
+#include <vstring.h>
+#include <dict.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+    const char *mapname;               /* starts with brace */
+    const char *want_err;              /* null or message */
+    const char *want_cont;             /* null or content */
+} PTEST_CASE;
+
+#define DICT_TYPE_TEST "test"
+#define LEN(x) VSTRING_LEN(x)
+#define STR(x) vstring_str(x)
+
+static void test_dict_stream(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    VSTRING *got_err = 0;
+    VSTRING *got_cont = vstring_alloc(100);
+    VSTREAM *fp;
+    struct stat st;
+    ssize_t want_len;
+    ssize_t got_len;
+
+    fp = dict_stream_open(DICT_TYPE_TEST, tp->mapname, O_RDONLY,
+                         0, &st, &got_err);
+    if (fp) {
+       if (tp->want_err) {
+           ptest_error(t, "got stream, want error '%s'", tp->want_err);
+       } else if (!tp->want_err && got_err && LEN(got_err) > 0) {
+           ptest_error(t, "got error '%s', want noerror", STR(got_err));
+       } else if (!tp->want_cont) {
+           ptest_error(t, "got stream, expected nostream");
+       } else {
+           want_len = strlen(tp->want_cont);
+           if ((got_len = vstream_fread_buf(fp, got_cont, 2 * want_len)) < 0) {
+               ptest_error(t, "content read error");
+           } else {
+               VSTRING_TERMINATE(got_cont);
+               if (strcmp(tp->want_cont, STR(got_cont)) != 0) {
+                   ptest_error(t, "got content '%s', want '%s'",
+                               STR(got_cont), tp->want_cont);
+               }
+           }
+       }
+    } else {
+       if (!tp->want_err) {
+           ptest_error(t, "got nostream, want noerror");
+       } else if (tp->want_cont) {
+           ptest_error(t, "got nostream, want stream");
+       } else if (strcmp(STR(got_err), tp->want_err) != 0) {
+           ptest_error(t, "got error '%s', want '%s'",
+                       STR(got_err), tp->want_err);
+       }
+    }
+    if (fp)
+       vstream_fclose(fp);
+    if (got_err)
+       vstring_free(got_err);
+    vstring_free(got_cont);
+}
+
+
+#define WANT_NOERR     0
+#define WANT_NOCONT    0
+
+const char rule_spec_error[] = DICT_TYPE_TEST " map: "
+"syntax error after '}' in \"{blah blah}x\"";
+
+const char inline_config_error[] = DICT_TYPE_TEST " map: "
+"syntax error after '}' in \"{{foo bar}, {blah blah}}x\"";
+
+static PTEST_CASE ptestcases[] = {
+    {"normal", test_dict_stream,
+       "{{foo bar}, {blah blah}}", WANT_NOERR, "foo bar\nblah blah\n"
+    },
+    {"trims leading/trailing wsp around rule-text", test_dict_stream,
+       "{{ foo bar }, { blah blah }}", WANT_NOERR, "foo bar\nblah blah\n"
+    },
+    {"trims leading/trailing comma-wsp around rule-spec", test_dict_stream,
+       "{, ,{foo bar}, {blah blah}, ,}", WANT_NOERR, "foo bar\nblah blah\n"
+    },
+    {"empty inline-file", test_dict_stream,
+       "{, }", WANT_NOERR, ""
+    },
+    {"propagates extpar error for inline-file", test_dict_stream,
+       "{{foo bar}, {blah blah}}x", inline_config_error, WANT_NOCONT
+    },
+    {"propagates extpar error for rule-spec", test_dict_stream,
+       "{{foo bar}, {blah blah}x}", rule_spec_error, WANT_NOCONT
+    },
+};
+
+#include <ptest_main.h>
index 043abc0718cc566f1c3e36917a2c1c0b859eff07..ff95d96199b2214f55407a9a4a4c098b31ae6b78 100644 (file)
@@ -38,9 +38,7 @@
 /*     Yorktown Heights, NY 10598, USA
 /*
 /*     Wietse Venema
-/*     Google, Inc.
-/*     111 8th Avenue
-/*     New York, NY 10011, USA
+/*     porcupine.org
 /*--*/
 
 /* System library. */
index 0ed87b8921bf5cf0b946a2770e1f1adb930aecb0..3db0bc70cf41f1c383118b0385dcd0c26ee82e81 100644 (file)
@@ -1,14 +1,12 @@
-/*++
-/* AUTHOR(S)
-/*     Wietse Venema
-/*     porcupine.org
-/*--*/
+ /*
+  * Test program to exercise dict_union.c. See PTEST_README for
+  * documentation.
+  */
 
  /*
   * System library.
   */
 #include <sys_defs.h>
-#include <stdlib.h>
 #include <string.h>
 
  /*
   */
 #include <argv.h>
 #include <dict_union.h>
-#include <msg.h>
-#include <msg_vstream.h>
-#include <stringops.h>
 #include <vstring.h>
 
  /*
-  * Testing library.
+  * Test library.
   */
 #include <dict_test_helper.h>
-
-#define LEN(x) VSTRING_LEN(x)
-#define STR(x) vstring_str(x)
+#include <ptest.h>
 
  /*
-  * TODO(wietse) move these to common testing header file.
+  * The following needs to be large enough to include a null terminator in
+  * every ptestcase.want field.
   */
-#define PASS   1
-#define FAIL   0
+#define MAX_PROBE      5
+
+struct probe {
+    const char *query;
+    const char *want_value;
+    int     want_error;
+};
 
-static VSTRING *msg_buf;
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+    const char *type_name;
+    const struct probe probes[MAX_PROBE];
+} PTEST_CASE;
 
-static int valid_refcounts_for_good_composite_syntax(void)
+#define STR_OR_NULL(s) ((s) ? (s) : "null")
+#define STR(x)  vstring_str(x)
+
+static void valid_refcounts_for_good_composite_syntax(PTEST_CTX *t,
+                                               const struct PTEST_CASE *tp)
 {
     DICT   *dict;
     int     open_flags = O_RDONLY;
@@ -54,200 +62,93 @@ static int valid_refcounts_for_good_composite_syntax(void)
 
     dict_compose_spec(DICT_TYPE_UNION, component_specs, open_flags, dict_flags,
                      composite_spec, reg_component_specs);
-    dict = dict_open_and_capture_msg(STR(composite_spec), open_flags, dict_flags,
-                                    msg_buf);
-
-#undef RETURN
-#define RETURN(x) do { \
-       if (dict) dict_close(dict); \
-       vstring_free(composite_spec); \
-       argv_free(reg_component_specs); \
-       return (x); \
-    } while (0);
+    dict = dict_open(STR(composite_spec), open_flags, dict_flags);
 
-    if (LEN(msg_buf) > 0) {
-       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
-       RETURN(FAIL);
-    }
     for (cpp = reg_component_specs->argv; *cpp; cpp++) {
        if (dict_handle(*cpp) == 0) {
-           msg_warn("table '%s' is not registered after dict_open()", *cpp);
-           RETURN(FAIL);
+           ptest_fatal(t, "table '%s' is not registered after dict_open()",
+                       *cpp);
        }
     }
     dict_close(dict);
-    dict = 0;
     for (cpp = reg_component_specs->argv; *cpp; cpp++) {
        if (dict_handle(*cpp) != 0) {
-           msg_warn("table '%s' is still registered after dict_close()", *cpp);
-           RETURN(FAIL);
+           ptest_fatal(t, "table '%s' is still registered after dict_close()",
+                       *cpp);
        }
     }
-    RETURN(PASS);
+    vstring_free(composite_spec);
+    argv_free(reg_component_specs);
 }
 
-static int valid_refcounts_for_bad_composite_syntax(void)
+static void test_dict_union(PTEST_CTX *t, const struct PTEST_CASE *tp)
 {
     DICT   *dict;
-    int     open_flags = O_RDONLY;
-    int     dict_flags = DICT_FLAG_LOCK;
-    VSTRING *composite_spec = vstring_alloc(100);
-    ARGV   *reg_component_specs = argv_alloc(3);
-    const char *component_specs[] = {
-       "static:one",
-       "static:two",
-       "inline{foo=three}",
-       0,
-    };
-    char  **cpp;
-    const char *want_msg = "bad syntax:";
-
-    dict_compose_spec(DICT_TYPE_UNION, component_specs, open_flags, dict_flags,
-                     composite_spec, reg_component_specs);
-    dict = dict_open_and_capture_msg(STR(composite_spec), open_flags,
-                                    dict_flags, msg_buf);
-
-#undef RETURN
-#define RETURN(x) do { \
-       dict_close(dict); \
-       vstring_free(composite_spec); \
-       argv_free(reg_component_specs); \
-       return (x); \
-    } while (0);
-
-    if (LEN(msg_buf) == 0) {
-       msg_warn("missing dict_open() warning: want '%s'", want_msg);
-       RETURN(FAIL);
-    }
-    if (strstr(STR(msg_buf), want_msg) == 0) {
-       msg_warn("unexpected warning message: got '%s', want '%s'",
-                STR(msg_buf), want_msg);
-       RETURN(FAIL);
-    }
-    for (cpp = reg_component_specs->argv; *cpp; cpp++) {
-       if (dict_handle(*cpp) != 0) {
-           msg_warn("table '%s' is registered after failed dict_open()",
-                    *cpp);
-           RETURN(FAIL);
+    const struct probe *pp;
+    const char *got_value;
+    int     got_error;
+
+    dict = dict_open(tp->type_name, O_RDONLY, 0);
+
+    for (pp = tp->probes; pp < tp->probes + MAX_PROBE && pp->query != 0; pp++) {
+       got_value = dict_get(dict, pp->query);
+       got_error = dict->error;
+       if (got_value == 0 && pp->want_value == 0)
+           continue;
+       if (got_value == 0 || pp->want_value == 0) {
+           ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want '%s'",
+                       pp->query, STR_OR_NULL(got_value),
+                       STR_OR_NULL(pp->want_value));
+           break;
        }
-    }
-    RETURN(PASS);
-}
-
-static int propagates_notfound_and_found(void)
-{
-    DICT   *dict;
-    int     open_flags = O_RDONLY;
-    int     dict_flags = DICT_FLAG_LOCK;
-    const char *dict_spec = ("unionmap:{static:one,static:two,"
-                            "inline:{foo=three}}");
-    static struct dict_get_verify_data expectations[] = {
-       {.key = "foo",.want_value = "one,two,three"},
-       {.key = "bar",.want_value = "one,two"},
-       {0},
-    };
-    int     ret;
-
-    dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
-                                    msg_buf);
-    if (LEN(msg_buf) > 0) {
-       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
-       ret = FAIL;
-    } else {
-       ret = dict_get_and_verify_bulk(dict, expectations);
-    }
-    dict_close(dict);
-    return (ret);
-}
-
-static int propagates_error(void)
-{
-    DICT   *dict;
-    int     open_flags = O_RDONLY;
-    int     dict_flags = DICT_FLAG_LOCK;
-    const char *dict_spec = "unionmap:{static:one,fail:fail}";
-    static struct dict_get_verify_data expectations[] = {
-       {.key = "foo",.want_value = 0,.want_error = DICT_ERR_RETRY},
-       {0},
-    };
-    int     ret;
-
-    dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
-                                    msg_buf);
-    if (LEN(msg_buf) > 0) {
-       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
-       ret = FAIL;
-    } else {
-       ret = dict_get_and_verify_bulk(dict, expectations);
-    }
-    dict_close(dict);
-    return (ret);
-}
-
-static int no_comma_for_not_found(void)
-{
-    DICT   *dict;
-    int     open_flags = O_RDONLY;
-    int     dict_flags = DICT_FLAG_LOCK;
-    const char *dict_spec = "unionmap:{regexp:{{/a|c/ 1}},regexp:{{/b|c/ 2}}}";
-    static struct dict_get_verify_data expectations[] = {
-       {.key = "x",.want_value = 0},
-       {.key = "a",.want_value = "1"},
-       {.key = "b",.want_value = "2"},
-       {.key = "c",.want_value = "1,2"},
-       {0},
-    };
-    int     ret;
-
-    dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
-                                    msg_buf);
-    if (LEN(msg_buf) > 0) {
-       msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
-       ret = FAIL;
-    } else {
-       ret = dict_get_and_verify_bulk(dict, expectations);
+       if (strcmp(got_value, pp->want_value) != 0) {
+           ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want '%s'",
+                       pp->query, got_value, pp->want_value);
+       }
+       if (got_error != pp->want_error)
+           ptest_error(t, "dict_get(dict,\"%s\") error: got %d, want %d",
+                       pp->query, got_error, pp->want_error);
     }
     dict_close(dict);
-    return (ret);
 }
 
-struct TEST_CASE {
-    const char *label;
-    int     (*action) (void);
-};
-
-static const struct TEST_CASE test_cases[] = {
-    {"valid_refcounts_for_good_composite_syntax", valid_refcounts_for_good_composite_syntax,},
-    {"valid_refcounts_for_bad_composite_syntax", valid_refcounts_for_bad_composite_syntax,},
-    {"propagates_notfound_and_found", propagates_notfound_and_found,},
-    {"propagates_error", propagates_error,},
-    {"no_comma_for_not_found", no_comma_for_not_found,},
-    {0},
+static const PTEST_CASE ptestcases[] = {
+    {
+       .testname = "valid refcounts for good composite syntax",
+       .action = valid_refcounts_for_good_composite_syntax,
+    }, {
+       .testname = "propagates notfound and found",
+       .action = test_dict_union,
+       .type_name = "unionmap:{static:one,inline:{foo=two}}",
+       .probes = {
+           {"foo", "one,two", DICT_STAT_SUCCESS},
+           {"bar", "one", DICT_STAT_SUCCESS},
+       },
+    }, {
+       .testname = "error propagation: static map + fail map",
+       .action = test_dict_union,
+       .type_name = "unionmap:{static:one,fail:fail}",
+       .probes = {
+           {"foo", 0, DICT_ERR_RETRY},
+       },
+    }, {
+       .testname = "error propagation: fail map + static map",
+       .action = test_dict_union,
+       .type_name = "unionmap:{fail:fail,static:one}",
+       .probes = {
+           {"foo", 0, DICT_ERR_RETRY},
+       },
+    }, {
+       .testname = "no comma for not found",
+       .action = test_dict_union,
+       .type_name = "unionmap:{regexp:{{/a|c/ 1}},regexp:{{/b|c/ 2}}}",
+       .probes = {
+           {.query = "x",.want_value = 0, DICT_STAT_SUCCESS},
+           {.query = "a",.want_value = "1", DICT_STAT_SUCCESS},
+           {.query = "b",.want_value = "2", DICT_STAT_SUCCESS},
+           {.query = "c",.want_value = "1,2", DICT_STAT_SUCCESS},
+       },
+    },
 };
 
-int     main(int argc, char **argv)
-{
-    static int tests_passed = 0;
-    static int tests_failed = 0;
-    const struct TEST_CASE *tp;
-
-    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
-
-    msg_buf = vstring_alloc(100);
-    dict_allow_surrogate = 1;
-
-    for (tp = test_cases; tp->label; tp++) {
-       msg_info("RUN  %s", tp->label);
-       if (tp->action() == PASS) {
-           msg_info("PASS %s", tp->label);
-           tests_passed += 1;
-       } else {
-           msg_info("FAIL %s", tp->label);
-           tests_failed += 1;
-       }
-    }
-    vstring_free(msg_buf);
-
-    msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed);
-    exit(tests_failed != 0);
-}
+#include <ptest_main.h>
diff --git a/postfix/src/util/find_inet_service.c b/postfix/src/util/find_inet_service.c
new file mode 100644 (file)
index 0000000..29948c1
--- /dev/null
@@ -0,0 +1,68 @@
+/*++
+/* NAME
+/*     find_inet_service 3
+/* SUMMARY
+/*     TCP service lookup
+/* SYNOPSIS
+/*     #include <find_inet_service.h>
+/*
+/*     int     find_inet_service(service, proto)
+/*     const char *service;
+/*     const char *proto;
+/* DESCRIPTION
+/*     find_inet_service() looks up the numerical TCP/IP port (in
+/*     host byte order) for the specified service. If the service
+/*     is in numerical form, then that is converted instead.
+/*
+/*     TCP services are mapped with known_tcp_ports(3).
+/* DIAGNOSTICS
+/*     find_inet_service() returns -1 when the service is not
+/*     found, or when a numerical service is out of range.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <wrap_netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+/* Application-specific. */
+
+#include <find_inet_service.h>
+#include <known_tcp_ports.h>
+#include <msg.h>
+#include <sane_strtol.h>
+#include <stringops.h>
+
+/* find_inet_service - translate numerical or symbolic service name */
+
+int     find_inet_service(const char *service, const char *protocol)
+{
+    struct servent *sp;
+    unsigned long lport;
+    char   *cp;
+
+    if (strcmp(protocol, "tcp") == 0)
+       service = filter_known_tcp_port(service);
+    if (alldig(service)) {
+       lport = sane_strtoul(service, &cp, 10);
+       if (*cp != '\0' || errno == ERANGE || lport > 65535)
+           return (-1);                        /* bad numerical service */
+       return ((int) lport);
+    } else {
+       if ((sp = getservbyname(service, protocol)) == 0)
+           return (-1);                        /* bad symbolic service */
+       return (ntohs(sp->s_port));
+    }
+}
diff --git a/postfix/src/util/find_inet_service.h b/postfix/src/util/find_inet_service.h
new file mode 100644 (file)
index 0000000..0f11697
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef _FIND_TCP_PORT_H_INCLUDED_
+#define _FIND_TCP_PORT_H_INCLUDED_
+
+/*++
+/* NAME
+/*     find_inet_service 3h
+/* SUMMARY
+/*     TCP service lookup
+/* SYNOPSIS
+/*     #include <find_inet_service.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * External interface.
+  */
+extern int find_inet_service(const char *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/postfix/src/util/find_inet_service_test.c b/postfix/src/util/find_inet_service_test.c
new file mode 100644 (file)
index 0000000..ba806e8
--- /dev/null
@@ -0,0 +1,119 @@
+ /*
+  * Test program to exercise find_inet_service.c. See pmock_expect_test.c and
+  * PTEST_README for documentation.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+
+ /*
+  * Utility library
+  */
+#include <find_inet_service.h>
+#include <known_tcp_ports.h>
+#include <msg.h>
+
+ /*
+  * Test library.
+  */
+#include <mock_servent.h>
+#include <ptest.h>
+
+struct association {
+    const char *lhs;                   /* service name */
+    const char *rhs;                   /* service port */
+};
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+    struct association associations[10];
+    const char *service;
+    const char *proto;
+    int     want_port;                 /* expected port, host byte order */
+    int     needs_mock;
+} PTEST_CASE;
+
+static void test_find_inet_service(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    struct servent *want_ent = 0;
+    const struct association *ap;
+    int     got_port;
+    const char *err;
+
+    /*
+     * Set up expectations. Note that the test infrastructure will catch
+     * fatal errors and panics for us.
+     */
+    clear_known_tcp_ports();
+    for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++)
+       err = add_known_tcp_port(ap->lhs, ap->rhs);
+    if (err != 0)
+       ptest_fatal(t, "add_known_tcp_port: got err '%s'", err);
+    if (tp->needs_mock) {
+       if (tp->want_port != -1)
+           want_ent = make_servent(tp->service, tp->want_port, tp->proto);
+       else
+           want_ent = 0;
+       expect_getservbyname(1, want_ent, tp->service, tp->proto);
+    }
+
+    /*
+     * Make the call and verify the result. If the call fails with a fatal
+     * error or panic, the test infrastructure will verify that the logging
+     * is as expected.
+     */
+    got_port = find_inet_service(tp->service, tp->proto);
+    if (got_port != tp->want_port) {
+       ptest_error(t, "find_inet_service: got port %d, want %d",
+                   got_port, tp->want_port);
+    }
+    if (want_ent)
+       free_servent(want_ent);
+}
+
+const PTEST_CASE ptestcases[] = {
+    {
+       "good-symbolic",
+       test_find_inet_service,
+        /* association */ {{"foobar", "25252"}, 0},
+        /* service */ "foobar",
+        /* proto */ "tcp",
+        /* want_port */ 25252,
+        /* needs mock */ 0,
+    },
+    {
+       "good-numeric",
+       test_find_inet_service,
+        /* association */ {{"foobar", "25252"}, 0},
+        /* service */ "25252",
+        /* proto */ "tcp",
+        /* want_port */ 25252,
+        /* needs mock */ 0,
+    },
+    {
+       "bad-symbolic",
+       test_find_inet_service,
+        /* association */ {{"foobar", "25252"}, 0},
+        /* service */ "an-impossible-name",
+        /* proto */ "tcp",
+        /* want_port */ -1,
+        /* needs mock */ 1,
+    },
+    {
+       "bad-numeric",
+       test_find_inet_service,
+        /* association */ {{"foobar", "25252"}, 0},
+        /* service */ "123456",
+        /* proto */ "tcp",
+        /* want_port */ -1,
+        /* needs mock */ 0,
+    },
+};
+
+ /*
+  * Test library.
+  */
+#include <ptest_main.h>
index 84f665238eb77d73d9bb518ba8d6be143bd488ef..a2f85eddb07259c7a1b2e424b698a8781b92fbc4 100644 (file)
@@ -1,3 +1,8 @@
+ /*
+  * Test program to exercise the hash_fnv implementation. See PTEST_README
+  * for documentation.
+  */
+
  /*
   * System library.
   */
   * Utility library.
   */
 #include <msg.h>
-#include <msg_vstream.h>
-#include <stringops.h>
 #include <hash_fnv.h>
 
-int     main(int srgc, char **argv)
-{
-    int     pass = 0;
-    int     fail = 0;
+ /*
+  * Test library.
+  */
+#include <ptest.h>
 
-    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *tp);
+    HASH_FNV_T want_hval;
+    const char *str;
+} PTEST_CASE;
+
+static void setup_test(void)
+{
 
     /*
      * Sanity check.
@@ -32,83 +43,39 @@ int     main(int srgc, char **argv)
      */
     if (putenv("NORANDOMIZE=") != 0)
        msg_fatal("putenv(\"NORANDOMIZE=\"): %m");
+}
 
-    /*
-     * Test: hashing produces the expected results.
-     */
-    {
-       struct testcase {
-           HASH_FNV_T hval;
-           const char *str;
-       };
-       static struct testcase testcases[] =
-       {
-#ifdef USE_FNV_32BIT
-           0x1c00fc06UL, "overdeeply",
-           0x1c00fc06UL, "undescript",
-           0x1e1e52a4UL, "fanfold",
-           0x1e1e52a4UL, "phrensied",
-#else
-           0xda19999ec0bda706ULL, "overdeeply",
-           0xd7b9e43f26396a66ULL, "undescript",
-           0xa50c585d385a2604ULL, "fanfold",
-           0x1ec3ef9bb2b734a4ULL, "phrensied",
-#endif
-           0,
-       };
-       struct testcase *tp;
-       HASH_FNV_T hval;
-       int     test_failed;
-
-       for (tp = testcases; tp->str; tp++) {
-           test_failed = 0;
-           msg_info("RUN  hash_fnvz(\"%s\")", tp->str);
-           if ((hval = hash_fnvz(tp->str)) != tp->hval) {
-               msg_warn("hash_fnv(\"%s\") want %lu, got: %lu",
-                        tp->str, (unsigned long) tp->hval,
-                        (unsigned long) hval);
-               test_failed = 1;
-           }
-           if (test_failed) {
-               fail += 1;
-               msg_info("FAIL hash_fnvz(\"%s\")", tp->str);
-           } else {
-               pass += 1;
-               msg_info("PASS hash_fnvz(\"%s\")", tp->str);
-           }
-       }
-    }
+static void test_known_input(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    HASH_FNV_T got_hval;
 
-    /*
-     * Test: hash_fnvz(s) is equivalent to hash_fnv(s, strlen(s)). No need to
-     * verify the actual result; we already did that above.
-     */
-    {
-       const char *strval = "foobar";
-       HASH_FNV_T h1;
-       HASH_FNV_T h2;
+    setup_test();
 
-       msg_info("RUN hash_fnvz(\"%s\") == hash_fnv(\"%s\", %ld)",
-                strval, strval, (long) strlen(strval));
+    if ((got_hval = hash_fnvz(tp->str)) != tp->want_hval)
+       ptest_error(t, "hash_fnvz(\"%s\") got %lu, want %lu",
+                   tp->str, (unsigned long) got_hval,
+                   (unsigned long) tp->want_hval);
 
-       h1 = hash_fnv(strval, strlen(strval));
-       h2 = hash_fnvz(strval);
+    if ((got_hval = hash_fnv(tp->str, strlen(tp->str))) != tp->want_hval)
+       ptest_error(t, "hash_fnv(\"%s\", strlen(\"%s\")) got %lu, want %lu",
+                   tp->str, tp->str, (unsigned long) got_hval,
+                   (unsigned long) tp->want_hval);
+}
 
-       if (h1 == h2) {
-           pass += 1;
-           msg_info("PASS: hash_fnvz(\"%s\") == hash_fnv(\"%s\", %ld)",
-                    strval, strval, (long) strlen(strval));
-       } else {
-           fail += 1;
-           msg_info("FAIL: hash_fnvz(\"%s\") == hash_fnv(\"%s\", %ld)",
-                    strval, strval, (long) strlen(strval));
-       }
-    }
 
+static const PTEST_CASE ptestcases[] =
+{
+#ifdef USE_FNV_32BIT
+    "test_known_input_overdeeply", test_known_input, 0x1c00fc06UL, "overdeeply",
+    "test_known_input_undescript", test_known_input, 0x1c00fc06UL, "undescript",
+    "test_known_input_fanfold", test_known_input, 0x1e1e52a4UL, "fanfold",
+    "test_known_input_phrensied", test_known_input, 0x1e1e52a4UL, "phrensied",
+#else
+    "test_known_input_overdeeply", test_known_input, 0xda19999ec0bda706ULL, "overdeeply",
+    "test_known_input_undescript", test_known_input, 0xd7b9e43f26396a66ULL, "undescript",
+    "test_known_input_fanfold", test_known_input, 0xa50c585d385a2604ULL, "fanfold",
+    "test_known_input_phrensied", test_known_input, 0x1ec3ef9bb2b734a4ULL, "phrensied",
+#endif
+};
 
-    /*
-     * Wrap up.
-     */
-    msg_info("PASS=%d FAIL=%d", pass, fail);
-    return (fail != 0);
-}
+#include <ptest_main.h>
index 1d524d4d37349c8ffd2ccf2f7bbd7b911df90e1e..3ddc084fd14a3f6efb78ebc08a9bf3657c1ab08a 100644 (file)
@@ -78,6 +78,8 @@ const char *add_known_tcp_port(const char *name, const char *port)
        return ("numerical service name");
     if (!alldig(port))
        return ("non-numerical service port");
+    if (strlen(port) > 5 || (strlen(port) == 5 && strcmp(port, "65535") > 0))
+       return ("port number out of range");
     if (known_tcp_ports == 0)
        known_tcp_ports = htable_create(10);
     if (htable_locate(known_tcp_ports, name) != 0)
@@ -139,115 +141,3 @@ char   *export_known_tcp_ports(VSTRING *out)
     VSTRING_TERMINATE(out);
     return (STR(out));
 }
-
-#ifdef TEST
-
-#include <msg.h>
-
-struct association {
-    const char *lhs;                   /* service name */
-    const char *rhs;                   /* service port */
-};
-
-struct probe {
-    const char *query;                 /* query */
-    const char *exp_reply;             /* expected reply */
-};
-
-struct test_case {
-    const char *label;                 /* identifies test case */
-    struct association associations[10];
-    const char *exp_err;               /* expected error */
-    const char *exp_export;            /* expected export output */
-    struct probe probes[10];
-};
-
-struct test_case test_cases[] = {
-    {"good",
-        /* association */ {{"smtp", "25"}, {"lmtp", "24"}, 0},
-        /* error */ 0,
-        /* export */ "lmtp=24 smtp=25",
-        /* probe */ {{"smtp", "25"}, {"1", "1"}, {"x", "x"}, {"lmtp", "24"}, 0}
-    },
-    {"duplicate lhs",
-        /* association */ {{"smtp", "25"}, {"smtp", "100"}, 0},
-        /* error */ "duplicate service name"
-    },
-    {"numerical lhs",
-        /* association */ {{"100", "100"}, 0},
-        /* error */ "numerical service name"
-    },
-    {"symbolic rhs",
-        /* association */ {{"smtp", "lmtp"}, 0},
-        /* error */ "non-numerical service port"
-    },
-    {"uninitialized",
-        /* association */ {0},
-        /* error */ 0,
-        /* export */ "",
-        /* probe */ {{"smtp", "smtp"}, {"1", "1"}, {"x", "x"}, 0}
-    },
-    0,
-};
-
-int     main(int argc, char **argv)
-{
-    VSTRING *export_buf;
-    struct test_case *tp;
-    struct association *ap;
-    struct probe *pp;
-    int     pass = 0;
-    int     fail = 0;
-    const char *err;
-    int     test_failed;
-    const char *reply;
-    const char *export;
-
-#define STRING_OR_NULL(s) ((s) ? (s) : "(null)")
-
-    export_buf = vstring_alloc(100);
-    for (tp = test_cases; tp->label != 0; tp++) {
-       test_failed = 0;
-       for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++)
-           err = add_known_tcp_port(ap->lhs, ap->rhs);
-       if (!err != !tp->exp_err) {
-           msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
-              tp->label, STRING_OR_NULL(err), STRING_OR_NULL(tp->exp_err));
-           test_failed = 1;
-       } else if (err != 0) {
-           if (strcmp(err, tp->exp_err) != 0) {
-               msg_warn("test case %s: got err: \"%s\", want: \"%s\"",
-                        tp->label, err, tp->exp_err);
-               test_failed = 1;
-           }
-       } else {
-           export = export_known_tcp_ports(export_buf);
-           if (strcmp(export, tp->exp_export) != 0) {
-               msg_warn("test case %s: got export: \"%s\", want: \"%s\"",
-                        tp->label, export, tp->exp_export);
-               test_failed = 1;
-           }
-           for (pp = tp->probes; test_failed == 0 && pp->query != 0; pp++) {
-               reply = filter_known_tcp_port(pp->query);
-               if (strcmp(reply, pp->exp_reply) != 0) {
-                   msg_warn("test case %s: got reply: \"%s\", want: \"%s\"",
-                            tp->label, reply, pp->exp_reply);
-                   test_failed = 1;
-               }
-           }
-       }
-       clear_known_tcp_ports();
-       if (test_failed) {
-           msg_info("%s: FAIL", tp->label);
-           fail++;
-       } else {
-           msg_info("%s: PASS", tp->label);
-           pass++;
-       }
-    }
-    msg_info("PASS=%d FAIL=%d", pass, fail);
-    vstring_free(export_buf);
-    exit(fail != 0);
-}
-
-#endif
diff --git a/postfix/src/util/known_tcp_ports.ref b/postfix/src/util/known_tcp_ports.ref
deleted file mode 100644 (file)
index adcf182..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-unknown: good: PASS
-unknown: duplicate lhs: PASS
-unknown: numerical lhs: PASS
-unknown: symbolic rhs: PASS
-unknown: uninitialized: PASS
-unknown: PASS=5 FAIL=0
diff --git a/postfix/src/util/known_tcp_ports_test.c b/postfix/src/util/known_tcp_ports_test.c
new file mode 100644 (file)
index 0000000..2655de8
--- /dev/null
@@ -0,0 +1,116 @@
+ /*
+  * Test program to exercise known_tcp_ports.c. See PTEST_README for
+  * documentation.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library
+  */
+#include <known_tcp_ports.h>
+#include <msg.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+struct association {
+    const char *lhs;                   /* service name */
+    const char *rhs;                   /* service port */
+};
+
+struct probe {
+    const char *query;                 /* query */
+    const char *want_reply;            /* expected reply */
+};
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* identifies test case */
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+    struct association associations[10];
+    const char *want_err;              /* expected error */
+    const char *want_export;           /* expected export output */
+    struct probe probes[10];
+} PTEST_CASE;
+
+static void test_known_tcp_ports(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    VSTRING *export_buf;
+    const struct association *ap;
+    const struct probe *pp;
+    const char *got_err;
+    const char *got_reply;
+    const char *got_export;
+
+#define STRING_OR_NULL(s) ((s) ? (s) : "(null)")
+
+    export_buf = vstring_alloc(100);
+    for (got_err = 0, ap = tp->associations; got_err == 0 && ap->lhs != 0; ap++)
+       got_err = add_known_tcp_port(ap->lhs, ap->rhs);
+    if (!got_err != !tp->want_err) {
+       ptest_error(t, "got error '%s', want '%s'",
+                   STRING_OR_NULL(got_err), STRING_OR_NULL(tp->want_err));
+    } else if (got_err != 0) {
+       if (strcmp(got_err, tp->want_err) != 0) {
+           ptest_error(t, "got err '%s', want '%s'", got_err, tp->want_err);
+       }
+    } else {
+       got_export = export_known_tcp_ports(export_buf);
+       if (strcmp(got_export, tp->want_export) != 0) {
+           ptest_error(t, "got export '%s', want '%s'",
+                       got_export, tp->want_export);
+       }
+       for (pp = tp->probes; pp->query != 0; pp++) {
+           got_reply = filter_known_tcp_port(pp->query);
+           if (strcmp(got_reply, pp->want_reply) != 0) {
+               ptest_error(t, "got reply '%s', want '%s'",
+                           got_reply, pp->want_reply);
+               break;
+           }
+       }
+    }
+    clear_known_tcp_ports();
+    vstring_free(export_buf);
+}
+
+const PTEST_CASE ptestcases[] = {
+    {"good", test_known_tcp_ports,
+        /* association */ {{"smtp", "25"}, {"lmtp", "24"}, 0},
+        /* error */ 0,
+        /* export */ "lmtp=24 smtp=25",
+        /* probe */ {{"smtp", "25"}, {"1", "1"}, {"x", "x"}, {"lmtp", "24"}, 0}
+    },
+    {"duplicate lhs", test_known_tcp_ports,
+        /* association */ {{"smtp", "25"}, {"smtp", "100"}, 0},
+        /* error */ "duplicate service name"
+    },
+    {"numerical lhs", test_known_tcp_ports,
+        /* association */ {{"100", "100"}, 0},
+        /* error */ "numerical service name"
+    },
+    {"symbolic rhs", test_known_tcp_ports,
+        /* association */ {{"smtp", "lmtp"}, 0},
+        /* error */ "non-numerical service port"
+    },
+    {"uninitialized", test_known_tcp_ports,
+        /* association */ {0},
+        /* error */ 0,
+        /* export */ "",
+        /* probe */ {{"smtp", "smtp"}, {"1", "1"}, {"x", "x"}, 0}
+    },
+    {"too large", test_known_tcp_ports,
+        /* association */ {{"one", "65535"}, {"two", "65536"}, 0},
+        /* error */ "port number out of range",
+    },
+};
+
+ /*
+  * Test library.
+  */
+#include <ptest_main.h>
index 70c6eab083635934af52671e78abdab9ae3bee73..5109068bd526263409fff781d2d81985a14a5bcd 100644 (file)
@@ -59,6 +59,9 @@
 /*     int     count;
 /*
 /*     void    msg_error_clear()
+/*
+/*     void    msg_set_longjmp_action(
+/*     NORETURN (*longjmp_action) (int)
 /* DESCRIPTION
 /*     This module reports diagnostics. By default, diagnostics are sent
 /*     to the standard error stream, but the disposition can be changed
 /*     This protection exists under the condition that these
 /*     specific resources are accessed exclusively via the msg_info()
 /*     etc.  functions.
+/* EXCEPTIONS AND NON-PRODUCTION TESTS
+/* .ad
+/* .fi
+/*     The default action for msg_fatal*() and msg_panic() is to
+/*     terminate the program. In order to support non-production tests,
+/*     the following function implements support to maintain control
+/*     over the execution.
+/*
+/*     msg_set_longjmp_action() accepts a pointer to non-returning
+/*     function that will be called by msg_fatal*() and msg_panic(),
+/*     with an argument of MSG_LONGJMP_FATAL and MSG_LONGJMP_PANIC,
+/*     respectively. Specify a null argument to disable this feature.
 /* SEE ALSO
 /*     msg_output(3) specify diagnostics disposition
-/*     msg_stdio(3) direct diagnostics to standard I/O stream
 /*     msg_vstream(3) direct diagnostics to VSTREAM.
 /*     msg_syslog(3) direct diagnostics to syslog daemon
 /* BUGS
 /*     IBM T.J. Watson Research
 /*     P.O. Box 704
 /*     Yorktown Heights, NY 10598, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
 /*--*/
 
 /* System libraries. */
@@ -179,6 +196,7 @@ int     msg_verbose = 0;
  /*
   * Private state.
   */
+MSG_LONGJMP_ACTION msg_longjmp_action;
 static MSG_CLEANUP_FN msg_cleanup_fn = 0;
 static int msg_error_count = 0;
 static int msg_error_bound = 13;
@@ -262,6 +280,11 @@ NORETURN vmsg_fatal(const char *fmt, va_list ap)
        if (msg_cleanup_fn)
            msg_cleanup_fn();
     }
+    if (msg_longjmp_action) {
+       /* This code is reachable during testing only. */
+       msg_exiting = 0;
+       msg_longjmp_action(MSG_LONGJMP_FATAL);
+    }
     sleep(1);
     /* In case we're running as a signal handler. */
     _exit(1);
@@ -285,6 +308,11 @@ NORETURN vmsg_fatal_status(int status, const char *fmt, va_list ap)
        if (msg_cleanup_fn)
            msg_cleanup_fn();
     }
+    if (msg_longjmp_action) {
+       /* This code is reachable during testing only. */
+       msg_exiting = 0;
+       msg_longjmp_action(MSG_LONGJMP_FATAL);
+    }
     sleep(1);
     /* In case we're running as a signal handler. */
     _exit(status);
@@ -306,6 +334,11 @@ NORETURN vmsg_panic(const char *fmt, va_list ap)
     if (msg_exiting++ == 0) {
        msg_vprintf(MSG_PANIC, fmt, ap);
     }
+    if (msg_longjmp_action) {
+       /* This code is reachable during testing only. */
+       msg_exiting = 0;
+       msg_longjmp_action(MSG_LONGJMP_PANIC);
+    }
     sleep(1);
     abort();                                   /* Die! */
     /* In case we're running as a signal handler. */
@@ -338,3 +371,10 @@ void    msg_error_clear(void)
 {
     msg_error_count = 0;
 }
+
+/* msg_set_longjmp_action() - exception handling */
+
+void    msg_set_longjmp_action(MSG_LONGJMP_ACTION action)
+{
+    msg_longjmp_action = action;
+}
index 6c75bafc1147dcf3def8135d44b87c976a94ef8e..994290c8139b72b669dd54280f6ac0927615b82e 100644 (file)
@@ -46,6 +46,15 @@ extern void PRINTFLIKE(4, 5) msg_rate_delay(time_t *, int,
                      void PRINTFPTRLIKE(1, 2) (*log_fn) (const char *,...),
                                                    const char *,...);
 
+ /*
+  * Exception call-back handler for unit tests.
+  */
+typedef NORETURN(*MSG_LONGJMP_ACTION) (int);
+extern void msg_set_longjmp_action(MSG_LONGJMP_ACTION);
+
+#define MSG_LONGJMP_FATAL      2       /* msg_fatal longjmp code */
+#define MSG_LONGJMP_PANIC      3       /* msg_panic longjmp code */
+
 /* LICENSE
 /* .ad
 /* .fi
@@ -55,6 +64,9 @@ extern void PRINTFLIKE(4, 5) msg_rate_delay(time_t *, int,
 /*     IBM T.J. Watson Research
 /*     P.O. Box 704
 /*     Yorktown Heights, NY 10598, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
 /*--*/
 
 #endif
index 6a999f64569a41f775779a03ac5a3f99167dd04c..c36659ffc14874d2dc612c5927033490ced22079 100644 (file)
@@ -165,7 +165,7 @@ static void msg_logger_disconnect(void)
 
 /* msg_logger_print - log info to service or file */
 
-static void msg_logger_print(int level, const char *text)
+static void msg_logger_print(int level, const char *text, void *unused)
 {
     time_t  now;
     struct tm *lt;
@@ -288,7 +288,7 @@ void    msg_logger_init(const char *progname, const char *hostname,
      */
     if (first_call) {
        first_call = 0;
-       msg_output(msg_logger_print);
+       msg_output_push(msg_logger_print, (void *) 0);
        msg_logger_buf = vstring_alloc(2048);
     }
 
index 6663877d7367170b24a1412aca75299ef3b53b8f..d49884a4e6ccbb4a3f9c759bc4576fb5fb598437 100644 (file)
@@ -6,10 +6,15 @@
 /* SYNOPSIS
 /*     #include <msg_output.h>
 /*
-/*     typedef void (*MSG_OUTPUT_FN)(int level, char *text)
+/*     typedef void (*MSG_OUTPUT_FN)(int level, const char *text)
 /*
-/*     void    msg_output(output_fn)
+/*     void    msg_output_push(output_fn, context)
 /*     MSG_OUTPUT_FN output_fn;
+/*     void    *context;
+/*
+/*     msg_output_pop(output_fn, context)
+/*     MSG_OUTPUT_FN output_fn
+/*     void    *context;
 /*
 /*     void    msg_printf(level, format, ...)
 /*     int     level;
 /*     This module implements low-level output management for the
 /*     msg(3) diagnostics interface.
 /*
-/*     msg_output() registers an output handler for the diagnostics
-/*     interface. An application can register multiple output handlers.
-/*     Output handlers are called in the specified order.
-/*     An output handler takes as arguments a severity level (MSG_INFO,
-/*     MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC, monotonically increasing
-/*     integer values ranging from 0 to MSG_LAST) and pre-formatted,
-/*     sanitized, text in the form of a null-terminated string.
+/*     msg_output_push() registers an output handler for the diagnostics
+/*     interface, ignoring a duplicate request. Output handlers are
+/*     called in the specified order. An output handler takes as
+/*     arguments: a severity level (MSG_INFO, MSG_WARN, MSG_ERROR,
+/*     MSG_FATAL, MSG_PANIC, i.e., integer values ranging from 0 to
+/*     MSG_LAST inclusive); pre-formatted, sanitized, text in the form
+/*     of a null-terminated string; and the caller-provided context.
+/*
+/*     msg_output_pop() unregisters the specified output handler and
+/*     context, and later registrations made with msg_output_push(). It
+/*     invokes a panic when the handler and context are not found.
 /*
 /*     msg_printf() and msg_vprintf() format their arguments, sanitize the
 /*     result, and call the output handlers registered with msg_output().
@@ -49,9 +58,7 @@
 /*     block the process.
 /* .IP \(bu
 /*     The signal handlers must call the above output routines not
-/*     until after msg_output() completes initialization, and not
-/*     until after the first formatted output to a VSTRING or
-/*     VSTREAM.
+/*     until after msg_output() completes initialization.
 /* .IP \(bu
 /*     Each msg_output() call-back function, and each Postfix or
 /*     system function called by that call-back function, either
@@ -80,6 +87,9 @@
 /*     Google, Inc.
 /*     111 8th Avenue
 /*     New York, NY 10011, USA
+/*
+/*     Wietse Venema
+/*     porcupine.org
 /*--*/
 
 /* System library. */
 #include <vstream.h>
 #include <msg_vstream.h>
 #include <stringops.h>
+#include <msg.h>
 #include <msg_output.h>
 
  /*
@@ -106,16 +117,22 @@ volatile int msg_vprintf_level;
   * Private state. Allow one nested call, so that one logging error can be
   * reported to stderr before bailing out.
   */
+typedef struct MSG_OUT_INFO {
+    MSG_OUTPUT_FN output_fn;
+    void   *context;
+} MSG_OUT_INFO;
+
 #define MSG_OUT_NESTING_LIMIT  2
-static MSG_OUTPUT_FN *msg_output_fn = 0;
+static MSG_OUT_INFO *msg_out_info = 0;
 static int msg_output_fn_count = 0;
 static VSTRING *msg_buffers[MSG_OUT_NESTING_LIMIT];
 
-/* msg_output - specify output handler */
+/* msg_output_push - specify output handler and context */
 
-void    msg_output(MSG_OUTPUT_FN output_fn)
+void    msg_output_push(MSG_OUTPUT_FN output_fn, void *context)
 {
     int     i;
+    MSG_OUT_INFO *mp;
 
     /*
      * Allocate all resources during initialization. This may result in a
@@ -126,16 +143,26 @@ void    msg_output(MSG_OUTPUT_FN output_fn)
            msg_buffers[i] = vstring_alloc(100);
     }
 
+    /*
+     * Deduplicate requests. This is a purely defensive measure for the case
+     * that msg_vstream_init() etc. are called more than once.
+     */
+    for (mp = msg_out_info; mp < msg_out_info + msg_output_fn_count; mp++)
+       if (mp->output_fn == output_fn && mp->context == context)
+           return;
+
     /*
      * We're not doing this often, so avoid complexity and allocate memory
      * for an exact fit.
      */
     if (msg_output_fn_count == 0)
-       msg_output_fn = (MSG_OUTPUT_FN *) mymalloc(sizeof(*msg_output_fn));
+       msg_out_info = (MSG_OUT_INFO *) mymalloc(sizeof(*msg_out_info));
     else
-       msg_output_fn = (MSG_OUTPUT_FN *) myrealloc((void *) msg_output_fn,
-                       (msg_output_fn_count + 1) * sizeof(*msg_output_fn));
-    msg_output_fn[msg_output_fn_count++] = output_fn;
+       msg_out_info = (MSG_OUT_INFO *) myrealloc((void *) msg_out_info,
+                        (msg_output_fn_count + 1) * sizeof(*msg_out_info));
+    msg_out_info[msg_output_fn_count++] = (MSG_OUT_INFO) {
+       output_fn, context
+    };
 }
 
 /* msg_printf - format text and log it */
@@ -159,7 +186,7 @@ void    msg_vprintf(int level, const char *format, va_list ap)
 
     if (msg_vprintf_level < MSG_OUT_NESTING_LIMIT) {
        msg_vprintf_level += 1;
-       /* On-the-fly initialization for test programs and startup errors. */
+       /* On-the-fly initialization for test programs and startup errors.  */
        if (msg_output_fn_count == 0)
            msg_vstream_init("unknown", VSTREAM_ERR);
        vp = msg_buffers[msg_vprintf_level - 1];
@@ -167,8 +194,26 @@ void    msg_vprintf(int level, const char *format, va_list ap)
        vstring_vsprintf(vp, format, ap);
        printable(vstring_str(vp), '?');
        for (i = 0; i < msg_output_fn_count; i++)
-           msg_output_fn[i] (level, vstring_str(vp));
+           msg_out_info[i].output_fn(level, vstring_str(vp),
+                                     msg_out_info[i].context);
        msg_vprintf_level -= 1;
     }
     errno = saved_errno;
 }
+
+/* msg_output_pop - pop output handler and context */
+
+void    msg_output_pop(MSG_OUTPUT_FN output_fn, void *context)
+{
+    MSG_OUT_INFO *mp;
+
+    for (mp = msg_out_info; /* See below */ ; mp++) {
+       if (mp >= msg_out_info + msg_output_fn_count)
+           msg_panic("msg_output_pop: handler %p and context %p not found",
+                     (void *) output_fn, (void *) context);
+       if (mp->output_fn == output_fn && mp->context == context) {
+           msg_output_fn_count = mp - msg_out_info;
+           return;
+       }
+    }
+}
index bd84276f6fd8065cdb24a7e7f2a0be9b27c05cf7..b03e33cf65d4a358d19be46ea406fdf89927d5d6 100644 (file)
@@ -19,8 +19,9 @@
   * External interface. Severity levels are documented to be monotonically
   * increasing from 0 up to MSG_LAST.
   */
-typedef void (*MSG_OUTPUT_FN) (int, const char *);
-extern void msg_output(MSG_OUTPUT_FN);
+typedef void (*MSG_OUTPUT_FN) (int, const char *, void *);
+extern void msg_output_push(MSG_OUTPUT_FN, void *);
+extern void msg_output_pop(MSG_OUTPUT_FN, void *);
 extern void PRINTFLIKE(2, 3) msg_printf(int, const char *,...);
 extern void msg_vprintf(int, const char *, va_list);
 
diff --git a/postfix/src/util/msg_output_test.c b/postfix/src/util/msg_output_test.c
new file mode 100644 (file)
index 0000000..f7a3168
--- /dev/null
@@ -0,0 +1,166 @@
+ /*
+  * Test program to exercise the msg_output module. See PTEST_README for
+  * documentation.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <argv.h>
+#include <msg.h>
+#include <msg_output.h>
+#include <mymalloc.h>
+#include <vstring.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+#include <match_basic.h>
+
+ /*
+  * Note: PTEST_RUN() calls msg_output_push() and msg_output_pop() before and
+  * after running a test, so that the tests below don't need to pop the
+  * handlers that they have pushed.
+  */
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+ /*
+  * Handler storage.
+  */
+static ARGV *got_argv;
+static VSTRING *buf;
+
+/* add_handler_event - append formatted handler inputs */
+
+static void add_handler_event(ARGV *argv, int level, const char *text,
+                                     char *context)
+{
+    if (buf == 0)
+       buf = vstring_alloc(10);
+    vstring_sprintf(buf, "%d:%s:%s", level, text, context);
+    argv_add(argv, vstring_str(buf), (char *) 0);
+}
+
+/* handler - output handler */
+
+static void handler(int level, const char *text, void *context)
+{
+    add_handler_event(got_argv, level, text, context);
+}
+
+static void test_msg_output_push_pop_works(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    ARGV   *want_argv = argv_alloc(1);
+    char   *req_context = "handler";
+
+    /*
+     * Install our logging output handler.
+     */
+    got_argv = argv_alloc(1);
+    msg_output_push(handler, req_context);
+
+    /*
+     * Expect and generate one logging event.
+     */
+    add_handler_event(want_argv, 0, "text", req_context);
+    expect_ptest_log_event(t, "text");
+    msg_info("text");
+
+    /*
+     * Verify that the event was sent to our logging handler.
+     */
+    (void) eq_argv(t, "handler events", got_argv, want_argv);
+
+    /*
+     * Pop our logging output handler and verify that it no longer receives
+     * logging events.
+     */
+    msg_output_pop(handler, req_context);
+    expect_ptest_log_event(t, "more text");
+    msg_info("more text");
+    if (got_argv->argc > 1)
+       ptest_error(t, "handler: got result after it was popped");
+
+    /*
+     * Clean up.
+     */
+    argv_free(got_argv);
+    argv_free(want_argv);
+}
+
+static void test_msg_output_push_dedups(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    ARGV   *want_argv = argv_alloc(1);
+    char   *req_context = "handler";
+
+    /*
+     * Push the same logging handler twice.
+     */
+    got_argv = argv_alloc(1);
+    msg_output_push(handler, req_context);
+    msg_output_push(handler, req_context);
+
+    /*
+     * Expect and generate a logging event.
+     */
+    add_handler_event(want_argv, 0, "text", req_context);
+    expect_ptest_log_event(t, "text");
+    msg_info("text");
+
+    /*
+     * Verify the handler is called only once.
+     */
+    (void) eq_argv(t, "handler events", got_argv, want_argv);
+
+    /*
+     * Clean up.
+     */
+    msg_output_pop(handler, req_context);
+    argv_free(got_argv);
+    argv_free(want_argv);
+}
+
+static void test_msg_output_pop_rejects_unregistered(PTEST_CTX *t,
+                                                  const PTEST_CASE *unused)
+{
+    char   *req_context = "handler";
+
+    /*
+     * Install our logging output handler.
+     */
+    got_argv = argv_alloc(1);
+    msg_output_push(handler, req_context);
+
+    /*
+     * Clean up.
+     */
+    msg_output_pop(handler, req_context);
+    argv_free(got_argv);
+
+    /*
+     * Force an msg_output_pop() panic.
+     */
+    expect_ptest_log_event(t, "panic: msg_output_pop: handler");
+    msg_output_pop(handler, req_context);
+}
+
+static PTEST_CASE ptestcases[] = {
+    {"test msg_output_push_pop works", test_msg_output_push_pop_works},
+    {"test msg_output_push dedups", test_msg_output_push_dedups},
+    {"test msg_output_pop rejects unregistered",
+    test_msg_output_pop_rejects_unregistered},
+};
+
+#include <ptest_main.h>
index 7c979c66d02587fec47633175252eab79d0c8b89..eb9427cb5b200e5554999874062c75651a662197 100644 (file)
@@ -157,7 +157,7 @@ static int msg_syslog_enable;
 
 /* msg_syslog_print - log info to syslog daemon */
 
-static void msg_syslog_print(int level, const char *text)
+static void msg_syslog_print(int level, const char *text, void *unused)
 {
     static int log_level[] = {
        LOG_INFO, LOG_WARNING, LOG_ERR, LOG_CRIT, LOG_CRIT,
@@ -207,7 +207,7 @@ void    msg_syslog_init(const char *name, int logopt, int facility)
     openlog(name, LOG_NDELAY | logopt, facility);
     if (first_call) {
        first_call = 0;
-       msg_output(msg_syslog_print);
+       msg_output_push(msg_syslog_print, (void *) 0);
     }
     msg_syslog_enable = 1;
 }
index 3477b546b04b179af4b3803c599e1420a1e89b12..48dd1c43b00634aab7d6932f52f1aad3987454c3 100644 (file)
@@ -9,6 +9,9 @@
 /*     void    msg_vstream_init(progname, stream)
 /*     const char *progname;
 /*     VSTREAM *stream;
+/*
+/*     void    msg_vstream_enable(yesno)
+/*     int     yesno;
 /* DESCRIPTION
 /*     This module implements support to report msg(3) diagnostics
 /*     to a VSTREAM.
@@ -16,6 +19,9 @@
 /*     msg_vstream_init() sets the program name that appears in each output
 /*     record, and directs diagnostics (see msg(3)) to the specified
 /*     VSTREAM. The \fIprogname\fR argument is not copied.
+/*
+/*     msg_vstream_enable() enables or disables msg_vstream logging,
+/*     depending on the argument value.
 /* SEE ALSO
 /*     msg(3)
 /* BUGS
   */
 static const char *msg_tag;
 static VSTREAM *msg_stream;
+static int msg_vstream_enabled;
 
 /* msg_vstream_print - log diagnostic to VSTREAM */
 
-static void msg_vstream_print(int level, const char *text)
+static void msg_vstream_print(int level, const char *text, void *unused)
 {
     static const char *level_text[] = {
        "info", "warning", "error", "fatal", "panic",
     };
 
+    if (!msg_vstream_enabled)
+       return;
+
     if (level < 0 || level >= (int) (sizeof(level_text) / sizeof(level_text[0])))
        msg_panic("invalid severity level: %d", level);
     if (level == MSG_INFO) {
@@ -76,13 +86,16 @@ static void msg_vstream_print(int level, const char *text)
 
 void    msg_vstream_init(const char *name, VSTREAM *vp)
 {
-    static int first_call = 1;
-
     msg_tag = name;
     msg_stream = vp;
     vstream_no_debug(vp);
-    if (first_call) {
-       first_call = 0;
-       msg_output(msg_vstream_print);
-    }
+    msg_output_push(msg_vstream_print, (void *) 0);
+    msg_vstream_enabled = 1;
+}
+
+/* msg_vstream_enable - on/off switch */
+
+void    msg_vstream_enable(int yesno)
+{
+    msg_vstream_enabled = yesno;
 }
index d0679a047cb1427295f229f4217eeb4fe35d1f9c..02ec67ccb205125d6a5184137a5490bd787a8c26 100644 (file)
@@ -19,6 +19,7 @@
   * External interface.
   */
 extern void msg_vstream_init(const char *, VSTREAM *);
+extern void msg_vstream_enable(int);
 
 /* LICENSE
 /* .ad
index dc24c58ac5cdf265381675aa8f5880751eef78a9..ef4ca7a5da4e072eda72964137db6add08178e8e 100644 (file)
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
-#include <netdb.h>
+#include <wrap_netdb.h>
 #include <string.h>
 #include <errno.h>
 #include <stdlib.h>
diff --git a/postfix/src/util/myaddrinfo.ref b/postfix/src/util/myaddrinfo.ref
deleted file mode 100644 (file)
index a1aa8ca..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-./myaddrinfo: === hostname belly.porcupine.org ===
-./myaddrinfo: belly.porcupine.org:(null) -> family=2 sock=1 proto=6 168.100.3.6:0
-./myaddrinfo: 168.100.3.6:0 -> belly.porcupine.org:0
-./myaddrinfo: belly.porcupine.org:(null) -> family=28 sock=1 proto=6 2604:8d00:189::6:0
-./myaddrinfo: 2604:8d00:189::6:0 -> belly.porcupine.org:0
-./myaddrinfo: === host address 168.100.3.2 ===
-./myaddrinfo: 168.100.3.2:(null) -> family=2 sock=1 proto=6 168.100.3.2:0
-./myaddrinfo: 168.100.3.2:0 -> spike.porcupine.org:0
diff --git a/postfix/src/util/myaddrinfo.ref2 b/postfix/src/util/myaddrinfo.ref2
deleted file mode 100644 (file)
index d7b83d0..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-./myaddrinfo: === hostname null.porcupine.org ===
-./myaddrinfo: warning: hostname_to_sockaddr(null.porcupine.org:(null)): hostname nor servname provided, or not known
-./myaddrinfo: === host address 10.0.0.0 ===
-./myaddrinfo: 10.0.0.0:(null) -> family=2 sock=1 proto=6 10.0.0.0:0
-./myaddrinfo: warning: sockaddr_to_hostname: hostname nor servname provided, or not known
diff --git a/postfix/src/util/myaddrinfo4.ref b/postfix/src/util/myaddrinfo4.ref
deleted file mode 100644 (file)
index 50dafe9..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-./myaddrinfo4: === hostname belly.porcupine.org ===
-./myaddrinfo4: belly.porcupine.org:(null) -> family=2 sock=1 proto=0 168.100.3.6:0
-./myaddrinfo4: warning: sockaddr_to_hostname: hostname nor servname provided, or not known
-./myaddrinfo4: === host address 168.100.3.2 ===
-./myaddrinfo4: 168.100.3.2:(null) -> family=2 sock=1 proto=0 168.100.3.2:0
-./myaddrinfo4: warning: sockaddr_to_hostname: hostname nor servname provided, or not known
diff --git a/postfix/src/util/myaddrinfo4.ref2 b/postfix/src/util/myaddrinfo4.ref2
deleted file mode 100644 (file)
index 1f6177d..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-./myaddrinfo4: === hostname null.porcupine.org ===
-./myaddrinfo4: warning: hostname_to_sockaddr(null.porcupine.org:(null)): No address associated with hostname
-./myaddrinfo4: === host address 10.0.0.0 ===
-./myaddrinfo4: 10.0.0.0:(null) -> family=2 sock=1 proto=0 10.0.0.0:0
-./myaddrinfo4: warning: sockaddr_to_hostname: hostname nor servname provided, or not known
diff --git a/postfix/src/util/myaddrinfo_test.c b/postfix/src/util/myaddrinfo_test.c
new file mode 100644 (file)
index 0000000..d334ee7
--- /dev/null
@@ -0,0 +1,215 @@
+ /*
+  * Test program for the myaddrinfo module. The purpose is to verify that the
+  * myaddrinfo functions make the expected calls, and that they forward the
+  * expected results. See comments in pmock_expect_test.c, and PTEST_README
+  * for documentation.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <wrap_netdb.h>
+
+ /*
+  * Utility library.
+  */
+#include <vstring.h>
+#include <inet_proto.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+#include <mock_getaddrinfo.h>
+
+#define STR     vstring_str
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *t, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+static void test_hostname_to_sockaddr_host(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    int     got_st, want_st = 0;
+    struct addrinfo *got_info = 0, *want_info;
+    struct addrinfo req_hints;
+    const char *hostname = "belly.porcupine.org";
+
+    inet_proto_init(tp->testname, "all");
+
+    /*
+     * Set up expectations.
+     */
+    memset(&req_hints, 0, sizeof(req_hints));
+    req_hints.ai_family = PF_INET;
+    want_info = make_addrinfo(&req_hints, (char *) 0, "168.100.3.6", 0);
+    req_hints.ai_family = PF_INET6;
+    want_info->ai_next = make_addrinfo(&req_hints, (char *) 0,
+                                      "2604:8d00:189::6", 0);
+    req_hints.ai_family = inet_proto_info()->ai_family;
+    req_hints.ai_socktype = SOCK_STREAM;       /* XXX */
+    expect_getaddrinfo(1, want_st, hostname, (char *) 0,
+                      &req_hints, want_info);
+
+    /*
+     * Call the mock and verify the results.
+     */
+    got_st = hostname_to_sockaddr(hostname, (char *) 0, 0, &got_info);
+    if (got_st != want_st) {
+       ptest_error(t, "hostname_to_sockaddr status: got %d, want %d",
+                   got_st, want_st);
+    } else if (!eq_addrinfo(t, "hostname_to_sockaddr addrinfo",
+                           got_info, want_info)) {
+       /* already logged by eq_addrinfo() */
+    }
+
+    /*
+     * Clean up.
+     */
+    freeaddrinfo(want_info);
+    if (got_info)
+       freeaddrinfo(got_info);
+}
+
+static void test_hostname_to_sockaddr_v4host(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    int     got_st, want_st = 0;
+    struct addrinfo *got_info = 0, *want_info;
+    struct addrinfo req_hints;
+    const char *hostname = "belly.porcupine.org";
+
+    inet_proto_init(tp->testname, "ipv4");
+
+    /*
+     * Set up expectations.
+     */
+    memset(&req_hints, 0, sizeof(req_hints));
+    req_hints.ai_family = PF_INET;
+    want_info = make_addrinfo(&req_hints, (char *) 0, "168.100.3.6", 0);
+    req_hints.ai_family = inet_proto_info()->ai_family;
+    req_hints.ai_socktype = SOCK_STREAM;       /* XXX */
+    expect_getaddrinfo(1, want_st, hostname, (char *) 0,
+                      &req_hints, want_info);
+
+    /*
+     * Call the mock and verify the results.
+     */
+    got_st = hostname_to_sockaddr(hostname, (char *) 0, 0, &got_info);
+    if (got_st != want_st) {
+       ptest_error(t, "hostname_to_sockaddr status: got %d, want %d",
+                   got_st, want_st);
+    } else if (!eq_addrinfo(t, "hostname_to_sockaddr addrinfo",
+                           got_info, want_info)) {
+       /* already logged by eq_addrinfo() */
+    }
+
+    /*
+     * Clean up.
+     */
+    freeaddrinfo(want_info);
+    if (got_info)
+       freeaddrinfo(got_info);
+}
+
+
+static void test_hostaddr_to_sockaddr_host(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    int     got_st, want_st = 0;
+    struct addrinfo *got_info, *want_info;
+    struct addrinfo req_hints;
+    const char *req_hostaddr = "168.100.3.2";
+
+    /*
+     * Set up expectations.
+     */
+    memset(&req_hints, 0, sizeof(req_hints));
+    req_hints.ai_family = PF_INET;
+    want_info = make_addrinfo(&req_hints, (char *) 0, req_hostaddr, 0);
+    req_hints.ai_family = inet_proto_info()->ai_family;
+    req_hints.ai_socktype = SOCK_STREAM;       /* XXX */
+    req_hints.ai_flags = AI_NUMERICHOST;
+    expect_getaddrinfo(1, want_st, req_hostaddr, (char *) 0,
+                      &req_hints, want_info);
+
+    /*
+     * Call the mock indirectly, and verify the results.
+     */
+    got_st = hostaddr_to_sockaddr(req_hostaddr, (char *) 0, 0, &got_info);
+    if (got_st != want_st) {
+       ptest_error(t, "hostaddr_to_sockaddr status: got %d, want %d",
+                   got_st, want_st);
+    } else if (!eq_addrinfo(t, "hostname_to_sockaddr addrinfo",
+                           got_info, want_info)) {
+       /* already logged by eq_addrinfo() */
+    }
+
+    /*
+     * Clean up.
+     */
+    freeaddrinfo(want_info);
+    if (got_info)
+       freeaddrinfo(got_info);
+}
+
+static void test_hostname_to_sockaddr_nxhost(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    int     got_st, want_st = EAI_NONAME;
+    struct addrinfo *got_info = 0, *want_info = 0;
+    struct addrinfo req_hints;
+    const char *hostname = "null.porcupine.org";
+
+    inet_proto_init(tp->testname, "all");
+
+    /*
+     * Set up expectations.
+     */
+    memset(&req_hints, 0, sizeof(req_hints));
+    req_hints.ai_family = inet_proto_info()->ai_family;
+    req_hints.ai_socktype = SOCK_STREAM;       /* XXX */
+    expect_getaddrinfo(1, want_st, hostname, (char *) 0, &req_hints, want_info);
+
+    /*
+     * Call the mock and verify the results.
+     */
+    got_st = hostname_to_sockaddr(hostname, (char *) 0, 0, &got_info);
+    if (got_st != want_st) {
+       ptest_error(t, "hostname_to_sockaddr status: got %d, want %d",
+                   got_st, want_st);
+    } else if (!eq_addrinfo(t, "hostname_to_sockaddr addrinfo",
+                           got_info, want_info)) {
+       /* already logged by eq_addrinfo() */
+    }
+
+    /*
+     * Clean up.
+     */
+    if (got_info)
+       freeaddrinfo(got_info);
+}
+
+ /*
+  * Test cases.
+  */
+const PTEST_CASE ptestcases[] = {
+    {
+        /* name */ "test hostname_to_sockaddr host only",
+        /* action */ test_hostname_to_sockaddr_host,
+    },
+    {
+        /* name */ "test hostname_to_sockaddr v4host only",
+        /* action */ test_hostname_to_sockaddr_v4host,
+    },
+    {
+        /* name */ "test hostaddr_to_sockaddr host only",
+        /* action */ test_hostaddr_to_sockaddr_host,
+    },
+    {
+        /* name */ "test hostname_to_sockaddr non-existent host only",
+        /* action */ test_hostname_to_sockaddr_nxhost,
+    },
+    /* TODO: sockadddr_to_hostaddr, sockaddr_to_hostname. */
+};
+
+#include <ptest_main.h>
index 94f7bb3e7aa733e31e0290e67022740ca46222bd..37a8afd2a8d0f676a001ef43a78dac9b9dbcac90 100644 (file)
@@ -249,7 +249,7 @@ char   *mystrndup(const char *str, ssize_t len)
     if (len < 0)
        msg_panic("mystrndup: requested length %ld", (long) len);
 #ifndef NO_SHARED_EMPTY_STRINGS
-    if (*str == 0)
+    if (*str == 0 || /* fix 20220615 */ len == 0)
        return ((char *) empty_string);
 #endif
     if ((cp = memchr(str, 0, len)) != 0)
diff --git a/postfix/src/util/mymalloc_test.c b/postfix/src/util/mymalloc_test.c
new file mode 100644 (file)
index 0000000..d64836d
--- /dev/null
@@ -0,0 +1,307 @@
+ /*
+  * Tests to verify mymalloc sanity checks. See PTEST_README for
+  * documentation.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <mymalloc.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+ /*
+  * See <ptest_main.h>
+  */
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+/* Test functions. */
+
+static void test_mymalloc_normal(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    void   *ptr;
+
+    ptr = mymalloc(100);
+    myfree(ptr);
+}
+
+static void test_mymalloc_panic_too_small(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    expect_ptest_log_event(t, "panic: mymalloc: requested length 0");
+    (void) mymalloc(0);
+    ptest_fatal(t, "mymalloc(0) returned");
+}
+
+static void test_mymalloc_fatal_out_of_mem(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    if (sizeof(size_t) <= 4)
+       ptest_skip(t);
+    expect_ptest_log_event(t, "fatal: mymalloc: insufficient memory for");
+    (void) mymalloc(SSIZE_T_MAX - 100);
+    ptest_fatal(t, "mymalloc(SSIZE_T_MAX-100) returned");
+}
+
+static void test_myfree_panic_double_free(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    void   *ptr;
+
+    expect_ptest_log_event(t, "panic: myfree: corrupt or unallocated memory block");
+    ptr = mymalloc(100);
+    myfree(ptr);
+    /* The next call unavoidably reads unallocated memory */
+    myfree(ptr);
+    ptest_fatal(t, "double myfree(_) returned");
+}
+
+static void test_myfree_panic_null(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    expect_ptest_log_event(t, "panic: myfree: null pointer input");
+    myfree((void *) 0);
+    ptest_fatal(t, "myfree(0) returned");
+}
+
+static void test_myrealloc_normal(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    void   *ptr;
+
+    ptr = mymalloc(100);
+    ptr = myrealloc(ptr, 200);
+    myfree(ptr);
+}
+
+static void test_myrealloc_panic_too_small(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    void   *ptr;
+
+    expect_ptest_log_event(t, "panic: myrealloc: requested length 0");
+    ptr = mymalloc(100);
+    ptest_defer(t, myfree, ptr);
+    (void) myrealloc(ptr, 0);
+    ptest_fatal(t, "myrealloc(_, 0) returned");
+}
+
+static void test_myrealloc_fatal_out_of_mem(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    void   *ptr;
+
+    if (sizeof(size_t) <= 4)
+       ptest_skip(t);
+
+    /*
+     * Unlike the previous test, this test can't use test_defer(t, myfree,
+     * ptr), because myrealloc() clears the memory block's signature field
+     * before it calls realloc().
+     */
+    expect_ptest_log_event(t, "fatal: myrealloc: insufficient memory for");
+    ptr = mymalloc(100);
+    (void) myrealloc(ptr, SSIZE_T_MAX - 100);
+    ptest_fatal(t, "myrealloc(_, SSIZE_T_MAX-100) returned");
+}
+
+static void test_myrealloc_panic_unallocated(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    void   *ptr;
+
+    expect_ptest_log_event(t, "panic: myrealloc: corrupt or unallocated memory block");
+    ptr = mymalloc(100);
+    myfree(ptr);
+    ptr = myrealloc(ptr, 200);
+    ptest_fatal(t, "myrealloc() after free() returned");
+}
+
+static void test_myrealloc_panic_null(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    expect_ptest_log_event(t, "panic: myrealloc: null pointer input");
+    (void) myrealloc((void *) 0, 200);
+    ptest_fatal(t, "myrealloc(0, _) returned");
+}
+
+static void test_mystrdup_normal(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    void   *ptr;
+
+    ptr = mystrdup("foo");
+    myfree(ptr);
+}
+
+static void test_mystrdup_panic_null(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    expect_ptest_log_event(t, "panic: mystrdup: null pointer argument");
+    (void) mystrdup((char *) 0);
+    ptest_fatal(t, "mystrdup(0) returned");
+}
+
+static void test_mystrdup_static_empty(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    char   *want;
+    char   *got;
+
+    /*
+     * Repeated  mystrdup("") produce the same result.
+     */
+    want = mystrdup("");
+    got = mystrdup("");
+    if (got != want)
+       ptest_error(t, "mystrdup: empty string results differ: got %p, want %p",
+                   got, want);
+
+    /*
+     * myfree() is a NOOP.
+     */
+    myfree(want);
+    myfree(got);
+}
+
+static void test_mystrndup_normal_short(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    char   *want = "foo";
+    char   *got;
+
+    got = mystrndup("foo", 5);
+    if (strcmp(got, want) != 0)
+       ptest_error(t, "mystrndup: got '%s', want '%s'", got, want);
+    myfree(got);
+}
+
+static void test_mystrndup_normal_long(PTEST_CTX *t,
+                                              const PTEST_CASE *tp)
+{
+    char   *want = "fooba";
+    char   *got;
+
+    got = mystrndup("foobar", 5);
+    if (strcmp(got, want) != 0)
+       ptest_error(t, "mystrndup: got '%s', want '%s'", got, want);
+    myfree(got);
+}
+
+static void test_mystrndup_panic_null(PTEST_CTX *t,
+                                             const PTEST_CASE *tp)
+{
+    expect_ptest_log_event(t, "panic: mystrndup: null pointer argument");
+    (void) mystrndup((char *) 0, 5);
+    ptest_fatal(t, "mystrndup(0, _) returned");
+}
+
+static void test_mystrndup_panic_too_small(PTEST_CTX *t,
+                                                  const PTEST_CASE *tp)
+{
+    expect_ptest_log_event(t, "panic: mystrndup: requested length -1");
+    (void) mystrndup("foo", -1);
+    ptest_fatal(t, "mystrndup(0,       -1) returned");
+}
+
+static void test_mystrndup_static_empty(PTEST_CTX *t,
+                                               const PTEST_CASE *tp)
+{
+    char   *want;
+    char   *got;
+
+    /*
+     * Different ways to save an empty string produce the same result.
+     */
+    want = mystrndup("", 10);
+    got = mystrndup("foo", 0);
+    if (got != want)
+       ptest_error(t, "mystrndup: empty string results differ: got %p, want %p",
+                   got, want);
+
+    /*
+     * myfree() is a NOOP for "empty" mystrdup() or mystrndup() results.
+     */
+    myfree(want);
+    myfree(got);
+}
+
+static void test_mymemdup_normal(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    void   *ptr;
+
+    ptr = mymemdup("abcdef", 5);
+    myfree(ptr);
+}
+
+static void test_mymemdup_panic_null(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    expect_ptest_log_event(t, "panic: mymemdup: null pointer argument");
+    (void) mymemdup((void *) 0, 100);
+    ptest_fatal(t, "mymemdup(0, _) returned");
+}
+
+static void test_mymemdup_panic_too_small(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    expect_ptest_log_event(t, "panic: mymalloc: requested length 0");
+    (void) mymemdup("abcdef", 0);
+    ptest_fatal(t, "mymemdup(_, 0) returned");
+}
+
+static void test_mymemdup_fatal_out_of_mem(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    if (sizeof(size_t) <= 4)
+       ptest_skip(t);
+    expect_ptest_log_event(t, "fatal: mymalloc: insufficient memory for");
+    (void) mymemdup("abcdef", SSIZE_T_MAX - 100);
+    ptest_fatal(t, "mymemdup(_, SSIZE_T_MAX-100) returned");
+}
+
+static const PTEST_CASE ptestcases[] = {
+    {"mymalloc + myfree normal case", test_mymalloc_normal,
+    },
+    {"mymalloc panic for too small request", test_mymalloc_panic_too_small,
+    },
+    {"mymalloc fatal for out of memory", test_mymalloc_fatal_out_of_mem,
+    },
+    {"myfree panic for double free", test_myfree_panic_double_free,
+    },
+    {"myfree panic for null input", test_myfree_panic_null,
+    },
+    {"myrealloc + myfree normal case", test_myrealloc_normal,
+    },
+    {"myrealloc panic for too small request", test_myrealloc_panic_too_small,
+    },
+    {"myrealloc fatal for out of memory", test_myrealloc_fatal_out_of_mem,
+    },
+    {"myrealloc panic for unallocated input", test_myrealloc_panic_unallocated,
+    },
+    {"myrealloc panic for null input", test_myrealloc_panic_null,
+    },
+    {"mystrdup + myfree normal case", test_mystrdup_normal,
+    },
+    {"mystrdup panic for null input", test_mystrdup_panic_null,
+    },
+    {"mystrdup static result for empty string", test_mystrdup_static_empty,
+    },
+    {"mystrndup + myfree normal short", test_mystrndup_normal_short,
+    },
+    {"mystrndup + myfree normal long", test_mystrndup_normal_long,
+    },
+    {"mystrndup panic for null input", test_mystrndup_panic_null,
+    },
+    {"mystrndup panic for for too small size", test_mystrndup_panic_too_small,
+    },
+    {"mystrndup static result for empty string", test_mystrndup_static_empty,
+    },
+    {"mymemdup normal case", test_mymemdup_normal,
+    },
+    {"mymemdup panic for null input", test_mymemdup_panic_null,
+    },
+    {"mymemdup panic for too small request", test_mymemdup_panic_too_small,
+    },
+    {"mymemdup fatal for out of memory", test_mymemdup_fatal_out_of_mem,
+    },
+};
+
+#include <ptest_main.h>
index d5f32b7c47dd1d91f72e7516b889377831c05bda..2b9f06f3762038079313306aaec2eabbbc92b4f9 100644 (file)
@@ -236,112 +236,3 @@ char   *mystrtokdq_cw(char **src, const char *sep, const char *blame)
        return (start);
     }
 }
-
-#ifdef TEST
-
- /*
-  * Test program.
-  */
-#include "msg.h"
-#include "mymalloc.h"
-
- /*
-  * The following needs to be large enough to include a null terminator in
-  * every testcase.expected field.
-  */
-#define EXPECT_SIZE    5
-
-struct testcase {
-    const char *action;
-    const char *input;
-    const char *expected[EXPECT_SIZE];
-};
-static const struct testcase testcases[] = {
-    {"mystrtok", ""},
-    {"mystrtok", "  foo  ", {"foo"}},
-    {"mystrtok", "  foo  bar  ", {"foo", "bar"}},
-    {"mystrtokq", ""},
-    {"mystrtokq", "foo bar", {"foo", "bar"}},
-    {"mystrtokq", "{ bar }  ", {"{ bar }"}},
-    {"mystrtokq", "foo { bar } baz", {"foo", "{ bar }", "baz"}},
-    {"mystrtokq", "foo{ bar } baz", {"foo{ bar }", "baz"}},
-    {"mystrtokq", "foo { bar }baz", {"foo", "{ bar }baz"}},
-    {"mystrtokdq", ""},
-    {"mystrtokdq", "  foo  ", {"foo"}},
-    {"mystrtokdq", "  foo  bar  ", {"foo", "bar"}},
-    {"mystrtokdq", "  foo\\ bar  ", {"foo\\ bar"}},
-    {"mystrtokdq", "  foo \\\" bar", {"foo", "\\\"", "bar"}},
-    {"mystrtokdq", "  foo \" bar baz\"  ", {"foo", "\" bar baz\""}},
-    {"mystrtok_cw", "#after text"},
-    {"mystrtok_cw", "before-text #after text", {"before-text"}},
-    {"mystrtokq_cw", "#after text"},
-    {"mystrtokq_cw", "{ before text } #after text", "{ before text }"},
-    {"mystrtokdq_cw", "#after text"},
-    {"mystrtokdq_cw", "\"before text\" #after text", {"\"before text\""}},
-};
-
-int     main(void)
-{
-    const struct testcase *tp;
-    char   *actual;
-    int     pass;
-    int     fail;
-    int     match;
-    int     n;
-
-#define NUM_TESTS       sizeof(testcases)/sizeof(testcases[0])
-#define STR_OR_NULL(s) ((s) ? (s) : "null")
-
-    for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
-       char   *saved_input = mystrdup(tp->input);
-       char   *cp = saved_input;
-
-       msg_info("RUN test case %ld %s >%s<",
-                (long) (tp - testcases), tp->action, tp->input);
-#if 0
-       msg_info("action=%s", tp->action);
-       msg_info("input=%s", tp->input);
-       for (n = 0; tp->expected[n]; tp++)
-           msg_info("expected[%d]=%s", n, tp->expected[n]);
-#endif
-
-       for (n = 0; n < EXPECT_SIZE; n++) {
-           if (strcmp(tp->action, "mystrtok") == 0) {
-               actual = mystrtok(&cp, CHARS_SPACE);
-           } else if (strcmp(tp->action, "mystrtokq") == 0) {
-               actual = mystrtokq(&cp, CHARS_SPACE, CHARS_BRACE);
-           } else if (strcmp(tp->action, "mystrtokdq") == 0) {
-               actual = mystrtokdq(&cp, CHARS_SPACE);
-           } else if (strcmp(tp->action, "mystrtok_cw") == 0) {
-               actual = mystrtok_cw(&cp, CHARS_SPACE, "test");
-           } else if (strcmp(tp->action, "mystrtokq_cw") == 0) {
-               actual = mystrtokq_cw(&cp, CHARS_SPACE, CHARS_BRACE, "test");
-           } else if (strcmp(tp->action, "mystrtokdq_cw") == 0) {
-               actual = mystrtokdq_cw(&cp, CHARS_SPACE, "test");
-           } else {
-               msg_panic("invalid command: %s", tp->action);
-           }
-           if ((match = (actual && tp->expected[n]) ?
-                (strcmp(actual, tp->expected[n]) == 0) :
-                (actual == tp->expected[n])) != 0) {
-               if (actual == 0) {
-                   msg_info("PASS test %ld", (long) (tp - testcases));
-                   pass++;
-                   break;
-               }
-           } else {
-               msg_warn("expected: >%s<, got: >%s<",
-                        STR_OR_NULL(tp->expected[n]), STR_OR_NULL(actual));
-               msg_info("FAIL test %ld", (long) (tp - testcases));
-               fail++;
-               break;
-           }
-       }
-       if (n >= EXPECT_SIZE)
-           msg_panic("need to increase EXPECT_SIZE");
-       myfree(saved_input);
-    }
-    return (fail > 0);
-}
-
-#endif
diff --git a/postfix/src/util/mystrtok.ref b/postfix/src/util/mystrtok.ref
deleted file mode 100644 (file)
index d0ca58a..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-unknown: RUN test case 0 mystrtok ><
-unknown: PASS test 0
-unknown: RUN test case 1 mystrtok >  foo  <
-unknown: PASS test 1
-unknown: RUN test case 2 mystrtok >  foo  bar  <
-unknown: PASS test 2
-unknown: RUN test case 3 mystrtokq ><
-unknown: PASS test 3
-unknown: RUN test case 4 mystrtokq >foo bar<
-unknown: PASS test 4
-unknown: RUN test case 5 mystrtokq >{ bar }  <
-unknown: PASS test 5
-unknown: RUN test case 6 mystrtokq >foo { bar } baz<
-unknown: PASS test 6
-unknown: RUN test case 7 mystrtokq >foo{ bar } baz<
-unknown: PASS test 7
-unknown: RUN test case 8 mystrtokq >foo { bar }baz<
-unknown: PASS test 8
-unknown: RUN test case 9 mystrtokdq ><
-unknown: PASS test 9
-unknown: RUN test case 10 mystrtokdq >  foo  <
-unknown: PASS test 10
-unknown: RUN test case 11 mystrtokdq >  foo  bar  <
-unknown: PASS test 11
-unknown: RUN test case 12 mystrtokdq >  foo\ bar  <
-unknown: PASS test 12
-unknown: RUN test case 13 mystrtokdq >  foo \" bar<
-unknown: PASS test 13
-unknown: RUN test case 14 mystrtokdq >  foo " bar baz"  <
-unknown: PASS test 14
-unknown: RUN test case 15 mystrtok_cw >#after text<
-unknown: warning: test: #comment after other text is not allowed: #after text...
-unknown: PASS test 15
-unknown: RUN test case 16 mystrtok_cw >before-text #after text<
-unknown: warning: test: #comment after other text is not allowed: #after text...
-unknown: PASS test 16
-unknown: RUN test case 17 mystrtokq_cw >#after text<
-unknown: warning: test: #comment after other text is not allowed: #after text...
-unknown: PASS test 17
-unknown: RUN test case 18 mystrtokq_cw >{ before text } #after text<
-unknown: warning: test: #comment after other text is not allowed: #after text...
-unknown: PASS test 18
-unknown: RUN test case 19 mystrtokdq_cw >#after text<
-unknown: warning: test: #comment after other text is not allowed: #after text...
-unknown: PASS test 19
-unknown: RUN test case 20 mystrtokdq_cw >"before text" #after text<
-unknown: warning: test: #comment after other text is not allowed: #after text...
-unknown: PASS test 20
diff --git a/postfix/src/util/mystrtok_test.c b/postfix/src/util/mystrtok_test.c
new file mode 100644 (file)
index 0000000..0aaf7fa
--- /dev/null
@@ -0,0 +1,242 @@
+ /*
+  * Test program to exercise mystrtok.c. See PTEST_README for documentation.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+} PTEST_CASE;
+
+typedef struct TEST_DATA {
+    const char *input;
+    const char *const * want;
+} TEST_DATA;
+
+#define STR_OR_NULL(s) ((s) ? (s) : "null")
+#define STR(x)         vstring_str(x)
+
+static bool match_one(PTEST_CTX *t, const char *got, const char *want)
+{
+    bool    ret;
+
+    ret = (got && want) ? (strcmp(got, want) == 0) : (got == want);
+    if (!ret)
+       ptest_error(t, "got '%s', want '%s'",
+                   STR_OR_NULL(got), STR_OR_NULL(want));
+    return (ret);
+}
+
+static void test_mystrtok(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *test_label = vstring_alloc(100);
+    const TEST_DATA test_data[] = {
+       {"", (const char *[]) {0},},
+       {"  foo  ", (const char *[]) {"foo", 0}},
+       {"  foo  bar  ", (const char *[]) {"foo", "bar", 0}},
+    };
+    const TEST_DATA *tp;
+
+    for (tp = test_data; tp < test_data + PTEST_NROF(test_data); tp++) {
+       vstring_sprintf(test_label, "(%s)", tp->input);
+       PTEST_RUN(t, STR(test_label), {
+           char   *got;
+           char   *saved_input = mystrdup(tp->input);
+           char   *cp = saved_input;
+           int     n;
+
+           for (n = 0; /* See below */; n++) {
+               got = mystrtok(&cp, CHARS_SPACE);
+               if (!match_one(t, got, tp->want[n]) || got == 0)
+                   break;
+           }
+           myfree(saved_input);
+       });
+    }
+    vstring_free(test_label);
+}
+
+static void test_mystrtokq(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *test_label = vstring_alloc(100);
+    const TEST_DATA test_data[] = {
+       {"", (const char *[]) {0}},
+       {"foo bar", (const char *[]) {"foo", "bar", 0}},
+       {"{ bar }  ", (const char *[]) {"{ bar }", 0}},
+       {"foo { bar } baz", (const char *[]) {"foo", "{ bar }", "baz", 0}},
+       {"foo{ bar } baz", (const char *[]) {"foo{ bar }", "baz", 0}},
+       {"foo { bar }baz", (const char *[]) {"foo", "{ bar }baz", 0}},
+    };
+    const TEST_DATA *tp;
+
+    for (tp = test_data; tp < test_data + PTEST_NROF(test_data); tp++) {
+       vstring_sprintf(test_label, "(%s)", tp->input);
+       PTEST_RUN(t, STR(test_label), {
+           char   *got;
+           char   *saved_input = mystrdup(tp->input);
+           char   *cp = saved_input;
+           int     n;
+
+           for (n = 0; /* See below */; n++) {
+               got = mystrtokq(&cp, CHARS_SPACE, CHARS_BRACE);
+               if (!match_one(t, got, tp->want[n]) || got == 0)
+                   break;
+           }
+           myfree(saved_input);
+       });
+    }
+    vstring_free(test_label);
+}
+
+static void test_mystrtokdq(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *test_label = vstring_alloc(100);
+    const TEST_DATA test_data[] = {
+       {"", (const char *[]) {0, 0,}},
+       {"  foo  ", (const char *[]) {"foo", 0,}},
+       {"  foo  bar  ", (const char *[]) {"foo", "bar", 0,}},
+       {"  foo\\ bar  ", (const char *[]) {"foo\\ bar", 0,}},
+       {"  foo \\\" bar", (const char *[]) {"foo", "\\\"", "bar", 0,}},
+       {"  foo \" bar baz\"  ", (const char *[]) {"foo", "\" bar baz\"", 0,}},
+    };
+    const TEST_DATA *tp;
+
+    for (tp = test_data; tp < test_data + PTEST_NROF(test_data); tp++) {
+       vstring_sprintf(test_label, "(%s)", tp->input);
+       PTEST_RUN(t, STR(test_label), {
+           char   *got;
+           char   *saved_input = mystrdup(tp->input);
+           char   *cp = saved_input;
+           int     n;
+
+           for (n = 0; /* See below */; n++) {
+               got = mystrtokdq(&cp, CHARS_SPACE);
+               if (!match_one(t, got, tp->want[n]) || got == 0)
+                   break;
+           }
+           myfree(saved_input);
+       });
+    }
+    vstring_free(test_label);
+}
+
+static void test_mystrtok_cw(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *test_label = vstring_alloc(100);
+    const TEST_DATA test_data[] = {
+       {"#after text", (const char *[]) {0,}},
+       {"before-text #after text", (const char *[]) {"before-text", 0,}},
+    };
+    const TEST_DATA *tp;
+
+    for (tp = test_data; tp < test_data + PTEST_NROF(test_data); tp++) {
+       vstring_sprintf(test_label, "(%s)", tp->input);
+       PTEST_RUN(t, STR(test_label), {
+           char   *got;
+           char   *saved_input = mystrdup(tp->input);
+           char   *cp = saved_input;
+           int     n;
+
+           expect_ptest_log_event(t,
+                               "#comment after other text is not allowed");
+
+           for (n = 0; /* See below */; n++) {
+               got = mystrtok_cw(&cp, CHARS_SPACE, "test");
+               if (!match_one(t, got, tp->want[n]) || got == 0)
+                   break;
+           }
+           myfree(saved_input);
+       });
+    }
+    vstring_free(test_label);
+}
+
+static void test_mystrtokq_cw(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *test_label = vstring_alloc(100);
+    const TEST_DATA test_data[] = {
+       {"#after text", (const char *[]) {0,}},
+       {"{ before text } #after text", (const char *[]) {"{ before text }", 0,}},
+    };
+    const TEST_DATA *tp;
+
+    for (tp = test_data; tp < test_data + PTEST_NROF(test_data); tp++) {
+       vstring_sprintf(test_label, "(%s)", tp->input);
+       PTEST_RUN(t, STR(test_label), {
+           char   *got;
+           char   *saved_input = mystrdup(tp->input);
+           char   *cp = saved_input;
+           int     n;
+
+           expect_ptest_log_event(t,
+                               "#comment after other text is not allowed");
+
+           for (n = 0; /* See below */; n++) {
+               got = mystrtokq_cw(&cp, CHARS_SPACE, CHARS_BRACE, "test");
+               if (!match_one(t, got, tp->want[n]) || got == 0)
+                   break;
+           }
+           myfree(saved_input);
+       });
+    }
+    vstring_free(test_label);
+}
+
+static void test_mystrtokdq_cw(PTEST_CTX *t, const PTEST_CASE *unused)
+{
+    VSTRING *test_label = vstring_alloc(100);
+    TEST_DATA test_data[] = {
+       {"#after text", (const char *[]) {0,}},
+       {"\"before text\" #after text", (const char *[]) {"\"before text\"", 0,}},
+    };
+    const TEST_DATA *tp;
+
+    for (tp = test_data; tp < test_data + PTEST_NROF(test_data); tp++) {
+       vstring_sprintf(test_label, "(%s)", tp->input);
+       PTEST_RUN(t, STR(test_label), {
+           char   *got;
+           char   *saved_input = mystrdup(tp->input);
+           char   *cp = saved_input;
+           int     n;
+
+           expect_ptest_log_event(t,
+                               "#comment after other text is not allowed");
+
+           for (n = 0; /* See below */; n++) {
+               got = mystrtokdq_cw(&cp, CHARS_SPACE, "test");
+               if (!match_one(t, got, tp->want[n]) || got == 0)
+                   break;
+           }
+           myfree(saved_input);
+       });
+    }
+    vstring_free(test_label);
+}
+
+static const PTEST_CASE ptestcases[] = {
+    {"test_mystrtok", test_mystrtok},
+    {"test_mystrtokq", test_mystrtokq},
+    {"test_mystrtokdq", test_mystrtokdq},
+    {"test_mystrtok_cw", test_mystrtok_cw},
+    {"test_mystrtokq_cw", test_mystrtokq_cw},
+    {"test_mystrtokdq_cw", test_mystrtokdq_cw},
+};
+
+#include <ptest_main.h>
index 5aaa85986814275a852819186307f07cf0b350f1..8cec06be78aabd192157634f1a0c85d60c887b49 100644 (file)
 /*     Enable case-insensitive matching.
 /*     This feature is not enabled by default when calling name_mask();
 /*     it has no effect with str_name_mask().
+/* .IP NAME_MASK_NULL
+/*     When converting from mask to string, output "0" when the
+/*     input mask is empty.
 /* .IP NAME_MASK_COMMA
 /*     Use comma instead of space when converting a mask to string.
 /* .IP NAME_MASK_PIPE
@@ -318,6 +321,8 @@ const char *str_name_mask_delim_opt(VSTRING *buf, const char *context,
     }
     if ((len = VSTRING_LEN(buf)) > 0)
        vstring_truncate(buf, len - strlen(delim));
+    else if (flags & NAME_MASK_NULL)
+       vstring_strcat(buf, "0");
     VSTRING_TERMINATE(buf);
 
     return (STR(buf));
@@ -445,6 +450,8 @@ const char *str_long_name_mask_opt(VSTRING *buf, const char *context,
     }
     if ((len = VSTRING_LEN(buf)) > 0)
        vstring_truncate(buf, len - 1);
+    else if (flags & NAME_MASK_NULL)
+       vstring_strcat(buf, "0");
     VSTRING_TERMINATE(buf);
 
     return (STR(buf));
index 1e7f393ff5e6c1b855455128eb137b1f6706bf84..27d7c4346fdc929b192b67a6c284943d63ae4af5 100644 (file)
@@ -32,6 +32,7 @@ typedef struct {
 #define NAME_MASK_NUMBER       (1<<5)
 #define NAME_MASK_WARN         (1<<6)
 #define NAME_MASK_IGNORE       (1<<7)
+#define NAME_MASK_NULL         (1<<8)
 
 #define NAME_MASK_REQUIRED \
     (NAME_MASK_FATAL | NAME_MASK_RETURN | NAME_MASK_WARN | NAME_MASK_IGNORE)
similarity index 99%
rename from postfix/src/util/stream_test.c
rename to postfix/src/util/sunos5_stream_test.c
index 3a6540742ea8e6401b8fc348af8ea1c9320ea21f..5c8f82fa4bf60fe9faf9acd150de1599665d7de0 100644 (file)
@@ -103,11 +103,9 @@ int     main(int argc, char **argv)
        msg_fatal("close server fd");
     return (0);
 }
-
 #else
 int     main(int argc, char **argv)
 {
     return (0);
 }
-
 #endif
diff --git a/postfix/src/util/unescape.in b/postfix/src/util/unescape.in
deleted file mode 100644 (file)
index 41f24a7..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
-\1\2\3\4\5\6\7\8\9
-\1234\2345\3456\4567
-rcpt to:<wietse@\317\200.porcupine.org>
diff --git a/postfix/src/util/unescape.ref b/postfix/src/util/unescape.ref
deleted file mode 100644 (file)
index fb79368..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-0000000   \a  \b   c   d   e  \f   g   h   i   j   k   l   m  \n   o   p
-          007 010 143 144 145 014 147 150 151 152 153 154 155 012 157 160
-0000020    q  \r   s  \t   u  \v   w   x   y   z  \n 001 002 003 004 005
-          161 015 163 011 165 013 167 170 171 172 012 001 002 003 004 005
-0000040  006  \a   8   9  \n   S   4 234   5 345   6   .   7  \n   r   c
-          006 007 070 071 012 123 064 234 065 345 066 056 067 012 162 143
-0000060    p   t       t   o   :   <   w   i   e   t   s   e   @  317 200
-          160 164 040 164 157 072 074 167 151 145 164 163 145 100 317 200
-0000100    .   p   o   r   c   u   p   i   n   e   .   o   r   g   >  \n
-          056 160 157 162 143 165 160 151 156 145 056 157 162 147 076 012
-0000120
diff --git a/postfix/src/util/unescape_test.c b/postfix/src/util/unescape_test.c
new file mode 100644 (file)
index 0000000..75fe523
--- /dev/null
@@ -0,0 +1,93 @@
+ /*
+  * Test program to exercise unescape.c. See PTEST_README for documentation.
+  */
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+  * Utility library
+  */
+#include <stringops.h>
+#include <msg.h>
+
+ /*
+  * Test library.
+  */
+#include <ptest.h>
+
+typedef struct PTEST_CASE {
+    const char *testname;              /* Human-readable description */
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
+    const char *input;
+    const char *want;
+} PTEST_CASE;
+
+ /*
+  * SLMs.
+  */
+#define STR    vstring_str
+
+static char *to_octal_string(VSTRING *buf, const char *str)
+{
+    const unsigned char *cp;
+
+    VSTRING_RESET(buf);
+    for (cp = (const unsigned char *) str; *cp; cp++) {
+       vstring_sprintf_append(buf, "%03o", *cp);
+       if (cp[1])
+           vstring_strcat(buf, " ");
+    }
+    VSTRING_TERMINATE(buf);
+    return (STR(buf));
+}
+
+static void test_unescape(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    VSTRING *got, *got_octal, *want_octal;;
+
+    got = vstring_alloc(100);
+    got_octal = vstring_alloc(100);
+    want_octal = vstring_alloc(100);
+
+    unescape(got, tp->input);
+    if (strcmp(STR(got), tp->want) != 0)
+       ptest_error(t, "unescape got '%s' want '%s'",
+                   to_octal_string(got_octal, STR(got)),
+                   to_octal_string(want_octal, tp->want));
+    vstring_free(got);
+    vstring_free(got_octal);
+    vstring_free(want_octal);
+}
+
+static const PTEST_CASE ptestcases[] = {
+    {
+        /* name */ "escape lowecase a-z",
+        /* action */ test_unescape,
+        /* input */ "\\a\\b\\c\\d\\e\\f\\g\\h\\i\\j\\k\\l\\m\\n\\o\\p\\q\\r\\s\\t\\u\\v\\w\\x\\y\\z",
+        /* want */ "\a\bcde\fghijklm\nopq\rs\tu\vwxyz",
+    }, {
+        /* name */ "escape digits 0-9",
+        /* action */ test_unescape,
+        /* input */ "\\1\\2\\3\\4\\5\\6\\7\\8\\9",
+        /* want */ "\001\002\003\004\005\006\00789",
+    }, {
+        /* name */ "\\nnn plus digit",
+        /* action */ test_unescape,
+        /* input */ "\\1234\\2345\\3456\\04567",
+        /* want */ "\1234\2345\3456\04567",
+    }, {
+        /* name */ "non-ascii email",
+        /* action */ test_unescape,
+        /* input */ "rcpt to:<wietse@\\317\\200.porcupine.org>",
+        /* want */ "rcpt to:<wietse@\317\200.porcupine.org>",
+    },
+};
+
+ /*
+  * Test library.
+  */
+#include <ptest_main.h>
diff --git a/postfix/src/util/wrap_netdb.c b/postfix/src/util/wrap_netdb.c
new file mode 100644 (file)
index 0000000..7d33f2f
--- /dev/null
@@ -0,0 +1,136 @@
+/*++
+/* NAME
+/*     wrap_netdb 3
+/* SUMMARY
+/*     mockable netdb wrappers
+/* SYNOPSIS
+/*     #include <wrap_netdb.h>
+/*
+/*     int     wrap_getaddrinfo(
+/*     const char *hostname,
+/*     const char *servname,
+/*     const struct addrinfo *hints,
+/*     struct addrinfo **res)
+/*
+/*     void    wrap_freeaddrinfo(struct addrinfo *ai)
+/*
+/*     int     wrap_getnameinfo(
+/*     const struct sockaddr *sa,
+/*     socklen_t salen,
+/*     char    *host,
+/*     size_t  hostlen,
+/*     char    *serv,
+/*     size_t  servlen,
+/*     int     flags)
+/*
+/*     struct servent *wrap_getservbyname(
+/*     const char *name,
+/*     const char *proto)
+/*
+/*     struct servent *wrap_getservbyport(
+/*     int     port,
+/*     const char *proto)
+/* DESCRIPTION
+/*     This module is a NOOP when the NO_MOCK_WRAPPERS macro is
+/*     defined.
+/*
+/*     This code implements a workaround for inconsistencies in
+/*     netdb.h header files, that can break test mock functions
+/*     that have the same name as a system library function.
+/*
+/*     For example, BSD and older Linux getnameinfo() implementations
+/*     use size_t for the hostlen and servlen arguments, but GLIBC
+/*     2.2 and later use socklen_t instead; those two types have
+/*     different sizes on LP64 systems. For a rationale why those
+/*     types were changed, see
+/*     https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xsh_chap02.html#tag_22_02_10_06
+/*
+/*     By default, 1) the header file of this module redirects
+/*     netdb function calls by prepending a "wrap_" name prefix
+/*     to netdb function names, 2) the code of this module implements
+/*     functions with "wrap_" name prefixes that forward redirected
+/*     calls to the real netdb functions, while taking care of any
+/*     necessary type incompatibilities, and 3) test mock functions
+/*     use the "wrap_" prefixed function names instead of the netdb
+/*     function names. Build with -DNO_MOCK_WRAPPERS to avoid
+/*     this workaround.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <wrap_netdb.h>
+
+#ifndef NO_MOCK_WRAPPERS
+
+/* wrap_getaddrinfo - wrap getaddrinfo() with stable internal API */
+
+int     wrap_getaddrinfo(const char *hostname, const char *servname,
+                                const struct addrinfo *hints,
+                                struct addrinfo **res)
+{
+#undef getaddrinfo
+    return (getaddrinfo(hostname, servname, hints, res));
+}
+
+/* wrap_freeaddrinfo - wrap freeaddrinfo() with stable internal API */
+
+void    wrap_freeaddrinfo(struct addrinfo *ai)
+{
+#undef freeaddrinfo
+    freeaddrinfo(ai);
+}
+
+/* wrap_getnameinfo - wrap getnameinfo() with stable internal API */
+
+int     wrap_getnameinfo(const struct sockaddr *sa, socklen_t salen,
+                                char *host, size_t hostlen,
+                                char *serv, size_t servlen, int flags)
+{
+#undef getnameinfo
+    return (getnameinfo(sa, salen, host, hostlen, serv, servlen, flags));
+}
+
+/* wrap_getservbyname - wrap getservbyname() with stable internal API */
+
+struct servent *wrap_getservbyname(const char *name, const char *proto)
+{
+#undef getservbyname
+    return (getservbyname(name, proto));
+}
+
+/* wrap_getservbyport - wrap getservbyport() with stable internal API */
+
+struct servent *wrap_getservbyport(int port, const char *proto)
+{
+#undef getservbyport
+    return (getservbyport(port, proto));
+}
+
+/* wrap_setservent - wrap setservent() with stable internal API */
+
+void    wrap_setservent(int stayopen)
+{
+#undef setservent
+    return (setservent(stayopen));
+}
+
+/* wrap_endservent - wrap endservent() with stable internal API */
+
+void    wrap_endservent(void)
+{
+#undef endservent
+    return (endservent());
+}
+
+#endif
diff --git a/postfix/src/util/wrap_netdb.h b/postfix/src/util/wrap_netdb.h
new file mode 100644 (file)
index 0000000..3e49e7a
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef _WRAP_NETDB_H_INCLUDED_
+#define _WRAP_NETDB_H_INCLUDED_
+
+/*++
+/* NAME
+/*     wrap_netdb 3h
+/* SUMMARY
+/*     mockable netdb wrappers
+/* SYNOPSIS
+/*     #include <wrap_netdb.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * System library
+  */
+#include <sys_defs.h>
+#include <netdb.h>
+
+ /*
+  * External interface.
+  */
+#ifndef NO_MOCK_WRAPPERS
+extern int wrap_getaddrinfo(const char *, const char *,
+                                            const struct addrinfo *,
+                                            struct addrinfo **);
+extern void wrap_freeaddrinfo(struct addrinfo *);
+extern int wrap_getnameinfo(const struct sockaddr *, socklen_t, char *,
+                                            size_t, char *, size_t, int);
+
+#define        getaddrinfo     wrap_getaddrinfo
+#define        freeaddrinfo    wrap_freeaddrinfo
+#define getnameinfo     wrap_getnameinfo
+
+extern struct servent *wrap_getservbyname(const char *, const char *);
+extern struct servent *wrap_getservbyport(int, const char *);
+extern void wrap_setservent(int);
+extern void wrap_endservent(void);
+
+#define        getservbyname   wrap_getservbyname
+#define getservbyport   wrap_getservbyport
+#define setservent     wrap_setservent
+#define endservent     wrap_endservent
+#endif
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     Google, Inc.
+/*     111 8th Avenue
+/*     New York, NY 10011, USA
+/*--*/
+
+#endif