mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-11-29 05:19:54 +01:00
Win32: Add test for safe stdio flushing
Test the two-phase flushing implemented in app_profile_dep.
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user