• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 26546993077

27 May 2026 08:34PM UTC coverage: 72.995% (+0.3%) from 72.667%
26546993077

push

github

bluca
test-pressure: set timeout to make not wait forever

If this runs on a slow or busy machine, then we may not get enough
pressure to trigger the event sources. In such case, the test does not
finish. It is problematic when the test is _not_ run with 'meson test',
e.g. debian/ubuntu CIs.

Let's introduce a timeout for each event loop, and skip test cases
gracefully.

8 of 12 new or added lines in 1 file covered. (66.67%)

19671 existing lines in 226 files now uncovered.

337119 of 461841 relevant lines covered (72.99%)

1326365.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.26
/src/shared/tests.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <stdlib.h>
4
#include <sys/mman.h>
5
#include <unistd.h>
6

7
#include "sd-bus.h"
8
#include "sd-daemon.h"
9

10
#include "alloc-util.h"
11
#include "argv-util.h"
12
#include "bus-error.h"
13
#include "bus-locator.h"
14
#include "bus-util.h"
15
#include "bus-wait-for-jobs.h"
16
#include "cgroup-setup.h"
17
#include "cgroup-util.h"
18
#include "coredump-util.h"
19
#include "env-file.h"
20
#include "env-util.h"
21
#include "errno-util.h"
22
#include "extract-word.h"
23
#include "fd-util.h"
24
#include "fs-util.h"
25
#include "hexdecoct.h"
26
#include "log.h"
27
#include "namespace-util.h"
28
#include "path-util.h"
29
#include "pidref.h"
30
#include "process-util.h"
31
#include "random-util.h"
32
#include "rlimit-util.h"
33
#include "strv.h"
34
#include "tests.h"
35
#include "tmpfile-util.h"
36
#include "uid-range.h"
37

38
char* setup_fake_runtime_dir(void) {
8✔
39
        char *t;
8✔
40

41
        ASSERT_OK(mkdtemp_malloc("/tmp/fake-xdg-runtime-XXXXXX", &t));
8✔
42
        ASSERT_OK(setenv("XDG_RUNTIME_DIR", t, /* overwrite= */ true));
8✔
43
        return t;
8✔
44
}
45

46
static void load_testdata_env(void) {
29✔
47
        static bool called = false;
29✔
48
        _cleanup_free_ char *s = NULL, *d = NULL, *envpath = NULL;
29✔
49
        _cleanup_strv_free_ char **pairs = NULL;
×
50
        int r;
29✔
51

52
        if (called)
29✔
53
                return;
54
        called = true;
14✔
55

56
        ASSERT_OK(readlink_and_make_absolute("/proc/self/exe", &s));
14✔
57
        ASSERT_OK(path_extract_directory(s, &d));
14✔
58
        ASSERT_NOT_NULL(envpath = path_join(d, "systemd-runtest.env"));
14✔
59

60
        r = load_env_file_pairs(NULL, envpath, &pairs);
14✔
61
        if (r < 0) {
14✔
62
                log_debug_errno(r, "Reading %s failed, ignoring: %m", envpath);
29✔
63
                return;
64
        }
65

66
        STRV_FOREACH_PAIR(k, v, pairs)
×
67
                ASSERT_OK(setenv(*k, *v, /* overwrite= */ false));
×
68
}
69

70
int get_testdata_dir(const char *suffix, char **ret) {
28✔
71
        const char *dir;
28✔
72
        char *p;
28✔
73

74
        assert(ret);
28✔
75

76
        load_testdata_env();
28✔
77

78
        /* if the env var is set, use that */
79
        dir = getenv("SYSTEMD_TEST_DATA");
28✔
80
        if (!dir)
28✔
81
                dir = SYSTEMD_TEST_DATA;
28✔
82
        if (access(dir, F_OK) < 0)
28✔
83
                return log_error_errno(errno, "ERROR: $SYSTEMD_TEST_DATA directory [%s] not accessible: %m", dir);
×
84

85
        p = path_join(dir, suffix);
28✔
86
        if (!p)
28✔
87
                return log_oom();
×
88

89
        *ret = p;
28✔
90
        return 0;
28✔
91
}
92

