{"schema":"libjg2-1",
"vpath":"/git/",
"avatar":"/git/avatar/",
"alang":"",
"gen_ut":1745477804,
"reponame":"libwebsockets",
"desc":"libwebsockets lightweight C networking library",
"owner": { "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" },"url":"https://libwebsockets.org/repo/libwebsockets",
"f":3,
"items": [
{"schema":"libjg2-1",
"cid":"bad2386bcf15ac2cb0cb327760249958",
"commit": {"type":"commit",
"time": 1561838916,
"time_ofs": 60,
"oid_tree": { "oid": "0d9be6b7d11bb45673e24a6408f66269b0f41c92", "alias": []},
"oid":{ "oid": "a7e1bac4acb959ee9ffe38d5588eb24847bbe16c", "alias": []},
"msg": "unit test sequencer",
"sig_commit": { "git_time": { "time": 1561838916, "offset": 60 }, "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" },
"sig_author": { "git_time": { "time": 1561555966, "offset": 60 }, "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" }},
"body": "unit test sequencer"
,
"diff": "diff --git a/CMakeLists.txt b/CMakeLists.txt\nindex 36040e2..7ae7ad4 100644\n--- a/CMakeLists.txt\n+++ b/CMakeLists.txt\n@@ -927,7 +927,9 @@ if (LWS_WITH_NETWORK)\n \t)\n \tif (LWS_WITH_ABSTRACT)\n \t\tlist(APPEND SOURCES\n-\t\t\tlib/abstract/abstract.c)\n+\t\t\tlib/abstract/abstract.c\n+\t\t\tlib/abstract/test-sequencer.c\n+\t\t)\n \tendif()\n \t\n \tif (LWS_WITH_STATS)\ndiff --git a/include/libwebsockets.h b/include/libwebsockets.h\nindex 92888c1..04932f7 100644\n--- a/include/libwebsockets.h\n+++ b/include/libwebsockets.h\n@@ -534,6 +534,8 @@ struct lws;\n \n #include \u003clibwebsockets/abstract/abstract.h\u003e\n \n+#include \u003clibwebsockets/lws-test-sequencer.h\u003e\n+\n #if defined(LWS_WITH_TLS)\n \n #if defined(LWS_WITH_MBEDTLS)\ndiff --git a/include/libwebsockets/abstract/transports/unit-test.h b/include/libwebsockets/abstract/transports/unit-test.h\nindex da96a31..1527ec8 100644\n--- a/include/libwebsockets/abstract/transports/unit-test.h\n+++ b/include/libwebsockets/abstract/transports/unit-test.h\n@@ -17,6 +17,12 @@\n * License along with this library; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA 02110-1301 USA\n+ *\n+ * This is an abstract transport useful for unit testing abstract protocols.\n+ *\n+ * Instead of passing data anywhere, you give the transport a list of packets\n+ * to deliver and packets you expect back from the abstract protocol it's\n+ * bound to.\n */\n \n enum {\n@@ -25,30 +31,48 @@ enum {\n \tLWS_AUT_EXPECT_DO_REMOTE_CLOSE\t\t\t\t\u003d (1 \u003c\u003c 2),\n \tLWS_AUT_EXPECT_TX /* expect this as tx from protocol */\t\u003d (1 \u003c\u003c 3),\n \tLWS_AUT_EXPECT_RX /* present this as rx to protocol */\t\u003d (1 \u003c\u003c 4),\n+\tLWS_AUT_EXPECT_SHOULD_FAIL\t\t\t\t\u003d (1 \u003c\u003c 5),\n+\tLWS_AUT_EXPECT_SHOULD_TIMEOUT\t\t\t\t\u003d (1 \u003c\u003c 6),\n };\n \n typedef enum {\n \tLPE_CONTINUE,\n \tLPE_SUCCEEDED,\n \tLPE_FAILED,\n-} lws_expect_disposition;\n+\tLPE_FAILED_UNEXPECTED_TIMEOUT,\n+\tLPE_FAILED_UNEXPECTED_PASS,\n+\tLPE_FAILED_UNEXPECTED_CLOSE,\n+\tLPE_SKIPPED,\n+\tLPE_CLOSING\n+} lws_unit_test_packet_disposition;\n+\n+typedef int (*lws_unit_test_packet_test_cb)(const void *cb_user, int disposition);\n+typedef int (*lws_unit_test_packet_cb)(lws_abs_t *instance);\n \n-typedef struct lws_expect {\n-\tvoid *buffer;\n-\tsize_t len;\n+/* each step in the unit test */\n \n-\tuint32_t flags;\n-} lws_expect_t;\n+typedef struct lws_unit_test_packet {\n+\tvoid\t\t\t\t*buffer;\n+\tlws_unit_test_packet_cb\t\tpre;\n+\tsize_t\t\t\t\tlen;\n \n-typedef int (*lws_expect_test_instance_init)(lws_abs_t *instance);\n+\tuint32_t\t\t\tflags;\n+} lws_unit_test_packet_t;\n \n-typedef struct lws_expect_test {\n-\tconst char *name;\t\t/* NULL indicates end of test array */\n-\tlws_expect_t *expect;\n-\tlws_expect_test_instance_init *init;\n-} lws_expect_test_t;\n+/* each unit test */\n+\n+typedef struct lws_unit_test {\n+\tconst char *\t\tname; /* NULL indicates end of test array */\n+\tlws_unit_test_packet_t *\t\texpect_array;\n+\tint\t\t\tmax_secs;\n+} lws_unit_test_t;\n \n enum {\n \tLTMI_PEER_V_EXPECT_TEST \u003d LTMI_TRANSPORT_BASE,\t/* u.value */\n-\tLTMI_PEER_V_EXPECT_TEST_ARRAY,\t\t\t/* u.value */\n+\tLTMI_PEER_V_EXPECT_RESULT_CB,\t\t\t/* u.value */\n+\tLTMI_PEER_V_EXPECT_RESULT_CB_ARG,\t\t/* u.value */\n };\n+\n+LWS_VISIBLE LWS_EXTERN const char *\n+lws_unit_test_result_name(int in);\n+\ndiff --git a/include/libwebsockets/lws-sequencer.h b/include/libwebsockets/lws-sequencer.h\nindex 0a22190..fea857f 100644\n--- a/include/libwebsockets/lws-sequencer.h\n+++ b/include/libwebsockets/lws-sequencer.h\n@@ -35,6 +35,7 @@ typedef enum {\n \tLWSSEQ_CREATED,\t\t/* sequencer created */\n \tLWSSEQ_DESTROYED,\t/* sequencer destroyed */\n \tLWSSEQ_TIMED_OUT,\t/* sequencer timeout */\n+\tLWSSEQ_HEARTBEAT,\t/* 1Hz callback */\n \n \tLWSSEQ_USER_BASE \u003d 100\t/* define your events from here */\n } lws_seq_events_t;\n@@ -75,6 +76,7 @@ typedef lws_seq_cb_return_t (*lws_seq_event_cb)(struct lws_sequencer *seq,\n *\t\t\tuser_size. The user area pointed to here is all zeroed\n *\t\t\tafter successful sequencer creation.\n * \u005cparam cb:\t\tcallback for events on this sequencer\n+ * \u005cparam name:\t\tUsed in sequencer logging\n *\n * This binds an abstract sequencer to a per-thread (by default, the single\n * event loop of an lws_context). After the event loop starts, the sequencer\n@@ -88,7 +90,7 @@ typedef lws_seq_cb_return_t (*lws_seq_event_cb)(struct lws_sequencer *seq,\n */\n LWS_VISIBLE LWS_EXTERN lws_sequencer_t *\n lws_sequencer_create(struct lws_context *context, int tsi, size_t user_size,\n-\t\t void **puser, lws_seq_event_cb cb);\n+\t\t void **puser, lws_seq_event_cb cb, const char *name);\n \n /**\n * lws_sequencer_destroy() - destroy the sequencer\n@@ -171,3 +173,14 @@ lws_sequencer_from_user(void *u);\n */\n LWS_VISIBLE LWS_EXTERN int\n lws_sequencer_secs_since_creation(lws_sequencer_t *seq);\n+\n+/**\n+ * lws_sequencer_name(): get the name of this sequencer\n+ *\n+ * \u005cparam seq: pointer to the lws_sequencer_t\n+ *\n+ * Returns the name given when the sequencer was created. This is useful to\n+ * annotate logging when then are multiple sequencers in play.\n+ */\n+LWS_VISIBLE LWS_EXTERN const char *\n+lws_sequencer_name(lws_sequencer_t *seq);\ndiff --git a/include/libwebsockets/lws-test-sequencer.h b/include/libwebsockets/lws-test-sequencer.h\nnew file mode 100644\nindex 0000000..45680b0\n--- /dev/null\n+++ b/include/libwebsockets/lws-test-sequencer.h\n@@ -0,0 +1,60 @@\n+/*\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2010-2019 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This library is free software; you can redistribute it and/or\n+ * modify it under the terms of the GNU Lesser General Public\n+ * License as published by the Free Software Foundation:\n+ * version 2.1 of the License.\n+ *\n+ * This library is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n+ * Lesser General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU Lesser General Public\n+ * License along with this library; if not, write to the Free Software\n+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n+ * MA 02110-1301 USA\n+ *\n+ * included from libwebsockets.h\n+ *\n+ * lws_test_sequencer manages running an array of unit tests.\n+ */\n+\n+typedef void (*lws_test_sequence_cb)(const void *cb_user);\n+\n+typedef struct lws_test_sequencer_args {\n+\tlws_abs_t\t\t*abs; /* abstract protocol + unit test txport */\n+\tlws_unit_test_t\t*tests; /* array of lws_unit_test_t */\n+\tint\t\t\t*results; /* takes result dispositions */\n+\tint\t\t\tresults_max; /* max space usable in results */\n+\tint\t\t\t*count_tests; /* count of done tests */\n+\tint\t\t\t*count_passes; /* count of passed tests */\n+\tlws_test_sequence_cb\tcb; /* completion callback */\n+\tvoid\t\t\t*cb_user; /* opaque user ptr given to cb */\n+} lws_test_sequencer_args_t;\n+\n+/**\n+ * lws_abs_unit_test_sequencer() - helper to sequence multiple unit tests\n+ *\n+ * \u005cparam args: lws_test_sequencer_args_t prepared with arguments for the tests\n+ *\n+ * This helper sequences one or more unit tests to run and collects the results.\n+ *\n+ * The incoming abs should be set up for the abstract protocol you want to test\n+ * and the lws unit-test transport.\n+ *\n+ * Results are one of\n+ *\n+ * \tLPE_SUCCEEDED\n+ *\tLPE_FAILED\n+ *\tLPE_FAILED_UNEXPECTED_TIMEOUT\n+ *\tLPE_FAILED_UNEXPECTED_PASS\n+ *\tLPE_FAILED_UNEXPECTED_CLOSE\n+ *\n+ * The callback args-\u003ecb is called when the tests have been done.\n+ */\n+LWS_VISIBLE LWS_EXTERN int\n+lws_abs_unit_test_sequencer(const lws_test_sequencer_args_t *args);\ndiff --git a/lib/abstract/README.md b/lib/abstract/README.md\nindex 91a84bc..865b698 100644\n--- a/lib/abstract/README.md\n+++ b/lib/abstract/README.md\n@@ -153,3 +153,18 @@ static const lws_token_map_t smtp_abs_tokens[] \u003d {\n - add your `lws_abs_transport` to the list `available_abs_transports` in\n `./lib/abstract/abstract.c`\n \n+# Protocol testing\n+\n+## unit tests\n+\n+lws features an abstract transport designed to facilitate unit testing. This\n+contains an lws_sequencer that performs the steps of tests involving sending the\n+protocol test vector buffers and confirming the response of the protocol matches\n+the test vectors.\n+\n+## test-sequencer\n+\n+test-sequencer is a helper that sequences running an array of unit tests and\n+collects the statistics and gives a PASS / FAIL result.\n+\n+See the SMTP client api test for an example of how to use.\ndiff --git a/lib/abstract/abstract.c b/lib/abstract/abstract.c\nindex 68dcf54..0f52869 100644\n--- a/lib/abstract/abstract.c\n+++ b/lib/abstract/abstract.c\n@@ -89,15 +89,17 @@ lws_abs_get_token(const lws_token_map_t *token_map, short name_index)\n void\n lws_abs_destroy_instance(lws_abs_t **ai)\n {\n-\tif ((*ai)-\u003eapi)\n-\t\t(*ai)-\u003eap-\u003edestroy(\u0026(*ai)-\u003eapi);\n-\tif ((*ai)-\u003eati)\n-\t\t(*ai)-\u003eat-\u003edestroy(\u0026(*ai)-\u003eati);\n+\tlws_abs_t *a \u003d *ai;\n \n-\tlws_dll2_remove(\u0026(*ai)-\u003eabstract_instances);\n+\tif (a-\u003eapi)\n+\t\ta-\u003eap-\u003edestroy(\u0026a-\u003eapi);\n+\tif (a-\u003eati)\n+\t\ta-\u003eat-\u003edestroy(\u0026a-\u003eati);\n+\n+\tlws_dll2_remove(\u0026a-\u003eabstract_instances);\n \n-\tfree(*ai);\n \t*ai \u003d NULL;\n+\tfree(a);\n }\n \n lws_abs_t *\ndiff --git a/lib/abstract/protocols/smtp/smtp.c b/lib/abstract/protocols/smtp/smtp.c\nindex f0444f6..668ab19 100644\n--- a/lib/abstract/protocols/smtp/smtp.c\n+++ b/lib/abstract/protocols/smtp/smtp.c\n@@ -174,8 +174,6 @@ lws_smtp_client_abs_rx(lws_abs_protocol_inst_t *api, uint8_t *buf, size_t len)\n \tif (!e)\n \t\treturn 0;\n \n-\tlwsl_debug(\u0022%s: rx: '%.*s'\u005cn\u0022, __func__, (int)len, (const char *)buf);\n-\n \tn \u003d atoi((char *)buf);\n \tif (n !\u003d retcodes[c-\u003eestate]) {\n \t\tlwsl_notice(\u0022%s: bad response from server: %d (state %d) %.*s\u005cn\u0022,\ndiff --git a/lib/abstract/test-sequencer.c b/lib/abstract/test-sequencer.c\nnew file mode 100644\nindex 0000000..b38a65f\n--- /dev/null\n+++ b/lib/abstract/test-sequencer.c\n@@ -0,0 +1,265 @@\n+/*\n+ * libwebsockets lib/abstract/test-sequencer.c\n+ *\n+ * Copyright (C) 2019 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This library is free software; you can redistribute it and/or\n+ * modify it under the terms of the GNU Lesser General Public\n+ * License as published by the Free Software Foundation:\n+ * version 2.1 of the License.\n+ *\n+ * This library is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n+ * Lesser General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU Lesser General Public\n+ * License along with this library; if not, write to the Free Software\n+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n+ * MA 02110-1301 USA\n+ *\n+ *\n+ * A helper for running multiple unit tests against abstract protocols.\n+ *\n+ * An lws_sequencer_t is used to base its actions in the event loop and manage\n+ * the sequencing of multiple tests. A new abstract connection is instantiated\n+ * for each test using te\n+ */\n+\n+#include \u003ccore/private.h\u003e\n+\n+struct lws_seq_test_sequencer {\n+\tlws_abs_t\t\t\toriginal_abs;\n+\n+\tlws_test_sequencer_args_t\targs;\n+\n+\tstruct lws_context\t\t*context;\n+\tstruct lws_vhost\t\t*vhost;\n+\tlws_sequencer_t\t\t\t*unit_test_seq;\n+\n+\t/* holds the per-test token for the unit-test transport to consume */\n+\tlws_token_map_t\t\t\tuttt[4];\n+\n+\tlws_abs_t\t\t\t*instance;\n+\n+\tint\t\t\t\tstate;\n+};\n+\n+/* sequencer messages specific to this sequencer */\n+\n+enum {\n+\tSEQ_MSG_PASS \u003d LWSSEQ_USER_BASE,\n+\tSEQ_MSG_FAIL,\n+\tSEQ_MSG_FAIL_TIMEOUT,\n+};\n+\n+/*\n+ * We get called back when the unit test transport has decided if the test\n+ * passed or failed. We get the priv, and report to the sequencer message queue\n+ * what the result was.\n+ */\n+\n+static int\n+unit_test_result_cb(const void *cb_user, int disposition)\n+{\n+\tconst struct lws_seq_test_sequencer *s \u003d\n+\t\t\t(const struct lws_seq_test_sequencer *)cb_user;\n+\tint r;\n+\n+\tlwsl_debug(\u0022%s: disp %d\u005cn\u0022, __func__, disposition);\n+\n+\tswitch (disposition) {\n+\tcase LPE_FAILED_UNEXPECTED_PASS:\n+\tcase LPE_FAILED_UNEXPECTED_CLOSE:\n+\tcase LPE_FAILED:\n+\t\tr \u003d SEQ_MSG_FAIL;\n+\t\tbreak;\n+\n+\tcase LPE_FAILED_UNEXPECTED_TIMEOUT:\n+\t\tr \u003d SEQ_MSG_FAIL_TIMEOUT;\n+\t\tbreak;\n+\n+\tcase LPE_SUCCEEDED:\n+\t\tr \u003d SEQ_MSG_PASS;\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tassert(0);\n+\t\treturn -1;\n+\t}\n+\n+\tlws_sequencer_event(s-\u003eunit_test_seq, r, NULL);\n+\n+\t((struct lws_seq_test_sequencer *)s)-\u003einstance \u003d NULL;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * We receive the unit test result callback's messages via the message queue.\n+ *\n+ * We log the results and always move on to the next test until there are no\n+ * more tests.\n+ */\n+\n+static lws_seq_cb_return_t\n+test_sequencer_cb(struct lws_sequencer *seq, void *user, int event, void *data)\n+{\n+\tstruct lws_seq_test_sequencer *s \u003d\n+\t\t\t\t(struct lws_seq_test_sequencer *)user;\n+\tlws_unit_test_packet_t *exp \u003d (lws_unit_test_packet_t *)\n+\t\t\t\t\ts-\u003eargs.tests[s-\u003estate].expect_array;\n+\tlws_abs_t test_abs;\n+\n+\tswitch ((int)event) {\n+\tcase LWSSEQ_CREATED: /* our sequencer just got started */\n+\t\tlwsl_notice(\u0022%s: %s: created\u005cn\u0022, __func__,\n+\t\t\t lws_sequencer_name(seq));\n+\t\ts-\u003estate \u003d 0; /* first thing we'll do is the first url */\n+\t\tgoto step;\n+\n+\tcase LWSSEQ_DESTROYED:\n+\t\t/*\n+\t\t * We are going down... if we have a child unit test sequencer\n+\t\t * still around inform and destroy it\n+\t\t */\n+\t\tif (s-\u003einstance) {\n+\t\t\ts-\u003einstance-\u003eat-\u003eclose(s-\u003einstance);\n+\t\t\ts-\u003einstance \u003d NULL;\n+\t\t}\n+\t\tbreak;\n+\n+\tcase SEQ_MSG_FAIL_TIMEOUT: /* current step timed out */\n+\t\tif (exp-\u003eflags \u0026 LWS_AUT_EXPECT_SHOULD_TIMEOUT) {\n+\t\t\tlwsl_user(\u0022%s: test %d got expected timeout\u005cn\u0022,\n+\t\t\t\t __func__, s-\u003estate);\n+\n+\t\t\tgoto pass;\n+\t\t}\n+\t\tlwsl_user(\u0022%s: seq timed out at step %d\u005cn\u0022, __func__, s-\u003estate);\n+\n+\t\ts-\u003eargs.results[s-\u003estate] \u003d LPE_FAILED_UNEXPECTED_TIMEOUT;\n+\t\tgoto done; /* always move on to the next test */\n+\n+\tcase SEQ_MSG_FAIL:\n+\t\tif (exp-\u003eflags \u0026 LWS_AUT_EXPECT_SHOULD_FAIL) {\n+\t\t\t/*\n+\t\t\t * in this case, we expected to fail like this, it's OK\n+\t\t\t */\n+\t\t\tlwsl_user(\u0022%s: test %d failed as expected\u005cn\u0022,\n+\t\t\t\t __func__, s-\u003estate);\n+\n+\t\t\tgoto pass; /* always move on to the next test */\n+\t\t}\n+\n+\t\tlwsl_user(\u0022%s: seq failed at step %d\u005cn\u0022, __func__, s-\u003estate);\n+\n+\t\ts-\u003eargs.results[s-\u003estate] \u003d LPE_FAILED;\n+\t\tgoto done; /* always move on to the next test */\n+\n+\tcase SEQ_MSG_PASS:\n+\t\tif (exp-\u003eflags \u0026 (LWS_AUT_EXPECT_SHOULD_FAIL |\n+\t\t\t\t LWS_AUT_EXPECT_SHOULD_TIMEOUT)) {\n+\t\t\t/*\n+\t\t\t * In these specific cases, done would be a failure,\n+\t\t\t * we expected to timeout or fail\n+\t\t\t */\n+\t\t\tlwsl_user(\u0022%s: seq failed at step %d\u005cn\u0022, __func__,\n+\t\t\t\t s-\u003estate);\n+\n+\t\t\ts-\u003eargs.results[s-\u003estate] \u003d LPE_FAILED_UNEXPECTED_PASS;\n+\n+\t\t\tgoto done; /* always move on to the next test */\n+\t\t}\n+\t\tlwsl_info(\u0022%s: seq done test %d\u005cn\u0022, __func__, s-\u003estate);\n+pass:\n+\t\t(*s-\u003eargs.count_passes)++;\n+\t\ts-\u003eargs.results[s-\u003estate] \u003d LPE_SUCCEEDED;\n+\n+done:\n+\t\tlws_sequencer_timeout(lws_sequencer_from_user(s), 0);\n+\t\ts-\u003estate++;\n+step:\n+\t\tif (!s-\u003eargs.tests[s-\u003estate].name) {\n+\t\t\t/* the sequence has completed */\n+\t\t\tlwsl_user(\u0022%s: sequence completed OK\u005cn\u0022, __func__);\n+\n+\t\t\tif (s-\u003eargs.cb)\n+\t\t\t\ts-\u003eargs.cb(s-\u003eargs.cb_user);\n+\n+\t\t\treturn LWSSEQ_RET_DESTROY;\n+\t\t}\n+\t\tlwsl_info(\u0022%s: starting test %d\u005cn\u0022, __func__, s-\u003estate);\n+\n+\t\tif (s-\u003estate \u003e\u003d s-\u003eargs.results_max) {\n+\t\t\tlwsl_err(\u0022%s: results array is too small\u005cn\u0022, __func__);\n+\n+\t\t\treturn LWSSEQ_RET_DESTROY;\n+\t\t}\n+\t\ttest_abs \u003d s-\u003eoriginal_abs;\n+\t\ts-\u003euttt[0].name_index \u003d LTMI_PEER_V_EXPECT_TEST;\n+\t\ts-\u003euttt[0].u.value \u003d (void *)\u0026s-\u003eargs.tests[s-\u003estate];\n+\t\ts-\u003euttt[1].name_index \u003d LTMI_PEER_V_EXPECT_RESULT_CB;\n+\t\ts-\u003euttt[1].u.value \u003d (void *)unit_test_result_cb;\n+\t\ts-\u003euttt[2].name_index \u003d LTMI_PEER_V_EXPECT_RESULT_CB_ARG;\n+\t\ts-\u003euttt[2].u.value \u003d (void *)s;\n+\t\t/* give the unit test transport the test tokens */\n+\t\ttest_abs.at_tokens \u003d s-\u003euttt;\n+\n+\t\ts-\u003einstance \u003d lws_abs_bind_and_create_instance(\u0026test_abs);\n+\t\tif (!s-\u003einstance) {\n+\t\t\tlwsl_notice(\u0022%s: failed to create step %d unit test\u005cn\u0022,\n+\t\t\t\t __func__, s-\u003estate);\n+\n+\t\t\treturn LWSSEQ_RET_DESTROY;\n+\t\t}\n+\t\t(*s-\u003eargs.count_tests)++;\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn LWSSEQ_RET_CONTINUE;\n+}\n+\n+\n+/*\n+ * Creates an lws_sequencer to manage the test sequence\n+ */\n+\n+int\n+lws_abs_unit_test_sequencer(const lws_test_sequencer_args_t *args)\n+{\n+\tstruct lws_seq_test_sequencer *s;\n+\tlws_sequencer_t *seq;\n+\n+\t/*\n+\t * Create a sequencer in the event loop to manage the tests\n+\t */\n+\n+\tseq \u003d lws_sequencer_create(args-\u003eabs-\u003evh-\u003econtext, 0,\n+\t\t\t\t sizeof(struct lws_seq_test_sequencer),\n+\t\t\t\t (void **)\u0026s, test_sequencer_cb, \u0022test-seq\u0022);\n+\tif (!seq) {\n+\t\tlwsl_err(\u0022%s: unable to create sequencer\u005cn\u0022, __func__);\n+\t\treturn 1;\n+\t}\n+\n+\t/*\n+\t * Take a copy of the original lws_abs_t we were passed so we can use\n+\t * it as the basis of the lws_abs_t we create the individual tests with\n+\t */\n+\ts-\u003eoriginal_abs \u003d *args-\u003eabs;\n+\n+\ts-\u003eargs \u003d *args;\n+\n+\ts-\u003econtext \u003d args-\u003eabs-\u003evh-\u003econtext;\n+\ts-\u003evhost \u003d args-\u003eabs-\u003evh;\n+\ts-\u003eunit_test_seq \u003d seq;\n+\n+\t*s-\u003eargs.count_tests \u003d 0;\n+\t*s-\u003eargs.count_passes \u003d 0;\n+\n+\treturn 0;\n+}\ndiff --git a/lib/abstract/transports/unit-test.c b/lib/abstract/transports/unit-test.c\nindex 7cd85cf..ccee684 100644\n--- a/lib/abstract/transports/unit-test.c\n+++ b/lib/abstract/transports/unit-test.c\n@@ -18,57 +18,67 @@\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n * MA 02110-1301 USA\n *\n+ *\n * An abstract transport that is useful for unit testing an abstract protocol.\n * It doesn't actually connect to anything, but checks the protocol's response\n- * to various canned packets.\n- *\n- * Although it doesn't use any socket itself, it still needs to respect the\n- * event loop so it can reflect the associated behaviours correctly. So it\n- * creates a wsi for these purposes, which is a RAW FILE open on /dev/null.\n+ * to provided canned packets from an array of test vectors.\n */\n \n #include \u0022core/private.h\u0022\n #include \u0022abstract/private.h\u0022\n \n+/* this is the transport priv instantiated at abs-\u003eati */\n+\n typedef struct lws_abstxp_unit_test_priv {\n-\tchar note[128];\n-\tstruct lws_abs *abs;\n+\tchar\t\t\t\t\tnote[128];\n+\tstruct lws_abs\t\t\t\t*abs;\n \n-\tstruct lws *wsi;\n-\tlws_expect_test_t *current_test;\n-\tlws_expect_t *expect;\n-\tlws_expect_disposition disposition;\n-\tint filefd;\n+\tlws_sequencer_t\t\t\t\t*seq;\n+\tlws_unit_test_t\t\t\t\t*current_test;\n+\tlws_unit_test_packet_t\t\t\t*expect;\n+\tlws_unit_test_packet_test_cb\t\tresult_cb;\n+\tconst void\t\t\t\t*result_cb_arg;\n \n-\tlws_dll2_t same_abs_transport_list;\n+\tlws_unit_test_packet_disposition\tdisposition;\n+\t/* synthesized protocol timeout */\n+\ttime_t\t\t\t\t\ttimeout;\n \n-\tuint8_t established:1;\n-\tuint8_t connecting:1;\n+\tuint8_t\t\t\t\t\testablished:1;\n+\tuint8_t\t\t\t\t\tconnecting:1;\n } abs_unit_test_priv_t;\n \n-struct vhd {\n-\tlws_dll2_owner_t owner;\n+typedef struct seq_priv {\n+\tlws_abs_t *ai;\n+} seq_priv_t;\n+\n+enum {\n+\tUTSEQ_MSG_WRITEABLE \u003d LWSSEQ_USER_BASE,\n+\tUTSEQ_MSG_CLOSING,\n+\tUTSEQ_MSG_TIMEOUT,\n+\tUTSEQ_MSG_CONNECTING,\n+\tUTSEQ_MSG_POST_TX_KICK,\n+\tUTSEQ_MSG_DISPOSITION_KNOWN\n };\n \n /*\n * A definitive result has appeared for the current test\n */\n \n-static lws_expect_disposition\n-lws_expect_dispose(abs_unit_test_priv_t *priv, lws_expect_disposition disp,\n-\t\t const char *note)\n+static lws_unit_test_packet_disposition\n+lws_unit_test_packet_dispose(abs_unit_test_priv_t *priv,\n+\t\t\t lws_unit_test_packet_disposition disp,\n+\t\t\t const char *note)\n {\n \tassert(priv-\u003edisposition \u003d\u003d LPE_CONTINUE);\n \n+\tlwsl_info(\u0022%s: %d\u005cn\u0022, __func__, disp);\n+\n \tif (note)\n \t\tlws_strncpy(priv-\u003enote, note, sizeof(priv-\u003enote));\n \n \tpriv-\u003edisposition \u003d disp;\n \n-\tlwsl_user(\u0022%s: %s: test %d: %s\u005cn\u0022, priv-\u003eabs-\u003eap-\u003ename,\n-\t\t priv-\u003ecurrent_test-\u003ename,\n-\t\t (int)(priv-\u003eexpect - priv-\u003ecurrent_test-\u003eexpect),\n-\t\t disp \u003d\u003d LPE_SUCCEEDED ? \u0022OK\u0022 : \u0022FAIL\u0022);\n+\tlws_sequencer_event(priv-\u003eseq, UTSEQ_MSG_DISPOSITION_KNOWN, NULL);\n \n \treturn disp;\n }\n@@ -77,25 +87,35 @@ lws_expect_dispose(abs_unit_test_priv_t *priv, lws_expect_disposition disp,\n * start on the next step of the test\n */\n \n-lws_expect_disposition\n+lws_unit_test_packet_disposition\n process_expect(abs_unit_test_priv_t *priv)\n {\n \tassert(priv-\u003edisposition \u003d\u003d LPE_CONTINUE);\n \n-\twhile (priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_RX) {\n-\t\tint f \u003d priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_LOCAL_CLOSE,\n-\t\t s \u003d priv-\u003eabs-\u003eap-\u003erx(priv-\u003eabs-\u003eapi, priv-\u003eexpect-\u003ebuffer,\n-\t\t\t\t\tpriv-\u003eexpect-\u003elen);\n+\twhile (priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_RX \u0026\u0026\n+\t priv-\u003edisposition \u003d\u003d LPE_CONTINUE) {\n+\t\tint f \u003d priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_LOCAL_CLOSE, s;\n+\n+\t\tif (priv-\u003eexpect-\u003epre)\n+\t\t\tpriv-\u003eexpect-\u003epre(priv-\u003eabs);\n+\n+\t\tlwsl_info(\u0022%s: rx()\u005cn\u0022, __func__);\n+\t\tlwsl_hexdump_debug(priv-\u003eexpect-\u003ebuffer, priv-\u003eexpect-\u003elen);\n+\t\ts \u003d priv-\u003eabs-\u003eap-\u003erx(priv-\u003eabs-\u003eapi, priv-\u003eexpect-\u003ebuffer,\n+\t\t\t\t\t\t\tpriv-\u003eexpect-\u003elen);\n+\n \t\tif (!!f !\u003d !!s) {\n \t\t\tlwsl_notice(\u0022%s: expected rx return %d, got %d\u005cn\u0022,\n \t\t\t\t\t__func__, !!f, s);\n \n-\t\t\treturn lws_expect_dispose(priv, LPE_FAILED,\n+\t\t\treturn lws_unit_test_packet_dispose(priv, LPE_FAILED,\n \t\t\t\t\t\t \u0022rx unexpected return\u0022);\n \t\t}\n \n-\t\tif (priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_TEST_END)\n-\t\t\treturn lws_expect_dispose(priv, LPE_SUCCEEDED, NULL);\n+\t\tif (priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_TEST_END) {\n+\t\t\tlws_unit_test_packet_dispose(priv, LPE_SUCCEEDED, NULL);\n+\t\t\tbreak;\n+\t\t}\n \n \t\tpriv-\u003eexpect++;\n \t}\n@@ -103,112 +123,164 @@ process_expect(abs_unit_test_priv_t *priv)\n \treturn LPE_CONTINUE;\n }\n \n-static int\n-heartbeat_cb(struct lws_dll2 *d, void *user)\n+static lws_seq_cb_return_t\n+unit_test_sequencer_cb(struct lws_sequencer *seq, void *user, int event,\n+\t\t void *data)\n {\n-\tabs_unit_test_priv_t *priv \u003d lws_container_of(d, abs_unit_test_priv_t,\n-\t\t\t\t\t\t same_abs_transport_list);\n+\tseq_priv_t *s \u003d (seq_priv_t *)user;\n+\tabs_unit_test_priv_t *priv \u003d (abs_unit_test_priv_t *)s-\u003eai-\u003eati;\n+\ttime_t now;\n+\n+\tswitch ((int)event) {\n+\tcase LWSSEQ_CREATED: /* our sequencer just got started */\n+\t\tlwsl_notice(\u0022%s: %s: created\u005cn\u0022, __func__,\n+\t\t\t lws_sequencer_name(seq));\n+\t\tif (s-\u003eai-\u003eat-\u003eclient_conn(s-\u003eai)) {\n+\t\t\tlwsl_notice(\u0022%s: %s: abstract client conn failed\u005cn\u0022,\n+\t\t\t\t\t__func__, lws_sequencer_name(seq));\n+\n+\t\t\treturn LWSSEQ_RET_DESTROY;\n+\t\t}\n+\t\tbreak;\n \n-\tif (priv-\u003eabs-\u003eap-\u003eheartbeat)\n-\t\tpriv-\u003eabs-\u003eap-\u003eheartbeat(priv-\u003eabs-\u003eapi);\n+\tcase LWSSEQ_DESTROYED:\n+\t\t/*\n+\t\t * This sequencer is about to be destroyed. If we have any\n+\t\t * other assets in play, detach them from us.\n+\t\t */\n \n-\treturn 0;\n-}\n+\t\tif (priv-\u003eabs)\n+\t\t\tlws_abs_destroy_instance(\u0026priv-\u003eabs);\n \n-static int\n-callback_abs_client_unit_test(struct lws *wsi, enum lws_callback_reasons reason,\n-\t\t\t void *user, void *in, size_t len)\n-{\n-\tabs_unit_test_priv_t *priv \u003d (abs_unit_test_priv_t *)user;\n-\tstruct vhd *vhd \u003d (struct vhd *)\n-\t\tlws_protocol_vh_priv_get(lws_get_vhost(wsi),\n-\t\t\t\t\t lws_get_protocol(wsi));\n-\n-\tswitch (reason) {\n-\tcase LWS_CALLBACK_PROTOCOL_INIT:\n-\t\tvhd \u003d lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),\n-\t\t\t\tlws_get_protocol(wsi), sizeof(struct vhd));\n-\t\tif (!vhd)\n-\t\t\treturn 1;\n-\n-\t\tlws_timed_callback_vh_protocol(lws_get_vhost(wsi),\n-\t\t\t\t\t lws_get_protocol(wsi),\n-\t\t\t\t\t LWS_CALLBACK_USER, 1);\n \t\tbreak;\n \n-\tcase LWS_CALLBACK_USER:\n+\tcase LWSSEQ_HEARTBEAT:\n+\n+\t\t/* synthesize a wsi-style timeout */\n+\n+\t\tif (!priv-\u003etimeout)\n+\t\t\tgoto ph;\n+\n+\t\ttime(\u0026now);\n+\n+\t\tif (now \u003c\u003d priv-\u003etimeout)\n+\t\t\tgoto ph;\n+\n+\t\tif (priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_SHOULD_TIMEOUT) {\n+\t\t\tlwsl_user(\u0022%s: test got expected timeout\u005cn\u0022,\n+\t\t\t\t __func__);\n+\t\t\tlws_unit_test_packet_dispose(priv,\n+\t\t\t\t\tLPE_FAILED_UNEXPECTED_TIMEOUT, NULL);\n+\n+\t\t\treturn LWSSEQ_RET_DESTROY;\n+\t\t}\n+\t\tlwsl_user(\u0022%s: seq timed out\u005cn\u0022, __func__);\n+\n+ph:\n+\t\tif (priv-\u003eabs-\u003eap-\u003eheartbeat)\n+\t\t\tpriv-\u003eabs-\u003eap-\u003eheartbeat(priv-\u003eabs-\u003eapi);\n+\t\tbreak;\n+\n+\tcase UTSEQ_MSG_DISPOSITION_KNOWN:\n+\n+\t\tlwsl_info(\u0022%s: %s: DISPOSITION_KNOWN %s: %s\u005cn\u0022, __func__,\n+\t\t\t priv-\u003eabs-\u003eap-\u003ename,\n+\t\t\t priv-\u003ecurrent_test-\u003ename,\n+\t\t\t priv-\u003edisposition \u003d\u003d LPE_SUCCEEDED ? \u0022OK\u0022 : \u0022FAIL\u0022);\n+\n \t\t/*\n-\t\t * This comes at 1Hz without a wsi context, so there is no\n-\t\t * valid priv. We need to track the live abstract objects that\n-\t\t * are using our abstract protocol, and pass the heartbeat\n-\t\t * through to the ones that care.\n+\t\t * if the test has a callback, call it back to let it\n+\t\t * know the result\n \t\t */\n-\t\tif (!vhd)\n-\t\t\tbreak;\n+\t\tif (priv-\u003eresult_cb)\n+\t\t\tpriv-\u003eresult_cb(priv-\u003eresult_cb_arg, priv-\u003edisposition);\n \n-\t\tlws_dll2_foreach_safe(\u0026vhd-\u003eowner, NULL, heartbeat_cb);\n+\t\treturn LWSSEQ_RET_DESTROY;\n \n-\t\tlws_timed_callback_vh_protocol(lws_get_vhost(wsi),\n-\t\t\t\t\t lws_get_protocol(wsi),\n-\t\t\t\t\t LWS_CALLBACK_USER, 1);\n-\t\tbreak;\n+ case UTSEQ_MSG_CONNECTING:\n+\t\tlwsl_debug(\u0022UTSEQ_MSG_CONNECTING\u005cn\u0022);\n \n- case LWS_CALLBACK_RAW_ADOPT_FILE:\n-\t\tlwsl_debug(\u0022LWS_CALLBACK_RAW_ADOPT_FILE\u005cn\u0022);\n-\t\tpriv-\u003econnecting \u003d 0;\n-\t\tpriv-\u003eestablished \u003d 1;\n \t\tif (priv-\u003eabs-\u003eap-\u003eaccept)\n \t\t\tpriv-\u003eabs-\u003eap-\u003eaccept(priv-\u003eabs-\u003eapi);\n- break;\n \n-\tcase LWS_CALLBACK_RAW_CLOSE_FILE:\n-\t\tif (!user)\n-\t\t\tbreak;\n-\t\tlwsl_debug(\u0022LWS_CALLBACK_RAW_CLOSE_FILE\u005cn\u0022);\n-\t\tpriv-\u003eestablished \u003d 0;\n-\t\tpriv-\u003econnecting \u003d 0;\n-\t\tif (priv-\u003eabs \u0026\u0026 priv-\u003eabs-\u003eap-\u003eclosed)\n-\t\t\tpriv-\u003eabs-\u003eap-\u003eclosed(priv-\u003eabs-\u003eapi);\n-\t\tif (priv-\u003efilefd !\u003d -1)\n-\t\t\tclose(priv-\u003efilefd);\n-\t\tpriv-\u003efilefd \u003d -1;\n-\t\tlws_set_wsi_user(wsi, NULL);\n-\t\tbreak;\n+\t\tpriv-\u003eestablished \u003d 1;\n+\n+\t\t/* fallthru */\n \n-\tcase LWS_CALLBACK_RAW_WRITEABLE_FILE:\n-\t\tlwsl_debug(\u0022LWS_CALLBACK_RAW_WRITEABLE_FILE\u005cn\u0022);\n-\t\tpriv-\u003eabs-\u003eap-\u003ewriteable(priv-\u003eabs-\u003eapi,\n-\t\t\t\tlws_get_peer_write_allowance(priv-\u003ewsi));\n+ case UTSEQ_MSG_POST_TX_KICK:\n+ \tif (priv-\u003edisposition)\n+ \t\tbreak;\n+\n+\t\tif (process_expect(priv) !\u003d LPE_CONTINUE) {\n+\t\t\tlwsl_notice(\u0022%s: UTSEQ_MSG_POST_TX_KICK failed\u005cn\u0022,\n+\t\t\t\t __func__);\n+\t\t\treturn LWSSEQ_RET_DESTROY;\n+\t\t}\n \t\tbreak;\n \n-\tcase LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL:\n-\t\tlws_dll2_add_tail(\u0026priv-\u003esame_abs_transport_list, \u0026vhd-\u003eowner);\n+\tcase UTSEQ_MSG_WRITEABLE:\n+\t\t/*\n+\t\t * inform the protocol our transport is writeable now\n+\t\t */\n+\t\tpriv-\u003eabs-\u003eap-\u003ewriteable(priv-\u003eabs-\u003eapi, 1024);\n \t\tbreak;\n \n-\tcase LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL:\n-\t\tlws_dll2_remove(\u0026priv-\u003esame_abs_transport_list);\n+\tcase UTSEQ_MSG_CLOSING:\n+\n+\t\tif (!(priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_LOCAL_CLOSE)) {\n+\t\t\tlwsl_user(\u0022%s: got unexpected close\u005cn\u0022, __func__);\n+\n+\t\t\tlws_unit_test_packet_dispose(priv,\n+\t\t\t\t\tLPE_FAILED_UNEXPECTED_CLOSE, NULL);\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\t/* tell the abstract protocol we are closing on them */\n+\n+\t\tif (priv-\u003eabs \u0026\u0026 priv-\u003eabs-\u003eap-\u003eclosed)\n+\t\t\tpriv-\u003eabs-\u003eap-\u003eclosed(priv-\u003eabs-\u003eapi);\n+\n+\t\tgoto done;\n+\n+\tcase UTSEQ_MSG_TIMEOUT: /* current step timed out */\n+\n+\t\ts-\u003eai-\u003eat-\u003eclose(s-\u003eai-\u003eati);\n+\n+\t\tif (!(priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_SHOULD_TIMEOUT)) {\n+\t\t\tlwsl_user(\u0022%s: got unexpected timeout\u005cn\u0022, __func__);\n+\n+\t\t\tlws_unit_test_packet_dispose(priv,\n+\t\t\t\t\tLPE_FAILED_UNEXPECTED_TIMEOUT, NULL);\n+\t\t\treturn LWSSEQ_RET_DESTROY;\n+\t\t}\n+\t\tgoto done;\n+\n+done:\n+\t\tlws_sequencer_timeout(lws_sequencer_from_user(s), 0);\n+\t\tpriv-\u003eexpect++;\n+\t\tif (!priv-\u003eexpect-\u003ebuffer) {\n+\t\t\t/* the sequence has completed */\n+\t\t\tlwsl_user(\u0022%s: sequence completed OK\u005cn\u0022, __func__);\n+\n+\t\t\treturn LWSSEQ_RET_DESTROY;\n+\t\t}\n \t\tbreak;\n \n \tdefault:\n \t\tbreak;\n \t}\n \n-\treturn 0;\n+\treturn LWSSEQ_RET_CONTINUE;\n }\n \n-const struct lws_protocols protocol_abs_client_unit_test \u003d {\n-\t\u0022lws-abs-cli-unit-test\u0022, callback_abs_client_unit_test,\n-\t0, 1024, 1024, NULL, 0\n-};\n-\n static int\n lws_atcut_close(lws_abs_transport_inst_t *ati)\n {\n \tabs_unit_test_priv_t *priv \u003d (abs_unit_test_priv_t *)ati;\n \n-\tlws_set_timeout(priv-\u003ewsi, 1, LWS_TO_KILL_SYNC);\n+\tlwsl_notice(\u0022%s\u005cn\u0022, __func__);\n \n-\t/* priv is destroyed in the CLOSE callback */\n+\tlws_sequencer_event(priv-\u003eseq, UTSEQ_MSG_CLOSING, NULL);\n \n \treturn 0;\n }\n@@ -220,10 +292,15 @@ lws_atcut_tx(lws_abs_transport_inst_t *ati, uint8_t *buf, size_t len)\n \n \tassert(priv-\u003edisposition \u003d\u003d LPE_CONTINUE);\n \n+\tlwsl_info(\u0022%s: received tx\u005cn\u0022, __func__);\n+\n+\tif (priv-\u003eexpect-\u003epre)\n+\t\tpriv-\u003eexpect-\u003epre(priv-\u003eabs);\n+\n \tif (!(priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_TX)) {\n \t\tlwsl_notice(\u0022%s: unexpected tx\u005cn\u0022, __func__);\n \t\tlwsl_hexdump_notice(buf, len);\n-\t\tlws_expect_dispose(priv, LPE_FAILED, \u0022unexpected tx\u0022);\n+\t\tlws_unit_test_packet_dispose(priv, LPE_FAILED, \u0022unexpected tx\u0022);\n \n \t\treturn 1;\n \t}\n@@ -231,28 +308,32 @@ lws_atcut_tx(lws_abs_transport_inst_t *ati, uint8_t *buf, size_t len)\n \tif (len !\u003d priv-\u003eexpect-\u003elen) {\n \t\tlwsl_notice(\u0022%s: unexpected tx len %zu, expected %zu\u005cn\u0022,\n \t\t\t\t__func__, len, priv-\u003eexpect-\u003elen);\n-\t\tlws_expect_dispose(priv, LPE_FAILED, \u0022tx len mismatch\u0022);\n+\t\tlws_unit_test_packet_dispose(priv, LPE_FAILED,\n+\t\t\t\t\t \u0022tx len mismatch\u0022);\n \n \t\treturn 1;\n \t}\n \n \tif (memcmp(buf, priv-\u003eexpect-\u003ebuffer, len)) {\n \t\tlwsl_notice(\u0022%s: tx mismatch (exp / actual)\u005cn\u0022, __func__);\n-\t\tlwsl_hexdump_notice(priv-\u003eexpect-\u003ebuffer, len);\n-\t\tlwsl_hexdump_notice(buf, len);\n-\t\tlws_expect_dispose(priv, LPE_FAILED, \u0022tx data mismatch\u0022);\n+\t\tlwsl_hexdump_debug(priv-\u003eexpect-\u003ebuffer, len);\n+\t\tlwsl_hexdump_debug(buf, len);\n+\t\tlws_unit_test_packet_dispose(priv, LPE_FAILED,\n+\t\t\t\t\t \u0022tx data mismatch\u0022);\n \n \t\treturn 1;\n \t}\n \n \tif (priv-\u003eexpect-\u003eflags \u0026 LWS_AUT_EXPECT_TEST_END) {\n-\t\tlws_expect_dispose(priv, LPE_SUCCEEDED, NULL);\n+\t\tlws_unit_test_packet_dispose(priv, LPE_SUCCEEDED, NULL);\n \n \t\treturn 1;\n \t}\n \n \tpriv-\u003eexpect++;\n \n+\tlws_sequencer_event(priv-\u003eseq, UTSEQ_MSG_POST_TX_KICK, NULL);\n+\n \treturn 0;\n }\n \n@@ -262,32 +343,13 @@ lws_atcut_client_conn(const lws_abs_t *abs)\n {\n \tabs_unit_test_priv_t *priv \u003d (abs_unit_test_priv_t *)abs-\u003eati;\n \tconst lws_token_map_t *tm;\n-\tlws_sock_file_fd_type u;\n-\n-\t/*\n-\t * we do this fresh for each test\n-\t */\n-\n-\tif (priv-\u003econnecting || priv-\u003eestablished)\n-\t\treturn 0;\n-\n-\tpriv-\u003efilefd \u003d lws_open(\u0022/dev/null\u0022, O_RDWR);\n-\tif (priv-\u003efilefd \u003d\u003d -1) {\n-\t\tlwsl_err(\u0022%s: Unable to open /dev/null\u005cn\u0022, __func__);\n \n+\tif (priv-\u003eestablished) {\n+\t\tlwsl_err(\u0022%s: already established\u005cn\u0022, __func__);\n \t\treturn 1;\n \t}\n-\tu.filefd \u003d (lws_filefd_type)(long long)priv-\u003efilefd;\n-\tif (!lws_adopt_descriptor_vhost(priv-\u003eabs-\u003evh, LWS_ADOPT_RAW_FILE_DESC,\n-\t\t\t\t\tu, \u0022unit-test\u0022, NULL)) {\n-\t\tlwsl_err(\u0022Failed to adopt file descriptor\u005cn\u0022);\n-\t\tclose(priv-\u003efilefd);\n-\t\tpriv-\u003efilefd \u003d -1;\n \n-\t\treturn 1;\n-\t}\n-\n-\t/* set up the test start pieces */\n+\t/* set up the test start pieces... the array of test expects... */\n \n \ttm \u003d lws_abs_get_token(abs-\u003eat_tokens, LTMI_PEER_V_EXPECT_TEST);\n \tif (!tm) {\n@@ -296,17 +358,31 @@ lws_atcut_client_conn(const lws_abs_t *abs)\n \n \t\treturn 1;\n \t}\n-\tpriv-\u003ecurrent_test \u003d (lws_expect_test_t *)tm-\u003eu.value;\n-\tpriv-\u003eexpect \u003d priv-\u003ecurrent_test-\u003eexpect;\n+\tpriv-\u003ecurrent_test \u003d (lws_unit_test_t *)tm-\u003eu.value;\n+\n+\t/* ... and the callback to deliver the result to */\n+\ttm \u003d lws_abs_get_token(abs-\u003eat_tokens, LTMI_PEER_V_EXPECT_RESULT_CB);\n+\tif (tm)\n+\t\tpriv-\u003eresult_cb \u003d (lws_unit_test_packet_test_cb)tm-\u003eu.value;\n+\telse\n+\t\tpriv-\u003eresult_cb \u003d NULL;\n+\n+\t/* ... and the arg to deliver it with */\n+\ttm \u003d lws_abs_get_token(abs-\u003eat_tokens,\n+\t\t\t LTMI_PEER_V_EXPECT_RESULT_CB_ARG);\n+\tif (tm)\n+\t\tpriv-\u003eresult_cb_arg \u003d tm-\u003eu.value;\n+\n+\tpriv-\u003eexpect \u003d priv-\u003ecurrent_test-\u003eexpect_array;\n \tpriv-\u003edisposition \u003d LPE_CONTINUE;\n \tpriv-\u003enote[0] \u003d '\u005c0';\n \n-\tlwsl_notice(\u0022%s: %s: %s: start\u005cn\u0022, __func__, abs-\u003eap-\u003ename,\n-\t\t priv-\u003ecurrent_test-\u003ename);\n+\tlws_sequencer_timeout(priv-\u003eseq, priv-\u003ecurrent_test-\u003emax_secs);\n \n-\tprocess_expect(priv);\n+\tlwsl_notice(\u0022%s: %s: test '%s': start\u005cn\u0022, __func__, abs-\u003eap-\u003ename,\n+\t\t priv-\u003ecurrent_test-\u003ename);\n \n-\tpriv-\u003econnecting \u003d 1;\n+\tlws_sequencer_event(priv-\u003eseq, UTSEQ_MSG_CONNECTING, NULL);\n \n \treturn 0;\n }\n@@ -320,18 +396,48 @@ lws_atcut_ask_for_writeable(lws_abs_transport_inst_t *ati)\n \tif (!priv-\u003eestablished)\n \t\treturn 1;\n \n-\tlws_callback_on_writable(priv-\u003ewsi);\n+\t/*\n+\t * Queue a writeable event... this won't be handled by teh sequencer\n+\t * until we have returned to the event loop, just like a real\n+\t * callback_on_writable()\n+\t */\n+\tlws_sequencer_event(priv-\u003eseq, UTSEQ_MSG_WRITEABLE, NULL);\n \n \treturn 0;\n }\n \n+/*\n+ * An abstract protocol + transport has been instantiated\n+ */\n+\n static int\n-lws_atcut_create(struct lws_abs *ai)\n+lws_atcut_create(lws_abs_t *ai)\n {\n-\tabs_unit_test_priv_t *at \u003d (abs_unit_test_priv_t *)ai-\u003eati;\n+\tabs_unit_test_priv_t *priv;\n+\tlws_sequencer_t *seq;\n+\tseq_priv_t *s;\n+\n+\t/*\n+\t * Create the sequencer for the steps in a single unit test\n+\t */\n+\n+\tseq \u003d lws_sequencer_create(ai-\u003evh-\u003econtext, 0, sizeof(*s),\n+\t\t\t\t (void **)\u0026s, unit_test_sequencer_cb,\n+\t\t\t\t \u0022unit-test-seq\u0022);\n+\tif (!seq) {\n+\t\tlwsl_err(\u0022%s: unable to create sequencer\u005cn\u0022, __func__);\n \n-\tmemset(at, 0, sizeof(*at));\n-\tat-\u003eabs \u003d ai;\n+\t\treturn 1;\n+\t}\n+\n+\tpriv \u003d ai-\u003eati;\n+\tmemset(s, 0, sizeof(*s));\n+\tmemset(priv, 0, sizeof(*priv));\n+\n+\t/* the sequencer priv just points to the lws_abs_t */\n+\ts-\u003eai \u003d ai;\n+\tpriv-\u003eabs \u003d ai;\n+\tpriv-\u003eseq \u003d seq;\n \n \treturn 0;\n }\n@@ -351,8 +457,14 @@ static int\n lws_atcut_set_timeout(lws_abs_transport_inst_t *ati, int reason, int secs)\n {\n \tabs_unit_test_priv_t *priv \u003d (abs_unit_test_priv_t *)ati;\n+\ttime_t now;\n \n-\tlws_set_timeout(priv-\u003ewsi, reason, secs);\n+\ttime(\u0026now);\n+\n+\tif (secs)\n+\t\tpriv-\u003etimeout \u003d now + secs;\n+\telse\n+\t\tpriv-\u003etimeout \u003d 0;\n \n \treturn 0;\n }\n@@ -368,6 +480,27 @@ lws_atcut_state(lws_abs_transport_inst_t *ati)\n \treturn 1;\n }\n \n+static const char *dnames[] \u003d {\n+\t\u0022INCOMPLETE\u0022,\n+\t\u0022PASS\u0022,\n+\t\u0022FAIL\u0022,\n+\t\u0022FAIL(TIMEOUT)\u0022,\n+\t\u0022FAIL(UNEXPECTED PASS)\u0022,\n+\t\u0022FAIL(UNEXPECTED CLOSE)\u0022,\n+\t\u0022SKIPPED\u0022\n+\t\u0022?\u0022,\n+\t\u0022?\u0022\n+};\n+\n+\n+const char *\n+lws_unit_test_result_name(int in)\n+{\n+\tif (in \u003c 0 || in \u003e (int)LWS_ARRAY_SIZE(dnames))\n+\t\treturn \u0022unknown\u0022;\n+\n+\treturn dnames[in];\n+}\n \n const lws_abs_transport_t lws_abs_transport_cli_unit_test \u003d {\n \t.name\t\t\t\u003d \u0022unit_test\u0022,\n@@ -387,33 +520,3 @@ const lws_abs_transport_t lws_abs_transport_cli_unit_test \u003d {\n \t.set_timeout\t\t\u003d lws_atcut_set_timeout,\n \t.state\t\t\t\u003d lws_atcut_state,\n };\n-\n-/*\n- * This goes through the test array instantiating a new protocol + transport\n- * for each test and keeping track of the results\n- */\n-\n-int\n-lws_abs_transport_unit_test_helper(lws_abs_t *abs)\n-{\n-\tlws_abs_t *instance;\n-\tconst lws_token_map_t *tm;\n-\n-\ttm \u003d lws_abs_get_token(abs-\u003eat_tokens, LTMI_PEER_V_EXPECT_TEST_ARRAY);\n-\tif (!tm) {\n-\t\tlwsl_err(\u0022%s: LTMI_PEER_V_EXPECT_TEST_ARRAY is required\u005cn\u0022,\n-\t\t\t __func__);\n-\n-\t\treturn 1;\n-\t}\n-\n-\t//wh\n-\n-\tinstance \u003d lws_abs_bind_and_create_instance(abs);\n-\tif (!instance) {\n-\t\tlwsl_err(\u0022%s: failed to create SMTP client\u005cn\u0022, __func__);\n-\t\treturn 1;\n-\t}\n-\n-\treturn 0;\n-}\ndiff --git a/lib/core-net/sequencer.c b/lib/core-net/sequencer.c\nindex 405747d..557df85 100644\n--- a/lib/core-net/sequencer.c\n+++ b/lib/core-net/sequencer.c\n@@ -42,6 +42,7 @@ typedef struct lws_sequencer {\n \tstruct lws_dll2_owner\t\tseq_event_owner;\n \tstruct lws_context_per_thread\t*pt;\n \tlws_seq_event_cb\t\tcb;\n+\tconst char\t\t\t*name;\n \n \ttime_t\t\t\t\ttime_created;\n \ttime_t\t\t\t\ttimeout; /* 0 or time we timeout */\n@@ -53,7 +54,7 @@ typedef struct lws_sequencer {\n \n lws_sequencer_t *\n lws_sequencer_create(struct lws_context *context, int tsi, size_t user_size,\n-\t\t void **puser, lws_seq_event_cb cb)\n+\t\t void **puser, lws_seq_event_cb cb, const char *name)\n {\n \tstruct lws_context_per_thread *pt \u003d \u0026context-\u003ept[tsi];\n \tlws_sequencer_t *seq \u003d lws_zalloc(sizeof(*seq) + user_size, __func__);\n@@ -63,6 +64,7 @@ lws_sequencer_create(struct lws_context *context, int tsi, size_t user_size,\n \n \tseq-\u003ecb \u003d cb;\n \tseq-\u003ept \u003d pt;\n+\tseq-\u003ename \u003d name;\n \n \t*puser \u003d (void *)\u0026seq[1];\n \n@@ -108,8 +110,11 @@ lws_sequencer_destroy(lws_sequencer_t **pseq)\n \t/* defeat another thread racing to add events while we are destroying */\n \tseq-\u003egoing_down \u003d 1;\n \n+\tseq-\u003ecb(seq, (void *)\u0026seq[1], LWSSEQ_DESTROYED, NULL);\n+\n \tlws_pt_lock(seq-\u003ept, __func__); /* -------------------------- pt { */\n \n+\tlws_dll2_remove(\u0026seq-\u003eseq_list);\n \tlws_dll2_remove(\u0026seq-\u003eseq_to_list);\n \tlws_dll2_remove(\u0026seq-\u003eseq_pend_list);\n \t/* remove and destroy any pending events */\n@@ -117,7 +122,6 @@ lws_sequencer_destroy(lws_sequencer_t **pseq)\n \n \tlws_pt_unlock(seq-\u003ept); /* } pt ---------------------------------- */\n \n-\tseq-\u003ecb(seq, (void *)\u0026seq[1], LWSSEQ_DESTROYED, NULL);\n \n \tlws_free_set_NULL(seq);\n }\n@@ -125,14 +129,20 @@ lws_sequencer_destroy(lws_sequencer_t **pseq)\n int\n lws_sequencer_event(lws_sequencer_t *seq, lws_seq_events_t e, void *data)\n {\n-\tlws_seq_event_t *seqe \u003d lws_zalloc(sizeof(*seqe), __func__);\n+\tlws_seq_event_t *seqe;\n \n-\tif (!seqe || seq-\u003egoing_down)\n+\tif (!seq || seq-\u003egoing_down)\n+\t\treturn 1;\n+\n+\tseqe \u003d lws_zalloc(sizeof(*seqe), __func__);\n+\tif (!seqe)\n \t\treturn 1;\n \n \tseqe-\u003ee \u003d e;\n \tseqe-\u003edata \u003d data;\n \n+\t// lwsl_notice(\u0022%s: seq %s: event %d\u005cn\u0022, __func__, seq-\u003ename, e);\n+\n \tlws_pt_lock(seq-\u003ept, __func__); /* ----------------------------- pt { */\n \n \tif (seq-\u003eseq_event_owner.count \u003e QUEUE_SANITY_LIMIT) {\n@@ -193,7 +203,8 @@ lws_sequencer_next_event(struct lws_dll2 *d, void *user)\n \tlws_pt_unlock(seq-\u003ept); /* } pt ------------------------------------- */\n \n \tif (n) {\n-\t\tlwsl_info(\u0022%s: destroying seq by request\u005cn\u0022, __func__);\n+\t\tlwsl_info(\u0022%s: destroying seq '%s' by request\u005cn\u0022, __func__,\n+\t\t\t\tseq-\u003ename);\n \t\tlws_sequencer_destroy(\u0026seq);\n \n \t\treturn LWSSEQ_RET_DESTROY;\n@@ -291,6 +302,18 @@ lws_sequencer_timeout_check(struct lws_context_per_thread *pt, time_t now)\n \n \t} lws_end_foreach_dll_safe(p, tp);\n \n+\t/* send every sequencer a heartbeat message... it can ignore it */\n+\n+\tlws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,\n+\t\t\t\t pt-\u003eseq_owner.head) {\n+\t\tlws_sequencer_t *s \u003d lws_container_of(p, lws_sequencer_t,\n+\t\t\t\t\t\t seq_list);\n+\n+\t\t/* queue the message to inform the sequencer */\n+\t\tlws_sequencer_event(s, LWSSEQ_HEARTBEAT, NULL);\n+\n+\t} lws_end_foreach_dll_safe(p, tp);\n+\n \treturn 0;\n }\n \n@@ -300,6 +323,12 @@ lws_sequencer_from_user(void *u)\n \treturn \u0026((lws_sequencer_t *)u)[-1];\n }\n \n+const char *\n+lws_sequencer_name(lws_sequencer_t *seq)\n+{\n+\treturn seq-\u003ename;\n+}\n+\n int\n lws_sequencer_secs_since_creation(lws_sequencer_t *seq)\n {\ndiff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c\nindex 5863368..f788f12 100644\n--- a/lib/core-net/vhost.c\n+++ b/lib/core-net/vhost.c\n@@ -61,9 +61,6 @@ const struct lws_protocols *available_abstract_protocols[] \u003d {\n #if defined(LWS_ROLE_RAW)\n \t\u0026protocol_abs_client_raw_skt,\n #endif\n-#if defined(LWS_WITH_ABSTRACT)\n-\t\u0026protocol_abs_client_unit_test,\n-#endif\n \tNULL\n };\n #endif\ndiff --git a/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt b/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt\nindex 4c8e671..43f4246 100644\n--- a/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt\n+++ b/minimal-examples/abstract/protocols/smtp-client/CMakeLists.txt\n@@ -1,7 +1,7 @@\n cmake_minimum_required(VERSION 2.8)\n include(CheckCSourceCompiles)\n \n-set(SAMP lws-unit-tests-smtp-client)\n+set(SAMP lws-api-test-smtp_client)\n set(SRCS main.c)\n \n # If we are being built as part of lws, confirm current build config supports\ndiff --git a/minimal-examples/abstract/protocols/smtp-client/main.c b/minimal-examples/abstract/protocols/smtp-client/main.c\nindex 7ed859a..47d4897 100644\n--- a/minimal-examples/abstract/protocols/smtp-client/main.c\n+++ b/minimal-examples/abstract/protocols/smtp-client/main.c\n@@ -1,5 +1,5 @@\n /*\n- * lws-unit-tests-smtp-client\n+ * lws-api-test-smtp_client\n *\n * Written in 2010-2019 by Andy Green \u003candy@warmcat.com\u003e\n *\n@@ -14,66 +14,6 @@\n static int interrupted, result \u003d 1;\n static const char *recip;\n \n-/*\n- * from https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol\n- */\n-\n-static lws_expect_t test_send1[] \u003d {\n-\t{\n-\t\t\u0022220 smtp.example.com ESMTP Postfix\u0022,\n-\t\t34, LWS_AUT_EXPECT_RX\n-\t}, {\n-\t\t\u0022HELO lws-test-client\u0022,\n-\t\t20, LWS_AUT_EXPECT_TX\n-\t}, {\n-\t\t\u0022250 smtp.example.com, I am glad to meet you\u0022,\n-\t\t43, LWS_AUT_EXPECT_RX\n-\t}, {\n-\t\t\u0022MAIL FROM:\u003cnoreply@warmcat.com\u003e\u0022,\n-\t\t31, LWS_AUT_EXPECT_TX\n-\t}, {\n-\t\t\u0022250 Ok\u0022,\n-\t\t6, LWS_AUT_EXPECT_RX\n-\t}, {\n-\t\t\u0022RCPT TO:andy@warmcat.com\u0022,\n-\t\t24, LWS_AUT_EXPECT_TX\n-\t}, {\n-\t\t\u0022250 Ok\u0022,\n-\t\t6, LWS_AUT_EXPECT_RX\n-\t}, {\n-\t\t\u0022DATA\u0022,\n-\t\t4, LWS_AUT_EXPECT_TX\n-\t}, {\n-\t\t\u0022354 End data with \u003cCR\u003e\u003cLF\u003e.\u003cCR\u003e\u003cLF\u003e\u0022,\n-\t\t35, LWS_AUT_EXPECT_RX\n-\t}, {\n-\t\t\u0022From: noreply@example.com\u005cn\u0022\n-\t\t\u0022To: andy@warmcat.com\u005cn\u0022\n-\t\t\u0022Subject: Test email for lws smtp-client\u005cn\u0022\n-\t\t\u0022\u005cn\u0022\n-\t\t\u0022Hello this was an api test for lws smtp-client\u005cn\u0022\n-\t\t\u0022\u005cr\u005cn.\u005cr\u005cn\u0022,\n-\t\t27 + 21 + 39 + 1 + 46 + 5, LWS_AUT_EXPECT_TX\n-\t}, {\n-\t\t\u0022250 Ok: queued as 12345\u0022,\n-\t\t23, LWS_AUT_EXPECT_RX\n-\t}, {\n-\t\t\u0022QUIT\u0022,\n-\t\t4, LWS_AUT_EXPECT_TX\n-\t}, {\n-\t\t\u0022221 Bye\u0022,\n-\t\t7, LWS_AUT_EXPECT_RX |\n-\t\t LWS_AUT_EXPECT_LOCAL_CLOSE |\n-\t\t LWS_AUT_EXPECT_DO_REMOTE_CLOSE |\n-\t\t LWS_AUT_EXPECT_TEST_END\n-\t}\n-};\n-\n-static lws_expect_test_t tests[] \u003d {\n-\t{ \u0022sending\u0022, test_send1 },\n-\t{ }\n-};\n-\n static void\n sigint_handler(int sig)\n {\n@@ -100,51 +40,17 @@ email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len)\n }\n \n /*\n- * The test helper calls this on the instance it created to prepare it for\n- * the test.\n- */\n-\n-static int\n-smtp_test_instance_init(lws_abs_t *instance)\n-{\n-\tlws_smtp_email_t email;\n-\n-\t/* attach an email to it */\n-\n-\tmemset(\u0026email, 0, sizeof(email));\n-\temail.data \u003d NULL /* email specific user data */;\n-\temail.email_from \u003d \u0022noreply@warmcat.com\u0022;\n-\temail.email_to \u003d \u0022andy@warmcat.com\u0022;\n-\temail.payload \u003d malloc(2048);\n-\tif (!email.payload)\n-\t\treturn 1;\n-\n-\tlws_snprintf((char *)email.payload, 2048,\n-\t\t\t\u0022From: noreply@example.com\u005cn\u0022\n-\t\t\t\u0022To: %s\u005cn\u0022\n-\t\t\t\u0022Subject: Test email for lws smtp-client\u005cn\u0022\n-\t\t\t\u0022\u005cn\u0022\n-\t\t\t\u0022Hello this was an api test for lws smtp-client\u005cn\u0022\n-\t\t\t\u0022\u005cr\u005cn.\u005cr\u005cn\u0022, recip);\n-\temail.done \u003d email_sent_or_failed;\n-\n-\tif (lws_smtp_client_add_email(instance, \u0026email)) {\n-\t\tlwsl_err(\u0022%s: failed to add email\u005cn\u0022, __func__);\n-\t\treturn 1;\n-\t}\n-\n-\treturn 0;\n-}\n-\n-/*\n * We're going to bind to the raw-skt transport, so tell that what we want it\n * to connect to\n */\n \n static const lws_token_map_t smtp_raw_skt_transport_tokens[] \u003d {\n {\n-\t.u \u003d { .value \u003d (const char *)tests },\n-\t.name_index \u003d LTMI_PEER_V_EXPECT_TEST_ARRAY,\n+\t.u \u003d { .value \u003d \u0022127.0.0.1\u0022 },\n+\t.name_index \u003d LTMI_PEER_V_DNS_ADDRESS,\n+ }, {\n+\t.u \u003d { .lvalue \u003d 25 },\n+\t.name_index \u003d LTMI_PEER_LV_PORT,\n }, {\n }\n };\n@@ -153,7 +59,6 @@ static const lws_token_map_t smtp_protocol_tokens[] \u003d {\n {\n \t.u \u003d { .value \u003d \u0022lws-test-client\u0022 },\n \t.name_index \u003d LTMI_PSMTP_V_HELO,\n-\t.init \u003d smtp_test_instance_init,\n }, {\n }\n };\n@@ -164,6 +69,8 @@ int main(int argc, const char **argv)\n \tint n \u003d 1, logs \u003d LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;\n \tstruct lws_context_creation_info info;\n \tstruct lws_context *context;\n+\tlws_abs_t abs, *instance;\n+\tlws_smtp_email_t email;\n \tstruct lws_vhost *vh;\n \tconst char *p;\n \n@@ -200,7 +107,9 @@ int main(int argc, const char **argv)\n \t\tgoto bail1;\n \t}\n \n-\t/* create the smtp client */\n+\t/*\n+\t * create an smtp client that's hooked up to real sockets\n+\t */\n \n \tmemset(\u0026abs, 0, sizeof(abs));\n \tabs.vh \u003d vh;\n@@ -214,22 +123,41 @@ int main(int argc, const char **argv)\n \n \t/* select the transport and bind its tokens */\n \n-\tabs.at \u003d lws_abs_transport_get_by_name(\u0022unit_tests\u0022);\n+\tabs.at \u003d lws_abs_transport_get_by_name(\u0022raw_skt\u0022);\n \tif (!abs.at)\n \t\tgoto bail1;\n-\n-\t/*\n-\t * The transport token we pass here to the test helper is the array\n-\t * of tests. The helper will iterate through it instantiating test\n-\t * connections with one test each.\n-\t */\n \tabs.at_tokens \u003d smtp_raw_skt_transport_tokens;\n \n-\tif (lws_abs_transport_unit_test_helper(\u0026abs)) {\n+\tinstance \u003d lws_abs_bind_and_create_instance(\u0026abs);\n+\tif (!instance) {\n \t\tlwsl_err(\u0022%s: failed to create SMTP client\u005cn\u0022, __func__);\n \t\tgoto bail1;\n \t}\n \n+\t/* attach an email to it */\n+\n+\tmemset(\u0026email, 0, sizeof(email));\n+\temail.data \u003d NULL /* email specific user data */;\n+\temail.email_from \u003d \u0022andy@warmcat.com\u0022;\n+\temail.email_to \u003d recip;\n+\temail.payload \u003d malloc(2048);\n+\tif (!email.payload) {\n+\t\tgoto bail1;\n+\t}\n+\n+\tlws_snprintf((char *)email.payload, 2048,\n+\t\t\t\u0022From: noreply@example.com\u005cn\u0022\n+\t\t\t\u0022To: %s\u005cn\u0022\n+\t\t\t\u0022Subject: Test email for lws smtp-client\u005cn\u0022\n+\t\t\t\u0022\u005cn\u0022\n+\t\t\t\u0022Hello this was an api test for lws smtp-client\u005cn\u0022\n+\t\t\t\u0022\u005cr\u005cn.\u005cr\u005cn\u0022, recip);\n+\temail.done \u003d email_sent_or_failed;\n+\n+\tif (lws_smtp_client_add_email(instance, \u0026email)) {\n+\t\tlwsl_err(\u0022%s: failed to add email\u005cn\u0022, __func__);\n+\t\tgoto bail;\n+\t}\n \n \t/* the usual lws event loop */\n \ndiff --git a/minimal-examples/api-tests/api-test-lws_sequencer/main.c b/minimal-examples/api-tests/api-test-lws_sequencer/main.c\nindex cbb3209..2ec07e8 100644\n--- a/minimal-examples/api-tests/api-test-lws_sequencer/main.c\n+++ b/minimal-examples/api-tests/api-test-lws_sequencer/main.c\n@@ -370,7 +370,7 @@ main(int argc, const char **argv)\n \t */\n \n \tseq \u003d lws_sequencer_create(context, 0, sizeof(struct myseq),\n-\t\t\t\t (void **)\u0026s, sequencer_cb);\n+\t\t\t\t (void **)\u0026s, sequencer_cb, \u0022seq\u0022);\n \tif (!seq) {\n \t\tlwsl_err(\u0022%s: unable to create sequencer\u005cn\u0022, __func__);\n \t\tgoto bail1;\ndiff --git a/minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt b/minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt\nindex 43f4246..4c8e671 100644\n--- a/minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt\n+++ b/minimal-examples/api-tests/api-test-smtp_client/CMakeLists.txt\n@@ -1,7 +1,7 @@\n cmake_minimum_required(VERSION 2.8)\n include(CheckCSourceCompiles)\n \n-set(SAMP lws-api-test-smtp_client)\n+set(SAMP lws-unit-tests-smtp-client)\n set(SRCS main.c)\n \n # If we are being built as part of lws, confirm current build config supports\ndiff --git a/minimal-examples/api-tests/api-test-smtp_client/README.md b/minimal-examples/api-tests/api-test-smtp_client/README.md\nindex a3b3d01..4c2052d 100644\n--- a/minimal-examples/api-tests/api-test-smtp_client/README.md\n+++ b/minimal-examples/api-tests/api-test-smtp_client/README.md\n@@ -1,6 +1,11 @@\n # lws api test smtp client\n \n-Demonstrates how to send email through your local MTA\n+Performs unit tests on the lws SMTP client abstract protocol\n+implementation.\n+\n+The first test \u0022sends mail to a server\u0022 (actually is prompted by\n+test vectors that look like a server) and the second test\n+confirm it can handle rejection by the \u0022server\u0022 cleanly.\n \n ## build\n \n@@ -19,11 +24,18 @@ Commandline option|Meaning\n \n \n ```\n- $ ./lws-api-test-smtp_client -r andy@warmcat.com\n-[2019/04/17 05:12:06:5293] USER: LWS API selftest: SMTP client\n-[2019/04/17 05:12:06:5635] NOTICE: LGSSMTP_IDLE: connecting to 127.0.0.1:25\n-[2019/04/17 05:12:06:6238] NOTICE: email_sent_or_failed: sent OK\n-[2019/04/17 05:12:06:6394] USER: Completed: PASS\n-\n+ $ ./lws-api-test-smtp_client\n+[2019/06/28 21:56:41:0711] USER: LWS API selftest: SMTP client unit tests\n+[2019/06/28 21:56:41:1114] NOTICE: test_sequencer_cb: test-seq: created\n+[2019/06/28 21:56:41:1259] NOTICE: unit_test_sequencer_cb: unit-test-seq: created\n+[2019/06/28 21:56:41:1272] NOTICE: lws_atcut_client_conn: smtp: test 'sending': start\n+[2019/06/28 21:56:41:1441] NOTICE: unit_test_sequencer_cb: unit-test-seq: created\n+[2019/06/28 21:56:41:1442] NOTICE: lws_atcut_client_conn: smtp: test 'rejected': start\n+[2019/06/28 21:56:41:1453] NOTICE: lws_smtp_client_abs_rx: bad response from server: 500 (state 4) 500 Service Unavailable\n+[2019/06/28 21:56:41:1467] USER: test_sequencer_cb: sequence completed OK\n+[2019/06/28 21:56:41:1474] USER: main: 2 tests 0 fail\n+[2019/06/28 21:56:41:1476] USER: test 0: PASS\n+[2019/06/28 21:56:41:1478] USER: test 1: PASS\n+[2019/06/28 21:56:41:1480] USER: Completed: PASS\n ```\n \ndiff --git a/minimal-examples/api-tests/api-test-smtp_client/main.c b/minimal-examples/api-tests/api-test-smtp_client/main.c\nindex d7a1d2d..4b64334 100644\n--- a/minimal-examples/api-tests/api-test-smtp_client/main.c\n+++ b/minimal-examples/api-tests/api-test-smtp_client/main.c\n@@ -1,77 +1,190 @@\n /*\n- * lws-api-test-smtp_client\n+ * lws-unit-tests-smtp-client\n *\n * Written in 2010-2019 by Andy Green \u003candy@warmcat.com\u003e\n *\n * This file is made available under the Creative Commons CC0 1.0\n * Universal Public Domain Dedication.\n+ *\n+ * This performs unit tests for the SMTP client abstract protocol\n */\n \n #include \u003clibwebsockets.h\u003e\n \n #include \u003csignal.h\u003e\n \n-static int interrupted, result \u003d 1;\n-static const char *recip;\n+static int interrupted, results[10], count_tests, count_passes;\n \n-static void\n-sigint_handler(int sig)\n+static int\n+email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len)\n {\n-\tinterrupted \u003d 1;\n+\tfree(email);\n+\n+\treturn 0;\n }\n \n+/*\n+ * The test helper calls this on the instance it created to prepare it for\n+ * the test. In our case, we need to queue up a test email to send on the\n+ * smtp client abstract protocol.\n+ */\n+\n static int\n-email_sent_or_failed(struct lws_smtp_email *email, void *buf, size_t len)\n+smtp_test_instance_init(lws_abs_t *instance)\n {\n-\t/* you could examine email-\u003edata here */\n-\tif (buf)\n-\t\tlwsl_notice(\u0022%s: %.*s\u005cn\u0022, __func__, (int)len, (const char *)buf);\n-\telse\n-\t\tlwsl_notice(\u0022%s:\u005cn\u0022, __func__);\n+\tlws_smtp_email_t *email \u003d (lws_smtp_email_t *)\n+\t\t\t\t\tmalloc(sizeof(*email) + 2048);\n+\n+\tif (!email)\n+\t\treturn 1;\n \n-\t/* destroy any allocations in email */\n+\t/* attach an email to it */\n \n-\tfree((char *)email-\u003epayload);\n+\tmemset(email, 0, sizeof(*email));\n+\temail-\u003edata \u003d NULL /* email specific user data */;\n+\temail-\u003eemail_from \u003d \u0022noreply@warmcat.com\u0022;\n+\temail-\u003eemail_to \u003d \u0022andy@warmcat.com\u0022;\n+\temail-\u003epayload \u003d (void *)\u0026email[1];\n \n-\tresult \u003d 0;\n-\tinterrupted \u003d 1;\n+\tlws_snprintf((char *)email-\u003epayload, 2048,\n+\t\t\t\u0022From: noreply@example.com\u005cn\u0022\n+\t\t\t\u0022To: %s\u005cn\u0022\n+\t\t\t\u0022Subject: Test email for lws smtp-client\u005cn\u0022\n+\t\t\t\u0022\u005cn\u0022\n+\t\t\t\u0022Hello this was an api test for lws smtp-client\u005cn\u0022\n+\t\t\t\u0022\u005cr\u005cn.\u005cr\u005cn\u0022, \u0022andy@warmcat.com\u0022);\n+\temail-\u003edone \u003d email_sent_or_failed;\n+\n+\tif (lws_smtp_client_add_email(instance, email)) {\n+\t\tlwsl_err(\u0022%s: failed to add email\u005cn\u0022, __func__);\n+\t\treturn 1;\n+\t}\n \n \treturn 0;\n }\n \n /*\n- * We're going to bind to the raw-skt transport, so tell that what we want it\n- * to connect to\n+ * from https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol\n+ *\n+ *\t\ttest vector sent to protocol\n+ *\t\t\t\ttest vector received from protocol\n */\n \n-static const lws_token_map_t smtp_raw_skt_transport_tokens[] \u003d {\n- {\n-\t.u \u003d { .value \u003d \u0022127.0.0.1\u0022 },\n-\t.name_index \u003d LTMI_PEER_V_DNS_ADDRESS,\n- }, {\n-\t.u \u003d { .lvalue \u003d 25 },\n-\t.name_index \u003d LTMI_PEER_LV_PORT,\n- }, {\n- }\n+static lws_unit_test_packet_t test_send1[] \u003d {\n+\t{\n+\t\t\u0022220 smtp.example.com ESMTP Postfix\u0022,\n+\t\tsmtp_test_instance_init, 34, LWS_AUT_EXPECT_RX\n+\t}, {\n+\t\t\t\t\u0022HELO lws-test-client\u005cx0a\u0022,\n+\t\tNULL, 21, LWS_AUT_EXPECT_TX\n+\t}, {\n+\t\t\u0022250 smtp.example.com, I am glad to meet you\u0022,\n+\t\tNULL, 43, LWS_AUT_EXPECT_RX\n+\t}, {\n+\t\t\t\t\u0022MAIL FROM: \u003cnoreply@warmcat.com\u003e\u005cx0a\u0022,\n+\t\tNULL, 33, LWS_AUT_EXPECT_TX\n+\t}, {\n+\t\t\u0022250 Ok\u0022,\n+\t\tNULL, 6, LWS_AUT_EXPECT_RX\n+\t}, {\n+\t\t\t\t\u0022RCPT TO: \u003candy@warmcat.com\u003e\u005cx0a\u0022,\n+\t\tNULL, 28, LWS_AUT_EXPECT_TX\n+\t}, {\n+\t\t\u0022250 Ok\u0022,\n+\t\tNULL, 6, LWS_AUT_EXPECT_RX\n+\t}, {\n+\t\t\t\t\u0022DATA\u005cx0a\u0022,\n+\t\tNULL, 5, LWS_AUT_EXPECT_TX\n+\t}, {\n+\t\t\u0022354 End data with \u003cCR\u003e\u003cLF\u003e.\u003cCR\u003e\u003cLF\u003e\u005cx0a\u0022,\n+\t\tNULL, 35, LWS_AUT_EXPECT_RX\n+\t}, {\n+\t\t\t\t\u0022From: noreply@example.com\u005cn\u0022\n+\t\t\t\t\u0022To: andy@warmcat.com\u005cn\u0022\n+\t\t\t\t\u0022Subject: Test email for lws smtp-client\u005cn\u0022\n+\t\t\t\t\u0022\u005cn\u0022\n+\t\t\t\t\u0022Hello this was an api test for lws smtp-client\u005cn\u0022\n+\t\t\t\t\u0022\u005cr\u005cn.\u005cr\u005cn\u0022,\n+\t\tNULL, 27 + 21 + 39 + 1 + 47 + 5, LWS_AUT_EXPECT_TX\n+\t}, {\n+\t\t\u0022250 Ok: queued as 12345\u005cx0a\u0022,\n+\t\tNULL, 23, LWS_AUT_EXPECT_RX\n+\t}, {\n+\t\t\t\t\u0022quit\u005cx0a\u0022,\n+\t\tNULL, 5, LWS_AUT_EXPECT_TX\n+\t}, {\n+\t\t\u0022221 Bye\u005cx0a\u0022,\n+\t\tNULL, 7, LWS_AUT_EXPECT_RX |\n+\t\t LWS_AUT_EXPECT_LOCAL_CLOSE |\n+\t\t LWS_AUT_EXPECT_DO_REMOTE_CLOSE |\n+\t\t LWS_AUT_EXPECT_TEST_END\n+\t}, { \t/* sentinel */\n+\n+\t}\n };\n \n+\n+static lws_unit_test_packet_t test_send2[] \u003d {\n+\t{\n+\t\t\u0022220 smtp.example.com ESMTP Postfix\u0022,\n+\t\tsmtp_test_instance_init, 34, LWS_AUT_EXPECT_RX\n+\t}, {\n+\t\t\t\t\u0022HELO lws-test-client\u005cx0a\u0022,\n+\t\tNULL, 21, LWS_AUT_EXPECT_TX\n+\t}, {\n+\t\t\u0022250 smtp.example.com, I am glad to meet you\u0022,\n+\t\tNULL, 43, LWS_AUT_EXPECT_RX\n+\t}, {\n+\t\t\t\t\u0022MAIL FROM: \u003cnoreply@warmcat.com\u003e\u005cx0a\u0022,\n+\t\tNULL, 33, LWS_AUT_EXPECT_TX\n+\t}, {\n+\t\t\u0022500 Service Unavailable\u0022,\n+\t\tNULL, 23, LWS_AUT_EXPECT_RX |\n+\t\t LWS_AUT_EXPECT_DO_REMOTE_CLOSE |\n+\t\t LWS_AUT_EXPECT_TEST_END\n+\t}, { \t/* sentinel */\n+\n+\t}\n+};\n+\n+static lws_unit_test_t tests[] \u003d {\n+\t{ \u0022sending\u0022, test_send1, 3 },\n+\t{ \u0022rejected\u0022, test_send2, 3 },\n+\t{ }\t/* sentinel */\n+};\n+\n+static void\n+sigint_handler(int sig)\n+{\n+\tinterrupted \u003d 1;\n+}\n+\n+/*\n+ * set the HELO our SMTP client will use\n+ */\n+\n static const lws_token_map_t smtp_protocol_tokens[] \u003d {\n {\n \t.u \u003d { .value \u003d \u0022lws-test-client\u0022 },\n \t.name_index \u003d LTMI_PSMTP_V_HELO,\n- }, {\n+ }, {\t/* sentinel */\n }\n };\n \n+void\n+tests_completion_cb(const void *cb_user)\n+{\n+\tinterrupted \u003d 1;\n+}\n \n int main(int argc, const char **argv)\n {\n \tint n \u003d 1, logs \u003d LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;\n \tstruct lws_context_creation_info info;\n+\tlws_test_sequencer_args_t args;\n \tstruct lws_context *context;\n-\tlws_abs_t abs, *instance;\n-\tlws_smtp_email_t email;\n \tstruct lws_vhost *vh;\n+\tlws_abs_t abs, *instance;\n \tconst char *p;\n \n \t/* the normal lws init */\n@@ -81,15 +194,8 @@ int main(int argc, const char **argv)\n \tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-d\u0022)))\n \t\tlogs \u003d atoi(p);\n \n-\tp \u003d lws_cmdline_option(argc, argv, \u0022-r\u0022);\n-\tif (!p) {\n-\t\tlwsl_err(\u0022-r \u003crecipient email\u003e is required\u005cn\u0022);\n-\t\treturn 1;\n-\t}\n-\trecip \u003d p;\n-\n \tlws_set_log_level(logs, NULL);\n-\tlwsl_user(\u0022LWS API selftest: SMTP client\u005cn\u0022);\n+\tlwsl_user(\u0022LWS API selftest: SMTP client unit tests\u005cn\u0022);\n \n \tmemset(\u0026info, 0, sizeof info); /* otherwise uninitialized garbage */\n \tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n@@ -107,9 +213,7 @@ int main(int argc, const char **argv)\n \t\tgoto bail1;\n \t}\n \n-\t/*\n-\t * create an smtp client that's hooked up to real sockets\n-\t */\n+\t/* create the smtp client */\n \n \tmemset(\u0026abs, 0, sizeof(abs));\n \tabs.vh \u003d vh;\n@@ -124,39 +228,28 @@ int main(int argc, const char **argv)\n \n \t/* select the transport and bind its tokens */\n \n-\tabs.at \u003d lws_abs_transport_get_by_name(\u0022raw_skt\u0022);\n+\tabs.at \u003d lws_abs_transport_get_by_name(\u0022unit_test\u0022);\n \tif (!abs.at)\n \t\tgoto bail1;\n \n-\tabs.at_tokens \u003d smtp_raw_skt_transport_tokens;\n-\n \tinstance \u003d lws_abs_bind_and_create_instance(\u0026abs);\n \tif (!instance)\n \t\tgoto bail1;\n \n-\t/* attach an email to it */\n-\n-\tmemset(\u0026email, 0, sizeof(email));\n-\temail.data \u003d NULL /* email specific user data */;\n-\temail.email_from \u003d \u0022andy@warmcat.com\u0022;\n-\temail.email_to \u003d recip;\n-\temail.payload \u003d malloc(2048);\n-\tif (!email.payload) {\n-\t\tgoto bail1;\n-\t}\n+\t/* configure the test sequencer */\n \n-\tlws_snprintf((char *)email.payload, 2048,\n-\t\t\t\u0022From: noreply@example.com\u005cn\u0022\n-\t\t\t\u0022To: %s\u005cn\u0022\n-\t\t\t\u0022Subject: Test email for lws smtp-client\u005cn\u0022\n-\t\t\t\u0022\u005cn\u0022\n-\t\t\t\u0022Hello this was an api test for lws smtp-client\u005cn\u0022\n-\t\t\t\u0022\u005cr\u005cn.\u005cr\u005cn\u0022, recip);\n-\temail.done \u003d email_sent_or_failed;\n+\targs.abs \u003d \u0026abs;\n+\targs.tests \u003d tests;\n+\targs.results \u003d results;\n+\targs.results_max \u003d LWS_ARRAY_SIZE(results);\n+\targs.count_tests \u003d \u0026count_tests;\n+\targs.count_passes \u003d \u0026count_passes;\n+\targs.cb \u003d tests_completion_cb;\n+\targs.cb_user \u003d NULL;\n \n-\tif (lws_smtp_client_add_email(instance, \u0026email)) {\n-\t\tlwsl_err(\u0022%s: failed to add email\u005cn\u0022, __func__);\n-\t\tgoto bail;\n+\tif (lws_abs_unit_test_sequencer(\u0026args)) {\n+\t\tlwsl_err(\u0022%s: failed to create test sequencer\u005cn\u0022, __func__);\n+\t\tgoto bail1;\n \t}\n \n \t/* the usual lws event loop */\n@@ -164,12 +257,19 @@ int main(int argc, const char **argv)\n \twhile (n \u003e\u003d 0 \u0026\u0026 !interrupted)\n \t\tn \u003d lws_service(context, 1000);\n \n-bail:\n+\t/* describe the overall test results */\n+\n+\tlwsl_user(\u0022%s: %d tests %d fail\u005cn\u0022, __func__, count_tests,\n+\t\t\tcount_tests - count_passes);\n+\tfor (n \u003d 0; n \u003c count_tests; n++)\n+\t\tlwsl_user(\u0022 test %d: %s\u005cn\u0022, n,\n+\t\t\t lws_unit_test_result_name(results[n]));\n \n bail1:\n-\tlwsl_user(\u0022Completed: %s\u005cn\u0022, result ? \u0022FAIL\u0022 : \u0022PASS\u0022);\n+\tlwsl_user(\u0022Completed: %s\u005cn\u0022,\n+\t\t !count_tests || count_passes !\u003d count_tests ? \u0022FAIL\u0022 : \u0022PASS\u0022);\n \n \tlws_context_destroy(context);\n \n-\treturn result;\n+\treturn !count_tests || count_passes !\u003d count_tests;\n }\n","s":{"c":1745477804,"u": 12966}}
],"g": 16289,"chitpc": 0,"ehitpc": 0,"indexed":0
,
"ab": 0, "si": 0, "db":0, "di":0, "sat":0, "lfc": "0000"}