validate: scenario: Add an action type to start the http scenario

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8128>
This commit is contained in:
Thibault Saunier 2024-12-10 13:00:32 -03:00 committed by GStreamer Marge Bot
parent 2548503898
commit 0259d46fd3
4 changed files with 167 additions and 4 deletions

View file

@ -281,6 +281,9 @@ def get_subprocess_env(options, gst_version):
prepend_env_var(env, "GST_VALIDATE_APPS_DIR", os.path.normpath(
"%s/subprojects/gst-examples/webrtc/check/validate/apps" %
SCRIPTDIR), options.sysroot)
env["GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH"] = os.path.normpath(
"%s/subprojects/gst-devtools/validate/launcher/RangeHTTPServer.py" %
SCRIPTDIR)
if options.wine:
return get_wine_subprocess_env(options, env)

View file

@ -44,6 +44,7 @@
#include "config.h"
#endif
#include <stdio.h>
#include <gst/gst.h>
#include <gio/gio.h>
#include <string.h>
@ -187,6 +188,12 @@ typedef struct
GstValidateAction *action;
} GstValidateSeekInformation;
typedef struct
{
GSubprocess *subprocess;
gint port;
} HTTPServer;
/* GstValidateScenario is not really thread safe and
* everything should be done from the thread GstValidate
* was inited from, unless stated otherwise.
@ -282,6 +289,8 @@ struct _GstValidateScenarioPrivate
guint segments_needed;
GMainContext *context;
GArray /*< HTTPServer> */ * http_servers;
};
typedef struct KeyFileGroupName
@ -5735,6 +5744,32 @@ one_actions_scenario_max:
}
}
static void
gst_validate_scenario_stop_http_servers (GstValidateScenario * scenario)
{
if (scenario->priv->http_servers) {
for (guint i = 0; i < scenario->priv->http_servers->len; i++) {
HTTPServer *server =
&g_array_index (scenario->priv->http_servers, HTTPServer, i);
if (server->subprocess) {
GError *error = NULL;
g_subprocess_force_exit (server->subprocess);
if (!g_subprocess_wait_check (server->subprocess, NULL, &error)) {
GST_WARNING_OBJECT (scenario,
"Error waiting for subprocess to exit: %s",
error ? error->message : "unknown error");
g_clear_error (&error);
}
g_clear_object (&server->subprocess);
}
}
g_array_free (scenario->priv->http_servers, TRUE);
scenario->priv->http_servers = NULL;
}
}
static void
gst_validate_scenario_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
@ -5936,6 +5971,8 @@ gst_validate_scenario_finalize (GObject * object)
gst_structure_free (self->description);
g_mutex_clear (&priv->lock);
gst_validate_scenario_stop_http_servers (self);
G_OBJECT_CLASS (gst_validate_scenario_parent_class)->finalize (object);
}
@ -7105,10 +7142,10 @@ _execute_stop (GstValidateScenario * scenario, GstValidateAction * action)
SCENARIO_UNLOCK (scenario);
gst_validate_scenario_check_dropped (scenario);
gst_bus_post (bus,
gst_message_new_request_state (GST_OBJECT_CAST (scenario),
GST_STATE_NULL));
gst_validate_scenario_stop_http_servers (scenario);
gst_object_unref (bus);
gst_object_unref (pipeline);
@ -7471,6 +7508,86 @@ done:
return res;
}
static GstValidateExecuteActionReturn
_execute_start_http_server (GstValidateScenario * scenario,
GstValidateAction * action)
{
GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
const gchar *server_path, *working_dir;
GError *err = NULL;
HTTPServer server = { 0 };
GInputStream *stdout_stream = NULL;
GSubprocess *subprocess = NULL;
gint port = 0;
server_path = g_getenv ("GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH");
REPORT_UNLESS (server_path, done,
"GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH not set");
REPORT_UNLESS (g_file_test (server_path,
G_FILE_TEST_IS_REGULAR), done,
"HTTP server script not found at: %s", server_path);
working_dir =
gst_structure_get_string (action->structure, "working-directory");
REPORT_UNLESS (working_dir, done, "working-directory not specified");
REPORT_UNLESS (g_file_test (working_dir,
G_FILE_TEST_IS_DIR), done,
"working-directory '%s' doesn't exist", working_dir);
gchar const *argv[3] = { server_path, "0", NULL };
gboolean no_pipe = FALSE;
gst_structure_get_boolean (action->structure, "no-pipe", &no_pipe);
GSubprocessLauncher *launcher =
g_subprocess_launcher_new (no_pipe ? G_SUBPROCESS_FLAGS_STDOUT_PIPE :
G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE);
g_subprocess_launcher_set_cwd (launcher, working_dir);
subprocess =
g_subprocess_launcher_spawnv (launcher, (const gchar * const *) argv,
&err);
g_object_unref (launcher);
REPORT_UNLESS (subprocess, done,
"Failed to start HTTP server: %s", err->message);
stdout_stream = g_subprocess_get_stdout_pipe (subprocess);
GDataInputStream *data_stream = g_data_input_stream_new (stdout_stream);
gchar *line = g_data_input_stream_read_line (data_stream, NULL, NULL, &err);
g_object_unref (data_stream);
REPORT_UNLESS (err == NULL, done, "Failed to read server output: %s",
err->message);
REPORT_UNLESS (sscanf (line, "PORT: %d", &port) == 1, done,
"Failed to parse port number from server output: %s", line);
server.port = port;
server.subprocess = subprocess;
if (!scenario->priv->http_servers)
scenario->priv->http_servers =
g_array_new (FALSE, FALSE, sizeof (HTTPServer));
g_array_append_val (scenario->priv->http_servers, server);
gint i = 1;
gchar *port_varname = g_strdup ("http_server_port");
while (gst_structure_has_field (scenario->priv->vars, port_varname)) {
g_free (port_varname);
port_varname = g_strdup_printf ("http_server_port_%d", i++);
}
gst_structure_set (scenario->priv->vars, port_varname,
G_TYPE_INT, port, NULL);
g_free (port_varname);
done:
if (subprocess && !server.subprocess) {
g_object_unref (subprocess);
}
g_clear_error (&err);
return res;
}
/**
* gst_validate_action_get_scenario:
@ -9018,6 +9135,40 @@ register_action_types (void)
" using the `run-on-sub-pipeline` action type.",
GST_VALIDATE_ACTION_TYPE_NONE);
REGISTER_ACTION_TYPE("start-http-server", _execute_start_http_server,
((GstValidateActionParameter[]) {
{
.name = "working-directory",
.description = "Servers current working directory",
.mandatory = TRUE,
.types = "string",
NULL
},
{
.name = "no-pipe",
.description = "Do not pipe http server stderr/stdout ",
.mandatory = FALSE,
.types = "bool",
.def = "false",
NULL,
},
{NULL}
}),
"Start an HTTP server in a separate process to serve files from $(working-directory).\n"
"The server is started on any available port and the action sets the `$(http_server_port)`\n"
"variable so it can be used afterward. For subsequent servers started, the\n"
"variable names become `http_server_port_1`, `http_server_port_2`, etc.\n\n"
"The server implementation must be specified through the GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH\n"
"environment variable. By default, it uses our `RangeHTTPServer.py` implementation which\n"
"provides support for HTTP range requests and directory listing as well as specific POST requests\n"
"for testing purposes (check the file for details).\n\n"
"Example:\n"
"```\n"
"- start-http-server, working-directory=/path/to/media/files\n"
"- set-property, playbin::uri=\"http://127.0.0.1:$(http_server_port)/video.mp4\"\n"
"```\n",
GST_VALIDATE_ACTION_TYPE_NONE);
/* Internal actions types to test the validate scenario implementation */
REGISTER_ACTION_TYPE("priv_check-action-type-calls",
_execute_check_action_type_calls, NULL, NULL, 0);

