diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7a968ac95b..5143ea73e1 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - uses: systemd/mkosi@77fce77807a9a92bc37edc8f1c967102e6236d94 + - uses: systemd/mkosi@444d247d1a1328bcfb84a945e84959bd4bd0e02d # 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/.github/workflows/linter.yml b/.github/workflows/linter.yml index 36c0bc9e33..96ed7dec96 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -40,7 +40,7 @@ jobs: GITHUB_ACTIONS_CONFIG_FILE: actionlint.yml ENABLE_GITHUB_PULL_REQUEST_SUMMARY_COMMENT: false - - uses: systemd/mkosi@77fce77807a9a92bc37edc8f1c967102e6236d94 + - uses: systemd/mkosi@444d247d1a1328bcfb84a945e84959bd4bd0e02d - name: Check that tabs are not used in Python code run: sh -c '! git grep -P "\\t" -- src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py' @@ -87,14 +87,3 @@ jobs: - name: Run coccinelle checks run: mkosi box -- meson test -C build --suite=coccinelle --print-errorlogs --no-stdsplit - - - name: Build with musl - run: | - mkosi box -- \ - env \ - CC=musl-gcc \ - CXX=musl-gcc \ - CFLAGS="-idirafter /usr/include" \ - CXXFLAGS="-idirafter /usr/include" \ - meson setup -Dlibc=musl -Ddbus-interfaces-dir=no musl - mkosi box -- ninja -C musl diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index 5ed4907665..de6673c957 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -169,7 +169,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - uses: systemd/mkosi@77fce77807a9a92bc37edc8f1c967102e6236d94 + - uses: systemd/mkosi@444d247d1a1328bcfb84a945e84959bd4bd0e02d # 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/.github/workflows/unit-tests-musl.sh b/.github/workflows/unit-tests-musl.sh deleted file mode 100755 index 5a9e68710a..0000000000 --- a/.github/workflows/unit-tests-musl.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: LGPL-2.1-or-later - -# shellcheck disable=SC2206 -PHASES=(${@:-SETUP BUILD RUN CLEANUP}) - -function info() { - echo -e "\033[33;1m$1\033[0m" -} - -function run_meson() { - if ! meson "$@"; then - find . -type f -name meson-log.txt -exec cat '{}' + - return 1 - fi -} - -set -ex - -for phase in "${PHASES[@]}"; do - case $phase in - SETUP) - info "Setup phase" - - # Alpine still uses split-usr. - for i in /bin/* /sbin/*; do - ln -rs "$i" "/usr/$i"; - done - ;; - BUILD) - info "Build systemd phase" - - run_meson setup --werror -Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true -Dlibc=musl build - ninja -v -C build - ;; - RUN) - info "Run phase" - - # Create dummy machine ID. - echo '052e58f661f94bd080e258b96aea3f7b' >/etc/machine-id - - # Start dbus for several unit tests. - mkdir -p /var/run/dbus - /usr/bin/dbus-daemon --system || : - - # Here, we explicitly set SYSTEMD_IN_CHROOT=yes as unfortunately runnin_in_chroot() does not - # correctly detect the environment. - env \ - SYSTEMD_IN_CHROOT=yes \ - meson test -C build --print-errorlogs --no-stdsplit --quiet - ;; - CLEANUP) - info "Cleanup phase" - ;; - *) - echo >&2 "Unknown phase '$phase'" - exit 1 - esac -done diff --git a/.github/workflows/unit-tests-musl.yml b/.github/workflows/unit-tests-musl.yml deleted file mode 100644 index e2a7908875..0000000000 --- a/.github/workflows/unit-tests-musl.yml +++ /dev/null @@ -1,113 +0,0 @@ ---- -# vi: ts=2 sw=2 et: -# SPDX-License-Identifier: LGPL-2.1-or-later -# -name: Unit tests (musl) -on: - pull_request: - paths: - - '**/meson.build' - - '.github/workflows/**' - - 'meson_options.txt' - - 'src/**' - - 'test/fuzz/**' - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Repository checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - - - name: Install build dependencies - uses: jirutka/setup-alpine@v1 - with: - arch: x86_64 - branch: edge - packages: > - acl - acl-dev - audit-dev - bash - bash-completion-dev - bpftool - build-base - bzip2-dev - coreutils - cryptsetup-dev - curl-dev - dbus - dbus-dev - elfutils-dev - gettext-dev - git - glib-dev - gnutls-dev - gperf - grep - iproute2 - iptables-dev - kbd - kmod - kmod-dev - libapparmor-dev - libarchive-dev - libbpf-dev - libcap-dev - libcap-utils - libfido2-dev - libgcrypt-dev - libidn2-dev - libmicrohttpd-dev - libpwquality-dev - libqrencode-dev - libseccomp-dev - libselinux-dev - libxkbcommon-dev - linux-pam-dev - lz4-dev - meson - openssl - openssl-dev - p11-kit-dev - pcre2-dev - pkgconf - polkit-dev - py3-elftools - py3-jinja2 - py3-pefile - py3-pytest - py3-lxml - quota-tools - rsync - sfdisk - tpm2-tss-dev - tpm2-tss-esys - tpm2-tss-rc - tpm2-tss-tcti-device - tzdata - util-linux-dev - util-linux-misc - utmps-dev - valgrind-dev - xen-dev - zlib-dev - zstd-dev - - - name: Setup - run: .github/workflows/unit-tests-musl.sh SETUP - shell: alpine.sh --root {0} - - name: Build - run: .github/workflows/unit-tests-musl.sh BUILD - shell: alpine.sh {0} - - name: Run - run: .github/workflows/unit-tests-musl.sh RUN - shell: alpine.sh --root {0} - - name: Cleanup - run: .github/workflows/unit-tests-musl.sh CLEANUP - shell: alpine.sh --root {0} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 7995064017..af265e33ac 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -53,3 +53,55 @@ jobs: sudo --preserve-env=GITHUB_ACTIONS,CI .github/workflows/unit-tests.sh SETUP - name: Build & test run: sudo --preserve-env=GITHUB_ACTIONS,CI .github/workflows/unit-tests.sh RUN_${{ matrix.run_phase }} + + build-musl: + name: Build & test (musl, postmarketOS) + runs-on: ubuntu-24.04 + concurrency: + group: ${{ github.workflow }}-musl-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Repository checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - uses: systemd/mkosi@444d247d1a1328bcfb84a945e84959bd4bd0e02d + + - name: Install apk + # ubuntu-24.04 doesn't package apk, so fetch the official statically-linked binary from + # upstream so mkosi can use it to populate the postmarketOS tools tree. + run: | + sudo curl -fsSL -o /usr/local/bin/apk \ + https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v3.0.6/x86_64/apk.static + echo 'f1489e05bace7d7dd0a687fcd38d50b585ac660af4231668b123649bef3718c4 /usr/local/bin/apk' | sha256sum --check + sudo chmod +x /usr/local/bin/apk + + - name: Build tools tree + run: | + tee mkosi/mkosi.local.conf </dev/null)" == "$GIT_COMMIT" ]]; then exit 0 diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/postmarketos.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/postmarketos.conf new file mode 100644 index 0000000000..845a85c760 --- /dev/null +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/postmarketos.conf @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=postmarketos + +# postmarketOS is musl-based natively, so it can host the musl build directly without needing +# the musl-gcc wrapper that the Fedora tools tree uses. We don't run a PrepareScripts to pull +# systemd build deps from a downstream packaging spec — postmarketOS doesn't ship one — so the +# Packages= list below is the canonical set of build deps for the musl CI build in +# .github/workflows/unit-tests.yml (build-musl job). +[Content] +Packages= + acl + acl-dev + audit-dev + bash + bash-completion-dev + bpftool + build-base + bzip2-dev + coccinelle + coreutils + cryptsetup-dev + curl-dev + dbus + dbus-dev + elfutils-dev + gettext-dev + git + github-cli + glib-dev + gnutls-dev + gperf + grep + iproute2 + iptables-dev + kbd + kmod + kmod-dev + lcov + libapparmor-dev + libarchive-dev + libbpf-dev + libcap-dev + libcap-utils + libfido2-dev + libgcrypt-dev + libgpg-error-dev + libidn2-dev + libmicrohttpd-dev + libpwquality-dev + libqrencode-dev + libseccomp-dev + libselinux-dev + libucontext-dev + libxkbcommon-dev + linux-pam-dev + lz4-dev + openssl + openssl-dev + p11-kit-dev + pcre2-dev + pkgconf + polkit-dev + py3-elftools + py3-jinja2 + py3-lxml + py3-pefile + py3-pytest + quota-tools + ruff + rsync + sfdisk + shellcheck + tpm2-tss-dev + tpm2-tss-esys + tpm2-tss-rc + tpm2-tss-tcti-device + tzdata + util-linux-dev + util-linux-misc + utmps-dev + valgrind-dev + xen-dev + zlib-dev + zstd-dev diff --git a/src/test/test-path.c b/src/test/test-path.c index 512eb96ead..52b9d75f60 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -16,15 +16,15 @@ #include "tests.h" #include "unit.h" -typedef void (*test_function_t)(Manager *m); +static char *runtime_dir = NULL; static int setup_test(Manager **m) { char **tests_path = STRV_MAKE("exists", "existsglobFOOBAR", "changed", "modified", "unit", "directorynotempty", "makedirectory"); - Manager *tmp = NULL; + _cleanup_(manager_freep) Manager *tmp = NULL; int r; - assert_se(m); + ASSERT_NOT_NULL(m); r = enter_cgroup_subroot(NULL); if (r == -ENOMEDIUM) @@ -33,385 +33,357 @@ static int setup_test(Manager **m) { r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &tmp); if (manager_errno_skip_test(r)) return log_tests_skipped_errno(r, "manager_new"); - assert_se(r >= 0); - assert_se(manager_startup(tmp, NULL, NULL, NULL, NULL) >= 0); + ASSERT_OK(r); + ASSERT_OK(manager_startup(tmp, NULL, NULL, NULL, NULL)); STRV_FOREACH(test_path, tests_path) { _cleanup_free_ char *p = NULL; p = strjoin("/tmp/test-path_", *test_path); - assert_se(p); + ASSERT_NOT_NULL(p); (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); } - *m = tmp; - + *m = TAKE_PTR(tmp); return 0; } -static void shutdown_test(Manager *m) { - assert_se(m); - - manager_free(m); -} - static Service *service_for_path(Manager *m, Path *path, const char *service_name) { _cleanup_free_ char *tmp = NULL; Unit *service_unit = NULL; - assert_se(m); - assert_se(path); + ASSERT_NOT_NULL(m); + ASSERT_NOT_NULL(path); if (!service_name) { - assert_se(tmp = strreplace(UNIT(path)->id, ".path", ".service")); + tmp = ASSERT_NOT_NULL(strreplace(UNIT(path)->id, ".path", ".service")); service_unit = manager_get_unit(m, tmp); } else service_unit = manager_get_unit(m, service_name); - assert_se(service_unit); + ASSERT_NOT_NULL(service_unit); return SERVICE(service_unit); } -static int _check_states(unsigned line, - Manager *m, Path *path, Service *service, PathState path_state, ServiceState service_state) { - assert_se(m); - assert_se(service); +/* Define a test that gets a freshly-initialized Manager passed as `m`. The body returns 0 on + * success or EXIT_TEST_SKIP to skip; the wrapper hands the value back to the test framework. */ +#define PATH_TEST(name) \ + static int test_##name##_body(Manager *m); \ + TEST_RET(name) { \ + _cleanup_(manager_freep) Manager *m = NULL; \ + int r = setup_test(&m); \ + if (r != 0) \ + return r; \ + return test_##name##_body(m); \ + } \ + static int test_##name##_body(Manager *m) + +static int _check_states( + unsigned line, + Manager *m, + Path *path, + Service *service, + PathState path_state, + ServiceState service_state) { + + ASSERT_NOT_NULL(m); + ASSERT_NOT_NULL(service); usec_t end = usec_add(now(CLOCK_MONOTONIC), 30 * USEC_PER_SEC); + PathState last_path_state = _PATH_STATE_INVALID; + PathResult last_path_result = _PATH_RESULT_INVALID; + ServiceState last_service_state = _SERVICE_STATE_INVALID; + ServiceResult last_service_result = _SERVICE_RESULT_INVALID; while (path->state != path_state || service->state != service_state || path->result != PATH_SUCCESS || service->result != SERVICE_SUCCESS) { - assert_se(sd_event_run(m->event, 100 * USEC_PER_MSEC) >= 0); + ASSERT_OK(sd_event_run(m->event, 100 * USEC_PER_MSEC)); usec_t n = now(CLOCK_MONOTONIC); - log_info("line %u: %s: state = %s; result = %s (left: %" PRIi64 ")", - line, - UNIT(path)->id, - path_state_to_string(path->state), - path_result_to_string(path->result), - (int64_t) (end - n)); - log_info("line %u: %s: state = %s; result = %s", - line, - UNIT(service)->id, - service_state_to_string(service->state), - service_result_to_string(service->result)); + if (path->state != last_path_state || path->result != last_path_result || + service->state != last_service_state || service->result != last_service_result) { + log_info("line %u: %s: state = %s; result = %s (left: %" PRIi64 ")", + line, + UNIT(path)->id, + path_state_to_string(path->state), + path_result_to_string(path->result), + (int64_t) (end - n)); + log_info("line %u: %s: state = %s; result = %s", + line, + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); + last_path_state = path->state; + last_path_result = path->result; + last_service_state = service->state; + last_service_result = service->result; + } + /* We may fail to start the service for reasons which are not under our control: cgroup + * setup denied, permission limits, resource exhaustion, etc. RESOURCES is terminal here + * for path units that don't auto-retry (PathChanged, PathModified) — they'd just sit in + * the failure state until the test timeout. Skip rather than wait. */ if (service->state == SERVICE_FAILED && - (service->main_exec_status.status == EXIT_CGROUP || service->result == SERVICE_FAILURE_RESOURCES)) { - const char *ci = ci_environment(); + (service->main_exec_status.status == EXIT_CGROUP || service->result == SERVICE_FAILURE_RESOURCES)) + return log_tests_skipped("Failed to start service %s: %s/%s", + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); - /* On a general purpose system we may fail to start the service for reasons which are - * not under our control: permission limits, resource exhaustion, etc. Let's skip the - * test in those cases. On developer machines we require proper setup. */ - if (!ci) - return log_notice_errno(SYNTHETIC_ERRNO(ECANCELED), - "Failed to start service %s, aborting test: %s/%s", - UNIT(service)->id, - service_state_to_string(service->state), - service_result_to_string(service->result)); + /* SERVICE_FAILURE_START_LIMIT_HIT is terminal: the unit won't recover without an explicit + * reset, so further looping is pointless. Skip the test rather than burning the 30s timeout. */ + if (service->state == SERVICE_FAILED && + service->result == SERVICE_FAILURE_START_LIMIT_HIT) + return log_tests_skipped("Failed to start service %s: %s/%s", + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); - /* On Salsa we can't setup cgroups so the unit always fails. The test checks if it - * can but continues if it cannot at the beginning, but on Salsa it fails here. */ - if (streq(ci, "salsa-ci")) - exit(EXIT_TEST_SKIP); - } - - if (n >= end) { - log_error("Test timeout when testing %s", UNIT(path)->id); - exit(EXIT_FAILURE); - } + if (n >= end) + log_test_failed("Test timeout when testing %s", UNIT(path)->id); } return 0; } -#define check_states(...) _check_states(__LINE__, __VA_ARGS__) -static void test_path_exists(Manager *m) { +#define check_states(...) \ + do { \ + int _r = _check_states(__LINE__, __VA_ARGS__); \ + if (_r != 0) \ + return _r; \ + } while (0) + +PATH_TEST(path_exists) { const char *test_path = "/tmp/test-path_exists"; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service restarts if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_existsglob(Manager *m) { +PATH_TEST(path_existsglob) { const char *test_path = "/tmp/test-path_existsglobFOOBAR"; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service restarts if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_changed(Manager *m) { +PATH_TEST(path_changed) { const char *test_path = "/tmp/test-path_changed"; FILE *f; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service does not restart if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - f = fopen(test_path, "w"); - assert_se(f); + f = ASSERT_NOT_NULL(fopen(test_path, "w")); fclose(f); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_modified(Manager *m) { +PATH_TEST(path_modified) { _cleanup_fclose_ FILE *f = NULL; const char *test_path = "/tmp/test-path_modified"; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service does not restart if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - f = fopen(test_path, "w"); - assert_se(f); + f = ASSERT_NOT_NULL(fopen(test_path, "w")); fputs("test", f); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_unit(Manager *m) { +PATH_TEST(path_unit) { const char *test_path = "/tmp/test-path_unit"; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, "path-mycustomunit.service"); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_directorynotempty(Manager *m) { +PATH_TEST(path_directorynotempty) { const char *test_file, *test_path = "/tmp/test-path_directorynotempty/"; Unit *unit = NULL; Path *path = NULL; Service *service = NULL; - assert_se(m); - - assert_se(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, &unit)); path = PATH(unit); service = service_for_path(m, path, NULL); - assert_se(access(test_path, F_OK) < 0); + ASSERT_FAIL(access(test_path, F_OK)); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); /* MakeDirectory default to no */ - assert_se(access(test_path, F_OK) < 0); + ASSERT_FAIL(access(test_path, F_OK)); - assert_se(mkdir_p(test_path, 0755) >= 0); + ASSERT_OK(mkdir_p(test_path, 0755)); test_file = strjoina(test_path, "test_file"); - assert_se(touch(test_file) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_file)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service restarts if directory is still not empty */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_makedirectory_directorymode(Manager *m) { +PATH_TEST(path_makedirectory_directorymode) { const char *test_path = "/tmp/test-path_makedirectory/"; Unit *unit = NULL; struct stat s; - assert_se(m); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, &unit)); - assert_se(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, &unit) >= 0); + ASSERT_FAIL(access(test_path, F_OK)); - assert_se(access(test_path, F_OK) < 0); - - assert_se(unit_start(unit, NULL) >= 0); + ASSERT_OK(unit_start(unit, NULL)); /* Check if the directory has been created */ - assert_se(access(test_path, F_OK) >= 0); + ASSERT_OK_ERRNO(access(test_path, F_OK)); /* Check the mode we specified with DirectoryMode=0744 */ - assert_se(stat(test_path, &s) >= 0); - assert_se((s.st_mode & S_IRWXU) == 0700); - assert_se((s.st_mode & S_IRWXG) == 0040); - assert_se((s.st_mode & S_IRWXO) == 0004); + ASSERT_OK_ERRNO(stat(test_path, &s)); + ASSERT_EQ((mode_t) (s.st_mode & S_IRWXU), (mode_t) 0700); + ASSERT_EQ((mode_t) (s.st_mode & S_IRWXG), (mode_t) 0040); + ASSERT_EQ((mode_t) (s.st_mode & S_IRWXO), (mode_t) 0004); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); + return 0; } -int main(int argc, char *argv[]) { - static const test_function_t tests[] = { - test_path_exists, - test_path_existsglob, - test_path_changed, - test_path_modified, - test_path_unit, - test_path_directorynotempty, - test_path_makedirectory_directorymode, - NULL, - }; - +static int intro(void) { _cleanup_free_ char *test_path = NULL; - _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; umask(022); - test_setup_logging(LOG_INFO); - ASSERT_OK(get_testdata_dir("test-path", &test_path)); ASSERT_OK(setenv_unit_path(test_path)); - assert_se(runtime_dir = setup_fake_runtime_dir()); + ASSERT_NOT_NULL(runtime_dir = setup_fake_runtime_dir()); - for (const test_function_t *test = tests; *test; test++) { - Manager *m = NULL; - int r; - - /* We create a clean environment for each test */ - r = setup_test(&m); - if (r != 0) - return r; - - (*test)(m); - - shutdown_test(m); - } - - return 0; + return EXIT_SUCCESS; } + +static int outro(void) { + runtime_dir = rm_rf_physical_and_free(runtime_dir); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_FULL(LOG_INFO, intro, outro); diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 0fe27601f1..11f843ddb6 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -456,6 +456,9 @@ def kernel_initrd(): items = sorted(glob.glob('/lib/modules/*/vmlinuz')) if not items: items = sorted(glob.glob('/boot/vmlinuz*')) + # Drop entries we cannot read (e.g. /boot/vmlinuz.old with mode 0600 on + # GitHub-hosted runners), the test opens the file later and would fail. + items = [p for p in items if os.access(p, os.R_OK)] if not items: return None diff --git a/tools/fetch-mkosi.py b/tools/fetch-mkosi.py index 481f607d40..11e0a6aafa 100755 --- a/tools/fetch-mkosi.py +++ b/tools/fetch-mkosi.py @@ -15,7 +15,9 @@ from pathlib import Path URL = 'https://github.com/systemd/mkosi' BRANCH = 'main' # We only want to ever use commits on upstream 'main' branch CONFIG = Path('mkosi/mkosi.conf') -WORKFLOWS = [Path('.github/workflows') / f for f in ['mkosi.yml', 'coverage.yml', 'linter.yml']] +WORKFLOWS = [ + Path('.github/workflows') / f for f in ['mkosi.yml', 'coverage.yml', 'linter.yml', 'unit-tests.yml'] +] def parse_args():