1 /* GStreamer
2 * Copyright (C) 2020 Igalia, S.L.
3 * Author: Thibault Saunier <tsaunier@igalia.com>
4
5 * gst-tester.c: tool to launch `.validatetest` files with
6 * TAP compatible output and supporting missing `gst-validate`
7 * application.
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public
20 * License along with this library; if not, write to the
21 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 */
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include <gio/gio.h>
32
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36
37 #ifdef G_OS_UNIX
38 #include <glib-unix.h>
39 #include <sys/wait.h>
40 #elif defined (G_OS_WIN32)
41 #include <windows.h>
42 #include <io.h>
43 #ifndef STDOUT_FILENO
44 #define STDOUT_FILENO 1
45 #endif
46 #define isatty _isatty
47 #endif
48
49 #if defined (G_OS_WIN32)
50 #define VALIDATE_NAME "gst-validate-" GST_API_VERSION ".exe"
51 #else
52 #define VALIDATE_NAME "gst-validate-" GST_API_VERSION
53 #endif
54
55
56 typedef struct
57 {
58 const gchar *testname;
59
60 GSubprocess *subproc;
61 GMainLoop *ml;
62 #if defined(G_OS_UNIX)
63 guint signal_watch_intr_id;
64 #endif
65 gint exitcode;
66 } Application;
67
68 #if defined(G_OS_UNIX)
69 /* As the interrupt handler is dispatched from GMainContext as a GSourceFunc
70 * handler, we can react to this by posting a message. */
71 static gboolean
intr_handler(gpointer user_data)72 intr_handler (gpointer user_data)
73 {
74 Application *app = user_data;
75
76 g_print ("Bail out! Got interupted.\n");
77
78 g_subprocess_force_exit (app->subproc);
79
80 /* remove signal handler */
81 app->signal_watch_intr_id = 0;
82 return G_SOURCE_REMOVE;
83 }
84 #endif
85
86 static void
_run_app(Application * app)87 _run_app (Application * app)
88 {
89 GError *err = NULL;
90 gboolean bailed_out = FALSE, skipped = FALSE;
91 gchar *_stdout = NULL;
92 gboolean is_tty = isatty (STDOUT_FILENO);
93
94 g_print ("1..1\n");
95 g_subprocess_communicate_utf8 (app->subproc, NULL, NULL,
96 is_tty ? NULL : &_stdout, NULL, &err);
97 if (_stdout) {
98 gchar *c;
99 GString *output = g_string_new (NULL);
100
101 for (c = _stdout; *c != '\0'; c++) {
102 g_string_append_c (output, *c);
103 if (!bailed_out && !skipped && *c == '\n' && *(c + 1) != '\0') {
104 if (strstr ((c + 1), "Bail out!") == c + 1) {
105 bailed_out = TRUE;
106 continue;
107 }
108
109 if (strstr ((c + 1), "ok") == c + 1 && strstr ((c + 1), "# SKIP")) {
110 skipped = TRUE;
111 app->exitcode = 0;
112 continue;
113 }
114
115 g_string_append (output, "# ");
116 }
117 }
118 g_print ("# %s\n", output->str);
119 g_string_free (output, TRUE);
120 g_free (_stdout);
121 }
122 #ifdef G_OS_UNIX
123 if (app->signal_watch_intr_id > 0)
124 g_source_remove (app->signal_watch_intr_id);
125 #endif
126
127 if (skipped || bailed_out)
128 goto done;
129
130 if (g_subprocess_get_if_signaled (app->subproc))
131 app->exitcode = g_subprocess_get_term_sig (app->subproc);
132 else
133 app->exitcode = g_subprocess_get_exit_status (app->subproc);
134
135 if (app->exitcode == 0) {
136 g_print ("ok 1 %s\n", app->testname);
137 } else if (app->exitcode == 18) {
138 g_print ("not ok 1 %s # Got a critical report\n", app->testname);
139 } else {
140 g_print ("not ok 1 %s # Unknown reason\n", app->testname);
141 }
142
143 done:
144 g_clear_object (&app->subproc);
145 g_main_loop_quit (app->ml);
146 }
147
148 int
main(int argc,gchar ** argv)149 main (int argc, gchar ** argv)
150 {
151 Application app = { 0, };
152 gchar *dirname;
153 GFile *f;
154 gchar **args = g_new0 (gchar *, argc + 2);
155 gint i;
156 GError *err = NULL;
157 gchar *filename;
158 gboolean is_tty = isatty (STDOUT_FILENO);
159
160 if (argc < 2) {
161 g_print ("1..0\nnot ok # Missing <testfile> argument\n");
162 return 1;
163 }
164
165 app.testname = argv[1];
166
167 dirname = g_path_get_dirname (argv[0]);
168 filename = g_build_filename ("subprojects", "gst-devtools",
169 "validate", "tools", VALIDATE_NAME, NULL);
170 f = g_file_new_for_path (filename);
171 g_free (filename);
172
173 if (g_file_query_exists (f, NULL)) {
174 /* Try to find `gst-validate` as a meson subproject */
175 g_free (args[0]);
176 g_clear_error (&err);
177 args[0] = g_file_get_path (f);
178 g_print ("# Running from meson subproject %s\n", args[0]);
179 }
180 g_free (dirname);
181 g_object_unref (f);
182
183 if (!args[0])
184 args[0] = g_strdup (VALIDATE_NAME);
185 args[1] = g_strdup ("--set-test-file");
186 for (i = 1; i < argc; i++)
187 args[i + 1] = g_strdup (argv[i]);
188
189 app.subproc = g_subprocess_newv ((const char *const *) args,
190 is_tty ? G_SUBPROCESS_FLAGS_STDIN_INHERIT :
191 G_SUBPROCESS_FLAGS_STDOUT_PIPE, &err);
192
193 if (!app.subproc) {
194 g_printerr ("%s %s\n", args[0], err->message);
195 if (g_error_matches (err, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT)) {
196 g_print ("1..0 # Skipped: `" VALIDATE_NAME "` not available\n");
197 return 0;
198 }
199
200 g_print ("1..0\nnot ok # %s\n", err->message);
201 return -1;
202 }
203
204 app.ml = g_main_loop_new (NULL, TRUE);
205
206 #ifdef G_OS_UNIX
207 app.signal_watch_intr_id =
208 g_unix_signal_add (SIGINT, (GSourceFunc) intr_handler, &app);
209 #endif
210
211 /* Running the subprocess in it own thread so that we can properly catch
212 * interuptions in the main thread main loop */
213 g_thread_new ("gst-tester-thread", (GThreadFunc) _run_app, &app);
214 g_main_loop_run (app.ml);
215 g_main_loop_unref (app.ml);
216 g_strfreev (args);
217
218 return app.exitcode;
219 }
220