93
const char* get_catalog_dir(void) {
1✔
94
        const char *env;
1✔
95

96
        load_testdata_env();
1✔
97

98
        /* if the env var is set, use that */
99
        env = getenv("SYSTEMD_CATALOG_DIR");
1✔
100
        if (!env)
1✔
101
                env = SYSTEMD_CATALOG_DIR;
1✔
102
        if (access(env, F_OK) < 0) {
1✔
103
                fprintf(stderr, "ERROR: $SYSTEMD_CATALOG_DIR directory [%s] does not exist\n", env);
×
104
                exit(EXIT_FAILURE);
×
105
        }
106
        return env;
1✔
107
}
108

109
bool slow_tests_enabled(void) {
17✔
110
        int r;
17✔
111

112
        r = getenv_bool("SYSTEMD_SLOW_TESTS");
17✔
113
        if (r >= 0)
17✔
114
                return r;
×
115

116
        if (r != -ENXIO)
17✔
117
                log_warning_errno(r, "Cannot parse $SYSTEMD_SLOW_TESTS, ignoring.");
×
118
        return SYSTEMD_SLOW_TESTS_DEFAULT;
119
}
120

121
void test_setup_logging(int level) {
443✔
122
        log_set_assert_return_is_critical(true);
443✔
123
        log_set_max_level(level);
443✔
124
        log_setup();
443✔
125
}
443✔
126

127
int write_tmpfile(char *pattern, const char *contents) {
14✔
128
        _cleanup_close_ int fd = -EBADF;
14✔
129

130
        assert(pattern);
14✔
131
        assert(contents);
14✔
132

133
        fd = mkostemp_safe(pattern);
14✔
134
        if (fd < 0)
14✔
135
                return fd;
136

137
        ssize_t l = strlen(contents);
14✔
138
        errno = 0;
14✔
139
        if (write(fd, contents, l) != l)
14✔
140
                return errno_or_else(EIO);
14✔
141
        return 0;
142
}
143

144
bool have_namespaces(void) {
4✔
145
        _cleanup_(pidref_done) PidRef pid = PIDREF_NULL;
4✔
146
        int r;
4✔
147

148
        /* Checks whether namespaces are available. In some cases they aren't. We do this by calling unshare(), and we
149
         * do so in a child process in order not to affect our own process. */
150

151
        ASSERT_OK(r = pidref_safe_fork("(have_namespace)", /* flags= */ 0, &pid));
4✔
152
        if (r == 0) {
7✔
153
                /* child */
154
                if (detach_mount_namespace() < 0)
3✔
155
                        _exit(EXIT_FAILURE);
×
156

157
                _exit(EXIT_SUCCESS);
3✔
158
        }
159

160
        ASSERT_OK(r = pidref_wait_for_terminate_and_check("(have_namespace)", &pid, /* flags= */ 0));
4✔
161
        if (r == EXIT_SUCCESS)
4✔
162
                return true;
163

164
        if (r == EXIT_FAILURE)
×
165
                return false;
166

167
        assert_not_reached();
×
168
}
169

170
bool userns_has_single_user(void) {
9✔
171
        _cleanup_(uid_range_freep) UIDRange *uidrange = NULL, *gidrange = NULL;
9✔
172

173
        /* Check if we're in a user namespace with only a single user mapped in. We special case this
174
         * scenario in a few tests because it's the only kind of namespace that can be created unprivileged
175
         * and as such happens more often than not, so we make sure to deal with it so that all tests pass
176
         * in such environments. */
177

178
        if (uid_range_load_userns(NULL, UID_RANGE_USERNS_INSIDE, &uidrange) < 0)
9✔
179
                return false;
180

181
        if (uid_range_load_userns(NULL, GID_RANGE_USERNS_INSIDE, &gidrange) < 0)
9✔
182
                return false;
183

184
        return uidrange->n_entries == 1 && uidrange->entries[0].nr == 1 &&
9✔
185
                gidrange->n_entries == 1 && gidrange->entries[0].nr == 1;
18✔
186
}
187

