Win32: Add test for safe stdio flushing

Test the two-phase flushing implemented in app_profile_dep.
This commit is contained in:
Luca Bacci
2025-11-14 16:16:58 +01:00
parent 15eeef71e3
commit e1a7b54f8b

View File

@@ -25,7 +25,13 @@
#include <glib.h>
#include <gprintf.h>
#include <glib/gstdio.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <fcntl.h>
#include <io.h>
#include <windows.h>
#include <winnt.h> /* for RTL_OSVERSIONINFO */
@@ -249,6 +255,157 @@ test_manifest_os_compatibility (void)
FreeLibrary (module_handle);
}
typedef struct {
FILE *stream;
bool done;
GMutex mutex;
GCond cond;
} StreamLockData_t;
static gpointer
thread_acquire_stdio_stream_lock (gpointer user_data)
{
StreamLockData_t *data = (StreamLockData_t *) user_data;
FILE *stream = data->stream;
/* For the test to be effective, this thread must be holding
* the stream lock when the subprocess "main function" (i.e.
* test_subprocess_early_flush) returns.
*
* This simulates a thread doing I/O on a stream at the time
* exit invokes ExitProcess. Note that _lock_file is exactly
* what stdio functions call internally.
*/
_lock_file (stream);
g_mutex_lock (&data->mutex);
data->done = true;
g_cond_signal (&data->cond);
g_mutex_unlock (&data->mutex);
data = NULL;
/* Sleep a bit to let the main thread exit. Note: if somehow
* the main thread doesn't exit in time, this test will
* succeed but won't actually verify anything.
*/
g_usleep (1 * G_USEC_PER_SEC);
_unlock_file (stream);
return NULL;
}
static void
test_subprocess_early_stdio_flush (void)
{
int64_t fd;
FILE *stream;
int ret;
fd = g_ascii_strtoull (g_getenv ("G_TEST_PIPE_FD"), NULL, 10);
g_assert_cmpuint (fd, >=, 0);
g_assert_cmpuint (fd, <=, INT_MAX);
stream = _fdopen ((int) fd, "w");
g_assert_nonnull (stream);
ret = setvbuf (stream, NULL, _IOFBF, 1024);
g_assert_cmpint (ret, ==, 0);
ret = fprintf (stream, "hello world");
g_assert_cmpint (ret, ==, (int) strlen ("hello world"));
g_assert_false (ferror (stream));
StreamLockData_t data;
memset (&data, 0, sizeof (data));
data.stream = stream;
data.done = false;
GThread *thread = g_thread_new ("lock stdio stream",
thread_acquire_stdio_stream_lock,
(gpointer) &data);
g_thread_unref (thread);
/* Wait until the worker thread has acquired the stream lock.
*/
g_mutex_lock (&data.mutex);
while (!data.done)
g_cond_wait (&data.cond, &data.mutex);
g_mutex_unlock (&data.mutex);
/* If the early flush is not implemented, the C RunTime will attempt
* to acquire the stream's internal CRITICAL_SECTION from DllMain.
* Then the process will be terminated abruptly (with the same exit
* code passed to exit) leaving stream unflushed.
*/
}
static gpointer
thread_read_pipe (gpointer user_data)
{
int fd = GPOINTER_TO_INT (user_data);
const char *iter = "hello world";
int size = (int) strlen (iter);
while (size > 0)
{
char buffer[20];
int ret = _read (fd, buffer, sizeof (buffer));
g_assert_cmpint (ret, >, 0);
g_assert_cmpint (ret, <=, size);
g_assert_cmpmem (buffer, ret, iter, ret);
iter += ret;
size -= ret;
}
return NULL;
}
static void
test_early_stdio_flush (void)
{
int pipe_fds[2];
int pipe_read;
int pipe_write;
char buffer[10];
GStrv envp = NULL;
GError *error = NULL;
int ret;
ret = _pipe (pipe_fds, 1024, _O_BINARY);
if (ret < 0)
g_error ("%s failed: %s", "_pipe", g_strerror (errno));
pipe_read = pipe_fds[0];
pipe_write = pipe_fds[1];
g_snprintf (buffer, sizeof (buffer), "%i", pipe_write);
envp = g_get_environ ();
envp = g_environ_setenv (g_steal_pointer (&envp), "G_TEST_PIPE_FD", buffer, TRUE);
GThread *thread = g_thread_new ("read pipe",
thread_read_pipe,
GINT_TO_POINTER (pipe_read));
typedef const char * const * GStrvConst_t;
g_test_trap_subprocess_with_envp ("/win32/subprocess/early-stdio-flush",
(GStrvConst_t) envp, 0,
G_TEST_SUBPROCESS_INHERIT_DESCRIPTORS);
g_test_trap_assert_passed ();
g_close (pipe_write, &error);
g_assert_no_error (error);
g_thread_join (thread);
g_close (pipe_read, NULL);
g_strfreev (envp);
}
int
main (int argc,
char *argv[])
@@ -275,6 +432,8 @@ main (int argc,
g_test_add_func ("/win32/subprocess/stderr-buffering-mode", test_subprocess_stderr_buffering_mode);
g_test_add_func ("/win32/stderr-buffering-mode", test_stderr_buffering_mode);
g_test_add_func ("/win32/manifest-os-compatibility", test_manifest_os_compatibility);
g_test_add_func ("/win32/subprocess/early-stdio-flush", test_subprocess_early_stdio_flush);
g_test_add_func ("/win32/early-stdio-flush", test_early_stdio_flush);
return g_test_run();
}