From f52006656394387d5709e39a7f79facbc8ed89d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Sep 2022 03:03:51 +0200 Subject: [PATCH 1/2] gutils: Add a private API to unset the cached temporary directory We may need to avoid using a cached temp directory for testing purposes, so let's provide an internal API to perform such task. This implies removing GOnce and going with mutex-based version, but that's still using atomic logic in most unix implementations anyways. --- glib/gutils.c | 39 ++++++++++++++++++++++++++++++++------- glib/gutilsprivate.h | 2 ++ tools/glib.supp | 13 +++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/glib/gutils.c b/glib/gutils.c index 186ef7d07..d73b6474b 100644 --- a/glib/gutils.c +++ b/glib/gutils.c @@ -598,6 +598,7 @@ static gchar *g_user_state_dir = NULL; static gchar *g_user_runtime_dir = NULL; static gchar **g_system_config_dirs = NULL; static gchar **g_user_special_dirs = NULL; +static gchar *g_tmp_dir = NULL; /* fifteen minutes of fame for everybody */ #define G_USER_DIRS_EXPIRE 15 * 60 @@ -941,6 +942,17 @@ g_get_home_dir (void) return home_dir; } +void +_g_unset_cached_tmp_dir (void) +{ + G_LOCK (g_utils_global); + /* We have to leak the old value, as user code could be retaining pointers + * to it. */ + g_ignore_leak (g_tmp_dir); + g_tmp_dir = NULL; + G_UNLOCK (g_utils_global); +} + /** * g_get_tmp_dir: * @@ -964,22 +976,33 @@ g_get_home_dir (void) const gchar * g_get_tmp_dir (void) { - static gchar *tmp_dir; + G_LOCK (g_utils_global); - if (g_once_init_enter (&tmp_dir)) + if (g_tmp_dir == NULL) { gchar *tmp; -#ifdef G_OS_WIN32 - tmp = g_strdup (g_getenv ("TEMP")); + tmp = g_strdup (g_getenv ("G_TEST_TMPDIR")); + if (tmp == NULL || *tmp == '\0') + { + g_free (tmp); + tmp = g_strdup (g_getenv ( +#ifdef G_OS_WIN32 + "TEMP" +#else /* G_OS_WIN32 */ + "TMPDIR" +#endif /* G_OS_WIN32 */ + )); + } + +#ifdef G_OS_WIN32 if (tmp == NULL || *tmp == '\0') { g_free (tmp); tmp = get_windows_directory_root (); } #else /* G_OS_WIN32 */ - tmp = g_strdup (g_getenv ("TMPDIR")); #ifdef P_tmpdir if (tmp == NULL || *tmp == '\0') @@ -1000,10 +1023,12 @@ g_get_tmp_dir (void) } #endif /* !G_OS_WIN32 */ - g_once_init_leave (&tmp_dir, tmp); + g_tmp_dir = g_steal_pointer (&tmp); } - return tmp_dir; + G_UNLOCK (g_utils_global); + + return g_tmp_dir; } /** diff --git a/glib/gutilsprivate.h b/glib/gutilsprivate.h index f7d435d61..0d9b0df14 100644 --- a/glib/gutilsprivate.h +++ b/glib/gutilsprivate.h @@ -59,6 +59,8 @@ g_nearest_pow (gsize num) return n + 1; } +void _g_unset_cached_tmp_dir (void); + G_END_DECLS #endif /* __G_UTILS_PRIVATE_H__ */ diff --git a/tools/glib.supp b/tools/glib.supp index 0609db44e..5b03557d9 100644 --- a/tools/glib.supp +++ b/tools/glib.supp @@ -840,6 +840,19 @@ fun:g_set_user_dirs } +# _g_unset_cached_tmp_dir() deliberately leaks the previous cached g_get_tmp_dir() values. +# These will not all be reachable on exit. +{ + g_get_tmp_dir + Memcheck:Leak + match-leak-kinds:definite,reachable + fun:malloc + ... + fun:g_get_tmp_dir + ... + fun:g_test_init +} + # g_get_system_data_dirs() caches a one-time allocation { g_get_system_data_dirs From ca0f04d1c6a89f59fa9bde0d62c7ddd22252567d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Sep 2022 03:04:54 +0200 Subject: [PATCH 2/2] gtestutils: Use $G_TEST_TMPDIR as temporary directory when defined During tests in which we are isolating directories, we may still create temporary files in the global temporary directory without cleaning them because the value returned by g_get_tmp_dir() is cached when we isolate the tests directory to the global TMPDIR. To ensure that we're always isolating the temporary directories, let's unset the cached temporary directory once we've defined $G_TEST_TMPDIR so that the returned value of g_get_tmpdir() can be recomputed using the test isolated temporary directory. --- glib/gtestutils.c | 1 + glib/tests/fileutils.c | 4 ++ glib/tests/meson.build | 1 + glib/tests/utils-isolated.c | 114 ++++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 glib/tests/utils-isolated.c diff --git a/glib/gtestutils.c b/glib/gtestutils.c index 709c229ca..22b48d9ef 100644 --- a/glib/gtestutils.c +++ b/glib/gtestutils.c @@ -1690,6 +1690,7 @@ void (*argv)[0], "G_TEST_TMPDIR"); exit (1); } + _g_unset_cached_tmp_dir (); /* And clear the traditional environment variables so subprocesses * spawned by the code under test can’t trash anything. If a test diff --git a/glib/tests/fileutils.c b/glib/tests/fileutils.c index 8ca25f9cc..b9149720d 100644 --- a/glib/tests/fileutils.c +++ b/glib/tests/fileutils.c @@ -1205,6 +1205,7 @@ test_dir_make_tmp (void) name = g_dir_make_tmp ("testXXXXXXtest", &error); g_assert_no_error (error); g_assert_true (g_file_test (name, G_FILE_TEST_IS_DIR)); + g_assert_true (g_str_has_prefix (name, g_getenv ("G_TEST_TMPDIR"))); ret = g_rmdir (name); g_assert_cmpint (ret, ==, 0); g_free (name); @@ -1212,6 +1213,7 @@ test_dir_make_tmp (void) name = g_dir_make_tmp (NULL, &error); g_assert_no_error (error); g_assert_true (g_file_test (name, G_FILE_TEST_IS_DIR)); + g_assert_true (g_str_has_prefix (name, g_getenv ("G_TEST_TMPDIR"))); ret = g_rmdir (name); g_assert_cmpint (ret, ==, 0); g_free (name); @@ -1238,6 +1240,7 @@ test_file_open_tmp (void) g_assert_cmpint (fd, !=, -1); g_assert_no_error (error); g_assert_nonnull (name); + g_assert_true (g_str_has_prefix (name, g_getenv ("G_TEST_TMPDIR"))); unlink (name); g_free (name); close (fd); @@ -1246,6 +1249,7 @@ test_file_open_tmp (void) g_assert_cmpint (fd, !=, -1); g_assert_no_error (error); g_assert_nonnull (name); + g_assert_true (g_str_has_prefix (name, g_getenv ("G_TEST_TMPDIR"))); g_unlink (name); g_free (name); close (fd); diff --git a/glib/tests/meson.build b/glib/tests/meson.build index a01649b77..cec91a49a 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -158,6 +158,7 @@ glib_tests = { 'utils' : { 'c_standards': c_standards.keys(), }, + 'utils-isolated' : {}, 'unicode' : {}, 'unicode-encoding' : {}, 'unicode-normalize': {}, diff --git a/glib/tests/utils-isolated.c b/glib/tests/utils-isolated.c new file mode 100644 index 000000000..6ffb3424f --- /dev/null +++ b/glib/tests/utils-isolated.c @@ -0,0 +1,114 @@ +/* Copyright (C) 2022 Marco Trevisan + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: Marco Trevisan + */ + +#include "config.h" + +#include + +/* Test that all of the well-known directories returned by GLib + * are returned as children of test_tmpdir when running with + * %G_TEST_OPTION_ISOLATE_DIRS. This ensures that tests should + * not interfere with each other in `/tmp` while running. + */ + +const char *test_tmpdir; + +static void +test_tmp_dir (void) +{ + g_assert_cmpstr (g_get_tmp_dir (), ==, test_tmpdir); +} + +static void +test_home_dir (void) +{ + g_assert_true (g_str_has_prefix (g_get_home_dir (), test_tmpdir)); +} + +static void +test_user_cache_dir (void) +{ + g_assert_true (g_str_has_prefix (g_get_user_cache_dir (), test_tmpdir)); +} + +static void +test_system_config_dirs (void) +{ + const char *const *dir; + + for (dir = g_get_system_config_dirs (); *dir != NULL; dir++) + g_assert_true (g_str_has_prefix (*dir, test_tmpdir)); +} + +static void +test_user_config_dir (void) +{ + g_assert_true (g_str_has_prefix (g_get_user_config_dir (), test_tmpdir)); +} + +static void +test_system_data_dirs (void) +{ + const char *const *dir; + + for (dir = g_get_system_data_dirs (); *dir != NULL; dir++) + g_assert_true (g_str_has_prefix (*dir, test_tmpdir)); +} + +static void +test_user_data_dir (void) +{ + g_assert_true (g_str_has_prefix (g_get_user_data_dir (), test_tmpdir)); +} + +static void +test_user_state_dir (void) +{ + g_assert_true (g_str_has_prefix (g_get_user_state_dir (), test_tmpdir)); +} + +static void +test_user_runtime_dir (void) +{ + g_assert_true (g_str_has_prefix (g_get_user_runtime_dir (), test_tmpdir)); +} + + +int +main (int argc, + char *argv[]) +{ + g_setenv ("LC_ALL", "C", TRUE); + g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); + + test_tmpdir = g_getenv ("G_TEST_TMPDIR"); + g_assert_nonnull (test_tmpdir); + + g_test_add_func ("/utils-isolated/tmp-dir", test_tmp_dir); + g_test_add_func ("/utils-isolated/home-dir", test_home_dir); + g_test_add_func ("/utils-isolated/user-cache-dir", test_user_cache_dir); + g_test_add_func ("/utils-isolated/system-config-dirs", test_system_config_dirs); + g_test_add_func ("/utils-isolated/user-config-dir", test_user_config_dir); + g_test_add_func ("/utils-isolated/system-data-dirs", test_system_data_dirs); + g_test_add_func ("/utils-isolated/user-data-dir", test_user_data_dir); + g_test_add_func ("/utils-isolated/user-state-dir", test_user_state_dir); + g_test_add_func ("/utils-isolated/user-runtime-dir", test_user_runtime_dir); + return g_test_run (); +}