From: Wietse Z Venema Date: Thu, 2 Apr 2026 05:00:00 +0000 (-0500) Subject: postfix-3.12-20260402 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=250e75ebd980eafa3ed5f25e1d9d6a896b794c2e;p=thirdparty%2Fpostfix.git postfix-3.12-20260402 --- diff --git a/postfix/.indent.pro b/postfix/.indent.pro index 3faf372e5..8b1707254 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -18,6 +18,7 @@ -TATTR_TABLE -TAUTHORITY_KEYID -TAUTO_CLNT +-TBASE_TEST_CASE -TBH_TABLE -TBINATTR -TBINATTR_INFO @@ -236,10 +237,17 @@ -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 @@ -295,6 +303,8 @@ -TPSC_SMTPD_COMMAND -TPSC_STARTTLS -TPSC_STATE +-TPTEST_CASE +-TPTEST_CTX -TQMGR_ENTRY -TQMGR_FEEDBACK -TQMGR_JOB @@ -374,6 +384,8 @@ -TSYS_EXITS_DETAIL -TTEST_BASE -TTEST_CASE +-TTEST_DATA +-TTEST_JMP_BUF -TTLSMGR_SCACHE -TTLSP_STATE -TTLSRPT_WRAPPER @@ -429,6 +441,7 @@ -TXSASL_SERVER_CREATE_ARGS -TXSASL_SERVER_IMPL -TXSASL_SERVER_IMPL_INFO +-Taddrinfo -Tbind_props -Tbson_iter_t -Tcipher_probe_t diff --git a/postfix/HISTORY b/postfix/HISTORY index 48cdd1d0a..528b33637 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -26549,7 +26549,6 @@ Apologies for any names omitted. Documentation: inet_interfaces and proxy_interfaces descriptions. File: proto/postconf.proto. - 20220719 Cleanup: Postfix 3.5.0 introduced debug logging noise in @@ -30691,3 +30690,111 @@ Apologies for any names omitted. Cleanup: added missing smtpd_data_restrictions and smtpd_end_of_data_restrictions in the sample master.cf file. Omission reported by Viktor Dukhovni. File: conf/master.cf. + +20260402 + + Feature: 'Ptest' infrastructure for unit tests, and 'Pmock' + infrastructure to make tests hermetic (i.e. independent of + host configuration, network configuration, DNS, or installed + Postfix files). + + Ptest was inspired by Go test, while Pmock implements a few + ideas from Google gmock. The PTEST_README file has some + information about how Postfix unit tests work. + + This changes the Postfix file footprint as follows: + + 1) This adds source files named 'foo_test.c', that test + code in 'foo.c' and 'foo.h'; and this adds source files + mock_foo.c and mock_foo.h with functions that produce + prepared outputs for expected inputs. For example, + mock_getaddrinfo.c implements getaddrinfo() and getnameinfo() + functions that return prepared outputs without doing host + or DNS lookups. More files will be added as tests are + converted to the Ptest/Pmock infrastructure. + + 2) This removes the test data files ('foo.in*', 'foo.ref*'). + + 3) This renames some files whose names end in test.c: for + example, dict_test.c is renamed to dict_cli.c. + + Reality check: the Postfix source tree contains 129 .c files + with legacy "#ifdef TEST" support for tests, 53 .c files + with a separate _test.c file, and 526 .c files that have + no test of their own. it is likely that all .c files with + have a _test.c file. The plan is therefore to attack the + problem from both ends, and to integrate support for + whole-program daemon tests that was used in 2025 to test a + new trivial-rewrite feature. + + Care was taken to minimize the changes to non-test code, + by making make clear which code paths rare reachable only + from tests. + + Changes to non-test code: + + Cleanup: migrate biff_notify() from gethostbyname() and + getservbyname() to myaddrinfo. File: local/biff_notify.c. + + Cleanup: new find_inet_service(3) module that returns an + error status; deprecate find_inet(3) where all errors are + fatal. File: util/find_inet_service.c. + + Cleanup: migrate the MySQL client from find_inet(3) to + find_inet_service(3). Files: src/global/dict_mysql.c. + + Feature: new NAME_MASK_NULL flag. When is present, the + str_name_mask_opt() function will output a string "0" when + the input mask is empty. Files: util/name_mask.[hc]. + + Cleanup: to make dict_open() easier to test, with support + for graceful degradation after dictionary type or name + syntax errors. File: util/dict_open.c. + + Cleanup: stricter parsing of TCP/UDP port numbers. File: + util/known_tcp_ports.c. + + Bugfix: mystrndup() ignored the case that length==0, when + compiled without NO_SHARED_EMPTY_STRINGS (the default). + File: util/mymalloc.c. + + Changes to make non-test code easier to test: + + Testing: support to make msg_fatal() and msg_panic() calls + testable. Instead of terminating the process, these functions + may transfer control to a call-back function that calls + [sig]longjmp(). Files: util/msg.c. + + Testing: support to make msg_xxx() calls testable. This + requires adding a context argument to msg_output() and to + msg_output() call-back functions, and making msg_output() + undoable. To that end, msg_output() is renamed to + msg_output_push(), and its companion function is called + msg_output_pop(). Files: util/msg_output.[hc]. + + Testing: support to make msg_xxx() calls testable. This + requires suspending output to VSTREAM_ERR while capturing + output for inspection. Files: util/msg_vstream.[hc]. + + Changes to test code: + + Removed .ref* and .in* files as tests were converted to + _test.c style. Files: global/hfrom_format.ref, + global/hfrom_format_test.c, global/login_sender_match.ref, + global/login_sender_match_test.c, global/map_search.ref, + global/map_search_test.c, global/smtp_reply_footer.ref, + global/smtp_reply_footer_test.c, util/known_tcp_ports.ref, + util/known_tcp_ports_test.c, util/myaddrinfo.ref*, + util/myaddrinfo_test.c, util/mystrtok.ref, util/mystrtok_test.c, + util/unescape.in, util/unescape.ref, util/unescape_test.c. + + Cleanup: renamed global/test_main.[hc] to + global/test_server_main.[hc]. Files: global/test_server_main.[hc], + bounce/bounce_tester.c, Makefiles. + + Renamed dict_test.c to dict_cli.c, stream_test.c to + sunos5_stream_test.c. + +TODO + + Reorganize PTEST_LIB, PMOCK_LIB, TESTLIB, TESTLIBS, etc. diff --git a/postfix/Makefile.in b/postfix/Makefile.in index 041cf9336..9f7802019 100644 --- a/postfix/Makefile.in +++ b/postfix/Makefile.in @@ -12,7 +12,7 @@ DIRS = src/util src/global src/dns src/tls src/xsasl src/master src/milter \ src/postsuper src/qmqpd src/spawn src/flush src/verify \ src/virtual src/proxymap src/anvil src/scache src/discard src/tlsmgr \ src/postmulti src/postscreen src/dnsblog src/tlsproxy \ - src/posttls-finger src/postlogd src/nbdb_reindexd src/testing + src/posttls-finger src/postlogd src/nbdb_reindexd src/ptest src/testing MANDIRS = proto man html LIBEXEC = libexec/post-install libexec/postfix-script libexec/postfix-wrapper \ libexec/postmulti-script libexec/postfix-non-bdb-script \ diff --git a/postfix/README_FILES/AAAREADME b/postfix/README_FILES/AAAREADME index c3c83da7d..7df74fe8b 100644 --- a/postfix/README_FILES/AAAREADME +++ b/postfix/README_FILES/AAAREADME @@ -89,3 +89,7 @@ OOtthheerr ttooppiiccss * XCLIENT_README: XCLIENT Command * XFORWARD_README: XFORWARD Command +FFoorr mmaaiinnttaaiinneerrss aanndd ccoonnttrriibbuuttoorrss + + * PTEST_README: Writing Postfix unit tests + diff --git a/postfix/README_FILES/PTEST_README b/postfix/README_FILES/PTEST_README new file mode 100644 index 000000000..522c5e862 --- /dev/null +++ b/postfix/README_FILES/PTEST_README @@ -0,0 +1,476 @@ +WWrriittiinngg PPoossttffiixx uunniitt tteessttss + +------------------------------------------------------------------------------- + +OOvveerrvviieeww + +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 + +SSiimmppllee eexxaammppllee + +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 + +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 " 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. + +TTeessttiinngg oonnee ffuunnccttiioonn wwiitthh TTEESSTT__CCAASSEE ddaattaa + +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 + +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 + +TTeessttiinngg ffuunnccttiioonnss wwiitthh ssuubbtteessttss + +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 + +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. + +SSuuggggeessttiioonnss ffoorr wwrriittiinngg tteessttss + +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 , 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. + +PPtteesstt AAPPII rreeffeerreennccee + + * Managing test errors + + * Managing log events + + * Managing test execution + + * Miscellaneous test APIs + +MMaannaaggiinngg tteesstt eerrrroorrss + +As one might expect, Ptest has support to flag unexpected test results as +errors. + +vvooiidd pptteesstt__eerrrroorr((PPTTEESSTT__CCTTXX **tt,, ccoonnsstt cchhaarr **ffoorrmmaatt,, ......)) + 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(). + +vvooiidd pptteesstt__ffaattaall((PPTTEESSTT__CCTTXX **tt,, ccoonnsstt cchhaarr **ffoorrmmaatt,, ......)) + 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. + +vvooiidd pptteesstt__iinnffoo((PPTTEESSTT__CCTTXX **tt,, ccoonnsstt cchhaarr **ffoorrmmaatt,, ......)) + 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. + +vvooiidd eexxppeecctt__pptteesstt__eerrrroorr((PPTTEESSTT__CCTTXX **tt,, ccoonnsstt cchhaarr **tteexxtt)) + 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()). + +MMaannaaggiinngg lloogg eevveennttss + +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: + +vvooiidd eexxppeecctt__pptteesstt__lloogg__eevveenntt((PPTTEESSTT__CCTTXX **tt,, ccoonnsstt cchhaarr **tteexxtt)) + 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(). + +MMaannaaggiinngg tteesstt eexxeeccuuttiioonn + +Ptest has a number of primitives that control test execution. + +vvooiidd PPTTEESSTT__RRUUNN((PPTTEESSTT__CCTTXX **tt,, ccoonnsstt cchhaarr **tteesstt__nnaammee,, {{ ccooddee iinn bbrraacceess }})) + 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. + +vvooiidd PPTTEESSTT__TTRRYY((PPTTEESSTT__CCTTXX **tt,, ccoonnsstt cchhaarr **tteesstt__nnaammee,, {{ ccooddee iinn bbrraacceess }})) + 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. + +NNOORREETTUURRNN pptteesstt__sskkiipp((PPTTEESSTT__CCTTXX **tt)) + 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. + +NNOORREETTUURRNN pptteesstt__rreettuurrnn((PPTTEESSTT__CCTTXX **tt)) + Used inside a { code in braces } block to terminate a PTEST_RUN subtest. + +vvooiidd pptteesstt__ddeeffeerr((PPTTEESSTT__CCTTXX **tt,, vvooiidd ((**ddeeffeerr__ffnn))((vvooiidd **)),, vvooiidd **ddeeffeerr__ccttxx)) + 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. + +MMiisscceellllaanneeoouuss tteesstt AAPPIIss + +PPTTEESSTT__CCTTXX **pptteesstt__ccttxx__ccuurrrreenntt((vvooiidd)) + 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. + diff --git a/postfix/conf/postfix-files b/postfix/conf/postfix-files index 0d95a975d..7db6b3c3b 100644 --- a/postfix/conf/postfix-files +++ b/postfix/conf/postfix-files @@ -312,6 +312,7 @@ $readme_directory/MILTER_README:f:root:-:644 $readme_directory/MONGODB_README:f:root:-:644 $readme_directory/MULTI_INSTANCE_README:f:root:-:644 $readme_directory/MYSQL_README:f:root:-:644 +$readme_directory/PTEST_README:f:root:-:644 $readme_directory/SMTPUTF8_README:f:root:-:644 $readme_directory/SQLITE_README:f:root:-:644 $readme_directory/NFS_README:f:root:-:644 @@ -378,6 +379,7 @@ $html_directory/MILTER_README.html:f:root:-:644 $html_directory/MONGODB_README.html:f:root:-:644 $html_directory/MULTI_INSTANCE_README.html:f:root:-:644 $html_directory/MYSQL_README.html:f:root:-:644 +$html_directory/PTEST_README.html:f:root:-:644 $html_directory/SMTPUTF8_README.html:f:root:-:644 $html_directory/SQLITE_README.html:f:root:-:644 $html_directory/NFS_README.html:f:root:-:644 diff --git a/postfix/html/PTEST_README.html b/postfix/html/PTEST_README.html new file mode 100644 index 000000000..165590613 --- /dev/null +++ b/postfix/html/PTEST_README.html @@ -0,0 +1,642 @@ + + + + + + +Writing Postfix unit tests + + + + + + + + +

Writing +Postfix unit tests

+ +
+ +

Overview

+ +

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

+ +

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.

    + +
      + +
    • 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.

      + +
    • 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. +

      + +
    • 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.

    + +
+ +

Testing one function with +TEST_CASE data

+ +

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
+
+
+ +

Testing functions with subtests

+ +

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.

+ +

Suggestions for writing tests

+ +

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.

    + +
+ +

Ptest API reference

+ + + +

Managing test errors

+ +

As one might expect, Ptest has support to flag unexpected test +results as errors.

+ +
+ +
void ptest_error(PTEST_CTX *t, +const char *format, ...)
+ +
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().

+ +
void ptest_fatal(PTEST_CTX *t, +const char *format, ...)
+ +
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. +

+ +
+ +
void ptest_info(PTEST_CTX *t, +const char *format, ...)
+ +
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.

+ +
+ +
void +expect_ptest_error(PTEST_CTX *t, const char *text) +
+ +
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()).
+ +
+ +

Managing log events

+ +

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:

+ +
+ +
void +expect_ptest_log_event(PTEST_CTX *t, const char *text) +
+ +
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().
+ +
+ +

Managing test execution

+ +

Ptest has a number of primitives that control test execution. +

+ +
+ +
void PTEST_RUN(PTEST_CTX *t, const +char *test_name, { code in braces })
+ +
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.

+ +
void PTEST_TRY(PTEST_CTX *t, const +char *test_name, { code in braces })
+ +
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. +

+ +
NORETURN ptest_skip(PTEST_CTX +*t)
+ +
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.

+ +
NORETURN ptest_return(PTEST_CTX +*t)
+ +
Used inside a { code in braces } block to terminate +a PTEST_RUN subtest.

+ +
void ptest_defer(PTEST_CTX *t, +void (*defer_fn)(void *), void *defer_ctx)
+ +
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.
+ +
+ +

Miscellaneous test APIs

+ +
+ +
PTEST_CTX +*ptest_ctx_current(void)
+ +
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.

+ +
+ + + + + diff --git a/postfix/html/index.html b/postfix/html/index.html index 00a7d0404..1f7507619 100644 --- a/postfix/html/index.html +++ b/postfix/html/index.html @@ -223,6 +223,14 @@ Recipients +

For maintainers and contributors

+ + + diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index b7159c0d8..c797672c3 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -1229,6 +1229,20 @@ while (<>) { s/(ftp:\/\/[^ ,"\(\)]*[^ ,"\(\):;!?.])/$1<\/a>/; s/\bRFC\s*([1-9]\d*)/$&<\/a>/g; + # Ptest hyperlinks + + s;\bptest_error\b;$&;g; + s;\bptest_fatal\b;$&;g; + s;\bptest_info\b;$&;g; + s;\bexpect_ptest_error\b;$&;g; + s;\bexpect_ptest_log_event\b;$&;g; + s;\bPTEST_RUN\b;$&;g; + s;\bPTEST_TRY\b;$&;g; + s;\bptest_skip\b;$&;g; + s;\bptest_return\b;$&;g; + s;\bptest_defer\b;$&;g; + s;\bpptest_ctx_current\b;$&;g; + # Split README/RFC/parameter/restriction hyperlinks that span line breaks s/()([-A-Za-z0-9_]*)\b([-<\/bB>]*\n *[]*)\b([-A-Za-z0-9_]*)(<\/a>)/$1$2$5$3$1$4$5/; diff --git a/postfix/proto/Makefile.in b/postfix/proto/Makefile.in index c1c876450..d150d11e6 100644 --- a/postfix/proto/Makefile.in +++ b/postfix/proto/Makefile.in @@ -40,6 +40,7 @@ HTML = ../html/ADDRESS_CLASS_README.html \ ../html/PGSQL_README.html \ ../html/POSTSCREEN_3_5_README.html \ ../html/POSTSCREEN_README.html \ + ../html/PTEST_README.html \ ../html/QSHAPE_README.html \ ../html/RESTRICTION_CLASS_README.html \ ../html/REQUIRETLS_README.html \ @@ -94,6 +95,7 @@ README = ../README_FILES/ADDRESS_CLASS_README \ ../README_FILES/PGSQL_README \ ../README_FILES/POSTSCREEN_3_5_README \ ../README_FILES/POSTSCREEN_README \ + ../README_FILES/PTEST_README \ ../README_FILES/QSHAPE_README \ ../README_FILES/RESTRICTION_CLASS_README \ ../README_FILES/REQUIRETLS_README \ @@ -287,6 +289,9 @@ clobber: ../html/POSTSCREEN_README.html: POSTSCREEN_README.html $(DETAB) $? | $(POSTLINK) >$@ +../html/PTEST_README.html: PTEST_README.html + $(DETAB) $? | $(POSTLINK) >$@ + ../html/QMQP_README.html: QMQP_README.html $(DETAB) $? | $(POSTLINK) >$@ @@ -482,6 +487,9 @@ clobber: ../README_FILES/POSTSCREEN_README: POSTSCREEN_README.html $(DETAB) $? | $(HT2READ) >$@ +../README_FILES/PTEST_README: PTEST_README.html + $(DETAB) $? | $(HT2READ) >$@ + ../README_FILES/QMQP_README: QMQP_README.html $(DETAB) $? | $(HT2READ) >$@ diff --git a/postfix/proto/PTEST_README.html b/postfix/proto/PTEST_README.html new file mode 100644 index 000000000..4d5259fca --- /dev/null +++ b/postfix/proto/PTEST_README.html @@ -0,0 +1,642 @@ + + + + + + +Writing Postfix unit tests + + + + + + + + +

Writing +Postfix unit tests

+ +
+ +

Overview

+ +

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

+ +

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.

    + +
      + +
    • 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.

      + +
    • 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. +

      + +
    • 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.

    + +
+ +

Testing one function with +TEST_CASE data

+ +

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
+
+
+ +

Testing functions with subtests

+ +

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.

+ +

Suggestions for writing tests

+ +

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.

    + +
+ +

Ptest API reference

+ + + +

Managing test errors

+ +

As one might expect, Ptest has support to flag unexpected test +results as errors.

+ +
+ +
void ptest_error(PTEST_CTX *t, +const char *format, ...)
+ +
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().

+ +
void ptest_fatal(PTEST_CTX *t, +const char *format, ...)
+ +
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. +

+ +
+ +
void ptest_info(PTEST_CTX *t, +const char *format, ...)
+ +
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.

+ +
+ +
void +expect_ptest_error(PTEST_CTX *t, const char *text) +
+ +
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()).
+ +
+ +

Managing log events

+ +

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:

+ +
+ +
void +expect_ptest_log_event(PTEST_CTX *t, const char *text) +
+ +
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().
+ +
+ +

Managing test execution

+ +

Ptest has a number of primitives that control test execution. +

+ +
+ +
void PTEST_RUN(PTEST_CTX *t, const +char *test_name, { code in braces })
+ +
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.

+ +
void PTEST_TRY(PTEST_CTX *t, const +char *test_name, { code in braces })
+ +
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. +

+ +
NORETURN ptest_skip(PTEST_CTX +*t)
+ +
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.

+ +
NORETURN ptest_return(PTEST_CTX +*t)
+ +
Used inside a { code in braces } block to terminate +a PTEST_RUN subtest.

+ +
void ptest_defer(PTEST_CTX *t, +void (*defer_fn)(void *), void *defer_ctx)
+ +
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.
+ +
+ +

Miscellaneous test APIs

+ +
+ +
PTEST_CTX +*ptest_ctx_current(void)
+ +
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.

+ +
+ + + + + diff --git a/postfix/proto/stop.double-cc b/postfix/proto/stop.double-cc index 40fe40e68..18329d08d 100644 --- a/postfix/proto/stop.double-cc +++ b/postfix/proto/stop.double-cc @@ -356,3 +356,11 @@ dict_open dict_open looks up our DICT_OPEN_INFO with dict_fn and mkmap_fn mkmap_open mkmap_open looks up our DICT_OPEN_INFO with dict_fn and mkmap_fn typedef struct MSG_CAPTURE MSG_CAPTURE MSG_CAPTURE MSG_CAPTURE msg_capt_create ssize_t init_size +copy_addrinfo copy_addrinfo expectation helper +int int eq_enum +int int eq_str +void void dns_set_h_errno int herrval +application application specific + Null name or name name +void void defer_ctx + To undo a ptest_defer call call the function with a diff --git a/postfix/proto/stop.double-proto-html b/postfix/proto/stop.double-proto-html index beee8a9b4..25c2fad2e 100644 --- a/postfix/proto/stop.double-proto-html +++ b/postfix/proto/stop.double-proto-html @@ -384,3 +384,9 @@ smtp_requiretls_policy smtp_requiretls_policy inline in tt tt p mynetworks mynetworks lmdb etc postfix network_table 44 enable redirect redirect hash to lmdb or cdb + 90 type_name unionmap static one fail fail +132 for cpp tp want_log cpp tp want_log MAX_WANT_LOG cpp cpp +184 test_map_search test_map_search + void ptest_defer PTEST_CTX t void defer_fn void void defer_ctx +130 type_name unionmap static one fail fail +187 for cpp tp want_log cpp tp want_log MAX_WANT_LOG cpp cpp diff --git a/postfix/proto/stop.spell-cc b/postfix/proto/stop.spell-cc index 3a58f60aa..b800a34e7 100644 --- a/postfix/proto/stop.spell-cc +++ b/postfix/proto/stop.spell-cc @@ -1916,7 +1916,59 @@ surr whoami IfChange ThenChange +Aargh +abc +addrinfos +clearjmp +deserialize +Deserialize +deserialized +endservent +errval +getservbyname +getservbyport +getservent +gotlen +herrno +herrval +hostlen +ipprotocol +JMP +Matcher +matchers +MATCHERS +MOCKABLE +NORAMDOMIZE +notexist +onlinepubs +opengroup +pmock +ptest +ptestcase +ptestcases +resetjmp +servent +servlen +setservent +SNDBUF +sockadddr +sockaddrs +stayopen +subtest +subtests +Subtests +unallocated +wantlen +wrapup +xrat +xsh +yesno +Ptest +Pmock +PMOCK +TESTLIB +TESTLIBS +undoable EKU clientAuth serverAuth -WebPKI diff --git a/postfix/proto/stop.spell-history b/postfix/proto/stop.spell-history index 924d594ea..b9ef6afd3 100644 --- a/postfix/proto/stop.spell-history +++ b/postfix/proto/stop.spell-history @@ -135,3 +135,6 @@ Teodor IfThisThenThat linter MAXINT +gmock +sunos +WebPKI diff --git a/postfix/proto/stop.spell-proto-html b/postfix/proto/stop.spell-proto-html index 66414fd63..caaa74a7f 100644 --- a/postfix/proto/stop.spell-proto-html +++ b/postfix/proto/stop.spell-proto-html @@ -415,17 +415,36 @@ hPP hPPx hx lcdb -#!/bin/sh - -# Spellchecks the proto HTML files. - -LANG=C; export LANG - -mantools/dehtml proto/*html proto/*.proto | tr '+' ' ' | spell | grep -F -vxf proto/stop | grep -F -vxf proto/stop.spell-proto-html +APIs +const +cpp +fn +Gtest +myfree +mymalloc +mymemdup +myrealloc +mystrdup +NORETURN +ptest +Ptest +PTEST +ptestcases +RDONLY +STR +struct +subtest +subtests +testcase +testcases +testname +tp +typedef +Valgrind +vstring EKU WebPKI ccerts clientAuth -dehtml +notfound serverAuth -vxf diff --git a/postfix/src/bounce/Makefile.in b/postfix/src/bounce/Makefile.in index 957ccbed4..b6d1b3a5e 100644 --- a/postfix/src/bounce/Makefile.in +++ b/postfix/src/bounce/Makefile.in @@ -355,7 +355,7 @@ bounce_notify_util_tester.o: ../../include/rec_type.h bounce_notify_util_tester.o: ../../include/recipient_list.h bounce_notify_util_tester.o: ../../include/record.h bounce_notify_util_tester.o: ../../include/sys_defs.h -bounce_notify_util_tester.o: ../../include/test_main.h +bounce_notify_util_tester.o: ../../include/test_server_main.h bounce_notify_util_tester.o: ../../include/vbuf.h bounce_notify_util_tester.o: ../../include/vstream.h bounce_notify_util_tester.o: ../../include/vstring.h diff --git a/postfix/src/bounce/bounce_notify_util_tester.c b/postfix/src/bounce/bounce_notify_util_tester.c index 3151ced48..2d6c12303 100644 --- a/postfix/src/bounce/bounce_notify_util_tester.c +++ b/postfix/src/bounce/bounce_notify_util_tester.c @@ -30,7 +30,7 @@ /* * Testing library. */ -#include +#include #define TEST_ENCODING "7bit" #define NO_SMTPUTF8 (0) @@ -156,7 +156,7 @@ int main(int argc, char **argv) 0, }; - test_main(argc, argv, test_driver, + test_server_main(argc, argv, test_driver, CA_TEST_MAIN_INT_TABLE(int_table), CA_TEST_MAIN_STR_TABLE(str_table), CA_TEST_MAIN_TIME_TABLE(time_table), diff --git a/postfix/src/dns/Makefile.in b/postfix/src/dns/Makefile.in index ddcd1de34..59b515baa 100644 --- a/postfix/src/dns/Makefile.in +++ b/postfix/src/dns/Makefile.in @@ -1,10 +1,10 @@ SHELL = /bin/sh SRCS = dns_lookup.c dns_rr.c dns_strerror.c dns_strtype.c dns_rr_to_pa.c \ dns_sa_to_rr.c dns_rr_eq_sa.c dns_rr_to_sa.c dns_strrecord.c \ - dns_rr_filter.c dns_str_resflags.c dns_sec.c + dns_rr_filter.c dns_str_resflags.c dns_sec.c dns_lookup_types.c OBJS = dns_lookup.o dns_rr.o dns_strerror.o dns_strtype.o dns_rr_to_pa.o \ dns_sa_to_rr.o dns_rr_eq_sa.o dns_rr_to_sa.o dns_strrecord.o \ - dns_rr_filter.o dns_str_resflags.o dns_sec.o + dns_rr_filter.o dns_str_resflags.o dns_sec.o dns_lookup_types.o HDRS = dns.h TESTSRC = test_dns_lookup.c test_alias_token.c DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) @@ -12,9 +12,10 @@ CFLAGS = $(DEBUG) $(OPT) $(DEFS) INCL = LIB = lib$(LIB_PREFIX)dns$(LIB_SUFFIX) TESTPROG= test_dns_lookup dns_rr_to_pa dns_rr_to_sa dns_sa_to_rr dns_rr_eq_sa \ - dns_rr_test + dns_rr_test dns_lookup_types_test LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +TEST_LIB= ../../lib/libtesting.a ../../lib/libptest.a LIB_DIR = ../../lib INC_DIR = ../../include @@ -22,17 +23,17 @@ INC_DIR = ../../include all: $(LIB) -$(OBJS): ../../conf/makedefs.out +$(OBJS) $(TEST_OBJ): ../../conf/makedefs.out Makefile: Makefile.in cat ../../conf/makedefs.out $? >$@ test: $(TESTPROG) -tests: test dns_rr_to_pa_test dns_rr_to_sa_test dns_sa_to_rr_test \ +tests: update test dns_rr_to_pa_test dns_rr_to_sa_test dns_sa_to_rr_test \ dns_rr_eq_sa_test no-a-test no-aaaa-test no-mx-test \ error-filter-test nullmx_test nxdomain_test mxonly_test \ - dnsbl_tests dns_rr_tests + dnsbl_tests test_dns_rr test_dns_lookup_types dnsbl_tests: \ dnsbl_ttl_127.0.0.2_bind_plain_test \ @@ -99,7 +100,7 @@ dns_rr_eq_sa: $(LIB) $(LIBS) mv $@.o junk $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) mv junk $@.o - + dns_rr_to_pa_test: dns_rr_to_pa dns_rr_to_pa.in dns_rr_to_pa.ref $(SHLIB_ENV) $(VALGRIND) ./dns_rr_to_pa `cat dns_rr_to_pa.in` >dns_rr_to_pa.tmp diff dns_rr_to_pa.ref dns_rr_to_pa.tmp @@ -161,6 +162,14 @@ mxonly_test: test_dns_lookup mxonly_test.ref diff mxonly_test.ref mxonly_test.tmp rm -f mxonly_test.tmp +dns_lookup_types_test: update dns_lookup_types_test.o \ + $(LIB_DIR)/mock_dns_lookup.o $(TEST_LIB) $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(LIB_DIR)/mock_dns_lookup.o \ + $(TEST_LIB) $(LIB) $(LIBS) $(SYSLIBS) + +test_dns_lookup_types: dns_lookup_types_test + $(SHLIB_ENV) $(VALGRIND) ./dns_lookup_types_test + # Non-existent record, libbind API, RFC 2308 disabled. dnsbl_ttl_127.0.0.1_bind_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_plain.ref @@ -241,11 +250,11 @@ dnsbl_ttl_127.0.0.2_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_p diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_ncache.tmp rm -f dnsbl_ttl_127.0.0.2_priv_ncache.tmp -dns_rr_tests: dns_rr_test +test_dns_rr: dns_rr_test $(SHLIB_ENV) $(VALGRIND) ./dns_rr_test dns_rr_test: dns_rr_test.o $(LIB) $(LIBS) - $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(LIB) $(LIBS) $(SYSLIBS) clean: rm -f *.o $(LIB) *core $(TESTPROG) junk @@ -280,6 +289,40 @@ dns_lookup.o: ../../include/vstream.h dns_lookup.o: ../../include/vstring.h dns_lookup.o: dns.h dns_lookup.o: dns_lookup.c +dns_lookup_types.o: ../../include/argv.h +dns_lookup_types.o: ../../include/check_arg.h +dns_lookup_types.o: ../../include/dict.h +dns_lookup_types.o: ../../include/maps.h +dns_lookup_types.o: ../../include/msg.h +dns_lookup_types.o: ../../include/myaddrinfo.h +dns_lookup_types.o: ../../include/myflock.h +dns_lookup_types.o: ../../include/sock_addr.h +dns_lookup_types.o: ../../include/sys_defs.h +dns_lookup_types.o: ../../include/vbuf.h +dns_lookup_types.o: ../../include/vstream.h +dns_lookup_types.o: ../../include/vstring.h +dns_lookup_types.o: dns.h +dns_lookup_types.o: dns_lookup_types.c +dns_lookup_types_test.o: ../../include/argv.h +dns_lookup_types_test.o: ../../include/check_arg.h +dns_lookup_types_test.o: ../../include/mock_dns.h +dns_lookup_types_test.o: ../../include/msg.h +dns_lookup_types_test.o: ../../include/msg_jmp.h +dns_lookup_types_test.o: ../../include/msg_output.h +dns_lookup_types_test.o: ../../include/msg_vstream.h +dns_lookup_types_test.o: ../../include/myaddrinfo.h +dns_lookup_types_test.o: ../../include/myrand.h +dns_lookup_types_test.o: ../../include/pmock_expect.h +dns_lookup_types_test.o: ../../include/ptest.h +dns_lookup_types_test.o: ../../include/ptest_main.h +dns_lookup_types_test.o: ../../include/sock_addr.h +dns_lookup_types_test.o: ../../include/stringops.h +dns_lookup_types_test.o: ../../include/sys_defs.h +dns_lookup_types_test.o: ../../include/vbuf.h +dns_lookup_types_test.o: ../../include/vstream.h +dns_lookup_types_test.o: ../../include/vstring.h +dns_lookup_types_test.o: dns.h +dns_lookup_types_test.o: dns_lookup_types_test.c dns_rr.o: ../../include/check_arg.h dns_rr.o: ../../include/msg.h dns_rr.o: ../../include/myaddrinfo.h diff --git a/postfix/src/dns/dns.h b/postfix/src/dns/dns.h index 6d8c6b25b..087e05e99 100644 --- a/postfix/src/dns/dns.h +++ b/postfix/src/dns/dns.h @@ -265,8 +265,9 @@ extern int dns_lookup_x(const char *, unsigned, unsigned, DNS_RR **, extern int dns_lookup_rl(const char *, unsigned, DNS_RR **, VSTRING *, VSTRING *, int *, int,...); extern int dns_lookup_rv(const char *, unsigned, DNS_RR **, VSTRING *, - VSTRING *, int *, int, unsigned *); + VSTRING *, int *, int, const unsigned *); extern int dns_get_h_errno(void); +extern void dns_set_h_errno(int); #define dns_lookup(name, type, rflags, list, fqdn, why) \ dns_lookup_x((name), (type), (rflags), (list), (fqdn), (why), (int *) 0, \ diff --git a/postfix/src/dns/dns_lookup.c b/postfix/src/dns/dns_lookup.c index 03c42e763..11115c3da 100644 --- a/postfix/src/dns/dns_lookup.c +++ b/postfix/src/dns/dns_lookup.c @@ -33,6 +33,9 @@ /* unsigned *ltype; /* /* int dns_get_h_errno() +/* +/* void dns_set_h_errno( +/* int errval) /* AUXILIARY FUNCTIONS /* extern int var_dns_ncache_ttl_fix; /* @@ -91,9 +94,9 @@ /* checks in the rest of Postfix, because it is not a valid /* host or domain name. /* -/* dns_get_h_errno() returns the last error. This deprecates -/* usage of the global h_errno variable. We should not rely -/* on that being updated. +/* dns_get_h_errno() returns the last error, and dns_set_h_errno() +/* sets it. This deprecates usage of the global h_errno variable. +/* We should not rely on that being updated. /* /* dns_lookup_l() and dns_lookup_v() allow the user to specify /* a list of resource types. @@ -1182,142 +1185,16 @@ int dns_lookup_x(const char *name, unsigned type, unsigned flags, return (DNS_NOTFOUND); } -/* dns_lookup_rl - DNS lookup interface with types list */ - -int dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist, - VSTRING *fqdn, VSTRING *why, int *rcode, - int lflags,...) -{ - va_list ap; - unsigned type, next; - int status = DNS_NOTFOUND; - int hpref_status = INT_MIN; - VSTRING *hpref_rtext = 0; - int hpref_rcode; - int hpref_h_errno; - DNS_RR *rr; - - /* Save intermediate highest-priority result. */ -#define SAVE_HPREF_STATUS() do { \ - hpref_status = status; \ - if (rcode) \ - hpref_rcode = *rcode; \ - if (why && status != DNS_OK) \ - vstring_strcpy(hpref_rtext ? hpref_rtext : \ - (hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \ - vstring_str(why)); \ - hpref_h_errno = DNS_GET_H_ERRNO(&dns_res_state); \ - } while (0) - - /* Restore intermediate highest-priority result. */ -#define RESTORE_HPREF_STATUS() do { \ - status = hpref_status; \ - if (rcode) \ - *rcode = hpref_rcode; \ - if (why && status != DNS_OK) \ - vstring_strcpy(why, vstring_str(hpref_rtext)); \ - DNS_SET_H_ERRNO(&dns_res_state, hpref_h_errno); \ - } while (0) - - if (rrlist) - *rrlist = 0; - va_start(ap, lflags); - for (type = va_arg(ap, unsigned); type != 0; type = next) { - next = va_arg(ap, unsigned); - if (msg_verbose) - msg_info("lookup %s type %s flags %s", - name, dns_strtype(type), dns_str_resflags(flags)); - status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0, - fqdn, why, rcode, lflags); - if (rrlist && rr) { - *rrlist = dns_rr_append(*rrlist, rr); - if (DNS_RR_IS_TRUNCATED(*rrlist)) - break; - } - if (status == DNS_OK) { - if (lflags & DNS_REQ_FLAG_STOP_OK) - break; - } else if (status == DNS_INVAL) { - if (lflags & DNS_REQ_FLAG_STOP_INVAL) - break; - } else if (status == DNS_POLICY) { - if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY)) - break; - } else if (status == DNS_NULLMX) { - if (lflags & DNS_REQ_FLAG_STOP_NULLMX) - break; - } - /* XXX Stop after NXDOMAIN error. */ - if (next == 0) - break; - if (status >= hpref_status) - SAVE_HPREF_STATUS(); /* save last info */ - } - va_end(ap); - if (status < hpref_status) - RESTORE_HPREF_STATUS(); /* else report last info */ - if (hpref_rtext) - vstring_free(hpref_rtext); - return (status); -} - -/* dns_lookup_rv - DNS lookup interface with types vector */ +/* dns_get_h_errno - get the last lookup status */ -int dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist, - VSTRING *fqdn, VSTRING *why, int *rcode, - int lflags, unsigned *types) +int dns_get_h_errno(void) { - unsigned type, next; - int status = DNS_NOTFOUND; - int hpref_status = INT_MIN; - VSTRING *hpref_rtext = 0; - int hpref_rcode; - int hpref_h_errno; - DNS_RR *rr; - - if (rrlist) - *rrlist = 0; - for (type = *types++; type != 0; type = next) { - next = *types++; - if (msg_verbose) - msg_info("lookup %s type %s flags %s", - name, dns_strtype(type), dns_str_resflags(flags)); - status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0, - fqdn, why, rcode, lflags); - if (rrlist && rr) { - *rrlist = dns_rr_append(*rrlist, rr); - if (DNS_RR_IS_TRUNCATED(*rrlist)) - break; - } - if (status == DNS_OK) { - if (lflags & DNS_REQ_FLAG_STOP_OK) - break; - } else if (status == DNS_INVAL) { - if (lflags & DNS_REQ_FLAG_STOP_INVAL) - break; - } else if (status == DNS_POLICY) { - if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY)) - break; - } else if (status == DNS_NULLMX) { - if (lflags & DNS_REQ_FLAG_STOP_NULLMX) - break; - } - /* XXX Stop after NXDOMAIN error. */ - if (next == 0) - break; - if (status >= hpref_status) - SAVE_HPREF_STATUS(); /* save last info */ - } - if (status < hpref_status) - RESTORE_HPREF_STATUS(); /* else report last info */ - if (hpref_rtext) - vstring_free(hpref_rtext); - return (status); + return (DNS_GET_H_ERRNO(&dns_res_state)); } -/* dns_get_h_errno - get the last lookup status */ +/* dns_set_h_errno - set the last lookup status */ -int dns_get_h_errno(void) +void dns_set_h_errno(int errval) { - return (DNS_GET_H_ERRNO(&dns_res_state)); + DNS_SET_H_ERRNO(&dns_res_state, errval); } diff --git a/postfix/src/dns/dns_lookup_types.c b/postfix/src/dns/dns_lookup_types.c new file mode 100644 index 000000000..bd09eda96 --- /dev/null +++ b/postfix/src/dns/dns_lookup_types.c @@ -0,0 +1,186 @@ +/*++ +/* NAME +/* dns_lookup_types 3 +/* SUMMARY +/* domain name service lookup for multiple types +/* SYNOPSIS +/* #include +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* DNS library. */ + +#define LIBDNS_INTERNAL +#include + + /* + * KISS memory management. + */ +#define MAX_TYPE 10 + +/* dns_lookup_rl - DNS lookup interface with types list */ + +int dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist, + VSTRING *fqdn, VSTRING *why, int *rcode, + int lflags,...) +{ + va_list ap; + unsigned type, types[MAX_TYPE]; + int count = 0; + + va_start(ap, lflags); + for (type = va_arg(ap, unsigned); type != 0; type = va_arg(ap, unsigned)) { + if (count >= MAX_TYPE - 1) + msg_panic("dns_lookup_rl: too many types"); + types[count++] = type; + } + types[count] = 0; + va_end(ap); + return (dns_lookup_rv(name, flags, rrlist, fqdn, why, rcode, lflags, types)); +} + +/* dns_lookup_rv - DNS lookup interface with types vector */ + +int dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist, + VSTRING *fqdn, VSTRING *why, int *rcode, + int lflags, const unsigned *types) +{ + unsigned type, next; + int status = DNS_NOTFOUND; + int hpref_status = INT_MIN; + VSTRING *hpref_rtext = 0; + int hpref_rcode; + int hpref_h_errno; + DNS_RR *rr; + + /* Save intermediate highest-priority result. */ +#define SAVE_HPREF_STATUS() do { \ + hpref_status = status; \ + if (rcode) \ + hpref_rcode = *rcode; \ + if (why && status != DNS_OK) \ + vstring_strcpy(hpref_rtext ? hpref_rtext : \ + (hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \ + vstring_str(why)); \ + hpref_h_errno = dns_get_h_errno(); \ + } while (0) + + /* Restore intermediate highest-priority result. */ +#define RESTORE_HPREF_STATUS() do { \ + status = hpref_status; \ + if (rcode) \ + *rcode = hpref_rcode; \ + if (why && status != DNS_OK) \ + vstring_strcpy(why, vstring_str(hpref_rtext)); \ + dns_set_h_errno(hpref_h_errno); \ + } while (0) + + if (rrlist) + *rrlist = 0; + for (type = *types++; type != 0; type = next) { + next = *types++; + if (msg_verbose) + msg_info("lookup %s type %s flags %s", + name, dns_strtype(type), dns_str_resflags(flags)); + status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0, + fqdn, why, rcode, lflags); + if (rrlist && rr) { + *rrlist = dns_rr_append(*rrlist, rr); + if (DNS_RR_IS_TRUNCATED(*rrlist)) + break; + } + if (status == DNS_OK) { + if (lflags & DNS_REQ_FLAG_STOP_OK) + break; + } else if (status == DNS_INVAL) { + if (lflags & DNS_REQ_FLAG_STOP_INVAL) + break; + } else if (status == DNS_POLICY) { + if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY)) + break; + } else if (status == DNS_NULLMX) { + if (lflags & DNS_REQ_FLAG_STOP_NULLMX) + break; + } + /* XXX Stop after NXDOMAIN error. */ + if (next == 0) + break; + if (status >= hpref_status) + SAVE_HPREF_STATUS(); /* save last info */ + } + if (status < hpref_status) + RESTORE_HPREF_STATUS(); /* else report last info */ + if (hpref_rtext) + vstring_free(hpref_rtext); + return (status); +} diff --git a/postfix/src/dns/dns_lookup_types_test.c b/postfix/src/dns/dns_lookup_types_test.c new file mode 100644 index 000000000..3813f7381 --- /dev/null +++ b/postfix/src/dns/dns_lookup_types_test.c @@ -0,0 +1,200 @@ + /* + * Test program to mocks including logging. See pmock_expect_test.c and + * ptest_main.h for a documented example. + */ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + + /* + * DNS library. + */ +#include + + /* + * Test library. + */ +#include +#include + +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 diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index ad40269ce..273e7ab67 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -36,7 +36,7 @@ SRCS = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \ mail_addr_form.c quote_flags.c maillog_client.c \ normalize_mailhost_addr.c map_search.c reject_deliver_request.c \ info_log_addr_form.c sasl_mech_filter.c login_sender_match.c \ - test_main.c compat_level.c config_known_tcp_ports.c \ + test_server_main.c compat_level.c config_known_tcp_ports.c \ hfrom_format.c rfc2047_code.c ascii_header_text.c sendopts.c \ pol_stats.c nbdb_clnt.c nbdb_util.c allowed_prefix.c \ nbdb_redirect.c nbdb_surrogate.c @@ -77,7 +77,7 @@ OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \ $(NON_PLUGIN_MAP_OBJ) mail_addr_form.o quote_flags.o maillog_client.o \ normalize_mailhost_addr.o map_search.o reject_deliver_request.o \ info_log_addr_form.o sasl_mech_filter.o login_sender_match.o \ - test_main.o compat_level.o config_known_tcp_ports.o \ + test_server_main.o compat_level.o config_known_tcp_ports.o \ hfrom_format.o rfc2047_code.o ascii_header_text.o sendopts.o \ pol_stats.o nbdb_clnt.o nbdb_util.o allowed_prefix.o \ nbdb_redirect.o nbdb_surrogate.o @@ -85,7 +85,6 @@ OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \ # When hard-linking these maps, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ), # otherwise it sets the PLUGIN_* macros. MAP_OBJ = dict_ldap.o dict_mysql.o dict_pgsql.o dict_sqlite.o dict_mongodb.o - HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \ canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \ conv_time.h db_common.h debug_peer.h debug_process.h defer.h \ @@ -116,14 +115,16 @@ HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \ attr_override.h mail_parm_split.h midna_adomain.h mail_addr_form.h \ maillog_client.h normalize_mailhost_addr.h map_search.h \ info_log_addr_form.h sasl_mech_filter.h login_sender_match.h \ - test_main.h compat_level.h config_known_tcp_ports.h \ + test_server_main.h compat_level.h config_known_tcp_ports.h \ hfrom_format.h rfc2047_code.h ascii_header_text.h sendopts.h \ pol_stats.h nbdb_clnt.h nbdb_util.h allowed_prefix.h \ nbdb_redirect.h nbdb_surrogate.h TESTSRC = rec2stream.c stream2rec.c recdump.c dict_sqlite_test.c \ ehlo_mask_test.c haproxy_srvr_test.c sendopts_test.c pol_stats_test.c \ allowed_prefix_test.c nbdb_util_test.c nbdb_redirect_test.c \ - nbdb_surrogate_test.c + nbdb_surrogate_test.c recdump.c login_sender_match_test.c \ + normalize_mailhost_addr_test.c smtp_reply_footer_test.c \ + map_search_test.c DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) INCL = @@ -137,15 +138,18 @@ TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \ valid_mailhost_addr own_inet_addr header_body_checks \ data_redirect addr_match_list safe_ultostr verify_sender_addr \ mail_dict server_acl uxtext mail_parm_split \ - fold_addr smtp_reply_footer mail_addr_map normalize_mailhost_addr \ - haproxy_srvr_test map_search delivered_hdr login_sender_match_test \ - compat_level config_known_tcp_ports hfrom_format rfc2047_code \ + fold_addr smtp_reply_footer_test mail_addr_map \ + normalize_mailhost_addr_test haproxy_srvr_test map_search_test \ + delivered_hdr_test login_sender_match_test compat_level \ + config_known_tcp_ports_test hfrom_format_test rfc2047_code \ ascii_header_text sendopts_test dict_sqlite_test pol_stats_test \ allowed_prefix_test nbdb_util_test nbdb_redirect_test \ nbdb_surrogate_test TESTLIB = $(LIB_DIR)/libtesting.a LIBS = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +PTEST_LIB= ../../lib/libptest.a +PMOCK_LIB= ../../lib/libtesting.a LIB_DIR = ../../lib INC_DIR = ../../include PLUGIN_MAP_SO = $(LIB_PREFIX)ldap$(LIB_SUFFIX) $(LIB_PREFIX)mysql$(LIB_SUFFIX) \ @@ -156,7 +160,7 @@ MAKES = all: $(LIB) $(PLUGIN_MAP_SO_MAKE) $(PLUGIN_MAP_OBJ) -$(OBJS) $(PLUGIN_MAP_OBJ): ../../conf/makedefs.out +$(OBJS) $(PLUGIN_MAP_OBJ) $(TESTPROG): ../../conf/makedefs.out Makefile: Makefile.in cat ../../conf/makedefs.out $? >$@ @@ -387,45 +391,24 @@ mail_parm_split: mail_parm_split.c $(LIB) $(LIBS) fold_addr: fold_addr.c $(LIB) $(LIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) -smtp_reply_footer: smtp_reply_footer.c $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) - -normalize_mailhost_addr: normalize_mailhost_addr.c $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) - -haproxy_srvr_test: haproxy_srvr_test.c $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) - -map_search: map_search.c $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) - -delivered_hdr: delivered_hdr.c $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) - -login_sender_match_test: login_sender_match_test.c $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) - compat_level: compat_level.c $(LIB) $(LIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) -hfrom_format: hfrom_format.c $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) - rfc2047_code: rfc2047_code.c $(LIB) $(LIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) ascii_header_text: ascii_header_text.c $(LIB) $(LIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) -sendopts_test: sendopts_test.c $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) +sendopts_test: sendopts_test.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) -dict_sqlite_test: dict_sqlite_test.c dict_sqlite.o $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c dict_sqlite.o $(LIB) $(LIBS) \ +dict_sqlite_test: dict_sqlite_test.o dict_sqlite.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o dict_sqlite.o $(LIB) $(LIBS) \ $(SYSLIBS) $(AUXLIBS_SQLITE) -pol_stats_test: pol_stats_test.c pol_stats.o $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c pol_stats.o $(LIB) $(LIBS) \ +pol_stats_test: pol_stats_test.o pol_stats.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o pol_stats.o $(LIB) $(LIBS) \ $(SYSLIBS) allowed_prefix_test: allowed_prefix_test.o $(TESTLIB) $(LIB) $(LIBS) @@ -440,19 +423,16 @@ nbdb_redirect_test: nbdb_redirect_test.o $(TESTLIB) $(LIB) $(LIBS) nbdb_surrogate_test: nbdb_surrogate_test.o $(TESTLIB) $(LIB) $(LIBS) $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIB) $(LIB) $(LIBS) $(SYSLIBS) -config_known_tcp_ports: config_known_tcp_ports.c $(LIB) $(LIBS) - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) - -tests: tok822_test mime_tests strip_addr_test tok822_limit_test \ +tests: update tok822_test mime_tests strip_addr_test tok822_limit_test \ xtext_test scache_multi_test test_ehlo_mask \ namadr_list_test mail_conf_time_test header_body_checks_tests \ server_acl_test resolve_local_test maps_test \ safe_ultostr_test mail_parm_split_test fold_addr_test \ - smtp_reply_footer_test off_cvt_test mail_addr_crunch_test \ + test_smtp_reply_footer off_cvt_test mail_addr_crunch_test \ mail_addr_find_test mail_addr_map_test quote_822_local_test \ - normalize_mailhost_addr_test test_haproxy_srvr map_search_test \ + test_normalize_mailhost_addr test_haproxy_srvr test_map_search \ delivered_hdr_test test_login_sender_match compat_level_test \ - config_known_tcp_ports_test hfrom_format_test rfc2047_code_test \ + test_config_known_tcp_ports test_hfrom_format rfc2047_code_test \ ascii_header_text_test test_sendopts test_dict_sqlite test_pol_stats \ test_allowed_prefix nbdb_tests @@ -555,7 +535,9 @@ server_acl_test: server_acl server_acl.in server_acl.ref rm -f server_acl.tmp resolve_local_test: resolve_local resolve_local.in resolve_local.ref - $(SHLIB_ENV) sh resolve_local.in >resolve_local.tmp 2>&1 + rm -f main.cf + touch -t 197601010000 main.cf + $(SHLIB_ENV) MAIL_CONFIG=. sh resolve_local.in >resolve_local.tmp 2>&1 diff resolve_local.ref resolve_local.tmp rm -f resolve_local.tmp @@ -731,10 +713,11 @@ fold_addr_test: fold_addr fold_addr_test.in fold_addr_test.ref diff fold_addr_test.ref fold_addr_test.tmp rm -f fold_addr_test.tmp -smtp_reply_footer_test: smtp_reply_footer smtp_reply_footer.ref - $(SHLIB_ENV) $(VALGRIND) ./smtp_reply_footer >smtp_reply_footer.tmp 2>&1 - diff smtp_reply_footer.ref smtp_reply_footer.tmp - rm -f smtp_reply_footer.tmp +smtp_reply_footer_test: smtp_reply_footer_test.o $(PTEST_LIB) $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS) + +test_smtp_reply_footer: smtp_reply_footer_test + $(SHLIB_ENV) $(VALGRIND) ./smtp_reply_footer_test off_cvt_test: off_cvt off_cvt.in off_cvt.ref $(SHLIB_ENV) $(VALGRIND) ./off_cvt off_cvt.tmp 2>&1 @@ -742,9 +725,11 @@ off_cvt_test: off_cvt off_cvt.in off_cvt.ref rm -f off_cvt.tmp mail_addr_crunch_test: update mail_addr_crunch mail_addr_crunch.in mail_addr_crunch.ref - -$(SHLIB_ENV) sh mail_addr_crunch.in >mail_addr_crunch.tmp 2>&1 + rm -f main.cf + touch -t 197601010000 main.cf + -$(SHLIB_ENV) MAIL_CONFIG=. sh mail_addr_crunch.in >mail_addr_crunch.tmp 2>&1 diff mail_addr_crunch.ref mail_addr_crunch.tmp - rm -f mail_addr_crunch.tmp + rm -f mail_addr_crunch.tmp main.cf mail_addr_find_test: update mail_addr_find mail_addr_find.in mail_addr_find.ref -$(SHLIB_ENV) $(VALGRIND) ./mail_addr_find mail_addr_find.tmp 2>&1 @@ -762,25 +747,37 @@ quote_822_local_test: update quote_822_local quote_822_local.in quote_822_local. diff quote_822_local.ref quote_822_local.tmp rm -f quote_822_local.tmp -normalize_mailhost_addr_test: update normalize_mailhost_addr - -$(SHLIB_ENV) $(VALGRIND) ./normalize_mailhost_addr >normalize_mailhost_addr.tmp 2>&1 - diff /dev/null normalize_mailhost_addr.tmp - rm -f normalize_mailhost_addr.tmp +normalize_mailhost_addr_test: normalize_mailhost_addr_test.o \ + $(PTEST_LIB) $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS) + +test_normalize_mailhost_addr: update normalize_mailhost_addr_test + $(SHLIB_ENV) $(VALGRIND) ./normalize_mailhost_addr_test + +haproxy_srvr_test: haproxy_srvr_test.o \ + $(PTEST_LIB) $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS) test_haproxy_srvr: update haproxy_srvr_test $(SHLIB_ENV) $(VALGRIND) ./haproxy_srvr_test -map_search_test: update map_search map_search.ref - -$(SHLIB_ENV) $(VALGRIND) ./map_search >map_search.tmp 2>&1 - diff map_search.ref map_search.tmp - rm -f map_search.tmp +map_search_test: map_search_test.o $(PTEST_LIB) $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS) + +test_map_search: update map_search_test + $(SHLIB_ENV) $(VALGRIND) ./map_search_test + +delivered_hdr_test: delivered_hdr_test.o $(PTEST_LIB) $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS) -delivered_hdr_test: update delivered_hdr delivered_hdr.ref - -$(SHLIB_ENV) $(VALGRIND) ./delivered_hdr >delivered_hdr.tmp 2>&1 - diff delivered_hdr.ref delivered_hdr.tmp - rm -f delivered_hdr.tmp +test_delivered_hdr: update delivered_hdr_test + $(SHLIB_ENV) $(VALGRIND) ./delivered_hdr_test -test_login_sender_match: update login_sender_match_test +login_sender_match_test: login_sender_match_test.o \ + $(PTEST_LIB) $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS) + +test_login_sender_match: update login_sender_match_test $(SHLIB_ENV) $(VALGRIND) ./login_sender_match_test compat_level_test: compat_level_expand_test compat_level_convert_test @@ -797,19 +794,17 @@ compat_level_convert_test: update compat_level compat_level_convert.in \ diff compat_level_convert.ref compat_level_convert.tmp rm -f compat_level_convert.tmp -config_known_tcp_ports_test: update config_known_tcp_ports \ - config_known_tcp_ports.ref - -$(SHLIB_ENV) $(VALGRIND) ./config_known_tcp_ports \ - >config_known_tcp_ports.tmp 2>&1 - diff config_known_tcp_ports.ref config_known_tcp_ports.tmp - rm -f config_known_tcp_ports.tmp +config_known_tcp_ports_test: config_known_tcp_ports_test.o $(PTEST_LIB) $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS) + +test_config_known_tcp_ports: update config_known_tcp_ports_test + $(SHLIB_ENV) $(VALGRIND) ./config_known_tcp_ports_test + +hfrom_format_test: hfrom_format_test.o $(PTEST_LIB) $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(PTEST_LIB) $(LIB) $(LIBS) $(SYSLIBS) -hfrom_format_test: update hfrom_format \ - hfrom_format.ref - -$(SHLIB_ENV) $(VALGRIND) ./hfrom_format \ - >hfrom_format.tmp 2>&1 - diff hfrom_format.ref hfrom_format.tmp - rm -f hfrom_format.tmp +test_hfrom_format: update hfrom_format_test + $(SHLIB_ENV) $(VALGRIND) ./hfrom_format_test rfc2047_code_test: update rfc2047_code $(SHLIB_ENV) $(VALGRIND) ./rfc2047_code @@ -1087,6 +1082,24 @@ config_known_tcp_ports.o: ../../include/vbuf.h config_known_tcp_ports.o: ../../include/vstring.h config_known_tcp_ports.o: config_known_tcp_ports.c config_known_tcp_ports.o: config_known_tcp_ports.h +config_known_tcp_ports_test.o: ../../include/argv.h +config_known_tcp_ports_test.o: ../../include/check_arg.h +config_known_tcp_ports_test.o: ../../include/known_tcp_ports.h +config_known_tcp_ports_test.o: ../../include/msg.h +config_known_tcp_ports_test.o: ../../include/msg_jmp.h +config_known_tcp_ports_test.o: ../../include/msg_output.h +config_known_tcp_ports_test.o: ../../include/msg_vstream.h +config_known_tcp_ports_test.o: ../../include/myrand.h +config_known_tcp_ports_test.o: ../../include/pmock_expect.h +config_known_tcp_ports_test.o: ../../include/ptest.h +config_known_tcp_ports_test.o: ../../include/ptest_main.h +config_known_tcp_ports_test.o: ../../include/stringops.h +config_known_tcp_ports_test.o: ../../include/sys_defs.h +config_known_tcp_ports_test.o: ../../include/vbuf.h +config_known_tcp_ports_test.o: ../../include/vstream.h +config_known_tcp_ports_test.o: ../../include/vstring.h +config_known_tcp_ports_test.o: config_known_tcp_ports.h +config_known_tcp_ports_test.o: config_known_tcp_ports_test.c conv_time.o: ../../include/msg.h conv_time.o: ../../include/sys_defs.h conv_time.o: conv_time.c @@ -1264,6 +1277,28 @@ delivered_hdr.o: quote_822_local.h delivered_hdr.o: quote_flags.h delivered_hdr.o: rec_type.h delivered_hdr.o: record.h +delivered_hdr_test.o: ../../include/argv.h +delivered_hdr_test.o: ../../include/check_arg.h +delivered_hdr_test.o: ../../include/msg.h +delivered_hdr_test.o: ../../include/msg_jmp.h +delivered_hdr_test.o: ../../include/msg_output.h +delivered_hdr_test.o: ../../include/msg_vstream.h +delivered_hdr_test.o: ../../include/mymalloc.h +delivered_hdr_test.o: ../../include/myrand.h +delivered_hdr_test.o: ../../include/pmock_expect.h +delivered_hdr_test.o: ../../include/ptest.h +delivered_hdr_test.o: ../../include/ptest_main.h +delivered_hdr_test.o: ../../include/stringops.h +delivered_hdr_test.o: ../../include/sys_defs.h +delivered_hdr_test.o: ../../include/vbuf.h +delivered_hdr_test.o: ../../include/vstream.h +delivered_hdr_test.o: ../../include/vstring.h +delivered_hdr_test.o: delivered_hdr.h +delivered_hdr_test.o: delivered_hdr_test.c +delivered_hdr_test.o: fold_addr.h +delivered_hdr_test.o: mail_params.h +delivered_hdr_test.o: rec_type.h +delivered_hdr_test.o: record.h dict_ldap.o: ../../include/argv.h dict_ldap.o: ../../include/binhash.h dict_ldap.o: ../../include/check_arg.h @@ -1327,7 +1362,7 @@ dict_mysql.o: ../../include/argv.h dict_mysql.o: ../../include/check_arg.h dict_mysql.o: ../../include/dict.h dict_mysql.o: ../../include/events.h -dict_mysql.o: ../../include/find_inet.h +dict_mysql.o: ../../include/find_inet_service.h dict_mysql.o: ../../include/match_list.h dict_mysql.o: ../../include/msg.h dict_mysql.o: ../../include/myflock.h @@ -1598,10 +1633,17 @@ haproxy_srvr.o: ../../include/vbuf.h haproxy_srvr.o: ../../include/vstring.h haproxy_srvr.o: haproxy_srvr.c haproxy_srvr.o: haproxy_srvr.h +haproxy_srvr_test.o: ../../include/argv.h haproxy_srvr_test.o: ../../include/check_arg.h haproxy_srvr_test.o: ../../include/msg.h +haproxy_srvr_test.o: ../../include/msg_jmp.h +haproxy_srvr_test.o: ../../include/msg_output.h haproxy_srvr_test.o: ../../include/msg_vstream.h haproxy_srvr_test.o: ../../include/myaddrinfo.h +haproxy_srvr_test.o: ../../include/myrand.h +haproxy_srvr_test.o: ../../include/pmock_expect.h +haproxy_srvr_test.o: ../../include/ptest.h +haproxy_srvr_test.o: ../../include/ptest_main.h haproxy_srvr_test.o: ../../include/sock_addr.h haproxy_srvr_test.o: ../../include/stringops.h haproxy_srvr_test.o: ../../include/sys_defs.h @@ -1655,6 +1697,24 @@ hfrom_format.o: ../../include/sys_defs.h hfrom_format.o: hfrom_format.c hfrom_format.o: hfrom_format.h hfrom_format.o: mail_params.h +hfrom_format_test.o: ../../include/argv.h +hfrom_format_test.o: ../../include/check_arg.h +hfrom_format_test.o: ../../include/msg.h +hfrom_format_test.o: ../../include/msg_jmp.h +hfrom_format_test.o: ../../include/msg_output.h +hfrom_format_test.o: ../../include/msg_vstream.h +hfrom_format_test.o: ../../include/myrand.h +hfrom_format_test.o: ../../include/pmock_expect.h +hfrom_format_test.o: ../../include/ptest.h +hfrom_format_test.o: ../../include/ptest_main.h +hfrom_format_test.o: ../../include/stringops.h +hfrom_format_test.o: ../../include/sys_defs.h +hfrom_format_test.o: ../../include/vbuf.h +hfrom_format_test.o: ../../include/vstream.h +hfrom_format_test.o: ../../include/vstring.h +hfrom_format_test.o: hfrom_format.h +hfrom_format_test.o: hfrom_format_test.c +hfrom_format_test.o: mail_params.h info_log_addr_form.o: ../../include/check_arg.h info_log_addr_form.o: ../../include/msg.h info_log_addr_form.o: ../../include/name_code.h @@ -1739,8 +1799,14 @@ login_sender_match_test.o: ../../include/argv.h login_sender_match_test.o: ../../include/check_arg.h login_sender_match_test.o: ../../include/dict.h login_sender_match_test.o: ../../include/msg.h +login_sender_match_test.o: ../../include/msg_jmp.h +login_sender_match_test.o: ../../include/msg_output.h login_sender_match_test.o: ../../include/msg_vstream.h login_sender_match_test.o: ../../include/myflock.h +login_sender_match_test.o: ../../include/myrand.h +login_sender_match_test.o: ../../include/pmock_expect.h +login_sender_match_test.o: ../../include/ptest.h +login_sender_match_test.o: ../../include/ptest_main.h login_sender_match_test.o: ../../include/stringops.h login_sender_match_test.o: ../../include/sys_defs.h login_sender_match_test.o: ../../include/vbuf.h @@ -2236,6 +2302,24 @@ map_search.o: ../../include/vbuf.h map_search.o: ../../include/vstring.h map_search.o: map_search.c map_search.o: map_search.h +map_search_test.o: ../../include/argv.h +map_search_test.o: ../../include/check_arg.h +map_search_test.o: ../../include/msg.h +map_search_test.o: ../../include/msg_jmp.h +map_search_test.o: ../../include/msg_output.h +map_search_test.o: ../../include/msg_vstream.h +map_search_test.o: ../../include/myrand.h +map_search_test.o: ../../include/name_code.h +map_search_test.o: ../../include/pmock_expect.h +map_search_test.o: ../../include/ptest.h +map_search_test.o: ../../include/ptest_main.h +map_search_test.o: ../../include/stringops.h +map_search_test.o: ../../include/sys_defs.h +map_search_test.o: ../../include/vbuf.h +map_search_test.o: ../../include/vstream.h +map_search_test.o: ../../include/vstring.h +map_search_test.o: map_search.h +map_search_test.o: map_search_test.c maps.o: ../../include/argv.h maps.o: ../../include/check_arg.h maps.o: ../../include/dict.h @@ -2570,6 +2654,25 @@ normalize_mailhost_addr.o: ../../include/vstring.h normalize_mailhost_addr.o: normalize_mailhost_addr.c normalize_mailhost_addr.o: normalize_mailhost_addr.h normalize_mailhost_addr.o: valid_mailhost_addr.h +normalize_mailhost_addr_test.o: ../../include/argv.h +normalize_mailhost_addr_test.o: ../../include/check_arg.h +normalize_mailhost_addr_test.o: ../../include/inet_proto.h +normalize_mailhost_addr_test.o: ../../include/msg.h +normalize_mailhost_addr_test.o: ../../include/msg_jmp.h +normalize_mailhost_addr_test.o: ../../include/msg_output.h +normalize_mailhost_addr_test.o: ../../include/msg_vstream.h +normalize_mailhost_addr_test.o: ../../include/mymalloc.h +normalize_mailhost_addr_test.o: ../../include/myrand.h +normalize_mailhost_addr_test.o: ../../include/pmock_expect.h +normalize_mailhost_addr_test.o: ../../include/ptest.h +normalize_mailhost_addr_test.o: ../../include/ptest_main.h +normalize_mailhost_addr_test.o: ../../include/stringops.h +normalize_mailhost_addr_test.o: ../../include/sys_defs.h +normalize_mailhost_addr_test.o: ../../include/vbuf.h +normalize_mailhost_addr_test.o: ../../include/vstream.h +normalize_mailhost_addr_test.o: ../../include/vstring.h +normalize_mailhost_addr_test.o: normalize_mailhost_addr.h +normalize_mailhost_addr_test.o: normalize_mailhost_addr_test.c off_cvt.o: ../../include/check_arg.h off_cvt.o: ../../include/msg.h off_cvt.o: ../../include/sys_defs.h @@ -3013,6 +3116,26 @@ smtp_reply_footer.o: ../../include/vstring.h smtp_reply_footer.o: dsn_util.h smtp_reply_footer.o: smtp_reply_footer.c smtp_reply_footer.o: smtp_reply_footer.h +smtp_reply_footer_test.o: ../../include/argv.h +smtp_reply_footer_test.o: ../../include/check_arg.h +smtp_reply_footer_test.o: ../../include/mac_expand.h +smtp_reply_footer_test.o: ../../include/mac_parse.h +smtp_reply_footer_test.o: ../../include/msg.h +smtp_reply_footer_test.o: ../../include/msg_jmp.h +smtp_reply_footer_test.o: ../../include/msg_output.h +smtp_reply_footer_test.o: ../../include/msg_vstream.h +smtp_reply_footer_test.o: ../../include/myrand.h +smtp_reply_footer_test.o: ../../include/pmock_expect.h +smtp_reply_footer_test.o: ../../include/ptest.h +smtp_reply_footer_test.o: ../../include/ptest_main.h +smtp_reply_footer_test.o: ../../include/stringops.h +smtp_reply_footer_test.o: ../../include/sys_defs.h +smtp_reply_footer_test.o: ../../include/vbuf.h +smtp_reply_footer_test.o: ../../include/vstream.h +smtp_reply_footer_test.o: ../../include/vstring.h +smtp_reply_footer_test.o: dsn_util.h +smtp_reply_footer_test.o: smtp_reply_footer.h +smtp_reply_footer_test.o: smtp_reply_footer_test.c smtp_stream.o: ../../include/check_arg.h smtp_stream.o: ../../include/iostuff.h smtp_stream.o: ../../include/msg.h @@ -3080,25 +3203,25 @@ sys_exits.o: ../../include/vbuf.h sys_exits.o: ../../include/vstring.h sys_exits.o: sys_exits.c sys_exits.o: sys_exits.h -test_main.o: ../../include/argv.h -test_main.o: ../../include/check_arg.h -test_main.o: ../../include/dict.h -test_main.o: ../../include/msg.h -test_main.o: ../../include/msg_vstream.h -test_main.o: ../../include/myflock.h -test_main.o: ../../include/mymalloc.h -test_main.o: ../../include/stringops.h -test_main.o: ../../include/sys_defs.h -test_main.o: ../../include/vbuf.h -test_main.o: ../../include/vstream.h -test_main.o: ../../include/vstring.h -test_main.o: mail_conf.h -test_main.o: mail_dict.h -test_main.o: mail_params.h -test_main.o: mail_task.h -test_main.o: mail_version.h -test_main.o: test_main.c -test_main.o: test_main.h +test_server_main.o: ../../include/argv.h +test_server_main.o: ../../include/check_arg.h +test_server_main.o: ../../include/dict.h +test_server_main.o: ../../include/msg.h +test_server_main.o: ../../include/msg_vstream.h +test_server_main.o: ../../include/myflock.h +test_server_main.o: ../../include/mymalloc.h +test_server_main.o: ../../include/stringops.h +test_server_main.o: ../../include/sys_defs.h +test_server_main.o: ../../include/vbuf.h +test_server_main.o: ../../include/vstream.h +test_server_main.o: ../../include/vstring.h +test_server_main.o: mail_conf.h +test_server_main.o: mail_dict.h +test_server_main.o: mail_params.h +test_server_main.o: mail_task.h +test_server_main.o: mail_version.h +test_server_main.o: test_server_main.c +test_server_main.o: test_server_main.h timed_ipc.o: ../../include/check_arg.h timed_ipc.o: ../../include/msg.h timed_ipc.o: ../../include/sys_defs.h diff --git a/postfix/src/global/config_known_tcp_ports.c b/postfix/src/global/config_known_tcp_ports.c index db61f4aae..15322a264 100644 --- a/postfix/src/global/config_known_tcp_ports.c +++ b/postfix/src/global/config_known_tcp_ports.c @@ -123,135 +123,3 @@ void config_known_tcp_ports(const char *source, const char *settings) } argv_free(associations); } - -#ifdef TEST - -#include -#include -#include - -#define STR(x) vstring_str(x) - - /* TODO(wietse) make this a proper VSTREAM interface */ - -/* vstream_swap - kludge to capture output for testing */ - -static void vstream_swap(VSTREAM *one, VSTREAM *two) -{ - VSTREAM save; - - save = *one; - *one = *two; - *two = save; -} - -struct test_case { - const char *label; /* identifies test case */ - const char *config; /* configuration under test */ - const char *exp_warning; /* expected warning or null */ - const char *exp_export; /* expected export or null */ -}; - -static struct test_case test_cases[] = { - {"good", - /* config */ "smtp = 25, smtps = submissions = 465, lmtp = 24", - /* warning */ "", - /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465" - }, - {"equal-equal", - /* config */ "smtp = 25, smtps == submissions = 465, lmtp = 24", - /* warning */ "config_known_tcp_ports: warning: equal-equal: " - "in \" smtps == submissions = 465\": missing service name before " - "\"=\"\n", - /* export */ "lmtp=24 smtp=25 smtps=465 submissions=465" - }, - {"port test 1", - /* config */ "smtps = submission =", - /* warning */ "config_known_tcp_ports: warning: port test 1: " - "in \"smtps = submission =\": missing port value after \"=\"\n", - /* export */ "" - }, - {"port test 2", - /* config */ "smtps = submission = 4 65", - /* warning */ "config_known_tcp_ports: warning: port test 2: " - "in \"smtps = submission = 4 65\": whitespace in port number\n", - /* export */ "" - }, - {"port test 3", - /* config */ "lmtp = 24, smtps = submission = foo", - /* warning */ "config_known_tcp_ports: warning: port test 3: " - "in \" smtps = submission = foo\": non-numerical service port\n", - /* export */ "lmtp=24" - }, - {"service name test 1", - /* config */ "smtps = sub mission = 465", - /* warning */ "config_known_tcp_ports: warning: service name test 1: " - "in \"smtps = sub mission = 465\": whitespace in service name\n", - /* export */ "smtps=465" - }, - {"service name test 2", - /* config */ "lmtp = 24, smtps = 1234 = submissions = 465", - /* warning */ "config_known_tcp_ports: warning: service name test 2: " - "in \" smtps = 1234 = submissions = 465\": numerical service name\n", - /* export */ "lmtp=24 smtps=465 submissions=465" - }, - 0, -}; - -int main(int argc, char **argv) -{ - VSTRING *export_buf; - struct test_case *tp; - int pass = 0; - int fail = 0; - int test_failed; - const char *export; - VSTRING *msg_buf; - VSTREAM *memory_stream; - -#define STRING_OR_NULL(s) ((s) ? (s) : "(null)") - - msg_vstream_init("config_known_tcp_ports", VSTREAM_ERR); - - export_buf = vstring_alloc(100); - msg_buf = vstring_alloc(100); - for (tp = test_cases; tp->label != 0; tp++) { - test_failed = 0; - if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0) - msg_fatal("open memory stream: %m"); - vstream_swap(VSTREAM_ERR, memory_stream); - config_known_tcp_ports(tp->label, tp->config); - vstream_swap(memory_stream, VSTREAM_ERR); - if (vstream_fclose(memory_stream)) - msg_fatal("close memory stream: %m"); - if (strcmp(STR(msg_buf), tp->exp_warning) != 0) { - msg_warn("test case %s: got error: \"%s\", want: \"%s\"", - tp->label, STR(msg_buf), - STRING_OR_NULL(tp->exp_warning)); - test_failed = 1; - } else { - export = export_known_tcp_ports(export_buf); - if (strcmp(export, tp->exp_export) != 0) { - msg_warn("test case %s: got export: \"%s\", want: \"%s\"", - tp->label, export, tp->exp_export); - test_failed = 1; - } - clear_known_tcp_ports(); - VSTRING_RESET(msg_buf); - VSTRING_TERMINATE(msg_buf); - } - if (test_failed) { - msg_info("%s: FAIL", tp->label); - fail++; - } else { - msg_info("%s: PASS", tp->label); - pass++; - } - } - msg_info("PASS=%d FAIL=%d", pass, fail); - vstring_free(msg_buf); - vstring_free(export_buf); - exit(fail != 0); -} - -#endif diff --git a/postfix/src/global/config_known_tcp_ports_test.c b/postfix/src/global/config_known_tcp_ports_test.c new file mode 100644 index 000000000..e1c331e67 --- /dev/null +++ b/postfix/src/global/config_known_tcp_ports_test.c @@ -0,0 +1,97 @@ + /* + * Test program to exercise config_known_tcp_ports.c. See ptest_main.h for a + * documented example. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * Test library. + */ +#include + +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 diff --git a/postfix/src/global/delivered_hdr.c b/postfix/src/global/delivered_hdr.c index 0aea1cc1d..1b20b3856 100644 --- a/postfix/src/global/delivered_hdr.c +++ b/postfix/src/global/delivered_hdr.c @@ -198,69 +198,3 @@ void delivered_hdr_free(DELIVERED_HDR_INFO *info) htable_free(info->table, (void (*) (void *)) 0); myfree((void *) info); } - -#ifdef TEST - -#include -#include - -char *var_drop_hdrs; - -int main(int arc, char **argv) -{ - - /* - * We write test records to a VSTRING, then read with delivered_hdr_init. - */ - VSTRING *mem_bp; - VSTREAM *mem_fp; - DELIVERED_HDR_INFO *dp; - struct test_case { - int rec_type; - const char *content; - int expect_find; - }; - const struct test_case test_cases[] = { - REC_TYPE_CONT, "Delivered-To: one", 1, - REC_TYPE_NORM, "Delivered-To: two", 0, - REC_TYPE_NORM, "Delivered-To: three", 1, - 0, - }; - const struct test_case *tp; - int actual_find; - int errors; - - msg_vstream_init(argv[0], VSTREAM_ERR); - - var_drop_hdrs = mystrdup(DEF_DROP_HDRS); - - mem_bp = vstring_alloc(VSTREAM_BUFSIZE); - if ((mem_fp = vstream_memopen(mem_bp, O_WRONLY)) == 0) - msg_panic("vstream_memopen O_WRONLY failed: %m"); - -#define REC_PUT_LIT(fp, type, lit) rec_put((fp), (type), (lit), strlen(lit)) - - for (tp = test_cases; tp->content != 0; tp++) - REC_PUT_LIT(mem_fp, tp->rec_type, tp->content); - - if (vstream_fclose(mem_fp)) - msg_panic("vstream_fclose fail: %m"); - - if ((mem_fp = vstream_memopen(mem_bp, O_RDONLY)) == 0) - msg_panic("vstream_memopen O_RDONLY failed: %m"); - - dp = delivered_hdr_init(mem_fp, 0, FOLD_ADDR_ALL); - - for (errors = 0, tp = test_cases; tp->content != 0; tp++) { - actual_find = - delivered_hdr_find(dp, tp->content + sizeof("Delivered-To:")); - msg_info("test case: %c >%s<: %s (expected: %s)", - tp->rec_type, tp->content, - actual_find == tp->expect_find ? "PASS" : "FAIL", - tp->expect_find ? "MATCH" : "NO MATCH"); - errors += (actual_find != tp->expect_find);; - } - exit(errors); -} - -#endif diff --git a/postfix/src/global/delivered_hdr_test.c b/postfix/src/global/delivered_hdr_test.c new file mode 100644 index 000000000..d47086710 --- /dev/null +++ b/postfix/src/global/delivered_hdr_test.c @@ -0,0 +1,117 @@ + /* + * Test program to exercise delivered_hdr.c. See ptest_main.h for a + * documented example. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include + + /* + * Test library. + */ +#include + + /* + * 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 diff --git a/postfix/src/global/dict_mysql.c b/postfix/src/global/dict_mysql.c index 89ec3c1ff..c0affafec 100644 --- a/postfix/src/global/dict_mysql.c +++ b/postfix/src/global/dict_mysql.c @@ -99,7 +99,7 @@ #include "argv.h" #include "vstring.h" #include "split_at.h" -#include "find_inet.h" +#include "find_inet_service.h" #include "myrand.h" #include "events.h" #include "stringops.h" @@ -850,7 +850,9 @@ static HOST *host_init(const char *hostname) } host->name = mystrdup(d); if ((s = split_at_right(host->name, ':')) != 0) - host->port = ntohs(find_inet_port(s, "tcp")); + if ((host->port = find_inet_service(s, "tcp")) < 0) + /* TODO: return null and create a surrogate dictionary. */ + msg_fatal("unknown service: %s/tcp", s); if (strcasecmp(host->name, "localhost") == 0) { /* The MySQL way: this will actually connect over the UNIX socket */ myfree(host->name); diff --git a/postfix/src/global/fold_addr.h b/postfix/src/global/fold_addr.h index ba8021df2..67008154b 100644 --- a/postfix/src/global/fold_addr.h +++ b/postfix/src/global/fold_addr.h @@ -11,6 +11,11 @@ /* DESCRIPTION /* .nf + /* + * Utility library. + */ +#include + /* * External interface. */ diff --git a/postfix/src/global/haproxy_srvr.h b/postfix/src/global/haproxy_srvr.h index 5536c6bb1..9467b058e 100644 --- a/postfix/src/global/haproxy_srvr.h +++ b/postfix/src/global/haproxy_srvr.h @@ -133,6 +133,79 @@ struct proxy_hdr_v2 { #endif /* _HAPROXY_SRVR_INTERNAL_ */ + /* + * Binary V2 protocol structure, also needed to create test data. + */ +#ifdef HAPROXY_SRVR_INTERNAL + + /* + * Begin protocol v2 definitions from haproxy/include/types/connection.h. + */ +#define PP2_SIGNATURE "\r\n\r\n\0\r\nQUIT\n" +#define PP2_SIGNATURE_LEN 12 +#define PP2_HEADER_LEN 16 + +/* ver_cmd byte */ +#define PP2_CMD_LOCAL 0x00 +#define PP2_CMD_PROXY 0x01 +#define PP2_CMD_MASK 0x0F + +#define PP2_VERSION 0x20 +#define PP2_VERSION_MASK 0xF0 + +/* fam byte */ +#define PP2_TRANS_UNSPEC 0x00 +#define PP2_TRANS_STREAM 0x01 +#define PP2_TRANS_DGRAM 0x02 +#define PP2_TRANS_MASK 0x0F + +#define PP2_FAM_UNSPEC 0x00 +#define PP2_FAM_INET 0x10 +#define PP2_FAM_INET6 0x20 +#define PP2_FAM_UNIX 0x30 +#define PP2_FAM_MASK 0xF0 + +/* len field (2 bytes) */ +#define PP2_ADDR_LEN_UNSPEC (0) +#define PP2_ADDR_LEN_INET (4 + 4 + 2 + 2) +#define PP2_ADDR_LEN_INET6 (16 + 16 + 2 + 2) +#define PP2_ADDR_LEN_UNIX (108 + 108) + +#define PP2_HDR_LEN_UNSPEC (PP2_HEADER_LEN + PP2_ADDR_LEN_UNSPEC) +#define PP2_HDR_LEN_INET (PP2_HEADER_LEN + PP2_ADDR_LEN_INET) +#define PP2_HDR_LEN_INET6 (PP2_HEADER_LEN + PP2_ADDR_LEN_INET6) +#define PP2_HDR_LEN_UNIX (PP2_HEADER_LEN + PP2_ADDR_LEN_UNIX) + +struct proxy_hdr_v2 { + uint8_t sig[PP2_SIGNATURE_LEN]; /* PP2_SIGNATURE */ + uint8_t ver_cmd; /* protocol version | command */ + uint8_t fam; /* protocol family and transport */ + uint16_t len; /* length of remainder */ + union { + struct { /* for TCP/UDP over IPv4, len = 12 */ + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ip4; + struct { /* for TCP/UDP over IPv6, len = 36 */ + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; + } ip6; + struct { /* for AF_UNIX sockets, len = 216 */ + uint8_t src_addr[108]; + uint8_t dst_addr[108]; + } unx; + } addr; +}; + + /* + * End protocol v2 definitions from haproxy/include/types/connection.h. + */ +#endif /* HAPROXY_SRVR_INTERNAL */ + /* LICENSE /* .ad /* .fi diff --git a/postfix/src/global/haproxy_srvr_test.c b/postfix/src/global/haproxy_srvr_test.c index 8dcdd8b58..7f959fdaa 100644 --- a/postfix/src/global/haproxy_srvr_test.c +++ b/postfix/src/global/haproxy_srvr_test.c @@ -1,248 +1,252 @@ + /* + * Test program to exercise haproxy_srvr.c. See ptest_main.h for a + * documented example. + */ + /* * System library. */ #include -#include +#include /* * Utility library. */ #include -#include -#include #include -#include #include /* * Global library. */ -#define _HAPROXY_SRVR_INTERNAL_ +#define HAPROXY_SRVR_INTERNAL #include /* - * Application-specific. + * Test library. */ -#define STR_OR_NULL(str) ((str) ? (str) : "(null)") +#include /* * Test cases with inputs and expected outputs. A request may contain * trailing garbage, and it may be too short. A v1 request may also contain - * malformed address or port information. TODO(wietse) add unit test with - * different inet_protocols setting. See normalize_v4mapped_addr_test.c. + * malformed address or port information. */ -typedef struct TEST_CASE { +typedef struct BASE_TEST_CASE { const char *haproxy_request; /* v1 or v2 request including thrash */ ssize_t haproxy_req_len; /* request length including thrash */ - ssize_t exp_req_len; /* parsed request length */ - int exp_non_proxy; /* request is not proxied */ - const char *exp_return; /* expected error string */ - const char *exp_client_addr; /* expected client address string */ - const char *exp_server_addr; /* expected client port string */ - const char *exp_client_port; /* expected client address string */ - const char *exp_server_port; /* expected server port string */ -} TEST_CASE; - -static TEST_CASE v1_test_cases[] = { + ssize_t want_req_len; /* parsed request length */ + int want_non_proxy; /* request is not proxied */ + const char *want_return; /* expected error string */ + const char *want_client_addr; /* expected client address string */ + const char *want_server_addr; /* expected client port string */ + const char *want_client_port; /* expected client address string */ + const char *want_server_port; /* expected server port string */ +} BASE_TEST_CASE; + + /* + * Initialize the haproxy_request, haproxy_req_len, and want_req_len fields. + */ +#define STRING_AND_LENS(s) (s), (sizeof(s) - 1), (sizeof(s) - 1) + + /* + * This table contains V1 protocol test cases that may also be converted + * into V2 protocol test cases. + */ +static BASE_TEST_CASE v1_test_cases[] = { /* IPv6. */ - {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"}, - {"PROXY TCP6 FC:00:00:00:1:2:3:4 FC:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"}, - {"PROXY TCP6 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"}, - {"PROXY TCP6 fc:00:00:00:1:2:3:4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"}, + {STRING_AND_LENS("PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123 321\n"), 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"}, + {STRING_AND_LENS("PROXY TCP6 FC:00:00:00:1:2:3:4 FC:00:00:00:4:3:2:1 123 321\n"), 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"}, + {STRING_AND_LENS("PROXY TCP6 1.2.3.4 4.3.2.1 123 321\n"), 0, "bad or missing client address"}, + {STRING_AND_LENS("PROXY TCP6 fc:00:00:00:1:2:3:4 4.3.2.1 123 321\n"), 0, "bad or missing server address"}, /* IPv4 in IPv6. */ - {"PROXY TCP6 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"}, - {"PROXY TCP6 ::FFFF:1.2.3.4 ::FFFF:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"}, - {"PROXY TCP4 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"}, - {"PROXY TCP4 1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"}, + {STRING_AND_LENS("PROXY TCP6 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n"), 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"}, + {STRING_AND_LENS("PROXY TCP6 ::FFFF:1.2.3.4 ::FFFF:4.3.2.1 123 321\n"), 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"}, + {STRING_AND_LENS("PROXY TCP4 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n"), 0, "bad or missing client address"}, + {STRING_AND_LENS("PROXY TCP4 1.2.3.4 ::ffff:4.3.2.1 123 321\n"), 0, "bad or missing server address"}, /* IPv4. */ - {"PROXY TCP4 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"}, - {"PROXY TCP4 01.02.03.04 04.03.02.01 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"}, - {"PROXY TCP4 1.2.3.4 4.3.2.1 123456 321\n", 0, 0, 0, "bad or missing client port"}, - {"PROXY TCP4 1.2.3.4 4.3.2.1 123 654321\n", 0, 0, 0, "bad or missing server port"}, - {"PROXY TCP4 1.2.3.4 4.3.2.1 0123 321\n", 0, 0, 0, "bad or missing client port"}, - {"PROXY TCP4 1.2.3.4 4.3.2.1 123 0321\n", 0, 0, 0, "bad or missing server port"}, + {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 123 321\n"), 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"}, + {STRING_AND_LENS("PROXY TCP4 01.02.03.04 04.03.02.01 123 321\n"), 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"}, + {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 123456 321\n"), 0, "bad or missing client port"}, + {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 123 654321\n"), 0, "bad or missing server port"}, + {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 0123 321\n"), 0, "bad or missing client port"}, + {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 123 0321\n"), 0, "bad or missing server port"}, /* Missing fields. */ - {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123\n", 0, 0, 0, "bad or missing server port"}, - {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1\n", 0, 0, 0, "bad or missing client port"}, - {"PROXY TCP6 fc:00:00:00:1:2:3:4\n", 0, 0, 0, "bad or missing server address"}, - {"PROXY TCP6\n", 0, 0, 0, "bad or missing client address"}, - {"PROXY TCP4 1.2.3.4 4.3.2.1 123\n", 0, 0, 0, "bad or missing server port"}, - {"PROXY TCP4 1.2.3.4 4.3.2.1\n", 0, 0, 0, "bad or missing client port"}, - {"PROXY TCP4 1.2.3.4\n", 0, 0, 0, "bad or missing server address"}, - {"PROXY TCP4\n", 0, 0, 0, "bad or missing client address"}, + {STRING_AND_LENS("PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123\n"), 0, "bad or missing server port"}, + {STRING_AND_LENS("PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1\n"), 0, "bad or missing client port"}, + {STRING_AND_LENS("PROXY TCP6 fc:00:00:00:1:2:3:4\n"), 0, "bad or missing server address"}, + {STRING_AND_LENS("PROXY TCP6\n"), 0, "bad or missing client address"}, + {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1 123\n"), 0, "bad or missing server port"}, + {STRING_AND_LENS("PROXY TCP4 1.2.3.4 4.3.2.1\n"), 0, "bad or missing client port"}, + {STRING_AND_LENS("PROXY TCP4 1.2.3.4\n"), 0, "bad or missing server address"}, + {STRING_AND_LENS("PROXY TCP4\n"), 0, "bad or missing client address"}, /* Other. */ - {"PROXY BLAH\n", 0, 0, 0, "bad or missing protocol type"}, - {"PROXY\n", 0, 0, 0, "short protocol header"}, + {STRING_AND_LENS("PROXY BLAH\n"), 0, "bad or missing protocol type"}, + {STRING_AND_LENS("PROXY\n"), 0, "short protocol header"}, {"BLAH\n", 0, 0, 0, "short protocol header"}, {"\n", 0, 0, 0, "short protocol header"}, {"", 0, 0, 0, "short protocol header"}, 0, }; + /* + * Limited V2-only tests. XXX Should we add error cases? Several errors are + * already tested with mutations of V2 handshakes that were generated from + * V1 handshakes. + */ static struct proxy_hdr_v2 v2_local_request = { PP2_SIGNATURE, PP2_VERSION | PP2_CMD_LOCAL, }; -static TEST_CASE v2_non_proxy_test = { +static BASE_TEST_CASE v2_non_proxy_test = { (char *) &v2_local_request, PP2_HEADER_LEN, PP2_HEADER_LEN, 1, }; -#define STR(x) vstring_str(x) -#define LEN(x) VSTRING_LEN(x) +#define STR_OR_NULL(s) ((s) ? (s) : "(null)") +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) -#define DO_SOCKADDR_OUTPUT 1 -#define NO_SOCKADDR_OUTPUT 0 +#define DO_SOCKADDR_OUTPUT 1 +#define NO_SOCKADDR_OUTPUT 0 -static int evaluate_sockaddr(const char *which, struct sockaddr *sa, - SOCKADDR_SIZE sa_len, const char *want_addr, - const char *want_port) +static void evaluate_sockaddr(PTEST_CTX *t, const char *which, + struct sockaddr *sa, + SOCKADDR_SIZE sa_len, + const char *want_addr, + const char *want_port) { - MAI_HOSTADDR_STR act_addr; - MAI_SERVPORT_STR act_port; + MAI_HOSTADDR_STR got_addr; + MAI_SERVPORT_STR got_port; int err; - int failed = 0; - if ((err = sockaddr_to_hostaddr(sa, sa_len, &act_addr, &act_port, 0)) != 0) { - msg_warn("sockaddr_to_hostaddr: %s", MAI_STRERROR(err)); - return (1); + if ((err = sockaddr_to_hostaddr(sa, sa_len, &got_addr, &got_port, 0)) != 0) { + ptest_error(t, "sockaddr_to_hostaddr: %s", MAI_STRERROR(err)); + return; } - if (strcmp(act_addr.buf, want_addr) != 0) { - msg_warn("got %s address '%s', want '%s'", - which, act_addr.buf, want_addr); - failed = 1; + if (strcmp(got_addr.buf, want_addr) != 0) { + ptest_error(t, "%s address got='%s', want='%s'", + which, got_addr.buf, want_addr); } - if (strcmp(act_port.buf, want_port) != 0) { - msg_warn("got %s port '%s', want '%s'", - which, act_port.buf, want_port); - failed = 1; + if (strcmp(got_port.buf, want_port) != 0) { + ptest_error(t, "%s port got='%s', want='%s'", + which, got_port.buf, want_port); } - return (failed); } -/* evaluate_test_case - evaluate one base or mutated test case */ +/* evaluate_test_case - evaluate one test case (base or mutation) */ -static int evaluate_test_case(const char *test_label, - const TEST_CASE *test_case, - int want_sockaddr_output) +static void evaluate_test_case(PTEST_CTX *t, const char *test_label, + const BASE_TEST_CASE *test_case, + int want_sockaddr_output) { - /* Actual results. */ - const char *act_return; - ssize_t act_req_len; - int act_non_proxy; - MAI_HOSTADDR_STR act_smtp_client_addr; - MAI_HOSTADDR_STR act_smtp_server_addr; - MAI_SERVPORT_STR act_smtp_client_port; - MAI_SERVPORT_STR act_smtp_server_port; - int test_failed; - - struct sockaddr_storage client_ss; - struct sockaddr *client_sa; - SOCKADDR_SIZE client_sa_len; - - struct sockaddr_storage server_ss; - struct sockaddr *server_sa; - SOCKADDR_SIZE server_sa_len; - - if (msg_verbose) - msg_info("test case=%s exp_client_addr=%s exp_server_addr=%s " - "exp_client_port=%s exp_server_port=%s", - test_label, STR_OR_NULL(test_case->exp_client_addr), - STR_OR_NULL(test_case->exp_server_addr), - STR_OR_NULL(test_case->exp_client_port), - STR_OR_NULL(test_case->exp_server_port)); - - if (want_sockaddr_output) { - client_sa = (struct sockaddr *) &client_ss; - client_sa_len = sizeof(client_ss); - server_sa = (struct sockaddr *) &server_ss; - server_sa_len = sizeof(server_ss); - } else { - client_sa = 0; - client_sa_len = 0; - server_sa = 0; - server_sa_len = 0; - } + PTEST_RUN(t, test_label, { + const char *got_return; + ssize_t got_req_len; + int got_non_proxy; + MAI_HOSTADDR_STR got_smtp_client_addr; + MAI_HOSTADDR_STR got_smtp_server_addr; + MAI_SERVPORT_STR got_smtp_client_port; + MAI_SERVPORT_STR got_smtp_server_port; + + struct sockaddr_storage client_ss; + struct sockaddr *client_sa; + SOCKADDR_SIZE client_sa_len; + + struct sockaddr_storage server_ss; + struct sockaddr *server_sa; + SOCKADDR_SIZE server_sa_len; + + if (want_sockaddr_output) { + client_sa = (struct sockaddr *) &client_ss; + client_sa_len = sizeof(client_ss); + server_sa = (struct sockaddr *) &server_ss; + server_sa_len = sizeof(server_ss); + } else { + client_sa = 0; + client_sa_len = 0; + server_sa = 0; + server_sa_len = 0; + } - /* - * Start the test. - */ - test_failed = 0; - act_req_len = test_case->haproxy_req_len; - act_return = - haproxy_srvr_parse_sa(test_case->haproxy_request, &act_req_len, - &act_non_proxy, - &act_smtp_client_addr, &act_smtp_client_port, - &act_smtp_server_addr, &act_smtp_server_port, - client_sa, &client_sa_len, - server_sa, &server_sa_len); - if (act_return != test_case->exp_return - && strcmp(STR_OR_NULL(act_return), STR_OR_NULL(test_case->exp_return))) { - msg_warn("test case %s return expected=>%s< actual=>%s<", - test_label, STR_OR_NULL(test_case->exp_return), - STR_OR_NULL(act_return)); - test_failed = 1; - return (test_failed); - } - if (act_req_len != test_case->exp_req_len) { - msg_warn("test case %s str_len expected=%ld actual=%ld", - test_label, - (long) test_case->exp_req_len, (long) act_req_len); - test_failed = 1; - return (test_failed); - } - if (act_non_proxy != test_case->exp_non_proxy) { - msg_warn("test case %s non_proxy expected=%d actual=%d", - test_label, - test_case->exp_non_proxy, act_non_proxy); - test_failed = 1; - return (test_failed); - } - if (test_case->exp_non_proxy || test_case->exp_return != 0) - /* No expected address/port results. */ - return (test_failed); + if (msg_verbose) + msg_info("test case=%s want_client_addr=%s want_server_addr=%s " + "want_client_port=%s want_server_port=%s", + test_label, STR_OR_NULL(test_case->want_client_addr), + STR_OR_NULL(test_case->want_server_addr), + STR_OR_NULL(test_case->want_client_port), + STR_OR_NULL(test_case->want_server_port)); + /* TODO(wietse) add client_sa/server_sa args */ - /* - * Compare address/port results against expected results. - */ - if (strcmp(test_case->exp_client_addr, act_smtp_client_addr.buf)) { - msg_warn("test case %s client_addr expected=%s actual=%s", - test_label, - test_case->exp_client_addr, act_smtp_client_addr.buf); - test_failed = 1; - } - if (strcmp(test_case->exp_server_addr, act_smtp_server_addr.buf)) { - msg_warn("test case %s server_addr expected=%s actual=%s", - test_label, - test_case->exp_server_addr, act_smtp_server_addr.buf); - test_failed = 1; - } - if (strcmp(test_case->exp_client_port, act_smtp_client_port.buf)) { - msg_warn("test case %s client_port expected=%s actual=%s", - test_label, - test_case->exp_client_port, act_smtp_client_port.buf); - test_failed = 1; - } - if (strcmp(test_case->exp_server_port, act_smtp_server_port.buf)) { - msg_warn("test case %s server_port expected=%s actual=%s", - test_label, - test_case->exp_server_port, act_smtp_server_port.buf); - test_failed = 1; - } - if (want_sockaddr_output) { - if (evaluate_sockaddr("client", client_sa, client_sa_len, - test_case->exp_client_addr, - test_case->exp_client_port) != 0 - || evaluate_sockaddr("server", server_sa, server_sa_len, - test_case->exp_server_addr, - test_case->exp_server_port) != 0) - test_failed = 1; - } - return (test_failed); + /* + * Start the test. + */ + got_req_len = test_case->haproxy_req_len; + got_return = + haproxy_srvr_parse_sa(test_case->haproxy_request, &got_req_len, + &got_non_proxy, + &got_smtp_client_addr, &got_smtp_client_port, + &got_smtp_server_addr, &got_smtp_server_port, + client_sa, &client_sa_len, + server_sa, &server_sa_len); + if (strcmp(STR_OR_NULL(got_return), STR_OR_NULL(test_case->want_return))) { + ptest_error(t, "haproxy_srvr_parse return got=%s want=%s", + STR_OR_NULL(got_return), + STR_OR_NULL(test_case->want_return)); + ptest_return(t); + } + if (got_req_len != test_case->want_req_len) { + ptest_error(t, "haproxy_srvr_parse str_len got=%ld want=%ld", + (long) got_req_len, + (long) test_case->want_req_len); + ptest_return(t); + } + if (got_non_proxy != test_case->want_non_proxy) { + ptest_error(t, "haproxy_srvr_parse non_proxy got=%d want=%d", + got_non_proxy, + test_case->want_non_proxy); + ptest_return(t); + } + if (test_case->want_non_proxy || test_case->want_return != 0) + /* No expected address/port results. */ + ptest_return(t); + + /* + * Compare address/port results against expected results. + */ + if (strcmp(test_case->want_client_addr, got_smtp_client_addr.buf)) { + ptest_error(t, "haproxy_srvr_parse client_addr got=%s want=%s", + got_smtp_client_addr.buf, + test_case->want_client_addr); + } + if (strcmp(test_case->want_server_addr, got_smtp_server_addr.buf)) { + ptest_error(t, "haproxy_srvr_parse server_addr got=%s want=%s", + got_smtp_server_addr.buf, + test_case->want_server_addr); + } + if (strcmp(test_case->want_client_port, got_smtp_client_port.buf)) { + ptest_error(t, "haproxy_srvr_parse client_port got=%s want=%s", + got_smtp_client_port.buf, + test_case->want_client_port); + } + if (strcmp(test_case->want_server_port, got_smtp_server_port.buf)) { + ptest_error(t, "haproxy_srvr_parse server_port got=%s want=%s", + got_smtp_server_port.buf, + test_case->want_server_port); + } + if (want_sockaddr_output) { + evaluate_sockaddr(t, "client", client_sa, client_sa_len, + test_case->want_client_addr, + test_case->want_client_port); + evaluate_sockaddr(t, "server", server_sa, server_sa_len, + test_case->want_server_addr, + test_case->want_server_port); + } + }); } /* convert_v1_proxy_req_to_v2 - convert well-formed v1 proxy request to v2 */ -static void convert_v1_proxy_req_to_v2(VSTRING *buf, const char *req, - ssize_t req_len) +static void convert_v1_proxy_req_to_v2(PTEST_CTX *t, VSTRING *buf, + const char *req, ssize_t req_len) { const char myname[] = "convert_v1_proxy_req_to_v2"; const char *err; @@ -271,19 +275,19 @@ static void convert_v1_proxy_req_to_v2(VSTRING *buf, const char *req, if ((err = haproxy_srvr_parse(req, &req_len, &non_proxy, &smtp_client_addr, &smtp_client_port, &smtp_server_addr, &smtp_server_port)) != 0 || non_proxy) - msg_fatal("%s: malformed or non-proxy request: %s", - myname, req); + ptest_fatal(t, "%s: malformed or non-proxy request: %s", + myname, req); if (hostaddr_to_sockaddr(smtp_client_addr.buf, smtp_client_port.buf, 0, &src_res) != 0) - msg_fatal("%s: unable to convert source address %s port %s", - myname, smtp_client_addr.buf, smtp_client_port.buf); + ptest_fatal(t, "%s: unable to convert source address %s port %s", + myname, smtp_client_addr.buf, smtp_client_port.buf); if (hostaddr_to_sockaddr(smtp_server_addr.buf, smtp_server_port.buf, 0, &dst_res) != 0) - msg_fatal("%s: unable to convert destination address %s port %s", - myname, smtp_server_addr.buf, smtp_server_port.buf); + ptest_fatal(t, "%s: unable to convert destination address %s port %s", + myname, smtp_server_addr.buf, smtp_server_port.buf); if (src_res->ai_family != dst_res->ai_family) - msg_fatal("%s: mixed source/destination address families", myname); + ptest_fatal(t, "%s: mixed source/destination address families", myname); #ifdef AF_INET6 if (src_res->ai_family == PF_INET6) { hdr_v2->fam = PP2_FAM_INET6 | PP2_TRANS_STREAM; @@ -306,229 +310,173 @@ static void convert_v1_proxy_req_to_v2(VSTRING *buf, const char *req, hdr_v2->addr.ip4.dst_addr = SOCK_ADDR_IN_ADDR(dst_res->ai_addr).s_addr; hdr_v2->addr.ip4.dst_port = SOCK_ADDR_IN_PORT(dst_res->ai_addr); } else { - msg_panic("unknown address family 0x%x", src_res->ai_family); + ptest_fatal(t, "unknown address family 0x%x", src_res->ai_family); } vstring_set_payload_size(buf, PP2_SIGNATURE_LEN + ntohs(hdr_v2->len)); freeaddrinfo(src_res); freeaddrinfo(dst_res); } -int main(int argc, char **argv) +static void run_test_variants(PTEST_CTX *t, BASE_TEST_CASE *v1_test_case) { VSTRING *test_label; - TEST_CASE *v1_test_case; - TEST_CASE v2_test_case; - TEST_CASE mutated_test_case; + BASE_TEST_CASE v2_test_case; + BASE_TEST_CASE mutated_test_case; VSTRING *v2_request_buf; VSTRING *mutated_request_buf; - msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR); - - /* Findings. */ - int tests_failed = 0; - int tests_passed = 0; - test_label = vstring_alloc(100); v2_request_buf = vstring_alloc(100); mutated_request_buf = vstring_alloc(100); /* - * Evaluate each case in the test case list. If the test input is - * well-formed, also test a number of mutations based on that test, - * before proceeding with the next test case in the list. + * Evaluate each v1 test case. */ - for (tests_failed = 0, tests_passed = 0, v1_test_case = v1_test_cases; - v1_test_case->haproxy_request != 0; v1_test_case++) { + vstring_sprintf(test_label, "v1 baseline (%sformed)", + v1_test_case->want_return ? "mal" : "well"); + evaluate_test_case(t, STR(test_label), v1_test_case, + NO_SOCKADDR_OUTPUT); - /* - * Fill in missing string length info in v1 test data. - */ - if (v1_test_case->haproxy_req_len == 0) - v1_test_case->haproxy_req_len = - strlen(v1_test_case->haproxy_request); - if (v1_test_case->exp_req_len == 0) - v1_test_case->exp_req_len = v1_test_case->haproxy_req_len; + /* + * If the v1 test input is malformed, skip the mutation tests. + */ + if (v1_test_case->want_return != 0) { + vstring_free(v2_request_buf); + vstring_free(mutated_request_buf); + vstring_free(test_label); + ptest_return(t); + } - /* - * Evaluate each v1 test case. - */ - vstring_sprintf(test_label, "%d (%s%.5s input)", - (int) (v1_test_case - v1_test_cases), - v1_test_case->exp_return ? "malformed" : "well-formed", - v1_test_case->exp_return ? "" : v1_test_case->haproxy_request + 5); - msg_info("RUN %s", STR(test_label)); - if (evaluate_test_case(STR(test_label), v1_test_case, - NO_SOCKADDR_OUTPUT)) { - msg_info("FAIL %s", STR(test_label)); - tests_failed += 1; - } else { - msg_info("PASS %s", STR(test_label)); - tests_passed += 1; - } + /* + * Mutation test: a well-formed v1 test case should also pass with output + * to sockaddr arguments. + */ + vstring_strcat(test_label, "(with sockaddr output)"); + evaluate_test_case(t, STR(test_label), v1_test_case, + DO_SOCKADDR_OUTPUT); - /* - * If the v1 test input is malformed, skip the mutation tests. - */ - if (v1_test_case->exp_return != 0) - continue; + /* + * Mutation test: a well-formed v1 test case should still pass after + * appending a byte, and should return the actual parsed header length. + * The test uses the implicit VSTRING null safety byte. + */ + vstring_sprintf(test_label, "v1 mutated (one byte appended)"); + mutated_test_case = *v1_test_case; + mutated_test_case.haproxy_req_len += 1; + /* reuse v1_test_case->want_req_len */ + evaluate_test_case(t, STR(test_label), &mutated_test_case, + NO_SOCKADDR_OUTPUT); - /* - * Mutation test: a well-formed v1 test case should also pass with - * output to sockaddr arguments. - */ - vstring_sprintf(test_label, "%d (with sockaddr output)", - (int) (v1_test_case - v1_test_cases)); - msg_info("RUN %s", STR(test_label)); - if (evaluate_test_case(STR(test_label), v1_test_case, - DO_SOCKADDR_OUTPUT)) { - msg_info("FAIL %s", STR(test_label)); - tests_failed += 1; - } else { - msg_info("PASS %s", STR(test_label)); - tests_passed += 1; - } + /* + * Mutation test: a well-formed v1 test case should fail after stripping + * the terminator. + */ + vstring_sprintf(test_label, "v1 mutated (last byte stripped)"); + mutated_test_case = *v1_test_case; + mutated_test_case.want_return = "missing protocol header terminator"; + mutated_test_case.haproxy_req_len -= 1; + mutated_test_case.want_req_len = mutated_test_case.haproxy_req_len; + evaluate_test_case(t, STR(test_label), &mutated_test_case, + NO_SOCKADDR_OUTPUT); - /* - * Mutation test: a well-formed v1 test case should still pass after - * appending a byte, and should return the actual parsed header - * length. The test uses the implicit VSTRING null safety byte. - */ - vstring_sprintf(test_label, "%d (one byte appended)", - (int) (v1_test_case - v1_test_cases)); - mutated_test_case = *v1_test_case; - mutated_test_case.haproxy_req_len += 1; - msg_info("RUN %s", STR(test_label)); - /* reuse v1_test_case->exp_req_len */ - if (evaluate_test_case(STR(test_label), &mutated_test_case, - NO_SOCKADDR_OUTPUT)) { - msg_info("FAIL %s", STR(test_label)); - tests_failed += 1; - } else { - msg_info("PASS %s", STR(test_label)); - tests_passed += 1; - } + /* + * A 'well-formed' v1 test case should pass after conversion to v2. + */ + vstring_sprintf(test_label, "v2 baseline (converted from v1)"); + v2_test_case = *v1_test_case; + convert_v1_proxy_req_to_v2(t, v2_request_buf, + v1_test_case->haproxy_request, + v1_test_case->haproxy_req_len); + v2_test_case.haproxy_request = STR(v2_request_buf); + v2_test_case.haproxy_req_len = PP2_HEADER_LEN + + ntohs(((struct proxy_hdr_v2 *) STR(v2_request_buf))->len); + v2_test_case.want_req_len = v2_test_case.haproxy_req_len; + evaluate_test_case(t, STR(test_label), &v2_test_case, + NO_SOCKADDR_OUTPUT); - /* - * Mutation test: a well-formed v1 test case should fail after - * stripping the terminator. - */ - vstring_sprintf(test_label, "%d (last byte stripped)", - (int) (v1_test_case - v1_test_cases)); - mutated_test_case = *v1_test_case; - mutated_test_case.exp_return = "missing protocol header terminator"; - mutated_test_case.haproxy_req_len -= 1; - mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len; - msg_info("RUN %s", STR(test_label)); - if (evaluate_test_case(STR(test_label), &mutated_test_case, - NO_SOCKADDR_OUTPUT)) { - msg_info("FAIL %s", STR(test_label)); - tests_failed += 1; - } else { - msg_info("PASS %s", STR(test_label)); - tests_passed += 1; - } + /* + * Mutation test: a well-formed v2 test case should also pass with output + * to sockaddr arguments. + */ + vstring_strcat(test_label, " (with sockaddr output)"); + evaluate_test_case(t, STR(test_label), &v2_test_case, + DO_SOCKADDR_OUTPUT); - /* - * A 'well-formed' v1 test case should pass after conversion to v2. - */ - vstring_sprintf(test_label, "%d (converted to v2)", - (int) (v1_test_case - v1_test_cases)); - v2_test_case = *v1_test_case; - convert_v1_proxy_req_to_v2(v2_request_buf, - v1_test_case->haproxy_request, - v1_test_case->haproxy_req_len); - v2_test_case.haproxy_request = STR(v2_request_buf); - v2_test_case.haproxy_req_len = PP2_HEADER_LEN - + ntohs(((struct proxy_hdr_v2 *) STR(v2_request_buf))->len); - v2_test_case.exp_req_len = v2_test_case.haproxy_req_len; - msg_info("RUN %s", STR(test_label)); - if (evaluate_test_case(STR(test_label), &v2_test_case, - NO_SOCKADDR_OUTPUT)) { - msg_info("FAIL %s", STR(test_label)); - tests_failed += 1; - } else { - msg_info("PASS %s", STR(test_label)); - tests_passed += 1; - } + /* + * Mutation test: a well-formed v2 test case should still pass after + * appending a byte, and should return the actual parsed header length. + * The test uses the implicit VSTRING null safety byte. + */ + vstring_sprintf(test_label, "v2 mutated (one byte appended)"); + mutated_test_case = v2_test_case; + mutated_test_case.haproxy_req_len += 1; + /* reuse v2_test_case->want_req_len */ + evaluate_test_case(t, STR(test_label), &mutated_test_case, + NO_SOCKADDR_OUTPUT); - /* - * Mutation test: a well-formed v2 test case should also pass with - * output to sockaddr arguments. - */ - vstring_sprintf(test_label, - "%d (converted to v2, with sockaddr output)", - (int) (v1_test_case - v1_test_cases)); - msg_info("RUN %s", STR(test_label)); - if (evaluate_test_case(STR(test_label), &v2_test_case, - DO_SOCKADDR_OUTPUT)) { - msg_info("FAIL %s", STR(test_label)); - tests_failed += 1; - } else { - msg_info("PASS %s", STR(test_label)); - tests_passed += 1; - } + /* + * Mutation test: a well-formed v2 test case should fail after stripping + * one byte + */ + vstring_sprintf(test_label, "v2 mutated (last byte stripped)"); + mutated_test_case = v2_test_case; + mutated_test_case.haproxy_req_len -= 1; + mutated_test_case.want_req_len = mutated_test_case.haproxy_req_len; + mutated_test_case.want_return = "short version 2 protocol header"; + evaluate_test_case(t, STR(test_label), &mutated_test_case, + NO_SOCKADDR_OUTPUT); - /* - * Mutation test: a well-formed v2 test case should still pass after - * appending a byte, and should return the actual parsed header - * length. The test uses the implicit VSTRING null safety byte. - */ - vstring_sprintf(test_label, "%d (converted to v2, one byte appended)", - (int) (v1_test_case - v1_test_cases)); - mutated_test_case = v2_test_case; - mutated_test_case.haproxy_req_len += 1; - /* reuse v2_test_case->exp_req_len */ - msg_info("RUN %s", STR(test_label)); - if (evaluate_test_case(STR(test_label), &mutated_test_case, - NO_SOCKADDR_OUTPUT)) { - msg_info("FAIL %s", STR(test_label)); - tests_failed += 1; - } else { - msg_info("PASS %s", STR(test_label)); - tests_passed += 1; - } + /* + * Clean up. + */ + vstring_free(v2_request_buf); + vstring_free(mutated_request_buf); + vstring_free(test_label); +} - /* - * Mutation test: a well-formed v2 test case should fail after - * stripping one byte - */ - vstring_sprintf(test_label, "%d (converted to v2, last byte stripped)", - (int) (v1_test_case - v1_test_cases)); - mutated_test_case = v2_test_case; - mutated_test_case.haproxy_req_len -= 1; - mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len; - mutated_test_case.exp_return = "short version 2 protocol header"; - msg_info("RUN %s", STR(test_label)); - if (evaluate_test_case(STR(test_label), &mutated_test_case, - NO_SOCKADDR_OUTPUT)) { - msg_info("FAIL %s", STR(test_label)); - tests_failed += 1; - } else { - msg_info("PASS %s", STR(test_label)); - tests_passed += 1; - } - } + /* + * PTEST adapter. + */ +typedef struct PTEST_CASE { + const char *testname; + void (*action) (PTEST_CTX *, const struct PTEST_CASE *); +} PTEST_CASE; + +static void run_proxy_tests(PTEST_CTX *t, const PTEST_CASE *unused) +{ + BASE_TEST_CASE *v1_test_case; + VSTRING *test_label; /* - * Additional V2-only tests. + * Run all variants of one base case test together in a subtest. */ - vstring_strcpy(test_label, "v2 non-proxy request"); - msg_info("RUN %s", STR(test_label)); - if (evaluate_test_case("v2 non-proxy request", &v2_non_proxy_test, - NO_SOCKADDR_OUTPUT)) { - msg_info("FAIL %s", STR(test_label)); - tests_failed += 1; - } else { - msg_info("PASS %s", STR(test_label)); - tests_passed += 1; + test_label = vstring_alloc(100); + for (v1_test_case = v1_test_cases; + v1_test_case->haproxy_request != 0; v1_test_case++) { + vstring_sprintf(test_label, "%d", + 1 + (int) (v1_test_case - v1_test_cases)); + PTEST_RUN(t, STR(test_label), { + run_test_variants(t, v1_test_case); + }); } /* - * Clean up. + * Additional V2-only test. */ - vstring_free(v2_request_buf); - vstring_free(mutated_request_buf); + vstring_sprintf(test_label, "%d", + 1 + (int) (v1_test_case - v1_test_cases)); + PTEST_RUN(t, STR(test_label), { + evaluate_test_case(t, "v2 non-proxy request", &v2_non_proxy_test, + DO_SOCKADDR_OUTPUT); + }); vstring_free(test_label); - msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed); - exit(tests_failed != 0); } + + /* + * PTEST adapter. + */ +static const PTEST_CASE ptestcases[] = { + "haproxy_srvr_test", run_proxy_tests, +}; + +#include diff --git a/postfix/src/global/hfrom_format.c b/postfix/src/global/hfrom_format.c index f0f850a3e..a0122fa96 100644 --- a/postfix/src/global/hfrom_format.c +++ b/postfix/src/global/hfrom_format.c @@ -91,191 +91,3 @@ const char *str_hfrom_format_code(int code) msg_fatal("invalid header format code: %d", code); return (name); } - -#ifdef TEST -#include -#include -#include - -#include -#include -#include - -#define STR(x) vstring_str(x) - - /* - * TODO(wietse) make this a proper VSTREAM interface. Instead of temporarily - * swapping streams, we could temporarily swap the stream's write function. - */ - -/* vstream_swap - kludge to capture output for testing */ - -static void vstream_swap(VSTREAM *one, VSTREAM *two) -{ - VSTREAM save; - - save = *one; - *one = *two; - *two = save; -} - -jmp_buf test_fatal_jbuf; - -#undef msg_fatal - -/* test_msg_fatal - does not return, and does not terminate */ - -void test_msg_fatal(const char *fmt,...) -{ - va_list ap; - - va_start(ap, fmt); - vmsg_warn(fmt, ap); - va_end(ap); - longjmp(test_fatal_jbuf, 1); -} - -struct name_test_case { - const char *label; /* identifies test case */ - const char *config; /* configuration under test */ - const char *exp_warning; /* expected warning or empty */ - const int exp_code; /* expected code */ -}; - -static struct name_test_case name_test_cases[] = { - {"hfrom_format_parse good-standard", - /* config */ HFROM_FORMAT_NAME_STD, - /* warning */ "", - /* exp_code */ HFROM_FORMAT_CODE_STD - }, - {"hfrom_format_parse good-obsolete", - /* config */ HFROM_FORMAT_NAME_OBS, - /* warning */ "", - /* exp_code */ HFROM_FORMAT_CODE_OBS - }, - {"hfrom_format_parse bad", - /* config */ "does-not-exist", - /* warning */ "hfrom_format: warning: invalid setting: \"hfrom_format_parse bad = does-not-exist\"\n", - /* code */ 0, - }, - {"hfrom_format_parse empty", - /* config */ "", - /* warning */ "hfrom_format: warning: invalid setting: \"hfrom_format_parse empty = \"\n", - /* code */ 0, - }, - 0, -}; - -struct code_test_case { - const char *label; /* identifies test case */ - int code; /* code under test */ - const char *exp_warning; /* expected warning or empty */ - const char *exp_name; /* expected namme */ -}; - -static struct code_test_case code_test_cases[] = { - {"str_hfrom_format_code good-standard", - /* code */ HFROM_FORMAT_CODE_STD, - /* warning */ "", - /* exp_name */ HFROM_FORMAT_NAME_STD - }, - {"str_hfrom_format_code good-obsolete", - /* code */ HFROM_FORMAT_CODE_OBS, - /* warning */ "", - /* exp_name */ HFROM_FORMAT_NAME_OBS - }, - {"str_hfrom_format_code bad", - /* config */ 12345, - /* warning */ "hfrom_format: warning: invalid header format code: 12345\n", - /* exp_name */ 0 - }, - 0, -}; - -int main(int argc, char **argv) -{ - struct name_test_case *np; - int code; - struct code_test_case *cp; - const char *name; - int pass = 0; - int fail = 0; - int test_failed; - VSTRING *msg_buf; - VSTREAM *memory_stream; - - msg_vstream_init("hfrom_format", VSTREAM_ERR); - msg_buf = vstring_alloc(100); - - for (np = name_test_cases; np->label != 0; np++) { - VSTRING_RESET(msg_buf); - VSTRING_TERMINATE(msg_buf); - test_failed = 0; - if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0) - msg_fatal("open memory stream: %m"); - vstream_swap(VSTREAM_ERR, memory_stream); - if (setjmp(test_fatal_jbuf) == 0) - code = hfrom_format_parse(np->label, np->config); - vstream_swap(memory_stream, VSTREAM_ERR); - if (vstream_fclose(memory_stream)) - msg_fatal("close memory stream: %m"); - if (strcmp(STR(msg_buf), np->exp_warning) != 0) { - msg_warn("test case %s: got error: \"%s\", want: \"%s\"", - np->label, STR(msg_buf), np->exp_warning); - test_failed = 1; - } - if (*np->exp_warning == 0) { - if (code != np->exp_code) { - msg_warn("test case %s: got code: \"%d\", want: \"%d\"(%s)", - np->label, code, np->exp_code, - str_hfrom_format_code(np->exp_code)); - test_failed = 1; - } - } - if (test_failed) { - msg_info("%s: FAIL", np->label); - fail++; - } else { - msg_info("%s: PASS", np->label); - pass++; - } - } - - for (cp = code_test_cases; cp->label != 0; cp++) { - VSTRING_RESET(msg_buf); - VSTRING_TERMINATE(msg_buf); - test_failed = 0; - if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0) - msg_fatal("open memory stream: %m"); - vstream_swap(VSTREAM_ERR, memory_stream); - if (setjmp(test_fatal_jbuf) == 0) - name = str_hfrom_format_code(cp->code); - vstream_swap(memory_stream, VSTREAM_ERR); - if (vstream_fclose(memory_stream)) - msg_fatal("close memory stream: %m"); - if (strcmp(STR(msg_buf), cp->exp_warning) != 0) { - msg_warn("test case %s: got error: \"%s\", want: \"%s\"", - cp->label, STR(msg_buf), cp->exp_warning); - test_failed = 1; - } else if (*cp->exp_warning == 0) { - if (strcmp(name, cp->exp_name)) { - msg_warn("test case %s: got name: \"%s\", want: \"%s\"", - cp->label, name, cp->exp_name); - test_failed = 1; - } - } - if (test_failed) { - msg_info("%s: FAIL", cp->label); - fail++; - } else { - msg_info("%s: PASS", cp->label); - pass++; - } - } - - msg_info("PASS=%d FAIL=%d", pass, fail); - vstring_free(msg_buf); - exit(fail != 0); -} - -#endif diff --git a/postfix/src/global/hfrom_format.ref b/postfix/src/global/hfrom_format.ref deleted file mode 100644 index 28ba8708a..000000000 --- a/postfix/src/global/hfrom_format.ref +++ /dev/null @@ -1,8 +0,0 @@ -hfrom_format: hfrom_format_parse good-standard: PASS -hfrom_format: hfrom_format_parse good-obsolete: PASS -hfrom_format: hfrom_format_parse bad: PASS -hfrom_format: hfrom_format_parse empty: PASS -hfrom_format: str_hfrom_format_code good-standard: PASS -hfrom_format: str_hfrom_format_code good-obsolete: PASS -hfrom_format: str_hfrom_format_code bad: PASS -hfrom_format: PASS=7 FAIL=0 diff --git a/postfix/src/global/hfrom_format_test.c b/postfix/src/global/hfrom_format_test.c new file mode 100644 index 000000000..9a6fe2e18 --- /dev/null +++ b/postfix/src/global/hfrom_format_test.c @@ -0,0 +1,139 @@ + /* + * Test program to exercise hfrom_format.c. See ptest_main.h for a + * documented example. + */ + + /* + * System library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include + + /* + * Test library. + */ +#include + + /* + * 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 diff --git a/postfix/src/global/login_sender_match.ref b/postfix/src/global/login_sender_match.ref deleted file mode 100644 index 20ea4832f..000000000 --- a/postfix/src/global/login_sender_match.ref +++ /dev/null @@ -1,35 +0,0 @@ -unknown: RUN test case 0 wildcard works -unknown: PASS test 0 -unknown: RUN test case 1 unknown user -unknown: PASS test 1 -unknown: RUN test case 2 bare user -unknown: PASS test 2 -unknown: RUN test case 3 user@domain -unknown: PASS test 3 -unknown: RUN test case 4 user+ext@domain -unknown: PASS test 4 -unknown: RUN test case 5 wrong sender -unknown: PASS test 5 -unknown: RUN test case 6 @domain -unknown: PASS test 6 -unknown: RUN test case 7 wrong @domain -unknown: PASS test 7 -unknown: RUN test case 8 null sender -unknown: PASS test 8 -unknown: RUN test case 9 wrong null sender -unknown: PASS test 9 -unknown: RUN test case 10 error -unknown: warning: fail:sorry lookup error for "baz" -unknown: PASS test 10 -unknown: RUN test case 11 no error -unknown: PASS test 11 -unknown: RUN test case 12 unknown uid:number -unknown: PASS test 12 -unknown: RUN test case 13 known uid:number -unknown: PASS test 13 -unknown: RUN test case 14 unknown "other last" -unknown: PASS test 14 -unknown: RUN test case 15 bare "first last" -unknown: PASS test 15 -unknown: RUN test case 16 "first last"@domain -unknown: PASS test 16 diff --git a/postfix/src/global/login_sender_match_test.c b/postfix/src/global/login_sender_match_test.c index 36d806d52..3f99802d2 100644 --- a/postfix/src/global/login_sender_match_test.c +++ b/postfix/src/global/login_sender_match_test.c @@ -1,144 +1,249 @@ /* - * System library. + * Test program to exercise login_sender_match.c. See and ptest_main.h for a + * documented example. */ -#include /* - * Utility library. + * System library. */ -#include -#include -#include +#include /* * Global library. */ -#include #include +#include -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 + +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 diff --git a/postfix/src/global/mail_addr_find.c b/postfix/src/global/mail_addr_find.c index c7e55455c..68efdcaa4 100644 --- a/postfix/src/global/mail_addr_find.c +++ b/postfix/src/global/mail_addr_find.c @@ -208,6 +208,8 @@ static int strategy_from_string(const char *strategy_string) /* strategy_to_string - internal form to symbolic strategy flags */ +#if 0 + static const char *strategy_to_string(VSTRING *res_buf, int strategy_mask) { static VSTRING *my_buf; @@ -219,6 +221,8 @@ static const char *strategy_to_string(VSTRING *res_buf, int strategy_mask) NAME_MASK_WARN | NAME_MASK_PIPE)); } +#endif + #endif /* diff --git a/postfix/src/global/mail_dict.c b/postfix/src/global/mail_dict.c index 55ac5dc22..94ed47b9a 100644 --- a/postfix/src/global/mail_dict.c +++ b/postfix/src/global/mail_dict.c @@ -118,7 +118,7 @@ int main(int argc, char **argv) var_proxywrite_service = DEF_PROXYWRITE_SERVICE; var_ipc_timeout = 3600; mail_dict_init(); - dict_test(argc, argv); + dict_cli(argc, argv); return (0); } diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index f60f3b453..4b7939447 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20260401" +#define MAIL_RELEASE_DATE "20260402" #define MAIL_VERSION_NUMBER "3.12" #ifdef SNAPSHOT diff --git a/postfix/src/global/map_search.c b/postfix/src/global/map_search.c index b10f7d516..2cead144a 100644 --- a/postfix/src/global/map_search.c +++ b/postfix/src/global/map_search.c @@ -252,146 +252,3 @@ const MAP_SEARCH *map_search_lookup(const char *map_spec) return ((MAP_SEARCH *) htable_find(map_search_table, map_spec)); } - - /* - * Test driver. - */ -#ifdef TEST -#include - - /* - * Test search actions. - */ -#define TEST_NAME_1 "one" -#define TEST_NAME_2 "two" -#define TEST_CODE_1 1 -#define TEST_CODE_2 2 - -#define BAD_NAME "bad" - -static const NAME_CODE search_actions[] = { - TEST_NAME_1, TEST_CODE_1, - TEST_NAME_2, TEST_CODE_2, - 0, MAP_SEARCH_CODE_UNKNOWN, -}; - -/* Helpers to simplify tests. */ - -static const char *string_or_null(const char *s) -{ - return (s ? s : "(null)"); -} - -static char *escape_order(VSTRING *buf, const char *search_order) -{ - return (STR(escape(buf, search_order, strlen(search_order)))); -} - -int main(int argc, char **argv) -{ - /* Test cases with inputs and expected outputs. */ - typedef struct TEST_CASE { - const char *map_spec; - int exp_return; /* 0=fail, 1=success */ - const char *exp_map_type_name; /* 0 or match */ - const char *exp_search_order; /* 0 or match */ - } TEST_CASE; - static TEST_CASE test_cases[] = { - {"type", 0, 0, 0}, - {"type:name", 1, "type:name", 0}, - {"{type:name}", 1, "type:name", 0}, - {"{type:name", 0, 0, 0}, /* } */ - {"{type}", 0, 0, 0}, - {"{type:name foo}", 0, 0, 0}, - {"{type:name foo=bar}", 0, 0, 0}, - {"{type:name search_order=}", 1, "type:name", ""}, - {"{type:name search_order=one, two}", 0, 0, 0}, - {"{type:name {search_order=one, two}}", 1, "type:name", "\01\02"}, - {"{type:name {search_order=one, two, bad}}", 0, 0, 0}, - {"{inline:{a=b} {search_order=one, two}}", 1, "inline:{a=b}", "\01\02"}, - {"{inline:{a=b, c=d} {search_order=one, two}}", 1, "inline:{a=b, c=d}", "\01\02"}, - {0}, - }; - TEST_CASE *test_case; - - /* Actual results. */ - const MAP_SEARCH *map_search_from_create; - const MAP_SEARCH *map_search_from_create_2nd; - const MAP_SEARCH *map_search_from_lookup; - - /* Findings. */ - int tests_failed = 0; - int test_failed; - - /* Scratch */ - VSTRING *expect_escaped = vstring_alloc(100); - VSTRING *actual_escaped = vstring_alloc(100); - - map_search_init(search_actions); - - for (tests_failed = 0, test_case = test_cases; test_case->map_spec; - tests_failed += test_failed, test_case++) { - test_failed = 0; - msg_info("test case %d: '%s'", - (int) (test_case - test_cases), test_case->map_spec); - map_search_from_create = map_search_create(test_case->map_spec); - if (!test_case->exp_return != !map_search_from_create) { - if (map_search_from_create) - msg_warn("test case %d return expected %s actual {%s, %s}", - (int) (test_case - test_cases), - test_case->exp_return ? "success" : "fail", - map_search_from_create->map_type_name, - escape_order(actual_escaped, - map_search_from_create->search_order)); - else - msg_warn("test case %d return expected %s actual %s", - (int) (test_case - test_cases), "success", - map_search_from_create ? "success" : "fail"); - test_failed = 1; - continue; - } - if (test_case->exp_return == 0) - continue; - map_search_from_lookup = map_search_lookup(test_case->map_spec); - if (map_search_from_create != map_search_from_lookup) { - msg_warn("test case %d map_search_lookup expected=%p actual=%p", - (int) (test_case - test_cases), - map_search_from_create, map_search_from_lookup); - test_failed = 1; - } - map_search_from_create_2nd = map_search_create(test_case->map_spec); - if (map_search_from_create != map_search_from_create_2nd) { - msg_warn("test case %d repeated map_search_create " - "expected=%p actual=%p", - (int) (test_case - test_cases), - map_search_from_create, map_search_from_create_2nd); - test_failed = 1; - } - if (strcmp(string_or_null(test_case->exp_map_type_name), - string_or_null(map_search_from_create->map_type_name))) { - msg_warn("test case %d map_type_name expected=%s actual=%s", - (int) (test_case - test_cases), - string_or_null(test_case->exp_map_type_name), - string_or_null(map_search_from_create->map_type_name)); - test_failed = 1; - } - if (strcmp(string_or_null(test_case->exp_search_order), - string_or_null(map_search_from_create->search_order))) { - msg_warn("test case %d search_order expected=%s actual=%s", - (int) (test_case - test_cases), - escape_order(expect_escaped, - string_or_null(test_case->exp_search_order)), - escape_order(actual_escaped, - string_or_null(map_search_from_create->search_order))); - test_failed = 1; - } - } - vstring_free(expect_escaped); - vstring_free(actual_escaped); - - if (tests_failed) - msg_info("tests failed: %d", tests_failed); - exit(tests_failed != 0); -} - -#endif diff --git a/postfix/src/global/map_search.ref b/postfix/src/global/map_search.ref deleted file mode 100644 index a296e4e4a..000000000 --- a/postfix/src/global/map_search.ref +++ /dev/null @@ -1,22 +0,0 @@ -unknown: test case 0: 'type' -unknown: warning: malformed map specification: 'type' -unknown: warning: expected maptype:mapname instead of 'type' -unknown: test case 1: 'type:name' -unknown: test case 2: '{type:name}' -unknown: test case 3: '{type:name' -unknown: warning: malformed map specification: 'missing '}' in "{type:name"' -unknown: test case 4: '{type}' -unknown: warning: malformed map specification: '{type}' -unknown: warning: expected maptype:mapname instead of 'type' -unknown: test case 5: '{type:name foo}' -unknown: warning: malformed map attribute in '{type:name foo}': 'missing '=' after attribute name' -unknown: test case 6: '{type:name foo=bar}' -unknown: warning: unknown map attribute in '{type:name foo=bar}': 'foo' -unknown: test case 7: '{type:name search_order=}' -unknown: test case 8: '{type:name search_order=one, two}' -unknown: warning: malformed map attribute in '{type:name search_order=one, two}': 'missing '=' after attribute name' -unknown: test case 9: '{type:name {search_order=one, two}}' -unknown: test case 10: '{type:name {search_order=one, two, bad}}' -unknown: warning: unknown search type 'bad' in '{type:name {search_order=one, two, bad}}' -unknown: test case 11: '{inline:{a=b} {search_order=one, two}}' -unknown: test case 12: '{inline:{a=b, c=d} {search_order=one, two}}' diff --git a/postfix/src/global/map_search_test.c b/postfix/src/global/map_search_test.c new file mode 100644 index 000000000..8eb6b91b7 --- /dev/null +++ b/postfix/src/global/map_search_test.c @@ -0,0 +1,242 @@ + /* + * Test program to exercise map_search.c. See ptest_main.h for a documented + * example. + */ + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * Test library. + */ +#include + + /* + * 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 diff --git a/postfix/src/global/nbdb_redirect_test.c b/postfix/src/global/nbdb_redirect_test.c index 81b737c19..728b5d707 100644 --- a/postfix/src/global/nbdb_redirect_test.c +++ b/postfix/src/global/nbdb_redirect_test.c @@ -138,6 +138,7 @@ static int redirects_dict_open_hash_to_def_db_type(const TEST_CASE *tp) status = FAIL; } dict_close(dict); + msg_capt_free(capture); } return (status); @@ -195,6 +196,7 @@ static int redirects_dict_open_btree_to_def_cache_db_type(const TEST_CASE *tp) status = FAIL; } dict_close(dict); + msg_capt_free(capture); } return (status); @@ -252,6 +254,7 @@ static int redirects_mkmap_open_hash_to_def_db_type(const TEST_CASE *tp) status = FAIL; } mkmap_close(mkmap); + msg_capt_free(capture); } return (status); @@ -310,6 +313,7 @@ static int redirects_mkmap_open_btree_to_def_cache_db_type(const TEST_CASE *tp) status = FAIL; } mkmap_close(mkmap); + msg_capt_free(capture); } return (status); diff --git a/postfix/src/global/normalize_mailhost_addr.c b/postfix/src/global/normalize_mailhost_addr.c index 0502b5025..70dadcef4 100644 --- a/postfix/src/global/normalize_mailhost_addr.c +++ b/postfix/src/global/normalize_mailhost_addr.c @@ -149,111 +149,3 @@ int normalize_mailhost_addr(const char *string, char **mailhost_addr, } return (0); } - - /* - * Test program. - */ -#ifdef TEST -#include -#include -#include - - /* - * Main test program. - */ -int main(int argc, char **argv) -{ - /* Test cases with inputs and expected outputs. */ - typedef struct TEST_CASE { - const char *inet_protocols; - const char *mailhost_addr; - int exp_return; - const char *exp_mailhost_addr; - char *exp_bare_addr; - int exp_addr_family; - } TEST_CASE; - static TEST_CASE test_cases[] = { - /* IPv4 in IPv6. */ - {"ipv4, ipv6", "ipv6:::ffff:1.2.3.4", 0, "1.2.3.4", "1.2.3.4", AF_INET}, - {"ipv6", "ipv6:::ffff:1.2.3.4", 0, "IPv6:::ffff:1.2.3.4", "::ffff:1.2.3.4", AF_INET6}, - /* Pass IPv4 or IPV6. */ - {"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", "fc00::1", AF_INET6}, - {"ipv4, ipv6", "1.2.3.4", 0, "1.2.3.4", "1.2.3.4", AF_INET}, - /* Normalize IPv4 or IPV6. */ - {"ipv4, ipv6", "ipv6:fc00::0", 0, "IPv6:fc00::", "fc00::", AF_INET6}, - {"ipv4, ipv6", "01.02.03.04", 0, "1.2.3.4", "1.2.3.4", AF_INET}, - /* Suppress specific outputs. */ - {"ipv4, ipv6", "ipv6:fc00::1", 0, 0, "fc00::1", AF_INET6}, - {"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", 0, AF_INET6}, - {"ipv4, ipv6", "ipv6:fc00::1", 0, "IPv6:fc00::1", "fc00::1", -1}, - /* Address type mismatch. */ - {"ipv4, ipv6", "::ffff:1.2.3.4", -1}, - {"ipv4", "ipv6:fc00::1", -1}, - {"ipv6", "1.2.3.4", -1}, - 0, - }; - TEST_CASE *test_case; - - /* Actual results. */ - int act_return; - char *act_mailhost_addr = mystrdup("initial_mailhost_addr"); - char *act_bare_addr = mystrdup("initial_bare_addr"); - int act_addr_family = 0xdeadbeef; - - /* Findings. */ - int tests_failed = 0; - int test_failed; - - for (tests_failed = 0, test_case = test_cases; test_case->inet_protocols; - tests_failed += test_failed, test_case++) { - test_failed = 0; - inet_proto_init(argv[0], test_case->inet_protocols); - act_return = - normalize_mailhost_addr(test_case->mailhost_addr, - test_case->exp_mailhost_addr ? - &act_mailhost_addr : (char **) 0, - test_case->exp_bare_addr ? - &act_bare_addr : (char **) 0, - test_case->exp_addr_family >= 0 ? - &act_addr_family : (int *) 0); - if (act_return != test_case->exp_return) { - msg_warn("test case %d return expected=%d actual=%d", - (int) (test_case - test_cases), - test_case->exp_return, act_return); - test_failed = 1; - continue; - } - if (test_case->exp_return != 0) - continue; - if (test_case->exp_mailhost_addr - && strcmp(test_case->exp_mailhost_addr, act_mailhost_addr)) { - msg_warn("test case %d mailhost_addr expected=%s actual=%s", - (int) (test_case - test_cases), - test_case->exp_mailhost_addr, act_mailhost_addr); - test_failed = 1; - } - if (test_case->exp_bare_addr - && strcmp(test_case->exp_bare_addr, act_bare_addr)) { - msg_warn("test case %d bare_addr expected=%s actual=%s", - (int) (test_case - test_cases), - test_case->exp_bare_addr, act_bare_addr); - test_failed = 1; - } - if (test_case->exp_addr_family >= 0 - && test_case->exp_addr_family != act_addr_family) { - msg_warn("test case %d addr_family expected=0x%x actual=0x%x", - (int) (test_case - test_cases), - test_case->exp_addr_family, act_addr_family); - test_failed = 1; - } - } - if (act_mailhost_addr) - myfree(act_mailhost_addr); - if (act_bare_addr) - myfree(act_bare_addr); - if (tests_failed) - msg_info("tests failed: %d", tests_failed); - exit(tests_failed != 0); -} - -#endif diff --git a/postfix/src/global/normalize_mailhost_addr_test.c b/postfix/src/global/normalize_mailhost_addr_test.c new file mode 100644 index 000000000..5dcf58b61 --- /dev/null +++ b/postfix/src/global/normalize_mailhost_addr_test.c @@ -0,0 +1,179 @@ + /* + * Test program to exercise normalize_mailhost_addr.c. See ptest_main.h for + * a documented example. + */ + + /* + * System library. + */ +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * Test library. + */ +#include + +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 diff --git a/postfix/src/global/smtp_reply_footer.c b/postfix/src/global/smtp_reply_footer.c index 6e5bb75d9..ef1e40d39 100644 --- a/postfix/src/global/smtp_reply_footer.c +++ b/postfix/src/global/smtp_reply_footer.c @@ -207,82 +207,3 @@ int smtp_reply_footer(VSTRING *buffer, ssize_t start, vstring_strcat(buffer, "\r\n"); return (mac_expand_error ? -2 : 0); } - -#ifdef TEST - -#include -#include -#include -#include -#include -#include -#include - -struct test_case { - const char *title; - const char *orig_reply; - const char *template; - const char *filter; - int expected_status; - const char *expected_reply; -}; - -#define NO_FILTER ((char *) 0) -#define NO_TEMPLATE "NO_TEMPLATE" -#define NO_ERROR (0) -#define BAD_SMTP (-1) -#define BAD_MACRO (-2) - -static const struct test_case test_cases[] = { - {"missing reply", "", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0}, - {"long smtp_code", "1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0}, - {"short smtp_code", "12 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0}, - {"good+bad smtp_code", "321 foo\r\n1234 foo", NO_TEMPLATE, NO_FILTER, BAD_SMTP, 0}, - {"1-line no dsn", "550 Foo", "\\c footer", NO_FILTER, NO_ERROR, "550 Foo footer"}, - {"1-line no dsn", "550 Foo", "Bar", NO_FILTER, NO_ERROR, "550-Foo\r\n550 Bar"}, - {"2-line no dsn", "550-Foo\r\n550 Bar", "Baz", NO_FILTER, NO_ERROR, "550-Foo\r\n550-Bar\r\n550 Baz"}, - {"1-line with dsn", "550 5.1.1 Foo", "Bar", NO_FILTER, NO_ERROR, "550-5.1.1 Foo\r\n550 5.1.1 Bar"}, - {"2-line with dsn", "550-5.1.1 Foo\r\n450 4.1.1 Bar", "Baz", NO_FILTER, NO_ERROR, "550-5.1.1 Foo\r\n450-4.1.1 Bar\r\n450 4.1.1 Baz"}, - {"bad macro", "220 myhostname", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0}, - {"bad macroCRLF", "220 myhostname\r\n", "\\c ${whatever", NO_FILTER, BAD_MACRO, 0}, - {"good macro", "220 myhostname", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY"}, - {"good macroCRLF", "220 myhostname\r\n", "\\c $whatever", NO_FILTER, NO_ERROR, "220 myhostname DUMMY\r\n"}, - 0, -}; - -static const char *lookup(const char *name, int unused_mode, void *context) -{ - return "DUMMY"; -} - -int main(int argc, char **argv) -{ - const struct test_case *tp; - int status; - VSTRING *buf = vstring_alloc(10); - void *context = 0; - - msg_vstream_init(argv[0], VSTREAM_ERR); - - for (tp = test_cases; tp->title != 0; tp++) { - vstring_strcpy(buf, tp->orig_reply); - status = smtp_reply_footer(buf, 0, tp->template, tp->filter, - lookup, context); - if (status != tp->expected_status) { - msg_warn("test \"%s\": status %d, expected %d", - tp->title, status, tp->expected_status); - } else if (status < 0 && strcmp(STR(buf), tp->orig_reply) != 0) { - msg_warn("test \"%s\": result \"%s\", expected \"%s\"", - tp->title, STR(buf), tp->orig_reply); - } else if (status == 0 && strcmp(STR(buf), tp->expected_reply) != 0) { - msg_warn("test \"%s\": result \"%s\", expected \"%s\"", - tp->title, STR(buf), tp->expected_reply); - } else { - msg_info("test \"%s\": pass", tp->title); - } - } - vstring_free(buf); - exit(0); -} - -#endif diff --git a/postfix/src/global/smtp_reply_footer.ref b/postfix/src/global/smtp_reply_footer.ref deleted file mode 100644 index d7eb5a7b5..000000000 --- a/postfix/src/global/smtp_reply_footer.ref +++ /dev/null @@ -1,15 +0,0 @@ -./smtp_reply_footer: test "missing reply": pass -./smtp_reply_footer: test "long smtp_code": pass -./smtp_reply_footer: test "short smtp_code": pass -./smtp_reply_footer: test "good+bad smtp_code": pass -./smtp_reply_footer: test "1-line no dsn": pass -./smtp_reply_footer: test "1-line no dsn": pass -./smtp_reply_footer: test "2-line no dsn": pass -./smtp_reply_footer: test "1-line with dsn": pass -./smtp_reply_footer: test "2-line with dsn": pass -./smtp_reply_footer: warning: truncated macro reference: " ${whatever" -./smtp_reply_footer: test "bad macro": pass -./smtp_reply_footer: warning: truncated macro reference: " ${whatever" -./smtp_reply_footer: test "bad macroCRLF": pass -./smtp_reply_footer: test "good macro": pass -./smtp_reply_footer: test "good macroCRLF": pass diff --git a/postfix/src/global/smtp_reply_footer_test.c b/postfix/src/global/smtp_reply_footer_test.c new file mode 100644 index 000000000..13aeaab86 --- /dev/null +++ b/postfix/src/global/smtp_reply_footer_test.c @@ -0,0 +1,90 @@ + /* + * Test program to exercise smtp_reply_footer.c. See ptest_main.h for a + * documented example. + */ + + /* + * System library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include + + /* + * Test library. + */ +#include + + /* + * 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 diff --git a/postfix/src/global/test_main.c b/postfix/src/global/test_server_main.c similarity index 93% rename from postfix/src/global/test_main.c rename to postfix/src/global/test_server_main.c index a783ce354..936d897d9 100644 --- a/postfix/src/global/test_main.c +++ b/postfix/src/global/test_server_main.c @@ -1,12 +1,12 @@ /*++ /* NAME -/* test_main 3 +/* test_server_main 3 /* SUMMARY /* test main program /* SYNOPSIS -/* #include +/* #include /* -/* 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); @@ -15,10 +15,10 @@ /* This module implements a test main program for stand-alone /* module tests. /* -/* test_main() should be called from a main program. It does +/* test_server_main() should be called from a main program. It does /* generic command-line options processing, and initializes /* configurable parameters. After calling the test_driver() -/* function, the test_main() function terminates. +/* function, the test_server_main() function terminates. /* /* Arguments: /* .IP "void (*test_driver)(int argc, char **argv)" @@ -27,7 +27,7 @@ /* The argc and argv specify the process name and non-option /* command-line arguments. /* .PP -/* Optional test_main() arguments are specified as a null-terminated +/* Optional test_server_main() arguments are specified as a null-terminated /* list with macros that have zero or more arguments: /* .IP "CA_TEST_MAIN_INT_TABLE(CONFIG_INT_TABLE *)" /* A table with configurable parameters, to be loaded from the @@ -116,11 +116,11 @@ /* * Test library. */ -#include +#include /* 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; diff --git a/postfix/src/global/test_main.h b/postfix/src/global/test_server_main.h similarity index 94% rename from postfix/src/global/test_main.h rename to postfix/src/global/test_server_main.h index aea605a1f..c2c247191 100644 --- a/postfix/src/global/test_main.h +++ b/postfix/src/global/test_server_main.h @@ -1,10 +1,10 @@ /*++ /* NAME -/* test_main 3h +/* test_server_main 3h /* SUMMARY /* test main program /* SYNOPSIS -/* #include +/* #include /* DESCRIPTION /* .nf @@ -45,7 +45,7 @@ CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_NBOOL_TABLE); CHECK_CPTR_HELPER_DCL(TEST_MAIN, CONFIG_LONG_TABLE); typedef void (*TEST_DRIVER_FN) (int, char **); -extern NORETURN test_main(int, char **, TEST_DRIVER_FN,...); +extern NORETURN test_server_main(int, char **, TEST_DRIVER_FN,...); /* LICENSE /* .ad diff --git a/postfix/src/local/Makefile.in b/postfix/src/local/Makefile.in index afdbab46d..c21e4ea57 100644 --- a/postfix/src/local/Makefile.in +++ b/postfix/src/local/Makefile.in @@ -93,8 +93,10 @@ alias.o: ../../include/vstream.h alias.o: ../../include/vstring.h alias.o: alias.c alias.o: local.h +biff_notify.o: ../../include/inet_proto.h biff_notify.o: ../../include/iostuff.h biff_notify.o: ../../include/msg.h +biff_notify.o: ../../include/myaddrinfo.h biff_notify.o: ../../include/sys_defs.h biff_notify.o: biff_notify.c biff_notify.o: biff_notify.h diff --git a/postfix/src/local/biff_notify.c b/postfix/src/local/biff_notify.c index a6a49258e..6aa75a220 100644 --- a/postfix/src/local/biff_notify.c +++ b/postfix/src/local/biff_notify.c @@ -30,6 +30,11 @@ /* 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. */ @@ -37,13 +42,14 @@ #include "sys_defs.h" #include #include -#include #include /* Utility library. */ #include #include +#include +#include /* Application-specific. */ @@ -53,37 +59,65 @@ void biff_notify(const char *text, ssize_t len) { - static struct sockaddr_in sin; + const char myname[] = "biff_notify"; + const char *hostname = "localhost"; + const char *servname = "biff"; + int sock_type = SOCK_DGRAM; + const INET_PROTO_INFO *proto_info; + static struct sockaddr_storage sa; + static SOCKADDR_SIZE sa_len; + static int sa_family; static int sock = -1; - struct hostent *hp; - struct servent *sp; + struct addrinfo *res0, *res; + int aierr; + int found; /* * Initialize a socket address structure, or re-use an existing one. */ - if (sin.sin_family == 0) { - if ((sp = getservbyname("biff", "udp")) == 0) { - msg_warn("service not found: biff/udp"); + if (sa_len == 0) { + if ((aierr = hostname_to_sockaddr(hostname, servname, sock_type, + &res0)) != 0) { + msg_warn("lookup failed for host '%s' or service '%s': %s", + hostname, servname, MAI_STRERROR(aierr)); return; } - if ((hp = gethostbyname("localhost")) == 0) { - msg_warn("host not found: localhost"); - return; + proto_info = inet_proto_info(); + for (found = 0, res = res0; !found && res != 0; res = res->ai_next) { + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host '%s' service '%s'", + res->ai_family, hostname, servname); + continue; + } + if (res->ai_addrlen > sizeof(sa)) { + msg_warn("skipping address size %d for host '%s' service '%s'", + res->ai_addrlen, hostname, servname); + continue; + } + found++; + memcpy(&sa, res->ai_addr, res->ai_addrlen); + sa_len = res->ai_addrlen; + sa_family = res->ai_family; + if (msg_verbose) { + MAI_HOSTADDR_STR hostaddr_str; + MAI_SERVPORT_STR servport_str; + + SOCKADDR_TO_HOSTADDR((struct sockaddr *) &sa, sa_len, + &hostaddr_str, &servport_str, 0); + msg_info("%s: sending to: {%s, %s}", + myname, hostaddr_str.buf, servport_str.buf); + } } - if ((int) hp->h_length > (int) sizeof(sin.sin_addr)) { - msg_warn("bad address size %d for localhost", hp->h_length); + freeaddrinfo(res0); + if (!found) return; - } - sin.sin_family = hp->h_addrtype; - sin.sin_port = sp->s_port; - memcpy((void *) &sin.sin_addr, hp->h_addr_list[0], hp->h_length); } /* * Open a socket, or re-use an existing one. */ if (sock < 0) { - if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + if ((sock = socket(sa_family, sock_type, 0)) < 0) { msg_warn("socket: %m"); return; } @@ -93,6 +127,6 @@ void biff_notify(const char *text, ssize_t len) /* * Biff! */ - if (sendto(sock, text, len, 0, (struct sockaddr *) &sin, sizeof(sin)) != len) + if (sendto(sock, text, len, 0, (struct sockaddr *) &sa, sa_len) != len) msg_warn("biff_notify: %m"); } diff --git a/postfix/src/postscreen/Makefile.in b/postfix/src/postscreen/Makefile.in index 24f26c905..8f34f7db9 100644 --- a/postfix/src/postscreen/Makefile.in +++ b/postfix/src/postscreen/Makefile.in @@ -10,11 +10,12 @@ OBJS = postscreen.o postscreen_dict.o postscreen_dnsbl.o \ postscreen_starttls.o postscreen_expand.o postscreen_endpt.o \ postscreen_haproxy.o HDRS = -TESTSRC = +TESTSRC = postscreen_dnsbl_test.c DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) -TESTPROG= +TESTPROG= postscreen_dnsbl_test PROG = postscreen +TEST_LIB= ../../lib/libtesting.a ../../lib/libptest.a INC_DIR = ../../include LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ @@ -27,14 +28,14 @@ LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ $(PROG): $(OBJS) $(LIBS) $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) -$(OBJS): ../../conf/makedefs.out +$(OBJS) $(TESTPROG): ../../conf/makedefs.out Makefile: Makefile.in cat ../../conf/makedefs.out $? >$@ test: $(TESTPROG) -tests: test +tests: test_postscreen_dnsbl root_tests: @@ -48,6 +49,14 @@ clean: tidy: clean +postscreen_dnsbl_test: postscreen_dnsbl_test.o postscreen_dnsbl.o \ + ../../lib/mock_server.o $(TEST_LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o postscreen_dnsbl.o \ + ../../lib/mock_server.o $(TEST_LIB) $(LIBS) $(SYSLIBS) + +test_postscreen_dnsbl: update postscreen_dnsbl_test + $(SHLIB_ENV) ${VALGRIND} ./postscreen_dnsbl_test + depend: $(MAKES) (sed '1,/^# do not edit/!d' Makefile.in; \ set -e; for i in [a-z][a-z0-9]*.c; do \ @@ -142,6 +151,43 @@ postscreen_dnsbl.o: ../../include/vstream.h postscreen_dnsbl.o: ../../include/vstring.h postscreen_dnsbl.o: postscreen.h postscreen_dnsbl.o: postscreen_dnsbl.c +postscreen_dnsbl_test.o: ../../include/addr_match_list.h +postscreen_dnsbl_test.o: ../../include/argv.h +postscreen_dnsbl_test.o: ../../include/attr.h +postscreen_dnsbl_test.o: ../../include/check_arg.h +postscreen_dnsbl_test.o: ../../include/connect.h +postscreen_dnsbl_test.o: ../../include/dict.h +postscreen_dnsbl_test.o: ../../include/dict_cache.h +postscreen_dnsbl_test.o: ../../include/events.h +postscreen_dnsbl_test.o: ../../include/htable.h +postscreen_dnsbl_test.o: ../../include/iostuff.h +postscreen_dnsbl_test.o: ../../include/mail_params.h +postscreen_dnsbl_test.o: ../../include/mail_proto.h +postscreen_dnsbl_test.o: ../../include/make_attr.h +postscreen_dnsbl_test.o: ../../include/maps.h +postscreen_dnsbl_test.o: ../../include/match_list.h +postscreen_dnsbl_test.o: ../../include/mock_server.h +postscreen_dnsbl_test.o: ../../include/msg.h +postscreen_dnsbl_test.o: ../../include/msg_jmp.h +postscreen_dnsbl_test.o: ../../include/msg_output.h +postscreen_dnsbl_test.o: ../../include/msg_vstream.h +postscreen_dnsbl_test.o: ../../include/myaddrinfo.h +postscreen_dnsbl_test.o: ../../include/myflock.h +postscreen_dnsbl_test.o: ../../include/mymalloc.h +postscreen_dnsbl_test.o: ../../include/myrand.h +postscreen_dnsbl_test.o: ../../include/nvtable.h +postscreen_dnsbl_test.o: ../../include/pmock_expect.h +postscreen_dnsbl_test.o: ../../include/ptest.h +postscreen_dnsbl_test.o: ../../include/ptest_main.h +postscreen_dnsbl_test.o: ../../include/server_acl.h +postscreen_dnsbl_test.o: ../../include/string_list.h +postscreen_dnsbl_test.o: ../../include/stringops.h +postscreen_dnsbl_test.o: ../../include/sys_defs.h +postscreen_dnsbl_test.o: ../../include/vbuf.h +postscreen_dnsbl_test.o: ../../include/vstream.h +postscreen_dnsbl_test.o: ../../include/vstring.h +postscreen_dnsbl_test.o: postscreen.h +postscreen_dnsbl_test.o: postscreen_dnsbl_test.c postscreen_early.o: ../../include/addr_match_list.h postscreen_early.o: ../../include/argv.h postscreen_early.o: ../../include/check_arg.h diff --git a/postfix/src/postscreen/postscreen.h b/postfix/src/postscreen/postscreen.h index 69a5e1750..b9ceeeb99 100644 --- a/postfix/src/postscreen/postscreen.h +++ b/postfix/src/postscreen/postscreen.h @@ -485,6 +485,7 @@ const char *psc_maps_find(MAPS *, const char *, int); extern void psc_dnsbl_init(void); extern int psc_dnsbl_retrieve(const char *, const char **, int, int *); extern int psc_dnsbl_request(const char *, void (*) (int, void *), void *); +extern void psc_dnsbl_deinit(void); /* * postscreen_tests.c diff --git a/postfix/src/postscreen/postscreen_dnsbl.c b/postfix/src/postscreen/postscreen_dnsbl.c index 4be962221..d95e0b4ee 100644 --- a/postfix/src/postscreen/postscreen_dnsbl.c +++ b/postfix/src/postscreen/postscreen_dnsbl.c @@ -15,10 +15,12 @@ /* /* int psc_dnsbl_retrieve(client_addr, dnsbl_name, dnsbl_index, /* dnsbl_ttl) -/* char *client_addr; +/* const char *client_addr; /* const char **dnsbl_name; /* int dnsbl_index; /* int *dnsbl_ttl; +/* AUXILIARY FUNCTIONS +/* void psc_dnsbl_deinit(void) /* DESCRIPTION /* This module implements preliminary support for DNSBL lookups. /* Multiple requests for the same information are handled with @@ -44,6 +46,9 @@ /* reference count. The reply TTL value is clamped to /* postscreen_dnsbl_min_ttl and postscreen_dnsbl_max_ttl. It /* is an error to retrieve a score without requesting it first. +/* +/* psc_dnsbl_deinit() tries to reset state so that psc_dnsbl_init() +/* can be called again. This is to support tests only. /* LICENSE /* .ad /* .fi @@ -115,7 +120,7 @@ static HTABLE *dnsbl_site_cache; /* indexed by DNSBNL domain */ static HTABLE_INFO **dnsbl_site_list; /* flattened cache */ typedef struct { - const char *safe_dnsbl; /* from postscreen_dnsbl_reply_map */ + char *safe_dnsbl; /* from postscreen_dnsbl_reply_map */ struct PSC_DNSBL_SITE *first; /* list of (filter, weight) tuples */ } PSC_DNSBL_HEAD; @@ -480,6 +485,8 @@ static void psc_dnsbl_receive(int event, void *context) vstream_fclose(stream); } +static int request_count; + /* psc_dnsbl_request - send dnsbl query, increment reference count */ int psc_dnsbl_request(const char *client_addr, @@ -492,7 +499,6 @@ int psc_dnsbl_request(const char *client_addr, HTABLE_INFO **ht; PSC_DNSBL_SCORE *score; HTABLE_INFO *hash_node; - static int request_count; /* * Some spambots make several connections at nearly the same time, @@ -622,3 +628,72 @@ void psc_dnsbl_init(void) reply_dnsbl = vstring_alloc(100); reply_addr = vstring_alloc(100); } + + /* Begin code reachable only by tests. */ + +static void psc_dnsbl_site_free(void *ptr) +{ + PSC_DNSBL_SITE *site = (PSC_DNSBL_SITE *) ptr; + + if (site->filter) + myfree(site->filter); + if (site->byte_codes) + myfree(site->byte_codes); + if (site->next) + psc_dnsbl_site_free(site->next); + myfree(site); +} + +static void psc_dnsbl_head_free(void *ptr) +{ + PSC_DNSBL_HEAD *head = (PSC_DNSBL_HEAD *) ptr; + + if (head->safe_dnsbl) + myfree(head->safe_dnsbl); + if (head->first) + psc_dnsbl_site_free(head->first); + myfree(head); +}; + +static void psc_dnsbl_score_free(void *ptr) +{ + PSC_DNSBL_SCORE *score = (PSC_DNSBL_SCORE *) ptr; + + myfree(score); +} + +/* psc_dnsbl_deinit - helper for tests only */ + +void psc_dnsbl_deinit(void) +{ + if (psc_dnsbl_service) { + myfree(psc_dnsbl_service); + psc_dnsbl_service = 0; + } + if (dnsbl_site_cache) { + htable_free(dnsbl_site_cache, psc_dnsbl_head_free); + dnsbl_site_cache = 0; + } + if (dnsbl_site_list) { + myfree(dnsbl_site_list); + dnsbl_site_list = 0; + } + if (dnsbl_score_cache) { + htable_free(dnsbl_score_cache, psc_dnsbl_score_free); + dnsbl_score_cache = 0; + } + if (reply_client) { + vstring_free(reply_client); + reply_client = 0; + } + if (reply_dnsbl) { + vstring_free(reply_dnsbl), reply_dnsbl = 0; + } + if (reply_addr) { + vstring_free(reply_addr); + reply_addr = 0; + } + request_count = 0; +} + + /* End code reachable only by tests. */ diff --git a/postfix/src/postscreen/postscreen_dnsbl_test.c b/postfix/src/postscreen/postscreen_dnsbl_test.c new file mode 100644 index 000000000..5cbcd4da8 --- /dev/null +++ b/postfix/src/postscreen/postscreen_dnsbl_test.c @@ -0,0 +1,569 @@ + /* + * Test program to exercise postscreen_dnsbl.c. See comments in + * mock_server.c, and PTEST_README for documented examples of unit tests. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include + + /* + * Test library. + */ +#include +#include +#include + + /* + * Application-specific. + */ +#include + + /* + * 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 diff --git a/postfix/src/ptest/Makefile.in b/postfix/src/ptest/Makefile.in new file mode 100644 index 000000000..102010b2c --- /dev/null +++ b/postfix/src/ptest/Makefile.in @@ -0,0 +1,174 @@ +SHELL = /bin/sh +SRCS = ptest_ctx.c ptest_error.c ptest_log.c pmock_expect.c ptest_run.c \ + msg_jmp.c +LIB_OBJ = ptest_ctx.o ptest_error.o ptest_log.o pmock_expect.o ptest_run.o \ + msg_jmp.o +HDRS = ptest.h pmock_expect.h ptest_main.h msg_jmp.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +INCL = +LIB = libptest.a +TESTPROG= pmock_expect_test ptest_log_test + +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +LIB_DIR = ../../lib +INC_DIR = ../../include +MAKES = + +.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c + +all: $(LIB_OBJ) $(MOCK_OBJ) + +$(LIB_OBJ) $(TESTPROG): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +$(LIB): $(LIB_OBJ) + $(_AR) $(ARFL) $(LIB) $? + $(_RANLIB) $(LIB) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR) + $(_RANLIB) $(LIB_DIR)/$(LIB) + +update: $(LIB_DIR)/$(LIB) $(HDRS) $(MOCK_OBJ) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + (cd $(INC_DIR); chmod 644 $(HDRS)) + -for i in $(MOCK_OBJ); \ + do \ + cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \ + done + +clean: + rm -f *.o $(LIB) *core $(TESTPROG) junk $(MAKES) *.tmp + +tidy: clean + +tests: update test_pmock_expect test_ptest_log + +pmock_expect_test: pmock_expect_test.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o $(LIB) $(LIBS) $(SYSLIBS) + +test_pmock_expect: update pmock_expect_test + $(SHLIB_ENV) ${VALGRIND} ./pmock_expect_test + +ptest_log_test: ptest_log_test.o ptest_log.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o ptest_log.o $(LIB) $(LIBS) $(SYSLIBS) + +test_ptest_log: update ptest_log_test + $(SHLIB_ENV) ${VALGRIND} ./ptest_log_test + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +msg_jmp.o: ../../include/msg.h +msg_jmp.o: ../../include/sys_defs.h +msg_jmp.o: msg_jmp.c +msg_jmp.o: msg_jmp.h +pmock_expect.o: ../../include/argv.h +pmock_expect.o: ../../include/check_arg.h +pmock_expect.o: ../../include/htable.h +pmock_expect.o: ../../include/msg.h +pmock_expect.o: ../../include/mymalloc.h +pmock_expect.o: ../../include/sys_defs.h +pmock_expect.o: ../../include/vbuf.h +pmock_expect.o: ../../include/vstream.h +pmock_expect.o: ../../include/vstring.h +pmock_expect.o: msg_jmp.h +pmock_expect.o: pmock_expect.c +pmock_expect.o: pmock_expect.h +pmock_expect.o: ptest.h +pmock_expect_test.o: ../../include/argv.h +pmock_expect_test.o: ../../include/check_arg.h +pmock_expect_test.o: ../../include/msg.h +pmock_expect_test.o: ../../include/msg_output.h +pmock_expect_test.o: ../../include/msg_vstream.h +pmock_expect_test.o: ../../include/mymalloc.h +pmock_expect_test.o: ../../include/myrand.h +pmock_expect_test.o: ../../include/stringops.h +pmock_expect_test.o: ../../include/sys_defs.h +pmock_expect_test.o: ../../include/vbuf.h +pmock_expect_test.o: ../../include/vstream.h +pmock_expect_test.o: ../../include/vstring.h +pmock_expect_test.o: msg_jmp.h +pmock_expect_test.o: pmock_expect.h +pmock_expect_test.o: pmock_expect_test.c +pmock_expect_test.o: ptest.h +pmock_expect_test.o: ptest_main.h +ptest_ctx.o: ../../include/argv.h +ptest_ctx.o: ../../include/check_arg.h +ptest_ctx.o: ../../include/msg.h +ptest_ctx.o: ../../include/mymalloc.h +ptest_ctx.o: ../../include/stringops.h +ptest_ctx.o: ../../include/sys_defs.h +ptest_ctx.o: ../../include/vbuf.h +ptest_ctx.o: ../../include/vstream.h +ptest_ctx.o: ../../include/vstring.h +ptest_ctx.o: msg_jmp.h +ptest_ctx.o: ptest.h +ptest_ctx.o: ptest_ctx.c +ptest_error.o: ../../include/argv.h +ptest_error.o: ../../include/check_arg.h +ptest_error.o: ../../include/msg.h +ptest_error.o: ../../include/mymalloc.h +ptest_error.o: ../../include/sys_defs.h +ptest_error.o: ../../include/vbuf.h +ptest_error.o: ../../include/vstream.h +ptest_error.o: ../../include/vstring.h +ptest_error.o: msg_jmp.h +ptest_error.o: ptest.h +ptest_error.o: ptest_error.c +ptest_log.o: ../../include/argv.h +ptest_log.o: ../../include/check_arg.h +ptest_log.o: ../../include/msg.h +ptest_log.o: ../../include/msg_output.h +ptest_log.o: ../../include/sys_defs.h +ptest_log.o: ../../include/vbuf.h +ptest_log.o: ../../include/vstream.h +ptest_log.o: ../../include/vstring.h +ptest_log.o: msg_jmp.h +ptest_log.o: ptest.h +ptest_log.o: ptest_log.c +ptest_log_test.o: ../../include/argv.h +ptest_log_test.o: ../../include/check_arg.h +ptest_log_test.o: ../../include/msg.h +ptest_log_test.o: ../../include/msg_output.h +ptest_log_test.o: ../../include/msg_vstream.h +ptest_log_test.o: ../../include/myrand.h +ptest_log_test.o: ../../include/stringops.h +ptest_log_test.o: ../../include/sys_defs.h +ptest_log_test.o: ../../include/vbuf.h +ptest_log_test.o: ../../include/vstream.h +ptest_log_test.o: ../../include/vstring.h +ptest_log_test.o: msg_jmp.h +ptest_log_test.o: pmock_expect.h +ptest_log_test.o: ptest.h +ptest_log_test.o: ptest_log_test.c +ptest_log_test.o: ptest_main.h +ptest_run.o: ../../include/argv.h +ptest_run.o: ../../include/check_arg.h +ptest_run.o: ../../include/msg.h +ptest_run.o: ../../include/msg_vstream.h +ptest_run.o: ../../include/sys_defs.h +ptest_run.o: ../../include/vbuf.h +ptest_run.o: ../../include/vstream.h +ptest_run.o: ../../include/vstring.h +ptest_run.o: msg_jmp.h +ptest_run.o: pmock_expect.h +ptest_run.o: ptest.h +ptest_run.o: ptest_run.c diff --git a/postfix/src/ptest/msg_jmp.c b/postfix/src/ptest/msg_jmp.c new file mode 100644 index 000000000..0535ea561 --- /dev/null +++ b/postfix/src/ptest/msg_jmp.c @@ -0,0 +1,77 @@ +/*++ +/* NAME +/* msg_jmp 3 +/* SUMMARY +/* msg plugin for tests +/* SYNOPSIS +/* #include +/* +/* 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 +#include + + /* + * Utility library. + */ +#include + + /* + * Ptest library. + */ +#include + + /* + * Private state. But it has to be caller-visible +because msg_setjmp() cannot be a function. + */ +MSG_JMP_BUF *msg_jmp_bufp; + +/* msg_longjmp - restore setjmp() previously-saved run-time state */ + +NORETURN msg_longjmp(int value) +{ +#ifdef NO_SIGSETJMP + longjmp(msg_jmp_bufp[0], value); +#else + siglongjmp(msg_jmp_bufp[0], value); +#endif +} diff --git a/postfix/src/ptest/msg_jmp.h b/postfix/src/ptest/msg_jmp.h new file mode 100644 index 000000000..92e1a19e4 --- /dev/null +++ b/postfix/src/ptest/msg_jmp.h @@ -0,0 +1,63 @@ +#ifndef _MSG_JMP_H_INCLUDED_ +#define _MSG_JMP_H_INCLUDED_ + +/*++ +/* NAME +/* msg_jmp 3h +/* SUMMARY +/* msg plugin for tests +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + +/* + * System library. + */ +#include + +/* + * Utility library. + */ +#include + +/* + * External interface. + */ + + /* + * Only for tests: make a long jump instead of terminating. + */ +#ifdef NO_SIGSETJMP +#define MSG_JMP_BUF jmp_buf +#define msg_setjmp(bufp) (msg_set_longjmp_action(msg_longjmp), \ + setjmp((msg_jmp_bufp = (bufp))[0])) +#else +#define MSG_JMP_BUF sigjmp_buf +#define msg_setjmp(bufp) (msg_set_longjmp_action(msg_longjmp), \ + sigsetjmp((msg_jmp_bufp = (bufp))[0], 1)) +#endif +#define msg_resetjmp(bufp) do { msg_jmp_bufp = (bufp); } while (0) +#define msg_clearjmp() do { \ + msg_set_longjmp_action(0); \ + msg_jmp_bufp = 0; \ + } while (0) + +extern MSG_JMP_BUF *msg_jmp_bufp; +extern NORETURN msg_longjmp(int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Wietse Venema +/* porcupine.org +/*--*/ + +#endif diff --git a/postfix/src/ptest/pmock_expect.c b/postfix/src/ptest/pmock_expect.c new file mode 100644 index 000000000..e6dd22514 --- /dev/null +++ b/postfix/src/ptest/pmock_expect.c @@ -0,0 +1,286 @@ +/*++ +/* NAME +/* pmock_expect 3h +/* SUMMARY +/* mock support for hermetic tests +/* SYNOPSIS +/* #include +/* +/* 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 + + /* + * Utility library. + */ +#include +#include + + /* + * Testing library. + */ +#include +#include + + /* + * Private structure with all expectations for a single mock application. + */ +typedef struct MOCK_APPL { + const MOCK_APPL_SIG *sig; /* application-specific */ + MOCK_EXPECT *head; /* first expectation */ + MOCK_EXPECT *tail; /* last expectation */ +} MOCK_APPL; + + /* + * Collection of MOCK_APPL instances indexed by application name. + */ +static HTABLE *mock_appl_list; + +/* mock_appl_create - create empty list for same-type expectations */ + +static MOCK_APPL *mock_appl_create(const MOCK_APPL_SIG *sig) +{ + MOCK_APPL *ma; + + /* + * Initialize self. + */ + ma = (MOCK_APPL *) mymalloc(sizeof(*ma)); + ma->sig = sig; + ma->head = ma->tail = 0; + return (ma); +} + +/* mock_appl_list_free - destroy application node */ + +static void mock_appl_list_free(MOCK_APPL *ma) +{ + myfree(ma); +} + +/* pmock_expect_create - create one mock expectation */ + +MOCK_EXPECT *pmock_expect_create(const MOCK_APPL_SIG *sig, const char *file, + int line, int calls_expected, + ssize_t size) +{ + MOCK_APPL *ma; + MOCK_EXPECT *me; + + /* + * Look up or instantiate the expectation for this mock application. + */ + if (mock_appl_list == 0) + mock_appl_list = htable_create(13); + if ((ma = (MOCK_APPL *) htable_find(mock_appl_list, sig->name)) == 0) { + ma = mock_appl_create(sig); + (void) htable_enter(mock_appl_list, sig->name, (void *) ma); + } + + /* + * Initialize the generic expectation fields. + */ + me = (MOCK_EXPECT *) mymalloc(size); + me->file = mystrdup(file); + me->line = line; + me->calls_expected = calls_expected; + me->calls_made = 0; + me->next = 0; + + /* + * Append the new expectation to this mock application list. + */ + if (ma->head == 0) + ma->head = me; + else + ma->tail->next = me; + ma->tail = me; + + /* + * Let the caller fill in their application-specific fields. + */ + return (me); +} + +/* pmock_expect_free - destroy one expectation node */ + +void pmock_expect_free(MOCK_EXPECT *me) +{ + myfree(me->file); + myfree(me); +} + +/* pmock_expect_apply - match inputs and apply outputs */ + +int pmock_expect_apply(const MOCK_APPL_SIG *sig, + const MOCK_EXPECT *inputs, + void *targets) +{ + MOCK_APPL *ma; + MOCK_EXPECT *me; + MOCK_EXPECT *saturated = 0; /* saturated expectation */ + VSTRING *buf; + PTEST_CTX *t; + + /* + * Look up the mock application list. + */ + if (mock_appl_list != 0 && (ma = (MOCK_APPL *) + htable_find(mock_appl_list, sig->name)) != 0) { + + /* + * Look for an expectation match that is not saturated. Remember the + * last saturated match. + */ + for (me = ma->head; me != 0; me = me->next) { + const MOCK_APPL_SIG *sig = ma->sig; + + if (sig->match_expect == 0 || sig->match_expect(me, inputs)) { + if (me->calls_expected == 0 + || me->calls_made < me->calls_expected) { + if (sig->assign_expect) + sig->assign_expect(me, targets); + me->calls_made += 1; + return (1); + } else { + saturated = me; + } + } + } + } + + /* + * Report a saturated or unmatched expectation. + */ + buf = vstring_alloc(100); + t = ptest_ctx_current(); + if (saturated != 0) { + ptest_error(t, "%s:%d too many calls: %s(%s)", + saturated->file, saturated->line, sig->name, + sig->print_expect(saturated, buf)); + } else { + ptest_error(t, "unexpected call: %s(%s)", sig->name, + sig->print_expect(inputs, buf)); + } + vstring_free(buf); + return (0); +} + +/* pmock_expect_wrapup - report unused expectations and clean up */ + +void pmock_expect_wrapup(PTEST_CTX *t) +{ + HTABLE_INFO **info, **ht; + MOCK_APPL *ma; + MOCK_EXPECT *me, *next_me; + VSTRING *buf = 0; + const char *plural[] = {"", "s"}; + + /* + * Iterate over each mock application. + * + * NOTE: do not call ptest_fatal(). This code runs after the test has + * completed. + */ + if (mock_appl_list != 0) { + info = htable_list(mock_appl_list); + for (ht = info; *ht; ht++) { + ma = (MOCK_APPL *) ht[0]->value; + + /* + * Iterate over each expectation. + */ + for (me = ma->head; me != 0; me = next_me) { + next_me = me->next; + if (me->calls_expected > 0 + && me->calls_expected > me->calls_made) { + ma->sig->print_expect(me, buf ? buf : + (buf = vstring_alloc(100))); + ptest_error(t, "%s:%d got %d call%s for %s(%s), want %d", + me->file, me->line, me->calls_made, + plural[me->calls_made != 1], + ma->sig->name, vstring_str(buf), + me->calls_expected); + } else if (me->calls_made == 0) { + ma->sig->print_expect(me, buf ? buf : + (buf = vstring_alloc(100))); + ptest_error(t, "%s:%d got 0 calls for %s(%s), want 1 or more", + me->file, me->line, ma->sig->name, + vstring_str(buf)); + } + ma->sig->free_expect(me); + } + htable_delete(mock_appl_list, ma->sig->name, (void (*) (void *)) 0); + mock_appl_list_free(ma); + } + if (buf) + vstring_free(buf); + myfree(info); + } + if (mock_appl_list != 0 && mock_appl_list->used != 0) + ptest_error(t, "pmock_expect_wrapup: mock_appl_list->used is %ld", + (long) mock_appl_list->used); +} diff --git a/postfix/src/ptest/pmock_expect.h b/postfix/src/ptest/pmock_expect.h new file mode 100644 index 000000000..d267c939a --- /dev/null +++ b/postfix/src/ptest/pmock_expect.h @@ -0,0 +1,78 @@ +#ifndef _PMOCK_EXPECT_H_INCLUDED_ +#define _PMOCK_EXPECT_H_INCLUDED_ + +/*++ +/* NAME +/* pmock_expect 3h +/* SUMMARY +/* mock test support +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Test library. + */ +#include + + /* + * MOCK expectation parent class instance. Real mock expectations will + * subclass this, and add their own application-specific fields with + * expected inputs and prepared outputs. + */ +typedef struct MOCK_EXPECT { + char *file; /* __FILE__ */ + int line; /* __LINE__ */ + int calls_expected; /* expected count */ + int calls_made; /* actual count */ + struct MOCK_EXPECT *next; /* linkage */ +} MOCK_EXPECT; + + /* + * Mock class signature and (subclass) methods. + */ +typedef int (*MOCK_EXPECT_MATCH_FN) (const MOCK_EXPECT *, const MOCK_EXPECT *); +typedef void (*MOCK_EXPECT_ASSIGN_FN) (const MOCK_EXPECT *, void *); +typedef char *(*MOCK_EXPECT_PRNT_FN) (const MOCK_EXPECT *, VSTRING *); +typedef void (*MOCK_EXPECT_FREE_FN) (MOCK_EXPECT *); + +typedef struct MOCK_APPL_SIG { + const char *name; /* application sans mock_ prefix */ + MOCK_EXPECT_MATCH_FN match_expect; /* match expectation inputs */ + MOCK_EXPECT_ASSIGN_FN assign_expect;/* assign expectation outputs */ + MOCK_EXPECT_PRNT_FN print_expect; /* print call or expectation */ + MOCK_EXPECT_FREE_FN free_expect; /* destruct expectation */ +} MOCK_APPL_SIG; + + /* + * Mock expectation constructor, called by expect_foo(). + */ +extern MOCK_EXPECT *pmock_expect_create(const MOCK_APPL_SIG *, const char *file, + int line, int calls_expected, + ssize_t); +extern int pmock_expect_apply(const MOCK_APPL_SIG *, const MOCK_EXPECT *, void *); +extern void pmock_expect_free(MOCK_EXPECT *); + + /* + * Report unused expectations and destroy all evidence and expectations. + */ +extern void pmock_expect_wrapup(PTEST_CTX *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/ptest/pmock_expect_test.c b/postfix/src/ptest/pmock_expect_test.c new file mode 100644 index 000000000..ef440dc3f --- /dev/null +++ b/postfix/src/ptest/pmock_expect_test.c @@ -0,0 +1,245 @@ + /* + * This file contains two parts. + * + * 1 - A trivial mock function, including code to set up expectations and to + * respond to calls. + * + * 2 - Test cases that exercise this mock function and the mock support + * infrastructure. + */ + + /* + * Part 1: This emulates a trivial function: + * + * int foo(const char *arg_in, char **arg_out) + * + * When the mock foo() function is called with an arg_in value that matches an + * expected input (see below) then the mock foo() function stores a prepared + * value through the arg_out argument, and returns a prepared function + * result value. + * + * The prepared response an result are set up with: + * + * void expect_foo(const char *file, int line, int calls_expected, int retval, + * const char *arg_in, const char *arg_out) + * + * This saves deep copies of arg_in and arg_out, and the result value in + * retval. The file name and line number are used to improve warning + * messages; typically these are specified at the call site with __FILE__ + * and __LINE__. + * + * The code below provides mock-specific helpers that match inputs against an + * expectation and that output prepared responses. These are called by the + * mock support infrastructure as needed. + */ + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include + + /* + * Test library. + */ +#include +#include +#include + + /* + * 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 diff --git a/postfix/src/ptest/ptest.h b/postfix/src/ptest/ptest.h new file mode 100644 index 000000000..c53cf688c --- /dev/null +++ b/postfix/src/ptest/ptest.h @@ -0,0 +1,147 @@ +#ifndef _PTEST_H_INCLUDED_ +#define _PTEST_H_INCLUDED_ + +/*++ +/* NAME +/* ptest 3h +/* SUMMARY +/* run-time test support +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Ptest library. + */ +#include + + /* + * TODO: factor out, and merge with DICT_JMP_BUF, MSG_JMP_BUF, + * SLMDB_JMP_BUF, VSTREAM_JMP_BUF. + */ +#ifdef NO_SIGSETJMP +#define TEST_JMP_BUF jmp_buf +#define ptest_longjmp(bufp, val) longjmp((bufp)[0], (val)) +#else +#define TEST_JMP_BUF sigjmp_buf +#define ptest_longjmp(bufp, val) siglongjmp((bufp)[0], (val)) +#endif + + /* + * All run-time test info in one place. + */ +typedef void (*PTEST_DEFER_FN) (void *); + +#define PTEST_CTX_FLAG_SKIP (1<<0) /* This test is skipped */ +#define PTEST_CTX_FLAG_FAIL (1<<1) /* This test has failed */ + +typedef struct PTEST_CTX { + /* ptest_ctx.c */ + char *name; /* Null, name, or name/name/... */ + TEST_JMP_BUF *jbuf; /* Used by ptest_fatal(), msg(3) */ + struct PTEST_CTX *parent; /* In case tests are nested */ + int flags; /* See above */ + /* ptest_run.c */ + int sub_pass; /* Subtests that passed */ + int sub_fail; /* Subtests that failed */ + int sub_skip; /* Subtests that were skipped */ + PTEST_DEFER_FN defer_fn; /* To be called after test... */ + void *defer_ctx; /* ...with this argument */ + /* ptest_error.c */ + VSTREAM *err_stream; /* Output stream */ + VSTRING *err_buf; /* Formatting buffer */ + ARGV *allow_errors; /* Allowed errors */ + /* ptest_log.c */ + VSTRING *log_buf; /* Formatting buffer */ + ARGV *allow_logs; /* Allowed logs */ +} PTEST_CTX; + + /* + * ptest_ctx.c + */ +extern PTEST_CTX *ptest_ctx_create(const char *, TEST_JMP_BUF *); +extern PTEST_CTX *ptest_ctx_current(void); +extern void ptest_ctx_free(PTEST_CTX *); + + /* + * ptest_error.c + */ +extern void ptest_error_setup(PTEST_CTX *, VSTREAM *); +extern void expect_ptest_error(PTEST_CTX *, const char *); +extern void PRINTFLIKE(2, 3) ptest_info(PTEST_CTX *, const char *,...); +extern void PRINTFLIKE(2, 3) ptest_error(PTEST_CTX *, const char *,...); +extern NORETURN PRINTFLIKE(2, 3) ptest_fatal(PTEST_CTX *, const char *,...); +extern int ptest_error_wrapup(PTEST_CTX *); + + /* + * ptest_log.c + */ +extern void ptest_log_setup(PTEST_CTX *); +extern void expect_ptest_log_event(PTEST_CTX *, const char *); +extern void ptest_log_wrapup(PTEST_CTX *); + + /* + * ptest_run.c + */ +extern void ptest_run_prolog(PTEST_CTX *); +extern void ptest_run_epilog(PTEST_CTX *, PTEST_CTX *); +extern NORETURN ptest_skip(PTEST_CTX *); +extern NORETURN ptest_return(PTEST_CTX *); +extern void ptest_defer(PTEST_CTX *, PTEST_DEFER_FN, void *); + +#define PTEST_RUN(t, _test_name, _body_in_braces) do { \ + MSG_JMP_BUF _new_buf; \ + PTEST_CTX *_parent = t; \ + t = ptest_ctx_create((_test_name), &_new_buf); \ + ptest_run_prolog(t); \ + if (msg_setjmp(&_new_buf) == 0) { \ + _body_in_braces \ + } \ + msg_resetjmp(_parent->jbuf); \ + ptest_run_epilog(t, _parent); \ + ptest_ctx_free(t); \ + t = _parent; \ +} while (0) + +#define PTEST_TRY(t, _body_in_braces) do { \ + MSG_JMP_BUF _new_buf; \ + if (msg_setjmp(&_new_buf) == 0) { \ + _body_in_braces \ + } \ + msg_resetjmp(t->jbuf); \ +} while (0) + + /* + * How many elements in a test case array. + */ +#define PTEST_NROF(x) (sizeof(x)/sizeof((x)[0])) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Wietse Venema +/* porcupine.org +/*--*/ + +#endif diff --git a/postfix/src/ptest/ptest_ctx.c b/postfix/src/ptest/ptest_ctx.c new file mode 100644 index 000000000..71b7c568a --- /dev/null +++ b/postfix/src/ptest/ptest_ctx.c @@ -0,0 +1,131 @@ +/*++ +/* NAME +/* ptest_ctx 3 +/* SUMMARY +/* test context support +/* SYNOPSIS +/* #include +/* +/* 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 +#include + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * Test library. + */ +#include + +static PTEST_CTX *ptest_ctx_head; + +/* ptest_ctx_create - create initialized PTEST_CTX object */ + +PTEST_CTX *ptest_ctx_create(const char *name, TEST_JMP_BUF *jbuf) +{ + PTEST_CTX *parent = ptest_ctx_head; + PTEST_CTX *t; + + t = mymalloc(sizeof(*t)); + if (name == 0) /* main-level context */ + t->name = 0; + else if (parent->name == 0) /* top-level test context */ + t->name = mystrdup(name); + else /* sub test */ + t->name = concatenate(parent->name, "/", name, (char *) 0); + t->jbuf = jbuf; + t->parent = parent; + t->flags = 0; + /* ptest_run.c specific */ + t->sub_pass = t->sub_fail = t->sub_skip = 0; + /* ptest_error.c specific */ + t->err_stream = 0; + t->err_buf = 0; + t->allow_errors = 0; + /* ptest_log.c specific */ + t->log_buf = 0; + t->allow_logs = 0; + /* ptest_defer.c specific */ + t->defer_fn = 0; + t->defer_ctx = 0; + + ptest_ctx_head = t; + + return (t); +} + +/* ptest_ctx_current - return current context or die */ + +PTEST_CTX *ptest_ctx_current() +{ + if (ptest_ctx_head == 0) + msg_panic("ptest_ctx_current: no test context"); + return (ptest_ctx_head); +} + +/* ptest_ctx_free - destroy PTEST_CTX or die */ + +void ptest_ctx_free(PTEST_CTX *t) +{ + if (t != ptest_ctx_head) + msg_panic("ptest_ctx_free: wrong test context - " + "should you use ptest_return()?"); + ptest_ctx_head = t->parent; + if (t->name) + myfree(t->name); + myfree((void *) t); +} diff --git a/postfix/src/ptest/ptest_error.c b/postfix/src/ptest/ptest_error.c new file mode 100644 index 000000000..f71e1230e --- /dev/null +++ b/postfix/src/ptest/ptest_error.c @@ -0,0 +1,222 @@ +/*++ +/* NAME +/* ptest_error 3 +/* SUMMARY +/* test error and non-error support +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Test library. + */ +#include + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* ptest_error_setup - populate PTEST_CTX object */ + +void ptest_error_setup(PTEST_CTX *t, VSTREAM *err_stream) +{ + t->flags &= ~PTEST_CTX_FLAG_FAIL; + t->err_stream = err_stream; + t->err_buf = vstring_alloc(100); + t->allow_errors = argv_alloc(1); +} + +/* expect_ptest_error - require and bless a non-fatal error */ + +void expect_ptest_error(PTEST_CTX *t, const char *text) +{ + argv_add(t->allow_errors, text, (char *) 0); +} + +/* ptest_info - report non-error condition */ + +void ptest_info(PTEST_CTX *t, const char *fmt,...) +{ + va_list ap; + + /* + * Format the message. + */ + va_start(ap, fmt); + vstream_vfprintf(t->err_stream, fmt, ap); + vstream_fprintf(t->err_stream, "\n"); + va_end(ap); + vstream_fflush(t->err_stream); +} + +/* ptest_error - report non-fatal error */ + +void ptest_error(PTEST_CTX *t, const char *fmt,...) +{ + va_list ap; + char **cpp; + + /* + * Format the message. + */ + va_start(ap, fmt); + vstring_vsprintf(t->err_buf, fmt, ap); + va_end(ap); + + /* + * Skip this error if it was expected. + */ + for (cpp = t->allow_errors->argv; *cpp; cpp++) { + if (strstr(STR(t->err_buf), *cpp) != 0) { + argv_delete(t->allow_errors, cpp - t->allow_errors->argv, 1); + return; + } + } + + /* + * Report the message. + */ + vstream_fprintf(t->err_stream, "error: %s\n", STR(t->err_buf)); + vstream_fflush(t->err_stream); + t->flags |= PTEST_CTX_FLAG_FAIL; +} + +/* ptest_fatal - report fatal error */ + +NORETURN ptest_fatal(PTEST_CTX *t, const char *fmt,...) +{ + va_list ap; + + /* + * This has no code in common with ptest_error(). + */ + vstream_fprintf(t->err_stream, "fatal: "); + va_start(ap, fmt); + vstream_vfprintf(t->err_stream, fmt, ap); + va_end(ap); + vstream_fprintf(t->err_stream, "\n"); + vstream_fflush(t->err_stream); + t->flags |= PTEST_CTX_FLAG_FAIL; + ptest_longjmp(t->jbuf, 1); +} + +/* ptest_error_wrapup - enforce error expectations and clean up */ + +int ptest_error_wrapup(PTEST_CTX *t) +{ + char **cpp; + int fail_flag; + + /* + * Report a new error if an expected error did not happen. + */ + for (cpp = t->allow_errors->argv; *cpp; cpp++) { + vstream_fprintf(t->err_stream, "Missing error: want '%s'\n", *cpp); + t->flags |= PTEST_CTX_FLAG_FAIL; + vstream_fflush(t->err_stream); + } + fail_flag = (t->flags & PTEST_CTX_FLAG_FAIL); + + /* + * Clean up the PTEST_CTX fields that we created. + */ + vstring_free(t->err_buf); + t->err_buf = 0; + argv_free(t->allow_errors); + t->allow_errors = 0; + t->flags &= ~PTEST_CTX_FLAG_FAIL; + return (fail_flag); +} diff --git a/postfix/src/ptest/ptest_log.c b/postfix/src/ptest/ptest_log.c new file mode 100644 index 000000000..c54112c98 --- /dev/null +++ b/postfix/src/ptest/ptest_log.c @@ -0,0 +1,148 @@ +/*++ +/* NAME +/* ptest_log 3 +/* SUMMARY +/* log event receiver support +/* SYNOPSIS +/* #include +/* +/* 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 +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Test library. + */ +#include + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* ptest_log_event - receive log event */ + +static void ptest_log_event(int level, const char *text, void *context) +{ + static const char *level_text[] = { + "info", "warning", "error", "fatal", "panic", + }; + PTEST_CTX *t = (PTEST_CTX *) context; + char **cpp; + + /* + * Silence events for parent handlers. + */ + if (t != ptest_ctx_current()) + return; + + /* + * Sanity checks. + */ + if (level < 0 || level >= (int) (sizeof(level_text) / sizeof(level_text[0]))) + msg_panic("ptest_log_event: invalid severity level: %d", level); + + /* + * Format the text. + */ + if (level == MSG_INFO) { + vstring_sprintf(t->log_buf, "%s", text); + } else { + vstring_sprintf(t->log_buf, "%s: %s", level_text[level], text); + } + + /* + * Handle expected versus unexpected text. + */ + for (cpp = t->allow_logs->argv; *cpp; cpp++) { + if (strstr(STR(t->log_buf), *cpp) != 0) { + argv_delete(t->allow_logs, cpp - t->allow_logs->argv, 1); + return; + } + } + ptest_error(t, "Unexpected log event: got '%s'", STR(t->log_buf)); +} + +/* ptest_log_setup - install logging receiver */ + +void ptest_log_setup(PTEST_CTX *t) +{ + if (t != ptest_ctx_current()) + msg_panic("ptest_log_setup: not current context"); + t->log_buf = vstring_alloc(100); + t->allow_logs = argv_alloc(1); + msg_output_push(ptest_log_event, (void *) t); +} + +/* expect_ptest_log_event - add log event expectation */ + +void expect_ptest_log_event(PTEST_CTX *t, const char *text) +{ + if (t != ptest_ctx_current()) + msg_panic("expect_ptest_log_event: not current context"); + argv_add(t->allow_logs, text, (char *) 0); +} + +/* ptest_log_wrapup - enforce logging expectations */ + +void ptest_log_wrapup(PTEST_CTX *t) +{ + char **cpp; + + msg_output_pop(ptest_log_event, (void *) t); + for (cpp = t->allow_logs->argv; *cpp; cpp++) + ptest_error(t, "Missing log event: want '%s'", *cpp); + vstring_free(t->log_buf); + t->log_buf = 0; + argv_free(t->allow_logs); + t->allow_logs = 0; +} diff --git a/postfix/src/ptest/ptest_log_test.c b/postfix/src/ptest/ptest_log_test.c new file mode 100644 index 000000000..275c700f1 --- /dev/null +++ b/postfix/src/ptest/ptest_log_test.c @@ -0,0 +1,78 @@ + /* + * Test program to exercise ptest_log functions including logging. See + * comments in ptest_main.h and pmock_expect_test.c for a documented + * example. + */ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include + + /* + * Ptest library. + */ +#include + +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 diff --git a/postfix/src/ptest/ptest_main.h b/postfix/src/ptest/ptest_main.h new file mode 100644 index 000000000..085a4345a --- /dev/null +++ b/postfix/src/ptest/ptest_main.h @@ -0,0 +1,179 @@ +/*++ +/* NAME +/* ptest_main 3h +/* SUMMARY +/* test driver +/* DESCRIPTION +/* This file should be included at the end of a *_test.c file. +/* It contains a main program and test driver, and supports +/* programs whether or not they use mocks as defined in +/* . +/* +/* 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 +/* #include +/* +/* /* +/* * 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 +/* +/* /* End example. */ +/* .fi +/* +/* The 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 +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * Test library. + */ +#include +#include + +/* main - test driver */ + +int main(int argc, char **argv) +{ + PTEST_CTX *t; + const PTEST_CASE *tp; + int fail; + +#ifndef DORANDOMIZE + + /* + * This must be set BEFORE the first hash table call. + */ + if (putenv("NORANDOMIZE=") != 0) + msg_fatal("putenv() failed: %m"); + + /* + * This is used by DNS client code. + */ + mysrand(0); +#endif + + /* + * Send msg(3) logging to stderr by default. + */ + msg_vstream_init(basename(argv[0]), VSTREAM_ERR); + + /* + * The main-level PTEST_CTX context has no name and no long jump context. + * It's sole purpose is to run tests and to aggregate pass/skip/fail + * counts. + */ + t = ptest_ctx_create((char *) 0, (MSG_JMP_BUF *) 0); + + /* + * Run each test in its own PTEST_CTX context with its own log + * interceptor and long jump context. Each test can invoke PTEST_RUN() to + * run one or more of subtests in their own context with their own test + * data, instead of having to store all test data in a PTEST_CASE + * structure. + */ + for (tp = ptestcases; tp < ptestcases + PTEST_NROF(ptestcases); tp++) { + if (tp->testname == 0) + msg_fatal("Null testname in ptestcases array!"); + PTEST_RUN(t, tp->testname, { + tp->action(t, tp); + }); + } + msg_info("PASS: %d, SKIP: %d, FAIL: %d", t->sub_pass, t->sub_skip, fail = t->sub_fail); + ptest_ctx_free(t); + exit(fail > 0); +} diff --git a/postfix/src/ptest/ptest_run.c b/postfix/src/ptest/ptest_run.c new file mode 100644 index 000000000..52622bcd0 --- /dev/null +++ b/postfix/src/ptest/ptest_run.c @@ -0,0 +1,159 @@ +/*++ +/* NAME +/* ptest_run 3h +/* SUMMARY +/* test runner +/* SYNOPSIS +/* #include +/* +/* 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 + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Test library. + */ +#include +#include + +/* 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; +} diff --git a/postfix/src/smtp/smtp_addr.c b/postfix/src/smtp/smtp_addr.c index 476ac87c4..5a0f6df14 100644 --- a/postfix/src/smtp/smtp_addr.c +++ b/postfix/src/smtp/smtp_addr.c @@ -31,7 +31,7 @@ /* lookups are done via the Internet domain name service (DNS). /* A reasonable number of CNAME indirections is permitted. When /* DNS lookups are disabled, host address lookup is done with -/* getnameinfo() or gethostbyname(). +/* myaddrinfo(3). /* /* smtp_domain_addr() looks up the network addresses for mail /* exchanger hosts listed for the named domain. Addresses are diff --git a/postfix/src/smtpd/Makefile.in b/postfix/src/smtpd/Makefile.in index 9ca64dc5e..7d130a822 100644 --- a/postfix/src/smtpd/Makefile.in +++ b/postfix/src/smtpd/Makefile.in @@ -10,7 +10,7 @@ OBJS = smtpd.o smtpd_token.o smtpd_check.o smtpd_chat.o smtpd_state.o \ HDRS = smtpd_token.h smtpd_check.h smtpd_chat.h smtpd_sasl_proto.h \ smtpd_sasl_glue.h smtpd_proxy.h smtpd_dsn_fix.h smtpd_milter.h \ smtpd_resolve.h smtpd_expand.h -TESTSRC = smtpd_token_test.c +TESTSRC = DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) TESTPROG= smtpd_token smtpd_check smtpd_peer_test diff --git a/postfix/src/testing/Makefile.in b/postfix/src/testing/Makefile.in index 4534831c4..3a77e6b34 100644 --- a/postfix/src/testing/Makefile.in +++ b/postfix/src/testing/Makefile.in @@ -1,11 +1,22 @@ SHELL = /bin/sh -SRCS = nosleep.c dict_test_helper.c mock_open_as.c mock_spawn_command.c \ +SRCS = mock_myaddrinfo.c mock_dns_lookup.c mock_servent.c \ + mock_getaddrinfo.c match_basic.c match_addr.c make_addr.c \ + addrinfo_to_string.c make_attr.c match_attr.c \ + nosleep.c dict_test_helper.c mock_open_as.c mock_spawn_command.c \ mock_stat.c msg_capture.c mock_dict.c -LIB_OBJ = dict_test_helper.o mock_open_as.o mock_spawn_command.o \ +LIB_OBJ = match_basic.o match_addr.o make_addr.o addrinfo_to_string.o \ + make_attr.o match_attr.o \ + dict_test_helper.o mock_open_as.o mock_spawn_command.o \ mock_stat.o msg_capture.o mock_dict.o -MOCK_OBJ= -TEST_OBJ= -HDRS = dict_test_helper.h mock_open_as.h mock_spawn_command.h \ +MOCK_OBJ= mock_myaddrinfo.o mock_dns_lookup.o mock_servent.o \ + mock_getaddrinfo.o mock_server.o +TEST_OBJ = mock_dns_lookup_test.o mock_getaddrinfo_test.o \ + mock_myaddrinfo_test.o mock_servent_test.o match_addr_test.o \ + mock_server_test.o match_attr_test.o match_basic.o +HDRS = mock_myaddrinfo.h mock_dns.h mock_servent.h mock_getaddrinfo.h \ + match_basic.h match_addr.h make_addr.h addrinfo_to_string.h \ + mock_server.h make_attr.h match_attr.h \ + dict_test_helper.h mock_open_as.h mock_spawn_command.h \ mock_stat.h msg_capture.h mock_dict.h TESTSRC = DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) @@ -13,9 +24,13 @@ CFLAGS = $(DEBUG) $(OPT) $(DEFS) INCL = LIB = libtesting.a LIB_SO = nosleep.so -TESTPROG= +TESTPROG= mock_dns_lookup_test mock_getaddrinfo_test \ + mock_myaddrinfo_test mock_servent_test match_addr_test \ + match_attr_test match_basic_test mock_server_test -LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ +LIBS = ../../lib/libptest.a \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) LIB_DIR = ../../lib INC_DIR = ../../include @@ -23,7 +38,7 @@ MAKES = .c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c -all: $(LIB_SO) $(LIB) $(MOCK_OBJ) +all: $(LIB_SO) $(LIB_OBJ) $(MOCK_OBJ) $(LIB_OBJ) $(MOCK_OBJ) $(TEST_OBJ): ../../conf/makedefs.out @@ -61,13 +76,62 @@ lib_update: $(LIB_DIR)/$(LIB) $(HDRS) $(MOCK_OBJ) cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \ done -tests: - clean: - rm -f $(LIB_SO) *.o + rm -f $(LIB_SO) *.o $(LIB) *core $(TESTPROG) junk $(MAKES) *.tmp tidy: clean +tests: update test_mock_dns_lookup test_mock_getaddrinfo test_mock_myaddrinfo \ + test_mock_servent test_match_addr test_match_basic test_mock_server + +mock_myaddrinfo_test: mock_myaddrinfo_test.o mock_myaddrinfo.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o mock_myaddrinfo.o $(LIB) $(LIBS) $(SYSLIBS) + +test_mock_myaddrinfo: update mock_myaddrinfo_test + $(SHLIB_ENV) ${VALGRIND} ./mock_myaddrinfo_test + +mock_dns_lookup_test: mock_dns_lookup_test.o mock_dns_lookup.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o mock_dns_lookup.o $(LIB) $(LIBS) $(SYSLIBS) + +test_mock_dns_lookup: update mock_dns_lookup_test + $(SHLIB_ENV) ${VALGRIND} ./mock_dns_lookup_test + +mock_servent_test: mock_servent_test.o mock_servent.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o mock_servent.o $(LIB) $(LIBS) $(SYSLIBS) + +test_mock_servent: update mock_servent_test + $(SHLIB_ENV) ${VALGRIND} ./mock_servent_test + +mock_getaddrinfo_test: mock_getaddrinfo_test.o mock_getaddrinfo.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o mock_getaddrinfo.o $(LIB) $(LIBS) $(SYSLIBS) + +test_mock_getaddrinfo: update mock_getaddrinfo_test + $(SHLIB_ENV) ${VALGRIND} ./mock_getaddrinfo_test + +match_addr_test: match_addr_test.o match_addr.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o match_addr.o $(LIB) $(LIBS) $(SYSLIBS) + +test_match_addr: update match_addr_test + $(SHLIB_ENV) ${VALGRIND} ./match_addr_test + +match_basic_test: match_basic_test.o match_basic.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o match_basic.o $(LIB) $(LIBS) $(SYSLIBS) + +test_match_basic: update match_basic_test + $(SHLIB_ENV) ${VALGRIND} ./match_basic_test + +mock_server_test: mock_server_test.o mock_server.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o mock_server.o $(LIB) $(LIBS) $(SYSLIBS) + +test_mock_server: update mock_server_test + $(SHLIB_ENV) ${VALGRIND} ./mock_server_test + +match_attr_test: match_attr_test.o match_attr.o $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.o match_attr.o $(LIB) $(LIBS) $(SYSLIBS) + +test_match_attr: update match_attr_test + $(SHLIB_ENV) ${VALGRIND} ./match_attr_test + depend: $(MAKES) (sed '1,/^# do not edit/!d' Makefile.in; \ set -e; for i in [a-z][a-z0-9]*.c; do \ @@ -78,6 +142,17 @@ depend: $(MAKES) @$(EXPORT) make -f Makefile.in Makefile 1>&2 # do not edit below this line - it is generated by 'make depend' +addrinfo_to_string.o: ../../include/check_arg.h +addrinfo_to_string.o: ../../include/msg.h +addrinfo_to_string.o: ../../include/myaddrinfo.h +addrinfo_to_string.o: ../../include/name_code.h +addrinfo_to_string.o: ../../include/name_mask.h +addrinfo_to_string.o: ../../include/sys_defs.h +addrinfo_to_string.o: ../../include/vbuf.h +addrinfo_to_string.o: ../../include/vstring.h +addrinfo_to_string.o: ../../include/wrap_netdb.h +addrinfo_to_string.o: addrinfo_to_string.c +addrinfo_to_string.o: addrinfo_to_string.h dict_test_helper.o: ../../include/argv.h dict_test_helper.o: ../../include/check_arg.h dict_test_helper.o: ../../include/dict.h @@ -90,6 +165,127 @@ dict_test_helper.o: ../../include/vstream.h dict_test_helper.o: ../../include/vstring.h dict_test_helper.o: dict_test_helper.c dict_test_helper.o: dict_test_helper.h +make_addr.o: ../../include/msg.h +make_addr.o: ../../include/mymalloc.h +make_addr.o: ../../include/sys_defs.h +make_addr.o: ../../include/wrap_netdb.h +make_addr.o: make_addr.c +make_addr.o: make_addr.h +make_attr.o: ../../include/argv.h +make_attr.o: ../../include/attr.h +make_attr.o: ../../include/check_arg.h +make_attr.o: ../../include/htable.h +make_attr.o: ../../include/msg.h +make_attr.o: ../../include/msg_jmp.h +make_attr.o: ../../include/mymalloc.h +make_attr.o: ../../include/nvtable.h +make_attr.o: ../../include/ptest.h +make_attr.o: ../../include/sys_defs.h +make_attr.o: ../../include/vbuf.h +make_attr.o: ../../include/vstream.h +make_attr.o: ../../include/vstring.h +make_attr.o: make_attr.c +make_attr.o: make_attr.h +match_addr.o: ../../include/argv.h +match_addr.o: ../../include/check_arg.h +match_addr.o: ../../include/msg.h +match_addr.o: ../../include/msg_jmp.h +match_addr.o: ../../include/ptest.h +match_addr.o: ../../include/sys_defs.h +match_addr.o: ../../include/vbuf.h +match_addr.o: ../../include/vstream.h +match_addr.o: ../../include/vstring.h +match_addr.o: ../../include/wrap_netdb.h +match_addr.o: addrinfo_to_string.h +match_addr.o: match_addr.c +match_addr.o: match_addr.h +match_addr.o: match_basic.h +match_addr_test.o: ../../include/argv.h +match_addr_test.o: ../../include/check_arg.h +match_addr_test.o: ../../include/msg.h +match_addr_test.o: ../../include/msg_jmp.h +match_addr_test.o: ../../include/msg_output.h +match_addr_test.o: ../../include/msg_vstream.h +match_addr_test.o: ../../include/myrand.h +match_addr_test.o: ../../include/pmock_expect.h +match_addr_test.o: ../../include/ptest.h +match_addr_test.o: ../../include/ptest_main.h +match_addr_test.o: ../../include/stringops.h +match_addr_test.o: ../../include/sys_defs.h +match_addr_test.o: ../../include/vbuf.h +match_addr_test.o: ../../include/vstream.h +match_addr_test.o: ../../include/vstring.h +match_addr_test.o: ../../include/wrap_netdb.h +match_addr_test.o: make_addr.h +match_addr_test.o: match_addr.h +match_addr_test.o: match_addr_test.c +match_attr.o: ../../include/argv.h +match_attr.o: ../../include/attr.h +match_attr.o: ../../include/check_arg.h +match_attr.o: ../../include/htable.h +match_attr.o: ../../include/msg.h +match_attr.o: ../../include/msg_jmp.h +match_attr.o: ../../include/mymalloc.h +match_attr.o: ../../include/nvtable.h +match_attr.o: ../../include/ptest.h +match_attr.o: ../../include/sys_defs.h +match_attr.o: ../../include/vbuf.h +match_attr.o: ../../include/vstream.h +match_attr.o: ../../include/vstring.h +match_attr.o: match_attr.c +match_attr.o: match_attr.h +match_attr_test.o: ../../include/argv.h +match_attr_test.o: ../../include/attr.h +match_attr_test.o: ../../include/check_arg.h +match_attr_test.o: ../../include/htable.h +match_attr_test.o: ../../include/msg.h +match_attr_test.o: ../../include/msg_jmp.h +match_attr_test.o: ../../include/msg_output.h +match_attr_test.o: ../../include/msg_vstream.h +match_attr_test.o: ../../include/mymalloc.h +match_attr_test.o: ../../include/myrand.h +match_attr_test.o: ../../include/nvtable.h +match_attr_test.o: ../../include/pmock_expect.h +match_attr_test.o: ../../include/ptest.h +match_attr_test.o: ../../include/ptest_main.h +match_attr_test.o: ../../include/stringops.h +match_attr_test.o: ../../include/sys_defs.h +match_attr_test.o: ../../include/vbuf.h +match_attr_test.o: ../../include/vstream.h +match_attr_test.o: ../../include/vstring.h +match_attr_test.o: make_attr.h +match_attr_test.o: match_attr.h +match_attr_test.o: match_attr_test.c +match_basic.o: ../../include/argv.h +match_basic.o: ../../include/check_arg.h +match_basic.o: ../../include/msg.h +match_basic.o: ../../include/msg_jmp.h +match_basic.o: ../../include/ptest.h +match_basic.o: ../../include/sys_defs.h +match_basic.o: ../../include/vbuf.h +match_basic.o: ../../include/vstream.h +match_basic.o: ../../include/vstring.h +match_basic.o: match_basic.c +match_basic.o: match_basic.h +match_basic_test.o: ../../include/argv.h +match_basic_test.o: ../../include/check_arg.h +match_basic_test.o: ../../include/msg.h +match_basic_test.o: ../../include/msg_jmp.h +match_basic_test.o: ../../include/msg_output.h +match_basic_test.o: ../../include/msg_vstream.h +match_basic_test.o: ../../include/myrand.h +match_basic_test.o: ../../include/name_code.h +match_basic_test.o: ../../include/name_mask.h +match_basic_test.o: ../../include/pmock_expect.h +match_basic_test.o: ../../include/ptest.h +match_basic_test.o: ../../include/ptest_main.h +match_basic_test.o: ../../include/stringops.h +match_basic_test.o: ../../include/sys_defs.h +match_basic_test.o: ../../include/vbuf.h +match_basic_test.o: ../../include/vstream.h +match_basic_test.o: ../../include/vstring.h +match_basic_test.o: match_basic.h +match_basic_test.o: match_basic_test.c mock_dict.o: ../../include/argv.h mock_dict.o: ../../include/check_arg.h mock_dict.o: ../../include/dict.h @@ -105,6 +301,129 @@ mock_dict.o: ../../include/vstream.h mock_dict.o: ../../include/vstring.h mock_dict.o: mock_dict.c mock_dict.o: mock_dict.h +mock_dns_lookup.o: ../../include/argv.h +mock_dns_lookup.o: ../../include/check_arg.h +mock_dns_lookup.o: ../../include/dns.h +mock_dns_lookup.o: ../../include/hex_code.h +mock_dns_lookup.o: ../../include/msg.h +mock_dns_lookup.o: ../../include/msg_jmp.h +mock_dns_lookup.o: ../../include/myaddrinfo.h +mock_dns_lookup.o: ../../include/mymalloc.h +mock_dns_lookup.o: ../../include/name_code.h +mock_dns_lookup.o: ../../include/pmock_expect.h +mock_dns_lookup.o: ../../include/ptest.h +mock_dns_lookup.o: ../../include/sock_addr.h +mock_dns_lookup.o: ../../include/stringops.h +mock_dns_lookup.o: ../../include/sys_defs.h +mock_dns_lookup.o: ../../include/vbuf.h +mock_dns_lookup.o: ../../include/vstream.h +mock_dns_lookup.o: ../../include/vstring.h +mock_dns_lookup.o: mock_dns.h +mock_dns_lookup.o: mock_dns_lookup.c +mock_dns_lookup_test.o: ../../include/argv.h +mock_dns_lookup_test.o: ../../include/check_arg.h +mock_dns_lookup_test.o: ../../include/dns.h +mock_dns_lookup_test.o: ../../include/msg.h +mock_dns_lookup_test.o: ../../include/msg_jmp.h +mock_dns_lookup_test.o: ../../include/msg_output.h +mock_dns_lookup_test.o: ../../include/msg_vstream.h +mock_dns_lookup_test.o: ../../include/myaddrinfo.h +mock_dns_lookup_test.o: ../../include/myrand.h +mock_dns_lookup_test.o: ../../include/pmock_expect.h +mock_dns_lookup_test.o: ../../include/ptest.h +mock_dns_lookup_test.o: ../../include/ptest_main.h +mock_dns_lookup_test.o: ../../include/sock_addr.h +mock_dns_lookup_test.o: ../../include/stringops.h +mock_dns_lookup_test.o: ../../include/sys_defs.h +mock_dns_lookup_test.o: ../../include/vbuf.h +mock_dns_lookup_test.o: ../../include/vstream.h +mock_dns_lookup_test.o: ../../include/vstring.h +mock_dns_lookup_test.o: mock_dns.h +mock_dns_lookup_test.o: mock_dns_lookup_test.c +mock_getaddrinfo.o: ../../include/argv.h +mock_getaddrinfo.o: ../../include/check_arg.h +mock_getaddrinfo.o: ../../include/msg.h +mock_getaddrinfo.o: ../../include/msg_jmp.h +mock_getaddrinfo.o: ../../include/myaddrinfo.h +mock_getaddrinfo.o: ../../include/mymalloc.h +mock_getaddrinfo.o: ../../include/pmock_expect.h +mock_getaddrinfo.o: ../../include/ptest.h +mock_getaddrinfo.o: ../../include/sys_defs.h +mock_getaddrinfo.o: ../../include/vbuf.h +mock_getaddrinfo.o: ../../include/vstream.h +mock_getaddrinfo.o: ../../include/vstring.h +mock_getaddrinfo.o: ../../include/wrap_netdb.h +mock_getaddrinfo.o: addrinfo_to_string.h +mock_getaddrinfo.o: make_addr.h +mock_getaddrinfo.o: match_addr.h +mock_getaddrinfo.o: match_basic.h +mock_getaddrinfo.o: mock_getaddrinfo.c +mock_getaddrinfo.o: mock_getaddrinfo.h +mock_getaddrinfo_test.o: ../../include/argv.h +mock_getaddrinfo_test.o: ../../include/check_arg.h +mock_getaddrinfo_test.o: ../../include/msg.h +mock_getaddrinfo_test.o: ../../include/msg_jmp.h +mock_getaddrinfo_test.o: ../../include/msg_output.h +mock_getaddrinfo_test.o: ../../include/msg_vstream.h +mock_getaddrinfo_test.o: ../../include/myaddrinfo.h +mock_getaddrinfo_test.o: ../../include/myrand.h +mock_getaddrinfo_test.o: ../../include/pmock_expect.h +mock_getaddrinfo_test.o: ../../include/ptest.h +mock_getaddrinfo_test.o: ../../include/ptest_main.h +mock_getaddrinfo_test.o: ../../include/stringops.h +mock_getaddrinfo_test.o: ../../include/sys_defs.h +mock_getaddrinfo_test.o: ../../include/vbuf.h +mock_getaddrinfo_test.o: ../../include/vstream.h +mock_getaddrinfo_test.o: ../../include/vstring.h +mock_getaddrinfo_test.o: ../../include/wrap_netdb.h +mock_getaddrinfo_test.o: addrinfo_to_string.h +mock_getaddrinfo_test.o: make_addr.h +mock_getaddrinfo_test.o: match_addr.h +mock_getaddrinfo_test.o: match_basic.h +mock_getaddrinfo_test.o: mock_getaddrinfo.h +mock_getaddrinfo_test.o: mock_getaddrinfo_test.c +mock_myaddrinfo.o: ../../include/argv.h +mock_myaddrinfo.o: ../../include/check_arg.h +mock_myaddrinfo.o: ../../include/msg.h +mock_myaddrinfo.o: ../../include/msg_jmp.h +mock_myaddrinfo.o: ../../include/myaddrinfo.h +mock_myaddrinfo.o: ../../include/mymalloc.h +mock_myaddrinfo.o: ../../include/pmock_expect.h +mock_myaddrinfo.o: ../../include/ptest.h +mock_myaddrinfo.o: ../../include/sys_defs.h +mock_myaddrinfo.o: ../../include/vbuf.h +mock_myaddrinfo.o: ../../include/vstream.h +mock_myaddrinfo.o: ../../include/vstring.h +mock_myaddrinfo.o: ../../include/wrap_netdb.h +mock_myaddrinfo.o: addrinfo_to_string.h +mock_myaddrinfo.o: make_addr.h +mock_myaddrinfo.o: match_addr.h +mock_myaddrinfo.o: match_basic.h +mock_myaddrinfo.o: mock_myaddrinfo.c +mock_myaddrinfo.o: mock_myaddrinfo.h +mock_myaddrinfo_test.o: ../../include/argv.h +mock_myaddrinfo_test.o: ../../include/check_arg.h +mock_myaddrinfo_test.o: ../../include/msg.h +mock_myaddrinfo_test.o: ../../include/msg_jmp.h +mock_myaddrinfo_test.o: ../../include/msg_output.h +mock_myaddrinfo_test.o: ../../include/msg_vstream.h +mock_myaddrinfo_test.o: ../../include/myaddrinfo.h +mock_myaddrinfo_test.o: ../../include/myrand.h +mock_myaddrinfo_test.o: ../../include/pmock_expect.h +mock_myaddrinfo_test.o: ../../include/ptest.h +mock_myaddrinfo_test.o: ../../include/ptest_main.h +mock_myaddrinfo_test.o: ../../include/stringops.h +mock_myaddrinfo_test.o: ../../include/sys_defs.h +mock_myaddrinfo_test.o: ../../include/vbuf.h +mock_myaddrinfo_test.o: ../../include/vstream.h +mock_myaddrinfo_test.o: ../../include/vstring.h +mock_myaddrinfo_test.o: ../../include/wrap_netdb.h +mock_myaddrinfo_test.o: addrinfo_to_string.h +mock_myaddrinfo_test.o: make_addr.h +mock_myaddrinfo_test.o: match_addr.h +mock_myaddrinfo_test.o: match_basic.h +mock_myaddrinfo_test.o: mock_myaddrinfo.h +mock_myaddrinfo_test.o: mock_myaddrinfo_test.c mock_open_as.o: ../../include/check_arg.h mock_open_as.o: ../../include/msg.h mock_open_as.o: ../../include/open_as.h @@ -114,6 +433,80 @@ mock_open_as.o: ../../include/vstream.h mock_open_as.o: ../../include/vstring.h mock_open_as.o: mock_open_as.c mock_open_as.o: mock_open_as.h +mock_servent.o: ../../include/argv.h +mock_servent.o: ../../include/check_arg.h +mock_servent.o: ../../include/msg.h +mock_servent.o: ../../include/msg_jmp.h +mock_servent.o: ../../include/mymalloc.h +mock_servent.o: ../../include/pmock_expect.h +mock_servent.o: ../../include/ptest.h +mock_servent.o: ../../include/sys_defs.h +mock_servent.o: ../../include/vbuf.h +mock_servent.o: ../../include/vstream.h +mock_servent.o: ../../include/vstring.h +mock_servent.o: ../../include/wrap_netdb.h +mock_servent.o: mock_servent.c +mock_servent.o: mock_servent.h +mock_servent_test.o: ../../include/argv.h +mock_servent_test.o: ../../include/check_arg.h +mock_servent_test.o: ../../include/msg.h +mock_servent_test.o: ../../include/msg_jmp.h +mock_servent_test.o: ../../include/msg_output.h +mock_servent_test.o: ../../include/msg_vstream.h +mock_servent_test.o: ../../include/myrand.h +mock_servent_test.o: ../../include/pmock_expect.h +mock_servent_test.o: ../../include/ptest.h +mock_servent_test.o: ../../include/ptest_main.h +mock_servent_test.o: ../../include/stringops.h +mock_servent_test.o: ../../include/sys_defs.h +mock_servent_test.o: ../../include/vbuf.h +mock_servent_test.o: ../../include/vstream.h +mock_servent_test.o: ../../include/vstring.h +mock_servent_test.o: ../../include/wrap_netdb.h +mock_servent_test.o: mock_servent.h +mock_servent_test.o: mock_servent_test.c +mock_server.o: ../../include/argv.h +mock_server.o: ../../include/check_arg.h +mock_server.o: ../../include/connect.h +mock_server.o: ../../include/events.h +mock_server.o: ../../include/iostuff.h +mock_server.o: ../../include/msg.h +mock_server.o: ../../include/msg_jmp.h +mock_server.o: ../../include/mymalloc.h +mock_server.o: ../../include/ptest.h +mock_server.o: ../../include/sys_defs.h +mock_server.o: ../../include/vbuf.h +mock_server.o: ../../include/vstream.h +mock_server.o: ../../include/vstring.h +mock_server.o: match_attr.h +mock_server.o: mock_server.c +mock_server.o: mock_server.h +mock_server_test.o: ../../include/argv.h +mock_server_test.o: ../../include/attr.h +mock_server_test.o: ../../include/check_arg.h +mock_server_test.o: ../../include/connect.h +mock_server_test.o: ../../include/events.h +mock_server_test.o: ../../include/htable.h +mock_server_test.o: ../../include/iostuff.h +mock_server_test.o: ../../include/mail_proto.h +mock_server_test.o: ../../include/msg.h +mock_server_test.o: ../../include/msg_jmp.h +mock_server_test.o: ../../include/msg_output.h +mock_server_test.o: ../../include/msg_vstream.h +mock_server_test.o: ../../include/mymalloc.h +mock_server_test.o: ../../include/myrand.h +mock_server_test.o: ../../include/nvtable.h +mock_server_test.o: ../../include/pmock_expect.h +mock_server_test.o: ../../include/ptest.h +mock_server_test.o: ../../include/ptest_main.h +mock_server_test.o: ../../include/stringops.h +mock_server_test.o: ../../include/sys_defs.h +mock_server_test.o: ../../include/vbuf.h +mock_server_test.o: ../../include/vstream.h +mock_server_test.o: ../../include/vstring.h +mock_server_test.o: make_attr.h +mock_server_test.o: mock_server.h +mock_server_test.o: mock_server_test.c mock_spawn_command.o: ../../include/check_arg.h mock_spawn_command.o: ../../include/msg.h mock_spawn_command.o: ../../include/spawn_command.h diff --git a/postfix/src/testing/addrinfo_to_string.c b/postfix/src/testing/addrinfo_to_string.c new file mode 100644 index 000000000..c08d75a19 --- /dev/null +++ b/postfix/src/testing/addrinfo_to_string.c @@ -0,0 +1,304 @@ +/*++ +/* NAME +/* addrinfo_to_string 3 +/* SUMMARY +/* address info to string conversion +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include +#include +#include +#include +#include /* sprintf/snprintf */ + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * Test library. + */ +#include + +#define STR_OR_NULL(s) ((s) ? (s) : "(null)") + +#ifdef NO_SNPRINTF +#define MY_SNPRINTF(bp, sz, fmt, ...) \ + sprintf((bp), (fmt), __VA_ARGS__) +#else +#define MY_SNPRINTF(bp, sz, fmt, ...) \ + snprintf((bp), (sz), (fmt), __VA_ARGS__) +#endif + +#define STR vstring_str + +/* pf_to_string - convert protocol family to human-readable form */ + +const char *pf_to_string(int af) +{ + static const NAME_CODE pf_codes[] = { + "PF_INET", PF_INET, + "PF_INET6", PF_INET6, + 0, + }; + const char *name; + + name = str_name_code(pf_codes, af); + return (name ? name : "unknown-protocol-family"); +} + +/* af_to_string - convert address family to human-readable form */ + +const char *af_to_string(int af) +{ + static const NAME_CODE af_codes[] = { + "AF_INET", AF_INET, + "AF_INET6", AF_INET6, + 0, + }; + const char *name; + + name = str_name_code(af_codes, af); + return (name ? name : "unknown-address-family"); +} + +/* socktype_to_string - convert socket type to human-readable form */ + +const char *socktype_to_string(int socktype) +{ + static const NAME_CODE socktypes[] = { + "SOCK_STREAM", SOCK_STREAM, + "SOCK_DGRAM", SOCK_DGRAM, + "SOCK_RAW", SOCK_RAW, + "0", 0, + 0, + }; + const char *name; + + name = str_name_code(socktypes, socktype); + return (name ? name : "unknown-socket-type"); +} + +/* ipprotocol_to_string - convert protocol to human-readable form */ + +const char *ipprotocol_to_string(int proto) +{ + static const NAME_CODE protocols[] = { + "IPPROTO_UDP", IPPROTO_UDP, + "IPPROTO_TCP", IPPROTO_TCP, + "0", 0, + 0, + }; + const char *name; + + name = str_name_code(protocols, proto); + return (name ? name : "unknown-protocol"); +} + +/* ai_flags_to_string - convert addrinfo flags to human-readable form */ + +const char *ai_flags_to_string(VSTRING *buf, int flags) +{ + static const NAME_MASK ai_flags[] = { +#ifdef AI_IDN + "AI_IDN", AI_IDN, +#endif +#ifdef AI_CANONIDN + "AI_CANONIDN", AI_CANONIDN, +#endif +#ifdef AI_IDN_ALLOW_UNASSIGNED + "AI_IDN_ALLOW_UNASSIGNED", AI_IDN_ALLOW_UNASSIGNED, +#endif +#ifdef AI_IDN_USE_STD3_ASCII_RULES + "AI_IDN_USE_STD3_ASCII_RULES", AI_IDN_USE_STD3_ASCII_RULES, +#endif + "AI_ADDRCONFIG", AI_ADDRCONFIG, + "AI_CANONNAME", AI_CANONNAME, + "AI_NUMERICHOST", AI_NUMERICHOST, + "AI_NUMERICSERV", AI_NUMERICSERV, + "AI_PASSIVE", AI_PASSIVE, + 0, + }; + + return (str_name_mask_opt(buf, "ai_flags_to_string", ai_flags, flags, + NAME_MASK_NUMBER | NAME_MASK_PIPE | NAME_MASK_NULL)); +} + +/* ni_flags_to_string - convert getnameinfo flags to human-readable form */ + +const char *ni_flags_to_string(VSTRING *buf, int flags) +{ + static const NAME_MASK ni_flags[] = { + "NI_NAMEREQD", NI_NAMEREQD, + "NI_DGRAM", NI_DGRAM, + "NI_NOFQDN", NI_NOFQDN, + "NI_NUMERICHOST", NI_NUMERICHOST, + "NI_NUMERICSERV", NI_NUMERICSERV, + 0, + }; + + return (str_name_mask_opt(buf, "ni_flags_to_string", ni_flags, flags, + NAME_MASK_NUMBER | NAME_MASK_PIPE | NAME_MASK_NULL)); +} + +/* append_addrinfo_to_string - print human-readable addrinfo */ + +char *append_addrinfo_to_string(VSTRING *buf, const struct addrinfo *ai) +{ + if (ai == 0) { + vstring_sprintf_append(buf, "(null)"); + } else { + VSTRING *sockaddr_buf = vstring_alloc(100); + VSTRING *flags_buf = vstring_alloc(100); + + vstring_sprintf_append(buf, "{%s, %s, %s, %s, %d, %s, %s}", + ai_flags_to_string(flags_buf, ai->ai_flags), + pf_to_string(ai->ai_family), + socktype_to_string(ai->ai_socktype), + ipprotocol_to_string(ai->ai_protocol), + ai->ai_addrlen, + sockaddr_to_string(sockaddr_buf, ai->ai_addr, + ai->ai_addrlen), + STR_OR_NULL(ai->ai_canonname)); + vstring_free(sockaddr_buf); + vstring_free(flags_buf); + } + return (STR(buf)); +} + +/* addrinfo_hints_to_string - append human-readable hints */ + +char *addrinfo_hints_to_string(VSTRING *buf, const struct addrinfo *ai) +{ + if (ai == 0) { + vstring_sprintf(buf, "(null)"); + } else { + VSTRING *flags_buf = vstring_alloc(100); + + vstring_sprintf(buf, "{%s, %s, %s, %s}", + ai_flags_to_string(flags_buf, ai->ai_flags), + pf_to_string(ai->ai_family), + socktype_to_string(ai->ai_socktype), + ipprotocol_to_string(ai->ai_protocol)); + vstring_free(flags_buf); + } + return (STR(buf)); +} + +/* sockaddr_to_strings - convert to printable host address and port */ + +static void sockaddr_to_strings(const struct sockaddr *sa, size_t salen, + MAI_HOSTADDR_STR *hostaddr, + MAI_SERVPORT_STR *portnum) +{ + if (sa->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *) sa; + + if (salen < sizeof(*sin)) + msg_fatal("sockaddr_to_strings: bad sockaddr length %ld", + (long) salen); + MY_SNPRINTF(portnum->buf, sizeof(*portnum), "%d", ntohs(sin->sin_port)); + if (inet_ntop(AF_INET, &sin->sin_addr, hostaddr->buf, + sizeof(*hostaddr)) == 0) + msg_fatal("inet_ntop(AF_INET,...): %m"); +#ifdef HAS_IPV6 + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; + + if (salen < sizeof(*sin6)) + msg_fatal("sockaddr_to_strings: bad sockaddr length %ld", + (long) salen); + MY_SNPRINTF(portnum->buf, sizeof(*portnum), "%d", + ntohs(sin6->sin6_port)); + if (inet_ntop(AF_INET6, &sin6->sin6_addr, hostaddr->buf, + sizeof(*hostaddr)) == 0) + msg_fatal("inet_ntop(AF_INET,...): %m"); +#endif + } else { + errno = EAFNOSUPPORT; + msg_panic("sockaddr_to_strings: protocol familiy %d: %m", + sa->sa_family); + } +} + +/* sockaddr_to_string - render human-readable sockaddr */ + +char *sockaddr_to_string(VSTRING *buf, const struct sockaddr *sa, + size_t salen) +{ + MAI_HOSTADDR_STR hostaddr; + MAI_SERVPORT_STR portnum; + + sockaddr_to_strings(sa, salen, &hostaddr, &portnum); + vstring_sprintf(buf, "{%s, %s, %s}", + af_to_string(sa->sa_family), + hostaddr.buf, portnum.buf); + return (STR(buf)); +} diff --git a/postfix/src/testing/addrinfo_to_string.h b/postfix/src/testing/addrinfo_to_string.h new file mode 100644 index 000000000..758cc2ddf --- /dev/null +++ b/postfix/src/testing/addrinfo_to_string.h @@ -0,0 +1,49 @@ +#ifndef _ADDRINFO_TO_STRING_H_INCLUDED_ +#define _ADDRINFO_TO_STRING_H_INCLUDED_ + +/*++ +/* NAME +/* addrinfo_to_string 3h +/* SUMMARY +/* address info to string conversion +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern const char *pf_to_string(int); +extern const char *af_to_string(int); +extern const char *socktype_to_string(int); +extern const char *ipprotocol_to_string(int); +extern const char *ai_flags_to_string(VSTRING *, int); +extern const char *ni_flags_to_string(VSTRING *, int); +extern char *append_addrinfo_to_string(VSTRING *, const struct addrinfo *); +extern char *addrinfo_hints_to_string(VSTRING *, const struct addrinfo *); +extern char *sockaddr_to_string(VSTRING *, const struct sockaddr *, size_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/testing/make_addr.c b/postfix/src/testing/make_addr.c new file mode 100644 index 000000000..8b6fcfe21 --- /dev/null +++ b/postfix/src/testing/make_addr.c @@ -0,0 +1,208 @@ +/*++ +/* NAME +/* make_addr 3 +/* SUMMARY +/* make_addrinfo(), freeaddrinfo(), make_sockaddr() for hermetic tests +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Test library. + */ +#include + +#define MYSTRDUP_OR_NULL(x) ((x) ? mystrdup(x) : 0) + +/* make_sockaddr - helper to create mock sockaddr instances */ + +struct sockaddr *make_sockaddr(int family, const char *addr, int port) +{ + if (family == AF_INET) { + struct sockaddr_in *sa; + + sa = (struct sockaddr_in *) mymalloc(sizeof(*sa)); + memset(sa, 0, sizeof(*sa)); + switch (inet_pton(AF_INET, addr, (void *) &sa->sin_addr)) { + case 1: + break; + case 0: + msg_fatal("bad address syntax: '%s'", addr); + case -1: + msg_fatal("inet_pton(AF_INET, %s, (ptr)): %m", addr); + } + sa->sin_family = AF_INET; + sa->sin_port = htons(port); + return ((struct sockaddr *) sa); +#ifdef HAS_IPV6 + } else if (family == AF_INET6) { + struct sockaddr_in6 *sa; + + sa = (struct sockaddr_in6 *) mymalloc(sizeof(*sa)); + memset(sa, 0, sizeof(*sa)); + switch (inet_pton(AF_INET6, addr, (void *) &sa->sin6_addr)) { + case 1: + break; + case 0: + msg_fatal("bad address syntax: '%s'", addr); + case -1: + msg_fatal("inet_pton(AF_INET6, %s, (ptr)): %m", addr); + } + sa->sin6_family = AF_INET6; + sa->sin6_port = htons(port); + return ((struct sockaddr *) sa); +#endif + } else { + errno = EAFNOSUPPORT; + msg_panic("make_sockaddr: address familiy %d: %m", family); + } +} + +/* free_sockaddr - destructor for make_sockaddr() and copy_addrinfo() */ + +void free_sockaddr(struct sockaddr *sa) +{ + myfree(sa); +} + +/* copy_addrinfo - expectation helper */ + +struct addrinfo *copy_addrinfo(const struct addrinfo *in) +{ + struct addrinfo *out; + + /* + * First a shallow copy, followed by deep copies for pointer fields. + */ + if (in == 0) + return (0); + out = (struct addrinfo *) mymalloc(sizeof(*out)); + *out = *in; + if (in->ai_addr != 0) { + out->ai_addr = (struct sockaddr *) mymalloc(in->ai_addrlen); + memcpy(out->ai_addr, in->ai_addr, in->ai_addrlen); + } + if (in->ai_canonname != 0) + out->ai_canonname = mystrdup(in->ai_canonname); + if (in->ai_next) + out->ai_next = copy_addrinfo(in->ai_next); + return (out); +} + +/* make_addrinfo - helper to create mock addrinfo input or result */ + +struct addrinfo *make_addrinfo(const struct addrinfo *hints, + const char *name, const char *addr, + int port) +{ + struct addrinfo *out; + + out = (struct addrinfo *) mymalloc(sizeof(*out)); + memset(out, 0, sizeof(*out)); + out->ai_canonname = MYSTRDUP_OR_NULL(name); + switch (hints->ai_family) { + case PF_INET6: + out->ai_addr = make_sockaddr(AF_INET6, addr, port); + out->ai_addrlen = sizeof(struct sockaddr_in6); + out->ai_family = hints->ai_family; + out->ai_socktype = hints->ai_socktype; + out->ai_protocol = hints->ai_protocol; + break; + case PF_INET: + out->ai_addr = make_sockaddr(AF_INET, addr, port); + out->ai_addrlen = sizeof(struct sockaddr_in); + out->ai_family = hints->ai_family; + out->ai_socktype = hints->ai_socktype; + out->ai_protocol = hints->ai_protocol; + break; + default: + msg_fatal("make_addrinfo: hints->ai_family: %d", hints->ai_family); + } + out->ai_next = 0; + return (out); +} + +/* freeaddrinfo - free the mock-generated addrinfo structures */ + +void freeaddrinfo(struct addrinfo *res) +{ + if (res) { + if (res->ai_next) + freeaddrinfo(res->ai_next); + if (res->ai_addr) + myfree(res->ai_addr); + if (res->ai_canonname) + myfree(res->ai_canonname); + myfree(res); + } +} diff --git a/postfix/src/testing/make_addr.h b/postfix/src/testing/make_addr.h new file mode 100644 index 000000000..ebe4a6df8 --- /dev/null +++ b/postfix/src/testing/make_addr.h @@ -0,0 +1,40 @@ +#ifndef _MAKE_ADDR_H_INCLUDED_ +#define _MAKE_ADDR_H_INCLUDED_ + +/*++ +/* NAME +/* make_addr 3h +/* SUMMARY +/* make_addrinfo(), freeaddrinfo(), make_sockaddr() for hermetic tests +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * External interface. + */ +extern struct addrinfo *make_addrinfo(const struct addrinfo *, const char *, + const char *, int); +extern struct addrinfo *copy_addrinfo(const struct addrinfo *); +extern struct sockaddr *make_sockaddr(int, const char *, int); +extern void free_sockaddr(struct sockaddr *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/testing/make_attr.c b/postfix/src/testing/make_attr.c new file mode 100644 index 000000000..1fd944b76 --- /dev/null +++ b/postfix/src/testing/make_attr.c @@ -0,0 +1,60 @@ +/*++ +/* NAME +/* make_attr 3 +/* SUMMARY +/* create serialized attribute request or response +/* SYNOPSIS +/* #include +/* +/* 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 +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Test library. + */ +#include +#include + +/* make_attr - serialize attribute list */ + +VSTRING *make_attr(int flags,...) +{ + static const char myname[] = "make_attr"; + VSTRING *res = vstring_alloc(100); + VSTREAM *stream; + va_list ap; + int err; + + if ((stream = vstream_memopen(res, O_WRONLY)) == 0) + ptest_fatal(ptest_ctx_current(), "%s: vstream_memopen: %m", myname);; + va_start(ap, flags); + err = attr_vprint(stream, flags, ap); + va_end(ap); + if (vstream_fclose(stream) != 0 || err) + ptest_fatal(ptest_ctx_current(), "%s: write attributes: %m", myname); + return (res); +} diff --git a/postfix/src/testing/make_attr.h b/postfix/src/testing/make_attr.h new file mode 100644 index 000000000..97659d901 --- /dev/null +++ b/postfix/src/testing/make_attr.h @@ -0,0 +1,35 @@ +#ifndef _MAKE_ATTR_H_INCLUDED_ +#define _MAKE_ATTR_H_INCLUDED_ + +/*++ +/* NAME +/* make_attr 3h +/* SUMMARY +/* create serialized attributes +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern VSTRING *make_attr(int,...); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/testing/match_addr.c b/postfix/src/testing/match_addr.c new file mode 100644 index 000000000..bce07b7ef --- /dev/null +++ b/postfix/src/testing/match_addr.c @@ -0,0 +1,142 @@ +/*++ +/* NAME +/* match_addr 3 +/* SUMMARY +/* matchers for network address information +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include +#include +#include + + /* + * Test library. + */ +#include +#include +#include +#include + +#define STR vstring_str + +/* _eq_addrinfo - match struct addrinfo instances */ + +int _eq_addrinfo(const char *file, int line, + PTEST_CTX *t, const char *what, + const struct addrinfo *got, + const struct addrinfo *want) +{ + if (got == 0 && want == 0) + return (1); + if (got == 0 || want == 0) { + if (t) { + VSTRING *buf = vstring_alloc(100); + + ptest_error(t, "%s:%d %s: got %s, want %s", + file, line, what, got ? + append_addrinfo_to_string(buf, got) : "(null)", + want ? + append_addrinfo_to_string(buf, want) : "(null)"); + vstring_free(buf); + } + return (0); + } + return (_eq_flags(file, line, t, "ai_flags", got->ai_flags, want->ai_flags, + ai_flags_to_string) + && _eq_enum(file, line, t, "ai_family", got->ai_family, + want->ai_family, af_to_string) + && _eq_enum(file, line, t, "ai_socktype", got->ai_socktype, + want->ai_socktype, socktype_to_string) + && _eq_enum(file, line, t, "ai_protocol", + got->ai_protocol, want->ai_protocol, + ipprotocol_to_string) + && _eq_size_t(file, line, t, "ai_addrlen", + got->ai_addrlen, want->ai_addrlen) + && _eq_sockaddr(file, line, t, "ai_addr", got->ai_addr, + got->ai_addrlen, want->ai_addr, want->ai_addrlen) + && _eq_addrinfo(file, line, t, what, got->ai_next, + want->ai_next)); +} + +/* _eq_sockaddr - sockaddr matcher */ + +int _eq_sockaddr(const char *file, int line, + PTEST_CTX *t, const char *what, + const struct sockaddr *got, size_t gotlen, + const struct sockaddr *want, size_t wantlen) +{ + if (got == 0 && want == 0) + return (1); + if (got == 0 || want == 0) { + if (t) { + VSTRING *got_buf = vstring_alloc(100); + VSTRING *want_buf = vstring_alloc(100); + + ptest_error(t, "%s:%d %s: got %s, want %s", + file, line, what, + got ? sockaddr_to_string(got_buf, got, gotlen) + : "(null)", + want ? sockaddr_to_string(want_buf, want, wantlen) + : "(null)"); + vstring_free(want_buf); + vstring_free(got_buf); + } + return (0); + } + if (!_eq_enum(file, line, (PTEST_CTX *) 0, "struct sockaddr address family", + got->sa_family, want->sa_family, af_to_string) + || !_eq_size_t(file, line, (PTEST_CTX *) 0, "struct sockaddr length", + gotlen, wantlen) + || memcmp(got, want, gotlen) != 0) { + VSTRING *got_buf = vstring_alloc(100); + VSTRING *want_buf = vstring_alloc(100); + + ptest_error(t, "%s:%d %s: got %s, want %s", + file, line, what, + sockaddr_to_string(got_buf, got, gotlen), + sockaddr_to_string(want_buf, want, wantlen)); + vstring_free(want_buf); + vstring_free(got_buf); + return (0); + } + return (1); +} diff --git a/postfix/src/testing/match_addr.h b/postfix/src/testing/match_addr.h new file mode 100644 index 000000000..b53978a30 --- /dev/null +++ b/postfix/src/testing/match_addr.h @@ -0,0 +1,53 @@ +#ifndef _MATCH_ADDR_H_INCLUDED_ +#define _MATCH_ADDR_H_INCLUDED_ + +/*++ +/* NAME +/* match_addr 3h +/* SUMMARY +/* network address matcher +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include +#include +#include + + /* + * Test library. + */ +#include + + /* + * Matchers. + */ +#define eq_addrinfo(...) _eq_addrinfo(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_addrinfo(const char *, int, PTEST_CTX *, const char *, + const struct addrinfo *, + const struct addrinfo *); + +#define eq_sockaddr(...) _eq_sockaddr(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_sockaddr(const char *, int, PTEST_CTX *, const char *, + const struct sockaddr *, size_t, + const struct sockaddr *, size_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/testing/match_addr_test.c b/postfix/src/testing/match_addr_test.c new file mode 100644 index 000000000..12f8b5c47 --- /dev/null +++ b/postfix/src/testing/match_addr_test.c @@ -0,0 +1,163 @@ + /* + * Test program to exercise make_addr functions including logging. See + * comments in ptest_main.h and pmock_expect_test.c for a documented + * example. + */ + + /* + * System library. + */ +#include +#include +#include +#include +#include + + /* + * Test library. + */ +#include +#include +#include + +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 diff --git a/postfix/src/testing/match_attr.c b/postfix/src/testing/match_attr.c new file mode 100644 index 000000000..5829263a5 --- /dev/null +++ b/postfix/src/testing/match_attr.c @@ -0,0 +1,160 @@ +/*++ +/* NAME +/* match_attr 3 +/* SUMMARY +/* matchers for network address information +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Test library. + */ +#include +#include + +/* compar -qsort callback */ + +static int compar(const void *a, const void *b) +{ + return (strcmp((*(HTABLE_INFO **) a)->key, (*(HTABLE_INFO **) b)->key)); +} + +/* _eq_attr - match serialized attributes */ + +int _eq_attr(const char *file, int line, PTEST_CTX *t, + const char *what, VSTRING *got_buf, VSTRING *want_buf) +{ + static const char myname[] = "eq_attr"; + HTABLE *got_hash; + HTABLE *want_hash; + int count; + VSTREAM *mp; + HTABLE_INFO **ht_list; + HTABLE_INFO **ht; + char *ht_value; + + if (VSTRING_LEN(got_buf) == VSTRING_LEN(want_buf) + && memcmp(vstring_str(got_buf), vstring_str(want_buf), + VSTRING_LEN(got_buf)) == 0) + return (1); + + if (t != 0) { + + /* + * Deserialize the actual attributes into a hash. This loses order + * information. TODO(wietse) maybe preserve order in two parallel + * ARGV objects, and use _eq_argv() under the covers. + */ + got_hash = htable_create(13); + if ((mp = vstream_memopen(got_buf, O_RDONLY)) == 0) + ptest_fatal(t, "%s: vstream_memopen: %m", myname); + count = attr_scan(mp, ATTR_FLAG_NONE, + ATTR_TYPE_HASH, got_hash, + ATTR_TYPE_END); + if (vstream_fclose(mp) != 0 || count <= 0) + ptest_fatal(t, "%s: vstream_fclose: %m", myname); + + /* + * Deserialize the wanted attributes into a hash. This loses order + * information. + */ + want_hash = htable_create(13); + if ((mp = vstream_memopen(want_buf, O_RDONLY)) == 0) + ptest_fatal(t, "%s: vstream_memopen: %m", myname); + count = attr_scan(mp, ATTR_FLAG_NONE, + ATTR_TYPE_HASH, want_hash, + ATTR_TYPE_END); + if (vstream_fclose(mp) != 0 || count <= 0) + ptest_fatal(t, "%s: vstream_fclose: %m", myname); + + /* + * Delete the intersection of the deserialized attribute lists. + */ + ht_list = htable_list(got_hash); + for (ht = ht_list; *ht; ht++) { + if ((ht_value = htable_find(want_hash, ht[0]->key)) != 0 + && strcmp(ht_value, ht[0]->value) == 0) { + htable_delete(want_hash, ht[0]->key, myfree); + /* At this point, ht_value is a dangling pointer. */ + htable_delete(got_hash, ht[0]->key, myfree); + /* At this point, ht is a dangling pointer. */ + } + } + myfree(ht_list); + + /* + * If the attributes differ only in order, then say so. We have no + * order information. This should never happen with real requests and + * responses. + */ + if (got_hash->used == 0 && want_hash->used == 0) { + ptest_error(t, "%s: attribute order differs", what); + } + + /* + * List differences in name or value. + */ + else { + ptest_error(t, "%s: attributes differ, +got/-want follows", what); + + /* + * Enumerate the unique attributes. + */ + ht_list = htable_list(got_hash); + qsort(ht_list, got_hash->used, sizeof(*ht_list), compar); + for (ht = ht_list; *ht; ht++) + ptest_error(t, "+%s = %s", ht[0]->key, (char *) ht[0]->value); + myfree(ht_list); + + ht_list = htable_list(want_hash); + qsort(ht_list, want_hash->used, sizeof(*ht_list), compar); + for (ht = ht_list; *ht; ht++) + ptest_error(t, "-%s = %s", ht[0]->key, (char *) ht[0]->value); + myfree(ht_list); + } + htable_free(got_hash, myfree); + htable_free(want_hash, myfree); + } + return (0); +} diff --git a/postfix/src/testing/match_attr.h b/postfix/src/testing/match_attr.h new file mode 100644 index 000000000..8efbb13ae --- /dev/null +++ b/postfix/src/testing/match_attr.h @@ -0,0 +1,43 @@ +#ifndef _MATCH_ATTR_H_INCLUDED_ +#define _MATCH_ATTR_H_INCLUDED_ + +/*++ +/* NAME +/* match_attr 3h +/* SUMMARY +/* attribute matching +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Test library. + */ +#include + + /* + * External interface. + */ +extern int _eq_attr(const char *, int, PTEST_CTX *, const char *, + VSTRING *, VSTRING *); + +#define eq_attr(...) _eq_attr(__FILE__, __LINE__, __VA_ARGS__) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/testing/match_attr_test.c b/postfix/src/testing/match_attr_test.c new file mode 100644 index 000000000..c9fb5405b --- /dev/null +++ b/postfix/src/testing/match_attr_test.c @@ -0,0 +1,135 @@ +/* + * Test program to exercise match_attr functions including logging. See + * documentation in PTEST_README for the structure of this file. + */ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Test library. + */ +#include +#include +#include + +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 diff --git a/postfix/src/testing/match_basic.c b/postfix/src/testing/match_basic.c new file mode 100644 index 000000000..ee401dff3 --- /dev/null +++ b/postfix/src/testing/match_basic.c @@ -0,0 +1,238 @@ +/*++ +/* NAME +/* match_basic 3 +/* SUMMARY +/* basic matchers +/* SYNOPSIS +/* #include +/* +/* 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 +#include + + /* + * Utility library. + */ +#include + + /* + * Test library. + */ +#include +#include + +#define STR_OR_NULL(s) ((s) ? (s) : "(null)") +#define ARGV_OR_NULL(s) ((s) ? "(ARGV)" : "(null)") + +/* _eq_int - match integers */ + +int _eq_int(const char *file, int line, PTEST_CTX *t, + const char *what, int got, int want) +{ + if (got != want) { + if (t) + ptest_error(t, "%s:%d: %s: got %d, want %d", file, line, what, got, want); + return (0); + } + return (1); +} + +/* _eq_size_t - match size_t */ + +int _eq_size_t(const char *file, int line, PTEST_CTX *t, + const char *what, size_t got, size_t want) +{ + if (got != want) { + if (t) + ptest_error(t, "%s:%d: %s: got %lu, want %lu", + file, line, what, (long) got, (long) want); + return (0); + } + return (1); +} + +/* _eq_ssize_t - match ssize_t */ + +int _eq_ssize_t(const char *file, int line, PTEST_CTX *t, + const char *what, ssize_t got, ssize_t want) +{ + if (got != want) { + if (t) + ptest_error(t, "%s:%d: %s: got %ld, want %ld", + file, line, what, (long) got, (long) want); + return (0); + } + return (1); +} + +/* _eq_flags - match flags */ + +int _eq_flags(const char *file, int line, PTEST_CTX *t, + const char *what, int got, int want, + const char *(flags_to_string) (VSTRING *, int)) +{ + if (got != want) { + if (t) { + VSTRING *got_buf = vstring_alloc(100); + VSTRING *want_buf = vstring_alloc(100); + + ptest_error(t, "%s:%d: %s: got '%s', want '%s'", file, line, what, + flags_to_string(got_buf, got), + flags_to_string(want_buf, want)); + vstring_free(got_buf); + vstring_free(want_buf); + } + return (0); + } + return (1); +} + +/* _eq_enum - match enum */ + +int _eq_enum(const char *file, int line, PTEST_CTX *t, + const char *what, int got, int want, + const char *(enum_to_string) (int)) +{ + if (got != want) { + if (t) + ptest_error(t, "%s:%d: %s: got '%s', want '%s'", file, line, what, + enum_to_string(got), enum_to_string(want)); + return (0); + } + return (1); +} + +/* _eq_str - match null-terminated strings */ + +int _eq_str(const char *file, int line, PTEST_CTX *t, + const char *what, const char *got, const char *want) +{ + if (got == 0 && want == 0) { + return (1); + } else if (got == 0 || want == 0) { + if (t) + ptest_error(t, "%s:%d: %s: got '%s', want '%s'", + file, line, what, STR_OR_NULL(got), + STR_OR_NULL(want)); + return (0); + } + if (strcmp(got, want) != 0) { + if (t) + ptest_error(t, "%s:%d: %s: got '%s', want '%s'", + file, line, what, got, want); + return (0); + } + return (1); +} + +/* _eq_argv - match ARGV instances */ + +int _eq_argv(const char *file, int line, PTEST_CTX *t, + const char *what, const ARGV *got, const ARGV *want) +{ + char **gpp, **wpp; + + if (got == 0 && want == 0) { + return (1); + } else if (got == 0 || want == 0) { + if (t) + ptest_error(t, "%s:%d: %s: got '%s', want '%s'", + file, line, what, ARGV_OR_NULL(got), + ARGV_OR_NULL(want)); + return (0); + } + (void) _eq_int(file, line, t, what, got->argc, want->argc); + + for (gpp = got->argv, wpp = want->argv; /* see below */ ; gpp++, wpp++) { + if (*gpp == 0 && *wpp == 0) { + return (1); + } else if (*gpp == 0 || *wpp == 0) { + if (t) + ptest_error(t, "%s:%d: %s: got '%s', want '%s'", + file, line, what, STR_OR_NULL(*gpp), + STR_OR_NULL(*wpp)); + return (0); + } else if (!_eq_str(file, line, t, what, *gpp, *wpp)) { + return (0); + } + } +} diff --git a/postfix/src/testing/match_basic.h b/postfix/src/testing/match_basic.h new file mode 100644 index 000000000..91d2cd228 --- /dev/null +++ b/postfix/src/testing/match_basic.h @@ -0,0 +1,78 @@ +#ifndef _MATCH_BASIC_H_INCLUDED_ +#define _MATCH_BASIC_H_INCLUDED_ + +/*++ +/* NAME +/* match_basic 3h +/* SUMMARY +/* basic matchers +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Test library. + */ +#include + + /* + * Matchers. + */ +#define eq_int(...) _eq_int(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_int(const char *, int, PTEST_CTX *, + const char *, int, int); + +#define eq_size_t(...) _eq_int(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_size_t(const char *, int, PTEST_CTX *, + const char *, size_t, size_t); + +#define eq_ssize_t(...) _eq_int(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_ssize_t(const char *, int, PTEST_CTX *, + const char *, ssize_t, ssize_t); + +#define eq_flags(...) _eq_flags(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_flags(const char *, int, PTEST_CTX *, + const char *, int, int, + const char *(*flags_to_str) (VSTRING *, int)); + +#define eq_enum(...) _eq_enum(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_enum(const char *, int, PTEST_CTX *, + const char *, int, int, const char *(*enum_to_str) (int)); + +#define eq_str(...) _eq_str(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_str(const char *, int, PTEST_CTX *, + const char *, const char *, const char *); + +#define eq_argv(...) _eq_argv(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_argv(const char *, int, PTEST_CTX *, + const char *, const ARGV *, const ARGV *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Wietse Venema +/* porcupine.org +/*--*/ + +#endif diff --git a/postfix/src/testing/match_basic_test.c b/postfix/src/testing/match_basic_test.c new file mode 100644 index 000000000..042b8806c --- /dev/null +++ b/postfix/src/testing/match_basic_test.c @@ -0,0 +1,160 @@ + /* + * Test program to exercise make_addr functions including logging. See + * comments in ptest_main.h and pmock_expect_test.c for a documented + * example. + */ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Test library. + */ +#include +#include + +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 diff --git a/postfix/src/testing/mock_dns.h b/postfix/src/testing/mock_dns.h new file mode 100644 index 000000000..5df2e861b --- /dev/null +++ b/postfix/src/testing/mock_dns.h @@ -0,0 +1,67 @@ +#ifndef _MOCK_DNS_H_INCLUDED_ +#define _MOCK_DNS_H_INCLUDED_ + +/*++ +/* NAME +/* mock_dns 3h +/* SUMMARY +/* emulate DNS support for hermetic tests +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * DNS library. + */ +#include + + /* + * Test library. + */ +#include + + /* + * Manage expectations and responses. Capture the source file name and line + * number for better diagnostics. + */ +#define expect_dns_lookup_x(...) \ + _expect_dns_lookup_x(__FILE__, __LINE__, __VA_ARGS__) + +extern void _expect_dns_lookup_x(const char *, int, int, int, int, + const char *, unsigned, unsigned, DNS_RR *, + VSTRING *, VSTRING *, int, unsigned); + + /* + * Matcher predicates. Capture the source file name and line number for + * better diagnostics. + */ +#define eq_dns_rr(...) _eq_dns_rr(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_dns_rr(const char *, int, PTEST_CTX *, const char *, DNS_RR *, + DNS_RR *); + + /* + * Helper to create test data. + */ +extern DNS_RR *make_dns_rr(const char *, const char *, unsigned, unsigned, + unsigned, unsigned, unsigned, unsigned, + unsigned, void *, size_t); + + /* + * Other helper. + */ +extern const char *dns_status_to_string(int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/testing/mock_dns_lookup.c b/postfix/src/testing/mock_dns_lookup.c new file mode 100644 index 000000000..a7500b20f --- /dev/null +++ b/postfix/src/testing/mock_dns_lookup.c @@ -0,0 +1,501 @@ +/*++ +/* NAME +/* mock_dns_lookup 3 +/* SUMMARY +/* dns_lookup mock for hermetic tests +/* SYNOPSIS +/* #include +/* +/* 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 + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * DNS library. + */ +#include + + /* + * Test library. + */ +#include +#include +#include + + /* + * Helpers. + */ +#define MYSTRDUP_OR_NULL(x) ((x) ? mystrdup(x) : 0) +#define VSTRDUP_OR_NULL(x) \ + ((x) ? vstring_strcpy(vstring_alloc(VSTRING_LEN(x)), vstring_str(x)) : 0) + +#define STR(x) vstring_str(x) + +#define STR_OR_NULL(s) ((s) ? (s) : "(null)") +#define VSTR_STR_OR_NULL(s) ((s) ? STR(s) : "(null)") + + /* + * Local state for the mock functions dns_get_herrno() and dns_set_herrno(), + * also updated when the mock function dns_lookup_x() is called. + * + * XXX This could leak information when tests are run successively in the same + * process. Should the test infrastructure fork() the process before each + * test? Leakage will not happen when each test calls a function that resets + * global_herrval. + */ +static int global_herrval = ~0; + +/* dns_status_to_string - convert status code to string */ + +const char *dns_status_to_string(int status) +{ + static const NAME_CODE status_string[] = { + "DNS_OK", DNS_OK, + "DNS_POLICY", DNS_POLICY, + "DNS_RETRY", DNS_RETRY, + "DNS_INVAL", DNS_INVAL, + "DNS_FAIL", DNS_FAIL, + "DNS_NULLMX", DNS_NULLMX, + "DNS_NOTFOUND", DNS_NOTFOUND, + 0, + }; + + return (str_name_code(status_string, status)); +} + +/* copy_dns_rrlist - deep copy */ + +static DNS_RR *copy_dns_rrlist(DNS_RR *list) +{ + DNS_RR *rr; + + if (list == 0) + return (0); + rr = dns_rr_copy(list); + rr->next = copy_dns_rrlist(list->next); + return (rr); +} + +/* make_dns_rr - dns_rr_create() wrapper */ + +DNS_RR *make_dns_rr(const char *qname, const char *rname, unsigned type, + unsigned class, unsigned ttl, + unsigned dnssec_valid, unsigned pref, + unsigned weight, unsigned port, + void *data, size_t data_len) +{ + DNS_RR *rr; + + rr = dns_rr_create(qname, rname, type, class, ttl, pref, weight, port, + data, data_len); + rr->dnssec_valid = dnssec_valid; + return (rr); +} + +/* _eq_dns_rr - equality predicate */ + +int _eq_dns_rr(const char *file, int line, PTEST_CTX *t, + const char *what, + DNS_RR *got, DNS_RR *want) +{ + if (got == 0 && want == 0) { + return (1); + } + if (got == 0 || want == 0) { + if (t) + ptest_error(t, "%s:%d %s: got %s, want %s", + file, line, what, got ? "(DNS_RR *)" : "(null)", + want ? "(DNS_RR *)" : "(null)"); + return (0); + } + if (strcmp(got->qname, want->qname) != 0) { + if (t) + ptest_error(t, "%s:%d %s: got qname '%s', want '%s'", + file, line, what, got->qname, want->qname); + return (0); + } + if (strcmp(got->rname, want->rname) != 0) { + if (t) + ptest_error(t, "%s:%d %s: got rname '%s', want '%s'", + file, line, what, got->rname, want->rname); + return (0); + } + if (got->type != want->type) { + if (t) + ptest_error(t, "%s:%d %s: got type %d, want %d", + file, line, what, got->type, want->type); + return (0); + } + if (got->class != want->class) { + if (t) + ptest_error(t, "%s:%d %s: got class %d, want %d", + file, line, what, got->class, want->class); + return (0); + } + if (got->ttl != want->ttl) { + if (t) + ptest_error(t, "%s:%d %s: got ttl %d, want %d", + file, line, what, got->ttl, want->ttl); + return (0); + } + if (got->dnssec_valid != want->dnssec_valid) { + if (t) + ptest_error(t, "%s:%d %s: got dnssec_valid %d, want %d", + file, line, what, got->dnssec_valid, want->dnssec_valid); + return (0); + } + if (got->pref != want->pref) { + if (t) + ptest_error(t, "%s:%d %s: got pref %d, want %d", + file, line, what, got->pref, want->pref); + return (0); + } + if (got->weight != want->weight) { + if (t) + ptest_error(t, "%s:%d %s: got weight %d, want %d", + file, line, what, got->weight, want->weight); + return (0); + } + if (got->port != want->port) { + if (t) + ptest_error(t, "%s:%d %s: got port %d, want %d", + file, line, what, got->port, want->port); + return (0); + } + if (got->data_len != want->data_len) { + if (t) + ptest_error(t, "%s:%d %s: got data_len %d, want %d", + file, line, what, (int) got->data_len, (int) want->data_len); + return (0); + } + if (memcmp(got->data, want->data, got->data_len) != 0) { + VSTRING *got_data_hex = vstring_alloc(100); + VSTRING *want_data_hex = vstring_alloc(100); + + if (t) + ptest_error(t, "%s:%d %s: got data %s, want %s", + file, line, what, STR(hex_encode_opt(got_data_hex, got->data, + got->data_len, HEX_ENCODE_FLAG_USE_COLON)), + STR(hex_encode_opt(want_data_hex, want->data, + want->data_len, HEX_ENCODE_FLAG_USE_COLON))); + vstring_free(got_data_hex); + vstring_free(want_data_hex); + return (0); + } + return (_eq_dns_rr(file, line, t, what, got->next, want->next)); +} + + /* + * Manage dns_lookup_x() expectations and responses. We use this structure + * for deep copies of expect_dns_lookup_x() expected inputs and prepared + * responses, and for shallow copies of dns_lookup_x() inputs. + */ +struct dns_lookup_x_expectation { + MOCK_EXPECT mock_expect; /* generic fields */ + int herrval; /* h_errno value */ + int retval; /* result value */ + char *name; /* inputs */ + unsigned type; + unsigned flags; + unsigned lflags; + DNS_RR *rrlist; /* outputs */ + VSTRING *fqdn; + VSTRING *why; + int rcode; +}; + + /* + * Pointers to dns_lookup_x() outputs. + */ +struct dns_lookup_x_targets { + int *herrval; + int *retval; + DNS_RR **rrlist; + VSTRING *fqdn; + VSTRING *why; + int *rcode; +}; + +/* match_dns_lookup_x - match inputs against expectation */ + +static int match_dns_lookup_x(const MOCK_EXPECT *expect, + const MOCK_EXPECT *inputs) +{ + struct dns_lookup_x_expectation *pe = + (struct dns_lookup_x_expectation *) expect; + struct dns_lookup_x_expectation *pi = + (struct dns_lookup_x_expectation *) inputs; + + return (strcmp(STR_OR_NULL(pe->name), + STR_OR_NULL(pi->name)) == 0 + && pe->type == pi->type + && pe->flags == pi->flags + && pe->lflags == pi->lflags); +} + +/* assign_dns_lookup_x - assign expected output */ + +static void assign_dns_lookup_x(const MOCK_EXPECT *expect, + void *targets) +{ + struct dns_lookup_x_expectation *pe = + (struct dns_lookup_x_expectation *) expect; + struct dns_lookup_x_targets *pt = + (struct dns_lookup_x_targets *) targets; + + if (pe->retval == DNS_OK) { + if (pt->rrlist) + *(pt->rrlist) = copy_dns_rrlist(pe->rrlist); + if (pt->fqdn && pe->fqdn) + vstring_strcpy(pt->fqdn, STR(pe->fqdn)); + } else { + if (pt->why && pe->why) + vstring_strcpy(pt->why, STR(pe->why)); + } + if (pt->rcode) + *pt->rcode = pe->rcode; + *pt->retval = pe->retval; + *pt->herrval = pe->herrval; +} + +/* print_dns_lookup_x - print expected inputs */ + +static char *print_dns_lookup_x(const MOCK_EXPECT *expect, + VSTRING *buf) +{ + struct dns_lookup_x_expectation *pe = + (struct dns_lookup_x_expectation *) expect; + + vstring_sprintf(buf, "\"%s\", %s, %d, (ptr), (ptr), (ptr), (ptr), %d", + STR_OR_NULL(pe->name), dns_strtype(pe->type), + pe->flags, pe->lflags); + return (STR(buf)); +} + +/* free_dns_lookup_x - destructor */ + +static void free_dns_lookup_x(MOCK_EXPECT *expect) +{ + struct dns_lookup_x_expectation *pe = + (struct dns_lookup_x_expectation *) expect; + + myfree(pe->name); + if (pe->retval == DNS_OK) { + if (pe->rrlist) + dns_rr_free(pe->rrlist); + if (pe->fqdn) + vstring_free(pe->fqdn); + } else { + if (pe->why) + vstring_free(pe->why); + } + pmock_expect_free(expect); +} + + /* + * The mock name and its helper callbacks in one place. + */ +static const MOCK_APPL_SIG dns_lookup_x_sig = { + "dns_lookup_x", + match_dns_lookup_x, + assign_dns_lookup_x, + print_dns_lookup_x, + free_dns_lookup_x, +}; + +/* _expect_dns_lookup_x - set up expectation */ + +void _expect_dns_lookup_x(const char *file, int line, int calls_expected, + int herrval, int retval, + const char *name, unsigned type, unsigned flags, + DNS_RR *rrlist, VSTRING *fqdn, VSTRING *why, + int rcode, unsigned lflags) +{ + struct dns_lookup_x_expectation *pe; + + pe = (struct dns_lookup_x_expectation *) + pmock_expect_create(&dns_lookup_x_sig, + file, line, calls_expected, sizeof(*pe)); + + /* + * Inputs. + */ + pe->name = MYSTRDUP_OR_NULL(name); + pe->type = type; + pe->flags = flags; + pe->lflags = lflags; + + /* + * Outputs. + */ + pe->herrval = herrval; + pe->retval = retval; + if (pe->retval == DNS_OK) { + pe->rrlist = copy_dns_rrlist(rrlist); + pe->fqdn = VSTRDUP_OR_NULL(fqdn); + } else { + pe->why = VSTRDUP_OR_NULL(why); + } + pe->rcode = rcode; +} + +/* dns_lookup_x - answer the call with prepared responses */ + +int dns_lookup_x(const char *name, unsigned type, unsigned flags, + DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why, + int *rcode, unsigned lflags) +{ + struct dns_lookup_x_expectation inputs; + struct dns_lookup_x_targets targets; + int retval = DNS_FAIL; + + /* + * Bundle the arguments to simplify handling. + */ + inputs.name = (char *) name; + inputs.type = type; + inputs.flags = flags; + inputs.lflags = lflags; + + targets.herrval = &global_herrval; + targets.retval = &retval; + targets.rrlist = rrlist; + targets.fqdn = fqdn; + targets.why = why; + targets.rcode = rcode; + + /* + * Aargh. + */ + if (rrlist) + *rrlist = 0; + + /* + * TODO: bail out if there was no match? + */ + (void) pmock_expect_apply(&dns_lookup_x_sig, + &inputs.mock_expect, (void *) &targets); + return (retval); +} + +/* dns_get_h_errno - return prepared answer */ + +int dns_get_h_errno(void) +{ + return (global_herrval); +} + +/* dns_set_h_errno - return prepared answer */ + +void dns_set_h_errno(int herrval) +{ + global_herrval = herrval; +} diff --git a/postfix/src/testing/mock_dns_lookup_test.c b/postfix/src/testing/mock_dns_lookup_test.c new file mode 100644 index 000000000..661777b9a --- /dev/null +++ b/postfix/src/testing/mock_dns_lookup_test.c @@ -0,0 +1,206 @@ + /* + * Test program to exercise mocks including logging. See comments in + * ptest_main.h and pmock_expect_test.c for a documented example. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Test library. + */ +#include +#include +#include + +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 diff --git a/postfix/src/testing/mock_getaddrinfo.c b/postfix/src/testing/mock_getaddrinfo.c new file mode 100644 index 000000000..41fc941a9 --- /dev/null +++ b/postfix/src/testing/mock_getaddrinfo.c @@ -0,0 +1,487 @@ +/*++ +/* NAME +/* mock_getaddrinfo 3 +/* SUMMARY +/* mock getaddrinfo/getnameinfo for hermetic tests +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include /* sprintf/snprintf */ + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Test library. + */ +#include +#include +#include + +#define MYSTRDUP_OR_NULL(x) ((x) ? mystrdup(x) : 0) +#define STR_OR_NULL(s) ((s) ? (s) : "(null)") + +#define STR vstring_str + + /* + * Manage getaddrinfo() expectations and responses. We use this structure + * for deep copies of expect_getaddrinfo() expected inputs and prepared + * responses, and for shallow copies of getaddrinfo() inputs, so that we can + * reuse the print_getaddrinfo() helper. + */ +struct getaddrinfo_expectation { + MOCK_EXPECT mock_expect; /* generic fields */ + int retval; /* result value */ + char *node; /* inputs */ + char *service; + struct addrinfo *hints; + struct addrinfo *res; /* outputs */ +}; + + /* + * Pointers to getaddrinfo() outputs. + */ +struct getaddrinfo_targets { + struct addrinfo **res; + int *retval; +}; + +/* match_getaddrinfo - match inputs against expectation */ + +static int match_getaddrinfo(const MOCK_EXPECT *expect, + const MOCK_EXPECT *inputs) +{ + struct getaddrinfo_expectation *pe = + (struct getaddrinfo_expectation *) expect; + struct getaddrinfo_expectation *pi = + (struct getaddrinfo_expectation *) inputs; + + return (strcmp(STR_OR_NULL(pe->node), STR_OR_NULL(pi->node)) == 0 + && strcmp(STR_OR_NULL(pe->service), STR_OR_NULL(pi->service)) == 0 + && eq_addrinfo((PTEST_CTX *) 0, (char *) 0, pe->hints, pi->hints)); +} + +/* assign_getaddrinfo - assign expected output */ + +static void assign_getaddrinfo(const MOCK_EXPECT *expect, void *targets) +{ + struct getaddrinfo_expectation *pe = + (struct getaddrinfo_expectation *) expect; + struct getaddrinfo_targets *pt = + (struct getaddrinfo_targets *) targets; + + if (pe->retval == 0) + *(pt->res) = copy_addrinfo(pe->res); + *pt->retval = pe->retval; +} + +/* print_getaddrinfo - print expected inputs */ + +static char *print_getaddrinfo(const MOCK_EXPECT *expect, VSTRING *buf) +{ + struct getaddrinfo_expectation *pe = + (struct getaddrinfo_expectation *) expect; + VSTRING *hints_buf = vstring_alloc(100); + + vstring_sprintf(buf, "\"%s\", \"%s\", %s, (ptr)", + STR_OR_NULL(pe->node), + STR_OR_NULL(pe->service), + addrinfo_hints_to_string(hints_buf, pe->hints)); + vstring_free(hints_buf); + return (vstring_str(buf)); +} + +/* free_getaddrinfo - destructor */ + +static void free_getaddrinfo(MOCK_EXPECT *expect) +{ + struct getaddrinfo_expectation *pe = + (struct getaddrinfo_expectation *) expect; + + if (pe->node) + myfree(pe->node); + if (pe->service) + myfree(pe->service); + if (pe->hints) + myfree(pe->hints); + if (pe->retval == 0) + freeaddrinfo(pe->res); + pmock_expect_free(expect); +} + + /* + * The mock name and its helper callbacks in one place. + */ +static const MOCK_APPL_SIG getaddrinfo_sig = { + "getaddrinfo", + match_getaddrinfo, + assign_getaddrinfo, + print_getaddrinfo, + free_getaddrinfo, +}; + +/* _expect_getaddrinfo - set up expectation */ + +void _expect_getaddrinfo(const char *file, int line, + int calls_expected, int retval, + const char *node, + const char *service, + const struct addrinfo *hints, + struct addrinfo *res) +{ + struct getaddrinfo_expectation *pe; + + pe = (struct getaddrinfo_expectation *) + pmock_expect_create(&getaddrinfo_sig, + file, line, calls_expected, sizeof(*pe)); + pe->retval = retval; + pe->node = MYSTRDUP_OR_NULL(node); + pe->service = MYSTRDUP_OR_NULL(service); + pe->hints = copy_addrinfo(hints); + if (pe->retval == 0) + pe->res = copy_addrinfo(res); +} + +/* getaddrinfo - mock getaddrinfo */ + +int getaddrinfo(const char *node, + const char *service, + const struct addrinfo *hints, + struct addrinfo **res) +{ + struct getaddrinfo_expectation inputs; + struct getaddrinfo_targets targets; + int retval = EAI_FAIL; + + /* + * Bundle the arguments to simplify handling. + */ + inputs.node = (char *) node; + inputs.service = (char *) service; + inputs.hints = (struct addrinfo *) hints; + + targets.res = res; + targets.retval = &retval; + + /* + * TODO: bail out if there was no match? + */ + (void) pmock_expect_apply(&getaddrinfo_sig, + &inputs.mock_expect, (void *) &targets); + return (retval); +} + + /* + * Manage getnameinfo() expectations and responses. We use this structure + * for deep copies of expect_getnameinfo() expected inputs and prepared + * responses, and for shallow copies of getnameinfo() inputs, so that we can + * reuse the print_getnameinfo() helper. + */ +struct getnameinfo_expectation { + MOCK_EXPECT mock_expect; /* generic fields */ + int retval; /* result value */ + struct sockaddr *sa; /* inputs */ + size_t salen; + char *host; /* outputs */ + size_t hostlen; + char *serv; + size_t servlen; + int flags; /* other input */ +}; + + /* + * Pointers to getnameinfo() outputs. + */ +struct getnameinfo_targets { + char *host; + size_t hostlen; + char *serv; + size_t servlen; + int *retval; +}; + +/* match_getnameinfo - match inputs against expectation */ + +static int match_getnameinfo(const MOCK_EXPECT *expect, + const MOCK_EXPECT *inputs) +{ + struct getnameinfo_expectation *pe = + (struct getnameinfo_expectation *) expect; + struct getnameinfo_expectation *pi = + (struct getnameinfo_expectation *) inputs; + + return (eq_sockaddr((PTEST_CTX *) 0, (char *) 0, + pe->sa, pe->salen, pi->sa, pi->salen) + && pe->flags == pi->flags); +} + +/* assign_getnameinfo - assign expected output */ + +static void assign_getnameinfo(const MOCK_EXPECT *expect, void *targets) +{ + struct getnameinfo_expectation *pe = + (struct getnameinfo_expectation *) expect; + struct getnameinfo_targets *pt = + (struct getnameinfo_targets *) targets; + +#define MIN_OF(x,y) ((x) < (y) ? (x) : (y)) + + if (pe->retval == 0) { + if (pt->host && pe->host) { + strncpy(pt->host, pe->host, MIN_OF(pt->hostlen, pe->hostlen)); + pt->host[pt->hostlen - 1] = 0; + } + if (pt->serv && pe->serv) { + strncpy(pt->serv, pe->serv, MIN_OF(pt->servlen, pe->servlen)); + pt->serv[pt->servlen - 1] = 0; + } + } + *pt->retval = pe->retval; +} + +/* print_getnameinfo - print inputs */ + +static char *print_getnameinfo(const MOCK_EXPECT *expect, VSTRING *buf) +{ + struct getnameinfo_expectation *pe = + (struct getnameinfo_expectation *) expect; + VSTRING *sockaddr_buf = vstring_alloc(100); + VSTRING *flags_buf = vstring_alloc(100); + + vstring_sprintf(buf, "%s, %ld, (ptr), (len), (ptr), (len), %s", + sockaddr_to_string(sockaddr_buf, pe->sa, pe->salen), + (long) pe->salen, + ni_flags_to_string(flags_buf, pe->flags)); + vstring_free(sockaddr_buf); + vstring_free(flags_buf); + return (STR(buf)); +} + +/* free_getnameinfo - destructor */ + +static void free_getnameinfo(MOCK_EXPECT *expect) +{ + struct getnameinfo_expectation *pe = + (struct getnameinfo_expectation *) expect; + + if (pe->sa) + myfree(pe->sa); + if (pe->host) + myfree(pe->host); + if (pe->serv) + myfree(pe->serv); + pmock_expect_free(expect); +} + + /* + * The mock name and its helper callbacks in one place. + */ +static const MOCK_APPL_SIG getnameinfo_sig = { + "getnameinfo", + match_getnameinfo, + assign_getnameinfo, + print_getnameinfo, + free_getnameinfo, +}; + +/* _expect_getnameinfo - set up expectation */ + +void _expect_getnameinfo(const char *file, int line, + int calls_expected, int retval, + const struct sockaddr *sa, size_t salen, + const char *host, size_t hostlen, + const char *serv, size_t servlen, + int flags) +{ + struct getnameinfo_expectation *pe; + + pe = (struct getnameinfo_expectation *) + pmock_expect_create(&getnameinfo_sig, + file, line, calls_expected, sizeof(*pe)); + pe->retval = retval; + pe->sa = (struct sockaddr *) mymalloc(salen); + memcpy(pe->sa, sa, salen); + pe->salen = salen; + pe->host = MYSTRDUP_OR_NULL(host); + pe->hostlen = hostlen; + pe->serv = MYSTRDUP_OR_NULL(serv); + pe->servlen = servlen; + pe->flags = flags; +} + +/* getnameinfo - mock getnameinfo */ + +int getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, + char *serv, size_t servlen, int flags) +{ + struct getnameinfo_expectation inputs; + struct getnameinfo_targets targets; + int retval = EAI_FAIL; + + /* + * Bundle the arguments to simplify handling. + */ + inputs.sa = (struct sockaddr *) sa; + inputs.salen = salen; + inputs.flags = flags; + + targets.host = host; + targets.hostlen = hostlen; + targets.serv = serv; + targets.servlen = servlen; + targets.retval = &retval; + + /* + * TODO: bail out if there was no match? + */ + (void) pmock_expect_apply(&getnameinfo_sig, + &inputs.mock_expect, (void *) &targets); + return (retval); +} diff --git a/postfix/src/testing/mock_getaddrinfo.h b/postfix/src/testing/mock_getaddrinfo.h new file mode 100644 index 000000000..0f89b68c1 --- /dev/null +++ b/postfix/src/testing/mock_getaddrinfo.h @@ -0,0 +1,68 @@ +#ifndef _MOCK_GETADDRINFO_H_INCLUDED_ +#define _MOCK_GETADDRINFO_H_INCLUDED_ + +/*++ +/* NAME +/* mock_getaddrinfo 3h +/* SUMMARY +/* getaddrinfo/getnameinfo mock for hermetic tests +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include /* MAI_HOSTNAME_STR, etc. */ + + /* + * Test library. + */ +#include +#include +#include +#include +#include + + /* + * Manage expectations and responses. Capture the source file name and line + * number for better diagnostics. + */ +#define expect_getaddrinfo(...) \ + _expect_getaddrinfo(__FILE__, __LINE__, __VA_ARGS__) + +extern void _expect_getaddrinfo(const char *, int, int, int, + const char *, const char *, + const struct addrinfo *, + struct addrinfo *); + +#define expect_getnameinfo(...) \ + _expect_getnameinfo(__FILE__, __LINE__, __VA_ARGS__) + +extern void _expect_getnameinfo(const char *, int, int, int, + const struct sockaddr *, size_t, + const char *, size_t, + const char *, size_t, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Wietse Venema +/* porcupine.org +/*--*/ + +#endif diff --git a/postfix/src/testing/mock_getaddrinfo_test.c b/postfix/src/testing/mock_getaddrinfo_test.c new file mode 100644 index 000000000..23c1b5653 --- /dev/null +++ b/postfix/src/testing/mock_getaddrinfo_test.c @@ -0,0 +1,220 @@ + /* + * Test program for the mock_getaddrinfo module. See comments in + * ptest_main.h and pmock_expect_test.c for a documented example. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include + + /* + * Test library. + */ +#include +#include +#include + +#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 diff --git a/postfix/src/testing/mock_myaddrinfo.c b/postfix/src/testing/mock_myaddrinfo.c new file mode 100644 index 000000000..aeae3cd26 --- /dev/null +++ b/postfix/src/testing/mock_myaddrinfo.c @@ -0,0 +1,776 @@ +/*++ +/* NAME +/* mock_myaddrinfo 3 +/* SUMMARY +/* myaddrinfo mock for hermetic tests +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include /* sprintf */ + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Test library. + */ +#include +#include +#include +#include + +#define MYSTRDUP_OR_NULL(x) ((x) ? mystrdup(x) : 0) +#define STR_OR_NULL(s) ((s) ? (s) : "(null)") + + /* + * Manage hostname_to_sockaddr_pf() expectations and responses. We use this + * structure for deep copies pf expect_hostname_to_sockaddr_pf() expected + * inputs and prepared responses, and for shallow copies of + * hostname_to_sockaddr_pf() inputs, so that we can reuse the + * print_hostname_to_sockaddr_pf() helper. + */ +struct hostname_to_sockaddr_pf_expectation { + MOCK_EXPECT mock_expect; /* generic fields */ + int retval; /* result value */ + char *hostname; /* inputs */ + int pf; + char *service; + int socktype; + struct addrinfo *res; /* other outputs */ +}; + + /* + * Pointers to hostname_to_sockaddr_pf() outputs. + */ +struct hostname_to_sockaddr_pf_targets { + struct addrinfo **res; + int *retval; +}; + +/* match_hostname_to_sockaddr_pf - match inputs against expectation */ + +static int match_hostname_to_sockaddr_pf(const MOCK_EXPECT *expect, + const MOCK_EXPECT *inputs) +{ + struct hostname_to_sockaddr_pf_expectation *pe = + (struct hostname_to_sockaddr_pf_expectation *) expect; + struct hostname_to_sockaddr_pf_expectation *pi = + (struct hostname_to_sockaddr_pf_expectation *) inputs; + + return (strcmp(STR_OR_NULL(pe->hostname), + STR_OR_NULL(pi->hostname)) == 0 + && pe->pf == pi->pf + && strcmp(STR_OR_NULL(pe->service), + STR_OR_NULL(pi->service)) == 0 + && pe->socktype == pi->socktype); +} + +/* assign_hostname_to_sockaddr_pf - assign expected output */ + +static void assign_hostname_to_sockaddr_pf(const MOCK_EXPECT *expect, + void *targets) +{ + struct hostname_to_sockaddr_pf_expectation *pe = + (struct hostname_to_sockaddr_pf_expectation *) expect; + struct hostname_to_sockaddr_pf_targets *pt = + (struct hostname_to_sockaddr_pf_targets *) targets; + + if (pe->retval == 0) + *(pt->res) = copy_addrinfo(pe->res); + *pt->retval = pe->retval; +} + +/* print_hostname_to_sockaddr_pf - print expected inputs */ + +static char *print_hostname_to_sockaddr_pf(const MOCK_EXPECT *expect, + VSTRING *buf) +{ + struct hostname_to_sockaddr_pf_expectation *pe = + (struct hostname_to_sockaddr_pf_expectation *) expect; + + vstring_sprintf(buf, "\"%s\", %d, \"%s\", %d, (ptr)", + STR_OR_NULL(pe->hostname), pe->pf, + STR_OR_NULL(pe->service), pe->socktype); + return (vstring_str(buf)); +} + +/* free_hostname_to_sockaddr_pf - destructor */ + +static void free_hostname_to_sockaddr_pf(MOCK_EXPECT *expect) +{ + struct hostname_to_sockaddr_pf_expectation *pe = + (struct hostname_to_sockaddr_pf_expectation *) expect; + + if (pe->hostname) + myfree(pe->hostname); + if (pe->service) + myfree(pe->service); + if (pe->retval == 0) + freeaddrinfo(pe->res); + pmock_expect_free(expect); +} + + /* + * The mock name and its helper callbacks in one place. + */ +static const MOCK_APPL_SIG hostname_to_sockaddr_pf_sig = { + "hostname_to_sockaddr_pf", + match_hostname_to_sockaddr_pf, + assign_hostname_to_sockaddr_pf, + print_hostname_to_sockaddr_pf, + free_hostname_to_sockaddr_pf, +}; + +/* _expect_hostname_to_sockaddr_pf - set up expectation */ + +void _expect_hostname_to_sockaddr_pf(const char *file, int line, + int calls_expected, int retval, + const char *hostname, int pf, + const char *service, + int socktype, + struct addrinfo *res) +{ + struct hostname_to_sockaddr_pf_expectation *pe; + + pe = (struct hostname_to_sockaddr_pf_expectation *) + pmock_expect_create(&hostname_to_sockaddr_pf_sig, + file, line, calls_expected, sizeof(*pe)); + pe->retval = retval; + pe->hostname = MYSTRDUP_OR_NULL(hostname); + pe->pf = pf; + pe->service = MYSTRDUP_OR_NULL(service); + pe->socktype = socktype; + if (pe->retval == 0) + pe->res = copy_addrinfo(res); +} + +/* hostname_to_sockaddr_pf - mock hostname_to_sockaddr_pf */ + +int hostname_to_sockaddr_pf(const char *hostname, int pf, + const char *service, int socktype, + struct addrinfo **res) +{ + struct hostname_to_sockaddr_pf_expectation inputs; + struct hostname_to_sockaddr_pf_targets targets; + int retval = EAI_FAIL; + + /* + * Bundle the arguments to simplify handling. + */ + inputs.hostname = (char *) hostname; + inputs.pf = pf; + inputs.service = (char *) service; + inputs.socktype = socktype; + + targets.res = res; + targets.retval = &retval; + + /* + * TODO: bail out if there was no match? + */ + (void) pmock_expect_apply(&hostname_to_sockaddr_pf_sig, + &inputs.mock_expect, (void *) &targets); + return (retval); +} + + /* + * Manage hostaddr_to_sockaddr() expectations and responses. We use this + * structure for deep copies of expect_hostaddr_to_sockaddr() expected + * inputs and prepared responses, and for shallow copies + * hostaddr_to_sockaddr() inputs, so that we can reuse the + * print_hostaddr_to_sockaddr() helper. + */ +struct hostaddr_to_sockaddr_expectation { + MOCK_EXPECT mock_expect; /* generic fields */ + int retval; /* result value */ + char *hostaddr; /* inputs */ + char *service; + int socktype; + struct addrinfo *res; /* other outputs */ +}; + + /* + * Pointers to hostaddr_to_sockaddr() outputs. + */ +struct hostaddr_to_sockaddr_targets { + struct addrinfo **res; + int *retval; +}; + +/* match_hostaddr_to_sockaddr - match inputs against expectation */ + +static int match_hostaddr_to_sockaddr(const MOCK_EXPECT *expect, + const MOCK_EXPECT *inputs) +{ + struct hostaddr_to_sockaddr_expectation *pe = + (struct hostaddr_to_sockaddr_expectation *) expect; + struct hostaddr_to_sockaddr_expectation *pi = + (struct hostaddr_to_sockaddr_expectation *) inputs; + + return (strcmp(STR_OR_NULL(pe->hostaddr), + STR_OR_NULL(pi->hostaddr)) == 0 + && strcmp(STR_OR_NULL(pe->service), + STR_OR_NULL(pi->service)) == 0 + && pe->socktype == pi->socktype); +} + +/* assign_hostaddr_to_sockaddr - assign expected output */ + +static void assign_hostaddr_to_sockaddr(const MOCK_EXPECT *expect, + void *targets) +{ + struct hostaddr_to_sockaddr_expectation *pe = + (struct hostaddr_to_sockaddr_expectation *) expect; + struct hostaddr_to_sockaddr_targets *pt = + (struct hostaddr_to_sockaddr_targets *) targets; + + if (pe->retval == 0) + *(pt->res) = copy_addrinfo(pe->res); + *pt->retval = pe->retval; +} + +/* print_hostaddr_to_sockaddr - print expected inputs */ + +static char *print_hostaddr_to_sockaddr(const MOCK_EXPECT *expect, + VSTRING *buf) +{ + struct hostaddr_to_sockaddr_expectation *pe = + (struct hostaddr_to_sockaddr_expectation *) expect; + + vstring_sprintf(buf, "\"%s\", \"%s\", %d, (ptr)", + STR_OR_NULL(pe->hostaddr), + STR_OR_NULL(pe->service), pe->socktype); + return (vstring_str(buf)); +} + +/* free_hostname_to_sockaddr_pf - destructor */ + +static void free_hostaddr_to_sockaddr(MOCK_EXPECT *expect) +{ + struct hostaddr_to_sockaddr_expectation *pe = + (struct hostaddr_to_sockaddr_expectation *) expect; + + if (pe->hostaddr) + myfree(pe->hostaddr); + if (pe->service) + myfree(pe->service); + if (pe->retval == 0) + freeaddrinfo(pe->res); + pmock_expect_free(expect); +} + + /* + * The mock name and its helper callbacks in one place. + */ +static const MOCK_APPL_SIG hostaddr_to_sockaddr_sig = { + "hostaddr_to_sockaddr", + match_hostaddr_to_sockaddr, + assign_hostaddr_to_sockaddr, + print_hostaddr_to_sockaddr, + free_hostaddr_to_sockaddr, +}; + +/* _expect_hostaddr_to_sockaddr - set up expectation */ + +void _expect_hostaddr_to_sockaddr(const char *file, int line, + int calls_expected, int retval, + const char *hostaddr, + const char *service, int socktype, + struct addrinfo *res) +{ + struct hostaddr_to_sockaddr_expectation *pe; + + pe = (struct hostaddr_to_sockaddr_expectation *) + pmock_expect_create(&hostaddr_to_sockaddr_sig, + file, line, calls_expected, sizeof(*pe)); + pe->retval = retval; + pe->hostaddr = MYSTRDUP_OR_NULL(hostaddr); + pe->service = MYSTRDUP_OR_NULL(service); + pe->socktype = socktype; + if (pe->retval == 0) + pe->res = copy_addrinfo(res); +} + +/* hostaddr_to_sockaddr - mock hostaddr_to_sockaddr */ + +int hostaddr_to_sockaddr(const char *hostaddr, const char *service, + int socktype, struct addrinfo **res) +{ + struct hostaddr_to_sockaddr_expectation inputs; + struct hostaddr_to_sockaddr_targets targets; + int retval = EAI_FAIL; + + /* + * Bundle the arguments to simplify handling. + */ + inputs.hostaddr = (char *) hostaddr; + inputs.service = (char *) service; + inputs.socktype = socktype; + + targets.res = res; + targets.retval = &retval; + + /* + * TODO: bail out if there was no match? + */ + (void) pmock_expect_apply(&hostaddr_to_sockaddr_sig, + &inputs.mock_expect, (void *) &targets); + return (retval); +} + + /* + * Manage sockaddr_to_hostaddr() expectations and responses. We use this + * structure for deep copies of expect_sockaddr_to_hostaddr() expected + * inputs and prepared responses, and for shallow copies of + * sockaddr_to_hostaddr() inputs, so that we can reuse the + * print_sockaddr_to_hostaddr() helper. + */ +struct sockaddr_to_hostaddr_expectation { + MOCK_EXPECT mock_expect; /* generic fields */ + int retval; /* result value */ + struct sockaddr_storage sa; /* inputs */ + SOCKADDR_SIZE salen; + int socktype; + MAI_HOSTADDR_STR *hostaddr; /* other outputs */ + MAI_SERVPORT_STR *portnum; + MAI_HOSTADDR_STR hostaddr_storage; + MAI_SERVPORT_STR portnum_storage; +}; + + /* + * Pointers to sockaddr_to_hostaddr() outputs. + */ +struct sockaddr_to_hostaddr_targets { + MAI_HOSTADDR_STR *hostaddr; + MAI_SERVPORT_STR *portnum; + int *retval; +}; + +/* match_sockaddr_to_hostaddr - match inputs against expectation */ + +static int match_sockaddr_to_hostaddr(const MOCK_EXPECT *expect, + const MOCK_EXPECT *inputs) +{ + struct sockaddr_to_hostaddr_expectation *pe = + (struct sockaddr_to_hostaddr_expectation *) expect; + struct sockaddr_to_hostaddr_expectation *pi = + (struct sockaddr_to_hostaddr_expectation *) inputs; + + return (pe->salen == pi->salen + && memcmp(&pe->sa, &pi->sa, pe->salen) == 0 + && pe->socktype == pi->socktype); +} + +/* assign_sockaddr_to_hostaddr - assign expected output */ + +static void assign_sockaddr_to_hostaddr(const MOCK_EXPECT *expect, + void *targets) +{ + struct sockaddr_to_hostaddr_expectation *pe = + (struct sockaddr_to_hostaddr_expectation *) expect; + struct sockaddr_to_hostaddr_targets *pt = + (struct sockaddr_to_hostaddr_targets *) targets; + + if (pe->retval == 0) { + if (pe->hostaddr && pt->hostaddr) + *pt->hostaddr = *pe->hostaddr; + if (pe->portnum && pt->portnum) + *pt->portnum = *pe->portnum; + } + *pt->retval = pe->retval; +} + +/* print_sockaddr_to_hostaddr - print expected inputs */ + +static char *print_sockaddr_to_hostaddr(const MOCK_EXPECT *expect, + VSTRING *buf) +{ + struct sockaddr_to_hostaddr_expectation *pe = + (struct sockaddr_to_hostaddr_expectation *) expect; + VSTRING *sockaddr_buf = vstring_alloc(100); + + vstring_sprintf(buf, "%s, %ld, (ptr), (ptr)", + sockaddr_to_string(sockaddr_buf, + (struct sockaddr *) &pe->sa, + pe->salen), + (long) pe->salen); + vstring_free(sockaddr_buf); + return (vstring_str(buf)); +} + + /* + * The mock name and its helper callbacks in one place. + */ +static const MOCK_APPL_SIG sockaddr_to_hostaddr_sig = { + "sockaddr_to_hostaddr", + match_sockaddr_to_hostaddr, + assign_sockaddr_to_hostaddr, + print_sockaddr_to_hostaddr, + pmock_expect_free, +}; + +/* _expect_sockaddr_to_hostaddr - binary address to printable address form */ + +void _expect_sockaddr_to_hostaddr(const char *file, int line, + int calls_expected, int retval, + const struct sockaddr *sa, + SOCKADDR_SIZE salen, + MAI_HOSTADDR_STR *hostaddr, + MAI_SERVPORT_STR *portnum, + int socktype) +{ + struct sockaddr_to_hostaddr_expectation *pe; + + pe = (struct sockaddr_to_hostaddr_expectation *) + pmock_expect_create(&sockaddr_to_hostaddr_sig, + file, line, calls_expected, sizeof(*pe)); + pe->retval = retval; + memcpy((void *) &pe->sa, (void *) sa, salen); + pe->salen = salen; + if (pe->retval == 0 && hostaddr) { + *(pe->hostaddr = &pe->hostaddr_storage) = *hostaddr; + } else { + pe->hostaddr = 0; + } + if (pe->retval == 0 && portnum) { + *(pe->portnum = &pe->portnum_storage) = *portnum; + } else { + pe->portnum = 0; + } + pe->socktype = socktype; +} + +/* sockaddr_to_hostaddr - mock sockaddr_to_hostaddr */ + +int sockaddr_to_hostaddr(const struct sockaddr *sa, + SOCKADDR_SIZE salen, + MAI_HOSTADDR_STR *hostaddr, + MAI_SERVPORT_STR *portnum, + int socktype) +{ + struct sockaddr_to_hostaddr_expectation inputs; + struct sockaddr_to_hostaddr_targets targets; + int retval = EAI_FAIL; + + /* + * Bundle the arguments to simplify handling. + */ + memcpy((void *) &inputs.sa, (void *) sa, salen); + inputs.salen = salen; + inputs.socktype = socktype; + + targets.hostaddr = hostaddr; + targets.portnum = portnum; + targets.retval = &retval; + + /* + * TODO: bail out if there was no match? + */ + (void) pmock_expect_apply(&sockaddr_to_hostaddr_sig, + &inputs.mock_expect, (void *) &targets); + return (retval); +} + + /* + * Manage sockaddr_to_hostname() expectations and responses. We use this + * structure for deep copies of expect_sockaddr_to_hostname() expected + * inputs and prepared responses, and for shallow copies of + * sockaddr_to_hostname() inputs, so that we can reuse the + * print_sockaddr_to_hostname() helper. + */ +struct sockaddr_to_hostname_expectation { + MOCK_EXPECT mock_expect; /* generic fields */ + int retval; /* result value */ + struct sockaddr_storage sa; /* inputs */ + SOCKADDR_SIZE salen; + int socktype; + MAI_HOSTNAME_STR *hostname; /* other outputs */ + MAI_SERVNAME_STR *service; + MAI_HOSTNAME_STR hostname_storage; + MAI_SERVNAME_STR service_storage; +}; + + /* + * Pointers to sockaddr_to_hostname() outputs. + */ +struct sockaddr_to_hostname_targets { + MAI_HOSTNAME_STR *hostname; + MAI_SERVNAME_STR *service; + int *retval; +}; + +/* match_sockaddr_to_hostname - match inputs against expectation */ + +static int match_sockaddr_to_hostname(const MOCK_EXPECT *expect, + const MOCK_EXPECT *inputs) +{ + struct sockaddr_to_hostname_expectation *pe = + (struct sockaddr_to_hostname_expectation *) expect; + struct sockaddr_to_hostname_expectation *pi = + (struct sockaddr_to_hostname_expectation *) inputs; + + return (pe->salen == pi->salen + && memcmp(&pe->sa, &pi->sa, pe->salen) == 0 + && pe->socktype == pi->socktype); +} + +/* assign_sockaddr_to_hostname - assign expected output */ + +static void assign_sockaddr_to_hostname(const MOCK_EXPECT *expect, + void *targets) +{ + struct sockaddr_to_hostname_expectation *pe = + (struct sockaddr_to_hostname_expectation *) expect; + struct sockaddr_to_hostname_targets *pt = + (struct sockaddr_to_hostname_targets *) targets; + + if (pe->retval == 0) { + if (pe->hostname && pt->hostname) + *pt->hostname = *pe->hostname; + if (pe->service && pt->service) + *pt->service = *pe->service; + } + *pt->retval = pe->retval; +} + +/* print_sockaddr_to_hostname - print expected inputs */ + +static char *print_sockaddr_to_hostname(const MOCK_EXPECT *expect, + VSTRING *buf) +{ + struct sockaddr_to_hostname_expectation *pe = + (struct sockaddr_to_hostname_expectation *) expect; + VSTRING *sockaddr_buf = vstring_alloc(100); + + vstring_sprintf(buf, "%s, %ld, (ptr), (ptr)", + sockaddr_to_string(sockaddr_buf, + (struct sockaddr *) &pe->sa, + pe->salen), + (long) pe->salen); + vstring_free(sockaddr_buf); + return (vstring_str(buf)); +} + + /* + * The mock name and its helper callbacks in one place. + */ +static const MOCK_APPL_SIG sockaddr_to_hostname_sig = { + "sockaddr_to_hostname", + match_sockaddr_to_hostname, + assign_sockaddr_to_hostname, + print_sockaddr_to_hostname, + pmock_expect_free, +}; + +/* _expect_sockaddr_to_hostname - set up expectations */ + +void _expect_sockaddr_to_hostname(const char *file, int line, + int calls_expected, int retval, + const struct sockaddr *sa, + SOCKADDR_SIZE salen, + MAI_HOSTNAME_STR *hostname, + MAI_SERVNAME_STR *service, + int socktype) +{ + struct sockaddr_to_hostname_expectation *pe; + + pe = (struct sockaddr_to_hostname_expectation *) + pmock_expect_create(&sockaddr_to_hostname_sig, + file, line, calls_expected, sizeof(*pe)); + pe->retval = retval; + memcpy((void *) &pe->sa, (void *) sa, salen); + pe->salen = salen; + if (retval == 0 && hostname) { + *(pe->hostname = &pe->hostname_storage) = *hostname; + } else { + pe->hostname = 0; + } + if (retval == 0 && service) { + *(pe->service = &pe->service_storage) = *service; + } else { + pe->service = 0; + } + pe->socktype = socktype; +} + +/* sockaddr_to_hostname - mock sockaddr_to_hostname */ + +int sockaddr_to_hostname(const struct sockaddr *sa, + SOCKADDR_SIZE salen, + MAI_HOSTNAME_STR *hostname, + MAI_SERVNAME_STR *service, + int socktype) +{ + struct sockaddr_to_hostname_expectation inputs; + struct sockaddr_to_hostname_targets targets; + int retval = EAI_FAIL; + + /* + * Bundle the arguments to simplify handling. + */ + memcpy((void *) &inputs.sa, (void *) sa, salen); + inputs.salen = salen; + inputs.socktype = socktype; + + targets.hostname = hostname; + targets.service = service; + targets.retval = &retval; + + /* + * TODO: bail out if there was no match? + */ + (void) pmock_expect_apply(&sockaddr_to_hostname_sig, + &inputs.mock_expect, (void *) &targets); + return (retval); +} diff --git a/postfix/src/testing/mock_myaddrinfo.h b/postfix/src/testing/mock_myaddrinfo.h new file mode 100644 index 000000000..6f2461df3 --- /dev/null +++ b/postfix/src/testing/mock_myaddrinfo.h @@ -0,0 +1,76 @@ +#ifndef _MOCK_MYADDRINFO_H_INCLUDED_ +#define _MOCK_MYADDRINFO_H_INCLUDED_ + +/*++ +/* NAME +/* mock_myaddrinfo 3h +/* SUMMARY +/* myaddrinfo mock for hermetic tests +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Test library. + */ +#include +#include +#include +#include +#include + + /* + * Manage expectations and responses. Capture the source file name and line + * number for better diagnostics. + */ +#define expect_hostname_to_sockaddr_pf(...) \ + _expect_hostname_to_sockaddr_pf(__FILE__, __LINE__, __VA_ARGS__) +#define expect_hostaddr_to_sockaddr(...) \ + _expect_hostaddr_to_sockaddr(__FILE__, __LINE__, __VA_ARGS__) +#define expect_sockaddr_to_hostaddr(...) \ + _expect_sockaddr_to_hostaddr(__FILE__, __LINE__, __VA_ARGS__) +#define expect_sockaddr_to_hostname(...) \ + _expect_sockaddr_to_hostname(__FILE__, __LINE__, __VA_ARGS__) + +extern void _expect_hostname_to_sockaddr_pf(const char *, int, int, int, + const char *, int, const char *, + int, struct addrinfo *); +extern void _expect_hostaddr_to_sockaddr(const char *, int, int, int, + const char *, const char *, + int, struct addrinfo *); +extern void _expect_sockaddr_to_hostaddr(const char *, int, int, int, + const struct sockaddr *, + SOCKADDR_SIZE, + MAI_HOSTADDR_STR *, + MAI_SERVPORT_STR *, int); +extern void _expect_sockaddr_to_hostname(const char *, int, int, int, + const struct sockaddr *, + SOCKADDR_SIZE, + MAI_HOSTNAME_STR *, + MAI_SERVNAME_STR *, int); + +#define expect_hostname_to_sockaddr(count, ret, host, serv, sock, res) \ + expect_hostname_to_sockaddr_pf((count), (ret), (host), PF_UNSPEC, \ + (serv), (sock), (res)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Wietse Venema +/* porcupine.org +/*--*/ + +#endif diff --git a/postfix/src/testing/mock_myaddrinfo_test.c b/postfix/src/testing/mock_myaddrinfo_test.c new file mode 100644 index 000000000..4d4131044 --- /dev/null +++ b/postfix/src/testing/mock_myaddrinfo_test.c @@ -0,0 +1,343 @@ + /* + * Test program to exercise mocks including logging. See comments in + * ptest_main.h and pmock_expect_test.c for a documented example. + */ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Test library. + */ +#include +#include +#include + +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 diff --git a/postfix/src/testing/mock_servent.c b/postfix/src/testing/mock_servent.c new file mode 100644 index 000000000..bd3d4c090 --- /dev/null +++ b/postfix/src/testing/mock_servent.c @@ -0,0 +1,474 @@ +/*++ +/* NAME +/* mock_servent 3 +/* SUMMARY +/* getservbyname mock for hermetic tests +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Test library. + */ +#include +#include + + /* + * Helpers. + */ +#define MYSTRDUP_OR_NULL(x) ((x) ? mystrdup(x) : 0) +#define STR_OR_NULL(s) ((s) ? (s) : "(null)") + +#define STR(x) vstring_str(x) + +/* copy_servent - deep copy */ + +static struct servent *copy_servent(struct servent * src) +{ + struct servent *dst; + + dst = (struct servent *) mymalloc(sizeof(*dst)); + dst->s_name = MYSTRDUP_OR_NULL(src->s_name); + dst->s_aliases = mymalloc(sizeof(*dst->s_aliases)); + dst->s_aliases[0] = 0; + dst->s_port = src->s_port; + dst->s_proto = MYSTRDUP_OR_NULL(src->s_proto); + return (dst); +} + +/* make_servent - create mock servent instance */ + +struct servent *make_servent(const char *name, int port, const char *proto) +{ + struct servent *dst; + + dst = (struct servent *) mymalloc(sizeof(*dst)); + dst->s_name = MYSTRDUP_OR_NULL(name); + dst->s_aliases = mymalloc(sizeof(*dst->s_aliases)); + dst->s_aliases[0] = 0; + dst->s_port = htons(port); + dst->s_proto = MYSTRDUP_OR_NULL(proto); + return (dst); +} + +/* free_servent - destructor */ + +void free_servent(struct servent * ent) +{ + if (ent->s_name) + myfree(ent->s_name); + if (ent->s_aliases) + myfree((char *) ent->s_aliases); + if (ent->s_proto) + myfree(ent->s_proto); + myfree(ent); +} + +/* eq_aliases - equality predicate */ + +static int eq_aliases(PTEST_CTX *t, const char *file, int line, const char *what, + char **got, char **want) +{ + if (got[0] == 0 && want[0] == 0) + return (1); + if (got[0] == 0 || want[0] == 0) { + if (t) + ptest_error(t, "%s: got alias %s, want %s", + what, got[0] ? got[0] : "(null)", + want[0] ? want[0] : "(null)"); + return (0); + } + if (strcmp(got[0], want[0]) != 0) { + if (t) + ptest_error(t, "%s: got alias '%s', want '%s'", + what, got[0], want[0]); + return (0); + } + return (1); +} + +/* _eq_servent - equality predicate */ + +int _eq_servent(const char *file, int line, PTEST_CTX *t, + const char *what, + struct servent * got, struct servent * want) +{ + if (got == 0 && want == 0) + return (1); + if (got == 0 || want == 0) { + if (t) + ptest_error(t, "%s: got %s, want %s", + what, got ? "(struct servent *)" : "(null)", + want ? "(struct servent *)" : "(null)"); + return (0); + } + if (strcmp(got->s_name, want->s_name) != 0) { + if (t) + ptest_error(t, "%s: got name '%s', want '%s'", + what, got->s_name, want->s_name); + return (0); + } + if (!eq_aliases(t, file, line, what, got->s_aliases, want->s_aliases)) + return (0); + if (got->s_port != want->s_port) { + if (t) + ptest_error(t, "%s: got port %d, want %d", + what, ntohs(got->s_port), ntohs(want->s_port)); + return (0); + } + if (strcmp(got->s_proto, want->s_proto) != 0) { + if (t) + ptest_error(t, "%s: got proto '%s', want '%s'", + what, got->s_proto, want->s_proto); + return (0); + } + return (1); +} + + /* + * Manage getservbyname() expectations and responses. We use this structure + * for deep copies of expect_getservbyname() expected inputs and prepared + * responses, and for shallow copies of getservbyname() inputs. + */ +struct getservbyname_expectation { + MOCK_EXPECT mock_expect; /* generic fields */ + char *name; /* inputs */ + char *proto; + struct servent *retval; /* outputs */ +}; + + /* + * Pointers to getservbyname() outputs. + */ +struct getservbyname_targets { + struct servent **retval; +}; + +/* match_getservbyname - match inputs against expectation */ + +static int match_getservbyname(const MOCK_EXPECT *expect, + const MOCK_EXPECT *inputs) +{ + struct getservbyname_expectation *pe = + (struct getservbyname_expectation *) expect; + struct getservbyname_expectation *pi = + (struct getservbyname_expectation *) inputs; + + return (strcmp(STR_OR_NULL(pe->name), + STR_OR_NULL(pi->name)) == 0 + && strcmp(STR_OR_NULL(pe->proto), + STR_OR_NULL(pi->proto)) == 0); +} + +/* assign_getservbyname - assign expected output */ + +static void assign_getservbyname(const MOCK_EXPECT *expect, + void *targets) +{ + struct getservbyname_expectation *pe = + (struct getservbyname_expectation *) expect; + struct getservbyname_targets *pt = + (struct getservbyname_targets *) targets; + + /* Not a deep copy. */ + *pt->retval = pe->retval; +} + +/* print_getservbyname - print expected inputs */ + +static char *print_getservbyname(const MOCK_EXPECT *expect, + VSTRING *buf) +{ + struct getservbyname_expectation *pe = + (struct getservbyname_expectation *) expect; + + vstring_sprintf(buf, "\"%s\", \"%s\"", + STR_OR_NULL(pe->name), STR_OR_NULL(pe->proto)); + return (STR(buf)); +} + +/* free_getservbyname - destructor */ + +static void free_getservbyname(MOCK_EXPECT *expect) +{ + struct getservbyname_expectation *pe = + (struct getservbyname_expectation *) expect; + + if (pe->name) + myfree(pe->name); + if (pe->proto) + myfree(pe->proto); + if (pe->retval) + free_servent(pe->retval); + pmock_expect_free(expect); +} + + /* + * The mock name and its helper callbacks in one place. + */ +static const MOCK_APPL_SIG getservbyname_sig = { + "getservbyname", + match_getservbyname, + assign_getservbyname, + print_getservbyname, + free_getservbyname, +}; + +/* _expect_getservbyname - set up expectation */ + +void _expect_getservbyname(const char *file, int line, + int calls_expected, struct servent * retval, + const char *name, const char *proto) +{ + struct getservbyname_expectation *pe; + + pe = (struct getservbyname_expectation *) + pmock_expect_create(&getservbyname_sig, + file, line, calls_expected, sizeof(*pe)); + /* Inputs. */ + pe->name = MYSTRDUP_OR_NULL(name); + pe->proto = MYSTRDUP_OR_NULL(proto); + /* Outputs. */ + pe->retval = retval ? copy_servent(retval) : retval; +} + +/* getservbyname - answer the call with prepared responses */ + +struct servent *getservbyname(const char *name, const char *proto) +{ + struct getservbyname_expectation inputs; + struct getservbyname_targets targets; + struct servent *retval = 0; + + /* + * Bundle the arguments to simplify handling. + */ + inputs.name = (char *) name; + inputs.proto = (char *) proto; + + targets.retval = &retval; + + /* + * TODO: bail out if there was no match? + */ + (void) pmock_expect_apply(&getservbyname_sig, + &inputs.mock_expect, (void *) &targets); + return (retval); +} + + /* + * Manage setservent() expectations. This function has no outputs. + */ +struct setservent_expectation { + MOCK_EXPECT mock_expect; /* generic fields */ + int stayopen; /* input */ +}; + +static int match_setservent(const MOCK_EXPECT *expect, + const MOCK_EXPECT *inputs) +{ + struct setservent_expectation *pe = + (struct setservent_expectation *) expect; + struct setservent_expectation *pi = + (struct setservent_expectation *) inputs; + + return (pe->stayopen == pi->stayopen); +} + +/* print_setservent - print expected inputs */ + +static char *print_setservent(const MOCK_EXPECT *expect, + VSTRING *buf) +{ + struct setservent_expectation *pe = + (struct setservent_expectation *) expect; + + vstring_sprintf(buf, "%d", pe->stayopen); + return (STR(buf)); +} + + /* + * The mock name and its helper callbacks in one place. + */ +static const MOCK_APPL_SIG setservent_sig = { + "setservent", + match_setservent, + 0, /* no outputs to assign */ + print_setservent, + pmock_expect_free, +}; + +/* _expect_setservent - set up expectation */ + +void _expect_setservent(const char *file, int line, + int calls_expected, int stayopen) +{ + struct setservent_expectation *pe; + + pe = (struct setservent_expectation *) + pmock_expect_create(&setservent_sig, + file, line, calls_expected, sizeof(*pe)); + /* Inputs. */ + pe->stayopen = stayopen; +} + +/* setservent - answer the call */ + +void setservent(int stayopen) +{ + struct setservent_expectation inputs; + + /* + * Bundle the arguments to simplify handling. + */ + inputs.stayopen = stayopen; + + /* + * TODO: bail out if there was no match? + */ + (void) pmock_expect_apply(&setservent_sig, + &inputs.mock_expect, (void *) 0); +} + + /* + * Manage endservent() expectations. This function has no arguments (all + * calls will match), and no outputs. + */ + +/* print_endservent - print expected inputs */ + +static char *print_endservent(const MOCK_EXPECT *expect, + VSTRING *buf) +{ + VSTRING_RESET(buf); + VSTRING_TERMINATE(buf); + return (STR(buf)); +} + + /* + * The mock name and its helper callbacks in one place. + */ +static const MOCK_APPL_SIG endservent_sig = { + "endservent", + 0, /* no inputs to match */ + 0, /* no outputs to assign */ + print_endservent, + pmock_expect_free, +}; + +/* _expect_endservent - set up expectation */ + +void _expect_endservent(const char *file, int line, int calls_expected) +{ + (void) pmock_expect_create(&endservent_sig, + file, line, calls_expected, sizeof(MOCK_EXPECT)); +} + +/* endservent - return no answer */ + +void endservent(void) +{ + + /* + * TODO: bail out if there was no match? + */ + (void) pmock_expect_apply(&endservent_sig, (MOCK_EXPECT *) 0, (void *) 0); +} diff --git a/postfix/src/testing/mock_servent.h b/postfix/src/testing/mock_servent.h new file mode 100644 index 000000000..4860c6d28 --- /dev/null +++ b/postfix/src/testing/mock_servent.h @@ -0,0 +1,76 @@ +#ifndef _MOCK_SERVENT_H_INCLUDED_ +#define _MOCK_SERVENT_H_INCLUDED_ + +/*++ +/* NAME +/* mock_servent 3h +/* SUMMARY +/* getservbyname mock for hermetic tests +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Test library. + */ +#include + + /* + * Manage expectations and responses. Capture the source file name and line + * number for better diagnostics. + */ +#define expect_getservbyname(...) \ + _expect_getservbyname(__FILE__, __LINE__, __VA_ARGS__) + +extern void _expect_getservbyname(const char *, int, int, struct servent *, + const char *, const char *); + +#define expect_setservent(...) \ + _expect_setservent(__FILE__, __LINE__, __VA_ARGS__) + +extern void _expect_setservent(const char *, int, int, int); + +#define expect_endservent(...) \ + _expect_endservent(__FILE__, __LINE__, __VA_ARGS__) + +extern void _expect_endservent(const char *, int, int); + + /* + * Matcher predicates. Capture the source file name and line number for + * better diagnostics. + */ +#define eq_servent(...) _eq_servent(__FILE__, __LINE__, __VA_ARGS__) + +extern int _eq_servent(const char *, int,PTEST_CTX *, + const char *, struct servent *, + struct servent *); + + /* + * Helper to create test data. + */ +extern struct servent *make_servent(const char *, int, const char *); + +extern void free_servent(struct servent *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Wietse Venema +/* porcupine.org +/*--*/ + +#endif diff --git a/postfix/src/testing/mock_servent_test.c b/postfix/src/testing/mock_servent_test.c new file mode 100644 index 000000000..29ffde89f --- /dev/null +++ b/postfix/src/testing/mock_servent_test.c @@ -0,0 +1,241 @@ + /* + * Test program to exercise mocks including logging. See pmock_expect_test.c + * and ptest_main.h for a documented example. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include + + /* + * Test library. + */ +#include +#include +#include + +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 diff --git a/postfix/src/testing/mock_server.c b/postfix/src/testing/mock_server.c new file mode 100644 index 000000000..458674498 --- /dev/null +++ b/postfix/src/testing/mock_server.c @@ -0,0 +1,332 @@ +/*++ +/* NAME +/* mock_server 3 +/* SUMMARY +/* Mock server for hermetic tests +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * Test libraries + */ +#include +#include +#include + + /* + * Macros to make obscure code more readable. + */ +#define COPY_VSTRING_OR_NULL(dst, src) do { \ + if ((src) != 0) { \ + if ((dst) == 0) \ + (dst) = vstring_alloc(LEN(src)); \ + vstring_memcpy((dst), STR(src), LEN(src)); \ + } else if ((dst) != 0) { \ + vstring_free(dst); \ + (dst) = 0; \ + } \ + } while (0) + +#define MOCK_SERVER_REQUEST_READ_EVENT(fd, action, context, timeout) do { \ + if (msg_verbose > 1) \ + msg_info("%s: read-request fd=%d", myname, (fd)); \ + event_enable_read((fd), (action), (context)); \ + event_request_timer((action), (context), (timeout)); \ + } while (0) + +#define MOCK_SERVER_CLEAR_EVENT_REQUEST(fd, action, context) do { \ + if (msg_verbose > 1) \ + msg_info("%s: clear-request fd=%d", myname, (fd)); \ + event_disable_readwrite(fd); \ + event_cancel_timer((action), (context)); \ + } while (0) + +#define MOCK_SERVER_TIMEOUT 10 + +#define MOCK_SERVER_SIDE (0) +#define MOCK_CLIENT_SIDE (1) + + /* + * Other macros. + */ +#define STR vstring_str +#define LEN VSTRING_LEN + + /* + * List head. We could use a RING object and save a few bits in data space, + * at the cost of more complex code. + */ +static MOCK_SERVER mock_unix_server_head; + +/* mock_unix_server_create - instantiate an unconnected mock server */ + +MOCK_SERVER *mock_unix_server_create(const char *dest) +{ + MOCK_SERVER *mp; + + mp = (MOCK_SERVER *) mymalloc(sizeof(*mp)); + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, mp->fds) < 0) { + myfree(mp); + ptest_error(ptest_ctx_current(), "mock_unix_server_create(%s): " + "socketpair(AF_LOCAL, SOCK_STREAM, 0, fds): %m", dest); + return (0); + } + mp->flags = 0; + mp->want_dest = mystrdup(dest); + mp->want_req = 0; + mp->resp = 0; + mp->iobuf = 0; + + /* + * Link the mock server into the waiting list. + */ + mp->next = mock_unix_server_head.next; + mock_unix_server_head.next = mp; + mp->prev = &mock_unix_server_head; + if (mp->next) + mp->next->prev = mp; + return (mp); +} + +/* mock_server_respond - send a prepared response */ + +static void mock_server_respond(MOCK_SERVER *mp) +{ + const char myname[] = "mock_server_respond"; + ssize_t got_len; + + got_len = write(mp->fds[MOCK_SERVER_SIDE], STR(mp->resp), LEN(mp->resp)); + if (got_len < 0) + ptest_fatal(ptest_ctx_current(), "%s: write: %m", myname); + if (got_len != LEN(mp->resp)) + ptest_fatal(ptest_ctx_current(), "%s: wrote %ld of %ld bytes", + myname, (long) got_len, (long) LEN(mp->resp)); +} + +/* mock_server_read_event - receive request and respond */ + +static void mock_server_read_event(int event, void *context) +{ + const char myname[] = "mock_server_read_event"; + MOCK_SERVER *mp = (MOCK_SERVER *) context; + ssize_t peek_len; + ssize_t got_len; + + /* + * Disarm this file descriptor. + */ + MOCK_SERVER_CLEAR_EVENT_REQUEST(mp->fds[MOCK_SERVER_SIDE], + mock_server_read_event, + context); + + /* + * Handle the event. + */ + switch (event) { + case EVENT_READ: + break; + case EVENT_TIME: + ptest_error(ptest_ctx_current(), "%s: timeout", myname); + return; + default: + ptest_fatal(ptest_ctx_current(), "%s: unexpected event: %d", + myname, event); + } + + /* + * Receive a request, if one is expected. + */ + switch (peek_len = peekfd(mp->fds[MOCK_SERVER_SIDE])) { + case -1: + ptest_error(ptest_ctx_current(), "%s: read: %m", myname); + return; + case 0: + ptest_error(ptest_ctx_current(), "%s: read EOF", myname); + return; + default: + break; + } + if (mp->iobuf == 0) + mp->iobuf = vstring_alloc(1000); + else + VSTRING_RESET(mp->iobuf); + VSTRING_SPACE(mp->iobuf, peek_len); + got_len = read(mp->fds[MOCK_SERVER_SIDE], STR(mp->iobuf), peek_len); + if (got_len != peek_len) { + ptest_fatal(ptest_ctx_current(), "%s: read %ld of %ld bytes", + myname, (long) got_len, (long) peek_len); + return; + } + vstring_set_payload_size(mp->iobuf, got_len); + if (!eq_attr(ptest_ctx_current(), "request", mp->iobuf, mp->want_req)) + return; + + /* + * Send the response, if available. + */ + else if (mp->resp) + mock_server_respond(mp); +} + +/* mock_server_interact - set up request and/or response */ + +void mock_server_interact(MOCK_SERVER *mp, + const VSTRING *req, + const VSTRING *resp) +{ + const char myname[] = "mock_server_interact"; + + if (req == 0 && resp == 0) + ptest_fatal(ptest_ctx_current(), "%s: null request and null response", + myname); + COPY_VSTRING_OR_NULL(mp->want_req, req); + COPY_VSTRING_OR_NULL(mp->resp, resp); + if (req != 0) { + MOCK_SERVER_REQUEST_READ_EVENT(mp->fds[MOCK_SERVER_SIDE], + mock_server_read_event, + (void *) mp, MOCK_SERVER_TIMEOUT); + } else { + mock_server_respond(mp); + } +} + +/* mock_server_unlink - detach one instance from its waiting list */ + +static void mock_server_unlink(MOCK_SERVER *mp) +{ + if (mp->next) + mp->next->prev = mp->prev; + if (mp->prev) + mp->prev->next = mp->next; + mp->prev = mp->next = 0; +} + +/* mock_server_free - destroy mock server */ + +void mock_server_free(MOCK_SERVER *mp) +{ + const char myname[] = "mock_server_free"; + + myfree(mp->want_dest); + if ((mp->flags & MOCK_SERVER_FLAG_CONNECTED) == 0) + (void) close(mp->fds[MOCK_CLIENT_SIDE]); + MOCK_SERVER_CLEAR_EVENT_REQUEST(mp->fds[MOCK_SERVER_SIDE], + mock_server_read_event, mp); + (void) close(mp->fds[MOCK_SERVER_SIDE]); + if (mp->want_req) + vstring_free(mp->want_req); + if (mp->resp) + vstring_free(mp->resp); + if (mp->iobuf) + vstring_free(mp->iobuf); + mock_server_unlink(mp); + myfree(mp); +} + +/* mock_server_free_void_ptr - destroy mock server */ + +void mock_server_free_void_ptr(void *ptr) +{ + mock_server_free(ptr); +} + +/* unix_connect - mock helper */ + +int unix_connect(const char *dest, int block_mode, int unused_timeout) +{ + MOCK_SERVER *mp; + + for (mp = mock_unix_server_head.next; /* see below */ ; mp = mp->next) { + if (mp == 0) { + errno = ENOENT; + return (-1); + } + if (strcmp(dest, mp->want_dest) == 0) { + mock_server_unlink(mp); + if (block_mode == NON_BLOCKING) + non_blocking(mp->fds[MOCK_CLIENT_SIDE], block_mode); + mp->flags |= MOCK_SERVER_FLAG_CONNECTED; + return (mp->fds[MOCK_CLIENT_SIDE]); + } + } +} diff --git a/postfix/src/testing/mock_server.h b/postfix/src/testing/mock_server.h new file mode 100644 index 000000000..c97d63449 --- /dev/null +++ b/postfix/src/testing/mock_server.h @@ -0,0 +1,53 @@ +#ifndef _MOCK_SERVER_H_INCLUDED_ +#define _MOCK_SERVER_H_INCLUDED_ + +/*++ +/* NAME +/* mock_server 3h +/* SUMMARY +/* Mock server support +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +typedef struct MOCK_SERVER { + int flags; + int fds[2]; /* pipe(2) result */ + char *want_dest; + VSTRING *want_req; /* serialized request, may be null */ + VSTRING *resp; /* serialized response, may be null */ + VSTRING *iobuf; /* I/O buffer */ + struct MOCK_SERVER *next; /* chain of unconnected servers */ + struct MOCK_SERVER *prev; /* chain of unconnected servers */ +} MOCK_SERVER; + +#define MOCK_SERVER_FLAG_CONNECTED (1<<0) + +extern MOCK_SERVER *mock_unix_server_create(const char *); +extern void mock_server_interact(MOCK_SERVER *, const VSTRING *, + const VSTRING *); +extern void mock_server_free(MOCK_SERVER *); +extern void mock_server_free_void_ptr(void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/testing/mock_server_test.c b/postfix/src/testing/mock_server_test.c new file mode 100644 index 000000000..e334d05f0 --- /dev/null +++ b/postfix/src/testing/mock_server_test.c @@ -0,0 +1,426 @@ + /* + * Test program to exercise mock_server.c. See PTEST_README for + * documentation for how this file is structured. + */ + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include + + /* + * Test library. + */ +#include +#include +#include + + /* + * 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 diff --git a/postfix/src/tlsmgr/Makefile.in b/postfix/src/tlsmgr/Makefile.in index 0ccf3e964..6e610cf77 100644 --- a/postfix/src/tlsmgr/Makefile.in +++ b/postfix/src/tlsmgr/Makefile.in @@ -2,7 +2,7 @@ SHELL = /bin/sh SRCS = tlsmgr.c OBJS = tlsmgr.o HDRS = -TESTSRC = smtpd_token_test.c +TESTSRC = DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) TESTPROG= diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index b7157beb2..0a0b023af 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -34,7 +34,7 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \ allascii.c load_file.c killme_after.c vstream_tweak.c \ pass_trigger.c edit_file.c inet_windowsize.c \ unix_pass_fd_fix.c dict_cache.c valid_utf8_string.c dict_thash.c \ - ip_match.c nbbio.c base32_code.c dict_test.c \ + ip_match.c nbbio.c base32_code.c dict_cli.c \ dict_fail.c msg_rate_delay.c dict_surrogate.c warn_stat.c \ dict_sockmap.c line_number.c recv_pass_attr.c pass_accept.c \ poll_fd.c timecmp.c slmdb.c dict_pipe.c dict_random.c \ @@ -48,7 +48,7 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \ inet_addr_sizes.c quote_for_json.c mystrerror.c \ sane_sockaddr_to_hostaddr.c normalize_ws.c valid_uri_scheme.c \ clean_ascii_cntrl_space.c normalize_v4mapped_addr.c ossl_digest.c \ - mac_midna.c wrap_stat.c dynamicmaps.c + mac_midna.c wrap_stat.c dynamicmaps.c find_inet_service.c wrap_netdb.c OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \ attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \ @@ -84,7 +84,7 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ allascii.o load_file.o killme_after.o vstream_tweak.o \ pass_trigger.o edit_file.o inet_windowsize.o \ unix_pass_fd_fix.o dict_cache.o valid_utf8_string.o dict_thash.o \ - ip_match.o nbbio.o base32_code.o dict_test.o \ + ip_match.o nbbio.o base32_code.o dict_cli.o \ dict_fail.o msg_rate_delay.o dict_surrogate.o warn_stat.o \ dict_sockmap.o line_number.o recv_pass_attr.o pass_accept.o \ poll_fd.o timecmp.o $(NON_PLUGIN_MAP_OBJ) dict_pipe.o dict_random.o \ @@ -98,7 +98,7 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ quote_for_json.o mystrerror.o sane_sockaddr_to_hostaddr.o \ normalize_ws.o valid_uri_scheme.o clean_ascii_cntrl_space.o \ normalize_v4mapped_addr.o ossl_digest.o mac_midna.o wrap_stat.o \ - dynamicmaps.o + dynamicmaps.o find_inet_service.o wrap_netdb.o # MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf. # When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ), # otherwise it sets the PLUGIN_* macros. @@ -133,9 +133,13 @@ HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \ known_tcp_ports.h sane_strtol.h hash_fnv.h ldseed.h mkmap.h \ inet_prefix_top.h inet_addr_sizes.h valid_uri_scheme.h \ clean_ascii_cntrl_space.h normalize_v4mapped_addr.h ossl_digest.h \ - mac_midna.h wrap_stat.h dynamicmaps.h + mac_midna.h wrap_stat.h dynamicmaps.h find_inet_service.h wrap_netdb.h TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ - stream_test.c dup2_pass_on_exec.c + sunos5_stream_test.c dup2_pass_on_exec.c argv_test.c dict_pipe_test.c \ + dict_stream_test.c dict_cli.c dict_union_test.c \ + find_inet_service_test.c hash_fnv_test.c known_tcp_ports_test.c \ + msg_output_test.c myaddrinfo_test.c mymalloc_test.c mystrtok_test.c \ + unescape_test.c DEFS = -I. -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) FILES = Makefile $(SRCS) $(HDRS) @@ -144,9 +148,9 @@ LIB = lib$(LIB_PREFIX)util$(LIB_SUFFIX) TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \ fifo_rdonly_bug fifo_rdwr_bug fifo_trigger fsspace fullname \ inet_addr_host inet_addr_local mac_parse make_dirs msg_syslog \ - mystrtok sigdelay translit valid_hostname vstream_popen \ - vstring vstring_vstream doze select_bug stream_test mac_expand \ - watchdog unescape hex_quote name_mask rand_sleep sane_time ctable \ + mystrtok_test sigdelay translit valid_hostname vstream_popen \ + vstring vstring_vstream doze select_bug sunos5_stream_test mac_expand \ + watchdog unescape_test hex_quote name_mask rand_sleep sane_time ctable \ inet_addr_list attr_print64 attr_scan64 base64_code attr_print0 \ attr_scan0 host_port attr_scan_plain attr_print_plain htable \ unix_recv_fd unix_send_fd stream_recv_fd stream_send_fd hex_code \ @@ -154,24 +158,25 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \ valid_utf8_string ip_match base32_code msg_rate_delay netstring \ vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \ vbuf_print split_qnameval vstream msg_logger byte_mask \ - known_tcp_ports dict_stream find_inet binhash hash_fnv_test argv \ + known_tcp_ports_test dict_stream_test binhash hash_fnv_test \ + argv_test find_inet_service_test mymalloc_test msg_output_test \ + dict_union_test dict_pipe_test myaddrinfo_test \ clean_env inet_prefix_top printable readlline quote_for_json \ normalize_ws valid_uri_scheme clean_ascii_cntrl_space \ - normalize_v4mapped_addr_test ossl_digest_test dict_pipe_test \ - dict_union_test + normalize_v4mapped_addr_test ossl_digest_test PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) $(LIB_PREFIX)lmdb$(LIB_SUFFIX) \ $(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(LIB_SUFFIX) \ $(LIB_PREFIX)db$(LIB_SUFFIX) HTABLE_FIX = NORANDOMIZE=1 LIB_DIR = ../../lib INC_DIR = ../../include -TESTLIB = $(LIB_DIR)/libtesting.a +TESTLIBS= $(LIB_DIR)/libtesting.a $(LIB_DIR)/libptest.a .c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c all: $(LIB) $(PLUGIN_MAP_SO_MAKE) $(PLUGIN_MAP_OBJ) -$(OBJS) $(PLUGIN_MAP_OBJ): ../../conf/makedefs.out +$(OBJS) $(PLUGIN_MAP_OBJ) $(TESTPROG): ../../conf/makedefs.out Makefile: Makefile.in cat ../../conf/makedefs.out $? >$@ @@ -300,11 +305,6 @@ sigdelay: $(LIB) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o -mystrtok: $(LIB) - mv $@.o junk - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) - mv junk $@.o - fifo_rdwr_bug: fifo_rdwr_bug.c $(LIB) $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) @@ -364,11 +364,6 @@ watchdog: $(LIB) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o -unescape: $(LIB) - mv $@.o junk - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) - mv junk $@.o - printable: $(LIB) mv $@.o junk $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) @@ -464,8 +459,8 @@ binhash: $(LIB) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o -hash_fnv_test: $(LIB) update - $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) +hash_fnv_test: $(LIB) $(TESTLIBS) update + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(TESTLIBS) $(SYSLIBS) unix_recv_fd: $(LIB) mv $@.o junk @@ -522,8 +517,8 @@ clean_env: $(LIB) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o -stream_test: stream_test.c $(LIB) - $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) +sunos5_stream_test: sunos5_stream_test.o $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(LIB) $(SYSLIBS) gcctest: gccw.c gccw.ref rm -f gccw.o @@ -601,10 +596,11 @@ split_qnameval: $(LIB) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o -known_tcp_ports: $(LIB) - mv $@.o junk - $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) - mv junk $@.o +known_tcp_ports_test: known_tcp_ports_test.o $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS) + +test_known_tcp_ports: update known_tcp_ports_test + $(SHLIB_ENV) ${VALGRIND} ./known_tcp_ports_test dict_stream: $(LIB) mv $@.o junk @@ -641,22 +637,22 @@ clean_ascii_cntrl_space: $(LIB) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o -normalize_v4mapped_addr_test: normalize_v4mapped_addr_test.c $(LIB) - $(CC) $(CFLAGS) -o $@ normalize_v4mapped_addr_test.c $(LIB) $(SYSLIBS) +normalize_v4mapped_addr_test: normalize_v4mapped_addr_test.o $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(LIB) $(SYSLIBS) -ossl_digest_test: ossl_digest_test.c $(LIB) - $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) +ossl_digest_test: ossl_digest_test.o $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(LIB) $(SYSLIBS) -dict_pipe_test: dict_pipe_test.c $(TESTLIB) $(LIB) - $(CC) $(CFLAGS) -o $@ $@.c $(TESTLIB) $(LIB) $(SYSLIBS) +dict_pipe_test: dict_pipe_test.o $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS) -dict_union_test: dict_union_test.c $(TESTLIB) $(LIB) - $(CC) $(CFLAGS) -o $@ $@.c $(TESTLIB) $(LIB) $(SYSLIBS) +dict_union_test: dict_union_test.o $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS) -tests: all valid_hostname_test mac_expand_test dict_test unescape_test \ +tests: update valid_hostname_test mac_expand_test dict_test test_unescape \ hex_quote_test ctable_test inet_addr_list_test base64_code_test \ attr_scan64_test attr_scan0_test host_port_test dict_tests \ - attr_scan_plain_test htable_test hex_code_test myaddrinfo_test \ + attr_scan_plain_test htable_test hex_code_test test_myaddrinfo \ format_tv_test ip_match_test name_mask_tests base32_code_test \ surrogate_test timecmp_test midna_domain_test casefold_test \ strcasecmp_utf8_test vbuf_print_test miss_endif_cidr_test \ @@ -668,7 +664,7 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \ test_normalize_v4mapped_addr test_ossl_digest test_dict_pipe \ test_dict_union test_hash_fnv -dict_tests: all dict_test \ +dict_tests: dict_test \ dict_pcre_tests dict_cidr_test dict_thash_test dict_static_test \ dict_inline_test dict_utf8_test dict_regexp_test \ dict_regexp_file_test dict_cidr_file_test dict_seq_test \ @@ -691,12 +687,11 @@ mac_expand_test: mac_expand mac_expand.in mac_expand.ref diff mac_expand.ref mac_expand.tmp rm -f mac_expand.tmp -unescape_test: unescape unescape.in unescape.ref - $(SHLIB_ENV) ${VALGRIND} ./unescape unescape.tmp - diff -b unescape.ref unescape.tmp -# $(SHLIB_ENV) ${VALGRIND} ./unescape unescape.tmp -# diff unescape.in unescape.tmp - rm -f unescape.tmp +unescape_test: unescape_test.o $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS) + +test_unescape: update unescape_test + $(SHLIB_ENV) ${VALGRIND} ./unescape_test printable_test: printable $(SHLIB_ENV) ${VALGRIND} ./printable @@ -744,14 +739,15 @@ attr_scan0_test: attr_print0 attr_scan0 attr_scan0.ref rm -f attr_scan0.tmp dict_test: dict_open testdb dict_test.in dict_test.ref - rm -f testdb.db testdb.dir testdb.pag - $(SHLIB_ENV) ../postmap/postmap -N hash:testdb + rm -f testdb.db testdb.dir testdb.pag main.cf + touch -t 197601010000 main.cf + $(SHLIB_ENV) ../postmap/postmap -Nc. hash:testdb $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp diff dict_test.ref dict_test.tmp - $(SHLIB_ENV) ../postmap/postmap -n hash:testdb + $(SHLIB_ENV) ../postmap/postmap -nc. hash:testdb $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp diff dict_test.ref dict_test.tmp - rm -f testdb.db testdb.dir testdb.pag dict_test.tmp + rm -f testdb.db testdb.dir testdb.pag dict_test.tmp main.cf dict_pcre_test: dict_open dict_pcre.in dict_pcre.map dict_pcre.ref $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:dict_pcre.map read \ @@ -846,20 +842,13 @@ hex_code_test: hex_code timecmp_test: timecmp $(SHLIB_ENV) ${VALGRIND} ./timecmp -myaddrinfo_test: myaddrinfo myaddrinfo.ref myaddrinfo.ref2 - $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all belly.porcupine.org 168.100.3.2 >myaddrinfo.tmp 2>&1 - diff myaddrinfo.ref myaddrinfo.tmp - rm -f myaddrinfo.tmp - $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all null.porcupine.org 10.0.0.0 >myaddrinfo.tmp 2>&1 - diff myaddrinfo.ref2 myaddrinfo.tmp - rm -f myaddrinfo.tmp - -myaddrinfo4_test: myaddrinfo4 myaddrinfo4.ref myaddrinfo4.ref2 - $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all belly.porcupine.org 168.100.3.2 >myaddrinfo4.tmp 2>&1 - diff myaddrinfo4.ref myaddrinfo4.tmp - $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all null.porcupine.org 10.0.0.0 >myaddrinfo4.tmp 2>&1 - diff myaddrinfo4.ref2 myaddrinfo4.tmp - rm -f myaddrinfo4.tmp +myaddrinfo_test: myaddrinfo_test.o $(LIB_DIR)/mock_getaddrinfo.o \ + $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(LIB_DIR)/mock_getaddrinfo.o \ + $(TESTLIBS) $(LIB) $(SYSLIBS) + +test_myaddrinfo: update myaddrinfo_test + $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo_test format_tv_test: format_tv format_tv.in format_tv.ref $(SHLIB_ENV) ${VALGRIND} ./format_tv format_tv.tmp @@ -952,11 +941,13 @@ base32_code_test: base32_code $(SHLIB_ENV) ${VALGRIND} ./base32_code dict_thash_test: ../postmap/postmap dict_thash.map dict_thash.in dict_thash.ref - $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.map 2>&1 | \ + rm -f main.cf + touch -t 197601010000 main.cf + $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fsc. texthash:dict_thash.map 2>&1 | \ LANG=C sort | diff dict_thash.map - - NORANDOMIZE=1 $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.in >dict_thash.tmp 2>&1 + NORANDOMIZE=1 $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fsc. texthash:dict_thash.in >dict_thash.tmp 2>&1 diff dict_thash.ref dict_thash.tmp - rm -f dict_thash.tmp + rm -f dict_thash.tmp main.cf surrogate_test: dict_open surrogate.ref cp /dev/null surrogate.tmp @@ -1082,20 +1073,17 @@ vstream_test: vstream vstream_test.in vstream_test.ref diff vstream_test.ref vstream_test.tmp rm -f vstream_test.tmp -mystrtok_test: mystrtok mystrtok.ref - $(SHLIB_ENV) ${VALGRIND} ./mystrtok >mystrtok.tmp 2>&1 - diff mystrtok.ref mystrtok.tmp - rm -f mystrtok.tmp +mystrtok_test: mystrtok_test.o $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS) -known_tcp_ports_test: known_tcp_ports known_tcp_ports.ref - $(SHLIB_ENV) ${VALGRIND} ./known_tcp_ports >known_tcp_ports.tmp 2>&1 - diff known_tcp_ports.ref known_tcp_ports.tmp - rm -f known_tcp_ports.tmp +test_mystrtok: update mystrtok_test + $(SHLIB_ENV) ${VALGRIND} ./mystrtok_test -dict_stream_test: dict_stream dict_stream.ref - $(SHLIB_ENV) ${VALGRIND} ./dict_stream >dict_stream.tmp 2>&1 - diff dict_stream.ref dict_stream.tmp - rm -f dict_stream.tmp +dict_stream_test: dict_stream_test.o $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS) + +test_dict_stream: update dict_stream_test + $(SHLIB_ENV) ${VALGRIND} ./dict_stream_test dict_inline_pcre_test: dict_open dict_inline_pcre.ref (echo get foo; echo get bar) | \ @@ -1124,13 +1112,31 @@ dict_debug_test: dict_open dict_debug_test.sh dict_debug_test.ref diff dict_debug_test.ref dict_debug_test.tmp rm -f dict_debug_test.tmp -find_inet_test: find_inet find_inet.ref - $(SHLIB_ENV) ${VALGRIND} ./find_inet >find_inet.tmp 2>&1 - diff find_inet.ref find_inet.tmp - rm -f find_inet.tmp - -argv_test: argv - $(SHLIB_ENV) ${VALGRIND} ./argv +find_inet_service_test: find_inet_service_test.o $(LIB_DIR)/mock_servent.o \ + $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o find_inet_service.o \ + $(LIB_DIR)/mock_servent.o $(TESTLIBS) $(LIB) $(SYSLIBS) + +test_find_inet_service: update find_inet_service_test + $(SHLIB_ENV) ${VALGRIND} ./find_inet_service_test + +mymalloc_test: mymalloc_test.o $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS) + +test_mymalloc: update mymalloc_test + $(SHLIB_ENV) ${VALGRIND} ./mymalloc_test + +msg_output_test: msg_output_test.o $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS) + +test_msg_output: update msg_output_test + $(SHLIB_ENV) ${VALGRIND} ./msg_output_test + +argv_test: argv_test.o $(TESTLIBS) $(LIB) + $(CC) $(CFLAGS) -o $@ $@.o $(TESTLIBS) $(LIB) $(SYSLIBS) + +test_argv: update argv_test + $(SHLIB_ENV) ${VALGRIND} ./argv_test inet_prefix_top_test: inet_prefix_top $(SHLIB_ENV) ${VALGRIND} ./inet_prefix_top @@ -1255,6 +1261,22 @@ argv_splitq.o: stringops.h argv_splitq.o: sys_defs.h argv_splitq.o: vbuf.h argv_splitq.o: vstring.h +argv_test.o: ../../include/msg_jmp.h +argv_test.o: ../../include/pmock_expect.h +argv_test.o: ../../include/ptest.h +argv_test.o: ../../include/ptest_main.h +argv_test.o: argv.h +argv_test.o: argv_test.c +argv_test.o: check_arg.h +argv_test.o: msg.h +argv_test.o: msg_output.h +argv_test.o: msg_vstream.h +argv_test.o: myrand.h +argv_test.o: stringops.h +argv_test.o: sys_defs.h +argv_test.o: vbuf.h +argv_test.o: vstream.h +argv_test.o: vstring.h attr_clnt.o: attr.h attr_clnt.o: attr_clnt.c attr_clnt.o: attr_clnt.h @@ -1540,6 +1562,22 @@ dict_cidr.o: vbuf.h dict_cidr.o: vstream.h dict_cidr.o: vstring.h dict_cidr.o: warn_stat.h +dict_cli.o: argv.h +dict_cli.o: check_arg.h +dict_cli.o: dict.h +dict_cli.o: dict_cli.c +dict_cli.o: dict_db.h +dict_cli.o: dict_lmdb.h +dict_cli.o: mkmap.h +dict_cli.o: msg.h +dict_cli.o: msg_vstream.h +dict_cli.o: myflock.h +dict_cli.o: stringops.h +dict_cli.o: sys_defs.h +dict_cli.o: vbuf.h +dict_cli.o: vstream.h +dict_cli.o: vstring.h +dict_cli.o: vstring_vstream.h dict_db.o: argv.h dict_db.o: check_arg.h dict_db.o: dict.h @@ -1755,14 +1793,20 @@ dict_pipe.o: vbuf.h dict_pipe.o: vstream.h dict_pipe.o: vstring.h dict_pipe_test.o: ../../include/dict_test_helper.h +dict_pipe_test.o: ../../include/msg_jmp.h +dict_pipe_test.o: ../../include/pmock_expect.h +dict_pipe_test.o: ../../include/ptest.h +dict_pipe_test.o: ../../include/ptest_main.h dict_pipe_test.o: argv.h dict_pipe_test.o: check_arg.h dict_pipe_test.o: dict.h dict_pipe_test.o: dict_pipe.h dict_pipe_test.o: dict_pipe_test.c dict_pipe_test.o: msg.h +dict_pipe_test.o: msg_output.h dict_pipe_test.o: msg_vstream.h dict_pipe_test.o: myflock.h +dict_pipe_test.o: myrand.h dict_pipe_test.o: stringops.h dict_pipe_test.o: sys_defs.h dict_pipe_test.o: vbuf.h @@ -1859,6 +1903,24 @@ dict_stream.o: sys_defs.h dict_stream.o: vbuf.h dict_stream.o: vstream.h dict_stream.o: vstring.h +dict_stream_test.o: ../../include/msg_jmp.h +dict_stream_test.o: ../../include/pmock_expect.h +dict_stream_test.o: ../../include/ptest.h +dict_stream_test.o: ../../include/ptest_main.h +dict_stream_test.o: argv.h +dict_stream_test.o: check_arg.h +dict_stream_test.o: dict.h +dict_stream_test.o: dict_stream_test.c +dict_stream_test.o: msg.h +dict_stream_test.o: msg_output.h +dict_stream_test.o: msg_vstream.h +dict_stream_test.o: myflock.h +dict_stream_test.o: myrand.h +dict_stream_test.o: stringops.h +dict_stream_test.o: sys_defs.h +dict_stream_test.o: vbuf.h +dict_stream_test.o: vstream.h +dict_stream_test.o: vstring.h dict_surrogate.o: argv.h dict_surrogate.o: check_arg.h dict_surrogate.o: compat_va_copy.h @@ -1888,22 +1950,6 @@ dict_tcp.o: vbuf.h dict_tcp.o: vstream.h dict_tcp.o: vstring.h dict_tcp.o: vstring_vstream.h -dict_test.o: argv.h -dict_test.o: check_arg.h -dict_test.o: dict.h -dict_test.o: dict_db.h -dict_test.o: dict_lmdb.h -dict_test.o: dict_test.c -dict_test.o: mkmap.h -dict_test.o: msg.h -dict_test.o: msg_vstream.h -dict_test.o: myflock.h -dict_test.o: stringops.h -dict_test.o: sys_defs.h -dict_test.o: vbuf.h -dict_test.o: vstream.h -dict_test.o: vstring.h -dict_test.o: vstring_vstream.h dict_thash.o: argv.h dict_thash.o: check_arg.h dict_thash.o: dict.h @@ -1936,14 +1982,20 @@ dict_union.o: vbuf.h dict_union.o: vstream.h dict_union.o: vstring.h dict_union_test.o: ../../include/dict_test_helper.h +dict_union_test.o: ../../include/msg_jmp.h +dict_union_test.o: ../../include/pmock_expect.h +dict_union_test.o: ../../include/ptest.h +dict_union_test.o: ../../include/ptest_main.h dict_union_test.o: argv.h dict_union_test.o: check_arg.h dict_union_test.o: dict.h dict_union_test.o: dict_union.h dict_union_test.o: dict_union_test.c dict_union_test.o: msg.h +dict_union_test.o: msg_output.h dict_union_test.o: msg_vstream.h dict_union_test.o: myflock.h +dict_union_test.o: myrand.h dict_union_test.o: stringops.h dict_union_test.o: sys_defs.h dict_union_test.o: vbuf.h @@ -2084,6 +2136,37 @@ find_inet.o: stringops.h find_inet.o: sys_defs.h find_inet.o: vbuf.h find_inet.o: vstring.h +find_inet_service.o: check_arg.h +find_inet_service.o: find_inet_service.c +find_inet_service.o: find_inet_service.h +find_inet_service.o: known_tcp_ports.h +find_inet_service.o: msg.h +find_inet_service.o: sane_strtol.h +find_inet_service.o: stringops.h +find_inet_service.o: sys_defs.h +find_inet_service.o: vbuf.h +find_inet_service.o: vstring.h +find_inet_service.o: wrap_netdb.h +find_inet_service_test.o: ../../include/mock_servent.h +find_inet_service_test.o: ../../include/msg_jmp.h +find_inet_service_test.o: ../../include/pmock_expect.h +find_inet_service_test.o: ../../include/ptest.h +find_inet_service_test.o: ../../include/ptest_main.h +find_inet_service_test.o: argv.h +find_inet_service_test.o: check_arg.h +find_inet_service_test.o: find_inet_service.h +find_inet_service_test.o: find_inet_service_test.c +find_inet_service_test.o: known_tcp_ports.h +find_inet_service_test.o: msg.h +find_inet_service_test.o: msg_output.h +find_inet_service_test.o: msg_vstream.h +find_inet_service_test.o: myrand.h +find_inet_service_test.o: stringops.h +find_inet_service_test.o: sys_defs.h +find_inet_service_test.o: vbuf.h +find_inet_service_test.o: vstream.h +find_inet_service_test.o: vstring.h +find_inet_service_test.o: wrap_netdb.h format_tv.o: check_arg.h format_tv.o: format_tv.c format_tv.o: format_tv.h @@ -2119,11 +2202,18 @@ hash_fnv.o: hash_fnv.h hash_fnv.o: ldseed.h hash_fnv.o: msg.h hash_fnv.o: sys_defs.h +hash_fnv_test.o: ../../include/msg_jmp.h +hash_fnv_test.o: ../../include/pmock_expect.h +hash_fnv_test.o: ../../include/ptest.h +hash_fnv_test.o: ../../include/ptest_main.h +hash_fnv_test.o: argv.h hash_fnv_test.o: check_arg.h hash_fnv_test.o: hash_fnv.h hash_fnv_test.o: hash_fnv_test.c hash_fnv_test.o: msg.h +hash_fnv_test.o: msg_output.h hash_fnv_test.o: msg_vstream.h +hash_fnv_test.o: myrand.h hash_fnv_test.o: stringops.h hash_fnv_test.o: sys_defs.h hash_fnv_test.o: vbuf.h @@ -2270,6 +2360,23 @@ known_tcp_ports.o: stringops.h known_tcp_ports.o: sys_defs.h known_tcp_ports.o: vbuf.h known_tcp_ports.o: vstring.h +known_tcp_ports_test.o: ../../include/msg_jmp.h +known_tcp_ports_test.o: ../../include/pmock_expect.h +known_tcp_ports_test.o: ../../include/ptest.h +known_tcp_ports_test.o: ../../include/ptest_main.h +known_tcp_ports_test.o: argv.h +known_tcp_ports_test.o: check_arg.h +known_tcp_ports_test.o: known_tcp_ports.h +known_tcp_ports_test.o: known_tcp_ports_test.c +known_tcp_ports_test.o: msg.h +known_tcp_ports_test.o: msg_output.h +known_tcp_ports_test.o: msg_vstream.h +known_tcp_ports_test.o: myrand.h +known_tcp_ports_test.o: stringops.h +known_tcp_ports_test.o: sys_defs.h +known_tcp_ports_test.o: vbuf.h +known_tcp_ports_test.o: vstream.h +known_tcp_ports_test.o: vstring.h ldseed.o: iostuff.h ldseed.o: ldseed.c ldseed.o: ldseed.h @@ -2294,8 +2401,6 @@ load_file.o: vbuf.h load_file.o: vstream.h load_file.o: warn_stat.h load_lib.o: load_lib.c -load_lib.o: load_lib.h -load_lib.o: msg.h load_lib.o: sys_defs.h logwriter.o: check_arg.h logwriter.o: iostuff.h @@ -2520,6 +2625,7 @@ msg_logger.o: vbuf.h msg_logger.o: vstream.h msg_logger.o: vstring.h msg_output.o: check_arg.h +msg_output.o: msg.h msg_output.o: msg_output.c msg_output.o: msg_output.h msg_output.o: msg_vstream.h @@ -2529,6 +2635,24 @@ msg_output.o: sys_defs.h msg_output.o: vbuf.h msg_output.o: vstream.h msg_output.o: vstring.h +msg_output_test.o: ../../include/match_basic.h +msg_output_test.o: ../../include/msg_jmp.h +msg_output_test.o: ../../include/pmock_expect.h +msg_output_test.o: ../../include/ptest.h +msg_output_test.o: ../../include/ptest_main.h +msg_output_test.o: argv.h +msg_output_test.o: check_arg.h +msg_output_test.o: msg.h +msg_output_test.o: msg_output.h +msg_output_test.o: msg_output_test.c +msg_output_test.o: msg_vstream.h +msg_output_test.o: mymalloc.h +msg_output_test.o: myrand.h +msg_output_test.o: stringops.h +msg_output_test.o: sys_defs.h +msg_output_test.o: vbuf.h +msg_output_test.o: vstream.h +msg_output_test.o: vstring.h msg_rate_delay.o: check_arg.h msg_rate_delay.o: events.h msg_rate_delay.o: msg.h @@ -2573,6 +2697,31 @@ myaddrinfo.o: sys_defs.h myaddrinfo.o: valid_hostname.h myaddrinfo.o: vbuf.h myaddrinfo.o: vstring.h +myaddrinfo.o: wrap_netdb.h +myaddrinfo_test.o: ../../include/addrinfo_to_string.h +myaddrinfo_test.o: ../../include/make_addr.h +myaddrinfo_test.o: ../../include/match_addr.h +myaddrinfo_test.o: ../../include/match_basic.h +myaddrinfo_test.o: ../../include/mock_getaddrinfo.h +myaddrinfo_test.o: ../../include/msg_jmp.h +myaddrinfo_test.o: ../../include/pmock_expect.h +myaddrinfo_test.o: ../../include/ptest.h +myaddrinfo_test.o: ../../include/ptest_main.h +myaddrinfo_test.o: argv.h +myaddrinfo_test.o: check_arg.h +myaddrinfo_test.o: inet_proto.h +myaddrinfo_test.o: msg.h +myaddrinfo_test.o: msg_output.h +myaddrinfo_test.o: msg_vstream.h +myaddrinfo_test.o: myaddrinfo.h +myaddrinfo_test.o: myaddrinfo_test.c +myaddrinfo_test.o: myrand.h +myaddrinfo_test.o: stringops.h +myaddrinfo_test.o: sys_defs.h +myaddrinfo_test.o: vbuf.h +myaddrinfo_test.o: vstream.h +myaddrinfo_test.o: vstring.h +myaddrinfo_test.o: wrap_netdb.h myflock.o: check_arg.h myflock.o: msg.h myflock.o: myflock.c @@ -2585,6 +2734,23 @@ mymalloc.o: msg.h mymalloc.o: mymalloc.c mymalloc.o: mymalloc.h mymalloc.o: sys_defs.h +mymalloc_test.o: ../../include/msg_jmp.h +mymalloc_test.o: ../../include/pmock_expect.h +mymalloc_test.o: ../../include/ptest.h +mymalloc_test.o: ../../include/ptest_main.h +mymalloc_test.o: argv.h +mymalloc_test.o: check_arg.h +mymalloc_test.o: msg.h +mymalloc_test.o: msg_output.h +mymalloc_test.o: msg_vstream.h +mymalloc_test.o: mymalloc.h +mymalloc_test.o: mymalloc_test.c +mymalloc_test.o: myrand.h +mymalloc_test.o: stringops.h +mymalloc_test.o: sys_defs.h +mymalloc_test.o: vbuf.h +mymalloc_test.o: vstream.h +mymalloc_test.o: vstring.h myrand.o: myrand.c myrand.o: myrand.h myrand.o: sys_defs.h @@ -2601,6 +2767,23 @@ mystrtok.o: stringops.h mystrtok.o: sys_defs.h mystrtok.o: vbuf.h mystrtok.o: vstring.h +mystrtok_test.o: ../../include/msg_jmp.h +mystrtok_test.o: ../../include/pmock_expect.h +mystrtok_test.o: ../../include/ptest.h +mystrtok_test.o: ../../include/ptest_main.h +mystrtok_test.o: argv.h +mystrtok_test.o: check_arg.h +mystrtok_test.o: msg.h +mystrtok_test.o: msg_output.h +mystrtok_test.o: msg_vstream.h +mystrtok_test.o: mymalloc.h +mystrtok_test.o: myrand.h +mystrtok_test.o: mystrtok_test.c +mystrtok_test.o: stringops.h +mystrtok_test.o: sys_defs.h +mystrtok_test.o: vbuf.h +mystrtok_test.o: vstream.h +mystrtok_test.o: vstring.h name_code.o: name_code.c name_code.o: name_code.h name_code.o: sys_defs.h @@ -2938,17 +3121,6 @@ stream_send_fd.o: iostuff.h stream_send_fd.o: msg.h stream_send_fd.o: stream_send_fd.c stream_send_fd.o: sys_defs.h -stream_test.o: check_arg.h -stream_test.o: connect.h -stream_test.o: htable.h -stream_test.o: iostuff.h -stream_test.o: listen.h -stream_test.o: msg.h -stream_test.o: msg_vstream.h -stream_test.o: stream_test.c -stream_test.o: sys_defs.h -stream_test.o: vbuf.h -stream_test.o: vstream.h stream_trigger.o: connect.h stream_trigger.o: events.h stream_trigger.o: iostuff.h @@ -2957,6 +3129,17 @@ stream_trigger.o: mymalloc.h stream_trigger.o: stream_trigger.c stream_trigger.o: sys_defs.h stream_trigger.o: trigger.h +sunos5_stream_test.o: check_arg.h +sunos5_stream_test.o: connect.h +sunos5_stream_test.o: htable.h +sunos5_stream_test.o: iostuff.h +sunos5_stream_test.o: listen.h +sunos5_stream_test.o: msg.h +sunos5_stream_test.o: msg_vstream.h +sunos5_stream_test.o: sunos5_stream_test.c +sunos5_stream_test.o: sys_defs.h +sunos5_stream_test.o: vbuf.h +sunos5_stream_test.o: vstream.h sys_compat.o: sys_compat.c sys_compat.o: sys_defs.h timecmp.o: timecmp.c @@ -2998,6 +3181,22 @@ unescape.o: sys_defs.h unescape.o: unescape.c unescape.o: vbuf.h unescape.o: vstring.h +unescape_test.o: ../../include/msg_jmp.h +unescape_test.o: ../../include/pmock_expect.h +unescape_test.o: ../../include/ptest.h +unescape_test.o: ../../include/ptest_main.h +unescape_test.o: argv.h +unescape_test.o: check_arg.h +unescape_test.o: msg.h +unescape_test.o: msg_output.h +unescape_test.o: msg_vstream.h +unescape_test.o: myrand.h +unescape_test.o: stringops.h +unescape_test.o: sys_defs.h +unescape_test.o: unescape_test.c +unescape_test.o: vbuf.h +unescape_test.o: vstream.h +unescape_test.o: vstring.h unix_connect.o: connect.h unix_connect.o: iostuff.h unix_connect.o: msg.h @@ -3163,6 +3362,9 @@ watchdog.o: posix_signals.h watchdog.o: sys_defs.h watchdog.o: watchdog.c watchdog.o: watchdog.h +wrap_netdb.o: sys_defs.h +wrap_netdb.o: wrap_netdb.c +wrap_netdb.o: wrap_netdb.h wrap_stat.o: sys_defs.h wrap_stat.o: wrap_stat.c wrap_stat.o: wrap_stat.h diff --git a/postfix/src/util/argv.c b/postfix/src/util/argv.c index a91b33bfb..ea76b5152 100644 --- a/postfix/src/util/argv.c +++ b/postfix/src/util/argv.c @@ -178,12 +178,6 @@ #include "vstring.h" #include "argv.h" -#ifdef TEST -extern NORETURN PRINTFLIKE(1, 2) test_msg_panic(const char *,...); - -#define msg_panic test_msg_panic -#endif - /* argv_free - destroy string array */ ARGV *argv_free(ARGV *argvp) @@ -296,6 +290,8 @@ void argv_add(ARGV *argvp,...) argvp->argv[argvp->argc] = 0; } +#if 0 + /* argv_addn - add string to vector */ void argv_addn(ARGV *argvp,...) @@ -319,6 +315,8 @@ void argv_addn(ARGV *argvp,...) argvp->argv[argvp->argc] = 0; } +#endif + /* argv_addv - optionally create ARGV, append string vector */ ARGV *argv_addv(ARGV *argvp, const char *const * argv) @@ -437,400 +435,3 @@ char *argv_join(VSTRING *buf, ARGV *argv, int delim) } return (vstring_str(buf)); } - -#ifdef TEST - - /* - * System library. - */ -#include - - /* - * Utility library. - */ -#include -#include - -#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 diff --git a/postfix/src/util/argv.h b/postfix/src/util/argv.h index 7331d3980..0de769daf 100644 --- a/postfix/src/util/argv.h +++ b/postfix/src/util/argv.h @@ -27,7 +27,9 @@ extern ARGV *argv_sort(ARGV *); /* backwards compatibility */ extern ARGV *argv_qsort(ARGV *, ARGV_COMPAR_FN); extern ARGV *argv_uniq(ARGV *, ARGV_COMPAR_FN); extern void argv_add(ARGV *,...); +#if 0 extern void argv_addn(ARGV *,...); +#endif extern ARGV *argv_addv(ARGV *, const char *const *); extern void argv_terminate(ARGV *); extern void argv_truncate(ARGV *, ssize_t); diff --git a/postfix/src/util/argv_test.c b/postfix/src/util/argv_test.c new file mode 100644 index 000000000..5f510e007 --- /dev/null +++ b/postfix/src/util/argv_test.c @@ -0,0 +1,558 @@ + /* + * Test program to exercise argv.c. See PTEST_README for documentation. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include + + /* + * Test library. + */ +#include + +#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 diff --git a/postfix/src/util/dict.h b/postfix/src/util/dict.h index 0e689192e..efefb885d 100644 --- a/postfix/src/util/dict.h +++ b/postfix/src/util/dict.h @@ -251,7 +251,7 @@ extern DICT *dict_utf8_activate(DICT *); /* * Driver for interactive or scripted tests. */ -void dict_test(int, char **); +void dict_cli(int, char **); /* * Behind-the-scenes support to continue execution with reduced diff --git a/postfix/src/util/dict_test.c b/postfix/src/util/dict_cli.c similarity index 97% rename from postfix/src/util/dict_test.c rename to postfix/src/util/dict_cli.c index 26fcd0975..6c5da1d5e 100644 --- a/postfix/src/util/dict_test.c +++ b/postfix/src/util/dict_cli.c @@ -33,7 +33,7 @@ static NORETURN usage(char *myname) msg_fatal("usage: %s type:file read|write|create [flags...]", myname); } -void dict_test(int argc, char **argv) +void dict_cli(int argc, char **argv) { VSTRING *keybuf = vstring_alloc(1); VSTRING *inbuf = vstring_alloc(1); @@ -148,6 +148,8 @@ void dict_test(int argc, char **argv) } else if (strcmp(cmd, "masks") == 0 && !key && !value) { vstream_printf("DICT_FLAG_PARANOID %s\n", dict_flags_str(DICT_FLAG_PARANOID)); + vstream_printf("DICT_FLAG_UTF8_MASK %s\n", + dict_flags_str(DICT_FLAG_UTF8_MASK)); } else { vstream_printf("usage: %s\n", USAGE); } diff --git a/postfix/src/util/dict_debug_test.ref b/postfix/src/util/dict_debug_test.ref index 04724960a..6e8c361e3 100644 --- a/postfix/src/util/dict_debug_test.ref +++ b/postfix/src/util/dict_debug_test.ref @@ -1,7 +1,9 @@ >>> dict_open 'debug:' read -./dict_open: fatal: open dictionary: expecting "type:name" form instead of "debug:" +./dict_open: error: open dictionary: expecting "type:name" form instead of "debug:" +owner=trusted (uid=2147483647) >>> dict_open 'debug:missing_colon_and_name' read -./dict_open: fatal: open dictionary: expecting "type:name" form instead of "missing_colon_and_name" +./dict_open: error: open dictionary: expecting "type:name" form instead of "missing_colon_and_name" +owner=trusted (uid=2147483647) >>> dict_open 'debug:static:{space in name}' read owner=trusted (uid=2147483647) > get k diff --git a/postfix/src/util/dict_debug_test.sh b/postfix/src/util/dict_debug_test.sh index a5c12a239..e86004437 100755 --- a/postfix/src/util/dict_debug_test.sh +++ b/postfix/src/util/dict_debug_test.sh @@ -2,10 +2,10 @@ set -e echo ">>> dict_open 'debug:' read" -! ${VALGRIND} ./dict_open 'debug:' read >> dict_open 'debug:missing_colon_and_name' read" -! ${VALGRIND} ./dict_open 'debug:missing_colon_and_name' read >> dict_open 'debug:static:{space in name}' read" ${VALGRIND} ./dict_open 'debug:static:{space in name}' read < -#include #include /* @@ -16,28 +13,38 @@ */ #include #include -#include -#include -#include #include /* - * Testing library. + * Test library. */ +#include #include -#define LEN(x) VSTRING_LEN(x) -#define STR(x) vstring_str(x) - /* - * TODO(wietse) move these to common testing header file. + * The following needs to be large enough to include a null terminator in + * every ptestcase.want field. */ -#define PASS 1 -#define FAIL 0 +#define MAX_PROBE 5 + +struct probe { + const char *query; + const char *want_value; + int want_error; +}; + +typedef struct PTEST_CASE { + const char *testname; + void (*action) (PTEST_CTX *, const struct PTEST_CASE *); + const char *type_name; + const struct probe probes[MAX_PROBE]; +} PTEST_CASE; -static VSTRING *msg_buf; +#define STR_OR_NULL(s) ((s) ? (s) : "null") +#define STR(x) vstring_str(x) -static int valid_refcounts_for_good_composite_syntax(void) +static void valid_refcounts_for_good_composite_syntax(PTEST_CTX *t, + const struct PTEST_CASE *tp) { DICT *dict; int open_flags = O_RDONLY; @@ -54,173 +61,86 @@ static int valid_refcounts_for_good_composite_syntax(void) dict_compose_spec(DICT_TYPE_PIPE, component_specs, open_flags, dict_flags, composite_spec, reg_component_specs); - dict = dict_open_and_capture_msg(STR(composite_spec), open_flags, - dict_flags, msg_buf); + dict = dict_open(STR(composite_spec), open_flags, dict_flags); -#undef RETURN -#define RETURN(x) do { \ - if (dict) dict_close(dict); \ - vstring_free(composite_spec); \ - argv_free(reg_component_specs); \ - return (x); \ - } while (0); - - if (LEN(msg_buf) > 0) { - msg_warn("unexpected dict_open() warning: got %s", STR(msg_buf)); - RETURN(FAIL); - } for (cpp = reg_component_specs->argv; *cpp; cpp++) { if (dict_handle(*cpp) == 0) { - msg_warn("table '%s' is not registered after dict_open()", *cpp); - RETURN(FAIL); + ptest_fatal(t, "table '%s' is not registered after dict_open()", + *cpp); } } dict_close(dict); - dict = 0; for (cpp = reg_component_specs->argv; *cpp; cpp++) { if (dict_handle(*cpp) != 0) { - msg_warn("table '%s' is still registered after dict_close()", *cpp); - RETURN(FAIL); + ptest_fatal(t, "table '%s' is still registered after dict_close()", + *cpp); } } - RETURN(PASS); + vstring_free(composite_spec); + argv_free(reg_component_specs); } -static int valid_refcounts_for_bad_composite_syntax(void) +static void test_dict_pipe(PTEST_CTX *t, const struct PTEST_CASE *tp) { DICT *dict; - int open_flags = O_RDONLY; - int dict_flags = DICT_FLAG_LOCK; - VSTRING *composite_spec = vstring_alloc(100); - ARGV *reg_component_specs = argv_alloc(3); - const char *component_specs[] = { - "static:foo", - "inline{xx=yy}", - "unix:passwd.byname", - 0, - }; - char **cpp; - const char *want_msg = "bad syntax:"; - - dict_compose_spec(DICT_TYPE_PIPE, component_specs, open_flags, dict_flags, - composite_spec, reg_component_specs); - dict = dict_open_and_capture_msg(STR(composite_spec), open_flags, dict_flags, - msg_buf); - -#undef RETURN -#define RETURN(x) do { \ - dict_close(dict); \ - vstring_free(composite_spec); \ - argv_free(reg_component_specs); \ - return (x); \ - } while (0); - - if (LEN(msg_buf) == 0) { - msg_warn("missing dict_open() warning: want '%s'", want_msg); - RETURN(FAIL); - } - if (strstr(STR(msg_buf), want_msg) == 0) { - msg_warn("unexpected warning message: got '%s', want '%s'", - STR(msg_buf), want_msg); - RETURN(FAIL); - } - for (cpp = reg_component_specs->argv; *cpp; cpp++) { - if (dict_handle(*cpp) != 0) { - msg_warn("table '%s' is registered after failed dict_open()", - *cpp); - RETURN(FAIL); + const struct probe *pp; + const char *got_value; + int got_error; + + if ((dict = dict_open(tp->type_name, O_RDONLY, 0)) == 0) + ptest_fatal(t, "dict_open(\"%s\", O_RDONLY, 0) failed: %m", + tp->type_name); + for (pp = tp->probes; pp < tp->probes + MAX_PROBE && pp->query != 0; pp++) { + got_value = dict_get(dict, pp->query); + got_error = dict->error; + if (got_value == 0 && pp->want_value == 0) + continue; + if (got_value == 0 || pp->want_value == 0) { + ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want '%s'", + pp->query, STR_OR_NULL(got_value), + STR_OR_NULL(pp->want_value)); + break; } + if (strcmp(got_value, pp->want_value) != 0) { + ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want '%s'", + pp->query, got_value, pp->want_value); + } + if (got_error != pp->want_error) + ptest_error(t, "dict_get(dict,\"%s\") error: got %d, want %d", + pp->query, got_error, pp->want_error); } - RETURN(PASS); -} - -static int propagates_notfound_and_found(void) -{ - DICT *dict; - int open_flags = O_RDONLY; - int dict_flags = DICT_FLAG_LOCK; - const char *dict_spec = "pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}"; - static struct dict_get_verify_data expectations[] = { - {.key = "k0",.want_value = 0}, - {.key = "k1",.want_value = 0}, - {.key = "k2",.want_value = "v3"}, - {0}, - }; - int ret; - - dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags, - msg_buf); - if (LEN(msg_buf) > 0) { - msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf)); - ret = FAIL; - } else { - ret = dict_get_and_verify_bulk(dict, expectations); - } - dict_close(dict); - return (ret); -} - -static int propagates_notfound_and_error(void) -{ - DICT *dict; - int open_flags = O_RDONLY; - int dict_flags = DICT_FLAG_LOCK; - const char *dict_spec = "pipemap:{inline:{k1=v1},fail:fail}"; - static struct dict_get_verify_data expectations[] = { - {.key = "k0",.want_value = 0,.want_error = DICT_ERR_NONE}, - {.key = "k1",.want_value = 0,.want_error = DICT_ERR_RETRY}, - {0}, - }; - int ret; - - dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags, - msg_buf); - if (LEN(msg_buf) > 0) { - msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf)); - ret = FAIL; - } else { - ret = dict_get_and_verify_bulk(dict, expectations); - } - dict_close(dict); - return (ret); + dict_free(dict); } -struct TEST_CASE { - const char *label; - int (*action) (void); +static const PTEST_CASE ptestcases[] = { + { + .testname = "valid refcounts for good composite syntax", + .action = valid_refcounts_for_good_composite_syntax, + }, { + .testname = "successful lookup: inline map + inline map", + .action = test_dict_pipe, + .type_name = "pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}", + .probes = { + {"k0", 0}, + {"k1", 0,}, + {"k2", "v3"}, + }, + }, { + .testname = "error propagation: inline map + fail map", + .action = test_dict_pipe, + .type_name = "pipemap:{inline:{k1=v1},fail:fail}", + .probes = { + {"k0", 0, 0}, + {"k1", 0, DICT_ERR_RETRY}, + }, + }, { + .testname = "error propagation: fail map + inline map", + .action = test_dict_pipe, + .type_name = "pipemap:{fail:fail,inline:{k1=v1}}", + .probes = { + {"k1", 0, DICT_ERR_RETRY}, + }, + }, }; -static const struct TEST_CASE test_cases[] = { - {"valid_refcounts_for_good_composite_syntax", valid_refcounts_for_good_composite_syntax,}, - {"valid_refcounts_for_bad_composite_syntax", valid_refcounts_for_bad_composite_syntax,}, - {"propagates_notfound_and_found", propagates_notfound_and_found,}, - {"propagates_notfound_and_error", propagates_notfound_and_error,}, - {0}, -}; - -int main(int argc, char **argv) -{ - static int tests_passed = 0; - static int tests_failed = 0; - const struct TEST_CASE *tp; - - msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR); - - msg_buf = vstring_alloc(100); - dict_allow_surrogate = 1; - - for (tp = test_cases; tp->label; tp++) { - msg_info("RUN %s", tp->label); - if (tp->action() == PASS) { - msg_info("PASS %s", tp->label); - tests_passed += 1; - } else { - msg_info("FAIL %s", tp->label); - tests_failed += 1; - } - } - vstring_free(msg_buf); - - msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed); - exit(tests_failed != 0); -} +#include diff --git a/postfix/src/util/dict_stream.c b/postfix/src/util/dict_stream.c index 446d8f859..304a2e26f 100644 --- a/postfix/src/util/dict_stream.c +++ b/postfix/src/util/dict_stream.c @@ -145,130 +145,3 @@ VSTREAM *dict_stream_open(const char *dict_type, const char *mapname, return (map_fp); } } - -#ifdef TEST - -#include - -int main(int argc, char **argv) -{ - struct testcase { - const char *title; - const char *mapname; /* starts with brace */ - const char *expect_err; /* null or message */ - const char *expect_cont; /* null or content */ - }; - -#define EXP_NOERR 0 -#define EXP_NOCONT 0 - -#define STRING_OR(s, text_if_null) ((s) ? (s) : (text_if_null)) -#define DICT_TYPE_TEST "test" - - const char rule_spec_error[] = DICT_TYPE_TEST " map: " - "syntax error after '}' in \"{blah blah}x\""; - const char inline_config_error[] = DICT_TYPE_TEST " map: " - "syntax error after '}' in \"{{foo bar}, {blah blah}}x\""; - struct testcase testcases[] = { - {"normal", - "{{foo bar}, {blah blah}}", EXP_NOERR, "foo bar\nblah blah\n" - }, - {"trims leading/trailing wsp around rule-text", - "{{ foo bar }, { blah blah }}", EXP_NOERR, "foo bar\nblah blah\n" - }, - {"trims leading/trailing comma-wsp around rule-spec", - "{, ,{foo bar}, {blah blah}, ,}", EXP_NOERR, "foo bar\nblah blah\n" - }, - {"empty inline-file", - "{, }", EXP_NOERR, "" - }, - {"propagates extpar error for inline-file", - "{{foo bar}, {blah blah}}x", inline_config_error, EXP_NOCONT - }, - {"propagates extpar error for rule-spec", - "{{foo bar}, {blah blah}x}", rule_spec_error, EXP_NOCONT - }, - 0, - }; - struct testcase *tp; - VSTRING *act_err = 0; - VSTRING *act_cont = vstring_alloc(100); - VSTREAM *fp; - struct stat st; - ssize_t exp_len; - ssize_t act_len; - int pass; - int fail; - - for (pass = fail = 0, tp = testcases; tp->title; tp++) { - int test_passed = 0; - - msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title); - -#if 0 - msg_info("title=%s", tp->title); - msg_info("mapname=%s", tp->mapname); - msg_info("expect_err=%s", STRING_OR_NULL(tp->expect_err)); - msg_info("expect_cont=%s", STRINGOR_NULL(tp->expect_cont)); -#endif - - if (act_err) - VSTRING_RESET(act_err); - fp = dict_stream_open(DICT_TYPE_TEST, tp->mapname, O_RDONLY, - 0, &st, &act_err); - if (fp) { - if (tp->expect_err) { - msg_warn("test case %s: got stream, expected error", tp->title); - } else if (!tp->expect_err && act_err && LEN(act_err) > 0) { - msg_warn("test case %s: got error '%s', expected noerror", - tp->title, STR(act_err)); - } else if (!tp->expect_cont) { - msg_warn("test case %s: got stream, expected nostream", - tp->title); - } else { - exp_len = strlen(tp->expect_cont); - if ((act_len = vstream_fread_buf(fp, act_cont, 2 * exp_len)) < 0) { - msg_warn("test case %s: content read error", tp->title); - } else { - VSTRING_TERMINATE(act_cont); - if (strcmp(tp->expect_cont, STR(act_cont)) != 0) { - msg_warn("test case %s: got content '%s', expected '%s'", - tp->title, STR(act_cont), tp->expect_cont); - } else { - test_passed = 1; - } - } - } - } else { - if (!tp->expect_err) { - msg_warn("test case %s: got nostream, expected noerror", - tp->title); - } else if (tp->expect_cont) { - msg_warn("test case %s: got nostream, expected stream", - tp->title); - } else if (strcmp(STR(act_err), tp->expect_err) != 0) { - msg_warn("test case %s: got error '%s', expected '%s'", - tp->title, STR(act_err), tp->expect_err); - } else { - test_passed = 1; - } - - } - if (test_passed) { - msg_info("PASS test %ld", (long) (tp - testcases)); - pass++; - } else { - msg_info("FAIL test %ld", (long) (tp - testcases)); - fail++; - } - if (fp) - vstream_fclose(fp); - } - if (act_err) - vstring_free(act_err); - vstring_free(act_cont); - msg_info("PASS=%d FAIL=%d", pass, fail); - return (fail > 0); -} - -#endif /* TEST */ diff --git a/postfix/src/util/dict_stream.ref b/postfix/src/util/dict_stream.ref deleted file mode 100644 index 87c30e59b..000000000 --- a/postfix/src/util/dict_stream.ref +++ /dev/null @@ -1,13 +0,0 @@ -unknown: RUN test case 0 normal -unknown: PASS test 0 -unknown: RUN test case 1 trims leading/trailing wsp around rule-text -unknown: PASS test 1 -unknown: RUN test case 2 trims leading/trailing comma-wsp around rule-spec -unknown: PASS test 2 -unknown: RUN test case 3 empty inline-file -unknown: PASS test 3 -unknown: RUN test case 4 propagates extpar error for inline-file -unknown: PASS test 4 -unknown: RUN test case 5 propagates extpar error for rule-spec -unknown: PASS test 5 -unknown: PASS=6 FAIL=0 diff --git a/postfix/src/util/dict_stream_test.c b/postfix/src/util/dict_stream_test.c new file mode 100644 index 000000000..5cbb54870 --- /dev/null +++ b/postfix/src/util/dict_stream_test.c @@ -0,0 +1,114 @@ + /* + * Test program to exercise dict_stream.c. See PTEST_README for + * documentation. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Test library. + */ +#include + +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 diff --git a/postfix/src/util/dict_union.c b/postfix/src/util/dict_union.c index 043abc071..ff95d9619 100644 --- a/postfix/src/util/dict_union.c +++ b/postfix/src/util/dict_union.c @@ -38,9 +38,7 @@ /* Yorktown Heights, NY 10598, USA /* /* Wietse Venema -/* Google, Inc. -/* 111 8th Avenue -/* New York, NY 10011, USA +/* porcupine.org /*--*/ /* System library. */ diff --git a/postfix/src/util/dict_union_test.c b/postfix/src/util/dict_union_test.c index 0ed87b892..3db0bc70c 100644 --- a/postfix/src/util/dict_union_test.c +++ b/postfix/src/util/dict_union_test.c @@ -1,14 +1,12 @@ -/*++ -/* AUTHOR(S) -/* Wietse Venema -/* porcupine.org -/*--*/ + /* + * Test program to exercise dict_union.c. See PTEST_README for + * documentation. + */ /* * System library. */ #include -#include #include /* @@ -16,28 +14,38 @@ */ #include #include -#include -#include -#include #include /* - * Testing library. + * Test library. */ #include - -#define LEN(x) VSTRING_LEN(x) -#define STR(x) vstring_str(x) +#include /* - * TODO(wietse) move these to common testing header file. + * The following needs to be large enough to include a null terminator in + * every ptestcase.want field. */ -#define PASS 1 -#define FAIL 0 +#define MAX_PROBE 5 + +struct probe { + const char *query; + const char *want_value; + int want_error; +}; -static VSTRING *msg_buf; +typedef struct PTEST_CASE { + const char *testname; + void (*action) (PTEST_CTX *, const struct PTEST_CASE *); + const char *type_name; + const struct probe probes[MAX_PROBE]; +} PTEST_CASE; -static int valid_refcounts_for_good_composite_syntax(void) +#define STR_OR_NULL(s) ((s) ? (s) : "null") +#define STR(x) vstring_str(x) + +static void valid_refcounts_for_good_composite_syntax(PTEST_CTX *t, + const struct PTEST_CASE *tp) { DICT *dict; int open_flags = O_RDONLY; @@ -54,200 +62,93 @@ static int valid_refcounts_for_good_composite_syntax(void) dict_compose_spec(DICT_TYPE_UNION, component_specs, open_flags, dict_flags, composite_spec, reg_component_specs); - dict = dict_open_and_capture_msg(STR(composite_spec), open_flags, dict_flags, - msg_buf); - -#undef RETURN -#define RETURN(x) do { \ - if (dict) dict_close(dict); \ - vstring_free(composite_spec); \ - argv_free(reg_component_specs); \ - return (x); \ - } while (0); + dict = dict_open(STR(composite_spec), open_flags, dict_flags); - if (LEN(msg_buf) > 0) { - msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf)); - RETURN(FAIL); - } for (cpp = reg_component_specs->argv; *cpp; cpp++) { if (dict_handle(*cpp) == 0) { - msg_warn("table '%s' is not registered after dict_open()", *cpp); - RETURN(FAIL); + ptest_fatal(t, "table '%s' is not registered after dict_open()", + *cpp); } } dict_close(dict); - dict = 0; for (cpp = reg_component_specs->argv; *cpp; cpp++) { if (dict_handle(*cpp) != 0) { - msg_warn("table '%s' is still registered after dict_close()", *cpp); - RETURN(FAIL); + ptest_fatal(t, "table '%s' is still registered after dict_close()", + *cpp); } } - RETURN(PASS); + vstring_free(composite_spec); + argv_free(reg_component_specs); } -static int valid_refcounts_for_bad_composite_syntax(void) +static void test_dict_union(PTEST_CTX *t, const struct PTEST_CASE *tp) { DICT *dict; - int open_flags = O_RDONLY; - int dict_flags = DICT_FLAG_LOCK; - VSTRING *composite_spec = vstring_alloc(100); - ARGV *reg_component_specs = argv_alloc(3); - const char *component_specs[] = { - "static:one", - "static:two", - "inline{foo=three}", - 0, - }; - char **cpp; - const char *want_msg = "bad syntax:"; - - dict_compose_spec(DICT_TYPE_UNION, component_specs, open_flags, dict_flags, - composite_spec, reg_component_specs); - dict = dict_open_and_capture_msg(STR(composite_spec), open_flags, - dict_flags, msg_buf); - -#undef RETURN -#define RETURN(x) do { \ - dict_close(dict); \ - vstring_free(composite_spec); \ - argv_free(reg_component_specs); \ - return (x); \ - } while (0); - - if (LEN(msg_buf) == 0) { - msg_warn("missing dict_open() warning: want '%s'", want_msg); - RETURN(FAIL); - } - if (strstr(STR(msg_buf), want_msg) == 0) { - msg_warn("unexpected warning message: got '%s', want '%s'", - STR(msg_buf), want_msg); - RETURN(FAIL); - } - for (cpp = reg_component_specs->argv; *cpp; cpp++) { - if (dict_handle(*cpp) != 0) { - msg_warn("table '%s' is registered after failed dict_open()", - *cpp); - RETURN(FAIL); + const struct probe *pp; + const char *got_value; + int got_error; + + dict = dict_open(tp->type_name, O_RDONLY, 0); + + for (pp = tp->probes; pp < tp->probes + MAX_PROBE && pp->query != 0; pp++) { + got_value = dict_get(dict, pp->query); + got_error = dict->error; + if (got_value == 0 && pp->want_value == 0) + continue; + if (got_value == 0 || pp->want_value == 0) { + ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want '%s'", + pp->query, STR_OR_NULL(got_value), + STR_OR_NULL(pp->want_value)); + break; } - } - RETURN(PASS); -} - -static int propagates_notfound_and_found(void) -{ - DICT *dict; - int open_flags = O_RDONLY; - int dict_flags = DICT_FLAG_LOCK; - const char *dict_spec = ("unionmap:{static:one,static:two," - "inline:{foo=three}}"); - static struct dict_get_verify_data expectations[] = { - {.key = "foo",.want_value = "one,two,three"}, - {.key = "bar",.want_value = "one,two"}, - {0}, - }; - int ret; - - dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags, - msg_buf); - if (LEN(msg_buf) > 0) { - msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf)); - ret = FAIL; - } else { - ret = dict_get_and_verify_bulk(dict, expectations); - } - dict_close(dict); - return (ret); -} - -static int propagates_error(void) -{ - DICT *dict; - int open_flags = O_RDONLY; - int dict_flags = DICT_FLAG_LOCK; - const char *dict_spec = "unionmap:{static:one,fail:fail}"; - static struct dict_get_verify_data expectations[] = { - {.key = "foo",.want_value = 0,.want_error = DICT_ERR_RETRY}, - {0}, - }; - int ret; - - dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags, - msg_buf); - if (LEN(msg_buf) > 0) { - msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf)); - ret = FAIL; - } else { - ret = dict_get_and_verify_bulk(dict, expectations); - } - dict_close(dict); - return (ret); -} - -static int no_comma_for_not_found(void) -{ - DICT *dict; - int open_flags = O_RDONLY; - int dict_flags = DICT_FLAG_LOCK; - const char *dict_spec = "unionmap:{regexp:{{/a|c/ 1}},regexp:{{/b|c/ 2}}}"; - static struct dict_get_verify_data expectations[] = { - {.key = "x",.want_value = 0}, - {.key = "a",.want_value = "1"}, - {.key = "b",.want_value = "2"}, - {.key = "c",.want_value = "1,2"}, - {0}, - }; - int ret; - - dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags, - msg_buf); - if (LEN(msg_buf) > 0) { - msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf)); - ret = FAIL; - } else { - ret = dict_get_and_verify_bulk(dict, expectations); + if (strcmp(got_value, pp->want_value) != 0) { + ptest_error(t, "dict_get(dict, \"%s\"): got '%s', want '%s'", + pp->query, got_value, pp->want_value); + } + if (got_error != pp->want_error) + ptest_error(t, "dict_get(dict,\"%s\") error: got %d, want %d", + pp->query, got_error, pp->want_error); } dict_close(dict); - return (ret); } -struct TEST_CASE { - const char *label; - int (*action) (void); -}; - -static const struct TEST_CASE test_cases[] = { - {"valid_refcounts_for_good_composite_syntax", valid_refcounts_for_good_composite_syntax,}, - {"valid_refcounts_for_bad_composite_syntax", valid_refcounts_for_bad_composite_syntax,}, - {"propagates_notfound_and_found", propagates_notfound_and_found,}, - {"propagates_error", propagates_error,}, - {"no_comma_for_not_found", no_comma_for_not_found,}, - {0}, +static const PTEST_CASE ptestcases[] = { + { + .testname = "valid refcounts for good composite syntax", + .action = valid_refcounts_for_good_composite_syntax, + }, { + .testname = "propagates notfound and found", + .action = test_dict_union, + .type_name = "unionmap:{static:one,inline:{foo=two}}", + .probes = { + {"foo", "one,two", DICT_STAT_SUCCESS}, + {"bar", "one", DICT_STAT_SUCCESS}, + }, + }, { + .testname = "error propagation: static map + fail map", + .action = test_dict_union, + .type_name = "unionmap:{static:one,fail:fail}", + .probes = { + {"foo", 0, DICT_ERR_RETRY}, + }, + }, { + .testname = "error propagation: fail map + static map", + .action = test_dict_union, + .type_name = "unionmap:{fail:fail,static:one}", + .probes = { + {"foo", 0, DICT_ERR_RETRY}, + }, + }, { + .testname = "no comma for not found", + .action = test_dict_union, + .type_name = "unionmap:{regexp:{{/a|c/ 1}},regexp:{{/b|c/ 2}}}", + .probes = { + {.query = "x",.want_value = 0, DICT_STAT_SUCCESS}, + {.query = "a",.want_value = "1", DICT_STAT_SUCCESS}, + {.query = "b",.want_value = "2", DICT_STAT_SUCCESS}, + {.query = "c",.want_value = "1,2", DICT_STAT_SUCCESS}, + }, + }, }; -int main(int argc, char **argv) -{ - static int tests_passed = 0; - static int tests_failed = 0; - const struct TEST_CASE *tp; - - msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR); - - msg_buf = vstring_alloc(100); - dict_allow_surrogate = 1; - - for (tp = test_cases; tp->label; tp++) { - msg_info("RUN %s", tp->label); - if (tp->action() == PASS) { - msg_info("PASS %s", tp->label); - tests_passed += 1; - } else { - msg_info("FAIL %s", tp->label); - tests_failed += 1; - } - } - vstring_free(msg_buf); - - msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed); - exit(tests_failed != 0); -} +#include diff --git a/postfix/src/util/find_inet_service.c b/postfix/src/util/find_inet_service.c new file mode 100644 index 000000000..29948c1b8 --- /dev/null +++ b/postfix/src/util/find_inet_service.c @@ -0,0 +1,68 @@ +/*++ +/* NAME +/* find_inet_service 3 +/* SUMMARY +/* TCP service lookup +/* SYNOPSIS +/* #include +/* +/* 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 +#include +#include +#include +#include + +/* Application-specific. */ + +#include +#include +#include +#include +#include + +/* find_inet_service - translate numerical or symbolic service name */ + +int find_inet_service(const char *service, const char *protocol) +{ + struct servent *sp; + unsigned long lport; + char *cp; + + if (strcmp(protocol, "tcp") == 0) + service = filter_known_tcp_port(service); + if (alldig(service)) { + lport = sane_strtoul(service, &cp, 10); + if (*cp != '\0' || errno == ERANGE || lport > 65535) + return (-1); /* bad numerical service */ + return ((int) lport); + } else { + if ((sp = getservbyname(service, protocol)) == 0) + return (-1); /* bad symbolic service */ + return (ntohs(sp->s_port)); + } +} diff --git a/postfix/src/util/find_inet_service.h b/postfix/src/util/find_inet_service.h new file mode 100644 index 000000000..0f1169728 --- /dev/null +++ b/postfix/src/util/find_inet_service.h @@ -0,0 +1,30 @@ +#ifndef _FIND_TCP_PORT_H_INCLUDED_ +#define _FIND_TCP_PORT_H_INCLUDED_ + +/*++ +/* NAME +/* find_inet_service 3h +/* SUMMARY +/* TCP service lookup +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int find_inet_service(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/postfix/src/util/find_inet_service_test.c b/postfix/src/util/find_inet_service_test.c new file mode 100644 index 000000000..ba806e80c --- /dev/null +++ b/postfix/src/util/find_inet_service_test.c @@ -0,0 +1,119 @@ + /* + * Test program to exercise find_inet_service.c. See pmock_expect_test.c and + * PTEST_README for documentation. + */ + + /* + * System library. + */ +#include + + /* + * Utility library + */ +#include +#include +#include + + /* + * Test library. + */ +#include +#include + +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 diff --git a/postfix/src/util/hash_fnv_test.c b/postfix/src/util/hash_fnv_test.c index 84f665238..a2f85eddb 100644 --- a/postfix/src/util/hash_fnv_test.c +++ b/postfix/src/util/hash_fnv_test.c @@ -1,3 +1,8 @@ + /* + * Test program to exercise the hash_fnv implementation. See PTEST_README + * for documentation. + */ + /* * System library. */ @@ -9,16 +14,22 @@ * Utility library. */ #include -#include -#include #include -int main(int srgc, char **argv) -{ - int pass = 0; - int fail = 0; + /* + * Test library. + */ +#include - msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR); +typedef struct PTEST_CASE { + const char *testname; + void (*action) (PTEST_CTX *t, const struct PTEST_CASE *tp); + HASH_FNV_T want_hval; + const char *str; +} PTEST_CASE; + +static void setup_test(void) +{ /* * Sanity check. @@ -32,83 +43,39 @@ int main(int srgc, char **argv) */ if (putenv("NORANDOMIZE=") != 0) msg_fatal("putenv(\"NORANDOMIZE=\"): %m"); +} - /* - * Test: hashing produces the expected results. - */ - { - struct testcase { - HASH_FNV_T hval; - const char *str; - }; - static struct testcase testcases[] = - { -#ifdef USE_FNV_32BIT - 0x1c00fc06UL, "overdeeply", - 0x1c00fc06UL, "undescript", - 0x1e1e52a4UL, "fanfold", - 0x1e1e52a4UL, "phrensied", -#else - 0xda19999ec0bda706ULL, "overdeeply", - 0xd7b9e43f26396a66ULL, "undescript", - 0xa50c585d385a2604ULL, "fanfold", - 0x1ec3ef9bb2b734a4ULL, "phrensied", -#endif - 0, - }; - struct testcase *tp; - HASH_FNV_T hval; - int test_failed; - - for (tp = testcases; tp->str; tp++) { - test_failed = 0; - msg_info("RUN hash_fnvz(\"%s\")", tp->str); - if ((hval = hash_fnvz(tp->str)) != tp->hval) { - msg_warn("hash_fnv(\"%s\") want %lu, got: %lu", - tp->str, (unsigned long) tp->hval, - (unsigned long) hval); - test_failed = 1; - } - if (test_failed) { - fail += 1; - msg_info("FAIL hash_fnvz(\"%s\")", tp->str); - } else { - pass += 1; - msg_info("PASS hash_fnvz(\"%s\")", tp->str); - } - } - } +static void test_known_input(PTEST_CTX *t, const PTEST_CASE *tp) +{ + HASH_FNV_T got_hval; - /* - * Test: hash_fnvz(s) is equivalent to hash_fnv(s, strlen(s)). No need to - * verify the actual result; we already did that above. - */ - { - const char *strval = "foobar"; - HASH_FNV_T h1; - HASH_FNV_T h2; + setup_test(); - msg_info("RUN hash_fnvz(\"%s\") == hash_fnv(\"%s\", %ld)", - strval, strval, (long) strlen(strval)); + if ((got_hval = hash_fnvz(tp->str)) != tp->want_hval) + ptest_error(t, "hash_fnvz(\"%s\") got %lu, want %lu", + tp->str, (unsigned long) got_hval, + (unsigned long) tp->want_hval); - h1 = hash_fnv(strval, strlen(strval)); - h2 = hash_fnvz(strval); + if ((got_hval = hash_fnv(tp->str, strlen(tp->str))) != tp->want_hval) + ptest_error(t, "hash_fnv(\"%s\", strlen(\"%s\")) got %lu, want %lu", + tp->str, tp->str, (unsigned long) got_hval, + (unsigned long) tp->want_hval); +} - if (h1 == h2) { - pass += 1; - msg_info("PASS: hash_fnvz(\"%s\") == hash_fnv(\"%s\", %ld)", - strval, strval, (long) strlen(strval)); - } else { - fail += 1; - msg_info("FAIL: hash_fnvz(\"%s\") == hash_fnv(\"%s\", %ld)", - strval, strval, (long) strlen(strval)); - } - } +static const PTEST_CASE ptestcases[] = +{ +#ifdef USE_FNV_32BIT + "test_known_input_overdeeply", test_known_input, 0x1c00fc06UL, "overdeeply", + "test_known_input_undescript", test_known_input, 0x1c00fc06UL, "undescript", + "test_known_input_fanfold", test_known_input, 0x1e1e52a4UL, "fanfold", + "test_known_input_phrensied", test_known_input, 0x1e1e52a4UL, "phrensied", +#else + "test_known_input_overdeeply", test_known_input, 0xda19999ec0bda706ULL, "overdeeply", + "test_known_input_undescript", test_known_input, 0xd7b9e43f26396a66ULL, "undescript", + "test_known_input_fanfold", test_known_input, 0xa50c585d385a2604ULL, "fanfold", + "test_known_input_phrensied", test_known_input, 0x1ec3ef9bb2b734a4ULL, "phrensied", +#endif +}; - /* - * Wrap up. - */ - msg_info("PASS=%d FAIL=%d", pass, fail); - return (fail != 0); -} +#include diff --git a/postfix/src/util/known_tcp_ports.c b/postfix/src/util/known_tcp_ports.c index 1d524d4d3..3ddc084fd 100644 --- a/postfix/src/util/known_tcp_ports.c +++ b/postfix/src/util/known_tcp_ports.c @@ -78,6 +78,8 @@ const char *add_known_tcp_port(const char *name, const char *port) return ("numerical service name"); if (!alldig(port)) return ("non-numerical service port"); + if (strlen(port) > 5 || (strlen(port) == 5 && strcmp(port, "65535") > 0)) + return ("port number out of range"); if (known_tcp_ports == 0) known_tcp_ports = htable_create(10); if (htable_locate(known_tcp_ports, name) != 0) @@ -139,115 +141,3 @@ char *export_known_tcp_ports(VSTRING *out) VSTRING_TERMINATE(out); return (STR(out)); } - -#ifdef TEST - -#include - -struct association { - const char *lhs; /* service name */ - const char *rhs; /* service port */ -}; - -struct probe { - const char *query; /* query */ - const char *exp_reply; /* expected reply */ -}; - -struct test_case { - const char *label; /* identifies test case */ - struct association associations[10]; - const char *exp_err; /* expected error */ - const char *exp_export; /* expected export output */ - struct probe probes[10]; -}; - -struct test_case test_cases[] = { - {"good", - /* association */ {{"smtp", "25"}, {"lmtp", "24"}, 0}, - /* error */ 0, - /* export */ "lmtp=24 smtp=25", - /* probe */ {{"smtp", "25"}, {"1", "1"}, {"x", "x"}, {"lmtp", "24"}, 0} - }, - {"duplicate lhs", - /* association */ {{"smtp", "25"}, {"smtp", "100"}, 0}, - /* error */ "duplicate service name" - }, - {"numerical lhs", - /* association */ {{"100", "100"}, 0}, - /* error */ "numerical service name" - }, - {"symbolic rhs", - /* association */ {{"smtp", "lmtp"}, 0}, - /* error */ "non-numerical service port" - }, - {"uninitialized", - /* association */ {0}, - /* error */ 0, - /* export */ "", - /* probe */ {{"smtp", "smtp"}, {"1", "1"}, {"x", "x"}, 0} - }, - 0, -}; - -int main(int argc, char **argv) -{ - VSTRING *export_buf; - struct test_case *tp; - struct association *ap; - struct probe *pp; - int pass = 0; - int fail = 0; - const char *err; - int test_failed; - const char *reply; - const char *export; - -#define STRING_OR_NULL(s) ((s) ? (s) : "(null)") - - export_buf = vstring_alloc(100); - for (tp = test_cases; tp->label != 0; tp++) { - test_failed = 0; - for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++) - err = add_known_tcp_port(ap->lhs, ap->rhs); - if (!err != !tp->exp_err) { - msg_warn("test case %s: got error: \"%s\", want: \"%s\"", - tp->label, STRING_OR_NULL(err), STRING_OR_NULL(tp->exp_err)); - test_failed = 1; - } else if (err != 0) { - if (strcmp(err, tp->exp_err) != 0) { - msg_warn("test case %s: got err: \"%s\", want: \"%s\"", - tp->label, err, tp->exp_err); - test_failed = 1; - } - } else { - export = export_known_tcp_ports(export_buf); - if (strcmp(export, tp->exp_export) != 0) { - msg_warn("test case %s: got export: \"%s\", want: \"%s\"", - tp->label, export, tp->exp_export); - test_failed = 1; - } - for (pp = tp->probes; test_failed == 0 && pp->query != 0; pp++) { - reply = filter_known_tcp_port(pp->query); - if (strcmp(reply, pp->exp_reply) != 0) { - msg_warn("test case %s: got reply: \"%s\", want: \"%s\"", - tp->label, reply, pp->exp_reply); - test_failed = 1; - } - } - } - clear_known_tcp_ports(); - if (test_failed) { - msg_info("%s: FAIL", tp->label); - fail++; - } else { - msg_info("%s: PASS", tp->label); - pass++; - } - } - msg_info("PASS=%d FAIL=%d", pass, fail); - vstring_free(export_buf); - exit(fail != 0); -} - -#endif diff --git a/postfix/src/util/known_tcp_ports.ref b/postfix/src/util/known_tcp_ports.ref deleted file mode 100644 index adcf18208..000000000 --- a/postfix/src/util/known_tcp_ports.ref +++ /dev/null @@ -1,6 +0,0 @@ -unknown: good: PASS -unknown: duplicate lhs: PASS -unknown: numerical lhs: PASS -unknown: symbolic rhs: PASS -unknown: uninitialized: PASS -unknown: PASS=5 FAIL=0 diff --git a/postfix/src/util/known_tcp_ports_test.c b/postfix/src/util/known_tcp_ports_test.c new file mode 100644 index 000000000..2655de8e3 --- /dev/null +++ b/postfix/src/util/known_tcp_ports_test.c @@ -0,0 +1,116 @@ + /* + * Test program to exercise known_tcp_ports.c. See PTEST_README for + * documentation. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library + */ +#include +#include + + /* + * Test library. + */ +#include + +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 diff --git a/postfix/src/util/msg.c b/postfix/src/util/msg.c index 70c6eab08..5109068bd 100644 --- a/postfix/src/util/msg.c +++ b/postfix/src/util/msg.c @@ -59,6 +59,9 @@ /* int count; /* /* void msg_error_clear() +/* +/* void msg_set_longjmp_action( +/* NORETURN (*longjmp_action) (int) /* DESCRIPTION /* This module reports diagnostics. By default, diagnostics are sent /* to the standard error stream, but the disposition can be changed @@ -131,9 +134,20 @@ /* 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 @@ -157,6 +171,9 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* porcupine.org /*--*/ /* System libraries. */ @@ -179,6 +196,7 @@ int msg_verbose = 0; /* * Private state. */ +MSG_LONGJMP_ACTION msg_longjmp_action; static MSG_CLEANUP_FN msg_cleanup_fn = 0; static int msg_error_count = 0; static int msg_error_bound = 13; @@ -262,6 +280,11 @@ NORETURN vmsg_fatal(const char *fmt, va_list ap) if (msg_cleanup_fn) msg_cleanup_fn(); } + if (msg_longjmp_action) { + /* This code is reachable during testing only. */ + msg_exiting = 0; + msg_longjmp_action(MSG_LONGJMP_FATAL); + } sleep(1); /* In case we're running as a signal handler. */ _exit(1); @@ -285,6 +308,11 @@ NORETURN vmsg_fatal_status(int status, const char *fmt, va_list ap) if (msg_cleanup_fn) msg_cleanup_fn(); } + if (msg_longjmp_action) { + /* This code is reachable during testing only. */ + msg_exiting = 0; + msg_longjmp_action(MSG_LONGJMP_FATAL); + } sleep(1); /* In case we're running as a signal handler. */ _exit(status); @@ -306,6 +334,11 @@ NORETURN vmsg_panic(const char *fmt, va_list ap) if (msg_exiting++ == 0) { msg_vprintf(MSG_PANIC, fmt, ap); } + if (msg_longjmp_action) { + /* This code is reachable during testing only. */ + msg_exiting = 0; + msg_longjmp_action(MSG_LONGJMP_PANIC); + } sleep(1); abort(); /* Die! */ /* In case we're running as a signal handler. */ @@ -338,3 +371,10 @@ void msg_error_clear(void) { msg_error_count = 0; } + +/* msg_set_longjmp_action() - exception handling */ + +void msg_set_longjmp_action(MSG_LONGJMP_ACTION action) +{ + msg_longjmp_action = action; +} diff --git a/postfix/src/util/msg.h b/postfix/src/util/msg.h index 6c75bafc1..994290c81 100644 --- a/postfix/src/util/msg.h +++ b/postfix/src/util/msg.h @@ -46,6 +46,15 @@ extern void PRINTFLIKE(4, 5) msg_rate_delay(time_t *, int, void PRINTFPTRLIKE(1, 2) (*log_fn) (const char *,...), const char *,...); + /* + * Exception call-back handler for unit tests. + */ +typedef NORETURN(*MSG_LONGJMP_ACTION) (int); +extern void msg_set_longjmp_action(MSG_LONGJMP_ACTION); + +#define MSG_LONGJMP_FATAL 2 /* msg_fatal longjmp code */ +#define MSG_LONGJMP_PANIC 3 /* msg_panic longjmp code */ + /* LICENSE /* .ad /* .fi @@ -55,6 +64,9 @@ extern void PRINTFLIKE(4, 5) msg_rate_delay(time_t *, int, /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* porcupine.org /*--*/ #endif diff --git a/postfix/src/util/msg_logger.c b/postfix/src/util/msg_logger.c index 6a999f645..c36659ffc 100644 --- a/postfix/src/util/msg_logger.c +++ b/postfix/src/util/msg_logger.c @@ -165,7 +165,7 @@ static void msg_logger_disconnect(void) /* msg_logger_print - log info to service or file */ -static void msg_logger_print(int level, const char *text) +static void msg_logger_print(int level, const char *text, void *unused) { time_t now; struct tm *lt; @@ -288,7 +288,7 @@ void msg_logger_init(const char *progname, const char *hostname, */ if (first_call) { first_call = 0; - msg_output(msg_logger_print); + msg_output_push(msg_logger_print, (void *) 0); msg_logger_buf = vstring_alloc(2048); } diff --git a/postfix/src/util/msg_output.c b/postfix/src/util/msg_output.c index 6663877d7..d49884a4e 100644 --- a/postfix/src/util/msg_output.c +++ b/postfix/src/util/msg_output.c @@ -6,10 +6,15 @@ /* SYNOPSIS /* #include /* -/* 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; @@ -23,13 +28,17 @@ /* This module implements low-level output management for the /* msg(3) diagnostics interface. /* -/* msg_output() registers an output handler for the diagnostics -/* interface. An application can register multiple output handlers. -/* Output handlers are called in the specified order. -/* An output handler takes as arguments a severity level (MSG_INFO, -/* MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC, monotonically increasing -/* integer values ranging from 0 to MSG_LAST) and pre-formatted, -/* sanitized, text in the form of a null-terminated string. +/* msg_output_push() registers an output handler for the diagnostics +/* interface, ignoring a duplicate request. Output handlers are +/* called in the specified order. An output handler takes as +/* arguments: a severity level (MSG_INFO, MSG_WARN, MSG_ERROR, +/* MSG_FATAL, MSG_PANIC, i.e., integer values ranging from 0 to +/* MSG_LAST inclusive); pre-formatted, sanitized, text in the form +/* of a null-terminated string; and the caller-provided context. +/* +/* msg_output_pop() unregisters the specified output handler and +/* context, and later registrations made with msg_output_push(). It +/* invokes a panic when the handler and context are not found. /* /* msg_printf() and msg_vprintf() format their arguments, sanitize the /* result, and call the output handlers registered with msg_output(). @@ -49,9 +58,7 @@ /* block the process. /* .IP \(bu /* The signal handlers must call the above output routines not -/* until after msg_output() completes initialization, and not -/* until after the first formatted output to a VSTRING or -/* VSTREAM. +/* until after msg_output() completes initialization. /* .IP \(bu /* Each msg_output() call-back function, and each Postfix or /* system function called by that call-back function, either @@ -80,6 +87,9 @@ /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA +/* +/* Wietse Venema +/* porcupine.org /*--*/ /* System library. */ @@ -95,6 +105,7 @@ #include #include #include +#include #include /* @@ -106,16 +117,22 @@ volatile int msg_vprintf_level; * Private state. Allow one nested call, so that one logging error can be * reported to stderr before bailing out. */ +typedef struct MSG_OUT_INFO { + MSG_OUTPUT_FN output_fn; + void *context; +} MSG_OUT_INFO; + #define MSG_OUT_NESTING_LIMIT 2 -static MSG_OUTPUT_FN *msg_output_fn = 0; +static MSG_OUT_INFO *msg_out_info = 0; static int msg_output_fn_count = 0; static VSTRING *msg_buffers[MSG_OUT_NESTING_LIMIT]; -/* msg_output - specify output handler */ +/* msg_output_push - specify output handler and context */ -void msg_output(MSG_OUTPUT_FN output_fn) +void msg_output_push(MSG_OUTPUT_FN output_fn, void *context) { int i; + MSG_OUT_INFO *mp; /* * Allocate all resources during initialization. This may result in a @@ -126,16 +143,26 @@ void msg_output(MSG_OUTPUT_FN output_fn) msg_buffers[i] = vstring_alloc(100); } + /* + * Deduplicate requests. This is a purely defensive measure for the case + * that msg_vstream_init() etc. are called more than once. + */ + for (mp = msg_out_info; mp < msg_out_info + msg_output_fn_count; mp++) + if (mp->output_fn == output_fn && mp->context == context) + return; + /* * We're not doing this often, so avoid complexity and allocate memory * for an exact fit. */ if (msg_output_fn_count == 0) - msg_output_fn = (MSG_OUTPUT_FN *) mymalloc(sizeof(*msg_output_fn)); + msg_out_info = (MSG_OUT_INFO *) mymalloc(sizeof(*msg_out_info)); else - msg_output_fn = (MSG_OUTPUT_FN *) myrealloc((void *) msg_output_fn, - (msg_output_fn_count + 1) * sizeof(*msg_output_fn)); - msg_output_fn[msg_output_fn_count++] = output_fn; + msg_out_info = (MSG_OUT_INFO *) myrealloc((void *) msg_out_info, + (msg_output_fn_count + 1) * sizeof(*msg_out_info)); + msg_out_info[msg_output_fn_count++] = (MSG_OUT_INFO) { + output_fn, context + }; } /* msg_printf - format text and log it */ @@ -159,7 +186,7 @@ void msg_vprintf(int level, const char *format, va_list ap) if (msg_vprintf_level < MSG_OUT_NESTING_LIMIT) { msg_vprintf_level += 1; - /* On-the-fly initialization for test programs and startup errors. */ + /* On-the-fly initialization for test programs and startup errors. */ if (msg_output_fn_count == 0) msg_vstream_init("unknown", VSTREAM_ERR); vp = msg_buffers[msg_vprintf_level - 1]; @@ -167,8 +194,26 @@ void msg_vprintf(int level, const char *format, va_list ap) vstring_vsprintf(vp, format, ap); printable(vstring_str(vp), '?'); for (i = 0; i < msg_output_fn_count; i++) - msg_output_fn[i] (level, vstring_str(vp)); + msg_out_info[i].output_fn(level, vstring_str(vp), + msg_out_info[i].context); msg_vprintf_level -= 1; } errno = saved_errno; } + +/* msg_output_pop - pop output handler and context */ + +void msg_output_pop(MSG_OUTPUT_FN output_fn, void *context) +{ + MSG_OUT_INFO *mp; + + for (mp = msg_out_info; /* See below */ ; mp++) { + if (mp >= msg_out_info + msg_output_fn_count) + msg_panic("msg_output_pop: handler %p and context %p not found", + (void *) output_fn, (void *) context); + if (mp->output_fn == output_fn && mp->context == context) { + msg_output_fn_count = mp - msg_out_info; + return; + } + } +} diff --git a/postfix/src/util/msg_output.h b/postfix/src/util/msg_output.h index bd84276f6..b03e33cf6 100644 --- a/postfix/src/util/msg_output.h +++ b/postfix/src/util/msg_output.h @@ -19,8 +19,9 @@ * External interface. Severity levels are documented to be monotonically * increasing from 0 up to MSG_LAST. */ -typedef void (*MSG_OUTPUT_FN) (int, const char *); -extern void msg_output(MSG_OUTPUT_FN); +typedef void (*MSG_OUTPUT_FN) (int, const char *, void *); +extern void msg_output_push(MSG_OUTPUT_FN, void *); +extern void msg_output_pop(MSG_OUTPUT_FN, void *); extern void PRINTFLIKE(2, 3) msg_printf(int, const char *,...); extern void msg_vprintf(int, const char *, va_list); diff --git a/postfix/src/util/msg_output_test.c b/postfix/src/util/msg_output_test.c new file mode 100644 index 000000000..f7a316818 --- /dev/null +++ b/postfix/src/util/msg_output_test.c @@ -0,0 +1,166 @@ + /* + * Test program to exercise the msg_output module. See PTEST_README for + * documentation. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Test library. + */ +#include +#include + + /* + * 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 diff --git a/postfix/src/util/msg_syslog.c b/postfix/src/util/msg_syslog.c index 7c979c66d..eb9427cb5 100644 --- a/postfix/src/util/msg_syslog.c +++ b/postfix/src/util/msg_syslog.c @@ -157,7 +157,7 @@ static int msg_syslog_enable; /* msg_syslog_print - log info to syslog daemon */ -static void msg_syslog_print(int level, const char *text) +static void msg_syslog_print(int level, const char *text, void *unused) { static int log_level[] = { LOG_INFO, LOG_WARNING, LOG_ERR, LOG_CRIT, LOG_CRIT, @@ -207,7 +207,7 @@ void msg_syslog_init(const char *name, int logopt, int facility) openlog(name, LOG_NDELAY | logopt, facility); if (first_call) { first_call = 0; - msg_output(msg_syslog_print); + msg_output_push(msg_syslog_print, (void *) 0); } msg_syslog_enable = 1; } diff --git a/postfix/src/util/msg_vstream.c b/postfix/src/util/msg_vstream.c index 3477b546b..48dd1c43b 100644 --- a/postfix/src/util/msg_vstream.c +++ b/postfix/src/util/msg_vstream.c @@ -9,6 +9,9 @@ /* void msg_vstream_init(progname, stream) /* const char *progname; /* VSTREAM *stream; +/* +/* void msg_vstream_enable(yesno) +/* int yesno; /* DESCRIPTION /* This module implements support to report msg(3) diagnostics /* to a VSTREAM. @@ -16,6 +19,9 @@ /* msg_vstream_init() sets the program name that appears in each output /* record, and directs diagnostics (see msg(3)) to the specified /* VSTREAM. The \fIprogname\fR argument is not copied. +/* +/* msg_vstream_enable() enables or disables msg_vstream logging, +/* depending on the argument value. /* SEE ALSO /* msg(3) /* BUGS @@ -51,15 +57,19 @@ */ static const char *msg_tag; static VSTREAM *msg_stream; +static int msg_vstream_enabled; /* msg_vstream_print - log diagnostic to VSTREAM */ -static void msg_vstream_print(int level, const char *text) +static void msg_vstream_print(int level, const char *text, void *unused) { static const char *level_text[] = { "info", "warning", "error", "fatal", "panic", }; + if (!msg_vstream_enabled) + return; + if (level < 0 || level >= (int) (sizeof(level_text) / sizeof(level_text[0]))) msg_panic("invalid severity level: %d", level); if (level == MSG_INFO) { @@ -76,13 +86,16 @@ static void msg_vstream_print(int level, const char *text) void msg_vstream_init(const char *name, VSTREAM *vp) { - static int first_call = 1; - msg_tag = name; msg_stream = vp; vstream_no_debug(vp); - if (first_call) { - first_call = 0; - msg_output(msg_vstream_print); - } + msg_output_push(msg_vstream_print, (void *) 0); + msg_vstream_enabled = 1; +} + +/* msg_vstream_enable - on/off switch */ + +void msg_vstream_enable(int yesno) +{ + msg_vstream_enabled = yesno; } diff --git a/postfix/src/util/msg_vstream.h b/postfix/src/util/msg_vstream.h index d0679a047..02ec67ccb 100644 --- a/postfix/src/util/msg_vstream.h +++ b/postfix/src/util/msg_vstream.h @@ -19,6 +19,7 @@ * External interface. */ extern void msg_vstream_init(const char *, VSTREAM *); +extern void msg_vstream_enable(int); /* LICENSE /* .ad diff --git a/postfix/src/util/myaddrinfo.c b/postfix/src/util/myaddrinfo.c index dc24c58ac..ef4ca7a5d 100644 --- a/postfix/src/util/myaddrinfo.c +++ b/postfix/src/util/myaddrinfo.c @@ -193,7 +193,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/postfix/src/util/myaddrinfo.ref b/postfix/src/util/myaddrinfo.ref deleted file mode 100644 index a1aa8ca02..000000000 --- a/postfix/src/util/myaddrinfo.ref +++ /dev/null @@ -1,8 +0,0 @@ -./myaddrinfo: === hostname belly.porcupine.org === -./myaddrinfo: belly.porcupine.org:(null) -> family=2 sock=1 proto=6 168.100.3.6:0 -./myaddrinfo: 168.100.3.6:0 -> belly.porcupine.org:0 -./myaddrinfo: belly.porcupine.org:(null) -> family=28 sock=1 proto=6 2604:8d00:189::6:0 -./myaddrinfo: 2604:8d00:189::6:0 -> belly.porcupine.org:0 -./myaddrinfo: === host address 168.100.3.2 === -./myaddrinfo: 168.100.3.2:(null) -> family=2 sock=1 proto=6 168.100.3.2:0 -./myaddrinfo: 168.100.3.2:0 -> spike.porcupine.org:0 diff --git a/postfix/src/util/myaddrinfo.ref2 b/postfix/src/util/myaddrinfo.ref2 deleted file mode 100644 index d7b83d008..000000000 --- a/postfix/src/util/myaddrinfo.ref2 +++ /dev/null @@ -1,5 +0,0 @@ -./myaddrinfo: === hostname null.porcupine.org === -./myaddrinfo: warning: hostname_to_sockaddr(null.porcupine.org:(null)): hostname nor servname provided, or not known -./myaddrinfo: === host address 10.0.0.0 === -./myaddrinfo: 10.0.0.0:(null) -> family=2 sock=1 proto=6 10.0.0.0:0 -./myaddrinfo: warning: sockaddr_to_hostname: hostname nor servname provided, or not known diff --git a/postfix/src/util/myaddrinfo4.ref b/postfix/src/util/myaddrinfo4.ref deleted file mode 100644 index 50dafe9da..000000000 --- a/postfix/src/util/myaddrinfo4.ref +++ /dev/null @@ -1,6 +0,0 @@ -./myaddrinfo4: === hostname belly.porcupine.org === -./myaddrinfo4: belly.porcupine.org:(null) -> family=2 sock=1 proto=0 168.100.3.6:0 -./myaddrinfo4: warning: sockaddr_to_hostname: hostname nor servname provided, or not known -./myaddrinfo4: === host address 168.100.3.2 === -./myaddrinfo4: 168.100.3.2:(null) -> family=2 sock=1 proto=0 168.100.3.2:0 -./myaddrinfo4: warning: sockaddr_to_hostname: hostname nor servname provided, or not known diff --git a/postfix/src/util/myaddrinfo4.ref2 b/postfix/src/util/myaddrinfo4.ref2 deleted file mode 100644 index 1f6177d3c..000000000 --- a/postfix/src/util/myaddrinfo4.ref2 +++ /dev/null @@ -1,5 +0,0 @@ -./myaddrinfo4: === hostname null.porcupine.org === -./myaddrinfo4: warning: hostname_to_sockaddr(null.porcupine.org:(null)): No address associated with hostname -./myaddrinfo4: === host address 10.0.0.0 === -./myaddrinfo4: 10.0.0.0:(null) -> family=2 sock=1 proto=0 10.0.0.0:0 -./myaddrinfo4: warning: sockaddr_to_hostname: hostname nor servname provided, or not known diff --git a/postfix/src/util/myaddrinfo_test.c b/postfix/src/util/myaddrinfo_test.c new file mode 100644 index 000000000..d334ee78d --- /dev/null +++ b/postfix/src/util/myaddrinfo_test.c @@ -0,0 +1,215 @@ + /* + * Test program for the myaddrinfo module. The purpose is to verify that the + * myaddrinfo functions make the expected calls, and that they forward the + * expected results. See comments in pmock_expect_test.c, and PTEST_README + * for documentation. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Test library. + */ +#include +#include + +#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 diff --git a/postfix/src/util/mymalloc.c b/postfix/src/util/mymalloc.c index 94f7bb3e7..37a8afd2a 100644 --- a/postfix/src/util/mymalloc.c +++ b/postfix/src/util/mymalloc.c @@ -249,7 +249,7 @@ char *mystrndup(const char *str, ssize_t len) if (len < 0) msg_panic("mystrndup: requested length %ld", (long) len); #ifndef NO_SHARED_EMPTY_STRINGS - if (*str == 0) + if (*str == 0 || /* fix 20220615 */ len == 0) return ((char *) empty_string); #endif if ((cp = memchr(str, 0, len)) != 0) diff --git a/postfix/src/util/mymalloc_test.c b/postfix/src/util/mymalloc_test.c new file mode 100644 index 000000000..d64836dae --- /dev/null +++ b/postfix/src/util/mymalloc_test.c @@ -0,0 +1,307 @@ + /* + * Tests to verify mymalloc sanity checks. See PTEST_README for + * documentation. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Test library. + */ +#include + + /* + * See + */ +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 diff --git a/postfix/src/util/mystrtok.c b/postfix/src/util/mystrtok.c index d5f32b7c4..2b9f06f37 100644 --- a/postfix/src/util/mystrtok.c +++ b/postfix/src/util/mystrtok.c @@ -236,112 +236,3 @@ char *mystrtokdq_cw(char **src, const char *sep, const char *blame) return (start); } } - -#ifdef TEST - - /* - * Test program. - */ -#include "msg.h" -#include "mymalloc.h" - - /* - * The following needs to be large enough to include a null terminator in - * every testcase.expected field. - */ -#define EXPECT_SIZE 5 - -struct testcase { - const char *action; - const char *input; - const char *expected[EXPECT_SIZE]; -}; -static const struct testcase testcases[] = { - {"mystrtok", ""}, - {"mystrtok", " foo ", {"foo"}}, - {"mystrtok", " foo bar ", {"foo", "bar"}}, - {"mystrtokq", ""}, - {"mystrtokq", "foo bar", {"foo", "bar"}}, - {"mystrtokq", "{ bar } ", {"{ bar }"}}, - {"mystrtokq", "foo { bar } baz", {"foo", "{ bar }", "baz"}}, - {"mystrtokq", "foo{ bar } baz", {"foo{ bar }", "baz"}}, - {"mystrtokq", "foo { bar }baz", {"foo", "{ bar }baz"}}, - {"mystrtokdq", ""}, - {"mystrtokdq", " foo ", {"foo"}}, - {"mystrtokdq", " foo bar ", {"foo", "bar"}}, - {"mystrtokdq", " foo\\ bar ", {"foo\\ bar"}}, - {"mystrtokdq", " foo \\\" bar", {"foo", "\\\"", "bar"}}, - {"mystrtokdq", " foo \" bar baz\" ", {"foo", "\" bar baz\""}}, - {"mystrtok_cw", "#after text"}, - {"mystrtok_cw", "before-text #after text", {"before-text"}}, - {"mystrtokq_cw", "#after text"}, - {"mystrtokq_cw", "{ before text } #after text", "{ before text }"}, - {"mystrtokdq_cw", "#after text"}, - {"mystrtokdq_cw", "\"before text\" #after text", {"\"before text\""}}, -}; - -int main(void) -{ - const struct testcase *tp; - char *actual; - int pass; - int fail; - int match; - int n; - -#define NUM_TESTS sizeof(testcases)/sizeof(testcases[0]) -#define STR_OR_NULL(s) ((s) ? (s) : "null") - - for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) { - char *saved_input = mystrdup(tp->input); - char *cp = saved_input; - - msg_info("RUN test case %ld %s >%s<", - (long) (tp - testcases), tp->action, tp->input); -#if 0 - msg_info("action=%s", tp->action); - msg_info("input=%s", tp->input); - for (n = 0; tp->expected[n]; tp++) - msg_info("expected[%d]=%s", n, tp->expected[n]); -#endif - - for (n = 0; n < EXPECT_SIZE; n++) { - if (strcmp(tp->action, "mystrtok") == 0) { - actual = mystrtok(&cp, CHARS_SPACE); - } else if (strcmp(tp->action, "mystrtokq") == 0) { - actual = mystrtokq(&cp, CHARS_SPACE, CHARS_BRACE); - } else if (strcmp(tp->action, "mystrtokdq") == 0) { - actual = mystrtokdq(&cp, CHARS_SPACE); - } else if (strcmp(tp->action, "mystrtok_cw") == 0) { - actual = mystrtok_cw(&cp, CHARS_SPACE, "test"); - } else if (strcmp(tp->action, "mystrtokq_cw") == 0) { - actual = mystrtokq_cw(&cp, CHARS_SPACE, CHARS_BRACE, "test"); - } else if (strcmp(tp->action, "mystrtokdq_cw") == 0) { - actual = mystrtokdq_cw(&cp, CHARS_SPACE, "test"); - } else { - msg_panic("invalid command: %s", tp->action); - } - if ((match = (actual && tp->expected[n]) ? - (strcmp(actual, tp->expected[n]) == 0) : - (actual == tp->expected[n])) != 0) { - if (actual == 0) { - msg_info("PASS test %ld", (long) (tp - testcases)); - pass++; - break; - } - } else { - msg_warn("expected: >%s<, got: >%s<", - STR_OR_NULL(tp->expected[n]), STR_OR_NULL(actual)); - msg_info("FAIL test %ld", (long) (tp - testcases)); - fail++; - break; - } - } - if (n >= EXPECT_SIZE) - msg_panic("need to increase EXPECT_SIZE"); - myfree(saved_input); - } - return (fail > 0); -} - -#endif diff --git a/postfix/src/util/mystrtok.ref b/postfix/src/util/mystrtok.ref deleted file mode 100644 index d0ca58a35..000000000 --- a/postfix/src/util/mystrtok.ref +++ /dev/null @@ -1,48 +0,0 @@ -unknown: RUN test case 0 mystrtok >< -unknown: PASS test 0 -unknown: RUN test case 1 mystrtok > foo < -unknown: PASS test 1 -unknown: RUN test case 2 mystrtok > foo bar < -unknown: PASS test 2 -unknown: RUN test case 3 mystrtokq >< -unknown: PASS test 3 -unknown: RUN test case 4 mystrtokq >foo bar< -unknown: PASS test 4 -unknown: RUN test case 5 mystrtokq >{ bar } < -unknown: PASS test 5 -unknown: RUN test case 6 mystrtokq >foo { bar } baz< -unknown: PASS test 6 -unknown: RUN test case 7 mystrtokq >foo{ bar } baz< -unknown: PASS test 7 -unknown: RUN test case 8 mystrtokq >foo { bar }baz< -unknown: PASS test 8 -unknown: RUN test case 9 mystrtokdq >< -unknown: PASS test 9 -unknown: RUN test case 10 mystrtokdq > foo < -unknown: PASS test 10 -unknown: RUN test case 11 mystrtokdq > foo bar < -unknown: PASS test 11 -unknown: RUN test case 12 mystrtokdq > foo\ bar < -unknown: PASS test 12 -unknown: RUN test case 13 mystrtokdq > foo \" bar< -unknown: PASS test 13 -unknown: RUN test case 14 mystrtokdq > foo " bar baz" < -unknown: PASS test 14 -unknown: RUN test case 15 mystrtok_cw >#after text< -unknown: warning: test: #comment after other text is not allowed: #after text... -unknown: PASS test 15 -unknown: RUN test case 16 mystrtok_cw >before-text #after text< -unknown: warning: test: #comment after other text is not allowed: #after text... -unknown: PASS test 16 -unknown: RUN test case 17 mystrtokq_cw >#after text< -unknown: warning: test: #comment after other text is not allowed: #after text... -unknown: PASS test 17 -unknown: RUN test case 18 mystrtokq_cw >{ before text } #after text< -unknown: warning: test: #comment after other text is not allowed: #after text... -unknown: PASS test 18 -unknown: RUN test case 19 mystrtokdq_cw >#after text< -unknown: warning: test: #comment after other text is not allowed: #after text... -unknown: PASS test 19 -unknown: RUN test case 20 mystrtokdq_cw >"before text" #after text< -unknown: warning: test: #comment after other text is not allowed: #after text... -unknown: PASS test 20 diff --git a/postfix/src/util/mystrtok_test.c b/postfix/src/util/mystrtok_test.c new file mode 100644 index 000000000..0aaf7fa0a --- /dev/null +++ b/postfix/src/util/mystrtok_test.c @@ -0,0 +1,242 @@ + /* + * Test program to exercise mystrtok.c. See PTEST_README for documentation. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Test library. + */ +#include + +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 diff --git a/postfix/src/util/name_mask.c b/postfix/src/util/name_mask.c index 5aaa85986..8cec06be7 100644 --- a/postfix/src/util/name_mask.c +++ b/postfix/src/util/name_mask.c @@ -167,6 +167,9 @@ /* Enable case-insensitive matching. /* This feature is not enabled by default when calling name_mask(); /* it has no effect with str_name_mask(). +/* .IP NAME_MASK_NULL +/* When converting from mask to string, output "0" when the +/* input mask is empty. /* .IP NAME_MASK_COMMA /* Use comma instead of space when converting a mask to string. /* .IP NAME_MASK_PIPE @@ -318,6 +321,8 @@ const char *str_name_mask_delim_opt(VSTRING *buf, const char *context, } if ((len = VSTRING_LEN(buf)) > 0) vstring_truncate(buf, len - strlen(delim)); + else if (flags & NAME_MASK_NULL) + vstring_strcat(buf, "0"); VSTRING_TERMINATE(buf); return (STR(buf)); @@ -445,6 +450,8 @@ const char *str_long_name_mask_opt(VSTRING *buf, const char *context, } if ((len = VSTRING_LEN(buf)) > 0) vstring_truncate(buf, len - 1); + else if (flags & NAME_MASK_NULL) + vstring_strcat(buf, "0"); VSTRING_TERMINATE(buf); return (STR(buf)); diff --git a/postfix/src/util/name_mask.h b/postfix/src/util/name_mask.h index 1e7f393ff..27d7c4346 100644 --- a/postfix/src/util/name_mask.h +++ b/postfix/src/util/name_mask.h @@ -32,6 +32,7 @@ typedef struct { #define NAME_MASK_NUMBER (1<<5) #define NAME_MASK_WARN (1<<6) #define NAME_MASK_IGNORE (1<<7) +#define NAME_MASK_NULL (1<<8) #define NAME_MASK_REQUIRED \ (NAME_MASK_FATAL | NAME_MASK_RETURN | NAME_MASK_WARN | NAME_MASK_IGNORE) diff --git a/postfix/src/util/stream_test.c b/postfix/src/util/sunos5_stream_test.c similarity index 99% rename from postfix/src/util/stream_test.c rename to postfix/src/util/sunos5_stream_test.c index 3a6540742..5c8f82fa4 100644 --- a/postfix/src/util/stream_test.c +++ b/postfix/src/util/sunos5_stream_test.c @@ -103,11 +103,9 @@ int main(int argc, char **argv) msg_fatal("close server fd"); return (0); } - #else int main(int argc, char **argv) { return (0); } - #endif diff --git a/postfix/src/util/unescape.in b/postfix/src/util/unescape.in deleted file mode 100644 index 41f24a72d..000000000 --- a/postfix/src/util/unescape.in +++ /dev/null @@ -1,4 +0,0 @@ -\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z -\1\2\3\4\5\6\7\8\9 -\1234\2345\3456\4567 -rcpt to: diff --git a/postfix/src/util/unescape.ref b/postfix/src/util/unescape.ref deleted file mode 100644 index fb7936800..000000000 --- a/postfix/src/util/unescape.ref +++ /dev/null @@ -1,11 +0,0 @@ -0000000 \a \b c d e \f g h i j k l m \n o p - 007 010 143 144 145 014 147 150 151 152 153 154 155 012 157 160 -0000020 q \r s \t u \v w x y z \n 001 002 003 004 005 - 161 015 163 011 165 013 167 170 171 172 012 001 002 003 004 005 -0000040 006 \a 8 9 \n S 4 234 5 345 6 . 7 \n r c - 006 007 070 071 012 123 064 234 065 345 066 056 067 012 162 143 -0000060 p t t o : < w i e t s e @ 317 200 - 160 164 040 164 157 072 074 167 151 145 164 163 145 100 317 200 -0000100 . p o r c u p i n e . o r g > \n - 056 160 157 162 143 165 160 151 156 145 056 157 162 147 076 012 -0000120 diff --git a/postfix/src/util/unescape_test.c b/postfix/src/util/unescape_test.c new file mode 100644 index 000000000..75fe523fd --- /dev/null +++ b/postfix/src/util/unescape_test.c @@ -0,0 +1,93 @@ + /* + * Test program to exercise unescape.c. See PTEST_README for documentation. + */ + + /* + * System library. + */ +#include +#include + + /* + * Utility library + */ +#include +#include + + /* + * Test library. + */ +#include + +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:", + /* want */ "rcpt to:", + }, +}; + + /* + * Test library. + */ +#include diff --git a/postfix/src/util/wrap_netdb.c b/postfix/src/util/wrap_netdb.c new file mode 100644 index 000000000..7d33f2fff --- /dev/null +++ b/postfix/src/util/wrap_netdb.c @@ -0,0 +1,136 @@ +/*++ +/* NAME +/* wrap_netdb 3 +/* SUMMARY +/* mockable netdb wrappers +/* SYNOPSIS +/* #include +/* +/* 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 +#include + +#ifndef NO_MOCK_WRAPPERS + +/* wrap_getaddrinfo - wrap getaddrinfo() with stable internal API */ + +int wrap_getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, + struct addrinfo **res) +{ +#undef getaddrinfo + return (getaddrinfo(hostname, servname, hints, res)); +} + +/* wrap_freeaddrinfo - wrap freeaddrinfo() with stable internal API */ + +void wrap_freeaddrinfo(struct addrinfo *ai) +{ +#undef freeaddrinfo + freeaddrinfo(ai); +} + +/* wrap_getnameinfo - wrap getnameinfo() with stable internal API */ + +int wrap_getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, + char *serv, size_t servlen, int flags) +{ +#undef getnameinfo + return (getnameinfo(sa, salen, host, hostlen, serv, servlen, flags)); +} + +/* wrap_getservbyname - wrap getservbyname() with stable internal API */ + +struct servent *wrap_getservbyname(const char *name, const char *proto) +{ +#undef getservbyname + return (getservbyname(name, proto)); +} + +/* wrap_getservbyport - wrap getservbyport() with stable internal API */ + +struct servent *wrap_getservbyport(int port, const char *proto) +{ +#undef getservbyport + return (getservbyport(port, proto)); +} + +/* wrap_setservent - wrap setservent() with stable internal API */ + +void wrap_setservent(int stayopen) +{ +#undef setservent + return (setservent(stayopen)); +} + +/* wrap_endservent - wrap endservent() with stable internal API */ + +void wrap_endservent(void) +{ +#undef endservent + return (endservent()); +} + +#endif diff --git a/postfix/src/util/wrap_netdb.h b/postfix/src/util/wrap_netdb.h new file mode 100644 index 000000000..3e49e7a0d --- /dev/null +++ b/postfix/src/util/wrap_netdb.h @@ -0,0 +1,57 @@ +#ifndef _WRAP_NETDB_H_INCLUDED_ +#define _WRAP_NETDB_H_INCLUDED_ + +/*++ +/* NAME +/* wrap_netdb 3h +/* SUMMARY +/* mockable netdb wrappers +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library + */ +#include +#include + + /* + * 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