static void
test_case(const char * tapname, const char * request, unsigned count)
{
    SXE  * client;
    tap_ev event;

    tap_test_case_name(tapname);

    SXEA1((client = test_new_tcp(NULL, "0.0.0.0", 0, client_connect, client_read, client_close)) != NULL,
            "Failed to allocate client SXE");
    SXEA1(sxe_connect(client, "127.0.0.1", SXE_LOCAL_PORT(listener)) == SXE_RETURN_OK, "Failed to connect to HTTPD");

#ifdef _WIN32
    usleep(10000);    /* Copied from test-errors.c and which is a TODO item */
#endif

    is_eq(test_tap_ev_queue_identifier_wait(q_client, TEST_WAIT, &event), "client_connect", "Got a client connected event");
    is(tap_ev_arg(event, "this"), client, "It's the client");
    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &event), "h_connect", "Got a server connected event");

    test_sxe_send(client, request, strlen(request), client_sent, "client_sent", q_client, TEST_WAIT, &event);

    if (count == 2) {
        is_eq(test_tap_ev_queue_identifier_wait(q_httpd , TEST_WAIT, &event), "h_header", "HTTPD: header event (a)"         );
        is_strncmp(tap_ev_arg(event, "key"  ), "Good"   , SXE_LITERAL_LENGTH( "Good"   ), "HTTPD: header was 'Good'"        );
        is_strncmp(tap_ev_arg(event, "value"), "Header" , SXE_LITERAL_LENGTH( "Header" ), "HTTPD: header value was 'Header'");

        is_eq(test_tap_ev_queue_identifier_wait(q_httpd , TEST_WAIT, &event), "h_header", "HTTPD: header event (b)"         );
        is_strncmp(tap_ev_arg(event, "key"  ), "Another", SXE_LITERAL_LENGTH( "Another"), "HTTPD: header was 'Another'"     );
        is_strncmp(tap_ev_arg(event, "value"), "Foo"    , SXE_LITERAL_LENGTH( "Foo"    ), "HTTPD: header value was 'Foo'"   );
    }

    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &event), "h_respond", "HTTPD: respond event");

    sxe_close(client);
    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &event), "h_close", "HTTPD: close event");
}
int
main(void)
{
    TEST_SHA1 sha1_expected;
    TEST_SHA1 sha1_got;
    char      hex_buffer[sizeof(SHA1_HEX)];
    unsigned  val = 0;

    plan_tests(15);
    is(sxe_hex_to_unsigned("0",     2), 0,                                  "'0':2    -> 0");
    is(sxe_hex_to_unsigned("face",  4), 0xface,                             "'face':4 -> 0xface");
    is(sxe_hex_to_unsigned("B00B",  2), 0xb0,                               "'B00B':2 -> 0xb0");
    is(sxe_hex_to_unsigned("XXXX",  4), SXE_UNSIGNED_MAXIMUM,               "'XXXX':4 -> 0x%x (SXE_UNSIGNED_MAXIMUM)",
       sxe_hex_to_unsigned("XXXX",  4));

    is(sxe_valid_hex_to_unsigned("0a", 2, &val), SXE_RETURN_OK,             "0a is valid hex");
    is(val, 10,                                                             "0a hex is 10 decimal");
    is(sxe_valid_hex_to_unsigned("F45C6AC6", 8, &val), SXE_RETURN_OK,       "F45C6AC6 is valid hex");
    is(val, 4099697350,                                                     "F45C6AC6 hex is 4099697350 decimal");
    is(sxe_valid_hex_to_unsigned("ZZ", 2, &val), SXE_RETURN_ERROR_INTERNAL, "ZZ is not valid hex");

    ok(sxe_hex_to_bytes((unsigned char *)&sha1_got, "goofy goober", 12) != SXE_RETURN_OK, "Conversion from hex 'goofy goober' to bytes failed");
    is(sxe_hex_to_bytes((unsigned char *)&sha1_got, SHA1_HEX,       40),   SXE_RETURN_OK, "Conversion from hex '%s' to bytes succeeded", SHA1_HEX);

    memcpy(&sha1_expected, sxe_sha1_expected_bytes, sizeof(sha1_expected));

    if (memcmp(&sha1_got, &sha1_expected, sizeof(TEST_SHA1)) == 0) {
        pass(                                                         "bytes are as expected");
    }
    else {
        SXEL1("Expected:");
        SXED1(&sha1_expected, sizeof(sha1_expected));
        SXEL1("Got:");
        SXED1(&sha1_got,      sizeof(sha1_got));
        fail(                                                         "bytes are not as expected");
    }

    tap_test_case_name("sxe_hex_from_bytes");
    hex_buffer[sizeof(hex_buffer) - 1] = 0xBE;
    is(sxe_hex_from_bytes(hex_buffer, sxe_sha1_expected_bytes, sizeof(sxe_sha1_expected_bytes)), hex_buffer, "Returns hex buffer");
    is_strncmp(hex_buffer, SHA1_HEX, SXE_LITERAL_LENGTH(SHA1_HEX),    "SHA1 converted to hex as expected");
    is((unsigned char)hex_buffer[sizeof(hex_buffer) - 1], 0xBE,       "Guard byte is intact");
    return exit_status();
}
Exemple #3
0
int main(void)
{
    int i, j, k = 0, len, set, hop;
    setvbuf(stdout, NULL, _IOLBF, 0);
    plan_tests(1 + nffstests + nshltests + 5 + 128);

    XMM ones = (__v2di){-1, -1};
    ok(xm_same(xm_ones, ones), "xm_ones produces FFFF...FFFF");

    for (i = 0; i < nffstests; ++i) {
        int act = findbit_1((uint8_t*)&ffstestv[i].inp, sizeof(XMM));
        ok(act == ffstestv[i].exp, "test %d: act:%d exp:%d", i, act, ffstestv[i].exp);
    }

    for (i = 0; i < nshltests; ++i) {
        char acts[99];
        XMM act = xm_shl(shltestv[0].exp, shltestv[i].nbits);
        xm_llx(act, acts);
        ok(0xFFFF == xm_same(act, shltestv[i].exp),
            "shl %d: %s", shltestv[i].nbits, acts);
    }       

    XMM bitz = { 1,8 };
    int iact = xm_ffs(bitz);
    ok(iact == 0, "xm_ffs(bitz) = %d", iact);
    iact = xm_fls(bitz);
    ok(iact == 67, "xm_fls(bitz) = %d", iact);
    
    bitz = _mm_setzero_si128();
    iact = xm_ffs(bitz);
    ok(iact == -1, "xm_ffs(zero) = %d", iact);
    iact = xm_fls(bitz);
    ok(iact == -1, "xm_fls(zero) = %d", iact);

    XMM stuff = (__v2di) { 0x07BB01426C62272EULL, 0x6295C58D62B82175ULL };
    char    str[48];
    xm_str(stuff, str);
    is_strncmp(str, "2E,27,62,6C,42,01,BB,07-75,21,B8,62,8D,C5,95,62", 48, "xm_str");

    XMM one = { 1, 0 }, hibit = xm_shl_177(one);
    for (i = 0; i < 128; ++i) {
        int pos = xm_ffs(xm_or(xm_shl(one, i), hibit));
        ok(pos == i, "xm_ffs(xm_shl(one,%d)) = %d", i, pos);
    }

#   define CMPSIZE 100000000L
    char *x = malloc(CMPSIZE), *y = malloc(CMPSIZE);
    memcpy(x, y, CMPSIZE);
    for (len = 20; len <= CMPSIZE; len *= 4) {
        for (i = 1; i < 17; i += i) {
            for (j = 1; j < 17; j += j) {
                for (set = 1; set < 2; set++) {
                    char *a, *b;
                    double t0 = tick();

                    for (hop = CMPSIZE / len, a = x, b = y; hop; --hop, a += len, b += len)
                        a[i+len-17] ^= set, k += cmpxm(a+i, b+j, len-17), a[i+len-17] ^= set;

                    double t1 = tick();

                    for (hop = CMPSIZE / len, a = x, b = y; hop; --hop, a += len, b += len) {
                        _mm_prefetch(a+i+448, _MM_HINT_NTA);
                        _mm_prefetch(b+j+448, _MM_HINT_NTA);
                        a[i+len-17] ^= set, k += memcmp(a+i, b+j, len-17), a[i+len-17] ^= set;
                    }
                    double t2 = tick();

                    printf("%9d %2d %2d %d %4.1f\n", len, i, j, set, (t2 - t1)/(t1 - t0));
               }
            }
        }
    }

    if (!k) puts("");
    return exit_status();
}
static void test_five_headers(void)
{
    SXE_HTTP_MESSAGE message;
    unsigned        line_len;

    tap_test_case_name("Five Headers - Normal Long Normal Long-With-Continuations Normal");
    max_buffer_size = 70;

    memset(message_headers, 0, sizeof(message_headers));

    snprintf(message_headers, sizeof(message_headers), "\r\n%s: %s\r\n%s: %s\r\n%s: %s\r\n%s: %s\r\n CONTINUE_LINE1\r\n\tCONTINUE_LINE2\r\n%s: %s\r\n\r\n",
                                                        HEADER(1), VALUE(1),
                                                        HEADER(2), LONG_VALUE(2),
                                                        HEADER(333), VALUE(3333),
                                                        LONG_HEADER(4), VALUE(4),
                                                        HEADER(55), VALUE(5));
    sxe_http_message_construct(&message, message_headers, max_buffer_size);
    line_len = strlen(message_headers);

    diag("Five Headers: The header is %u bytes, the max buffer size is %u bytes", line_len, max_buffer_size);

    is(sxe_http_message_parse_next_line_element(&message, SXE_HTTP_LINE_ELEMENT_TYPE_END_OF_LINE), SXE_RETURN_END_OF_FILE,
                                                "Parsed message request line");

    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_OK, "Get header 1");
    is(message.ignore_length, 0, "No ignore");
    is(message.ignore_length, 0, "Ignore length is 0");
    is_strncmp(sxe_http_message_get_header_name(&message), HEADER(1), strlen(HEADER(1)), "Name is " HEADER(1));
    is(sxe_http_message_get_header_name_length(&message), strlen(HEADER(1)), "Name length is %u",   (unsigned)strlen(HEADER(1)));
    is_strncmp(sxe_http_message_get_header_value(&message), VALUE(1), strlen(VALUE(1)), "Value is " VALUE(1));
    is(sxe_http_message_get_header_value_length(&message), strlen(VALUE(1)), "Value length is %u",  (unsigned)strlen(VALUE(1)));

    /* Don't do any consume since last sxe_http_message_parse_next_header() returns OK */

    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_WARN_WOULD_BLOCK, "Get long header");
    is(message.ignore_length, 0, "No ignore");
    /* 21 == 2 + strlen(HEADER(1)) + 2 + strlen(VALUE(1)) + 2 */
    is(sxe_http_message_consume_parsed_headers(&message),  21, "Consuming the request line and header 1");
    /* Simulate sxe_buf_consume() */
    memmove(message_headers, &message_headers[21], sizeof(message_headers) - 21);
    diag("Five Headers: Consumed 21 bytes");

    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_WARN_WOULD_BLOCK, "Get long header");
    is(sxe_http_message_consume_parsed_headers(&message),  0,                     "Buffer is full");
    /* Start ignoring */
    memmove(message_headers, &message_headers[max_buffer_size], sizeof(message_headers) - max_buffer_size);
    diag("Five Headers: Consumed %u bytes", max_buffer_size);
    message.ignore_line = 1;

    sxe_http_message_increase_buffer_length(&message, max_buffer_size);
    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_WARN_WOULD_BLOCK, "Get long header");
    is(message.ignore_line, 0, "Ignore buffer flag is clear, since found the end of long header 2");
    /* 55 = strlen(HEADER(2)) + 2 + strlen(LONG_VALUE(2)) + 2 - 70 */
    is(message.ignore_length, 55, "Ignore length is right");
    memmove(message_headers, &message_headers[55], sizeof(message_headers) - 55);
    diag("Five Headers: Consumed 55 bytes");

    sxe_http_message_increase_buffer_length(&message, max_buffer_size);
    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_OK, "Get normal header 3");
    is(message.ignore_length, 0, "No ignore");
    is_strncmp(sxe_http_message_get_header_name(&message), HEADER(333), strlen(HEADER(333)), "Name is " HEADER(333));
    is(sxe_http_message_get_header_name_length(&message), strlen(HEADER(333)), "Name length is %u", (unsigned)strlen(HEADER(333)));
    is_strncmp(sxe_http_message_get_header_value(&message), VALUE(3333), strlen(VALUE(3333)), "Value is " VALUE(3333));
    is(sxe_http_message_get_header_value_length(&message), strlen(VALUE(3333)), "Value length is %u", (unsigned)strlen(VALUE(3333)));

    /* Don't do any consume since last sxe_http_message_parse_next_header() returns OK */

    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_WARN_WOULD_BLOCK, "Get long header");
    is(message.ignore_length, 0, "No ignore");
    /* 24 == strlen(HEADER(333)) + 2 + strlen(VALUE(3333)) + 2 */
    is(sxe_http_message_consume_parsed_headers(&message),  24, "Consuming the header 3");
    /* Simulate sxe_buf_consume() */
    memmove(message_headers, &message_headers[24], sizeof(message_headers) - 24);
    diag("Five Headers: Consumed 24 bytes");

    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_WARN_WOULD_BLOCK, "Get long header");
    is(sxe_http_message_consume_parsed_headers(&message),  0,                     "Buffer is full");
    /* Start ignoring */
    memmove(message_headers, &message_headers[max_buffer_size], sizeof(message_headers) - max_buffer_size);
    diag("Five Headers: Consumed %u bytes", max_buffer_size);
    message.ignore_line = 1;

    sxe_http_message_increase_buffer_length(&message, max_buffer_size);
    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_WARN_WOULD_BLOCK, "Get long header 4");
    is(message.ignore_line, 1, "Ignore buffer flag is set, the end of long header 4 not found yet");
    /* 55 = strlen(HEADER(2)) + 2 + strlen(LONG_VALUE(2)) + 2 - 70 */
    is(message.ignore_length, max_buffer_size, "Ignore length is right");
    memmove(message_headers, &message_headers[max_buffer_size], sizeof(message_headers) - max_buffer_size);
    diag("Five Headers: Consumed %u bytes", max_buffer_size);

    /* 41 = 351(line_len) - 21 - 55 - 24 - 3 * 210 */
    sxe_http_message_increase_buffer_length(&message, 41);
    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_WARN_WOULD_BLOCK, "Get long header 4");
    is(message.ignore_line, 0, "Ignore buffer flag is set, the end of long header 4 not found yet");
    /* 19 = strlen("\r\n\tCONTINUE_LINE2\r\n") */
    is(message.ignore_length, 19, "Ignore length is right");
    memmove(message_headers, &message_headers[19], sizeof(message_headers) - 19);
    diag("Five Headers: Consumed 19 bytes, the left part of long header 4");

    /* 22 = 41 - 19 */
    sxe_http_message_increase_buffer_length(&message, 22);
    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_OK, "Get the header 5");
    is(message.ignore_line, 0, "Ignore buffer flag is clear");
    is(message.ignore_length, 0, "Ignore length is 0");
    is_strncmp(sxe_http_message_get_header_name(&message), HEADER(55), strlen(HEADER(55)), "Name is " HEADER(55));
    is(sxe_http_message_get_header_name_length(&message), strlen(HEADER(55)), "Name length is %u",    (unsigned)strlen(HEADER(55)));
    is_strncmp(sxe_http_message_get_header_value(&message), VALUE(5), strlen(VALUE(5)), "Value is "   VALUE(5));
    is(sxe_http_message_get_header_value_length(&message), strlen(VALUE(5)), "Value length is %u",    (unsigned)strlen(VALUE(5)));

    /* Don't do any consume since last sxe_http_message_parse_next_header() returns OK */

    is(sxe_http_message_parse_next_header(&message), SXE_RETURN_END_OF_FILE, "Get long header");
    /* 22 = strlen("HEADER-55: VALUE_5\r\n\r\n") */
    is(sxe_http_message_consume_parsed_headers(&message), 22, "Consumed the rest of 22 bytes which are header 5 and eof");
}
Exemple #5
0
static void
log_line(SXE_LOG_LEVEL level, char * line)
{
    char buf[256];

    if (line[strlen(line) - 1] != '\n') {
        fail("Expected 0x0a at end of line, not 0x%02x", line[strlen(line) - 1]);
        exit(1);
    }

    line[strlen(line) - 1] = '\0';

    switch (test_state) {
    case 0:
        is(level, SXE_LOG_LEVEL_TRACE, "Line was logged at TRACE level");
        is_strncmp(&line[strlen(line) - strlen(entering)], entering, strlen(entering), "Test line 0 contains '%s': '%s'", entering, line);
        break;

    case 1:
        ok(strstr(line, __FILE__) != NULL, "Test line 1 includes file name '%s': '%s'", __FILE__, line);
        break;

    case 2:
        is_strncmp(&line[strlen(line) - strlen(logging)], logging, strlen(logging), "Test line 2 ends with '%s': '%s'", logging, line);
        is_strncmp(&line[strlen(line) - strlen(logging) - strlen(idlevin)], idlevin, strlen(idlevin), "Test line 2 has id 99, level 6 and indent 2");
        break;

    case 3:
        ok(strstr(line, dumpdata) != NULL,
           "Test line 3 contains '%s'", dumpdata);
        ok(strstr(line, dumphex) != NULL,
           "Test line 3 contains hex '%s'", dumphex);
        break;

    case 4:
        is_strncmp(&line[strlen(line) - strlen(exiting)], exiting, strlen(exiting), "Test line 4 ends with '%s': '%s'", exiting, line);
        break;

    case 5:
        strcpy(buf, "} // ");
        strcat(buf, SXE_FILE);
        ok(strstr(line, buf)         != NULL,                 "Test line 5 includes end of block with file name '%s': '%s'", buf, line);
        break;

    case 6:
        is(strlen(line),                TEST_MAX_LINE,        "Very long log line truncated");
        is_eq(&line[TEST_MAX_LINE - 2], "..",                 "Truncation indicator found");
        break;

    case 7:
        is(strlen(line),                TEST_MAX_LINE,        "Very long entry line truncated");
        break;

    case 8:
        ok(strstr(line, __FILE__)    != NULL,                 "Test line 8 includes file name '%s': '%s'", __FILE__, line);
        break;

    case 9:
        is_eq(&line[strlen(line) - strlen(escaped)], escaped, "Test line 9 ends with '%.*s': '%s'", (int)strlen(escaped) - 1,
              escaped, line);
        break;

    case 10:
        is(strlen(line),                TEST_MAX_LINE,        "Line of 300 backspaces escaped and truncated");
        break;

    case 11:
        /* Doesn't crash */
        break;

    case 12:
        is(level, SXE_LOG_LEVEL_FATAL, "Assertions are logged at level FATAL");
        ok((strstr(line, "ERROR: assertion 'this != &self' failed at test/test-sxe-log.c") != NULL) ||
           (strstr(line, "ERROR: assertion 'this != &self' failed at libsxe/lib-sxe-log/test/test-sxe-log.c") != NULL) ||
           (strstr(line, "ERROR: assertion 'this != &self' failed at ../libsxe/lib-sxe-log/test/test-sxe-log.c") != NULL),
           "Assertion line includes expected stringized test: '%s'", line);
#ifdef WINDOWS_NT
        diag("info: You can expect and ignore a message saying:");
        diag("    > This application has requested the Runtime to terminate it in an unusual way.");
        diag("    > Please contact the application's support team for more information.");
#endif
        break;

    default:
        diag("Unexpected test sequence number %u.\n", test_state);
        exit(1);
    }

    test_state++;
}
Exemple #6
0
int
main(void)
{
    SXE_HTTPD                httpd;
    SXE_HTTPD_REQUEST      * request;
    tap_ev                   ev;
    SXE                    * listener;
    SXE                    * c;
    SXE                    * c2;
    char                     buffer[1024];

    tap_plan(24, TAP_FLAG_ON_FAILURE_EXIT, NULL);
    test_sxe_register_and_init(12);

    sxe_httpd_construct(&httpd, 3, 10, 512, 0);

    SXE_HTTPD_SET_HANDLER(&httpd, connect, h_connect);
    SXE_HTTPD_SET_HANDLER(&httpd, request, h_request);
    SXE_HTTPD_SET_HANDLER(&httpd, header,  h_header);
    SXE_HTTPD_SET_HANDLER(&httpd, eoh,     h_eoh);
    SXE_HTTPD_SET_HANDLER(&httpd, body,    h_body);
    SXE_HTTPD_SET_HANDLER(&httpd, respond, h_respond);
    SXE_HTTPD_SET_HANDLER(&httpd, close,   h_close);

    listener = test_httpd_listen(&httpd, "0.0.0.0", 0);

    c = test_new_tcp(NULL, "0.0.0.0", 0, client_connect, client_read, NULL);
    sxe_connect(c, "127.0.0.1", SXE_LOCAL_PORT(listener));

    is_eq(test_tap_ev_queue_identifier_wait(q_client, TEST_WAIT, &ev), "client_connect",      "1st Client connected to HTTPD");
    TEST_SXE_SEND_LITERAL(c, "POST /this/is/a/URL HTTP/1.1\r\nContent-Length: 10\r\n\r\n12345678\r\n", client_sent, q_client, TEST_WAIT, &ev);

    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &ev), "h_connect",            "HTTPD: connected");
    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &ev), "h_request",            "HTTPD: new request");

    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &ev), "h_header",             "HTTPD: header event");
    is_strncmp(tap_ev_arg(ev, "key"), "Content-Length", SXE_LITERAL_LENGTH("Content-Length"), "HTTPD: header was 'Connect'");
    is_strncmp(tap_ev_arg(ev, "value"), "10", SXE_LITERAL_LENGTH("10"),                       "HTTPD: header value was 'whatever'");

    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &ev), "h_eoh",                "HTTPD: eoh (end of headers) event");

    test_ev_queue_wait_read(q_httpd, TEST_WAIT, &ev, NULL, "h_body", buffer, 10, "HTTPD body handler");
    is_strncmp(buffer, "12345678\r\n", SXE_LITERAL_LENGTH("12345678\r\n"),                    "HTTPD: read correct body");

    /* httpd is still in "req_response" state */
    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &ev), "h_respond",            "HTTPD: respond event");

    /* Extra data from client, e.g., "content-length" is wrong and shorter than real packet */
    TEST_SXE_SEND_LITERAL(c, "AB", client_sent, q_client, TEST_WAIT, &ev);

    /* Send another valid request again, the connection is in "sink" mode now, data will get ignored */
    TEST_SXE_SEND_LITERAL(c, "POST /this/is/a/URL HTTP/1.1\r\nContent-Length: 10\r\n\r\n12345678\r\n", client_sent, q_client, TEST_WAIT, &ev);
    test_process_all_libev_events();

    /* In sink mode, no more httpd events */
    is(tap_ev_queue_length(q_httpd), 0, "No lurking httpd events");

    /* New client connection should not be affected */
    c2 = test_new_tcp(NULL, "0.0.0.0", 0, client_connect, client_read, client_close);
    sxe_connect(c2, "127.0.0.1", SXE_LOCAL_PORT(listener));

    is_eq(test_tap_ev_queue_identifier_wait(q_client, TEST_WAIT, &ev), "client_connect",      "2nd Client connected to HTTPD");
    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &ev), "h_connect",            "HTTPD: connected");

    TEST_SXE_SEND_LITERAL(c2, "GET /simple HTTP/1.1\r\n\r\n", client_sent, q_client, TEST_WAIT, &ev);

    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &ev), "h_request",            "HTTPD: new request");
    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &ev), "h_eoh",                "HTTPD: eoh (end of headers) event");
    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &ev), "h_respond",            "HTTPD: respond event");
    request = (SXE_HTTPD_REQUEST *)(long)tap_ev_arg(ev, "request");
    sxe_httpd_response_simple(request, h_sent, NULL, 200, "OK", NULL, "Connection", "close", NULL);
    is_eq(test_tap_ev_queue_identifier_wait(q_httpd, TEST_WAIT, &ev), "h_sent",               "HTTPD: finished responding");
    sxe_close(request->sxe);

#define TEST_200_CLOSE_RESPONSE "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
    test_ev_queue_wait_read(q_client, TEST_WAIT, &ev, c2, "client_read", buffer, SXE_LITERAL_LENGTH(TEST_200_CLOSE_RESPONSE), "client");
    is_strncmp(buffer, TEST_200_CLOSE_RESPONSE, SXE_LITERAL_LENGTH(TEST_200_CLOSE_RESPONSE),  "GET response is a 200 OK with close");
    is_eq(test_tap_ev_queue_identifier_wait(q_client, TEST_WAIT, &ev), "client_close",        "Got a client close event");

    return exit_status();
}