-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
Documentation: inet_interfaces and proxy_interfaces
descriptions. File: proto/postconf.proto.
-
20220719
Cleanup: Postfix 3.5.0 introduced debug logging noise in
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.
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 \
* 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
+
--- /dev/null
+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.
+
$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
$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
--- /dev/null
+<!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 <ptest_main.h>
+</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 <ptest_main.h></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 <ptest_main.h>
+</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->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 <a href="PTEST_README.html#ptest_error">ptest_error</a>(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 <a href="PTEST_README.html#ptest_error">ptest_error</a>(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 <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->query, got_error, pp->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->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->want_log; cpp < tp->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->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 <ptest_main.h>
+</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
+<what you got>, want <what you want>". </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>
+
</ul>
+<p><strong> For maintainers and contributors </strong></p>
+
+<ul>
+
+<li> <a href="PTEST_README.html"> Writing Postfix unit tests </a>
+
+</ul>
+
</td>
</table>
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/;
../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 \
../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 \
../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) >$@
../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) >$@
--- /dev/null
+<!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 <ptest_main.h>
+</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 <ptest_main.h></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 <ptest_main.h>
+</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->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 }
+</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->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 }
+...
+</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 <ptest_main.h>
+</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
+<what you got>, want <what you want>". </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>
+
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
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
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
IfThisThenThat
linter
MAXINT
+gmock
+sunos
+WebPKI
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
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
/*
* Testing library.
*/
-#include <test_main.h>
+#include <test_server_main.h>
#define TEST_ENCODING "7bit"
#define NO_SMTPUTF8 (0)
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),
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)
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
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 \
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
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
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
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
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, \
/* unsigned *ltype;
/*
/* int dns_get_h_errno()
+/*
+/* void dns_set_h_errno(
+/* int errval)
/* AUXILIARY FUNCTIONS
/* extern int var_dns_ncache_ttl_fix;
/*
/* 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.
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);
}
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+ /*
+ * 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>
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
$(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
# 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 \
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 =
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) \
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 $? >$@
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)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
}
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
--- /dev/null
+ /*
+ * 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>
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
--- /dev/null
+ /*
+ * 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>
#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"
}
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);
/* DESCRIPTION
/* .nf
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
/*
* External interface.
*/
#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
+ /*
+ * 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;
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;
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>
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
+++ /dev/null
-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
--- /dev/null
+ /*
+ * 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>
+++ /dev/null
-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
/*
- * 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>
/* 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;
NAME_MASK_WARN | NAME_MASK_PIPE));
}
+#endif
+
#endif
/*
var_proxywrite_service = DEF_PROXYWRITE_SERVICE;
var_ipc_timeout = 3600;
mail_dict_init();
- dict_test(argc, argv);
+ dict_cli(argc, argv);
return (0);
}
* 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
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
+++ /dev/null
-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}}'
--- /dev/null
+ /*
+ * 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>
status = FAIL;
}
dict_close(dict);
+ msg_capt_free(capture);
}
return (status);
status = FAIL;
}
dict_close(dict);
+ msg_capt_free(capture);
}
return (status);
status = FAIL;
}
mkmap_close(mkmap);
+ msg_capt_free(capture);
}
return (status);
status = FAIL;
}
mkmap_close(mkmap);
+ msg_capt_free(capture);
}
return (status);
}
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
--- /dev/null
+ /*
+ * 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>
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
+++ /dev/null
-./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
--- /dev/null
+ /*
+ * 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>
/*++
/* 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)"
/* 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;
/*++
/* NAME
-/* test_main 3h
+/* test_server_main 3h
/* SUMMARY
/* test main program
/* SYNOPSIS
-/* #include <test_main.h>
+/* #include <test_server_main.h>
/* DESCRIPTION
/* .nf
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
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
/* 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;
}
/*
* 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");
}
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) \
$(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:
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 \
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
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
/*
/* 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
/* 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
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;
vstream_fclose(stream);
}
+static int request_count;
+
/* psc_dnsbl_request - send dnsbl query, increment reference count */
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,
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. */
--- /dev/null
+ /*
+ * 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>
--- /dev/null
+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
--- /dev/null
+/*++
+/* 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
+}
--- /dev/null
+#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
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+#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
--- /dev/null
+ /*
+ * 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>
--- /dev/null
+#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
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+/*++
+/* 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;
+}
--- /dev/null
+ /*
+ * 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>
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+/*++
+/* 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;
+}
/* 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
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
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)
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
.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
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 \
@$(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
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
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
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
--- /dev/null
+/*++
+/* 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));
+}
--- /dev/null
+#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
--- /dev/null
+/*++
+/* 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);
+ }
+}
--- /dev/null
+#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
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+#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
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+#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
--- /dev/null
+ /*
+ * 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>
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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>
--- /dev/null
+/*++
+/* 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);
+ }
+ }
+}
--- /dev/null
+#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
--- /dev/null
+ /*
+ * 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>
--- /dev/null
+#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
--- /dev/null
+/*++
+/* 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;
+}
--- /dev/null
+ /*
+ * 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>
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+#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
--- /dev/null
+ /*
+ * 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>
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+#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
--- /dev/null
+ /*
+ * 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>
--- /dev/null
+/*++
+/* 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);
+}
--- /dev/null
+#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
--- /dev/null
+ /*
+ * 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>
--- /dev/null
+/*++
+/* 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]);
+ }
+ }
+}
--- /dev/null
+#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
--- /dev/null
+ /*
+ * 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>
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=
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 \
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 \
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 \
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.
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)
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 \
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 $? >$@
$(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)
$(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)
$(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
$(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
$(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
$(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 \
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 \
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
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 \
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
$(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
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) | \
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
#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)
argvp->argv[argvp->argc] = 0;
}
+#if 0
+
/* argv_addn - add string to vector */
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)
}
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
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);
--- /dev/null
+ /*
+ * 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>
/*
* 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
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);
} 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);
}
>>> 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
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
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);
}
*/
int main(int argc, char **argv)
{
- dict_test(argc, argv);
+ dict_cli(argc, argv);
return (0);
}
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* porcupine.org
/*--*/
/* System library. */
-/*++
-/* 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;
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>
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 */
+++ /dev/null
-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
--- /dev/null
+ /*
+ * 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>
/* Yorktown Heights, NY 10598, USA
/*
/* Wietse Venema
-/* Google, Inc.
-/* 111 8th Avenue
-/* New York, NY 10011, USA
+/* porcupine.org
/*--*/
/* System library. */
-/*++
-/* 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;
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>
--- /dev/null
+/*++
+/* 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));
+ }
+}
--- /dev/null
+#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
--- /dev/null
+ /*
+ * 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>
+ /*
+ * 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.
*/
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>
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)
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
+++ /dev/null
-unknown: good: PASS
-unknown: duplicate lhs: PASS
-unknown: numerical lhs: PASS
-unknown: symbolic rhs: PASS
-unknown: uninitialized: PASS
-unknown: PASS=5 FAIL=0
--- /dev/null
+ /*
+ * 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>
/* 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. */
/*
* 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;
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);
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);
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. */
{
msg_error_count = 0;
}
+
+/* msg_set_longjmp_action() - exception handling */
+
+void msg_set_longjmp_action(MSG_LONGJMP_ACTION action)
+{
+ msg_longjmp_action = action;
+}
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
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* porcupine.org
/*--*/
#endif
/* 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;
*/
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);
}
/* 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().
/* 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
/* 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>
/*
* 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
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 */
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];
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;
+ }
+ }
+}
* 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);
--- /dev/null
+ /*
+ * 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>
/* 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,
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;
}
/* 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.
/* 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) {
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;
}
* External interface.
*/
extern void msg_vstream_init(const char *, VSTREAM *);
+extern void msg_vstream_enable(int);
/* LICENSE
/* .ad
#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>
+++ /dev/null
-./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
+++ /dev/null
-./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
+++ /dev/null
-./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
+++ /dev/null
-./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
--- /dev/null
+ /*
+ * 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>
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)
--- /dev/null
+ /*
+ * 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>
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
+++ /dev/null
-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
--- /dev/null
+ /*
+ * 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>
/* 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
}
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));
}
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));
#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)
msg_fatal("close server fd");
return (0);
}
-
#else
int main(int argc, char **argv)
{
return (0);
}
-
#endif
+++ /dev/null
-\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>
+++ /dev/null
-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
--- /dev/null
+ /*
+ * 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>
--- /dev/null
+/*++
+/* 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
--- /dev/null
+#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