mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 16:50:47 +00:00
918 lines
26 KiB
C
918 lines
26 KiB
C
/* GStreamer unit tests for the curlhttpsrc element
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <gio/gio.h>
|
|
#include <glib.h>
|
|
#include <glib/gprintf.h>
|
|
|
|
#include <gst/check/gstcheck.h>
|
|
|
|
gboolean redirect = TRUE;
|
|
|
|
static const char **cookies = NULL;
|
|
|
|
typedef struct _GioHttpServer
|
|
{
|
|
guint16 port;
|
|
char *root;
|
|
GSocketService *service;
|
|
guint64 delay;
|
|
} GioHttpServer;
|
|
|
|
typedef struct _HttpHeader
|
|
{
|
|
gchar *header;
|
|
gchar *value;
|
|
} HttpHeader;
|
|
|
|
typedef struct _HttpRequest
|
|
{
|
|
gchar *method;
|
|
gchar *version;
|
|
gchar *path;
|
|
gchar *query;
|
|
gint64 range_start;
|
|
gint64 range_stop;
|
|
GSList *headers;
|
|
} HttpRequest;
|
|
|
|
static GioHttpServer *run_server (void);
|
|
static void stop_server (GioHttpServer * server);
|
|
static guint16 get_port_from_server (GioHttpServer * server);
|
|
|
|
static const gchar *STATUS_OK = "200 OK";
|
|
static const gchar *STATUS_PARTIAL_CONTENT = "206 OK";
|
|
static const gchar *STATUS_MOVED_PERMANENTLY = "301 Moved Permanently";
|
|
static const gchar *STATUS_MOVED_TEMPORARILY = "302 Moved Temporarily";
|
|
static const gchar *STATUS_TEMPORARY_REDIRECT = "307 Temporary Redirect";
|
|
static const gchar *STATUS_FORBIDDEN = "403 Forbidden";
|
|
static const gchar *STATUS_NOT_FOUND = "404 Not Found";
|
|
|
|
static const guint64 http_content_length = G_GUINT64_CONSTANT (1024);
|
|
|
|
static void
|
|
do_get (GioHttpServer * server, const HttpRequest * req, GOutputStream * out)
|
|
{
|
|
gboolean send_error_doc = FALSE;
|
|
const gchar *status = STATUS_OK;
|
|
const gchar *content_type = "application/octet-stream";
|
|
guint64 buflen;
|
|
GString *s;
|
|
gpointer *buf = NULL;
|
|
gsize written = 0;
|
|
|
|
GST_DEBUG ("%s request: \"%s\"", req->method, req->path);
|
|
|
|
if (!strcmp (req->path, "/301"))
|
|
status = STATUS_MOVED_PERMANENTLY;
|
|
else if (!strcmp (req->path, "/302"))
|
|
status = STATUS_MOVED_TEMPORARILY;
|
|
else if (!strcmp (req->path, "/307"))
|
|
status = STATUS_TEMPORARY_REDIRECT;
|
|
else if (!strcmp (req->path, "/403"))
|
|
status = STATUS_FORBIDDEN;
|
|
else if (!strcmp (req->path, "/404"))
|
|
status = STATUS_NOT_FOUND;
|
|
else if (!strcmp (req->path, "/404-with-data")) {
|
|
status = STATUS_NOT_FOUND;
|
|
send_error_doc = TRUE;
|
|
}
|
|
if (g_strcmp0 (req->method, "GET") == 0 &&
|
|
(req->range_start > 0 || req->range_stop >= 0)) {
|
|
status = STATUS_PARTIAL_CONTENT;
|
|
}
|
|
s = g_string_new ("HTTP/");
|
|
g_string_append_printf (s, "%s %s\r\n", req->version, status);
|
|
|
|
if (g_str_has_prefix (status, "30")) {
|
|
g_string_append_printf (s, "Location: %s-redirected\r\n", req->path);
|
|
}
|
|
|
|
if (g_strcmp0 (req->method, "GET") == 0
|
|
|| g_strcmp0 (req->method, "HEAD") == 0) {
|
|
g_string_append_printf (s, "Accept-Ranges: bytes\r\n");
|
|
}
|
|
if (status == STATUS_OK || status == STATUS_PARTIAL_CONTENT || send_error_doc) {
|
|
g_string_append_printf (s, "Content-Type: %s\r\n", content_type);
|
|
buflen = http_content_length;
|
|
if (req->range_start > 0 && req->range_stop >= 0) {
|
|
buflen = 1 + MIN (req->range_stop, buflen - 1) - req->range_start;
|
|
} else if (req->range_start > 0) {
|
|
buflen = buflen - req->range_start;
|
|
} else if (req->range_stop >= 0) {
|
|
buflen = 1 + MIN (req->range_stop, buflen - 1);
|
|
}
|
|
if (buflen != http_content_length) {
|
|
g_string_append_printf (s, "Content-Range: bytes %" G_GINT64_FORMAT "-%"
|
|
G_GINT64_FORMAT "/%" G_GUINT64_FORMAT "\r\n",
|
|
req->range_start,
|
|
req->range_stop >= 0 ? req->range_stop : (http_content_length - 1),
|
|
http_content_length);
|
|
}
|
|
GST_TRACE ("buflen = %" G_GUINT64_FORMAT " range = %" G_GINT64_FORMAT
|
|
" -> %" G_GINT64_FORMAT, buflen, req->range_start, req->range_stop);
|
|
buf = g_malloc (buflen);
|
|
memset (buf, 0, buflen);
|
|
g_string_append_printf (s, "Content-Length: %" G_GUINT64_FORMAT "\r\n",
|
|
buflen);
|
|
}
|
|
|
|
g_string_append (s, "\r\n");
|
|
GST_DEBUG ("Response headers: %u\n%s\n********", (guint) s->len, s->str);
|
|
g_output_stream_write_all (out, s->str, s->len, &written, NULL, NULL);
|
|
fail_if (written != s->len);
|
|
g_string_free (s, TRUE);
|
|
if (buf) {
|
|
g_output_stream_write_all (out, buf, buflen, &written, NULL, NULL);
|
|
fail_if (written != buflen);
|
|
g_free (buf);
|
|
}
|
|
}
|
|
|
|
static void
|
|
send_error (GOutputStream * out, int error_code, const gchar * reason)
|
|
{
|
|
gchar *res;
|
|
|
|
res = g_strdup_printf ("HTTP/1.0 %d %s\r\n\r\n"
|
|
"<html><head><title>%d %s</title></head>"
|
|
"<body>%s</body></html>", error_code, reason, error_code, reason, reason);
|
|
g_output_stream_write_all (out, res, strlen (res), NULL, NULL, NULL);
|
|
g_free (res);
|
|
}
|
|
|
|
static HttpHeader *
|
|
http_header_new (const gchar * header, const gchar * value)
|
|
{
|
|
HttpHeader *ret;
|
|
|
|
ret = g_slice_new (HttpHeader);
|
|
ret->header = g_strdup (header);
|
|
ret->value = g_strdup (value);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
http_header_free (HttpHeader * header)
|
|
{
|
|
if (header) {
|
|
g_free (header->header);
|
|
g_free (header->value);
|
|
g_slice_free (HttpHeader, header);
|
|
}
|
|
}
|
|
|
|
static HttpRequest *
|
|
http_request_new (const gchar * method, const gchar * version,
|
|
const gchar * path, const gchar * query)
|
|
{
|
|
HttpRequest *req;
|
|
|
|
req = g_slice_new0 (HttpRequest);
|
|
req->method = g_strdup (method);
|
|
if (version)
|
|
req->version = g_strdup (version);
|
|
req->path = g_uri_unescape_string (path, NULL);
|
|
if (query)
|
|
req->query = g_strdup (query);
|
|
req->range_start = 0;
|
|
req->range_stop = -1;
|
|
return req;
|
|
}
|
|
|
|
static void
|
|
http_request_free (HttpRequest * req)
|
|
{
|
|
if (!req)
|
|
return;
|
|
g_free (req->method);
|
|
g_free (req->version);
|
|
g_free (req->path);
|
|
g_free (req->query);
|
|
if (req->headers)
|
|
g_slist_free_full (req->headers, (GDestroyNotify) http_header_free);
|
|
g_slice_free (HttpRequest, req);
|
|
}
|
|
|
|
static gboolean
|
|
server_callback (GThreadedSocketService * service,
|
|
GSocketConnection * connection,
|
|
GSocketListener * listener, gpointer user_data)
|
|
{
|
|
GioHttpServer *server = (GioHttpServer *) user_data;
|
|
GOutputStream *out;
|
|
GInputStream *in;
|
|
GDataInputStream *data = NULL;
|
|
gchar *line = NULL, *escaped, *tmp;
|
|
HttpRequest *req = NULL;
|
|
gboolean done = FALSE;
|
|
gchar *version = NULL, *query;
|
|
|
|
in = g_io_stream_get_input_stream (G_IO_STREAM (connection));
|
|
out = g_io_stream_get_output_stream (G_IO_STREAM (connection));
|
|
|
|
data = g_data_input_stream_new (in);
|
|
|
|
g_data_input_stream_set_newline_type (data, G_DATA_STREAM_NEWLINE_TYPE_ANY);
|
|
|
|
line = g_data_input_stream_read_line (data, NULL, NULL, NULL);
|
|
|
|
if (line == NULL) {
|
|
send_error (out, 400, "Invalid request");
|
|
goto out;
|
|
}
|
|
|
|
tmp = strchr (line, ' ');
|
|
if (!tmp) {
|
|
send_error (out, 400, "Invalid request");
|
|
goto out;
|
|
}
|
|
*tmp = '\0';
|
|
escaped = tmp + 1;
|
|
|
|
tmp = strchr (escaped, ' ');
|
|
if (tmp != NULL) {
|
|
*tmp = 0;
|
|
version = tmp + 6; /* skip "HTTP/" from version field */
|
|
}
|
|
|
|
query = strchr (escaped, '?');
|
|
if (query != NULL) {
|
|
*query = '\0';
|
|
query++;
|
|
}
|
|
|
|
req = http_request_new (line, version, escaped, query);
|
|
|
|
GST_TRACE ("%s %s HTTP/%s", req->method, req->path, req->version);
|
|
|
|
while (!done) {
|
|
g_free (line);
|
|
line = g_data_input_stream_read_line (data, NULL, NULL, NULL);
|
|
if (!line) {
|
|
send_error (out, 400, "Invalid request");
|
|
goto out;
|
|
}
|
|
tmp = strchr (line, ':');
|
|
if (!tmp) {
|
|
/* reached end of HTTP request headers */
|
|
done = TRUE;
|
|
continue;
|
|
}
|
|
*tmp = '\0';
|
|
do {
|
|
++tmp;
|
|
} while (*tmp == ' ');
|
|
GST_TRACE ("Request header: %s: %s", line, tmp);
|
|
req->headers = g_slist_append (req->headers, http_header_new (line, tmp));
|
|
if (g_ascii_strcasecmp (line, "range") == 0) {
|
|
gchar *start, *end;
|
|
start = strchr (tmp, '=');
|
|
if (!start) {
|
|
GST_ERROR ("Invalid range request: %s", tmp);
|
|
send_error (out, 400, "Invalid request");
|
|
goto out;
|
|
}
|
|
start++;
|
|
end = strchr (start, '-');
|
|
if (!end) {
|
|
GST_ERROR ("Invalid range request: %s", tmp);
|
|
send_error (out, 400, "Invalid request");
|
|
goto out;
|
|
}
|
|
*end = '\0';
|
|
end++;
|
|
if (*start != '\0') {
|
|
req->range_start = atoi (start);
|
|
}
|
|
if (*end != '\0') {
|
|
req->range_stop = atoi (end);
|
|
}
|
|
GST_DEBUG ("RANGE request %" G_GINT64_FORMAT " -> %" G_GINT64_FORMAT,
|
|
req->range_start, req->range_stop);
|
|
}
|
|
}
|
|
if (server->delay) {
|
|
g_usleep (server->delay);
|
|
}
|
|
do_get (server, req, out);
|
|
|
|
out:
|
|
g_free (line);
|
|
http_request_free (req);
|
|
if (data)
|
|
g_object_unref (data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static guint16
|
|
get_port_from_server (GioHttpServer * server)
|
|
{
|
|
fail_if (server == NULL);
|
|
return server->port;
|
|
}
|
|
|
|
static GioHttpServer *
|
|
run_server (void)
|
|
{
|
|
GioHttpServer *server;
|
|
GError *error = NULL;
|
|
|
|
server = g_slice_new0 (GioHttpServer);
|
|
server->service = g_threaded_socket_service_new (10);
|
|
server->port =
|
|
g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (server->service),
|
|
NULL, &error);
|
|
fail_if (server->port == 0);
|
|
g_signal_connect (server->service, "run", G_CALLBACK (server_callback),
|
|
server);
|
|
|
|
GST_DEBUG ("HTTP server listening on port %u", server->port);
|
|
|
|
/* check if we can connect to our local http server */
|
|
{
|
|
GSocketConnection *conn;
|
|
GSocketClient *client;
|
|
|
|
client = g_socket_client_new ();
|
|
g_socket_client_set_timeout (client, 2);
|
|
conn =
|
|
g_socket_client_connect_to_host (client, "127.0.0.1", server->port,
|
|
NULL, NULL);
|
|
if (conn == NULL) {
|
|
GST_INFO ("Couldn't connect to 127.0.0.1:%u", server->port);
|
|
g_object_unref (client);
|
|
g_slice_free (GioHttpServer, server);
|
|
return NULL;
|
|
}
|
|
|
|
g_object_unref (conn);
|
|
g_object_unref (client);
|
|
}
|
|
|
|
return server;
|
|
}
|
|
|
|
static void
|
|
stop_server (GioHttpServer * server)
|
|
{
|
|
fail_if (server == NULL);
|
|
GST_DEBUG ("Stopping server...");
|
|
g_socket_service_stop (server->service);
|
|
g_socket_listener_close (G_SOCKET_LISTENER (server->service));
|
|
g_object_unref (server->service);
|
|
g_slice_free (GioHttpServer, server);
|
|
GST_DEBUG ("Server stopped");
|
|
}
|
|
|
|
static void
|
|
handoff_cb (GstElement * fakesink, GstBuffer * buf, GstPad * pad,
|
|
GstBuffer ** p_outbuf)
|
|
{
|
|
GST_LOG ("handoff, buf = %p", buf);
|
|
if (*p_outbuf == NULL)
|
|
*p_outbuf = gst_buffer_ref (buf);
|
|
}
|
|
|
|
static gboolean
|
|
run_test (const gchar * path, gint expected_status_code,
|
|
gboolean has_body, gboolean has_error)
|
|
{
|
|
GstStateChangeReturn ret;
|
|
GstElement *pipe, *src, *sink;
|
|
GstBuffer *buf = NULL;
|
|
GstMessage *msg;
|
|
gchar *url;
|
|
gboolean res = FALSE;
|
|
GioHttpServer *server;
|
|
guint port;
|
|
gboolean done = FALSE;
|
|
|
|
server = run_server ();
|
|
fail_if (server == NULL, "Failed to start up HTTP server");
|
|
|
|
pipe = gst_pipeline_new (NULL);
|
|
fail_unless (pipe != NULL);
|
|
|
|
src = gst_element_factory_make ("curlhttpsrc", NULL);
|
|
fail_unless (src != NULL);
|
|
|
|
sink = gst_element_factory_make ("fakesink", NULL);
|
|
fail_unless (sink != NULL);
|
|
|
|
gst_bin_add (GST_BIN (pipe), src);
|
|
gst_bin_add (GST_BIN (pipe), sink);
|
|
fail_unless (gst_element_link (src, sink));
|
|
|
|
port = get_port_from_server (server);
|
|
url = g_strdup_printf ("http://127.0.0.1:%u%s", port, path);
|
|
fail_unless (url != NULL);
|
|
g_object_set (src, "location", url, NULL);
|
|
g_free (url);
|
|
|
|
g_object_set (src, "automatic-redirect", redirect, NULL);
|
|
if (cookies != NULL)
|
|
g_object_set (src, "cookies", cookies, NULL);
|
|
g_object_set (sink, "signal-handoffs", TRUE, NULL);
|
|
/*g_object_set (sink, "dump", TRUE, NULL); */
|
|
g_signal_connect (sink, "preroll-handoff", G_CALLBACK (handoff_cb), &buf);
|
|
|
|
ret = gst_element_set_state (pipe, GST_STATE_PAUSED);
|
|
if (ret != GST_STATE_CHANGE_ASYNC) {
|
|
GST_DEBUG ("failed to start up curl http src, ret = %d", ret);
|
|
goto done;
|
|
}
|
|
|
|
gst_element_set_state (pipe, GST_STATE_PLAYING);
|
|
while (!done) {
|
|
msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
|
|
GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);
|
|
GST_DEBUG ("Message: %" GST_PTR_FORMAT, msg);
|
|
switch (GST_MESSAGE_TYPE (msg)) {
|
|
case GST_MESSAGE_ERROR:{
|
|
gchar *debug = NULL;
|
|
GError *err = NULL;
|
|
gint rc = -1;
|
|
const GstStructure *details = NULL;
|
|
|
|
gst_message_parse_error (msg, &err, &debug);
|
|
gst_message_parse_error_details (msg, &details);
|
|
GST_DEBUG ("debug object: %s", debug);
|
|
GST_DEBUG ("err->message: \"%s\"", err->message);
|
|
GST_DEBUG ("err->details: %" GST_PTR_FORMAT, details);
|
|
if (g_str_has_suffix (err->message, "Not Found"))
|
|
rc = 404;
|
|
else if (g_str_has_suffix (err->message, "Forbidden"))
|
|
rc = 403;
|
|
else if (g_str_has_suffix (err->message, "Unauthorized"))
|
|
rc = 401;
|
|
else if (g_str_has_suffix (err->message, "Found"))
|
|
rc = 302;
|
|
if (details) {
|
|
if (gst_structure_has_field_typed (details, "http-status-code",
|
|
G_TYPE_UINT)) {
|
|
guint code = 0;
|
|
gst_structure_get_uint (details, "http-status-code", &code);
|
|
rc = code;
|
|
}
|
|
}
|
|
g_error_free (err);
|
|
g_free (debug);
|
|
fail_unless (has_error);
|
|
GST_DEBUG ("Got HTTP error %d, expected_status_code %d", rc,
|
|
expected_status_code);
|
|
res = (rc == expected_status_code);
|
|
done = TRUE;
|
|
}
|
|
break;
|
|
case GST_MESSAGE_EOS:
|
|
if (!has_error)
|
|
done = TRUE;
|
|
break;
|
|
default:
|
|
fail_if (TRUE, "Unexpected GstMessage");
|
|
break;
|
|
}
|
|
gst_message_unref (msg);
|
|
}
|
|
|
|
/* don't wait for more than 10 seconds */
|
|
ret = gst_element_get_state (pipe, NULL, NULL, 10 * GST_SECOND);
|
|
GST_LOG ("ret = %u", ret);
|
|
|
|
if (buf != NULL) {
|
|
fail_unless (has_body);
|
|
/* we want to test the buffer offset, nothing else; if there's a failure
|
|
* it might be for lots of reasons (no network connection, whatever), we're
|
|
* not interested in those */
|
|
GST_DEBUG ("buffer offset = %" G_GUINT64_FORMAT, GST_BUFFER_OFFSET (buf));
|
|
|
|
/* first buffer should have a 0 offset */
|
|
fail_unless (GST_BUFFER_OFFSET (buf) == 0);
|
|
gst_buffer_unref (buf);
|
|
}
|
|
res = TRUE;
|
|
|
|
done:
|
|
|
|
gst_element_set_state (pipe, GST_STATE_NULL);
|
|
gst_object_unref (pipe);
|
|
stop_server (server);
|
|
return res;
|
|
}
|
|
|
|
GST_START_TEST (test_first_buffer_has_offset)
|
|
{
|
|
fail_unless (run_test ("/", 200, TRUE, FALSE));
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_not_found)
|
|
{
|
|
fail_unless (run_test ("/404", 404, FALSE, TRUE));
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_not_found_with_data)
|
|
{
|
|
fail_unless (run_test ("/404-with-data", 404, TRUE, TRUE));
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_forbidden)
|
|
{
|
|
fail_unless (run_test ("/403", 403, FALSE, TRUE));
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_redirect_no)
|
|
{
|
|
redirect = FALSE;
|
|
fail_unless (run_test ("/302", 302, FALSE, FALSE));
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_redirect_yes)
|
|
{
|
|
redirect = TRUE;
|
|
fail_unless (run_test ("/302", 200, TRUE, FALSE));
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_cookies)
|
|
{
|
|
static const char *biscotti[] = { "delacre=yummie", "koekje=lu", NULL };
|
|
gboolean res;
|
|
|
|
cookies = biscotti;
|
|
res = run_test ("/", 200, TRUE, FALSE);
|
|
cookies = NULL;
|
|
fail_unless (res);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
typedef struct _HttpSrcTestDownloader
|
|
{
|
|
GstElement *bin;
|
|
GstElement *src;
|
|
GstElement *sink;
|
|
GioHttpServer *server;
|
|
guint count;
|
|
gint64 start_position;
|
|
gint64 stop_position;
|
|
} HttpSrcTestDownloader;
|
|
|
|
static gboolean
|
|
move_element_to_ready (gpointer user_data)
|
|
{
|
|
HttpSrcTestDownloader *tp = (HttpSrcTestDownloader *) user_data;
|
|
|
|
GST_TRACE_OBJECT (tp->bin, "Move bin to READY state");
|
|
gst_element_set_state (tp->bin, GST_STATE_READY);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static GstPadProbeReturn
|
|
src_event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
HttpSrcTestDownloader *tp = (HttpSrcTestDownloader *) user_data;
|
|
GstEvent *event;
|
|
|
|
event = gst_pad_probe_info_get_event (info);
|
|
|
|
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
|
|
GST_DEBUG_OBJECT (tp->bin, "finished last request");
|
|
g_idle_add (move_element_to_ready, tp);
|
|
}
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static void
|
|
start_next_download (HttpSrcTestDownloader * tp)
|
|
{
|
|
gchar *url;
|
|
|
|
url = g_strdup_printf ("http://127.0.0.1:%u/multi/%s-%u",
|
|
tp->server->port, GST_ELEMENT_NAME (tp->bin), tp->count);
|
|
fail_unless (url != NULL);
|
|
GST_DEBUG_OBJECT (tp->bin, "Start next request for: %s", url);
|
|
g_object_set (tp->src, "location", url, NULL);
|
|
g_free (url);
|
|
if (tp->start_position != 0 || tp->stop_position != -1) {
|
|
/* Send the seek event to the uri_handler, as the other pipeline elements
|
|
* can't handle it when READY. */
|
|
GST_DEBUG ("Range get %" G_GINT64_FORMAT " -> %" G_GINT64_FORMAT,
|
|
tp->start_position, tp->stop_position);
|
|
fail_if (!gst_element_send_event (tp->src, gst_event_new_seek (1.0,
|
|
GST_FORMAT_BYTES, (GstSeekFlags) GST_SEEK_FLAG_FLUSH,
|
|
GST_SEEK_TYPE_SET, tp->start_position, GST_SEEK_TYPE_SET,
|
|
tp->stop_position + 1)),
|
|
"Source element can't handle range requests");
|
|
}
|
|
fail_unless (gst_element_sync_state_with_parent (tp->bin));
|
|
}
|
|
|
|
static HttpSrcTestDownloader *
|
|
test_curl_http_src_downloader_new (const gchar * name, guint64 delay)
|
|
{
|
|
HttpSrcTestDownloader *tp;
|
|
gchar *url;
|
|
GstPad *src_pad;
|
|
|
|
tp = g_slice_new0 (HttpSrcTestDownloader);
|
|
tp->server = run_server ();
|
|
fail_if (tp->server == NULL, "Failed to start up HTTP server");
|
|
tp->server->delay = delay;
|
|
tp->start_position = 0;
|
|
tp->stop_position = -1;
|
|
|
|
tp->src = gst_element_factory_make ("curlhttpsrc", NULL);
|
|
fail_unless (tp->src != NULL);
|
|
|
|
url = g_strdup_printf ("http://127.0.0.1:%u/multi/%s-0", tp->server->port,
|
|
name);
|
|
fail_unless (url != NULL);
|
|
g_object_set (tp->src, "location", url, NULL);
|
|
g_free (url);
|
|
|
|
src_pad = gst_element_get_static_pad (tp->src, "src");
|
|
fail_unless (src_pad != NULL);
|
|
gst_pad_add_probe (src_pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
|
|
src_event_probe, tp, NULL);
|
|
gst_object_unref (src_pad);
|
|
|
|
tp->sink = gst_element_factory_make ("fakesink", NULL);
|
|
fail_unless (tp->sink != NULL);
|
|
|
|
tp->bin = gst_bin_new (name);
|
|
fail_unless (tp->bin != NULL);
|
|
|
|
gst_bin_add (GST_BIN (tp->bin), tp->src);
|
|
gst_bin_add (GST_BIN (tp->bin), tp->sink);
|
|
fail_unless (gst_element_link (tp->src, tp->sink));
|
|
gst_element_set_locked_state (GST_ELEMENT (tp->bin), TRUE);
|
|
|
|
return tp;
|
|
}
|
|
|
|
static void
|
|
test_curl_http_src_downloader_free (HttpSrcTestDownloader * downloader)
|
|
{
|
|
gst_element_set_state (downloader->bin, GST_STATE_NULL);
|
|
stop_server (downloader->server);
|
|
g_slice_free (HttpSrcTestDownloader, downloader);
|
|
}
|
|
|
|
typedef struct _MultipleHttpRequestsContext
|
|
{
|
|
GMainLoop *loop;
|
|
GstElement *pipe;
|
|
HttpSrcTestDownloader *downloader1;
|
|
HttpSrcTestDownloader *downloader2;
|
|
gboolean failed;
|
|
} MultipleHttpRequestsContext;
|
|
|
|
static gboolean
|
|
bus_message (GstBus * bus, GstMessage * msg, gpointer user_data)
|
|
{
|
|
MultipleHttpRequestsContext *context =
|
|
(MultipleHttpRequestsContext *) user_data;
|
|
gchar *debug;
|
|
GError *err;
|
|
GstState newstate;
|
|
GstState pending;
|
|
const GstStructure *details;
|
|
|
|
GST_TRACE ("Message: %" GST_PTR_FORMAT, msg);
|
|
switch (GST_MESSAGE_TYPE (msg)) {
|
|
case GST_MESSAGE_STATE_CHANGED:
|
|
gst_message_parse_state_changed (msg, NULL, &newstate, &pending);
|
|
if (newstate == GST_STATE_PLAYING && pending == GST_STATE_VOID_PENDING &&
|
|
GST_MESSAGE_SRC (msg) == GST_OBJECT (context->pipe)) {
|
|
GST_DEBUG ("Test ready to start");
|
|
start_next_download (context->downloader1);
|
|
if (context->downloader2)
|
|
start_next_download (context->downloader2);
|
|
} else if (newstate == GST_STATE_READY
|
|
&& pending == GST_STATE_VOID_PENDING) {
|
|
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (context->downloader1->bin)) {
|
|
if (++context->downloader1->count < 20) {
|
|
start_next_download (context->downloader1);
|
|
}
|
|
} else if (context->downloader2 && GST_MESSAGE_SRC (msg) ==
|
|
GST_OBJECT (context->downloader2->bin)) {
|
|
if (++context->downloader2->count < 20) {
|
|
start_next_download (context->downloader2);
|
|
}
|
|
}
|
|
if (context->downloader1->count == 20 &&
|
|
(context->downloader2 == NULL
|
|
|| context->downloader2->count == 20)) {
|
|
g_main_loop_quit (context->loop);
|
|
}
|
|
}
|
|
break;
|
|
case GST_MESSAGE_ERROR:
|
|
debug = NULL;
|
|
err = NULL;
|
|
details = NULL;
|
|
gst_message_parse_error (msg, &err, &debug);
|
|
gst_message_parse_error_details (msg, &details);
|
|
GST_DEBUG ("err->debug: %s", debug);
|
|
GST_DEBUG ("err->message: \"%s\"", err->message);
|
|
GST_DEBUG ("err->details: %" GST_PTR_FORMAT, details);
|
|
g_error_free (err);
|
|
g_free (debug);
|
|
context->failed = TRUE;
|
|
g_main_loop_quit (context->loop);
|
|
break;
|
|
case GST_MESSAGE_EOS:
|
|
if (context->downloader1->count == 20 &&
|
|
(context->downloader2 == NULL || context->downloader2->count == 20)) {
|
|
g_main_loop_quit (context->loop);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* test_multiple_http_requests tries to reproduce the way in which
|
|
* GstAdaptiveDemux makes use of URI source elements. GstAdaptiveDemux
|
|
* creates a bin with the httpsrc element and a queue element and sets the
|
|
* locked state of that bin to TRUE, so that it does not follow the state
|
|
* transitions of its parent. It then moves this bin to the PLAYING state
|
|
* to start each download and back to READY when the download completes.
|
|
*/
|
|
GST_START_TEST (test_multiple_http_requests)
|
|
{
|
|
GstStateChangeReturn ret;
|
|
MultipleHttpRequestsContext context;
|
|
guint watch_id;
|
|
GstBus *bus;
|
|
|
|
context.loop = g_main_loop_new (NULL, FALSE);
|
|
context.downloader1 =
|
|
test_curl_http_src_downloader_new ("bin1", 5 * G_USEC_PER_SEC / 1000);
|
|
fail_unless (context.downloader1 != NULL);
|
|
context.downloader2 =
|
|
test_curl_http_src_downloader_new ("bin2", 7 * G_USEC_PER_SEC / 1000);
|
|
fail_unless (context.downloader2 != NULL);
|
|
|
|
context.pipe = gst_pipeline_new (NULL);
|
|
fail_unless (context.pipe != NULL);
|
|
|
|
gst_bin_add (GST_BIN_CAST (context.pipe), context.downloader1->bin);
|
|
gst_bin_add (GST_BIN_CAST (context.pipe), context.downloader2->bin);
|
|
|
|
bus = gst_pipeline_get_bus (GST_PIPELINE (context.pipe));
|
|
watch_id = gst_bus_add_watch (bus, bus_message, &context);
|
|
gst_object_unref (bus);
|
|
|
|
GST_DEBUG ("Start pipeline playing");
|
|
ret = gst_element_set_state (context.pipe, GST_STATE_PLAYING);
|
|
fail_unless (ret == GST_STATE_CHANGE_ASYNC
|
|
|| ret == GST_STATE_CHANGE_SUCCESS);
|
|
|
|
g_main_loop_run (context.loop);
|
|
g_source_remove (watch_id);
|
|
test_curl_http_src_downloader_free (context.downloader1);
|
|
test_curl_http_src_downloader_free (context.downloader2);
|
|
gst_element_set_state (context.pipe, GST_STATE_NULL);
|
|
gst_object_unref (context.pipe);
|
|
g_main_loop_unref (context.loop);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
typedef struct _DataProbeResult
|
|
{
|
|
guint64 received;
|
|
} DataProbeResult;
|
|
|
|
static GstPadProbeReturn
|
|
src_data_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
DataProbeResult *dpr = (DataProbeResult *) user_data;
|
|
GstBuffer *buf;
|
|
|
|
if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_BUFFER) {
|
|
buf = GST_PAD_PROBE_INFO_BUFFER (info);
|
|
dpr->received += gst_buffer_get_size (buf);
|
|
}
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
GST_START_TEST (test_range_get)
|
|
{
|
|
GstStateChangeReturn ret;
|
|
MultipleHttpRequestsContext context;
|
|
guint watch_id;
|
|
GstBus *bus;
|
|
GstPad *src_pad;
|
|
gulong probe_id;
|
|
DataProbeResult dpr;
|
|
|
|
context.loop = g_main_loop_new (NULL, FALSE);
|
|
context.downloader1 =
|
|
test_curl_http_src_downloader_new ("bin1", 5 * G_USEC_PER_SEC / 1000);
|
|
fail_unless (context.downloader1 != NULL);
|
|
context.downloader1->start_position = 128;
|
|
context.downloader1->stop_position = 255;
|
|
src_pad = gst_element_get_static_pad (context.downloader1->src, "src");
|
|
fail_unless (src_pad != NULL);
|
|
dpr.received = 0;
|
|
probe_id = gst_pad_add_probe (src_pad, GST_PAD_PROBE_TYPE_BUFFER,
|
|
src_data_probe, &dpr, NULL);
|
|
fail_unless (probe_id > 0);
|
|
context.downloader2 = NULL;
|
|
|
|
context.pipe = gst_pipeline_new (NULL);
|
|
fail_unless (context.pipe != NULL);
|
|
|
|
gst_bin_add (GST_BIN_CAST (context.pipe), context.downloader1->bin);
|
|
|
|
bus = gst_pipeline_get_bus (GST_PIPELINE (context.pipe));
|
|
watch_id = gst_bus_add_watch (bus, bus_message, &context);
|
|
gst_object_unref (bus);
|
|
|
|
GST_DEBUG ("Start pipeline playing");
|
|
ret = gst_element_set_state (context.pipe, GST_STATE_PLAYING);
|
|
fail_unless (ret == GST_STATE_CHANGE_ASYNC
|
|
|| ret == GST_STATE_CHANGE_SUCCESS);
|
|
|
|
g_main_loop_run (context.loop);
|
|
fail_unless_equals_uint64 (dpr.received,
|
|
1 + context.downloader1->stop_position -
|
|
context.downloader1->start_position);
|
|
g_source_remove (watch_id);
|
|
gst_pad_remove_probe (src_pad, probe_id);
|
|
gst_object_unref (src_pad);
|
|
test_curl_http_src_downloader_free (context.downloader1);
|
|
gst_element_set_state (context.pipe, GST_STATE_NULL);
|
|
gst_object_unref (context.pipe);
|
|
g_main_loop_unref (context.loop);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
static Suite *
|
|
curlhttpsrc_suite (void)
|
|
{
|
|
TCase *tc_chain;
|
|
Suite *s;
|
|
|
|
/* we don't support exceptions from the proxy, so just unset the environment
|
|
* variable - in case it's set in the test environment it would otherwise
|
|
* prevent us from connecting to localhost (like jenkins.qa.ubuntu.com) */
|
|
g_unsetenv ("http_proxy");
|
|
|
|
s = suite_create ("curlhttpsrc");
|
|
tc_chain = tcase_create ("general");
|
|
|
|
suite_add_tcase (s, tc_chain);
|
|
tcase_add_test (tc_chain, test_first_buffer_has_offset);
|
|
tcase_add_test (tc_chain, test_redirect_yes);
|
|
tcase_add_test (tc_chain, test_redirect_no);
|
|
tcase_add_test (tc_chain, test_not_found);
|
|
tcase_add_test (tc_chain, test_not_found_with_data);
|
|
tcase_add_test (tc_chain, test_forbidden);
|
|
tcase_add_test (tc_chain, test_cookies);
|
|
tcase_add_test (tc_chain, test_multiple_http_requests);
|
|
tcase_add_test (tc_chain, test_range_get);
|
|
|
|
return s;
|
|
}
|
|
|
|
GST_CHECK_MAIN (curlhttpsrc);
|