188
bool can_memlock(void) {
2✔
189
        /* Let's see if we can mlock() a larger blob of memory. BPF programs are charged against
190
         * RLIMIT_MEMLOCK, hence let's first make sure we can lock memory at all, and skip the test if we
191
         * cannot. Why not check RLIMIT_MEMLOCK explicitly? Because in container environments the
192
         * RLIMIT_MEMLOCK value we see might not match the RLIMIT_MEMLOCK value actually in effect. */
193

194
        void *p = mmap(NULL, CAN_MEMLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0);
2✔
195
        if (p == MAP_FAILED)
2✔
196
                return false;
197

198
        bool b = mlock(p, CAN_MEMLOCK_SIZE) >= 0;
2✔
199
        if (b)
2✔
200
                ASSERT_OK(munlock(p, CAN_MEMLOCK_SIZE));
×
201

202
        ASSERT_OK(munmap(p, CAN_MEMLOCK_SIZE));
2✔
203
        return b;
204
}
205

206
static int allocate_scope(void) {
16✔
207
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
22✔
208
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
16✔
209
        _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
×
210
        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
16✔
211
        _cleanup_free_ char *scope = NULL, *cgroup_root = NULL;
16✔
212
        const char *object;
16✔
213
        int r;
16✔
214

215
        /* Let's try to run this test in a scope of its own, with delegation turned on, so that PID 1 doesn't
216
         * interfere with our cgroup management. */
217
        if (cg_pid_get_path(0, &cgroup_root) >= 0 && cg_is_delegated(cgroup_root) && stderr_is_journal()) {
16✔
218
                log_debug("Already running as a unit with delegated cgroup, not allocating a cgroup subroot.");
10✔
219
                return 0;
220
        }
221

222
        if (geteuid() == 0)
6✔
223
                r = sd_bus_default_system(&bus);
6✔
224
        else
225
                r = sd_bus_default_user(&bus);
×
226
        if (r < 0)
6✔
227
                return log_error_errno(r, "Failed to connect to system bus: %m");
×
228

229
        r = bus_wait_for_jobs_new(bus, &w);
6✔
230
        if (r < 0)
6✔
231
                return log_error_errno(r, "Could not watch jobs: %m");
×
232

233
        if (asprintf(&scope, "%s-%" PRIx64 ".scope", program_invocation_short_name, random_u64()) < 0)
6✔
234
                return log_oom();
×
235

236
        r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
6✔
237
        if (r < 0)
6✔
238
                return bus_log_create_error(r);
×
239

240
        /* Name and Mode */
241
        r = sd_bus_message_append(m, "ss", scope, "fail");
6✔
242
        if (r < 0)
6✔
243
                return bus_log_create_error(r);
×
244

245
        /* Properties */
246
        r = sd_bus_message_open_container(m, 'a', "(sv)");
6✔
247
        if (r < 0)
6✔
248
                return bus_log_create_error(r);
×
249

250
        r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid_cached());
6✔
251
        if (r < 0)
6✔
252
                return bus_log_create_error(r);
×
253

254
        r = sd_bus_message_append(m, "(sv)", "Delegate", "b", 1);
6✔
255
        if (r < 0)
6✔
256
                return bus_log_create_error(r);
×
257

258
        r = sd_bus_message_append(m, "(sv)", "CollectMode", "s", "inactive-or-failed");
6✔
259
        if (r < 0)
6✔
260
                return bus_log_create_error(r);
×
261

262
        r = sd_bus_message_close_container(m);
6✔
263
        if (r < 0)
6✔
264
                return bus_log_create_error(r);
×
265

266
        /* Auxiliary units */
267
        r = sd_bus_message_append(m, "a(sa(sv))", 0);
6✔
268
        if (r < 0)
6✔
269
                return bus_log_create_error(r);
×
270

271
        r = sd_bus_call(bus, m, 0, &error, &reply);
6✔
272
        if (r < 0)
6✔
273
                return log_error_errno(r, "Failed to start transient scope unit: %s", bus_error_message(&error, r));
×
274

275
        r = sd_bus_message_read(reply, "o", &object);
6✔
276
        if (r < 0)
6✔
277
                return bus_log_parse_error(r);
×
278

279
        r = bus_wait_for_jobs_one(w, object, BUS_WAIT_JOBS_LOG_ERROR, NULL);
6✔
280
        if (r < 0)
6✔
281
                return r;
×
282

283
        return 0;
284
}
285

