| /* ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #define APR_WANT_MEMFUNC |
| #include <apr_want.h> |
| #include "apr.h" |
| #include "apr_pools.h" |
| #include <apr_strings.h> |
| #include "apr_env.h" |
| |
| #include <stdlib.h> |
| |
| #include "serf.h" |
| |
| #include "test_serf.h" |
| |
| /*****************************************************************************/ |
| /* Server setup function(s) |
| */ |
| |
| const char * get_srcdir_file(apr_pool_t *pool, const char * file) |
| { |
| char *srcdir = ""; |
| |
| if (apr_env_get(&srcdir, "srcdir", pool) == APR_SUCCESS) { |
| return apr_pstrcat(pool, srcdir, "/", file, NULL); |
| } |
| else { |
| return file; |
| } |
| } |
| |
| /* cleanup for conn */ |
| static apr_status_t cleanup_conn(void *baton) |
| { |
| serf_connection_t *conn = baton; |
| |
| serf_connection_close(conn); |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Default implementation of a serf_connection_closed_t callback. */ |
| static void default_closed_connection(serf_connection_t *conn, |
| void *closed_baton, |
| apr_status_t why, |
| apr_pool_t *pool) |
| { |
| if (why) { |
| abort(); |
| } |
| } |
| |
| /* Default implementation of a serf_connection_setup_t callback. */ |
| static apr_status_t default_http_conn_setup(apr_socket_t *skt, |
| serf_bucket_t **input_bkt, |
| serf_bucket_t **output_bkt, |
| void *setup_baton, |
| apr_pool_t *pool) |
| { |
| test_baton_t *tb = setup_baton; |
| |
| *input_bkt = serf_bucket_socket_create(skt, tb->bkt_alloc); |
| return APR_SUCCESS; |
| } |
| |
| /* This function makes serf use SSL on the connection. */ |
| apr_status_t default_https_conn_setup(apr_socket_t *skt, |
| serf_bucket_t **input_bkt, |
| serf_bucket_t **output_bkt, |
| void *setup_baton, |
| apr_pool_t *pool) |
| { |
| test_baton_t *tb = setup_baton; |
| |
| *input_bkt = serf_bucket_socket_create(skt, tb->bkt_alloc); |
| *input_bkt = serf_bucket_ssl_decrypt_create(*input_bkt, NULL, |
| tb->bkt_alloc); |
| tb->ssl_context = serf_bucket_ssl_encrypt_context_get(*input_bkt); |
| |
| if (output_bkt) { |
| *output_bkt = serf_bucket_ssl_encrypt_create(*output_bkt, |
| tb->ssl_context, |
| tb->bkt_alloc); |
| } |
| |
| if (tb->server_cert_cb) |
| serf_ssl_server_cert_callback_set(tb->ssl_context, |
| tb->server_cert_cb, |
| tb); |
| |
| if (tb->enable_ocsp_stapling) |
| serf_ssl_check_cert_status_request(tb->ssl_context, 1); |
| |
| serf_ssl_set_hostname(tb->ssl_context, "localhost"); |
| |
| return APR_SUCCESS; |
| } |
| |
| apr_status_t use_new_connection(test_baton_t *tb, |
| apr_pool_t *pool) |
| { |
| apr_uri_t url; |
| apr_status_t status; |
| |
| if (tb->connection) |
| cleanup_conn(tb->connection); |
| tb->connection = NULL; |
| |
| status = apr_uri_parse(pool, tb->serv_url, &url); |
| if (status != APR_SUCCESS) |
| return status; |
| |
| status = serf_connection_create2(&tb->connection, tb->context, |
| url, |
| tb->conn_setup, |
| tb, |
| default_closed_connection, |
| tb, |
| pool); |
| |
| apr_pool_cleanup_register(pool, tb->connection, cleanup_conn, |
| apr_pool_cleanup_null); |
| |
| return status; |
| } |
| |
| static test_baton_t *initTestCtx(CuTest *tc, apr_pool_t *pool) |
| { |
| test_baton_t *tb; |
| tb = apr_pcalloc(pool, sizeof(*tb)); |
| tb->pool = pool; |
| tb->bkt_alloc = test__create_bucket_allocator(tc, pool); |
| tb->accepted_requests = apr_array_make(pool, 10, sizeof(int)); |
| tb->sent_requests = apr_array_make(pool, 10, sizeof(int)); |
| tb->handled_requests = apr_array_make(pool, 10, sizeof(int)); |
| return tb; |
| } |
| |
| serf_bucket_t* accept_response(serf_request_t *request, |
| serf_bucket_t *stream, |
| void *acceptor_baton, |
| apr_pool_t *pool) |
| { |
| serf_bucket_t *c; |
| serf_bucket_alloc_t *bkt_alloc; |
| handler_baton_t *ctx = acceptor_baton; |
| serf_bucket_t *response; |
| |
| /* get the per-request bucket allocator */ |
| bkt_alloc = serf_request_get_alloc(request); |
| |
| /* Create a barrier so the response doesn't eat us! */ |
| c = serf_bucket_barrier_create(stream, bkt_alloc); |
| |
| APR_ARRAY_PUSH(ctx->accepted_requests, int) = ctx->req_id; |
| |
| response = serf_bucket_response_create(c, bkt_alloc); |
| |
| if (strcasecmp(ctx->method, "HEAD") == 0) |
| serf_bucket_response_set_head(response); |
| |
| return response; |
| } |
| |
| apr_status_t setup_request(serf_request_t *request, |
| void *setup_baton, |
| serf_bucket_t **req_bkt, |
| serf_response_acceptor_t *acceptor, |
| void **acceptor_baton, |
| serf_response_handler_t *handler, |
| void **handler_baton, |
| apr_pool_t *pool) |
| { |
| handler_baton_t *ctx = setup_baton; |
| serf_bucket_t *body_bkt; |
| apr_status_t status = APR_SUCCESS; |
| |
| if (ctx->request_setup) |
| { |
| status = ctx->request_setup(request, setup_baton, req_bkt, pool); |
| } |
| else if (ctx->request) |
| { |
| /* Create a raw request bucket. */ |
| *req_bkt = serf_bucket_simple_create(ctx->request, strlen(ctx->request), |
| NULL, NULL, |
| serf_request_get_alloc(request)); |
| } |
| else |
| { |
| if (ctx->req_id >= 0) { |
| /* create a simple body text */ |
| const char *str = apr_psprintf(pool, "%d", ctx->req_id); |
| |
| body_bkt = serf_bucket_simple_create( |
| str, strlen(str), NULL, NULL, |
| serf_request_get_alloc(request)); |
| } |
| else |
| body_bkt = NULL; |
| |
| *req_bkt = |
| serf_request_bucket_request_create(request, |
| ctx->method, ctx->path, |
| body_bkt, |
| serf_request_get_alloc(request)); |
| } |
| |
| APR_ARRAY_PUSH(ctx->sent_requests, int) = ctx->req_id; |
| |
| *acceptor = ctx->acceptor; |
| *acceptor_baton = ctx; |
| *handler = ctx->handler; |
| *handler_baton = ctx; |
| |
| return status; |
| } |
| |
| apr_status_t handle_response(serf_request_t *request, |
| serf_bucket_t *response, |
| void *handler_baton, |
| apr_pool_t *pool) |
| { |
| handler_baton_t *ctx = handler_baton; |
| |
| if (! response) { |
| serf_connection_request_create(ctx->tb->connection, |
| setup_request, |
| ctx); |
| return APR_SUCCESS; |
| } |
| |
| while (1) { |
| apr_status_t status; |
| const char *data; |
| apr_size_t len; |
| |
| status = serf_bucket_read(response, 2048, &data, &len); |
| if (SERF_BUCKET_READ_ERROR(status)) |
| return status; |
| |
| if (APR_STATUS_IS_EOF(status)) { |
| APR_ARRAY_PUSH(ctx->handled_requests, int) = ctx->req_id; |
| ctx->done = TRUE; |
| return APR_EOF; |
| } |
| |
| if (APR_STATUS_IS_EAGAIN(status)) { |
| return status; |
| } |
| |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| void setup_handler(test_baton_t *tb, handler_baton_t *handler_ctx, |
| const char *method, const char *path, |
| int req_id, |
| test_request_setup_t req_setup, |
| serf_response_handler_t handler) |
| { |
| handler_ctx->method = method; |
| handler_ctx->path = path; |
| handler_ctx->done = FALSE; |
| |
| handler_ctx->acceptor = accept_response; |
| handler_ctx->acceptor_baton = NULL; |
| handler_ctx->handler = handler ? handler : handle_response; |
| handler_ctx->req_id = req_id; |
| handler_ctx->accepted_requests = tb->accepted_requests; |
| handler_ctx->sent_requests = tb->sent_requests; |
| handler_ctx->handled_requests = tb->handled_requests; |
| handler_ctx->tb = tb; |
| handler_ctx->request = NULL; |
| handler_ctx->request_setup = req_setup; |
| } |
| |
| void create_new_prio_request(test_baton_t *tb, |
| handler_baton_t *handler_ctx, |
| const char *method, const char *path, |
| int req_id) |
| { |
| setup_handler(tb, handler_ctx, method, path, req_id, NULL, NULL); |
| serf_connection_priority_request_create(tb->connection, |
| setup_request, |
| handler_ctx); |
| } |
| |
| void create_new_request(test_baton_t *tb, |
| handler_baton_t *handler_ctx, |
| const char *method, const char *path, |
| int req_id) |
| { |
| setup_handler(tb, handler_ctx, method, path, req_id, NULL, NULL); |
| serf_connection_request_create(tb->connection, |
| setup_request, |
| handler_ctx); |
| } |
| |
| void |
| create_new_request_ex(test_baton_t *tb, |
| handler_baton_t *handler_ctx, |
| const char *method, const char *path, |
| int req_id, |
| test_request_setup_t req_setup, |
| serf_response_handler_t handler) |
| { |
| setup_handler(tb, handler_ctx, method, path, req_id, req_setup, handler); |
| serf_connection_request_create(tb->connection, |
| setup_request, |
| handler_ctx); |
| } |
| |
| const char *create_large_response_message(apr_pool_t *pool) |
| { |
| const char *response = "HTTP/1.1 200 OK" CRLF |
| "Transfer-Encoding: chunked" CRLF |
| CRLF; |
| struct iovec vecs[500]; |
| const int num_vecs = 500; |
| int i, j; |
| apr_size_t len; |
| |
| vecs[0].iov_base = (char *)response; |
| vecs[0].iov_len = strlen(response); |
| |
| for (i = 1; i < num_vecs; i++) |
| { |
| int chunk_len = 10 * i * 3; |
| char *chunk; |
| char *buf; |
| |
| /* end with empty chunk */ |
| if (i == num_vecs - 1) |
| chunk_len = 0; |
| |
| buf = apr_pcalloc(pool, chunk_len + 1); |
| for (j = 0; j < chunk_len; j += 10) |
| memcpy(buf + j, "0123456789", 10); |
| |
| chunk = apr_pstrcat(pool, |
| apr_psprintf(pool, "%x", chunk_len), |
| CRLF, buf, CRLF, NULL); |
| vecs[i].iov_base = chunk; |
| vecs[i].iov_len = strlen(chunk); |
| } |
| |
| return apr_pstrcatv(pool, vecs, num_vecs, &len); |
| } |
| |
| const char *create_large_request_message_body(apr_pool_t *pool) |
| { |
| struct iovec vecs[500]; |
| const int num_vecs = 500; |
| int i, j; |
| apr_size_t len; |
| |
| for (i = 0; i < num_vecs; i++) |
| { |
| int chunk_len = 10 * (i + 1) * 3; |
| char *chunk; |
| char *buf; |
| |
| /* end with empty chunk */ |
| if (i == num_vecs - 1) |
| chunk_len = 0; |
| |
| buf = apr_pcalloc(pool, chunk_len + 1); |
| for (j = 0; j < chunk_len; j += 10) |
| memcpy(buf + j, "0123456789", 10); |
| |
| chunk = apr_pstrcat(pool, |
| apr_psprintf(pool, "%x", chunk_len), |
| CRLF, buf, CRLF, NULL); |
| vecs[i].iov_base = chunk; |
| vecs[i].iov_len = strlen(chunk); |
| } |
| |
| return apr_pstrcatv(pool, vecs, num_vecs, &len); |
| |
| } |
| |
| /* Dummy authn callback, shouldn't be called! */ |
| apr_status_t dummy_authn_callback(char **username, |
| char **password, |
| serf_request_t *request, void *baton, |
| int code, const char *authn_type, |
| const char *realm, |
| apr_pool_t *pool) |
| { |
| handler_baton_t *handler_ctx = baton; |
| test_baton_t *tb = handler_ctx->tb; |
| |
| test__log(TEST_VERBOSE, __FILE__, "dummy_authn_callback\n"); |
| |
| tb->result_flags |= TEST_RESULT_AUTHNCB_CALLED; |
| |
| return REPORT_TEST_SUITE_ERROR(); |
| } |
| |
| /*****************************************************************************/ |
| /* Test utility functions, to be used with the MockHTTPinC framework */ |
| /*****************************************************************************/ |
| |
| apr_status_t |
| setup_test_client_context(test_baton_t *tb, |
| serf_connection_setup_t conn_setup, |
| apr_pool_t *pool) |
| { |
| apr_status_t status; |
| |
| if (!tb->context) |
| tb->context = serf_context_create(pool); |
| |
| tb->conn_setup = conn_setup ? conn_setup : |
| default_http_conn_setup; |
| status = use_new_connection(tb, pool); |
| |
| return status; |
| } |
| |
| apr_status_t |
| setup_test_client_https_context(test_baton_t *tb, |
| serf_connection_setup_t conn_setup, |
| serf_ssl_need_server_cert_t server_cert_cb, |
| apr_pool_t *pool) |
| { |
| apr_status_t status; |
| |
| status = setup_test_client_context(tb, |
| conn_setup ? conn_setup: |
| default_https_conn_setup, |
| pool); |
| tb->server_cert_cb = server_cert_cb; |
| |
| return status; |
| } |
| |
| apr_status_t |
| setup_test_client_context_with_proxy(test_baton_t *tb, |
| serf_connection_setup_t conn_setup, |
| apr_pool_t *pool) |
| { |
| apr_status_t status; |
| |
| tb->context = serf_context_create(pool); |
| tb->conn_setup = conn_setup ? conn_setup : |
| default_http_conn_setup; |
| |
| /* Configure serf to use the proxy server */ |
| serf_config_proxy(tb->context, tb->proxy_addr); |
| |
| status = use_new_connection(tb, pool); |
| |
| return status; |
| } |
| |
| apr_status_t |
| setup_serf_https_context_with_proxy(test_baton_t *tb, |
| serf_connection_setup_t conn_setup, |
| serf_ssl_need_server_cert_t server_cert_cb, |
| apr_pool_t *pool) |
| { |
| apr_status_t status; |
| |
| status = setup_test_client_context_with_proxy(tb, |
| conn_setup ? conn_setup: |
| default_https_conn_setup, |
| pool); |
| tb->server_cert_cb = server_cert_cb; |
| |
| return status; |
| } |
| |
| apr_status_t |
| run_client_and_mock_servers_loops(test_baton_t *tb, |
| int num_requests, |
| handler_baton_t handler_ctx[], |
| apr_pool_t *pool) |
| { |
| apr_pool_t *iter_pool; |
| int i, done = 0; |
| MockHTTP *mh = tb->mh; |
| apr_status_t status; |
| apr_time_t finish_time = apr_time_now() + apr_time_from_sec(15); |
| |
| apr_pool_create(&iter_pool, pool); |
| |
| while (!done) |
| { |
| mhError_t err; |
| apr_pool_clear(iter_pool); |
| |
| /* run server event loop */ |
| err = mhRunServerLoop(mh); |
| |
| /* Even if the mock server returned an error, it may have written |
| something to the client. So process that data first, handle the error |
| later. */ |
| |
| /* run client event loop */ |
| status = serf_context_run(tb->context, 0, iter_pool); |
| if (!APR_STATUS_IS_TIMEUP(status) && |
| SERF_BUCKET_READ_ERROR(status)) |
| return status; |
| |
| done = 1; |
| for (i = 0; i < num_requests; i++) |
| done &= handler_ctx[i].done; |
| |
| if (!done && (apr_time_now() > finish_time)) |
| return APR_ETIMEDOUT; |
| |
| if (err == MOCKHTTP_TEST_FAILED) |
| return REPORT_TEST_SUITE_ERROR(); |
| } |
| apr_pool_destroy(iter_pool); |
| |
| return APR_SUCCESS; |
| } |
| |
| void |
| run_client_and_mock_servers_loops_expect_ok(CuTest *tc, test_baton_t *tb, |
| int num_requests, |
| handler_baton_t handler_ctx[], |
| apr_pool_t *pool) |
| { |
| apr_status_t status; |
| |
| status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx, |
| pool); |
| CuAssertIntEquals_Msg(tc, serf_error_string(status), APR_SUCCESS, status); |
| |
| /* Check that the requests were sent and reveived by the server in the order |
| we created them */ |
| Verify(tb->mh) |
| CuAssert(tc, ErrorMessage, VerifyAllRequestsReceivedInOrder); |
| EndVerify |
| |
| CuAssertIntEquals(tc, num_requests, tb->sent_requests->nelts); |
| CuAssertIntEquals(tc, num_requests, tb->accepted_requests->nelts); |
| CuAssertIntEquals(tc, num_requests, tb->handled_requests->nelts); |
| } |
| |
| void setup_test_mock_server(test_baton_t *tb) |
| { |
| if (!tb->mh) /* TODO: move this to test_setup */ |
| tb->mh = mhInit(); |
| |
| InitMockServers(tb->mh) |
| SetupServer(WithHTTP, WithID("server"), WithPort(30080)) |
| EndInit |
| tb->serv_port = mhServerPortNr(tb->mh); |
| tb->serv_host = apr_psprintf(tb->pool, "%s:%d", "localhost", tb->serv_port); |
| tb->serv_url = apr_psprintf(tb->pool, "http://%s", tb->serv_host); |
| } |
| |
| apr_status_t setup_test_mock_proxy(test_baton_t *tb) |
| { |
| if (!tb->mh) |
| tb->mh = mhInit(); |
| |
| InitMockServers(tb->mh) |
| SetupProxy(WithHTTP, WithID("proxy"), WithPort(PROXY_PORT)) |
| EndInit |
| tb->proxy_port = mhProxyPortNr(tb->mh); |
| return apr_sockaddr_info_get(&tb->proxy_addr, |
| "localhost", APR_UNSPEC, |
| mhProxyPortNr(tb->mh), 0, |
| tb->pool); |
| } |
| |
| void setup_test_mock_https_server(test_baton_t *tb, |
| const char *keyfile, |
| const char **certfiles, |
| test_verify_clientcert_t t) |
| { |
| if (!tb->mh) |
| tb->mh = mhInit(); |
| |
| InitMockServers(tb->mh) |
| SetupServer(WithHTTPS, WithID("server"), WithPort(30080), |
| WithCertificateFilesPrefix(get_srcdir_file(tb->pool, |
| "test/certs")), |
| WithCertificateKeyFile(keyfile), |
| WithCertificateKeyPassPhrase("serftest"), |
| WithCertificateFileArray(certfiles), |
| OnConditionThat(t == test_clientcert_mandatory, |
| WithRequiredClientCertificate), |
| OnConditionThat(t == test_clientcert_optional, |
| WithOptionalClientCertificate)) |
| EndInit |
| tb->serv_port = mhServerPortNr(tb->mh); |
| tb->serv_host = apr_psprintf(tb->pool, "%s:%d", "localhost", tb->serv_port); |
| tb->serv_url = apr_psprintf(tb->pool, "https://%s", tb->serv_host); |
| } |
| |
| const char *create_large_request_message(apr_pool_t *pool, const char *body) |
| { |
| const char *request = "GET / HTTP/1.1" CRLF |
| "Host: localhost:12345" CRLF |
| "Transfer-Encoding: chunked" CRLF |
| CRLF; |
| |
| return apr_pstrcat(pool, request, body, NULL); |
| } |
| |
| static int pool_abort_func(int retcode) |
| { |
| fprintf(stderr, "Out of memory\n"); |
| abort(); |
| return 0; |
| } |
| |
| void *test_setup(void *test) |
| { |
| CuTest* tc = test; |
| apr_pool_t *test_pool; |
| apr_allocator_t *allocator; |
| apr_pool_create(&test_pool, NULL); |
| apr_pool_abort_set(pool_abort_func, test_pool); |
| |
| /* Keep a maximum of 16 MB unused memory inside APR. */ |
| allocator = apr_pool_allocator_get(test_pool); |
| if (allocator != NULL) |
| apr_allocator_max_free_set(allocator, 16384 * 1024); |
| /* else: APR pool debugging... leave this to apr */ |
| |
| return initTestCtx(tc, test_pool); |
| } |
| |
| void *test_teardown(void *baton) |
| { |
| test_baton_t *tb = baton; |
| if (tb->mh) |
| mhCleanup(tb->mh); |
| apr_pool_destroy(tb->pool); /* tb is now an invalid pointer */ |
| return NULL; |
| } |
| |
| apr_status_t test__report_suite_error(const char *filename, long line) |
| { |
| test__log(TEST_VERBOSE, __FILE__, "Error in test suite at line %ld\n", line); |
| |
| return SERF_ERROR_ISSUE_IN_TESTSUITE; |
| } |
| |
| /*****************************************************************************/ |
| /* Logging functions */ |
| /*****************************************************************************/ |
| static void log_time(void) |
| { |
| apr_time_exp_t tm; |
| |
| apr_time_exp_lt(&tm, apr_time_now()); |
| fprintf(stderr, "%d-%02d-%02dT%02d:%02d:%02d.%06d%+03d ", |
| 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday, |
| tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_usec, |
| tm.tm_gmtoff/3600); |
| } |
| |
| void test__log(int verbose_flag, const char *filename, const char *fmt, ...) |
| { |
| va_list argp; |
| |
| if (verbose_flag) { |
| log_time(); |
| |
| if (filename) |
| fprintf(stderr, "%s: ", filename); |
| |
| va_start(argp, fmt); |
| vfprintf(stderr, fmt, argp); |
| va_end(argp); |
| } |
| } |
| |
| void test__log_nopref(int verbose_flag, const char *fmt, ...) |
| { |
| va_list argp; |
| |
| if (verbose_flag) { |
| va_start(argp, fmt); |
| vfprintf(stderr, fmt, argp); |
| va_end(argp); |
| } |
| } |
| |
| void test__log_skt(int verbose_flag, const char *filename, apr_socket_t *skt, |
| const char *fmt, ...) |
| { |
| va_list argp; |
| |
| if (verbose_flag) { |
| apr_sockaddr_t *sa; |
| log_time(); |
| |
| if (skt) { |
| /* Log local and remote ip address:port */ |
| fprintf(stderr, "[l:"); |
| if (apr_socket_addr_get(&sa, APR_LOCAL, skt) == APR_SUCCESS) { |
| char buf[32]; |
| apr_sockaddr_ip_getbuf(buf, 32, sa); |
| fprintf(stderr, "%s:%d", buf, sa->port); |
| } |
| fprintf(stderr, " r:"); |
| if (apr_socket_addr_get(&sa, APR_REMOTE, skt) == APR_SUCCESS) { |
| char buf[32]; |
| apr_sockaddr_ip_getbuf(buf, 32, sa); |
| fprintf(stderr, "%s:%d", buf, sa->port); |
| } |
| fprintf(stderr, "] "); |
| } |
| |
| if (filename) |
| fprintf(stderr, "%s: ", filename); |
| |
| va_start(argp, fmt); |
| vfprintf(stderr, fmt, argp); |
| va_end(argp); |
| } |
| } |
| |
| /* Implements serf_unfreed_func_t: prints unfreed memory blocks to stderr |
| * prefixed with test name. */ |
| static void bucket_unfreed_memory_cb(void *baton, void *block) |
| { |
| CuTest *tc = baton; |
| fprintf(stderr, "%s: Unfreed block %p\n", tc->name, block); |
| } |
| |
| serf_bucket_alloc_t * |
| test__create_bucket_allocator(CuTest *tc, apr_pool_t *pool) |
| { |
| return serf_bucket_allocator_create(pool, bucket_unfreed_memory_cb, tc); |
| } |