diff --git a/CMakeLists.txt b/CMakeLists.txt index 36040e2d5..7ae7ad447 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -927,7 +927,9 @@ if (LWS_WITH_NETWORK) ) if (LWS_WITH_ABSTRACT) list(APPEND SOURCES - lib/abstract/abstract.c) + lib/abstract/abstract.c + lib/abstract/test-sequencer.c + ) endif() if (LWS_WITH_STATS) diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 92888c1b5..04932f7c1 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -534,6 +534,8 @@ struct lws; #include +#include + #if defined(LWS_WITH_TLS) #if defined(LWS_WITH_MBEDTLS) diff --git a/include/libwebsockets/abstract/transports/unit-test.h b/include/libwebsockets/abstract/transports/unit-test.h index da96a313e..1527ec8ff 100644 --- a/include/libwebsockets/abstract/transports/unit-test.h +++ b/include/libwebsockets/abstract/transports/unit-test.h @@ -17,6 +17,12 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA + * + * This is an abstract transport useful for unit testing abstract protocols. + * + * Instead of passing data anywhere, you give the transport a list of packets + * to deliver and packets you expect back from the abstract protocol it's + * bound to. */ enum { @@ -25,30 +31,48 @@ enum { LWS_AUT_EXPECT_DO_REMOTE_CLOSE = (1 << 2), LWS_AUT_EXPECT_TX /* expect this as tx from protocol */ = (1 << 3), LWS_AUT_EXPECT_RX /* present this as rx to protocol */ = (1 << 4), + LWS_AUT_EXPECT_SHOULD_FAIL = (1 << 5), + LWS_AUT_EXPECT_SHOULD_TIMEOUT = (1 << 6), }; typedef enum { LPE_CONTINUE, LPE_SUCCEEDED, LPE_FAILED, -} lws_expect_disposition; + LPE_FAILED_UNEXPECTED_TIMEOUT, + LPE_FAILED_UNEXPECTED_PASS, + LPE_FAILED_UNEXPECTED_CLOSE, + LPE_SKIPPED, + LPE_CLOSING +} lws_unit_test_packet_disposition; -typedef struct lws_expect { - void *buffer; - size_t len; +typedef int (*lws_unit_test_packet_test_cb)(const void *cb_user, int disposition); +typedef int (*lws_unit_test_packet_cb)(lws_abs_t *instance); - uint32_t flags; -} lws_expect_t; +/* each step in the unit test */ -typedef int (*lws_expect_test_instance_init)(lws_abs_t *instance); +typedef struct lws_unit_test_packet { + void *buffer; + lws_unit_test_packet_cb pre; + size_t len; -typedef struct lws_expect_test { - const char *name; /* NULL indicates end of test array */ - lws_expect_t *expect; - lws_expect_test_instance_init *init; -} lws_expect_test_t; + uint32_t flags; +} lws_unit_test_packet_t; + +/* each unit test */ + +typedef struct lws_unit_test { + const char * name; /* NULL indicates end of test array */ + lws_unit_test_packet_t * expect_array; + int max_secs; +} lws_unit_test_t; enum { LTMI_PEER_V_EXPECT_TEST = LTMI_TRANSPORT_BASE, /* u.value */ - LTMI_PEER_V_EXPECT_TEST_ARRAY, /* u.value */ + LTMI_PEER_V_EXPECT_RESULT_CB, /* u.value */ + LTMI_PEER_V_EXPECT_RESULT_CB_ARG, /* u.value */ }; + +LWS_VISIBLE LWS_EXTERN const char * +lws_unit_test_result_name(int in); + diff --git a/include/libwebsockets/lws-sequencer.h b/include/libwebsockets/lws-sequencer.h index 0a2219039..fea857f3b 100644 --- a/include/libwebsockets/lws-sequencer.h +++ b/include/libwebsockets/lws-sequencer.h @@ -35,6 +35,7 @@ typedef enum { LWSSEQ_CREATED, /* sequencer created */ LWSSEQ_DESTROYED, /* sequencer destroyed */ LWSSEQ_TIMED_OUT, /* sequencer timeout */ + LWSSEQ_HEARTBEAT, /* 1Hz callback */ LWSSEQ_USER_BASE = 100 /* define your events from here */ } lws_seq_events_t; @@ -75,6 +76,7 @@ typedef lws_seq_cb_return_t (*lws_seq_event_cb)(struct lws_sequencer *seq, * user_size. The user area pointed to here is all zeroed * after successful sequencer creation. * \param cb: callback for events on this sequencer + * \param name: Used in sequencer logging * * This binds an abstract sequencer to a per-thread (by default, the single * event loop of an lws_context). After the event loop starts, the sequencer @@ -88,7 +90,7 @@ typedef lws_seq_cb_return_t (*lws_seq_event_cb)(struct lws_sequencer *seq, */ LWS_VISIBLE LWS_EXTERN lws_sequencer_t * lws_sequencer_create(struct lws_context *context, int tsi, size_t user_size, - void **puser, lws_seq_event_cb cb); + void **puser, lws_seq_event_cb cb, const char *name); /** * lws_sequencer_destroy() - destroy the sequencer @@ -171,3 +173,14 @@ lws_sequencer_from_user(void *u); */ LWS_VISIBLE LWS_EXTERN int lws_sequencer_secs_since_creation(lws_sequencer_t *seq); + +/** + * lws_sequencer_name(): get the name of this sequencer + * + * \param seq: pointer to the lws_sequencer_t + * + * Returns the name given when the sequencer was created. This is useful to + * annotate logging when then are multiple sequencers in play. + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_sequencer_name(lws_sequencer_t *seq); diff --git a/include/libwebsockets/lws-test-sequencer.h b/include/libwebsockets/lws-test-sequencer.h new file mode 100644 index 000000000..45680b08d --- /dev/null +++ b/include/libwebsockets/lws-test-sequencer.h @@ -0,0 +1,60 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * included from libwebsockets.h + * + * lws_test_sequencer manages running an array of unit tests. + */ + +typedef void (*lws_test_sequence_cb)(const void *cb_user); + +typedef struct lws_test_sequencer_args { + lws_abs_t *abs; /* abstract protocol + unit test txport */ + lws_unit_test_t *tests; /* array of lws_unit_test_t */ + int *results; /* takes result dispositions */ + int results_max; /* max space usable in results */ + int *count_tests; /* count of done tests */ + int *count_passes; /* count of passed tests */ + lws_test_sequence_cb cb; /* completion callback */ + void *cb_user; /* opaque user ptr given to cb */ +} lws_test_sequencer_args_t; + +/** + * lws_abs_unit_test_sequencer() - helper to sequence multiple unit tests + * + * \param args: lws_test_sequencer_args_t prepared with arguments for the tests + * + * This helper sequences one or more unit tests to run and collects the results. + * + * The incoming abs should be set up for the abstract protocol you want to test + * and the lws unit-test transport. + * + * Results are one of + * + * LPE_SUCCEEDED + * LPE_FAILED + * LPE_FAILED_UNEXPECTED_TIMEOUT + * LPE_FAILED_UNEXPECTED_PASS + * LPE_FAILED_UNEXPECTED_CLOSE + * + * The callback args->cb is called when the tests have been done. + */ +LWS_VISIBLE LWS_EXTERN int +lws_abs_unit_test_sequencer(const lws_test_sequencer_args_t *args); diff --git a/lib/abstract/README.md b/lib/abstract/README.md index 91a84bc62..865b69843 100644 --- a/lib/abstract/README.md +++ b/lib/abstract/README.md @@ -153,3 +153,18 @@ static const lws_token_map_t smtp_abs_tokens[] = { - add your `lws_abs_transport` to the list `available_abs_transports` in `./lib/abstract/abstract.c` +# Protocol testing + +## unit tests + +lws features an abstract transport designed to facilitate unit testing. This +contains an lws_sequencer that performs the steps of tests involving sending the +protocol test vector buffers and confirming the response of the protocol matches +the test vectors. + +## test-sequencer + +test-sequencer is a helper that sequences running an array of unit tests and +collects the statistics and gives a PASS / FAIL result. + +See the SMTP client api test for an example of how to use. diff --git a/lib/abstract/abstract.c b/lib/abstract/abstract.c index 68dcf54ef..0f52869c8 100644 --- a/lib/abstract/abstract.c +++ b/lib/abstract/abstract.c @@ -89,15 +89,17 @@ lws_abs_get_token(const lws_token_map_t *token_map, short name_index) void lws_abs_destroy_instance(lws_abs_t **ai) { - if ((*ai)->api) - (*ai)->ap->destroy(&(*ai)->api); - if ((*ai)->ati) - (*ai)->at->destroy(&(*ai)->ati); + lws_abs_t *a = *ai; - lws_dll2_remove(&(*ai)->abstract_instances); + if (a->api) + a->ap->destroy(&a->api); + if (a->ati) + a->at->destroy(&a->ati); + + lws_dll2_remove(&a->abstract_instances); - free(*ai); *ai = NULL; + free(a); } lws_abs_t * diff --git a/lib/abstract/protocols/smtp/smtp.c b/lib/abstract/protocols/smtp/smtp.c index f0444f62d..668ab195d 100644 --- a/lib/abstract/protocols/smtp/smtp.c +++ b/lib/abstract/protocols/smtp/smtp.c @@ -174,8 +174,6 @@ lws_smtp_client_abs_rx(lws_abs_protocol_inst_t *api, uint8_t *buf, size_t len) if (!e) return 0; - lwsl_debug("%s: rx: '%.*s'\n", __func__, (int)len, (const char *)buf); - n = atoi((char *)buf); if (n != retcodes[c->estate]) { lwsl_notice("%s: bad response from server: %d (state %d) %.*s\n", diff --git a/lib/abstract/test-sequencer.c b/lib/abstract/test-sequencer.c new file mode 100644 index 000000000..b38a65fe0 --- /dev/null +++ b/lib/abstract/test-sequencer.c @@ -0,0 +1,265 @@ +/* + * libwebsockets lib/abstract/test-sequencer.c + * + * Copyright (C) 2019 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * + * A helper for running multiple unit tests against abstract protocols. + * + * An lws_sequencer_t is used to base its actions in the event loop and manage + * the sequencing of multiple tests. A new abstract connection is instantiated + * for each test using te + */ + +#include + +struct lws_seq_test_sequencer { + lws_abs_t original_abs; + + lws_test_sequencer_args_t args; + + struct lws_context *context; + struct lws_vhost *vhost; + lws_sequencer_t *unit_test_seq; + + /* holds the per-test token for the unit-test transport to consume */ + lws_token_map_t uttt[4]; + + lws_abs_t *instance; + + int state; +}; + +/* sequencer messages specific to this sequencer */ + +enum { + SEQ_MSG_PASS = LWSSEQ_USER_BASE, + SEQ_MSG_FAIL, + SEQ_MSG_FAIL_TIMEOUT, +}; + +/* + * We get called back when the unit test transport has decided if the test + * passed or failed. We get the priv, and report to the sequencer message queue + * what the result was. + */ + +static int +unit_test_result_cb(const void *cb_user, int disposition) +{ + const struct lws_seq_test_sequencer *s = + (const struct lws_seq_test_sequencer *)cb_user; + int r; + + lwsl_debug("%s: disp %d\n", __func__, disposition); + + switch (disposition) { + case LPE_FAILED_UNEXPECTED_PASS: + case LPE_FAILED_UNEXPECTED_CLOSE: + case LPE_FAILED: + r = SEQ_MSG_FAIL; + break; + + case LPE_FAILED_UNEXPECTED_TIMEOUT: + r = SEQ_MSG_FAIL_TIMEOUT; + break; + + case LPE_SUCCEEDED: + r = SEQ_MSG_PASS; + break; + + default: + assert(0); + return -1; + } + + lws_sequencer_event(s->unit_test_seq, r, NULL); + + ((struct lws_seq_test_sequencer *)s)->instance = NULL; + + return 0; +} + +/* + * We receive the unit test result callback's messages via the message queue. + * + * We log the results and always move on to the next test until there are no + * more tests. + */ + +static lws_seq_cb_return_t +test_sequencer_cb(struct lws_sequencer *seq, void *user, int event, void *data) +{ + struct lws_seq_test_sequencer *s = + (struct lws_seq_test_sequencer *)user; + lws_unit_test_packet_t *exp = (lws_unit_test_packet_t *) + s->args.tests[s->state].expect_array; + lws_abs_t test_abs; + + switch ((int)event) { + case LWSSEQ_CREATED: /* our sequencer just got started */ + lwsl_notice("%s: %s: created\n", __func__, + lws_sequencer_name(seq)); + s->state = 0; /* first thing we'll do is the first url */ + goto step; + + case LWSSEQ_DESTROYED: + /* + * We are going down... if we have a child unit test sequencer + * still around inform and destroy it + */ + if (s->instance) { + s->instance->at->close(s->instance); + s->instance = NULL; + } + break; + + case SEQ_MSG_FAIL_TIMEOUT: /* current step timed out */ + if (exp->flags & LWS_AUT_EXPECT_SHOULD_TIMEOUT) { + lwsl_user("%s: test %d got expected timeout\n", + __func__, s->state); + + goto pass; + } + lwsl_user("%s: seq timed out at step %d\n", __func__, s->state); + + s->args.results[s->state] = LPE_FAILED_UNEXPECTED_TIMEOUT; + goto done; /* always move on to the next test */ + + case SEQ_MSG_FAIL: + if (exp->flags & LWS_AUT_EXPECT_SHOULD_FAIL) { + /* + * in this case, we expected to fail like this, it's OK + */ + lwsl_user("%s: test %d failed as expected\n", + __func__, s->state); + + goto pass; /* always move on to the next test */ + } + + lwsl_user("%s: seq failed at step %d\n", __func__, s->state); + + s->args.results[s->state] = LPE_FAILED; + goto done; /* always move on to the next test */ + + case SEQ_MSG_PASS: + if (exp->flags & (LWS_AUT_EXPECT_SHOULD_FAIL | + LWS_AUT_EXPECT_SHOULD_TIMEOUT)) { + /* + * In these specific cases, done would be a failure, + * we expected to timeout or fail + */ + lwsl_user("%s: seq failed at step %d\n", __func__, + s->state); + + s->args.results[s->state] = LPE_FAILED_UNEXPECTED_PASS; + + goto done; /* always move on to the next test */ + } + lwsl_info("%s: seq done test %d\n", __func__, s->state); +pass: + (*s->args.count_passes)++; + s->args.results[s->state] = LPE_SUCCEEDED; + +done: + lws_sequencer_timeout(lws_sequencer_from_user(s), 0); + s->state++; +step: + if (!s->args.tests[s->state].name) { + /* the sequence has completed */ + lwsl_user("%s: sequence completed OK\n", __func__); + + if (s->args.cb) + s->args.cb(s->args.cb_user); + + return LWSSEQ_RET_DESTROY; + } + lwsl_info("%s: starting test %d\n", __func__, s->state); + + if (s->state >= s->args.results_max) { + lwsl_err("%s: results array is too small\n", __func__); + + return LWSSEQ_RET_DESTROY; + } + test_abs = s->original_abs; + s->uttt[0].name_index = LTMI_PEER_V_EXPECT_TEST; + s->uttt[0].u.value = (void *)&s->args.tests[s->state]; + s->uttt[1].name_index = LTMI_PEER_V_EXPECT_RESULT_CB; + s->uttt[1].u.value = (void *)unit_test_result_cb; + s->uttt[2].name_index = LTMI_PEER_V_EXPECT_RESULT_CB_ARG; + s->uttt[2].u.value = (void *)s; + /* give the unit test transport the test tokens */ + test_abs.at_tokens = s->uttt; + + s->instance = lws_abs_bind_and_create_instance(&test_abs); + if (!s->instance) { + lwsl_notice("%s: failed to create step %d unit test\n", + __func__, s->state); + + return LWSSEQ_RET_DESTROY; + } + (*s->args.count_tests)++; + break; + + default: + break; + } + + return LWSSEQ_RET_CONTINUE; +} + + +/* + * Creates an lws_sequencer to manage the test sequence + */ + +int +lws_abs_unit_test_sequencer(const lws_test_sequencer_args_t *args) +{ + struct lws_seq_test_sequencer *s; + lws_sequencer_t *seq; + + /* + * Create a sequencer in the event loop to manage the tests + */ + + seq = lws_sequencer_create(args->abs->vh->context, 0, + sizeof(struct lws_seq_test_sequencer), + (void **)&s, test_sequencer_cb, "test-seq"); + if (!seq) { + lwsl_err("%s: unable to create sequencer\n", __func__); + return 1; + } + + /* + * Take a copy of the original lws_abs_t we were passed so we can use + * it as the basis of the lws_abs_t we create the individual tests with + */ + s->original_abs = *args->abs; + + s->args = *args; + + s->context = args->abs->vh->context; + s->vhost = args->abs->vh; + s->unit_test_seq = seq; + + *s->args.count_tests = 0; + *s->args.count_passes = 0; + + return 0; +} diff --git a/lib/abstract/transports/unit-test.c b/lib/abstract/transports/unit-test.c index 7cd85cff8..ccee684f3 100644 --- a/lib/abstract/transports/unit-test.c +++ b/lib/abstract/transports/unit-test.c @@ -18,57 +18,67 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * + * * An abstract transport that is useful for unit testing an abstract protocol. * It doesn't actually connect to anything, but checks the protocol's response - * to various canned packets. - * - * Although it doesn't use any socket itself, it still needs to respect the - * event loop so it can reflect the associated behaviours correctly. So it - * creates a wsi for these purposes, which is a RAW FILE open on /dev/null. + * to provided canned packets from an array of test vectors. */ #include "core/private.h" #include "abstract/private.h" +/* this is the transport priv instantiated at abs->ati */ + typedef struct lws_abstxp_unit_test_priv { - char note[128]; - struct lws_abs *abs; + char note[128]; + struct lws_abs *abs; - struct lws *wsi; - lws_expect_test_t *current_test; - lws_expect_t *expect; - lws_expect_disposition disposition; - int filefd; + lws_sequencer_t *seq; + lws_unit_test_t *current_test; + lws_unit_test_packet_t *expect; + lws_unit_test_packet_test_cb result_cb; + const void *result_cb_arg; - lws_dll2_t same_abs_transport_list; + lws_unit_test_packet_disposition disposition; + /* synthesized protocol timeout */ + time_t timeout; - uint8_t established:1; - uint8_t connecting:1; + uint8_t established:1; + uint8_t connecting:1; } abs_unit_test_priv_t; -struct vhd { - lws_dll2_owner_t owner; +typedef struct seq_priv { + lws_abs_t *ai; +} seq_priv_t; + +enum { + UTSEQ_MSG_WRITEABLE = LWSSEQ_USER_BASE, + UTSEQ_MSG_CLOSING, + UTSEQ_MSG_TIMEOUT, + UTSEQ_MSG_CONNECTING, + UTSEQ_MSG_POST_TX_KICK, + UTSEQ_MSG_DISPOSITION_KNOWN }; /* * A definitive result has appeared for the current test */ -static lws_expect_disposition -lws_expect_dispose(abs_unit_test_priv_t *priv, lws_expect_disposition disp, - const char *note) +static lws_unit_test_packet_disposition +lws_unit_test_packet_dispose(abs_unit_test_priv_t *priv, + lws_unit_test_packet_disposition disp, + const char *note) { assert(priv->disposition == LPE_CONTINUE); + lwsl_info("%s: %d\n", __func__, disp); + if (note) lws_strncpy(priv->note, note, sizeof(priv->note)); priv->disposition = disp; - lwsl_user("%s: %s: test %d: %s\n", priv->abs->ap->name, - priv->current_test->name, - (int)(priv->expect - priv->current_test->expect), - disp == LPE_SUCCEEDED ? "OK" : "FAIL"); + lws_sequencer_event(priv->seq, UTSEQ_MSG_DISPOSITION_KNOWN, NULL); return disp; } @@ -77,25 +87,35 @@ lws_expect_dispose(abs_unit_test_priv_t *priv, lws_expect_disposition disp, * start on the next step of the test */ -lws_expect_disposition +lws_unit_test_packet_disposition process_expect(abs_unit_test_priv_t *priv) { assert(priv->disposition == LPE_CONTINUE); - while (priv->expect->flags & LWS_AUT_EXPECT_RX) { - int f = priv->expect->flags & LWS_AUT_EXPECT_LOCAL_CLOSE, - s = priv->abs->ap->rx(priv->abs->api, priv->expect->buffer, - priv->expect->len); + while (priv->expect->flags & LWS_AUT_EXPECT_RX && + priv->disposition == LPE_CONTINUE) { + int f = priv->expect->flags & LWS_AUT_EXPECT_LOCAL_CLOSE, s; + + if (priv->expect->pre) + priv->expect->pre(priv->abs); + + lwsl_info("%s: rx()\n", __func__); + lwsl_hexdump_debug(priv->expect->buffer, priv->expect->len); + s = priv->abs->ap->rx(priv->abs->api, priv->expect->buffer, + priv->expect->len); + if (!!f != !!s) { lwsl_notice("%s: expected rx return %d, got %d\n", __func__, !!f, s); - return lws_expect_dispose(priv, LPE_FAILED, + return lws_unit_test_packet_dispose(priv, LPE_FAILED, "rx unexpected return"); } - if (priv->expect->flags & LWS_AUT_EXPECT_TEST_END) - return lws_expect_dispose(priv, LPE_SUCCEEDED, NULL); + if (priv->expect->flags & LWS_AUT_EXPECT_TEST_END) { + lws_unit_test_packet_dispose(priv, LPE_SUCCEEDED, NULL); + break; + } priv->expect++; } @@ -103,112 +123,164 @@ process_expect(abs_unit_test_priv_t *priv) return LPE_CONTINUE; } -static int -heartbeat_cb(struct lws_dll2 *d, void *user) +static lws_seq_cb_return_t +unit_test_sequencer_cb(struct lws_sequencer *seq, void *user, int event, + void *data) { - abs_unit_test_priv_t *priv = lws_container_of(d, abs_unit_test_priv_t, - same_abs_transport_list); + seq_priv_t *s = (seq_priv_t *)user; + abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)s->ai->ati; + time_t now; - if (priv->abs->ap->heartbeat) - priv->abs->ap->heartbeat(priv->abs->api); + switch ((int)event) { + case LWSSEQ_CREATED: /* our sequencer just got started */ + lwsl_notice("%s: %s: created\n", __func__, + lws_sequencer_name(seq)); + if (s->ai->at->client_conn(s->ai)) { + lwsl_notice("%s: %s: abstract client conn failed\n", + __func__, lws_sequencer_name(seq)); - return 0; -} - -static int -callback_abs_client_unit_test(struct lws *wsi, enum lws_callback_reasons reason, - void *user, void *in, size_t len) -{ - abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)user; - struct vhd *vhd = (struct vhd *) - lws_protocol_vh_priv_get(lws_get_vhost(wsi), - lws_get_protocol(wsi)); - - switch (reason) { - case LWS_CALLBACK_PROTOCOL_INIT: - vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), - lws_get_protocol(wsi), sizeof(struct vhd)); - if (!vhd) - return 1; - - lws_timed_callback_vh_protocol(lws_get_vhost(wsi), - lws_get_protocol(wsi), - LWS_CALLBACK_USER, 1); + return LWSSEQ_RET_DESTROY; + } break; - case LWS_CALLBACK_USER: + case LWSSEQ_DESTROYED: /* - * This comes at 1Hz without a wsi context, so there is no - * valid priv. We need to track the live abstract objects that - * are using our abstract protocol, and pass the heartbeat - * through to the ones that care. + * This sequencer is about to be destroyed. If we have any + * other assets in play, detach them from us. */ - if (!vhd) - break; - lws_dll2_foreach_safe(&vhd->owner, NULL, heartbeat_cb); + if (priv->abs) + lws_abs_destroy_instance(&priv->abs); - lws_timed_callback_vh_protocol(lws_get_vhost(wsi), - lws_get_protocol(wsi), - LWS_CALLBACK_USER, 1); break; - case LWS_CALLBACK_RAW_ADOPT_FILE: - lwsl_debug("LWS_CALLBACK_RAW_ADOPT_FILE\n"); - priv->connecting = 0; - priv->established = 1; + case LWSSEQ_HEARTBEAT: + + /* synthesize a wsi-style timeout */ + + if (!priv->timeout) + goto ph; + + time(&now); + + if (now <= priv->timeout) + goto ph; + + if (priv->expect->flags & LWS_AUT_EXPECT_SHOULD_TIMEOUT) { + lwsl_user("%s: test got expected timeout\n", + __func__); + lws_unit_test_packet_dispose(priv, + LPE_FAILED_UNEXPECTED_TIMEOUT, NULL); + + return LWSSEQ_RET_DESTROY; + } + lwsl_user("%s: seq timed out\n", __func__); + +ph: + if (priv->abs->ap->heartbeat) + priv->abs->ap->heartbeat(priv->abs->api); + break; + + case UTSEQ_MSG_DISPOSITION_KNOWN: + + lwsl_info("%s: %s: DISPOSITION_KNOWN %s: %s\n", __func__, + priv->abs->ap->name, + priv->current_test->name, + priv->disposition == LPE_SUCCEEDED ? "OK" : "FAIL"); + + /* + * if the test has a callback, call it back to let it + * know the result + */ + if (priv->result_cb) + priv->result_cb(priv->result_cb_arg, priv->disposition); + + return LWSSEQ_RET_DESTROY; + + case UTSEQ_MSG_CONNECTING: + lwsl_debug("UTSEQ_MSG_CONNECTING\n"); + if (priv->abs->ap->accept) priv->abs->ap->accept(priv->abs->api); - break; - case LWS_CALLBACK_RAW_CLOSE_FILE: - if (!user) - break; - lwsl_debug("LWS_CALLBACK_RAW_CLOSE_FILE\n"); - priv->established = 0; - priv->connecting = 0; + priv->established = 1; + + /* fallthru */ + + case UTSEQ_MSG_POST_TX_KICK: + if (priv->disposition) + break; + + if (process_expect(priv) != LPE_CONTINUE) { + lwsl_notice("%s: UTSEQ_MSG_POST_TX_KICK failed\n", + __func__); + return LWSSEQ_RET_DESTROY; + } + break; + + case UTSEQ_MSG_WRITEABLE: + /* + * inform the protocol our transport is writeable now + */ + priv->abs->ap->writeable(priv->abs->api, 1024); + break; + + case UTSEQ_MSG_CLOSING: + + if (!(priv->expect->flags & LWS_AUT_EXPECT_LOCAL_CLOSE)) { + lwsl_user("%s: got unexpected close\n", __func__); + + lws_unit_test_packet_dispose(priv, + LPE_FAILED_UNEXPECTED_CLOSE, NULL); + goto done; + } + + /* tell the abstract protocol we are closing on them */ + if (priv->abs && priv->abs->ap->closed) priv->abs->ap->closed(priv->abs->api); - if (priv->filefd != -1) - close(priv->filefd); - priv->filefd = -1; - lws_set_wsi_user(wsi, NULL); - break; - case LWS_CALLBACK_RAW_WRITEABLE_FILE: - lwsl_debug("LWS_CALLBACK_RAW_WRITEABLE_FILE\n"); - priv->abs->ap->writeable(priv->abs->api, - lws_get_peer_write_allowance(priv->wsi)); - break; + goto done; - case LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL: - lws_dll2_add_tail(&priv->same_abs_transport_list, &vhd->owner); - break; + case UTSEQ_MSG_TIMEOUT: /* current step timed out */ - case LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL: - lws_dll2_remove(&priv->same_abs_transport_list); + s->ai->at->close(s->ai->ati); + + if (!(priv->expect->flags & LWS_AUT_EXPECT_SHOULD_TIMEOUT)) { + lwsl_user("%s: got unexpected timeout\n", __func__); + + lws_unit_test_packet_dispose(priv, + LPE_FAILED_UNEXPECTED_TIMEOUT, NULL); + return LWSSEQ_RET_DESTROY; + } + goto done; + +done: + lws_sequencer_timeout(lws_sequencer_from_user(s), 0); + priv->expect++; + if (!priv->expect->buffer) { + /* the sequence has completed */ + lwsl_user("%s: sequence completed OK\n", __func__); + + return LWSSEQ_RET_DESTROY; + } break; default: break; } - return 0; + return LWSSEQ_RET_CONTINUE; } -const struct lws_protocols protocol_abs_client_unit_test = { - "lws-abs-cli-unit-test", callback_abs_client_unit_test, - 0, 1024, 1024, NULL, 0 -}; - static int lws_atcut_close(lws_abs_transport_inst_t *ati) { abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati; - lws_set_timeout(priv->wsi, 1, LWS_TO_KILL_SYNC); + lwsl_notice("%s\n", __func__); - /* priv is destroyed in the CLOSE callback */ + lws_sequencer_event(priv->seq, UTSEQ_MSG_CLOSING, NULL); return 0; } @@ -220,10 +292,15 @@ lws_atcut_tx(lws_abs_transport_inst_t *ati, uint8_t *buf, size_t len) assert(priv->disposition == LPE_CONTINUE); + lwsl_info("%s: received tx\n", __func__); + + if (priv->expect->pre) + priv->expect->pre(priv->abs); + if (!(priv->expect->flags & LWS_AUT_EXPECT_TX)) { lwsl_notice("%s: unexpected tx\n", __func__); lwsl_hexdump_notice(buf, len); - lws_expect_dispose(priv, LPE_FAILED, "unexpected tx"); + lws_unit_test_packet_dispose(priv, LPE_FAILED, "unexpected tx"); return 1; } @@ -231,28 +308,32 @@ lws_atcut_tx(lws_abs_transport_inst_t *ati, uint8_t *buf, size_t len) if (len != priv->expect->len) { lwsl_notice("%s: unexpected tx len %zu, expected %zu\n", __func__, len, priv->expect->len); - lws_expect_dispose(priv, LPE_FAILED, "tx len mismatch"); + lws_unit_test_packet_dispose(priv, LPE_FAILED, + "tx len mismatch"); return 1; } if (memcmp(buf, priv->expect->buffer, len)) { lwsl_notice("%s: tx mismatch (exp / actual)\n", __func__); - lwsl_hexdump_notice(priv->expect->buffer, len); - lwsl_hexdump_notice(buf, len); - lws_expect_dispose(priv, LPE_FAILED, "tx data mismatch"); + lwsl_hexdump_debug(priv->expect->buffer, len); + lwsl_hexdump_debug(buf, len); + lws_unit_test_packet_dispose(priv, LPE_FAILED, + "tx data mismatch"); return 1; } if (priv->expect->flags & LWS_AUT_EXPECT_TEST_END) { - lws_expect_dispose(priv, LPE_SUCCEEDED, NULL); + lws_unit_test_packet_dispose(priv, LPE_SUCCEEDED, NULL); return 1; } priv->expect++; + lws_sequencer_event(priv->seq, UTSEQ_MSG_POST_TX_KICK, NULL); + return 0; } @@ -262,32 +343,13 @@ lws_atcut_client_conn(const lws_abs_t *abs) { abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)abs->ati; const lws_token_map_t *tm; - lws_sock_file_fd_type u; - - /* - * we do this fresh for each test - */ - - if (priv->connecting || priv->established) - return 0; - - priv->filefd = lws_open("/dev/null", O_RDWR); - if (priv->filefd == -1) { - lwsl_err("%s: Unable to open /dev/null\n", __func__); - - return 1; - } - u.filefd = (lws_filefd_type)(long long)priv->filefd; - if (!lws_adopt_descriptor_vhost(priv->abs->vh, LWS_ADOPT_RAW_FILE_DESC, - u, "unit-test", NULL)) { - lwsl_err("Failed to adopt file descriptor\n"); - close(priv->filefd); - priv->filefd = -1; + if (priv->established) { + lwsl_err("%s: already established\n", __func__); return 1; } - /* set up the test start pieces */ + /* set up the test start pieces... the array of test expects... */ tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_V_EXPECT_TEST); if (!tm) { @@ -296,17 +358,31 @@ lws_atcut_client_conn(const lws_abs_t *abs) return 1; } - priv->current_test = (lws_expect_test_t *)tm->u.value; - priv->expect = priv->current_test->expect; + priv->current_test = (lws_unit_test_t *)tm->u.value; + + /* ... and the callback to deliver the result to */ + tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_V_EXPECT_RESULT_CB); + if (tm) + priv->result_cb = (lws_unit_test_packet_test_cb)tm->u.value; + else + priv->result_cb = NULL; + + /* ... and the arg to deliver it with */ + tm = lws_abs_get_token(abs->at_tokens, + LTMI_PEER_V_EXPECT_RESULT_CB_ARG); + if (tm) + priv->result_cb_arg = tm->u.value; + + priv->expect = priv->current_test->expect_array; priv->disposition = LPE_CONTINUE; priv->note[0] = '\0'; - lwsl_notice("%s: %s: %s: start\n", __func__, abs->ap->name, + lws_sequencer_timeout(priv->seq, priv->current_test->max_secs); + + lwsl_notice("%s: %s: test '%s': start\n", __func__, abs->ap->name, priv->current_test->name); - process_expect(priv); - - priv->connecting = 1; + lws_sequencer_event(priv->seq, UTSEQ_MSG_CONNECTING, NULL); return 0; } @@ -320,18 +396,48 @@ lws_atcut_ask_for_writeable(lws_abs_transport_inst_t *ati) if (!priv->established) return 1; - lws_callback_on_writable(priv->wsi); + /* + * Queue a writeable event... this won't be handled by teh sequencer + * until we have returned to the event loop, just like a real + * callback_on_writable() + */ + lws_sequencer_event(priv->seq, UTSEQ_MSG_WRITEABLE, NULL); return 0; } -static int -lws_atcut_create(struct lws_abs *ai) -{ - abs_unit_test_priv_t *at = (abs_unit_test_priv_t *)ai->ati; +/* + * An abstract protocol + transport has been instantiated + */ - memset(at, 0, sizeof(*at)); - at->abs = ai; +static int +lws_atcut_create(lws_abs_t *ai) +{ + abs_unit_test_priv_t *priv; + lws_sequencer_t *seq; + seq_priv_t *s; + + /* + * Create the sequencer for the steps in a single unit test + */ + + seq = lws_sequencer_create(ai->vh->context, 0, sizeof(*s), + (void **)&s, unit_test_sequencer_cb, + "unit-test-seq"); + if (!seq) { + lwsl_err("%s: unable to create sequencer\n", __func__); + + return 1; + } + + priv = ai->ati; + memset(s, 0, sizeof(*s)); + memset(priv, 0, sizeof(*priv)); + + /* the sequencer priv just points to the lws_abs_t */ + s->ai = ai; + priv->abs = ai; + priv->seq = seq; return 0; } @@ -351,8 +457,14 @@ static int lws_atcut_set_timeout(lws_abs_transport_inst_t *ati, int reason, int secs) { abs_unit_test_priv_t *priv = (abs_unit_test_priv_t *)ati; + time_t now; - lws_set_timeout(priv->wsi, reason, secs); + time(&now); + + if (secs) + priv->timeout = now + secs; + else + priv->timeout = 0; return 0; } @@ -368,6 +480,27 @@ lws_atcut_state(lws_abs_transport_inst_t *ati) return 1; } +static const char *dnames[] = { + "INCOMPLETE", + "PASS", + "FAIL", + "FAIL(TIMEOUT)", + "FAIL(UNEXPECTED PASS)", + "FAIL(UNEXPECTED CLOSE)", + "SKIPPED" + "?", + "?" +}; + + +const char * +lws_unit_test_result_name(int in) +{ + if (in < 0 || in > (int)LWS_ARRAY_SIZE(dnames)) + return "unknown"; + + return dnames[in]; +} const lws_abs_transport_t lws_abs_transport_cli_unit_test = { .name = "unit_test", @@ -387,33 +520,3 @@ const lws_abs_transport_t lws_abs_transport_cli_unit_test = { .set_timeout = lws_atcut_set_timeout, .state = lws_atcut_state, }; - -/* - * This goes through the test array instantiating a new protocol + transport - * for each test and keeping track of the results - */ - -int -lws_abs_transport_unit_test_helper(lws_abs_t *abs) -{ - lws_abs_t *instance; - const lws_token_map_t *tm; - - tm = lws_abs_get_token(abs->at_tokens, LTMI_PEER_V_EXPECT_TEST_ARRAY); - if (!tm) { - lwsl_err("%s: LTMI_PEER_V_EXPECT_TEST_ARRAY is required\n", - __func__); - - return 1; - } - - //wh - - instance = lws_abs_bind_and_create_instance(abs); - if (!instance) { - lwsl_err("%s: failed to create SMTP client\n", __func__); - return 1; - } - - return 0; -} diff --git a/lib/core-net/sequencer.c b/lib/core-net/sequencer.c index 405747d2d..557df85ad 100644 --- a/lib/core-net/sequencer.c +++ b/lib/core-net/sequencer.c @@ -42,6 +42,7 @@ typedef struct lws_sequencer { struct lws_dll2_owner seq_event_owner; struct lws_context_per_thread *pt; lws_seq_event_cb cb; + const char *name; time_t time_created; time_t timeout; /* 0 or time we timeout */ @@ -53,7 +54,7 @@ typedef struct lws_sequencer { lws_sequencer_t * lws_sequencer_create(struct lws_context *context, int tsi, size_t user_size, - void **puser, lws_seq_event_cb cb) + void **puser, lws_seq_event_cb cb, const char *name) { struct lws_context_per_thread *pt = &context->pt[tsi]; lws_sequencer_t *seq = lws_zalloc(sizeof(*seq) + user_size, __func__); @@ -63,6 +64,7 @@ lws_sequencer_create(struct lws_context *context, int tsi, size_t user_size, seq->cb = cb; seq->pt = pt; + seq->name = name; *puser = (void *)&seq[1]; @@ -108,8 +110,11 @@ lws_sequencer_destroy(lws_sequencer_t **pseq) /* defeat another thread racing to add events while we are destroying */ seq->going_down = 1; + seq->cb(seq, (void *)&seq[1], LWSSEQ_DESTROYED, NULL); + lws_pt_lock(seq->pt, __func__); /* -------------------------- pt { */ + lws_dll2_remove(&seq->seq_list); lws_dll2_remove(&seq->seq_to_list); lws_dll2_remove(&seq->seq_pend_list); /* remove and destroy any pending events */ @@ -117,7 +122,6 @@ lws_sequencer_destroy(lws_sequencer_t **pseq) lws_pt_unlock(seq->pt); /* } pt ---------------------------------- */ - seq->cb(seq, (void *)&seq[1], LWSSEQ_DESTROYED, NULL); lws_free_set_NULL(seq); } @@ -125,14 +129,20 @@ lws_sequencer_destroy(lws_sequencer_t **pseq) int lws_sequencer_event(lws_sequencer_t *seq, lws_seq_events_t e, void *data) { - lws_seq_event_t *seqe = lws_zalloc(sizeof(*seqe), __func__); + lws_seq_event_t *seqe; - if (!seqe || seq->going_down) + if (!seq || seq->going_down) + return 1; + + seqe = lws_zalloc(sizeof(*seqe), __func__); + if (!seqe) return 1; seqe->e = e; seqe->data = data; + // lwsl_notice("%s: seq %s: event %d\n", __func__, seq->name, e); + lws_pt_lock(seq->pt, __func__); /* ----------------------------- pt { */ if (seq->seq_event_owner.count > QUEUE_SANITY_LIMIT) { @@ -193,7 +203,8 @@ lws_sequencer_next_event(struct lws_dll2 *d, void *user) lws_pt_unlock(seq->pt); /* } pt ------------------------------------- */ if (n) { - lwsl_info("%s: destroying seq by request\n", __func__); + lwsl_info("%s: destroying seq '%s' by request\n", __func__, + seq->name); lws_sequencer_destroy(&seq); return LWSSEQ_RET_DESTROY; @@ -291,6 +302,18 @@ lws_sequencer_timeout_check(struct lws_context_per_thread *pt, time_t now) } lws_end_foreach_dll_safe(p, tp); + /* send every sequencer a heartbeat message... it can ignore it */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp, + pt->seq_owner.head) { + lws_sequencer_t *s = lws_container_of(p, lws_sequencer_t, + seq_list); + + /* queue the message to inform the sequencer */ + lws_sequencer_event(s, LWSSEQ_HEARTBEAT, NULL); + + } lws_end_foreach_dll_safe(p, tp); + return 0; } @@ -300,6 +323,12 @@ lws_sequencer_from_user(void *u) return &((lws_sequencer_t *)u)[-1]; } +const char * +lws_sequencer_name(lws_sequencer_t *seq) +{ + return seq->name; +} + int lws_sequencer_secs_since_creation(lws_sequencer_t *seq) { diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index 586336804..f788f1210 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -60,9 +60,6 @@ const struct lws_event_loop_ops *available_event_libs[] = { const struct lws_protocols *available_abstract_protocols[] = { #if defined(LWS_ROLE_RAW) &protocol_abs_client_raw_skt, -#endif -#if defined(LWS_WITH_ABSTRACT) - &protocol_abs_client_unit_test, #endif NULL }; diff --git a/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt b/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt index 4c8e671ff..43f424695 100644 --- a/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt +++ b/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 2.8) include(CheckCSourceCompiles) -set(SAMP lws-unit-tests-smtp-client) +set(SAMP lws-api-test-smtp_client) set(SRCS main.c) # If we are being built as part of lws, confirm current build config supports diff --git a/minimal-examples/abstract/protocols/smtp-client/main.c b/minimal-examples/abstract/protocols/smtp-client/main.c index 7ed859a06..47d48979d 100644 --- a/minimal-examples/abstract/protocols/smtp-client/main.c +++ b/minimal-examples/abstract/protocols/smtp-client/main.c @@ -1,5 +1,5 @@ /* - * lws-unit-tests-smtp-client + * lws-api-test-smtp_client * * Written in 2010-2019 by Andy Green * @@ -14,66 +14,6 @@ static int interrupted, result = 1; static const char *recip; -/* - * from https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol - */ - -static lws_expect_t test_send1[] = { - { - "220 smtp.example.com ESMTP Postfix", - 34, LWS_AUT_EXPECT_RX - }, { - "HELO lws-test-client", - 20, LWS_AUT_EXPECT_TX - }, { - "250 smtp.example.com, I am glad to meet you", - 43, LWS_AUT_EXPECT_RX - }, { - "MAIL FROM:", - 31, LWS_AUT_EXPECT_TX - }, { - "250 Ok", - 6, LWS_AUT_EXPECT_RX - }, { - "RCPT TO:andy@warmcat.com", - 24, LWS_AUT_EXPECT_TX - }, { - "250 Ok", - 6, LWS_AUT_EXPECT_RX - }, { - "DATA", - 4, LWS_AUT_EXPECT_TX - }, { - "354 End data with .", - 35, LWS_AUT_EXPECT_RX - }, { - "From: noreply@example.com\n" - "To: andy@warmcat.com\n" - "Subject: Test email for lws smtp-client\n" - "\n" - "Hello this was an api test for lws smtp-client\n" - "\r\n.\r\n", - 27 + 21 + 39 + 1 + 46 + 5, LWS_AUT_EXPECT_TX - }, { - "250 Ok: queued as 12345", - 23, LWS_AUT_EXPECT_RX - }, { - "QUIT", - 4, LWS_AUT_EXPECT_TX - }, { - "221 Bye", - 7, LWS_AUT_EXPECT_RX | - LWS_AUT_EXPECT_LOCAL_CLOSE | - LWS_AUT_EXPECT_DO_REMOTE_CLOSE | - LWS_AUT_EXPECT_TEST_END - } -}; - -static lws_expect_test_t tests[] = { - { "sending", test_send1 }, - { } -}; - static void sigint_handler(int sig) { @@ -99,43 +39,6 @@ email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len) return 0; } -/* - * The test helper calls this on the instance it created to prepare it for - * the test. - */ - -static int -smtp_test_instance_init(lws_abs_t *instance) -{ - lws_smtp_email_t email; - - /* attach an email to it */ - - memset(&email, 0, sizeof(email)); - email.data = NULL /* email specific user data */; - email.email_from = "noreply@warmcat.com"; - email.email_to = "andy@warmcat.com"; - email.payload = malloc(2048); - if (!email.payload) - return 1; - - lws_snprintf((char *)email.payload, 2048, - "From: noreply@example.com\n" - "To: %s\n" - "Subject: Test email for lws smtp-client\n" - "\n" - "Hello this was an api test for lws smtp-client\n" - "\r\n.\r\n", recip); - email.done = email_sent_or_failed; - - if (lws_smtp_client_add_email(instance, &email)) { - lwsl_err("%s: failed to add email\n", __func__); - return 1; - } - - return 0; -} - /* * We're going to bind to the raw-skt transport, so tell that what we want it * to connect to @@ -143,8 +46,11 @@ smtp_test_instance_init(lws_abs_t *instance) static const lws_token_map_t smtp_raw_skt_transport_tokens[] = { { - .u = { .value = (const char *)tests }, - .name_index = LTMI_PEER_V_EXPECT_TEST_ARRAY, + .u = { .value = "127.0.0.1" }, + .name_index = LTMI_PEER_V_DNS_ADDRESS, + }, { + .u = { .lvalue = 25 }, + .name_index = LTMI_PEER_LV_PORT, }, { } }; @@ -153,7 +59,6 @@ static const lws_token_map_t smtp_protocol_tokens[] = { { .u = { .value = "lws-test-client" }, .name_index = LTMI_PSMTP_V_HELO, - .init = smtp_test_instance_init, }, { } }; @@ -164,6 +69,8 @@ int main(int argc, const char **argv) int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; struct lws_context_creation_info info; struct lws_context *context; + lws_abs_t abs, *instance; + lws_smtp_email_t email; struct lws_vhost *vh; const char *p; @@ -200,7 +107,9 @@ int main(int argc, const char **argv) goto bail1; } - /* create the smtp client */ + /* + * create an smtp client that's hooked up to real sockets + */ memset(&abs, 0, sizeof(abs)); abs.vh = vh; @@ -214,22 +123,41 @@ int main(int argc, const char **argv) /* select the transport and bind its tokens */ - abs.at = lws_abs_transport_get_by_name("unit_tests"); + abs.at = lws_abs_transport_get_by_name("raw_skt"); if (!abs.at) goto bail1; - - /* - * The transport token we pass here to the test helper is the array - * of tests. The helper will iterate through it instantiating test - * connections with one test each. - */ abs.at_tokens = smtp_raw_skt_transport_tokens; - if (lws_abs_transport_unit_test_helper(&abs)) { + instance = lws_abs_bind_and_create_instance(&abs); + if (!instance) { lwsl_err("%s: failed to create SMTP client\n", __func__); goto bail1; } + /* attach an email to it */ + + memset(&email, 0, sizeof(email)); + email.data = NULL /* email specific user data */; + email.email_from = "andy@warmcat.com"; + email.email_to = recip; + email.payload = malloc(2048); + if (!email.payload) { + goto bail1; + } + + lws_snprintf((char *)email.payload, 2048, + "From: noreply@example.com\n" + "To: %s\n" + "Subject: Test email for lws smtp-client\n" + "\n" + "Hello this was an api test for lws smtp-client\n" + "\r\n.\r\n", recip); + email.done = email_sent_or_failed; + + if (lws_smtp_client_add_email(instance, &email)) { + lwsl_err("%s: failed to add email\n", __func__); + goto bail; + } /* the usual lws event loop */ diff --git a/minimal-examples/api-tests/api-test-lws_sequencer/main.c b/minimal-examples/api-tests/api-test-lws_sequencer/main.c index cbb3209d3..2ec07e8b2 100644 --- a/minimal-examples/api-tests/api-test-lws_sequencer/main.c +++ b/minimal-examples/api-tests/api-test-lws_sequencer/main.c @@ -370,7 +370,7 @@ main(int argc, const char **argv) */ seq = lws_sequencer_create(context, 0, sizeof(struct myseq), - (void **)&s, sequencer_cb); + (void **)&s, sequencer_cb, "seq"); if (!seq) { lwsl_err("%s: unable to create sequencer\n", __func__); goto bail1; diff --git a/minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt b/minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt index 43f424695..4c8e671ff 100644 --- a/minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt +++ b/minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 2.8) include(CheckCSourceCompiles) -set(SAMP lws-api-test-smtp_client) +set(SAMP lws-unit-tests-smtp-client) set(SRCS main.c) # If we are being built as part of lws, confirm current build config supports diff --git a/minimal-examples/api-tests/api-test-smtp_client/README.md b/minimal-examples/api-tests/api-test-smtp_client/README.md index a3b3d01ff..4c2052d92 100644 --- a/minimal-examples/api-tests/api-test-smtp_client/README.md +++ b/minimal-examples/api-tests/api-test-smtp_client/README.md @@ -1,6 +1,11 @@ # lws api test smtp client -Demonstrates how to send email through your local MTA +Performs unit tests on the lws SMTP client abstract protocol +implementation. + +The first test "sends mail to a server" (actually is prompted by +test vectors that look like a server) and the second test +confirm it can handle rejection by the "server" cleanly. ## build @@ -19,11 +24,18 @@ Commandline option|Meaning ``` - $ ./lws-api-test-smtp_client -r andy@warmcat.com -[2019/04/17 05:12:06:5293] USER: LWS API selftest: SMTP client -[2019/04/17 05:12:06:5635] NOTICE: LGSSMTP_IDLE: connecting to 127.0.0.1:25 -[2019/04/17 05:12:06:6238] NOTICE: email_sent_or_failed: sent OK -[2019/04/17 05:12:06:6394] USER: Completed: PASS - + $ ./lws-api-test-smtp_client +[2019/06/28 21:56:41:0711] USER: LWS API selftest: SMTP client unit tests +[2019/06/28 21:56:41:1114] NOTICE: test_sequencer_cb: test-seq: created +[2019/06/28 21:56:41:1259] NOTICE: unit_test_sequencer_cb: unit-test-seq: created +[2019/06/28 21:56:41:1272] NOTICE: lws_atcut_client_conn: smtp: test 'sending': start +[2019/06/28 21:56:41:1441] NOTICE: unit_test_sequencer_cb: unit-test-seq: created +[2019/06/28 21:56:41:1442] NOTICE: lws_atcut_client_conn: smtp: test 'rejected': start +[2019/06/28 21:56:41:1453] NOTICE: lws_smtp_client_abs_rx: bad response from server: 500 (state 4) 500 Service Unavailable +[2019/06/28 21:56:41:1467] USER: test_sequencer_cb: sequence completed OK +[2019/06/28 21:56:41:1474] USER: main: 2 tests 0 fail +[2019/06/28 21:56:41:1476] USER: test 0: PASS +[2019/06/28 21:56:41:1478] USER: test 1: PASS +[2019/06/28 21:56:41:1480] USER: Completed: PASS ``` diff --git a/minimal-examples/api-tests/api-test-smtp_client/main.c b/minimal-examples/api-tests/api-test-smtp_client/main.c index d7a1d2d33..4b64334bf 100644 --- a/minimal-examples/api-tests/api-test-smtp_client/main.c +++ b/minimal-examples/api-tests/api-test-smtp_client/main.c @@ -1,18 +1,157 @@ /* - * lws-api-test-smtp_client + * lws-unit-tests-smtp-client * * Written in 2010-2019 by Andy Green * * This file is made available under the Creative Commons CC0 1.0 * Universal Public Domain Dedication. + * + * This performs unit tests for the SMTP client abstract protocol */ #include #include -static int interrupted, result = 1; -static const char *recip; +static int interrupted, results[10], count_tests, count_passes; + +static int +email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len) +{ + free(email); + + return 0; +} + +/* + * The test helper calls this on the instance it created to prepare it for + * the test. In our case, we need to queue up a test email to send on the + * smtp client abstract protocol. + */ + +static int +smtp_test_instance_init(lws_abs_t *instance) +{ + lws_smtp_email_t *email = (lws_smtp_email_t *) + malloc(sizeof(*email) + 2048); + + if (!email) + return 1; + + /* attach an email to it */ + + memset(email, 0, sizeof(*email)); + email->data = NULL /* email specific user data */; + email->email_from = "noreply@warmcat.com"; + email->email_to = "andy@warmcat.com"; + email->payload = (void *)&email[1]; + + lws_snprintf((char *)email->payload, 2048, + "From: noreply@example.com\n" + "To: %s\n" + "Subject: Test email for lws smtp-client\n" + "\n" + "Hello this was an api test for lws smtp-client\n" + "\r\n.\r\n", "andy@warmcat.com"); + email->done = email_sent_or_failed; + + if (lws_smtp_client_add_email(instance, email)) { + lwsl_err("%s: failed to add email\n", __func__); + return 1; + } + + return 0; +} + +/* + * from https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol + * + * test vector sent to protocol + * test vector received from protocol + */ + +static lws_unit_test_packet_t test_send1[] = { + { + "220 smtp.example.com ESMTP Postfix", + smtp_test_instance_init, 34, LWS_AUT_EXPECT_RX + }, { + "HELO lws-test-client\x0a", + NULL, 21, LWS_AUT_EXPECT_TX + }, { + "250 smtp.example.com, I am glad to meet you", + NULL, 43, LWS_AUT_EXPECT_RX + }, { + "MAIL FROM: \x0a", + NULL, 33, LWS_AUT_EXPECT_TX + }, { + "250 Ok", + NULL, 6, LWS_AUT_EXPECT_RX + }, { + "RCPT TO: \x0a", + NULL, 28, LWS_AUT_EXPECT_TX + }, { + "250 Ok", + NULL, 6, LWS_AUT_EXPECT_RX + }, { + "DATA\x0a", + NULL, 5, LWS_AUT_EXPECT_TX + }, { + "354 End data with .\x0a", + NULL, 35, LWS_AUT_EXPECT_RX + }, { + "From: noreply@example.com\n" + "To: andy@warmcat.com\n" + "Subject: Test email for lws smtp-client\n" + "\n" + "Hello this was an api test for lws smtp-client\n" + "\r\n.\r\n", + NULL, 27 + 21 + 39 + 1 + 47 + 5, LWS_AUT_EXPECT_TX + }, { + "250 Ok: queued as 12345\x0a", + NULL, 23, LWS_AUT_EXPECT_RX + }, { + "quit\x0a", + NULL, 5, LWS_AUT_EXPECT_TX + }, { + "221 Bye\x0a", + NULL, 7, LWS_AUT_EXPECT_RX | + LWS_AUT_EXPECT_LOCAL_CLOSE | + LWS_AUT_EXPECT_DO_REMOTE_CLOSE | + LWS_AUT_EXPECT_TEST_END + }, { /* sentinel */ + + } +}; + + +static lws_unit_test_packet_t test_send2[] = { + { + "220 smtp.example.com ESMTP Postfix", + smtp_test_instance_init, 34, LWS_AUT_EXPECT_RX + }, { + "HELO lws-test-client\x0a", + NULL, 21, LWS_AUT_EXPECT_TX + }, { + "250 smtp.example.com, I am glad to meet you", + NULL, 43, LWS_AUT_EXPECT_RX + }, { + "MAIL FROM: \x0a", + NULL, 33, LWS_AUT_EXPECT_TX + }, { + "500 Service Unavailable", + NULL, 23, LWS_AUT_EXPECT_RX | + LWS_AUT_EXPECT_DO_REMOTE_CLOSE | + LWS_AUT_EXPECT_TEST_END + }, { /* sentinel */ + + } +}; + +static lws_unit_test_t tests[] = { + { "sending", test_send1, 3 }, + { "rejected", test_send2, 3 }, + { } /* sentinel */ +}; static void sigint_handler(int sig) @@ -20,58 +159,32 @@ sigint_handler(int sig) interrupted = 1; } -static int -email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len) -{ - /* you could examine email->data here */ - if (buf) - lwsl_notice("%s: %.*s\n", __func__, (int)len, (const char *)buf); - else - lwsl_notice("%s:\n", __func__); - - /* destroy any allocations in email */ - - free((char *)email->payload); - - result = 0; - interrupted = 1; - - return 0; -} - /* - * We're going to bind to the raw-skt transport, so tell that what we want it - * to connect to + * set the HELO our SMTP client will use */ -static const lws_token_map_t smtp_raw_skt_transport_tokens[] = { - { - .u = { .value = "127.0.0.1" }, - .name_index = LTMI_PEER_V_DNS_ADDRESS, - }, { - .u = { .lvalue = 25 }, - .name_index = LTMI_PEER_LV_PORT, - }, { - } -}; - static const lws_token_map_t smtp_protocol_tokens[] = { { .u = { .value = "lws-test-client" }, .name_index = LTMI_PSMTP_V_HELO, - }, { + }, { /* sentinel */ } }; +void +tests_completion_cb(const void *cb_user) +{ + interrupted = 1; +} int main(int argc, const char **argv) { int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; struct lws_context_creation_info info; + lws_test_sequencer_args_t args; struct lws_context *context; - lws_abs_t abs, *instance; - lws_smtp_email_t email; struct lws_vhost *vh; + lws_abs_t abs, *instance; const char *p; /* the normal lws init */ @@ -81,15 +194,8 @@ int main(int argc, const char **argv) if ((p = lws_cmdline_option(argc, argv, "-d"))) logs = atoi(p); - p = lws_cmdline_option(argc, argv, "-r"); - if (!p) { - lwsl_err("-r is required\n"); - return 1; - } - recip = p; - lws_set_log_level(logs, NULL); - lwsl_user("LWS API selftest: SMTP client\n"); + lwsl_user("LWS API selftest: SMTP client unit tests\n"); memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ info.port = CONTEXT_PORT_NO_LISTEN; @@ -107,9 +213,7 @@ int main(int argc, const char **argv) goto bail1; } - /* - * create an smtp client that's hooked up to real sockets - */ + /* create the smtp client */ memset(&abs, 0, sizeof(abs)); abs.vh = vh; @@ -124,52 +228,48 @@ int main(int argc, const char **argv) /* select the transport and bind its tokens */ - abs.at = lws_abs_transport_get_by_name("raw_skt"); + abs.at = lws_abs_transport_get_by_name("unit_test"); if (!abs.at) goto bail1; - abs.at_tokens = smtp_raw_skt_transport_tokens; - instance = lws_abs_bind_and_create_instance(&abs); if (!instance) goto bail1; - /* attach an email to it */ + /* configure the test sequencer */ - memset(&email, 0, sizeof(email)); - email.data = NULL /* email specific user data */; - email.email_from = "andy@warmcat.com"; - email.email_to = recip; - email.payload = malloc(2048); - if (!email.payload) { + args.abs = &abs; + args.tests = tests; + args.results = results; + args.results_max = LWS_ARRAY_SIZE(results); + args.count_tests = &count_tests; + args.count_passes = &count_passes; + args.cb = tests_completion_cb; + args.cb_user = NULL; + + if (lws_abs_unit_test_sequencer(&args)) { + lwsl_err("%s: failed to create test sequencer\n", __func__); goto bail1; } - lws_snprintf((char *)email.payload, 2048, - "From: noreply@example.com\n" - "To: %s\n" - "Subject: Test email for lws smtp-client\n" - "\n" - "Hello this was an api test for lws smtp-client\n" - "\r\n.\r\n", recip); - email.done = email_sent_or_failed; - - if (lws_smtp_client_add_email(instance, &email)) { - lwsl_err("%s: failed to add email\n", __func__); - goto bail; - } - /* the usual lws event loop */ while (n >= 0 && !interrupted) n = lws_service(context, 1000); -bail: + /* describe the overall test results */ + + lwsl_user("%s: %d tests %d fail\n", __func__, count_tests, + count_tests - count_passes); + for (n = 0; n < count_tests; n++) + lwsl_user(" test %d: %s\n", n, + lws_unit_test_result_name(results[n])); bail1: - lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS"); + lwsl_user("Completed: %s\n", + !count_tests || count_passes != count_tests ? "FAIL" : "PASS"); lws_context_destroy(context); - return result; + return !count_tests || count_passes != count_tests; }