286
static int enter_cgroup(char **ret_cgroup, bool enter_subroot) {
16✔
287
        _cleanup_free_ char *cgroup_root = NULL, *cgroup_subroot = NULL;
16✔
288
        CGroupMask supported;
16✔
289
        int r;
16✔
290

291
        r = cg_is_available();
16✔
292
        if (r < 0)
16✔
293
                return r;
294
        if (r == 0)
16✔
295
                return log_warning_errno(SYNTHETIC_ERRNO(ENOMEDIUM), "cgroupfs v2 is not mounted.");
×
296

297
        r = allocate_scope();
16✔
298
        if (r < 0)
16✔
299
                log_warning_errno(r, "Couldn't allocate a scope unit for this test, proceeding without.");
×
300

301
        r = cg_pid_get_path(0, &cgroup_root);
16✔
302
        if (IN_SET(r, -ENOMEDIUM, -ENOENT))
16✔
303
                return log_warning_errno(r, "cg_pid_get_path(0, ...) failed: %m");
×
304
        ASSERT_OK(r);
16✔
305

306
        if (enter_subroot)
16✔
307
                ASSERT_OK(asprintf(&cgroup_subroot, "%s/%" PRIx64, cgroup_root, random_u64()));
15✔
308
        else
309
                ASSERT_NOT_NULL(cgroup_subroot = strdup(cgroup_root));
1✔
310

311
        ASSERT_OK(cg_mask_supported(&supported));
16✔
312

313
        /* If this fails, then we don't mind as the later cgroup operations will fail too, and it's fine if
314
         * we handle any errors at that point. */
315

316
        r = cg_create_and_attach(cgroup_subroot, 0);
16✔
317
        if (r < 0)
16✔
318
                return r;
319

320
        if (ret_cgroup)
16✔
321
                *ret_cgroup = TAKE_PTR(cgroup_subroot);
1✔
322

323
        return 0;
324
}
325

326
int enter_cgroup_subroot(char **ret_cgroup) {
15✔
327
        return enter_cgroup(ret_cgroup, true);
15✔
328
}
329

330
int enter_cgroup_root(char **ret_cgroup) {
1✔
331
        return enter_cgroup(ret_cgroup, false);
1✔
332
}
333

334
int define_hex_ptr_internal(const char *hex, void **name, size_t *name_len) {
180✔
335
        return unhexmem_full(hex, strlen_ptr(hex), false, name, name_len);
352✔
336
}
337

UNCOV
338
const char* ci_environment(void) {
×
339
        /* We return a string because we might want to provide multiple bits of information later on: not
340
         * just the general CI environment type, but also whether we're sanitizing or not, etc. The caller is
341
         * expected to use strstr on the returned value. */
UNCOV
342
        static const char *ans = POINTER_MAX;
×
UNCOV
343
        int r;
×
344

UNCOV
345
        if (ans != POINTER_MAX)
×
346
                return ans;
347

348
        /* We allow specifying the environment with $CITYPE. Nobody uses this so far, but we are ready. */
UNCOV
349
        const char *citype = getenv("CITYPE");
×
UNCOV
350
        if (!isempty(citype))
×
351
                return (ans = citype);
×
352

UNCOV
353
        if (getenv_bool("TRAVIS") > 0)
×
354
                return (ans = "travis");
×
UNCOV
355
        if (getenv_bool("SEMAPHORE") > 0)
×
356
                return (ans = "semaphore");
×
UNCOV
357
        if (getenv_bool("GITHUB_ACTIONS") > 0)
×
358
                return (ans = "github-actions");
×
UNCOV
359
        if (getenv("AUTOPKGTEST_ARTIFACTS") || getenv("AUTOPKGTEST_TMP"))
×
360
                return (ans = "autopkgtest");
×
UNCOV
361
        if (getenv("SALSA_CI_IMAGES"))
×
362
                return (ans = "salsa-ci");
×
363

UNCOV
364
        FOREACH_STRING(var, "CI", "CONTINUOUS_INTEGRATION") {
×
365
                /* Those vars are booleans according to Semaphore and Travis docs:
366
                 * https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
367
                 * https://docs.semaphoreci.com/ci-cd-environment/environment-variables/#ci
368
                 */
UNCOV
369
                r = getenv_bool(var);
×
UNCOV
370
                if (r > 0)
×
371
                        return (ans = "unknown"); /* Some other unknown thing */
×
UNCOV
372
                if (r == 0)
×
373
                        return (ans = NULL);
×
374
        }
375

UNCOV
376
        return (ans = NULL);
×
377
}
378

