1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

unit test sequencer

This commit is contained in:
Andy Green 2019-06-26 14:32:46 +01:00
parent 604a718e92
commit a7e1bac4ac
18 changed files with 954 additions and 404 deletions

View file

@ -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)

View file

@ -534,6 +534,8 @@ struct lws;
#include <libwebsockets/abstract/abstract.h>
#include <libwebsockets/lws-test-sequencer.h>
#if defined(LWS_WITH_TLS)
#if defined(LWS_WITH_MBEDTLS)

View file

@ -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);

View file

@ -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);

View file

@ -0,0 +1,60 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
*
* 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);

View file

@ -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.

View file

@ -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 *

View file

@ -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",

View file

@ -0,0 +1,265 @@
/*
* libwebsockets lib/abstract/test-sequencer.c
*
* Copyright (C) 2019 Andy Green <andy@warmcat.com>
*
* 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 <core/private.h>
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;
}

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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
};

View file

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

View file

@ -1,5 +1,5 @@
/*
* lws-unit-tests-smtp-client
* lws-api-test-smtp_client
*
* Written in 2010-2019 by Andy Green <andy@warmcat.com>
*
@ -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:<noreply@warmcat.com>",
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 <CR><LF>.<CR><LF>",
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 */

View file

@ -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;

View file

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

View file

@ -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
```

View file

@ -1,18 +1,157 @@
/*
* lws-api-test-smtp_client
* lws-unit-tests-smtp-client
*
* Written in 2010-2019 by Andy Green <andy@warmcat.com>
*
* 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 <libwebsockets.h>
#include <signal.h>
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: <noreply@warmcat.com>\x0a",
NULL, 33, LWS_AUT_EXPECT_TX
}, {
"250 Ok",
NULL, 6, LWS_AUT_EXPECT_RX
}, {
"RCPT TO: <andy@warmcat.com>\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 <CR><LF>.<CR><LF>\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: <noreply@warmcat.com>\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 <recipient email> 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;
}