/* * Copyright 2021 Igalia S.L. * * This file 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; either version 2 of the * License, or (at your option) any later version. * * This file 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 program. If not, see . * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "test-utils.h" #include "soup-connection.h" #include "soup-message-private.h" #include "soup-message-headers-private.h" #include "soup-server-message-private.h" #include "soup-body-input-stream-http2.h" #include static GUri *base_uri; typedef struct { SoupSession *session; } Test; #define LARGE_N_CHARS 24 #define LARGE_CHARS_REPEAT 1024 static void setup_session (Test *test, gconstpointer data) { test->session = soup_test_session_new (NULL); } static void teardown_session (Test *test, gconstpointer data) { soup_test_session_abort_unref (test->session); } static void do_basic_async_test (Test *test, gconstpointer data) { SoupMessage *msg; GBytes *response; GError *error = NULL; msg = soup_message_new_from_uri (SOUP_METHOD_GET, base_uri); g_assert_cmpuint (soup_message_get_http_version (msg), ==, SOUP_HTTP_1_1); response = soup_test_session_async_send (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (soup_message_get_http_version (msg), ==, SOUP_HTTP_2_0); g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Hello world"); g_bytes_unref (response); g_object_unref (msg); } static void do_basic_sync_test (Test *test, gconstpointer data) { SoupMessage *msg; GBytes *response; GError *error = NULL; msg = soup_message_new_from_uri (SOUP_METHOD_GET, base_uri); g_assert_cmpuint (soup_message_get_http_version (msg), ==, SOUP_HTTP_1_1); response = soup_session_send_and_read (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (soup_message_get_http_version (msg), ==, SOUP_HTTP_2_0); g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Hello world"); g_bytes_unref (response); g_object_unref (msg); } static void do_no_content_async_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GBytes *response; GError *error = NULL; uri = g_uri_parse_relative (base_uri, "/no-content", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); response = soup_test_session_async_send (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (soup_message_get_status (msg), ==, 204); g_assert_cmpuint (g_bytes_get_size (response), ==, 0); g_uri_unref (uri); g_bytes_unref (response); g_object_unref (msg); } static void do_large_test (Test *test, gconstpointer data) { gboolean async = GPOINTER_TO_INT (data); GUri *uri; SoupMessage *msg; GBytes *response; GError *error = NULL; uri = g_uri_parse_relative (base_uri, "/large", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); /* This is both large and read in chunks */ if (async) response = soup_test_session_async_send (test->session, msg, NULL, &error); else response = soup_session_send_and_read (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (g_bytes_get_size (response), ==, (LARGE_N_CHARS * LARGE_CHARS_REPEAT) + 1); g_uri_unref (uri); g_bytes_unref (response); g_object_unref (msg); } static GBytes * read_stream_to_bytes_sync (GInputStream *stream) { GOutputStream *out = g_memory_output_stream_new_resizable (); GError *error = NULL; gssize read = g_output_stream_splice (out, stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); g_assert_no_error (error); g_assert_cmpint (read, >=, 0); GBytes *bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (out)); g_object_unref (out); return bytes; } static void on_send_complete (GObject *source, GAsyncResult *res, gpointer user_data) { SoupSession *sess = SOUP_SESSION (source); GError *error = NULL; GInputStream *stream; GBytes **bytes_out = user_data; stream = soup_session_send_finish (sess, res, &error); g_assert_no_error (error); g_assert_nonnull (stream); *bytes_out = read_stream_to_bytes_sync (stream); g_object_unref (stream); } static void do_multi_message_async_test (Test *test, gconstpointer data) { GMainContext *async_context = g_main_context_ref_thread_default (); GUri *uri1, *uri2; SoupMessage *msg1, *msg2; GBytes *response1 = NULL; GBytes *response2 = NULL; uri1 = g_uri_parse_relative (base_uri, "echo_query?body%201", SOUP_HTTP_URI_FLAGS, NULL); msg1 = soup_message_new_from_uri (SOUP_METHOD_GET, uri1); soup_message_set_http_version (msg1, SOUP_HTTP_2_0); uri2 = g_uri_parse_relative (base_uri, "echo_query?body%202", SOUP_HTTP_URI_FLAGS, NULL); msg2 = soup_message_new_from_uri (SOUP_METHOD_GET, uri2); soup_message_set_http_version (msg2, SOUP_HTTP_2_0); soup_session_send_async (test->session, msg1, G_PRIORITY_DEFAULT, NULL, on_send_complete, &response1); soup_session_send_async (test->session, msg2, G_PRIORITY_DEFAULT, NULL, on_send_complete, &response2); while (!response1 || !response2) { g_main_context_iteration (async_context, TRUE); } g_assert_cmpuint (soup_message_get_http_version (msg1), ==, SOUP_HTTP_2_0); g_assert_cmpuint (soup_message_get_http_version (msg2), ==, SOUP_HTTP_2_0); g_assert_cmpstr (g_bytes_get_data (response1, NULL), ==, "body%201"); g_assert_cmpstr (g_bytes_get_data (response2, NULL), ==, "body%202"); while (g_main_context_pending (async_context)) g_main_context_iteration (async_context, FALSE); g_bytes_unref (response1); g_bytes_unref (response2); g_object_unref (msg1); g_object_unref (msg2); g_uri_unref (uri1); g_uri_unref (uri2); g_main_context_unref (async_context); } static void on_send_and_read_cancelled_complete (SoupSession *session, GAsyncResult *result, gboolean *done) { GError *error = NULL; GBytes *response = soup_session_send_and_read_finish (session, result, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_assert_null (response); g_error_free (error); *done = TRUE; } static void on_send_and_read_complete (SoupSession *session, GAsyncResult *result, gboolean *done) { GError *error = NULL; GBytes *response = soup_session_send_and_read_finish (session, result, &error); g_assert_no_error (error); g_assert_nonnull (response); g_bytes_unref (response); *done = TRUE; } static void do_cancellation_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GMainContext *async_context = g_main_context_ref_thread_default (); GCancellable *cancellable = g_cancellable_new (); gboolean done = FALSE; uri = g_uri_parse_relative (base_uri, "/large", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); soup_session_send_and_read_async (test->session, msg, G_PRIORITY_DEFAULT, cancellable, (GAsyncReadyCallback)on_send_and_read_cancelled_complete, &done); /* Cancel right after getting the headers */ g_signal_connect_swapped (msg, "got-headers", G_CALLBACK (g_cancellable_cancel), cancellable); while (!done) g_main_context_iteration (async_context, FALSE); g_object_unref (msg); done = FALSE; msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); soup_session_send_and_read_async (test->session, msg, G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback)on_send_and_read_complete, &done); while (!done) g_main_context_iteration (async_context, FALSE); g_uri_unref (uri); g_object_unref (msg); g_object_unref (cancellable); g_main_context_unref (async_context); } static void do_one_cancel_after_send_request_test (SoupSession *session, gboolean reuse_cancellable, gboolean cancelled_by_session) { SoupMessage *msg; GCancellable *cancellable; GInputStream *istream; GOutputStream *ostream; guint flags = SOUP_TEST_REQUEST_CANCEL_AFTER_SEND_FINISH; GBytes *body; GError *error = NULL; if (cancelled_by_session) flags |= SOUP_TEST_REQUEST_CANCEL_BY_SESSION; msg = soup_message_new_from_uri (SOUP_METHOD_GET, base_uri); cancellable = g_cancellable_new (); istream = soup_test_request_send (session, msg, cancellable, flags, &error); g_assert_no_error (error); g_assert_nonnull (istream); /* If we use a new cancellable to read the stream * it shouldn't fail with cancelled error. */ if (!reuse_cancellable) { g_object_unref (cancellable); cancellable = g_cancellable_new (); } ostream = g_memory_output_stream_new_resizable (); g_output_stream_splice (ostream, istream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, cancellable, &error); if (reuse_cancellable || cancelled_by_session) { g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_clear_error (&error); } else { g_assert_no_error (error); body = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (ostream)); g_assert_cmpstr (g_bytes_get_data (body, NULL), ==, "Hello world"); g_bytes_unref (body); } while (g_main_context_pending (NULL)) g_main_context_iteration (NULL, FALSE); g_object_unref (cancellable); g_object_unref (ostream); g_object_unref (istream); g_object_unref (msg); } static void do_cancellation_after_send_test (Test *test, gconstpointer data) { do_one_cancel_after_send_request_test (test->session, TRUE, FALSE); do_one_cancel_after_send_request_test (test->session, FALSE, FALSE); do_one_cancel_after_send_request_test (test->session, FALSE, TRUE); } static void do_post_sync_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GInputStream *response; GBytes *bytes = g_bytes_new_static ("body 1", sizeof ("body 1")); GError *error = NULL; uri = g_uri_parse_relative (base_uri, "/echo_post", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_POST, uri); soup_message_set_request_body_from_bytes (msg, "text/plain", bytes); response = soup_session_send (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_nonnull (response); GBytes *response_bytes = read_stream_to_bytes_sync (response); g_assert_cmpstr (g_bytes_get_data (response_bytes, NULL), ==, "body 1"); g_bytes_unref (response_bytes); g_object_unref (response); g_bytes_unref (bytes); g_object_unref (msg); g_uri_unref (uri); } static void do_post_large_sync_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GInputStream *response; guint large_size = 10000; char *large_data; unsigned int i; GError *error = NULL; large_data = g_malloc (large_size); for (i = 0; i < large_size; i++) large_data[i] = i & 0xFF; GBytes *bytes = g_bytes_new_take (large_data, large_size); uri = g_uri_parse_relative (base_uri, "/echo_post", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_POST, uri); soup_message_set_request_body_from_bytes (msg, "text/plain", bytes); response = soup_session_send (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_nonnull (response); GBytes *response_bytes = read_stream_to_bytes_sync (response); g_assert_true (g_bytes_equal (bytes, response_bytes)); g_bytes_unref (response_bytes); g_object_unref (response); g_bytes_unref (bytes); g_object_unref (msg); g_uri_unref (uri); } static void do_post_async_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GBytes *response = NULL; GMainContext *async_context = g_main_context_ref_thread_default (); GBytes *bytes = g_bytes_new_static ("body 1", sizeof ("body 1")); uri = g_uri_parse_relative (base_uri, "/echo_post", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_POST, uri); soup_message_set_request_body_from_bytes (msg, "text/plain", bytes); soup_session_send_async (test->session, msg, G_PRIORITY_DEFAULT, NULL, on_send_complete, &response); while (!response) g_main_context_iteration (async_context, TRUE); g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "body 1"); while (g_main_context_pending (async_context)) g_main_context_iteration (async_context, FALSE); g_bytes_unref (response); g_bytes_unref (bytes); g_main_context_unref (async_context); g_object_unref (msg); g_uri_unref (uri); } static void do_post_large_async_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GBytes *response = NULL; GMainContext *async_context = g_main_context_ref_thread_default (); guint large_size = 10000; char *large_data; unsigned int i; large_data = g_malloc (large_size); for (i = 0; i < large_size; i++) large_data[i] = i & 0xFF; GBytes *bytes = g_bytes_new_take (large_data, large_size); uri = g_uri_parse_relative (base_uri, "/echo_post", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_POST, uri); soup_message_set_request_body_from_bytes (msg, "text/plain", bytes); soup_session_send_async (test->session, msg, G_PRIORITY_DEFAULT, NULL, on_send_complete, &response); while (!response) g_main_context_iteration (async_context, TRUE); g_assert_true (g_bytes_equal (bytes, response)); while (g_main_context_pending (async_context)) g_main_context_iteration (async_context, FALSE); g_bytes_unref (response); g_bytes_unref (bytes); g_main_context_unref (async_context); g_object_unref (msg); g_uri_unref (uri); } static void do_post_blocked_async_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GBytes *response = NULL; GMainContext *async_context = g_main_context_ref_thread_default (); GInputStream *in_stream = soup_body_input_stream_http2_new (); soup_body_input_stream_http2_add_data (SOUP_BODY_INPUT_STREAM_HTTP2 (in_stream), (guint8*)"Part 1 -", 8); uri = g_uri_parse_relative (base_uri, "/echo_post", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_POST, uri); soup_message_set_request_body (msg, "text/plain", in_stream, 8 + 8); soup_session_send_async (test->session, msg, G_PRIORITY_DEFAULT, NULL, on_send_complete, &response); while (!response) { // Let it iterate for a bit waiting on blocked data if (soup_body_input_stream_http2_is_blocked (SOUP_BODY_INPUT_STREAM_HTTP2 (in_stream))) { soup_body_input_stream_http2_add_data (SOUP_BODY_INPUT_STREAM_HTTP2 (in_stream), (guint8*)" Part 2", 8); soup_body_input_stream_http2_complete (SOUP_BODY_INPUT_STREAM_HTTP2 (in_stream)); } g_main_context_iteration (async_context, TRUE); } g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Part 1 - Part 2"); while (g_main_context_pending (async_context)) g_main_context_iteration (async_context, FALSE); g_bytes_unref (response); g_object_unref (in_stream); g_main_context_unref (async_context); g_object_unref (msg); g_uri_unref (uri); } static void do_post_file_async_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GBytes *response = NULL; GMainContext *async_context = g_main_context_ref_thread_default (); GFile *in_file = g_file_new_for_path (g_test_get_filename (G_TEST_DIST, "test-cert.pem", NULL)); GFileInputStream *in_stream = g_file_read (in_file, NULL, NULL); g_assert_nonnull (in_stream); uri = g_uri_parse_relative (base_uri, "/echo_post", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_POST, uri); soup_message_set_request_body (msg, "application/x-x509-ca-cert", G_INPUT_STREAM (in_stream), -1); soup_session_send_async (test->session, msg, G_PRIORITY_DEFAULT, NULL, on_send_complete, &response); while (!response) g_main_context_iteration (async_context, TRUE); g_assert_true (g_str_has_prefix (g_bytes_get_data (response, NULL), "-----BEGIN CERTIFICATE-----")); while (g_main_context_pending (async_context)) g_main_context_iteration (async_context, FALSE); g_bytes_unref (response); g_object_unref (in_stream); g_object_unref (in_file); g_main_context_unref (async_context); g_object_unref (msg); g_uri_unref (uri); } static gboolean on_delayed_auth (SoupAuth *auth) { g_test_message ("Authenticating"); soup_auth_authenticate (auth, "username", "password"); return G_SOURCE_REMOVE; } static gboolean on_authenticate (SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data) { g_test_message ("Authenticate request"); /* Force it to pause the message by delaying auth */ g_timeout_add (500, (GSourceFunc)on_delayed_auth, auth); return TRUE; } static void do_paused_async_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GBytes *response; GError *error = NULL; uri = g_uri_parse_relative (base_uri, "/auth", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); g_signal_connect (msg, "authenticate", G_CALLBACK (on_authenticate), NULL); response = soup_test_session_async_send (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Authenticated"); g_bytes_unref (response); g_object_unref (msg); g_uri_unref (uri); } static SoupConnection *last_connection; static void on_send_ready (GObject *source, GAsyncResult *res, gpointer user_data) { SoupSession *sess = SOUP_SESSION (source); SoupMessage *msg = soup_session_get_async_result_message (sess, res); guint *complete_count = user_data; SoupConnection *conn; GError *error = NULL; GInputStream *stream; stream = soup_session_send_finish (sess, res, &error); g_assert_no_error (error); g_assert_nonnull (stream); GBytes *result = read_stream_to_bytes_sync (stream); g_object_unref (stream); g_assert_nonnull (result); g_assert_cmpstr (g_bytes_get_data (result, NULL), ==, "Hello world"); g_bytes_unref (result); g_assert_nonnull (msg); g_assert_cmpuint (soup_message_get_http_version (msg), ==, SOUP_HTTP_2_0); conn = soup_message_get_connection (msg); if (last_connection) g_assert (last_connection == conn); else last_connection = conn; g_test_message ("Conn (%u) = %p", *complete_count, conn); *complete_count += 1; } static void do_connections_test (Test *test, gconstpointer data) { GMainContext *async_context; guint complete_count = 0; GUri *uri; if (g_getenv ("ASAN_OPTIONS")) { g_test_skip ("Flakey on asan GitLab runner"); return; } async_context = g_main_context_ref_thread_default (); uri = g_uri_parse_relative (base_uri, "/slow", SOUP_HTTP_URI_FLAGS, NULL); #define N_TESTS 100 for (unsigned int i = 0; i < N_TESTS; ++i) { SoupMessage *msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); soup_session_send_async (test->session, msg, G_PRIORITY_DEFAULT, NULL, on_send_ready, &complete_count); g_object_unref (msg); } while (complete_count != N_TESTS) { g_main_context_iteration (async_context, TRUE); } while (g_main_context_pending (async_context)) g_main_context_iteration (async_context, FALSE); /* After no messages reference the connection it should be IDLE and reusable */ g_assert_cmpuint (soup_connection_get_state (last_connection), ==, SOUP_CONNECTION_IDLE); SoupMessage *msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); soup_session_send_async (test->session, msg, G_PRIORITY_DEFAULT, NULL, on_send_ready, &complete_count); g_object_unref (msg); while (complete_count != N_TESTS + 1) g_main_context_iteration (async_context, TRUE); while (g_main_context_pending (async_context)) g_main_context_iteration (async_context, FALSE); g_uri_unref (uri); g_main_context_unref (async_context); } static void do_misdirected_request_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GBytes *response; GError *error = NULL; uri = g_uri_parse_relative (base_uri, "/misdirected_request", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); response = soup_test_session_async_send (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Success!"); g_bytes_unref (response); g_object_unref (msg); g_uri_unref (uri); } static void log_printer (SoupLogger *logger, SoupLoggerLogLevel level, char direction, const char *data, gpointer user_data) { gboolean *has_logged_body = user_data; // We are testing that the request body is logged // which is backend specific for now if (direction == '>' && g_strcmp0 (data, "Test") == 0) *has_logged_body = TRUE; } static void do_logging_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GBytes *response; GError *error = NULL; GBytes *bytes = g_bytes_new_static ("Test", sizeof ("Test")); gboolean has_logged_body = FALSE; SoupLogger *logger = soup_logger_new (SOUP_LOGGER_LOG_BODY); soup_logger_set_printer (logger, log_printer, &has_logged_body, NULL); soup_session_add_feature (test->session, SOUP_SESSION_FEATURE (logger)); uri = g_uri_parse_relative (base_uri, "/echo_post", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_POST, uri); soup_message_set_request_body_from_bytes (msg, "text/plain", bytes); response = soup_test_session_async_send (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Test"); g_assert_true (has_logged_body); g_bytes_unref (response); g_object_unref (msg); g_uri_unref (uri); } static void msg_got_body_data_cb (SoupMessage *msg, guint chunk_size, guint64 *response_body_bytes_received) { *response_body_bytes_received += chunk_size; } static void do_metrics_size_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GBytes *response; GError *error = NULL; guint64 response_body_bytes_received = 0; GBytes *bytes = g_bytes_new_static ("Test", sizeof ("Test")); uri = g_uri_parse_relative (base_uri, "/echo_post", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_POST, uri); g_signal_connect (msg, "got-body-data", G_CALLBACK (msg_got_body_data_cb), &response_body_bytes_received); soup_message_set_request_body_from_bytes (msg, "text/plain", bytes); soup_message_add_flags (msg, SOUP_MESSAGE_COLLECT_METRICS); response = soup_test_session_async_send (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Test"); SoupMessageMetrics *metrics = soup_message_get_metrics (msg); g_assert_nonnull (metrics); g_assert_cmpuint (soup_message_metrics_get_request_header_bytes_sent (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_request_body_size (metrics), ==, g_bytes_get_size (bytes)); g_assert_cmpuint (soup_message_metrics_get_request_body_bytes_sent (metrics), >, soup_message_metrics_get_request_body_size (metrics)); g_assert_cmpuint (soup_message_metrics_get_response_header_bytes_received (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_response_body_size (metrics), ==, g_bytes_get_size (response)); g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), >, soup_message_metrics_get_response_body_size (metrics)); g_assert_cmpuint (soup_message_metrics_get_response_body_bytes_received (metrics), ==, response_body_bytes_received); g_bytes_unref (response); g_bytes_unref (bytes); g_object_unref (msg); g_uri_unref (uri); } static void metrics_test_network_event_cb (SoupMessage *msg, GSocketClientEvent event, GIOStream *connection, guint *network_event_called) { SoupMessageMetrics *metrics = soup_message_get_metrics (msg); g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), >, 0); switch (event) { case G_SOCKET_CLIENT_RESOLVING: g_assert_cmpuint (soup_message_metrics_get_dns_start (metrics), >, 0); break; case G_SOCKET_CLIENT_RESOLVED: g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), >=, soup_message_metrics_get_dns_start (metrics)); break; case G_SOCKET_CLIENT_CONNECTING: g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), >=, soup_message_metrics_get_dns_end (metrics)); break; case G_SOCKET_CLIENT_TLS_HANDSHAKING: g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), >=, soup_message_metrics_get_connect_start (metrics)); break; case G_SOCKET_CLIENT_COMPLETE: g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), >=, soup_message_metrics_get_connect_start (metrics)); g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), >=, soup_message_metrics_get_tls_start (metrics)); break; default: return; } *network_event_called += 1; } static void metrics_test_message_starting_cb (SoupMessage *msg, gboolean *starting_called) { SoupMessageMetrics *metrics = soup_message_get_metrics (msg); g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), >=, soup_message_metrics_get_fetch_start (metrics)); *starting_called = TRUE; } static void metrics_test_status_changed_cb (SoupMessage *msg, GParamSpec *pspec, gboolean *status_changed_called) { SoupMessageMetrics *metrics = soup_message_get_metrics (msg); g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), >=, soup_message_metrics_get_request_start (metrics)); *status_changed_called = TRUE; } static void metrics_test_got_body_cb (SoupMessage *msg, gboolean *got_body_called) { SoupMessageMetrics *metrics = soup_message_get_metrics (msg); g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >=, soup_message_metrics_get_response_start (metrics)); *got_body_called = TRUE; } static void do_one_metrics_time_test (SoupSession *session, gboolean is_new_connection) { SoupMessage *msg; GBytes *body; SoupMessageMetrics *metrics; gboolean starting_called = FALSE; gboolean status_changed_called = FALSE; gboolean got_body_called = FALSE; guint network_event_called = 0; msg = soup_message_new_from_uri (SOUP_METHOD_GET, base_uri); soup_message_add_flags (msg, SOUP_MESSAGE_COLLECT_METRICS); g_signal_connect (msg, "starting", G_CALLBACK (metrics_test_message_starting_cb), &starting_called); g_signal_connect (msg, "notify::status-code", G_CALLBACK (metrics_test_status_changed_cb), &status_changed_called); g_signal_connect (msg, "got-body", G_CALLBACK (metrics_test_got_body_cb), &got_body_called); g_signal_connect (msg, "network-event", G_CALLBACK (metrics_test_network_event_cb), &network_event_called); body = soup_session_send_and_read (session, msg, NULL, NULL); soup_test_assert_message_status (msg, SOUP_STATUS_OK); g_bytes_unref (body); g_assert_true (starting_called); g_assert_true (status_changed_called); g_assert_true (got_body_called); if (is_new_connection) g_assert_cmpuint (network_event_called, ==, 5); else g_assert_cmpuint (network_event_called, ==, 0); metrics = soup_message_get_metrics (msg); g_assert_nonnull (metrics); g_assert_cmpuint (soup_message_metrics_get_fetch_start (metrics), >, 0); if (is_new_connection) { g_assert_cmpuint (soup_message_metrics_get_dns_start (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), >, 0); } else { g_assert_cmpuint (soup_message_metrics_get_dns_start (metrics), ==, 0); g_assert_cmpuint (soup_message_metrics_get_dns_end (metrics), ==, 0); g_assert_cmpuint (soup_message_metrics_get_connect_start (metrics), ==, 0); g_assert_cmpuint (soup_message_metrics_get_tls_start (metrics), ==, 0); g_assert_cmpuint (soup_message_metrics_get_connect_end (metrics), ==, 0); } g_assert_cmpuint (soup_message_metrics_get_request_start (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_response_start (metrics), >, 0); g_assert_cmpuint (soup_message_metrics_get_response_end (metrics), >, 0); g_object_unref (msg); } static void do_metrics_time_test (Test *test, gconstpointer data) { do_one_metrics_time_test (test->session, TRUE); do_one_metrics_time_test (test->session, FALSE); } static void on_preconnect_ready (SoupSession *session, GAsyncResult *result, SoupConnection **conn) { SoupMessage *msg = soup_session_get_async_result_message (session, result); GError *error = NULL; *conn = soup_message_get_connection (msg); soup_session_preconnect_finish (session, result, &error); g_assert_no_error (error); } static void do_preconnect_test (Test *test, gconstpointer data) { GMainContext *async_context = g_main_context_ref_thread_default (); SoupMessage *msg = soup_message_new_from_uri (SOUP_METHOD_GET, base_uri); GError *error = NULL; SoupConnection *conn = NULL; guint32 connection_id; soup_session_preconnect_async (test->session, msg, G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback)on_preconnect_ready, &conn); while (!conn) g_main_context_iteration (async_context, FALSE); connection_id = soup_message_get_connection_id (msg); g_assert_cmpuint (soup_connection_get_state (conn), ==, SOUP_CONNECTION_IDLE); g_object_unref (msg); msg = soup_message_new_from_uri (SOUP_METHOD_GET, base_uri); GBytes *response = soup_test_session_async_send (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_cmpstr (g_bytes_get_data (response, NULL), ==, "Hello world"); g_assert_cmpuint (soup_message_get_connection_id (msg), ==, connection_id); g_bytes_unref (response); g_object_unref (msg); g_main_context_unref (async_context); } static void do_invalid_header_test (Test *test, gconstpointer data) { static const char *invalid_headers[] = { "Connection", "Keep-Alive", "Proxy-Connection", "Transfer-Encoding", "Upgrade" }; guint i; for (i = 0; i < G_N_ELEMENTS (invalid_headers); i++) { SoupMessage *msg; SoupMessageHeaders *request_headers; GBytes *body; GError *error = NULL; msg = soup_message_new_from_uri (SOUP_METHOD_GET, base_uri); request_headers = soup_message_get_request_headers (msg); soup_message_headers_append (request_headers, invalid_headers[i], "Value"); body = soup_test_session_async_send (test->session, msg, NULL, &error); g_assert_no_error (error); g_assert_cmpstr (g_bytes_get_data (body, NULL), ==, "Hello world"); g_bytes_unref (body); g_object_unref (msg); } } static void do_invalid_header_received_test (Test *test, gconstpointer data) { gboolean async = GPOINTER_TO_INT (data); GUri *uri; SoupMessage *msg; GBytes *response; GError *error = NULL; uri = g_uri_parse_relative (base_uri, "/invalid-header", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); if (async) response = soup_test_session_async_send (test->session, msg, NULL, &error); else response = soup_session_send_and_read (test->session, msg, NULL, &error); g_assert_null (response); g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_cmpstr (error->message, ==, "HTTP/2 Error: PROTOCOL_ERROR"); g_clear_error (&error); g_uri_unref (uri); g_object_unref (msg); } #ifdef HAVE_NGHTTP2_OPTION_SET_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION static void do_invalid_header_rfc9113_received_test (Test *test, gconstpointer data) { gboolean async = GPOINTER_TO_INT (data); GUri *uri; SoupMessage *msg; GBytes *response; GError *error = NULL; uri = g_uri_parse_relative (base_uri, "/invalid-header-rfc9113", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); if (async) response = soup_test_session_async_send (test->session, msg, NULL, &error); else response = soup_session_send_and_read (test->session, msg, NULL, &error); g_assert_nonnull (response); g_assert_no_error (error); g_clear_error (&error); g_object_unref (msg); g_uri_unref (uri); } #endif static void content_sniffed (SoupMessage *msg, const char *content_type, GHashTable *params, char **sniffed_type) { g_object_set_data (G_OBJECT (msg), "content-sniffed", GINT_TO_POINTER (TRUE)); *sniffed_type = g_strdup (content_type); } static void got_headers (SoupMessage *msg) { soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") == NULL, "content-sniffed got emitted before got-headers"); g_object_set_data (G_OBJECT (msg), "got-headers", GINT_TO_POINTER (TRUE)); } static void sniffer_test_send_ready_cb (SoupSession *session, GAsyncResult *result, GInputStream **stream) { GError *error = NULL; *stream = soup_session_send_finish (session, result, &error); g_assert_no_error (error); g_assert_nonnull (*stream); } static void do_one_sniffer_test (SoupSession *session, const char *path, gsize expected_size, gboolean should_sniff, GMainContext *async_context) { GUri *uri; SoupMessage *msg; GInputStream *stream = NULL; GBytes *bytes; char *sniffed_type = NULL; uri = g_uri_parse_relative (base_uri, path, SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); g_object_connect (msg, "signal::got-headers", got_headers, NULL, "signal::content-sniffed", content_sniffed, &sniffed_type, NULL); if (async_context) { soup_session_send_async (session, msg, G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback)sniffer_test_send_ready_cb, &stream); while (!stream) g_main_context_iteration (async_context, TRUE); } else { GError *error = NULL; stream = soup_session_send (session, msg, NULL, &error); g_assert_no_error (error); g_assert_nonnull (stream); } if (should_sniff) { soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") != NULL, "content-sniffed did not get emitted"); g_assert_cmpstr (sniffed_type, ==, "text/plain"); } else { soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") == NULL, "content-sniffed got emitted without a sniffer"); g_assert_null (sniffed_type); } bytes = read_stream_to_bytes_sync (stream); g_assert_cmpuint (g_bytes_get_size (bytes), ==, expected_size); g_free (sniffed_type); g_object_unref (stream); g_bytes_unref (bytes); g_object_unref (msg); g_uri_unref (uri); } static void do_sniffer_async_test (Test *test, gconstpointer data) { GMainContext *async_context = g_main_context_ref_thread_default (); gboolean should_content_sniff = GPOINTER_TO_INT (data); if (should_content_sniff) soup_session_add_feature_by_type (test->session, SOUP_TYPE_CONTENT_SNIFFER); do_one_sniffer_test (test->session, "/", 11, should_content_sniff, async_context); do_one_sniffer_test (test->session, "/large", (LARGE_N_CHARS * LARGE_CHARS_REPEAT) + 1, should_content_sniff, async_context); do_one_sniffer_test (test->session, "/no-content", 0, should_content_sniff, async_context); while (g_main_context_pending (async_context)) g_main_context_iteration (async_context, FALSE); g_main_context_unref (async_context); } static void do_sniffer_sync_test (Test *test, gconstpointer data) { gboolean should_content_sniff = GPOINTER_TO_INT (data); if (should_content_sniff) soup_session_add_feature_by_type (test->session, SOUP_TYPE_CONTENT_SNIFFER); do_one_sniffer_test (test->session, "/", 11, should_content_sniff, NULL); do_one_sniffer_test (test->session, "/large", (LARGE_N_CHARS * LARGE_CHARS_REPEAT) + 1, should_content_sniff, NULL); do_one_sniffer_test (test->session, "/no-content", 0, should_content_sniff, NULL); } static void do_timeout_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GBytes *response; GError *error = NULL; soup_session_set_timeout (test->session, 2); uri = g_uri_parse_relative (base_uri, "/timeout", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); response = soup_test_session_async_send (test->session, msg, NULL, &error); g_assert_null (response); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT); g_object_unref (msg); g_uri_unref (uri); while (g_main_context_pending (NULL)) g_main_context_iteration (NULL, FALSE); } static void do_connection_closed_test (Test *test, gconstpointer data) { GUri *uri; SoupMessage *msg; GInputStream *stream; GError *error = NULL; uri = g_uri_parse_relative (base_uri, "/close", SOUP_HTTP_URI_FLAGS, NULL); msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); stream = soup_session_send (test->session, msg, NULL, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT); g_clear_error (&error); g_clear_object (&stream); g_object_unref (msg); g_uri_unref (uri); } static gboolean unpause_message (SoupServerMessage *msg) { soup_server_message_unpause (msg); g_object_unref (msg); return FALSE; } static void server_handler (SoupServer *server, SoupServerMessage *msg, const char *path, GHashTable *query, gpointer user_data) { g_assert_cmpuint (soup_server_message_get_http_version (msg), ==, SOUP_HTTP_2_0); if (strcmp (path, "/") == 0 || strcmp (path, "/slow") == 0 || strcmp (path, "/timeout") == 0) { gboolean is_slow = path[1] == 's'; gboolean is_timeout = path[1] == 't'; soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); soup_server_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "Hello world", 11); if (is_slow || is_timeout) { GSource *timeout; soup_server_message_pause (msg); timeout = soup_add_timeout (g_main_context_get_thread_default (), is_timeout ? 4000 : 1000, (GSourceFunc)unpause_message, g_object_ref (msg)); g_source_unref (timeout); } } else if (strcmp (path, "/no-content") == 0) { soup_server_message_set_status (msg, SOUP_STATUS_NO_CONTENT, NULL); } else if (strcmp (path, "/large") == 0) { int i, j; SoupMessageBody *response_body; char letter = 'A'; /* Send increasing letters just to aid debugging */ response_body = soup_server_message_get_response_body (msg); for (i = 0; i < LARGE_N_CHARS; i++, letter++) { GString *chunk = g_string_new (NULL); for (j = 0; j < LARGE_CHARS_REPEAT; j++) chunk = g_string_append_c (chunk, letter); soup_message_body_append_bytes (response_body, g_string_free_to_bytes (chunk)); } soup_message_body_append (response_body, SOUP_MEMORY_STATIC, "\0", 1); soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); } else if (strcmp (path, "/echo_query") == 0) { const char *query_str = g_uri_get_query (soup_server_message_get_uri (msg)); soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); soup_server_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, query_str, strlen (query_str)); } else if (strcmp (path, "/echo_post") == 0) { SoupMessageBody *request_body; request_body = soup_server_message_get_request_body (msg); soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); soup_server_message_set_response (msg, "text/plain", SOUP_MEMORY_COPY, request_body->data, request_body->length); } else if (strcmp (path, "/misdirected_request") == 0) { static SoupServerConnection *conn = NULL; if (!conn) { conn = soup_server_message_get_connection (msg); soup_server_message_set_status (msg, SOUP_STATUS_MISDIRECTED_REQUEST, NULL); } else { /* Message is retried on a different connection */ g_assert_false (conn == soup_server_message_get_connection (msg)); soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); soup_server_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "Success!", 8); } } else if (strcmp (path, "/auth") == 0) { soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); soup_server_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "Authenticated", 13); } else if (strcmp (path, "/invalid-header") == 0) { SoupMessageHeaders *response_headers; response_headers = soup_server_message_get_response_headers (msg); /* Use soup_message_headers_append_common to skip the validation check. */ soup_message_headers_append_common (response_headers, SOUP_HEADER_CONTENT_TYPE, "\r"); soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); } else if (strcmp (path, "/invalid-header-rfc9113") == 0) { SoupMessageHeaders *response_headers; response_headers = soup_server_message_get_response_headers (msg); soup_message_headers_append (response_headers, "Invalid-Header-Value", "foo "); soup_server_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "Success!", 8); soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); } else if (strcmp (path, "/close") == 0) { SoupServerConnection *conn; int fd; conn = soup_server_message_get_connection (msg); fd = g_socket_get_fd (soup_server_connection_get_socket (conn)); #ifdef G_OS_WIN32 shutdown (fd, SD_SEND); #else shutdown (fd, SHUT_WR); #endif soup_server_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "Success!", 8); } } static gboolean server_basic_auth_callback (SoupAuthDomain *auth_domain, SoupServerMessage *msg, const char *username, const char *password, gpointer data) { if (strcmp (username, "username") != 0) return FALSE; return strcmp (password, "password") == 0; } int main (int argc, char **argv) { SoupServer *server; SoupAuthDomain *auth; int ret; test_init (argc, argv, NULL); if (!tls_available) return 0; server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD | SOUP_TEST_SERVER_HTTP2); auth = soup_auth_domain_basic_new ("realm", "http2-test", "auth-callback", server_basic_auth_callback, NULL); soup_auth_domain_add_path (auth, "/auth"); soup_server_add_auth_domain (server, auth); g_object_unref (auth); soup_server_add_handler (server, NULL, server_handler, NULL, NULL); base_uri = soup_test_server_get_uri (server, "https", "127.0.0.1"); g_test_add ("/http2/basic/async", Test, NULL, setup_session, do_basic_async_test, teardown_session); g_test_add ("/http2/basic/sync", Test, NULL, setup_session, do_basic_sync_test, teardown_session); g_test_add ("/http2/no_content/async", Test, NULL, setup_session, do_no_content_async_test, teardown_session); g_test_add ("/http2/large/async", Test, GINT_TO_POINTER (TRUE), setup_session, do_large_test, teardown_session); g_test_add ("/http2/large/sync", Test, GINT_TO_POINTER (FALSE), setup_session, do_large_test, teardown_session); g_test_add ("/http2/multiplexing/async", Test, NULL, setup_session, do_multi_message_async_test, teardown_session); g_test_add ("/http2/post/async", Test, NULL, setup_session, do_post_async_test, teardown_session); g_test_add ("/http2/post/sync", Test, NULL, setup_session, do_post_sync_test, teardown_session); g_test_add ("/http2/post/large/sync", Test, NULL, setup_session, do_post_large_sync_test, teardown_session); g_test_add ("/http2/post/large/async", Test, NULL, setup_session, do_post_large_async_test, teardown_session); g_test_add ("/http2/post/blocked/async", Test, NULL, setup_session, do_post_blocked_async_test, teardown_session); g_test_add ("/http2/post/file/async", Test, NULL, setup_session, do_post_file_async_test, teardown_session); g_test_add ("/http2/paused/async", Test, NULL, setup_session, do_paused_async_test, teardown_session); g_test_add ("/http2/connections", Test, NULL, setup_session, do_connections_test, teardown_session); g_test_add ("/http2/misdirected_request", Test, NULL, setup_session, do_misdirected_request_test, teardown_session); g_test_add ("/http2/logging", Test, NULL, setup_session, do_logging_test, teardown_session); g_test_add ("/http2/metrics/size", Test, NULL, setup_session, do_metrics_size_test, teardown_session); g_test_add ("/http2/metrics/time", Test, NULL, setup_session, do_metrics_time_test, teardown_session); g_test_add ("/http2/preconnect", Test, NULL, setup_session, do_preconnect_test, teardown_session); g_test_add ("/http2/cancellation", Test, NULL, setup_session, do_cancellation_test, teardown_session); g_test_add ("/http2/cancellation-after-send", Test, NULL, setup_session, do_cancellation_after_send_test, teardown_session); g_test_add ("/http2/invalid-header", Test, NULL, setup_session, do_invalid_header_test, teardown_session); g_test_add ("/http2/invalid-header-received/async", Test, GINT_TO_POINTER (TRUE), setup_session, do_invalid_header_received_test, teardown_session); g_test_add ("/http2/invalid-header-received/sync", Test, GINT_TO_POINTER (FALSE), setup_session, do_invalid_header_received_test, teardown_session); #ifdef HAVE_NGHTTP2_OPTION_SET_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION g_test_add ("/http2/invalid-header-rfc9113-received/async", Test, GINT_TO_POINTER (TRUE), setup_session, do_invalid_header_rfc9113_received_test, teardown_session); g_test_add ("/http2/invalid-header-rfc9113-received/sync", Test, GINT_TO_POINTER (FALSE), setup_session, do_invalid_header_rfc9113_received_test, teardown_session); #endif g_test_add ("/http2/sniffer/with-sniffer/async", Test, GINT_TO_POINTER (TRUE), setup_session, do_sniffer_async_test, teardown_session); g_test_add ("/http2/sniffer/no-sniffer/async", Test, GINT_TO_POINTER (FALSE), setup_session, do_sniffer_async_test, teardown_session); g_test_add ("/http2/sniffer/with-sniffer/sync", Test, GINT_TO_POINTER (TRUE), setup_session, do_sniffer_sync_test, teardown_session); g_test_add ("/http2/sniffer/no-sniffer/sync", Test, GINT_TO_POINTER (FALSE), setup_session, do_sniffer_sync_test, teardown_session); g_test_add ("/http2/timeout", Test, NULL, setup_session, do_timeout_test, teardown_session); g_test_add ("/http2/connection-closed", Test, NULL, setup_session, do_connection_closed_test, teardown_session); ret = g_test_run (); g_uri_unref (base_uri); soup_test_server_quit_unref (server); test_cleanup (); return ret; }