summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaan De Meyer <daan.j.demeyer@gmail.com>2024-12-05 10:47:45 +0100
committerGitHub <noreply@github.com>2024-12-05 10:47:45 +0100
commit900ac3a76a5770173229f5641506611fbb7c8af7 (patch)
tree8d9d795496299d05cc274156743e9251a6431690
parentdmi: add RISC-V 64bit support (diff)
parentci: Implement coverage on top of mkosi (diff)
downloadsystemd-900ac3a76a5770173229f5641506611fbb7c8af7.tar.xz
systemd-900ac3a76a5770173229f5641506611fbb7c8af7.zip
ci: Implement coverage on top of mkosi (#35407)
-rw-r--r--.github/workflows/coverage.yml145
-rw-r--r--.github/workflows/mkosi.yml2
-rw-r--r--mkosi.conf2
-rw-r--r--mkosi.conf.d/05-tools/mkosi.conf3
-rw-r--r--mkosi.conf.d/05-tools/mkosi.conf.d/arch.conf1
-rw-r--r--mkosi.coverage/mkosi.conf9
-rwxr-xr-xmkosi.coverage/mkosi.postinst56
-rwxr-xr-xmkosi.extra.common/usr/lib/systemd/coverage-forwarder9
-rw-r--r--mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset2
-rw-r--r--mkosi.extra.common/usr/lib/systemd/system/coverage-forwarder.service18
-rwxr-xr-xmkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot4
-rwxr-xr-xmkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.build.chroot4
-rwxr-xr-xmkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot4
-rwxr-xr-xmkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot4
-rw-r--r--mkosi.images/initrd/mkosi.conf2
-rw-r--r--src/shared/creds-util.c3
-rw-r--r--src/test/test-creds.c110
-rw-r--r--src/test/test-execute.c6
-rwxr-xr-xtest/integration-test-wrapper.py196
-rw-r--r--test/test-execute/exec-ambientcapabilities-dynuser.service1
-rw-r--r--test/test-execute/exec-dynamicuser-fixeduser-adm.service1
-rw-r--r--test/test-execute/exec-dynamicuser-fixeduser-games.service1
-rw-r--r--test/test-execute/exec-dynamicuser-fixeduser-one-supplementarygroup.service1
-rw-r--r--test/test-execute/exec-dynamicuser-fixeduser.service1
-rw-r--r--test/test-execute/exec-dynamicuser-runtimedirectory1.service1
-rw-r--r--test/test-execute/exec-dynamicuser-runtimedirectory2.service1
-rw-r--r--test/test-execute/exec-dynamicuser-runtimedirectory3.service1
-rw-r--r--test/test-execute/exec-dynamicuser-statedir-migrate-step2.service1
-rw-r--r--test/test-execute/exec-dynamicuser-statedir.service1
-rw-r--r--test/test-execute/exec-dynamicuser-supplementarygroups.service1
-rwxr-xr-xtest/test-network/systemd-networkd-tests.py2
-rwxr-xr-xtest/units/TEST-38-FREEZER.sh5
32 files changed, 513 insertions, 85 deletions
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
new file mode 100644
index 0000000000..73409e53ef
--- /dev/null
+++ b/.github/workflows/coverage.yml
@@ -0,0 +1,145 @@
+---
+# SPDX-License-Identifier: LGPL-2.1-or-later
+name: coverage
+
+on:
+ schedule:
+ # Calculate coverage daily at midnight
+ - cron: '0 0 * * *'
+
+permissions:
+ contents: read
+
+jobs:
+ coverage:
+ runs-on: ubuntu-24.04
+
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - uses: systemd/mkosi@07ef37c4c0dad5dfc6cec86c967a7600df1cd88c
+
+ # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space
+ # immediately, we remove the files in the background. However, we first move them to a different location
+ # so that nothing tries to use anything in these directories anymore while we're busy deleting them.
+ - name: Free disk space
+ run: |
+ sudo mv /usr/local /usr/local.trash
+ sudo mv /opt/hostedtoolcache /opt/hostedtoolcache.trash
+ sudo systemd-run rm -rf /usr/local.trash /opt/hostedtoolcache.trash
+
+ - name: Btrfs
+ run: |
+ truncate --size=100G btrfs.raw
+ mkfs.btrfs btrfs.raw
+ sudo mkdir /mnt/mkosi
+ LOOP="$(sudo losetup --find --show --direct-io=on btrfs.raw)"
+ sudo mount "$LOOP" /mnt/mkosi --options compress=zstd:1,user_subvol_rm_allowed,noatime,discard=async,space_cache=v2
+ sudo chown "$(id -u):$(id -g)" /mnt/mkosi
+ mkdir /mnt/mkosi/tmp
+ echo "TMPDIR=/mnt/mkosi/tmp" >>"$GITHUB_ENV"
+ ln -s /mnt/mkosi/build build
+
+ - name: Configure
+ run: |
+ # XXX: drop after the HyperV bug that breaks secure boot KVM guests is solved
+ sed -i "s/'firmware'\s*:\s*'auto'/'firmware' : 'uefi'/g" test/*/meson.build
+
+ tee mkosi.local.conf <<EOF
+ [Distribution]
+ Distribution=arch
+
+ [Build]
+ ToolsTree=default
+ ToolsTreeDistribution=arch
+ UseSubvolumes=yes
+ WithTests=no
+
+ WorkspaceDirectory=$TMPDIR
+ PackageCacheDirectory=$TMPDIR/cache
+
+ Environment=
+ # Build debuginfo packages since we'll be publishing the packages as artifacts.
+ WITH_DEBUG=1
+ CFLAGS=-Og
+ MESON_OPTIONS=--werror
+ COVERAGE=1
+
+ [Host]
+ QemuMem=4G
+ EOF
+
+ - name: Generate secure boot key
+ run: mkosi --debug genkey
+
+ - name: Show image summary
+ run: mkosi summary
+
+ - name: Build tools tree
+ run: mkosi -f sandbox true
+
+ - name: PATH
+ run: echo "$PATH"
+
+ - name: Configure meson
+ run: mkosi sandbox meson setup --buildtype=debugoptimized -Dintegration-tests=true build
+
+ - name: Build image
+ run: sudo --preserve-env mkosi sandbox meson compile -C build mkosi
+
+ - name: Initial coverage report
+ run: |
+ mkdir -p build/test/coverage
+ mkosi sandbox \
+ lcov \
+ --directory build/mkosi.builddir/arch~rolling~x86-64 \
+ --capture \
+ --initial \
+ --exclude "*.gperf" \
+ --output-file build/test/coverage/initial.coverage-info \
+ --base-directory src/ \
+ --ignore-errors source \
+ --no-external \
+ --substitute "s#src/src#src#g"
+
+ - name: Run integration tests
+ run: |
+ sudo --preserve-env \
+ mkosi sandbox \
+ meson test \
+ -C build \
+ --no-rebuild \
+ --suite integration-tests \
+ --print-errorlogs \
+ --no-stdsplit \
+ --num-processes "$(($(nproc) - 1))" \
+ --timeout-multiplier 2 \
+ --max-lines 300
+
+ - name: Archive failed test journals
+ uses: actions/upload-artifact@v4
+ if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable')
+ with:
+ name: ci-coverage-${{ github.run_id }}-${{ github.run_attempt }}-arch-rolling-failed-test-journals
+ path: |
+ build/test/journal/*.journal
+ build/meson-logs/*
+ retention-days: 7
+
+ - name: Combine coverage reports
+ run: |
+ lcov_args=()
+
+ while read -r file; do
+ lcov_args+=(--add-tracefile "${file}")
+ done < <(find build/test/coverage -name "TEST-*.coverage-info")
+
+ mkosi sandbox lcov --ignore-errors inconsistent,inconsistent "${lcov_args[@]}" --output-file build/test/coverage/everything.coverage-info
+
+ - name: List coverage report
+ run: mkosi sandbox lcov --ignore-errors inconsistent,inconsistent --list build/test/coverage/everything.coverage-info
+
+ - name: Coveralls
+ uses: coverallsapp/github-action@cfd0633edbd2411b532b808ba7a8b5e04f76d2c8
+ if: github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable'
+ with:
+ file: build/test/coverage/everything.coverage-info
diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml
index 9e20a63179..156b8bae89 100644
--- a/.github/workflows/mkosi.yml
+++ b/.github/workflows/mkosi.yml
@@ -105,7 +105,7 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- - uses: systemd/mkosi@0825cca8084674ec8fa27502134b1bc601f79e0c
+ - uses: systemd/mkosi@07ef37c4c0dad5dfc6cec86c967a7600df1cd88c
# Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space
# immediately, we remove the files in the background. However, we first move them to a different location
diff --git a/mkosi.conf b/mkosi.conf
index 835b1d4b9c..35a19a27aa 100644
--- a/mkosi.conf
+++ b/mkosi.conf
@@ -22,6 +22,7 @@ PassEnvironment=
SYSEXT
WITH_DEBUG
ASAN_OPTIONS
+ COVERAGE
[Output]
RepartDirectories=mkosi.repart
@@ -150,3 +151,4 @@ QemuKvm=yes
[Include]
Include=%D/mkosi.sanitizers
+ %D/mkosi.coverage
diff --git a/mkosi.conf.d/05-tools/mkosi.conf b/mkosi.conf.d/05-tools/mkosi.conf
index 746dd37870..15c336a304 100644
--- a/mkosi.conf.d/05-tools/mkosi.conf
+++ b/mkosi.conf.d/05-tools/mkosi.conf
@@ -4,5 +4,8 @@
ToolsTreePackages=
gcc
gperf
+ lcov
+ llvm
meson
pkgconf
+ rsync
diff --git a/mkosi.conf.d/05-tools/mkosi.conf.d/arch.conf b/mkosi.conf.d/05-tools/mkosi.conf.d/arch.conf
index 7aba50248a..5787aa8f44 100644
--- a/mkosi.conf.d/05-tools/mkosi.conf.d/arch.conf
+++ b/mkosi.conf.d/05-tools/mkosi.conf.d/arch.conf
@@ -10,6 +10,7 @@ ToolsTreePackages=
libcap
libmicrohttpd
mypy
+ perl-json-xs
python-jinja
python-pytest
ruff
diff --git a/mkosi.coverage/mkosi.conf b/mkosi.coverage/mkosi.conf
new file mode 100644
index 0000000000..a9224195b4
--- /dev/null
+++ b/mkosi.coverage/mkosi.conf
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Match]
+Environment=COVERAGE=1
+
+[Content]
+KernelCommandLine=
+ COVERAGE_BUILD_DIR=/coverage
+ systemd.setenv=COVERAGE_BUILD_DIR=/coverage
diff --git a/mkosi.coverage/mkosi.postinst b/mkosi.coverage/mkosi.postinst
new file mode 100755
index 0000000000..ccb153f76d
--- /dev/null
+++ b/mkosi.coverage/mkosi.postinst
@@ -0,0 +1,56 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -e
+
+(
+ shopt -s nullglob
+ rm -f "$BUILDROOT"/coverage/*.gcda
+)
+
+# When using -fprofile-dir=, GCC creates all gcda files under the given directory at the same location as the
+# gcno file in the build directory, but with each '/' replaced with '#'. LLVM creates each gcda file under
+# the given directory without replacing each '/' with '#'. Because we want all processes to be able to write
+# gcda files under /coverage regardless of which user they are running as, we pre-create all files under
+# /coverage and make them world readable and writable so that we don't have to mess with umasks for each
+# process that writes to /coverage.
+if ((LLVM)); then
+ rsync --recursive --include='*/' --exclude='*' --relative "$BUILDDIR" "$BUILDROOT/coverage"
+ find "$BUILDDIR" -name '*.gcno' | sed 's/gcno/gcda/' | xargs -I '{}' touch "$BUILDROOT/coverage/{}"
+else
+ find "$BUILDDIR" -name '*.gcno' | sed 's/gcno/gcda/' | sed 's/\//#/g' | xargs -I '{}' touch "$BUILDROOT/coverage/{}"
+fi
+
+chmod --recursive 777 "$BUILDROOT/coverage"
+
+# When built with gcov, disable ProtectSystem= and ProtectHome= in the test images, since it prevents gcov to
+# write the coverage reports (*.gcda files).
+mkdir -p "$BUILDROOT/usr/lib/systemd/system/service.d/"
+cat >"$BUILDROOT/usr/lib/systemd/system/service.d/99-gcov-override.conf" <<EOF
+[Service]
+ProtectSystem=no
+ProtectHome=no
+EOF
+
+# Similarly, set ReadWritePaths= to the coverage directory in the test image to make the coverage work with
+# units using DynamicUser=yes. Do this only for services with test- prefix and a couple of known-to-use
+# DynamicUser=yes services, as setting this system-wide has many undesirable side-effects, as it creates its
+# own namespace.
+for service in capsule@ test- systemd-journal-{gatewayd,upload}; do
+ mkdir -p "$BUILDROOT/usr/lib/systemd/system/$service.service.d/"
+ cat >"$BUILDROOT/usr/lib/systemd/system/$service.service.d/99-gcov-rwpaths-override.conf" <<EOF
+[Service]
+ReadWritePaths=/coverage
+EOF
+done
+
+# Ditto, but for the user daemon.
+mkdir -p "$BUILDROOT/usr/lib/systemd/user/test-.service.d/"
+cat >"$BUILDROOT/usr/lib/systemd/user/test-.service.d/99-gcov-rwpaths-override.conf" <<EOF
+[Service]
+ReadWritePaths=/coverage
+EOF
+
+# Bind the coverage directory into nspawn containers that are executed using machinectl. Unfortunately, the
+# .nspawn files don't support drop-ins so we have to inject the bind mount directly into the
+# systemd-nspawn@.service unit.
+sed -ri "s/^ExecStart=.+$/& --bind=\/coverage/" "$BUILDROOT/usr/lib/systemd/system/systemd-nspawn@.service"
diff --git a/mkosi.extra.common/usr/lib/systemd/coverage-forwarder b/mkosi.extra.common/usr/lib/systemd/coverage-forwarder
new file mode 100755
index 0000000000..e6c7a88a4d
--- /dev/null
+++ b/mkosi.extra.common/usr/lib/systemd/coverage-forwarder
@@ -0,0 +1,9 @@
+#!/bin/bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+logger --journald <<EOF
+MESSAGE=Tarball with coverage data from /coverage
+COVERAGE_TAR=$(tar --create --file - --directory /coverage --zstd . | base64 --wrap=0)
+EOF
+
+journalctl --flush
diff --git a/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset b/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset
index 5a15e6bcbb..269692b646 100644
--- a/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset
+++ b/mkosi.extra.common/usr/lib/systemd/system-preset/00-mkosi.preset
@@ -39,3 +39,5 @@ disable iscsiuio.socket
# mkosi relabels the image itself so no need to do it on boot.
disable selinux-autorelabel-mark.service
+
+enable coverage-forwarder.service
diff --git a/mkosi.extra.common/usr/lib/systemd/system/coverage-forwarder.service b/mkosi.extra.common/usr/lib/systemd/system/coverage-forwarder.service
new file mode 100644
index 0000000000..b332f7ec58
--- /dev/null
+++ b/mkosi.extra.common/usr/lib/systemd/system/coverage-forwarder.service
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+[Unit]
+Description=Forward coverage data to the journal before shutting down
+ConditionEnvironment=COVERAGE_BUILD_DIR
+
+DefaultDependencies=no
+After=systemd-journald.socket
+Requires=systemd-journald.socket
+After=shutdown.target initrd-switch-root.target
+Before=final.target initrd-switch-root.service
+
+[Service]
+Type=oneshot
+ExecStart=/usr/lib/systemd/coverage-forwarder
+
+[Install]
+WantedBy=final.target initrd-switch-root.target
diff --git a/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot b/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot
index 6c66888afe..83c4960ac8 100755
--- a/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot
+++ b/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot
@@ -32,6 +32,10 @@ MKOSI_MESON_OPTIONS="-D mode=developer -D b_sanitize=${SANITIZERS:-none}"
if ((WIPE)) && [[ -d "$BUILDDIR/meson-private" ]]; then
MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS --wipe"
fi
+if ((COVERAGE)); then
+ MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS -D b_coverage=true"
+ MKOSI_CFLAGS="$MKOSI_CFLAGS -fprofile-dir=/coverage"
+fi
# Override the default options. We specifically disable "strip", "zipman" and "lto" as they slow down builds
# significantly. OPTIONS= cannot be overridden on the makepkg command line so we append to /etc/makepkg.conf
diff --git a/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.build.chroot b/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.build.chroot
index 1c019e162c..1de1578e20 100755
--- a/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.build.chroot
+++ b/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.build.chroot
@@ -52,6 +52,10 @@ MKOSI_MESON_OPTIONS="-D mode=developer -D b_sanitize=${SANITIZERS:-none}"
if ((WIPE)) && [[ -d "$BUILDDIR/meson-private" ]]; then
MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS --wipe"
fi
+if ((COVERAGE)); then
+ MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS -D b_coverage=true"
+ MKOSI_CFLAGS="$MKOSI_CFLAGS -fprofile-dir=/coverage"
+fi
(
shopt -s nullglob
diff --git a/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot b/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot
index 45b9bd06af..5f3e53ff53 100755
--- a/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot
+++ b/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot
@@ -48,6 +48,10 @@ MKOSI_MESON_OPTIONS="-D mode=developer -D b_sanitize=${SANITIZERS:-none}"
if ((WIPE)) && [[ -d "$BUILDDIR/meson-private" ]]; then
MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS --wipe"
fi
+if ((COVERAGE)); then
+ MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS -D b_coverage=true"
+ MKOSI_CFLAGS="$MKOSI_CFLAGS -fprofile-dir=/coverage"
+fi
# TODO: Drop GENSYMBOLS_LEVEL once https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=986746 is fixed.
build() {
diff --git a/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot b/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot
index 6c1cf2aed4..7349038638 100755
--- a/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot
+++ b/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot
@@ -52,6 +52,10 @@ MKOSI_MESON_OPTIONS="-D mode=developer -D b_sanitize=${SANITIZERS:-none}"
if ((WIPE)) && [[ -d "$BUILDDIR/meson-private" ]]; then
MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS --wipe"
fi
+if ((COVERAGE)); then
+ MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS -D b_coverage=true"
+ MKOSI_CFLAGS="$MKOSI_CFLAGS -fprofile-dir=/coverage"
+fi
# TODO: Drop when the spec is fixed (either the patch is adapted or not applied when building for upstream).
sed --in-place '/0009-pid1-handle-console-specificities-weirdness-for-s390.patch/d' "pkg/$PKG_SUBDIR/systemd.spec"
diff --git a/mkosi.images/initrd/mkosi.conf b/mkosi.images/initrd/mkosi.conf
index b76b47ecda..ac66dd933b 100644
--- a/mkosi.images/initrd/mkosi.conf
+++ b/mkosi.images/initrd/mkosi.conf
@@ -4,6 +4,7 @@
Include=
mkosi-initrd
%D/mkosi.sanitizers
+ %D/mkosi.coverage
[Content]
ExtraTrees=%D/mkosi.extra.common
@@ -12,3 +13,4 @@ Packages=
findutils
grep
sed
+ tar
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
index 762dfd675a..6ee18838cc 100644
--- a/src/shared/creds-util.c
+++ b/src/shared/creds-util.c
@@ -853,7 +853,8 @@ int encrypt_credential_and_warn(
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) {
if (!uid_is_valid(uid))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Scoped credential selected, but no UID specified.");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Scoped credential key type "SD_ID128_FORMAT_STR" selected, but no UID specified.", SD_ID128_FORMAT_VAL(with_key));
} else
uid = UID_INVALID;
diff --git a/src/test/test-creds.c b/src/test/test-creds.c
index cc9cc73778..e82c8fd755 100644
--- a/src/test/test-creds.c
+++ b/src/test/test-creds.c
@@ -20,104 +20,104 @@ TEST(read_credential_strings) {
const char *e = getenv("CREDENTIALS_DIRECTORY");
if (e)
- assert_se(saved = strdup(e));
+ ASSERT_NOT_NULL(saved = strdup(e));
- assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0);
+ ASSERT_OK_ZERO(read_credential_strings_many("foo", &x, "bar", &y));
ASSERT_NULL(x);
ASSERT_NULL(y);
- assert_se(mkdtemp_malloc(NULL, &tmp) >= 0);
+ ASSERT_OK(mkdtemp_malloc(NULL, &tmp));
- assert_se(setenv("CREDENTIALS_DIRECTORY", tmp, /* override= */ true) >= 0);
+ ASSERT_OK_ERRNO(setenv("CREDENTIALS_DIRECTORY", tmp, /* override= */ true));
- assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0);
+ ASSERT_OK_ZERO(read_credential_strings_many("foo", &x, "bar", &y));
ASSERT_NULL(x);
ASSERT_NULL(y);
- assert_se(p = path_join(tmp, "bar"));
- assert_se(write_string_file(p, "piff", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
+ ASSERT_NOT_NULL(p = path_join(tmp, "bar"));
+ ASSERT_OK(write_string_file(p, "piff", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE));
- assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0);
+ ASSERT_OK_ZERO(read_credential_strings_many("foo", &x, "bar", &y));
ASSERT_NULL(x);
ASSERT_STREQ(y, "piff");
- assert_se(write_string_file(p, "paff", WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
+ ASSERT_OK(write_string_file(p, "paff", WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_AVOID_NEWLINE));
- assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0);
+ ASSERT_OK_ZERO(read_credential_strings_many("foo", &x, "bar", &y));
ASSERT_NULL(x);
ASSERT_STREQ(y, "paff");
p = mfree(p);
- assert_se(p = path_join(tmp, "foo"));
- assert_se(write_string_file(p, "knurz", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0);
+ ASSERT_NOT_NULL(p = path_join(tmp, "foo"));
+ ASSERT_OK(write_string_file(p, "knurz", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE));
- assert_se(read_credential_strings_many("foo", &x, "bar", &y) >= 0);
+ ASSERT_OK(read_credential_strings_many("foo", &x, "bar", &y));
ASSERT_STREQ(x, "knurz");
ASSERT_STREQ(y, "paff");
p = mfree(p);
- assert_se(p = path_join(tmp, "bazz"));
- assert_se(f = fopen(p, "w"));
- assert_se(fwrite("x\0y", 1, 3, f) == 3); /* embedded NUL byte should result in EBADMSG when reading back with read_credential_strings_many() */
+ ASSERT_NOT_NULL(p = path_join(tmp, "bazz"));
+ ASSERT_NOT_NULL(f = fopen(p, "w"));
+ ASSERT_EQ(fwrite("x\0y", 1, 3, f), 3UL); /* embedded NUL byte should result in EBADMSG when reading back with read_credential_strings_many() */
f = safe_fclose(f);
y = mfree(y);
- assert_se(read_credential_strings_many("bazz", &x, "bar", &y) == -EBADMSG);
+ ASSERT_ERROR(read_credential_strings_many("bazz", &x, "bar", &y), EBADMSG);
ASSERT_STREQ(x, "knurz");
ASSERT_STREQ(y, "paff");
if (saved)
- assert_se(setenv("CREDENTIALS_DIRECTORY", saved, /* override= */ 1) >= 0);
+ ASSERT_OK_ERRNO(setenv("CREDENTIALS_DIRECTORY", saved, /* override= */ 1));
else
- assert_se(unsetenv("CREDENTIALS_DIRECTORY") >= 0);
+ ASSERT_OK_ERRNO(unsetenv("CREDENTIALS_DIRECTORY"));
}
TEST(credential_name_valid) {
char buf[NAME_MAX+2];
- assert_se(!credential_name_valid(NULL));
- assert_se(!credential_name_valid(""));
- assert_se(!credential_name_valid("."));
- assert_se(!credential_name_valid(".."));
- assert_se(!credential_name_valid("foo/bar"));
- assert_se(credential_name_valid("foo"));
+ ASSERT_FALSE(credential_name_valid(NULL));
+ ASSERT_FALSE(credential_name_valid(""));
+ ASSERT_FALSE(credential_name_valid("."));
+ ASSERT_FALSE(credential_name_valid(".."));
+ ASSERT_FALSE(credential_name_valid("foo/bar"));
+ ASSERT_TRUE(credential_name_valid("foo"));
memset(buf, 'x', sizeof(buf)-1);
buf[sizeof(buf)-1] = 0;
- assert_se(!credential_name_valid(buf));
+ ASSERT_FALSE(credential_name_valid(buf));
buf[sizeof(buf)-2] = 0;
- assert_se(credential_name_valid(buf));
+ ASSERT_TRUE(credential_name_valid(buf));
}
TEST(credential_glob_valid) {
char buf[NAME_MAX+2];
- assert_se(!credential_glob_valid(NULL));
- assert_se(!credential_glob_valid(""));
- assert_se(!credential_glob_valid("."));
- assert_se(!credential_glob_valid(".."));
- assert_se(!credential_glob_valid("foo/bar"));
- assert_se(credential_glob_valid("foo"));
- assert_se(credential_glob_valid("foo*"));
- assert_se(credential_glob_valid("x*"));
- assert_se(credential_glob_valid("*"));
- assert_se(!credential_glob_valid("?"));
- assert_se(!credential_glob_valid("*a"));
- assert_se(!credential_glob_valid("a?"));
- assert_se(!credential_glob_valid("a[abc]"));
- assert_se(!credential_glob_valid("a[abc]"));
+ ASSERT_FALSE(credential_glob_valid(NULL));
+ ASSERT_FALSE(credential_glob_valid(""));
+ ASSERT_FALSE(credential_glob_valid("."));
+ ASSERT_FALSE(credential_glob_valid(".."));
+ ASSERT_FALSE(credential_glob_valid("foo/bar"));
+ ASSERT_TRUE(credential_glob_valid("foo"));
+ ASSERT_TRUE(credential_glob_valid("foo*"));
+ ASSERT_TRUE(credential_glob_valid("x*"));
+ ASSERT_TRUE(credential_glob_valid("*"));
+ ASSERT_FALSE(credential_glob_valid("?"));
+ ASSERT_FALSE(credential_glob_valid("*a"));
+ ASSERT_FALSE(credential_glob_valid("a?"));
+ ASSERT_FALSE(credential_glob_valid("a[abc]"));
+ ASSERT_FALSE(credential_glob_valid("a[abc]"));
memset(buf, 'x', sizeof(buf)-1);
buf[sizeof(buf)-1] = 0;
- assert_se(!credential_glob_valid(buf));
+ ASSERT_FALSE(credential_glob_valid(buf));
buf[sizeof(buf)-2] = 0;
- assert_se(credential_glob_valid(buf));
+ ASSERT_TRUE(credential_glob_valid(buf));
buf[sizeof(buf)-2] = '*';
- assert_se(credential_glob_valid(buf));
+ ASSERT_TRUE(credential_glob_valid(buf));
}
static void test_encrypt_decrypt_with(sd_id128_t mode, uid_t uid) {
@@ -152,7 +152,7 @@ static void test_encrypt_decrypt_with(sd_id128_t mode, uid_t uid) {
return;
}
- assert_se(r >= 0);
+ ASSERT_OK(r);
_cleanup_(iovec_done) struct iovec decrypted = {};
r = decrypt_credential_and_warn(
@@ -164,7 +164,7 @@ static void test_encrypt_decrypt_with(sd_id128_t mode, uid_t uid) {
&encrypted,
CREDENTIAL_ALLOW_NULL,
&decrypted);
- assert_se(r == -EREMOTE); /* name didn't match */
+ ASSERT_ERROR(r, EREMOTE); /* name didn't match */
r = decrypt_credential_and_warn(
"foo",
@@ -175,9 +175,9 @@ static void test_encrypt_decrypt_with(sd_id128_t mode, uid_t uid) {
&encrypted,
CREDENTIAL_ALLOW_NULL,
&decrypted);
- assert_se(r >= 0);
+ ASSERT_OK(r);
- assert_se(iovec_memcmp(&plaintext, &decrypted) == 0);
+ ASSERT_EQ(iovec_memcmp(&plaintext, &decrypted), 0);
}
static bool try_tpm2(void) {
@@ -203,17 +203,17 @@ TEST(credential_encrypt_decrypt) {
test_encrypt_decrypt_with(CRED_AES256_GCM_BY_NULL, UID_INVALID);
- assert_se(mkdtemp_malloc(NULL, &d) >= 0);
+ ASSERT_OK(mkdtemp_malloc(NULL, &d));
j = path_join(d, "secret");
- assert_se(j);
+ ASSERT_NOT_NULL(j);
const char *e = getenv("SYSTEMD_CREDENTIAL_SECRET");
_cleanup_free_ char *ec = NULL;
if (e)
- assert_se(ec = strdup(e));
+ ASSERT_NOT_NULL(ec = strdup(e));
- assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", j, true) >= 0);
+ ASSERT_OK_ERRNO(setenv("SYSTEMD_CREDENTIAL_SECRET", j, true));
test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST, UID_INVALID);
test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST_SCOPED, 0);
@@ -225,7 +225,7 @@ TEST(credential_encrypt_decrypt) {
}
if (ec)
- assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", ec, true) >= 0);
+ ASSERT_OK_ERRNO(setenv("SYSTEMD_CREDENTIAL_SECRET", ec, true));
}
TEST(mime_type_matches) {
@@ -246,10 +246,10 @@ TEST(mime_type_matches) {
FOREACH_ELEMENT(t, tags) {
_cleanup_free_ char *encoded = NULL;
- assert_se(base64mem(t, sizeof(sd_id128_t), &encoded) >= 0);
+ ASSERT_OK(base64mem(t, sizeof(sd_id128_t), &encoded));
/* Validate that the size matches expectations for the 4/3 factor size increase (rounding up) */
- assert_se(strlen(encoded) == DIV_ROUND_UP((128U / 8U), 3U) * 4U);
+ ASSERT_EQ(strlen(encoded), DIV_ROUND_UP((128U / 8U), 3U) * 4U);
/* Cut off rounded string where the ID ends, but now round down to get rid of characters that might contain follow-up data */
encoded[128 / 6] = 0;
diff --git a/src/test/test-execute.c b/src/test/test-execute.c
index 95ccf5490d..de575ec1e6 100644
--- a/src/test/test-execute.c
+++ b/src/test/test-execute.c
@@ -1448,8 +1448,10 @@ static int prepare_ns(const char *process_name) {
_cleanup_free_ char *unit_dir = NULL, *build_dir = NULL, *build_dir_mount = NULL;
int ret;
- /* Make "/" read-only. */
- ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL));
+ const char *coverage = getenv("COVERAGE_BUILD_DIR");
+ if (!coverage)
+ /* Make "/" read-only. */
+ ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL));
/* Creating a new user namespace in the above means all MS_SHARED mounts become MS_SLAVE.
* Let's put them back to MS_SHARED here, since that's what we want as defaults. (This will
diff --git a/test/integration-test-wrapper.py b/test/integration-test-wrapper.py
index a3f90dc9fa..09dcda92e1 100755
--- a/test/integration-test-wrapper.py
+++ b/test/integration-test-wrapper.py
@@ -4,12 +4,15 @@
"""Test wrapper command for driving integration tests."""
import argparse
+import base64
+import dataclasses
import json
import os
import re
import shlex
import subprocess
import sys
+import tempfile
import textwrap
from pathlib import Path
@@ -33,6 +36,47 @@ ExecStart=false
"""
+def sandbox(args: argparse.Namespace) -> list[str]:
+ return [
+ args.mkosi,
+ '--directory', os.fspath(args.meson_source_dir),
+ '--extra-search-path', os.fspath(args.meson_build_dir),
+ 'sandbox',
+ ] # fmt: skip
+
+
+@dataclasses.dataclass(frozen=True)
+class Summary:
+ distribution: str
+ release: str
+ architecture: str
+ builddir: Path
+ environment: dict[str, str]
+
+ @classmethod
+ def get(cls, args: argparse.Namespace) -> 'Summary':
+ j = json.loads(
+ subprocess.run(
+ [
+ args.mkosi,
+ '--directory', os.fspath(args.meson_source_dir),
+ '--json',
+ 'summary',
+ ],
+ stdout=subprocess.PIPE,
+ text=True,
+ ).stdout
+ ) # fmt: skip
+
+ return Summary(
+ distribution=j['Images'][-1]['Distribution'],
+ release=j['Images'][-1]['Release'],
+ architecture=j['Images'][-1]['Architecture'],
+ builddir=Path(j['Images'][-1]['BuildDirectory']),
+ environment=j['Images'][-1]['Environment'],
+ )
+
+
def process_coredumps(args: argparse.Namespace, journal_file: Path) -> bool:
# Collect executable paths of all coredumps and filter out the expected ones.
@@ -42,11 +86,7 @@ def process_coredumps(args: argparse.Namespace, journal_file: Path) -> bool:
exclude_regex = None
result = subprocess.run(
- [
- args.mkosi,
- '--directory', os.fspath(args.meson_source_dir),
- '--extra-search-path', os.fspath(args.meson_build_dir),
- 'sandbox',
+ sandbox(args) + [
'coredumpctl',
'--file', journal_file,
'--json=short',
@@ -69,11 +109,7 @@ def process_coredumps(args: argparse.Namespace, journal_file: Path) -> bool:
return False
subprocess.run(
- [
- args.mkosi,
- '--directory', os.fspath(args.meson_source_dir),
- '--extra-search-path', os.fspath(args.meson_build_dir),
- 'sandbox',
+ sandbox(args) + [
'coredumpctl',
'--file', journal_file,
'--no-pager',
@@ -86,6 +122,119 @@ def process_coredumps(args: argparse.Namespace, journal_file: Path) -> bool:
return True
+def process_coverage(args: argparse.Namespace, summary: Summary, name: str, journal_file: Path) -> None:
+ coverage = subprocess.run(
+ sandbox(args) + [
+ 'journalctl',
+ '--file', journal_file,
+ '--field=COVERAGE_TAR',
+ ],
+ stdout=subprocess.PIPE,
+ text=True,
+ check=True,
+ ).stdout # fmt: skip
+
+ (args.meson_build_dir / 'test/coverage').mkdir(exist_ok=True)
+
+ initial = args.meson_build_dir / 'test/coverage/initial.coverage-info'
+ output = args.meson_build_dir / f'test/coverage/{name}.coverage-info'
+
+ for b64 in coverage.splitlines():
+ tarball = base64.b64decode(b64)
+
+ with tempfile.TemporaryDirectory(prefix='coverage-') as tmp:
+ subprocess.run(
+ sandbox(args) + [
+ 'tar',
+ '--extract',
+ '--file', '-',
+ '--directory', tmp,
+ '--keep-directory-symlink',
+ '--no-overwrite-dir',
+ '--zstd',
+ ],
+ input=tarball,
+ check=True,
+ ) # fmt: skip
+
+ for p in Path(tmp).iterdir():
+ if not p.name.startswith('#'):
+ continue
+
+ dst = Path(tmp) / p.name.replace('#', '/').lstrip('/')
+ dst.parent.mkdir(parents=True, exist_ok=True)
+ p.rename(dst)
+
+ subprocess.run(
+ sandbox(args) + [
+ 'find',
+ tmp,
+ '-name', '*.gcda',
+ '-size', '0',
+ '-delete',
+ ],
+ input=tarball,
+ check=True,
+ ) # fmt: skip
+
+ subprocess.run(
+ sandbox(args)
+ + [
+ 'rsync',
+ '--archive',
+ '--prune-empty-dirs',
+ '--include=*/',
+ '--include=*.gcno',
+ '--exclude=*',
+ f'{os.fspath(args.meson_build_dir / summary.builddir)}/',
+ os.fspath(Path(tmp) / 'work/build'),
+ ],
+ check=True,
+ )
+
+ subprocess.run(
+ sandbox(args)
+ + [
+ 'lcov',
+ *(
+ [
+ '--gcov-tool', 'llvm-cov',
+ '--gcov-tool', 'gcov',
+ ]
+ if summary.environment.get('LLVM', '0') == '1'
+ else []
+ ),
+ '--directory', tmp,
+ '--base-directory', 'src/',
+ '--capture',
+ '--exclude', '*.gperf',
+ '--output-file', f'{output}.new',
+ '--ignore-errors', 'inconsistent,inconsistent,source,negative',
+ '--substitute', 's#src/src#src#g',
+ '--no-external',
+ '--quiet',
+ ],
+ check=True,
+ ) # fmt: skip
+
+ subprocess.run(
+ sandbox(args)
+ + [
+ 'lcov',
+ '--ignore-errors', 'inconsistent,inconsistent,format,corrupt,empty',
+ '--add-tracefile', output if output.exists() else initial,
+ '--add-tracefile', f'{output}.new',
+ '--output-file', output,
+ '--quiet',
+ ],
+ check=True,
+ ) # fmt: skip
+
+ Path(f'{output}.new').unlink()
+
+ print(f'Wrote coverage report for {name} to {output}', file=sys.stderr)
+
+
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--mkosi', required=True)
@@ -127,6 +276,7 @@ def main() -> None:
keep_journal = os.getenv('TEST_SAVE_JOURNAL', 'fail')
shell = bool(int(os.getenv('TEST_SHELL', '0')))
+ summary = Summary.get(args)
if shell and not sys.stderr.isatty():
print(
@@ -250,6 +400,13 @@ def main() -> None:
coredumps = process_coredumps(args, journal_file)
+ if (
+ summary.environment.get('COVERAGE', '0') == '1'
+ and result.returncode in (args.exit_code, 77)
+ and not coredumps
+ ):
+ process_coverage(args, summary, name, journal_file)
+
if keep_journal == '0' or (
keep_journal == 'fail' and result.returncode in (args.exit_code, 77) and not coredumps
):
@@ -262,22 +419,11 @@ def main() -> None:
if os.getenv('GITHUB_ACTIONS'):
id = os.environ['GITHUB_RUN_ID']
+ workflow = os.environ['GITHUB_WORKFLOW']
iteration = os.environ['GITHUB_RUN_ATTEMPT']
- j = json.loads(
- subprocess.run(
- [
- args.mkosi,
- '--directory', os.fspath(args.meson_source_dir),
- '--json',
- 'summary',
- ],
- stdout=subprocess.PIPE,
- text=True,
- ).stdout
- ) # fmt: skip
- distribution = j['Images'][-1]['Distribution']
- release = j['Images'][-1]['Release']
- artifact = f'ci-mkosi-{id}-{iteration}-{distribution}-{release}-failed-test-journals'
+ artifact = (
+ f'ci-{workflow}-{id}-{iteration}-{summary.distribution}-{summary.release}-failed-test-journals'
+ )
ops += [f'gh run download {id} --name {artifact} -D ci/{artifact}']
journal_file = Path(f'ci/{artifact}/test/journal/{name}.journal')
diff --git a/test/test-execute/exec-ambientcapabilities-dynuser.service b/test/test-execute/exec-ambientcapabilities-dynuser.service
index ab815f39a3..b927c7dbca 100644
--- a/test/test-execute/exec-ambientcapabilities-dynuser.service
+++ b/test/test-execute/exec-ambientcapabilities-dynuser.service
@@ -9,3 +9,4 @@ AmbientCapabilities=CAP_CHOWN CAP_SETUID CAP_NET_RAW
DynamicUser=yes
PrivateUsers=yes
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
+ReadWritePaths=-/coverage
diff --git a/test/test-execute/exec-dynamicuser-fixeduser-adm.service b/test/test-execute/exec-dynamicuser-fixeduser-adm.service
index 1b7f232cd1..3a7f8aef60 100644
--- a/test/test-execute/exec-dynamicuser-fixeduser-adm.service
+++ b/test/test-execute/exec-dynamicuser-fixeduser-adm.service
@@ -10,3 +10,4 @@ ExecStart=sh -x -c 'test "$$(id -nG)" = "adm" && test "$$(id -ng)" = "adm" && te
ExecStart=sh -x -c 'test "$$(id -nG)" = "adm" && test "$$(id -ng)" = "adm" && test "$$(id -nu)" = "adm"'
DynamicUser=yes
User=adm
+ReadWritePaths=-/coverage
diff --git a/test/test-execute/exec-dynamicuser-fixeduser-games.service b/test/test-execute/exec-dynamicuser-fixeduser-games.service
index b13c23a74d..40048d27a8 100644
--- a/test/test-execute/exec-dynamicuser-fixeduser-games.service
+++ b/test/test-execute/exec-dynamicuser-fixeduser-games.service
@@ -10,3 +10,4 @@ ExecStart=sh -x -c 'test "$$(id -nG)" = "games" && test "$$(id -ng)" = "games" &
ExecStart=sh -x -c 'test "$$(id -nG)" = "games" && test "$$(id -ng)" = "games" && test "$$(id -nu)" = "games"'
DynamicUser=yes
User=games
+ReadWritePaths=-/coverage
diff --git a/test/test-execute/exec-dynamicuser-fixeduser-one-supplementarygroup.service b/test/test-execute/exec-dynamicuser-fixeduser-one-supplementarygroup.service
index e494c33551..e58b524033 100644
--- a/test/test-execute/exec-dynamicuser-fixeduser-one-supplementarygroup.service
+++ b/test/test-execute/exec-dynamicuser-fixeduser-one-supplementarygroup.service
@@ -9,3 +9,4 @@ Type=oneshot
User=1
DynamicUser=yes
SupplementaryGroups=1
+ReadWritePaths=-/coverage
diff --git a/test/test-execute/exec-dynamicuser-fixeduser.service b/test/test-execute/exec-dynamicuser-fixeduser.service
index 4ebfc20cde..8e5244d891 100644
--- a/test/test-execute/exec-dynamicuser-fixeduser.service
+++ b/test/test-execute/exec-dynamicuser-fixeduser.service
@@ -8,3 +8,4 @@ ExecStart=sh -x -c 'test "$$(id -g)" = "1" && test "$$(id -u)" = "1"'
Type=oneshot
User=1
DynamicUser=yes
+ReadWritePaths=-/coverage
diff --git a/test/test-execute/exec-dynamicuser-runtimedirectory1.service b/test/test-execute/exec-dynamicuser-runtimedirectory1.service
index 59d3bf0884..671b316736 100644
--- a/test/test-execute/exec-dynamicuser-runtimedirectory1.service
+++ b/test/test-execute/exec-dynamicuser-runtimedirectory1.service
@@ -11,3 +11,4 @@ RuntimeDirectory=test-exec_runtimedirectorypreserve
RuntimeDirectoryPreserve=yes
DynamicUser=yes
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
+ReadWritePaths=-/coverage
diff --git a/test/test-execute/exec-dynamicuser-runtimedirectory2.service b/test/test-execute/exec-dynamicuser-runtimedirectory2.service
index 6ff9d7503a..cdb80848e3 100644
--- a/test/test-execute/exec-dynamicuser-runtimedirectory2.service
+++ b/test/test-execute/exec-dynamicuser-runtimedirectory2.service
@@ -12,3 +12,4 @@ RuntimeDirectory=test-exec_runtimedirectorypreserve
RuntimeDirectoryPreserve=yes
DynamicUser=yes
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
+ReadWritePaths=-/coverage
diff --git a/test/test-execute/exec-dynamicuser-runtimedirectory3.service b/test/test-execute/exec-dynamicuser-runtimedirectory3.service
index cebb819476..51a9e44c6f 100644
--- a/test/test-execute/exec-dynamicuser-runtimedirectory3.service
+++ b/test/test-execute/exec-dynamicuser-runtimedirectory3.service
@@ -11,3 +11,4 @@ Type=oneshot
RuntimeDirectory=test-exec_runtimedirectorypreserve
DynamicUser=yes
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
+ReadWritePaths=-/coverage
diff --git a/test/test-execute/exec-dynamicuser-statedir-migrate-step2.service b/test/test-execute/exec-dynamicuser-statedir-migrate-step2.service
index 7261f4a174..f22862378c 100644
--- a/test/test-execute/exec-dynamicuser-statedir-migrate-step2.service
+++ b/test/test-execute/exec-dynamicuser-statedir-migrate-step2.service
@@ -25,3 +25,4 @@ Type=oneshot
DynamicUser=yes
StateDirectory=test-dynamicuser-migrate test-dynamicuser-migrate2/hoge
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
+ReadWritePaths=-/coverage
diff --git a/test/test-execute/exec-dynamicuser-statedir.service b/test/test-execute/exec-dynamicuser-statedir.service
index 636a70259c..1e4fe818ac 100644
--- a/test/test-execute/exec-dynamicuser-statedir.service
+++ b/test/test-execute/exec-dynamicuser-statedir.service
@@ -84,3 +84,4 @@ Type=oneshot
DynamicUser=yes
StateDirectory=waldo quux/pief aaa/bbb aaa aaa/ccc xxx/yyy:aaa/111 xxx:aaa/222 xxx/zzz:aaa/333 abc:d\:ef
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
+ReadWritePaths=-/coverage
diff --git a/test/test-execute/exec-dynamicuser-supplementarygroups.service b/test/test-execute/exec-dynamicuser-supplementarygroups.service
index be1b8f76f2..fd88a790e4 100644
--- a/test/test-execute/exec-dynamicuser-supplementarygroups.service
+++ b/test/test-execute/exec-dynamicuser-supplementarygroups.service
@@ -9,3 +9,4 @@ Type=oneshot
DynamicUser=yes
SupplementaryGroups=1 2
EnvironmentFile=-/usr/lib/systemd/systemd-asan-env
+ReadWritePaths=-/coverage
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 215f3cb1cc..1fd1b2290f 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -8655,7 +8655,7 @@ if __name__ == '__main__':
asan_options = ns.asan_options
lsan_options = ns.lsan_options
ubsan_options = ns.ubsan_options
- with_coverage = ns.with_coverage
+ with_coverage = ns.with_coverage or "COVERAGE_BUILD_DIR" in os.environ
show_journal = ns.show_journal
if use_valgrind:
diff --git a/test/units/TEST-38-FREEZER.sh b/test/units/TEST-38-FREEZER.sh
index 07597843e2..4c483df46a 100755
--- a/test/units/TEST-38-FREEZER.sh
+++ b/test/units/TEST-38-FREEZER.sh
@@ -7,6 +7,11 @@ set -o pipefail
# shellcheck source=test/units/test-control.sh
. "$(dirname "$0")"/test-control.sh
+if [[ -n "${COVERAGE_BUILD_DIR:-}" ]]; then
+ echo "TEST-38-FREEZER freezes when systemd is built with coverage enabled" >/skipped
+ exit 77
+fi
+
systemd-analyze log-level debug
unit=TEST-38-FREEZER-sleep.service