diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd95dcba0175..2c51d7d6562c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: permissions: {} runs-on: ubuntu-latest container: - image: ghcr.io/cockpit-project/unit-tests + image: quay.io/cockpit/tasks:latest options: --user root steps: - name: Checkout website repository diff --git a/.github/workflows/unit-tests-refresh.yml b/.github/workflows/unit-tests-refresh.yml deleted file mode 100644 index d3b88ebd1d29..000000000000 --- a/.github/workflows/unit-tests-refresh.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: unit-test-refresh -on: - schedule: - # auto-refresh every Sunday evening - - cron: '0 22 * * 0' - # can be run manually on https://github.com/cockpit-project/cockpit/actions - workflow_dispatch: -jobs: - # we do both builds and all tests in a single run, so that we only upload the containers on success - refresh: - runs-on: ubuntu-22.04 - permissions: - contents: read - packages: write - timeout-minutes: 60 - steps: - - name: Clone repository - uses: actions/checkout@v4 - with: - # need this to also fetch tags - fetch-depth: 0 - - - name: Build fresh containers - timeout-minutes: 10 - run: containers/unit-tests/build - - - name: Run amd64 gcc check-memory test - timeout-minutes: 20 - run: containers/unit-tests/start --verbose --env=CC=gcc --image-tag=latest --make check-memory - - - name: Run amd64 clang distcheck test - timeout-minutes: 15 - run: containers/unit-tests/start --verbose --env=CC=clang --image-tag=latest --make distcheck - - - name: Run amd64 gcc check test - timeout-minutes: 15 - run: containers/unit-tests/start --verbose --env=CC=gcc --image-tag=latest --make check - - - name: Run pytest-cov test - timeout-minutes: 15 - run: containers/unit-tests/start --verbose --env=CC=gcc --image-tag=latest --make pytest-cov - - - name: Log into container registry - run: podman login -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} ghcr.io - - - name: Push containers to registry - run: | - podman push ghcr.io/cockpit-project/unit-tests:latest diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 6450def79b95..470159c908ab 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -4,37 +4,33 @@ jobs: unit-tests: runs-on: ubuntu-22.04 permissions: {} + container: + image: quay.io/cockpit/tasks:latest + options: --user 1001 strategy: matrix: - startarg: - - { make: 'check-memory', tag: 'latest' } - - { make: 'distcheck', tag: 'latest' } + target: + - check-memory + - distcheck # this runs static code checks, unlike distcheck - - { make: 'check', tag: 'latest' } - - { make: 'pytest-cov', tag: 'latest' } + - check + - pytest-cov fail-fast: false timeout-minutes: 60 + env: + FORCE_COLOR: 1 + TEST_BROWSER: firefox + CFLAGS: '-O2' steps: - name: Clone repository uses: actions/checkout@v4 with: # need this to also fetch tags fetch-depth: 0 + submodules: true - - name: Build unit test container if it changed - run: | - changes=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }}..HEAD -- containers/unit-tests/) - if [ -n "${changes}" ]; then - containers/unit-tests/build - fi - - - name: Run unit-tests container + - name: Run unit test timeout-minutes: 30 run: | - containers/unit-tests/start \ - --verbose \ - --env=FORCE_COLOR=1 \ - --env=CFLAGS='-O2' \ - --env=EXTRA_DISTCHECK_CONFIGURE_FLAGS='${{ matrix.startarg.distcheck_flags }}' \ - --image-tag='${{ matrix.startarg.tag }}' \ - --make '${{ matrix.startarg.make }}' + ./autogen.sh + make -j$(nproc) '${{ matrix.target }}' diff --git a/containers/unit-tests/Dockerfile b/containers/unit-tests/Dockerfile deleted file mode 100644 index 19d8200b8a8c..000000000000 --- a/containers/unit-tests/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -ARG debian_arch=amd64 -FROM docker.io/${debian_arch}/debian:testing - -ARG personality=linux64 -ENV personality=${personality} - -COPY setup.sh / -RUN ${personality} /setup.sh ${personality} && rm -rf /setup.sh - -# 'builder' user created in setup.sh -USER builder -WORKDIR /home/builder - -ENV LANG=C.UTF-8 - -# HACK: unbreak distcheck on Debian: https://bugs.debian.org/1035546 -ENV DEB_PYTHON_INSTALL_LAYOUT=deb - -VOLUME /source - -COPY entrypoint / -ENTRYPOINT ["/entrypoint"] -CMD ["/bin/bash"] - -# for filtering from our 'exec' script -LABEL org.cockpit-project.container=unit-tests diff --git a/containers/unit-tests/README.md b/containers/unit-tests/README.md deleted file mode 100644 index d257d8acffc9..000000000000 --- a/containers/unit-tests/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Cockpit unit test container - -This container has all build dependencies and toolchains (GCC and clang) that we -want to exercise Cockpit with, mostly for `make distcheck` and `make check-memory`. -This container runs on [GitHub](.github/workflows/unit-tests.yml), but can be easily -run locally too. - -It assumes that the Cockpit source git checkout is available in `/source`. It -will not modify that directory or take uncommitted changes into account, but it -will re-use an already existing `node_modules/` directory. - -The scripts can use either podman (preferred) or docker. If you use docker, you -need to run all commands as root. With podman the containers work as either user -or root. - -## Building - -The `build` script will build the `cockpit/unit-tests` container. - -## Running tests - -You need to disable SELinux with `sudo setenforce 0` for this. There is no -other way for the container to access the files in your build tree (do *not* -use the `--volume` `:Z` option, as that will destroy the file labels on the -host). - -Tests in that container get started with the `start` script. By default, this -script runs the unit tests on amd64. The script accepts a number of arguments -to modify its behaviour: - - - `--env CC=othercc` to set the `CC` environment variable inside the container (ie: - to build with a different compiler) - - `--image-tag` to specify a different tag to use for the `cockpit/unit-tests` image - -Additionally, a testing scenario can be provided with specifying a `make` target. -Supported scenarios are: - - - `check-memory`: runs 'make check-memory' (ie: run the unit tests under valgrind) - - `distcheck`: runs 'make distcheck' and some related checks - - `pycheck`: runs browser unit tests against the Python bridge - -Some examples: - - $ ./start --make check-memory # run the valgrind tests on amd64 - - $ ./start --env=CC=clang --make check-memory # run the valgrind tests, compiled with clang - -## Debugging tests - -For interactive debugging, run a shell in the container: - - $ ./start - -You will find the cockpit source tree (from the host) mounted at `/source` in -the container. Run - - $ /source/autogen.sh - -to create a build tree, then you can run any make or other debugging command -interactively. - -You can also attach to another container using the provided `exec` script. For example: - - $ ./exec uname -a # run a command as the "builder" user - - $ ./exec --root # start a shell as root - -## More Info - - * [Cockpit Project](https://cockpit-project.org) - * [Cockpit Development](https://github.com/cockpit-project/cockpit) diff --git a/containers/unit-tests/build b/containers/unit-tests/build deleted file mode 100755 index b100a1f7a706..000000000000 --- a/containers/unit-tests/build +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -eu - -dir=$(dirname "$0") - -podman build --build-arg debian_arch=amd64 --build-arg personality=linux64 -t ghcr.io/cockpit-project/unit-tests ${dir} diff --git a/containers/unit-tests/entrypoint b/containers/unit-tests/entrypoint deleted file mode 100755 index 8a53d1074499..000000000000 --- a/containers/unit-tests/entrypoint +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -e - -export TEST_BROWSER=firefox - -printf "Host: " && uname -srvm - -. /usr/lib/os-release -printf "Container: \${NAME} \${VERSION} / " && ${personality} uname -nrvm -echo - -set -ex -exec ${personality} -- "$@" diff --git a/containers/unit-tests/setup.sh b/containers/unit-tests/setup.sh deleted file mode 100755 index 24764675438d..000000000000 --- a/containers/unit-tests/setup.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/sh -ex - -dependencies="\ - appstream-util \ - autoconf \ - automake \ - build-essential \ - clang \ - curl \ - dbus \ - firefox-esr \ - flake8 \ - gcc-multilib \ - gdb \ - git \ - glib-networking \ - glib-networking-dbgsym\ - gtk-doc-tools \ - gettext \ - libc6-dbg \ - libfontconfig1 \ - libglib2.0-0-dbgsym \ - libglib2.0-dev \ - libgnutls28-dev \ - libjavascript-minifier-xs-perl \ - libjson-glib-dev \ - libjson-perl \ - libkrb5-dev \ - libpam0g-dev \ - libpcp-import1-dev \ - libpcp-pmda3-dev \ - libpcp3-dev \ - libpolkit-agent-1-dev \ - libpolkit-gobject-1-dev \ - libssh-4-dbgsym \ - libssh-dev \ - libsystemd-dev \ - mypy \ - npm \ - nodejs \ - pkg-config \ - python3 \ - python3-mypy \ - python3-pip \ - python3-pytest-asyncio \ - python3-pytest-cov \ - python3-pytest-timeout \ - ssh \ - strace \ - valgrind \ - vulture \ - xmlto \ - xsltproc \ -" - -echo "deb http://deb.debian.org/debian-debug/ testing-debug main" > /etc/apt/sources.list.d/ddebs.list -echo "deb http://deb.debian.org/debian-debug/ testing-proposed-updates-debug main" >> /etc/apt/sources.list.d/ddebs.list -apt-get update -apt-get install -y --no-install-recommends eatmydata -DEBIAN_FRONTEND=noninteractive eatmydata apt-get install -y --no-install-recommends ${dependencies} - -# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1057968 -echo "deb http://deb.debian.org/debian unstable main" > /etc/apt/sources.list.d/unstable.list -apt-get update -apt-get install -y --no-install-recommends python3-flake8 -rm /etc/apt/sources.list.d/unstable.list - -adduser --gecos "Builder" builder - -if [ "$(uname -m)" = "x86_64" ] ; then - # See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1030835 - pip install --break-system-packages ruff -fi - -# minimize image -# useful command: dpkg-query --show -f '${package} ${installed-size}\n' | sort -k2n -dpkg -P --force-depends libgl1-mesa-dri libglx-mesa0 perl - -rm -rf /var/cache/apt /var/lib/apt /var/log/* /usr/share/doc/ /usr/share/man/ /usr/share/help /usr/share/info diff --git a/containers/unit-tests/start b/containers/unit-tests/start deleted file mode 100755 index e01cb66370ad..000000000000 --- a/containers/unit-tests/start +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/python3 - -# This file is part of Cockpit. -# -# Copyright (C) 2022 Red Hat, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import argparse -import os -import shlex -import sys -import tempfile -from subprocess import run - - -def logged(func): - def wrapper(args, **kwargs): - print('+', shlex.join(args)) - return func(args, **kwargs) - return wrapper - - -def git(*args): - run(['git', *args], check=True) - - -def git_output(*args): - return run(['git', *args], check=True, capture_output=True, text=True).stdout.strip() - - -def podman(*args, check=True): - if os.path.exists('/run/.toolboxenv'): - cmd = ['flatpak-spawn', '--host', 'podman', *args] - else: - cmd = ['podman', *args] - - return run(cmd, check=check) - - -class PodmanTemporaryDirectory(tempfile.TemporaryDirectory): - """TemporaryDirectory subclass capable of removing files owned by subuids""" - @classmethod - def _rmtree(cls, name, ignore_errors=False): # noqa: FBT002 - del ignore_errors # can't remove or rename this kwarg - podman('unshare', 'rm', '-r', name) - - def __enter__(self): - # Override the TemporaryDirectory behaviour of returning its name here - return self - - -class SourceDirectory(PodmanTemporaryDirectory): - def __init__(self): - super().__init__(prefix='cockpit-source.') - - def prepare(self, args): - if args.branch: - opts = ['-c', 'advice.detachedHead=false', '-b', args.branch] - else: - opts = [] - - git('clone', '--recurse-submodule=vendor/*', *opts, '.', self.name) - - if not args.head and not args.branch: - if stash := git_output('stash', 'create'): - git('-C', self.name, 'fetch', '--quiet', '--no-write-fetch-head', 'origin', stash) - git('-C', self.name, 'stash', 'apply', stash) - - if not args.no_node_modules: - run([f'{self.name}/tools/node-modules', 'checkout'], check=True) - - -class ResultsDirectory(PodmanTemporaryDirectory): - def __init__(self): - super().__init__(prefix='cockpit-results.') - - def copy_out(self, destination): - podman('unshare', 'cp', '-rT', self.name, destination) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--verbose', '-v', action='store_true', help='Show commands when running them') - parser.add_argument('--results', metavar='DIRECTORY', help="Copy container /results to the given host directory") - - group = parser.add_argument_group(title='Container options') - group.add_argument('--image', default='ghcr.io/cockpit-project/unit-tests', help='Container image to use') - group.add_argument('--image-tag', default='latest', help='Container image tag to use') - group.add_argument('--env', metavar='NAME=VAL', action='append', default=[], - help='Set an environment variable in the container') - group.add_argument('--network', action="store_true", - help="Enable network in the container (default: disabled)") - group.add_argument('--interactive', '-i', action="store_true", - help="Interactive mode (implied by no command or script)") - group.add_argument('--tty', '-t', action="store_true", - help="Allocate a pseudoterminal (implied by no command or script)") - group.add_argument('--user', help="Pass through the --user flag to podman") - group.add_argument('--entrypoint', metavar='CMD', help="Provide the --entrypoint flag to podman") - group.add_argument('--workdir', help="Provide the --workdir flag to podman") - - group = parser.add_argument_group(title='What to build').add_mutually_exclusive_group() - group.add_argument('--head', action='store_true', help='Build the HEAD commit') - group.add_argument('-b', dest='branch', metavar='NAME', help='Build the named branch or tag') - group.add_argument('--work-tree', action='store_true', - help='Build the HEAD commit, plus changes on the filesystem (default)') - - group = parser.add_argument_group(title='Preparation').add_mutually_exclusive_group() - group.add_argument('--no-node-modules', action='store_true', - help='Disable checking out node_modules/ during preparation') - - group = parser.add_argument_group(title='Command to run').add_mutually_exclusive_group() - group.add_argument('-c', metavar='SCRIPT', dest='script', help="Run the provided shell script") - group.add_argument('--make-dist', action='store_true', help='Run `make dist`. Requires --results.') - group.add_argument('--make', metavar='TARGET', help='Run `make` on the given target') - # re: default=[]: https://github.com/python/cpython/issues/86020 - group.add_argument('command', metavar='CMD', nargs='*', default=[], help="Run a normal command, with arguments") - - args = parser.parse_args() - - if args.results and os.path.exists(args.results): - parser.error(f'--results directory `{args.results}` already exists') - - if args.make_dist and not args.results: - parser.error('--make-dist requires --results directory') - - if args.verbose: - global run - run = logged(run) - - with SourceDirectory() as source_dir, ResultsDirectory() as results_dir: - options = { - '--rm', - '--log-driver=none', - f'--volume={source_dir.name}:/source:Z,U', - } - - if args.results: - options.add(f'--volume={results_dir.name}:/results:Z,U') - - if not args.network: - options.add('--network=none') - if args.user: - options.add(f'--user={args.user}') - if args.entrypoint: - options.add(f'--entrypoint={args.entrypoint}') - if args.workdir: - options.add(f'--workdir={args.workdir}') - if args.interactive: - options.add('--interactive') - if args.tty: - options.add('--tty') - for keyval in args.env: - options.add(f'--env={keyval}') - - command = [] - if args.command: - command = args.command - elif args.script: - command = ['sh', '-c', args.script] - elif args.make: - command = ['sh', '-c', '/source/autogen.sh; exec make -j$(nproc) ' + shlex.quote(args.make)] - elif args.make_dist: - command = ['sh', '-c', 'cp -t /results $(/source/tools/make-dist)'] - else: - options.update(['--tty', '--interactive']) - - source_dir.prepare(args) - - result = podman('run', *options, f'{args.image}:{args.image_tag}', *command) - - if result.returncode == 0 and args.results: - results_dir.copy_out(args.results) - - return result.returncode - - -if __name__ == '__main__': - sys.exit(main())