View file

@ -50,8 +50,15 @@ import time
_bandwidth = 0
def debug(msg):
print(f'msg: {msg}', file=sys.stderr)
class ThreadingSimpleServer(ThreadingMixIn, http.server.HTTPServer):
pass
def server_bind(self):
super().server_bind()
print(f"PORT: {self.server_port}")
sys.stdout.flush()
class RangeHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
@ -72,7 +79,7 @@ class RangeHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
"""Serve a GET request."""
f, start_range, end_range = self.send_head()
print("Got values of {} and {}".format(start_range, end_range))
debug("Got values of {} and {}".format(start_range, end_range))
if f:
f.seek(start_range, 0)
chunk = 0x1000
@ -165,7 +172,7 @@ class RangeHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
self.send_header("Content-Length", end_range - start_range)
self.end_headers()
print("Sending bytes {} to {}...".format(start_range, end_range))
debug("Sending bytes {} to {}...".format(start_range, end_range))
return (f, start_range, end_range)
def list_directory(self, path):
@ -289,3 +296,4 @@ def test(handler_class=RangeHTTPRequestHandler, server_class=http.server.HTTPSer
if __name__ == "__main__":
httpd = ThreadingSimpleServer(("0.0.0.0", int(sys.argv[1])), RangeHTTPRequestHandler)
httpd.serve_forever()
print("EXIT")

View file

@ -2352,6 +2352,7 @@ class _TestsLauncher(Loggable):
self._stop_server()
def run_tests(self):
os.environ["GST_VALIDATE_LAUNCHER_HTTP_SERVER_PATH"] = os.path.join(os.path.dirname(__file__), "RangeHTTPServer.py")
r = 0
try:
self._start_server()