379
int run_test_table(const TestFunc *start, const TestFunc *end) {
305✔
380
        _cleanup_strv_free_ char **tests = NULL;
279✔
381
        int r = EXIT_SUCCESS;
305✔
382
        bool ran = false;
305✔
383

384
        if (!start)
305✔
385
                return r;
386

387
        const char *e = getenv("TESTFUNCS");
305✔
388
        if (e) {
305✔
389
                r = strv_split_full(&tests, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
×
390
                if (r < 0)
×
391
                        return log_error_errno(r, "Failed to parse $TESTFUNCS: %m");
×
392
        }
393

394
        for (const TestFunc *t = start; t + 1 <= end; t++) {
2,603✔
395
                if (tests && !strv_contains(tests, t->name))
2,324✔
396
                        continue;
×
397

398
                if (t->sd_booted && sd_booted() <= 0) {
2,324✔
399
                        log_info("/* systemd not booted, skipping %s */", t->name);
×
400
                        if (t->has_ret && r == EXIT_SUCCESS)
×
401
                                r = EXIT_TEST_SKIP;
×
402
                } else {
403
                        log_info("/* %s */", t->name);
2,324✔
404

405
                        if (t->has_ret) {
2,324✔
406
                                int r2 = t->f.int_func();
23✔
407
                                if (r == EXIT_SUCCESS)
23✔
408
                                        r = r2;
20✔
409
                        } else
410
                                t->f.void_func();
2,301✔
411
                }
412

413
                ran = true;
414
        }
415

416
        if (!ran)
279✔
417
                return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No matching tests found.");
×
418

419
        return r;
420
}
421

422
void test_prepare(int argc, char *argv[], int log_level) {
310✔
423
        save_argc_argv(argc, argv);
310✔
424
        test_setup_logging(log_level);
310✔
425
}
310✔
426

427
/* Returns:
428
 * ASSERT_SIGNAL_FORK_CHILD  = We are in the child process
429
 * ASSERT_SIGNAL_FORK_PARENT = We are in the parent process (signal/status stored in *ret_signal)
430
 * <0                        = Error (negative errno)
431
 */
432
int assert_signal_internal(int *ret_signal) {
75✔
433
        siginfo_t siginfo = {};
75✔
434
        int r;
75✔
435

436
        assert(ret_signal);
75✔
437

438
        r = fork();
75✔
439
        if (r < 0)
81✔
440
                return -errno;
81✔
441

442
        if (r == 0) {
81✔
443
                /* Speed things up by never even attempting to generate a coredump */
444
                (void) set_dumpable(SUID_DUMP_DISABLE);
6✔
445

446
                /* But still set an rlimit just in case */
447
                (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(0));
6✔
448
                return ASSERT_SIGNAL_FORK_CHILD;
6✔
449
        }
450

451
        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
75✔
452
        r = pidref_set_pid(&pidref, r);
75✔
453
        if (r < 0)
75✔
454
                return r;
455

456
        r = pidref_wait_for_terminate(&pidref, &siginfo);
75✔
457
        if (r < 0)
75✔
458
                return r;
459

460
        /* si_status means different things depending on si_code:
461
         * - CLD_EXITED: si_status is the exit code
462
         * - CLD_KILLED/CLD_DUMPED: si_status is the signal number that killed the process
463
         * We need to return the signal number only if the child was killed by a signal. */
464
        if (IN_SET(siginfo.si_code, CLD_KILLED, CLD_DUMPED))
75✔
465
                *ret_signal = siginfo.si_status;
75✔
466
        else
467
                *ret_signal = 0;
×
468

469
        return ASSERT_SIGNAL_FORK_PARENT;
470
}
471

472

473
void log_test_failed_internal(const char *file, int line, const char *func, const char *format, ...) {
×
474
        va_list ap;
×
475

476
        va_start(ap, format);
×
477
        DISABLE_WARNING_FORMAT_NONLITERAL;
×
478
        log_internalv(LOG_ERR, 0, file, line, func, format, ap);
×
479
        REENABLE_WARNING;
×
480
        va_end(ap);
×
481

482
        abort();
×
483
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc