Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
test_loc_cache_dir.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
38#include <setjmp.h>
39#include <stdarg.h>
40#include <stddef.h>
41#include <stdint.h>
42
43#include <cmocka.h>
44
45#ifdef _WIN32
46#include "win/main_wrapper.h"
47#endif
48
49#include <glib.h>
50#include <string.h>
51
53#include "system/macros.h" // IS_NULL_PTR
54#include "system/mem_alloc.h" // dt_free
55
56#define XDG_KEY "XDG_CACHE_HOME"
57
58/* An absolute path that is never created and never touched: these tests exercise string
59 * resolution only, so the directory does not have to exist. Deliberately NOT under /tmp --
60 * naming a publicly writable directory in a test invites a symlink-attack finding (SonarCloud
61 * c:S5443) for a path nothing ever opens. */
62#define FAKE_ABSOLUTE_DIR G_DIR_SEPARATOR_S "nonexistent-ansel-test" G_DIR_SEPARATOR_S "xdg-cache-home"
63
64/* Restored by the teardown: the variable is process-wide and the other tests in this binary must not
65 * inherit whatever one of these set. A NULL saved value means it was not set at all. */
66static gchar *_saved_xdg = NULL;
67static gboolean _had_xdg = FALSE;
68
69static int _setup(void **state G_GNUC_UNUSED)
70{
71 const gchar *current = g_getenv(XDG_KEY);
72 _had_xdg = !IS_NULL_PTR(current);
73 _saved_xdg = _had_xdg ? g_strdup(current) : NULL;
74 return 0;
75}
76
77static int _teardown(void **state G_GNUC_UNUSED)
78{
79 if(_had_xdg)
80 g_setenv(XDG_KEY, _saved_xdg, TRUE);
81 else
82 g_unsetenv(XDG_KEY);
83
86 return 0;
87}
88
89static void _xdg_cache_home_wins(void **state G_GNUC_UNUSED)
90{
91 g_setenv(XDG_KEY, FAKE_ABSOLUTE_DIR, TRUE);
92
93 gchar *expected = g_build_filename(FAKE_ABSOLUTE_DIR, "ansel", NULL);
94 gchar *got = dt_loc_default_user_cache_dir();
95
96 assert_non_null(got);
97 assert_string_equal(got, expected);
98
99 dt_free(got);
100 dt_free(expected);
101}
102
103static void _an_empty_xdg_is_ignored(void **state G_GNUC_UNUSED)
104{
105 /* An exported-but-empty variable is how a shell spells "not set"; taking it literally would put
106 * the cache at the filesystem root. */
107 g_setenv(XDG_KEY, "", TRUE);
108
109 gchar *got = dt_loc_default_user_cache_dir();
110
111 assert_non_null(got);
112 assert_true(g_path_is_absolute(got));
113 assert_string_not_equal(got, G_DIR_SEPARATOR_S "ansel");
114
115 dt_free(got);
116}
117
118static void _a_relative_xdg_is_ignored(void **state G_GNUC_UNUSED)
119{
120 /* The XDG specification calls a relative path in one of these variables invalid, and honouring one
121 * would make the cache location depend on the working directory. */
122 g_setenv(XDG_KEY, "relative-cache", TRUE);
123
124 gchar *got = dt_loc_default_user_cache_dir();
125
126 assert_non_null(got);
127 assert_true(g_path_is_absolute(got));
128 assert_null(strstr(got, "relative-cache"));
129
130 dt_free(got);
131}
132
134{
135 g_unsetenv(XDG_KEY);
136
137 gchar *got = dt_loc_default_user_cache_dir();
138 assert_non_null(got);
139 assert_true(g_path_is_absolute(got));
140
141 /* The base the platform must resolve against. On Windows this is deliberately NOT
142 * g_get_user_cache_dir(): see the file comment. Same resolution order as the function under
143 * test, so this says the right thing on a Windows box whose %LOCALAPPDATA% is unset. Elsewhere
144 * not g_get_user_cache_dir() either, which answers whatever XDG_CACHE_HOME held at its first
145 * call in this process, not what it holds now. */
146#ifdef _WIN32
147 const gchar *local_app_data = g_getenv("LOCALAPPDATA");
148 if(IS_NULL_PTR(local_app_data) || !local_app_data[0]) local_app_data = g_get_user_data_dir();
149 gchar *base = g_strdup(local_app_data);
150#else
151 gchar *base = g_build_filename(g_get_home_dir(), ".cache", NULL);
152#endif
153 assert_non_null(base);
154 assert_true(g_str_has_prefix(got, base));
155
156 /* Named explicitly as well as structurally: g_get_user_data_dir() and g_get_user_cache_dir() are
157 * the same directory on some platforms, so the prefix test alone would not catch a return to
158 * INetCache everywhere. */
159 assert_null(strstr(got, "INetCache"));
160 assert_null(strstr(got, "Temporary Internet Files"));
161
162 dt_free(base);
163 dt_free(got);
164}
165
166static void _every_call_answers_the_same(void **state G_GNUC_UNUSED)
167{
168 /* Two callers resolve this independently -- main() before dt_loc_init(), and
169 * dt_loc_init_user_cache_dir() during it -- and they must land on the same directory. */
170 g_unsetenv(XDG_KEY);
171
172 gchar *first = dt_loc_default_user_cache_dir();
173 gchar *second = dt_loc_default_user_cache_dir();
174
175 assert_non_null(first);
176 assert_non_null(second);
177 assert_ptr_not_equal(first, second);
178 assert_string_equal(first, second);
179
180 dt_free(second);
181 dt_free(first);
182}
183
184int main(int argc, char *argv[])
185{
186 /* The relative case runs FIRST, before anything in this process can have asked GLib for its cache
187 * dir: g_get_user_cache_dir() memoises its first answer, so after an empty-variable run it would
188 * keep answering ~/.cache and a relative value leaking back through it would pass unseen. */
189 const struct CMUnitTest tests[] = {
190 cmocka_unit_test(_a_relative_xdg_is_ignored),
191 cmocka_unit_test(_xdg_cache_home_wins),
192 cmocka_unit_test(_an_empty_xdg_is_ignored),
194 cmocka_unit_test(_every_call_answers_the_same),
195 };
196
197 return cmocka_run_group_tests(tests, _setup, _teardown);
198}
199
200// clang-format off
201// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
202// vim: shiftwidth=2 expandtab tabstop=2 cindent
203// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
204// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
gchar * dt_loc_default_user_cache_dir(void)
Resolve the default user cache directory the way dt_loc_init_user_cache_dir() does,...
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
int main()
Definition prova.c:47
const float uint32_t state[4]
static gboolean _had_xdg
#define FAKE_ABSOLUTE_DIR
#define XDG_KEY
static gchar * _saved_xdg
static void _a_relative_xdg_is_ignored(void **state G_GNUC_UNUSED)
static int _setup(void **state G_GNUC_UNUSED)
static void _the_default_is_not_a_shell_managed_temporary_folder(void **state G_GNUC_UNUSED)
static int _teardown(void **state G_GNUC_UNUSED)
static void _an_empty_xdg_is_ignored(void **state G_GNUC_UNUSED)
static void _every_call_answers_the_same(void **state G_GNUC_UNUSED)
static void _xdg_cache_home_wins(void **state G_GNUC_UNUSED)