diff --git a/.appveyor.yml b/.appveyor.yml index 242702aba..370c1e3c6 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,21 +1,21 @@ # This file is part of BenchExec, a framework for reliable benchmarking: # https://github.com/sosy-lab/benchexec # -# SPDX-FileCopyrightText: 2007-2020 Dirk Beyer +# SPDX-FileCopyrightText: 2007-2024 Dirk Beyer # # SPDX-License-Identifier: Apache-2.0 +image: "Visual Studio 2022" + environment: matrix: - PYTHON: "C:\\Python38" - - PYTHON: "C:\\Python39" + - PYTHON: "C:\\Python312" build: off install: - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% - # The package installation via our setup.py file uses easy_install, which fails to correctly install lxml. - # As some Python environments don't have lxml preinstalled, install it here to avoid errors during the execution in those cases. - python -m pip install --user ".[dev]" test_script: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f391b3c49..52da6fded 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,54 +33,105 @@ stages: - images - test -.unit-tests: &unit-tests +.tests:cgroupsv1: &tests-cgroupsv1 stage: test before_script: # Create user, we do not want to test as root - adduser --disabled-login --gecos "" $PRIMARY_USER - # Activate coverage for subprocesses - - printf 'import coverage\ncoverage.process_startup()\n' > "/usr/local/lib/python${PYTHON_VERSION}/site-packages/sitecustomize.py" # Give $PRIMARY_USER permission to create cgroups - test/for_each_of_my_cgroups.sh chgrp $PRIMARY_USER - test/for_each_of_my_cgroups.sh chmod g+w $PRIMARY_USER + # Install pytest-cov (does not work with --user) + - pip install pytest-cov # Install BenchExec with `dev` dependencies - sudo -u $PRIMARY_USER pip install --user ".[dev]" # Start lxcfs - lxcfs /var/lib/lxcfs & script: - - sudo -u $PRIMARY_USER - COVERAGE_PROCESS_START=.coveragerc - coverage run -m pytest + - sudo -u $PRIMARY_USER python -m pytest -ra --cov after_script: - - sudo -u $PRIMARY_USER coverage combine - - sudo -u $PRIMARY_USER coverage report - sudo -u $PRIMARY_USER coverage xml tags: - privileged + coverage: '/TOTAL.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' artifacts: - paths: - - coverage.xml + reports: + coverage_report: + coverage_format: cobertura + path: coverage.xml + +tests:cgroups1:python-3.8: + <<: *tests-cgroupsv1 + variables: + PYTHON_VERSION: '3.8' + +tests:cgroupsv1:python-3.9: + <<: *tests-cgroupsv1 + variables: + PYTHON_VERSION: '3.9' + +tests:cgroupsv1:python-3.10: + <<: *tests-cgroupsv1 + variables: + PYTHON_VERSION: '3.10' + +tests:cgroupsv1:python-3.11: + <<: *tests-cgroupsv1 + variables: + PYTHON_VERSION: '3.11' + +tests:cgroupsv1:python-3.12: + <<: *tests-cgroupsv1 + variables: + PYTHON_VERSION: '3.12' -unit-tests:python-3.8: - <<: *unit-tests +.tests:cgroupsv2: &tests-cgroupsv2 + stage: test + before_script: + # Prepare cgroups + - test/setup_cgroupsv2_in_container.sh + # Install pytest-cov (does not work with --user) + - pip install pytest-cov + # Install BenchExec with `dev` dependencies + - pip install --user ".[dev]" + script: + - python -m pytest -ra --cov + after_script: + - coverage xml + tags: + - cgroupsv2 + coverage: '/TOTAL.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' + artifacts: + reports: + coverage_report: + coverage_format: cobertura + path: coverage.xml + +tests:cgroupsv2:python-3.8: + <<: *tests-cgroupsv2 variables: PYTHON_VERSION: '3.8' -unit-tests:python-3.9: - <<: *unit-tests +tests:cgroupsv2:python-3.9: + <<: *tests-cgroupsv2 variables: PYTHON_VERSION: '3.9' -unit-tests:python-3.10: - <<: *unit-tests +tests:cgroupsv2:python-3.10: + <<: *tests-cgroupsv2 variables: PYTHON_VERSION: '3.10' -unit-tests:python-3.11: - <<: *unit-tests +tests:cgroupsv2:python-3.11: + <<: *tests-cgroupsv2 variables: PYTHON_VERSION: '3.11' +tests:cgroupsv2:python-3.12: + <<: *tests-cgroupsv2 + variables: + PYTHON_VERSION: '3.12' + # Static checks check-format: stage: test @@ -228,3 +279,9 @@ build-docker:test:python-3.11: variables: DOCKERFILE: test/Dockerfile.python-3.11 IMAGE: test:python-3.11 + +build-docker:test:python-3.12: + extends: .build-docker + variables: + DOCKERFILE: test/Dockerfile.python-3.12 + IMAGE: test:python-3.12 diff --git a/benchexec/baseexecutor.py b/benchexec/baseexecutor.py index 64fc1cdca..d62ff24c0 100644 --- a/benchexec/baseexecutor.py +++ b/benchexec/baseexecutor.py @@ -80,9 +80,10 @@ def _start_execution( @param child_setup_fn a function without parameters that is called in the child process before the tool is started @param parent_cleanup_fn a function that is called in the parent process - immediately after the tool terminated, with three parameters: + immediately after the tool terminated, with four parameters: the result of parent_setup_fn, the result of the executed process as ProcessExitCode, - and the base path for looking up files as parameter values + the base path for looking up files as parameter values, + and the cgroup of the tool @return: a tuple of PID of process and a blocking function, which waits for the process and a triple of the exit code and the resource usage of the process and the result of parent_cleanup_fn (do not use os.wait) @@ -125,11 +126,11 @@ def wait_and_get_result(): exitcode, ru_child = self._wait_for_process(p.pid, args[0]) parent_cleanup = parent_cleanup_fn( - parent_setup, util.ProcessExitCode.from_raw(exitcode), "" + parent_setup, util.ProcessExitCode.from_raw(exitcode), "", cgroups ) return exitcode, ru_child, parent_cleanup - return p.pid, wait_and_get_result + return p.pid, cgroups, wait_and_get_result def _wait_for_process(self, pid, name): """Wait for the given process to terminate. diff --git a/benchexec/cgroups.py b/benchexec/cgroups.py index d27e483d3..971fdf9ed 100644 --- a/benchexec/cgroups.py +++ b/benchexec/cgroups.py @@ -170,6 +170,10 @@ def handle_errors(self, critical_cgroups): def create_fresh_child_cgroup(self, subsystems): pass + @abstractmethod + def create_fresh_child_cgroup_for_delegation(self): + pass + @abstractmethod def add_task(self, pid): pass diff --git a/benchexec/cgroupsv1.py b/benchexec/cgroupsv1.py index 505e95c72..e77bbacd0 100644 --- a/benchexec/cgroupsv1.py +++ b/benchexec/cgroupsv1.py @@ -357,7 +357,7 @@ def get_group_name(gid): else: sys.exit(_ERROR_MSG_OTHER) # e.g., subsystem not mounted - def create_fresh_child_cgroup(self, subsystems): + def create_fresh_child_cgroup(self, subsystems, prefix=CGROUP_NAME_PREFIX): """ Create child cgroups of the current cgroup for at least the given subsystems. @return: A Cgroup instance representing the new child cgroup(s). @@ -374,7 +374,7 @@ def create_fresh_child_cgroup(self, subsystems): ] continue - cgroup = tempfile.mkdtemp(prefix=CGROUP_NAME_PREFIX, dir=parentCgroup) + cgroup = tempfile.mkdtemp(prefix=prefix, dir=parentCgroup) createdCgroupsPerSubsystem[subsystem] = cgroup createdCgroupsPerParent[parentCgroup] = cgroup @@ -395,6 +395,18 @@ def copy_parent_to_child(name): return CgroupsV1(createdCgroupsPerSubsystem) + def create_fresh_child_cgroup_for_delegation(self, prefix="delegate_"): + """ + Create a child cgroup with all controllers. + On cgroupsv1 there is no difference to a regular child cgroup. + """ + child_cgroup = self.create_fresh_child_cgroup(self.subsystems.keys(), prefix) + assert ( + self.subsystems.keys() == child_cgroup.subsystems.keys() + ), "delegation failed for at least one controller" + + return child_cgroup + def add_task(self, pid): """ Add a process to the cgroups represented by this instance. diff --git a/benchexec/container.py b/benchexec/container.py index ffe7cea3a..db82328e0 100644 --- a/benchexec/container.py +++ b/benchexec/container.py @@ -110,7 +110,9 @@ DIR_MODES = [DIR_HIDDEN, DIR_READ_ONLY, DIR_OVERLAY, DIR_FULL_ACCESS] """modes how a directory can be mounted in the container""" -LXCFS_PROC_DIR = b"/var/lib/lxcfs/proc" +LXCFS_BASE_DIR = b"/var/lib/lxcfs" +LXCFS_PROC_DIR = LXCFS_BASE_DIR + b"/proc" +SYS_CPU_DIR = b"/sys/devices/system/cpu" _CLONE_NESTED_CALLBACK = ctypes.CFUNCTYPE(ctypes.c_int) """Type for callback of execute_in_namespace, nested in our primary callback.""" @@ -1184,6 +1186,15 @@ def setup_container_system_config(basedir, mountdir, dir_modes): {"h": CONTAINER_HOME, "p": os.path.dirname(CONTAINER_HOME)}, ) + # Virtualize CPU info with LXCFS if directory is not hidden nor full-access + if ( + os.access(LXCFS_BASE_DIR + SYS_CPU_DIR, os.R_OK) + and determine_directory_mode(dir_modes, SYS_CPU_DIR) == DIR_READ_ONLY + ): + make_bind_mount( + LXCFS_BASE_DIR + SYS_CPU_DIR, mountdir + SYS_CPU_DIR, private=True + ) + def is_container_system_config_file(file): """Determine whether a given file is one of the files created by diff --git a/benchexec/containerexecutor.py b/benchexec/containerexecutor.py index 1954729a7..53eaa443a 100644 --- a/benchexec/containerexecutor.py +++ b/benchexec/containerexecutor.py @@ -388,8 +388,8 @@ def is_accessible(path): # container, but we do not want a warning per run. if not is_accessible(container.LXCFS_PROC_DIR): logging.info( - "LXCFS is not available," - " some host information like the uptime leaks into the container." + "LXCFS is not available, some host information like the uptime" + " and the total number of CPU cores leaks into the container." ) if not NATIVE_CLONE_CALLBACK_SUPPORTED: @@ -457,13 +457,14 @@ def execute_run( cgroups = self._cgroups.create_fresh_child_cgroup( self._cgroups.subsystems.keys() ) - pid = None + tool_pid = None + tool_cgroups = None returnvalue = 0 logging.debug("Starting process.") try: - pid, result_fn = self._start_execution( + tool_pid, tool_cgroups, result_fn = self._start_execution( args=args, stdin=None, stdout=None, @@ -481,7 +482,7 @@ def execute_run( ) with self.SUB_PROCESS_PIDS_LOCK: - self.SUB_PROCESS_PIDS.add(pid) + self.SUB_PROCESS_PIDS.add(tool_pid) # wait until process has terminated returnvalue, unused_ru_child, unused = result_fn() @@ -491,7 +492,7 @@ def execute_run( logging.debug("Process terminated, exit code %s.", returnvalue) with self.SUB_PROCESS_PIDS_LOCK: - self.SUB_PROCESS_PIDS.discard(pid) + self.SUB_PROCESS_PIDS.discard(tool_pid) if temp_dir is not None: logging.debug("Cleaning up temporary directory.") @@ -969,15 +970,24 @@ def check_child_exit_code(): child_pid, ) - # cgroups is the cgroups where we configure limits. - # So for isolation, we need to create a child cgroup that becomes the root - # of the cgroup ns, such that the limit settings are not accessible in the - # container and cannot be changed. - if use_cgroup_ns: - cgroups = cgroups.create_fresh_child_cgroup_for_delegation() + # cgroups is the cgroup where we configure limits. + # We add another layer of cgroups below it, for two reasons: + # - We want to move our child process (the init process of the container) + # into a cgroups where the limits apply because LXCFS reports the limits + # of the init process of the container. + # - On cgroupsv2 we want to move the grandchild process (the started tool) + # into a cgroup that becomes the root of the cgroup ns, + # such that no other cgroup (in particular the one with the limits) + # is accessible in the container and the limits cannot be changed + # from within the container. + child_cgroup = cgroups.create_fresh_child_cgroup( + cgroups.subsystems.keys(), prefix="init_" + ) + child_cgroup.add_task(child_pid) + grandchild_cgroups = cgroups.create_fresh_child_cgroup_for_delegation() # start measurements - cgroups.add_task(grandchild_pid) + grandchild_cgroups.add_task(grandchild_pid) parent_setup = parent_setup_fn() # Signal grandchild that setup is finished @@ -1015,7 +1025,10 @@ def wait_for_grandchild(): base_path = f"/proc/{child_pid}/root" parent_cleanup = parent_cleanup_fn( - parent_setup, util.ProcessExitCode.from_raw(exitcode), base_path + parent_setup, + util.ProcessExitCode.from_raw(exitcode), + base_path, + grandchild_cgroups, ) if result_files_patterns: @@ -1032,7 +1045,7 @@ def wait_for_grandchild(): return exitcode, ru_child, parent_cleanup - return grandchild_pid, wait_for_grandchild + return grandchild_pid, grandchild_cgroups, wait_for_grandchild def _setup_container_filesystem(self, temp_dir, output_dir, memlimit, memory_nodes): """Setup the filesystem layout in the container. diff --git a/benchexec/runexecutor.py b/benchexec/runexecutor.py index c3cf77d3d..e1107378c 100644 --- a/benchexec/runexecutor.py +++ b/benchexec/runexecutor.py @@ -831,7 +831,7 @@ def preParent(): walltime_before = time.monotonic() return starttime, walltime_before - def postParent(preParent_result, exit_code, base_path): + def postParent(preParent_result, exit_code, base_path, tool_cgroups): """Cleanup that is executed in the parent process immediately after the actual tool terminated.""" # finish measurements starttime, walltime_before = preParent_result @@ -846,8 +846,8 @@ def postParent(preParent_result, exit_code, base_path): # process existed, and killing via cgroups prevents this. # But if we do not have freezer, it is safer to just let all processes run # until the container is killed. - if cgroups.FREEZE in cgroups: - cgroups.kill_all_tasks() + if tool_cgroups.FREEZE in tool_cgroups: + tool_cgroups.kill_all_tasks() # For a similar reason, we cancel all limits. Otherwise a run could have # terminationreason=walltime because copying output files took a long time. @@ -882,7 +882,8 @@ def preSubprocess(): error_filename, args, write_header=write_header ) - pid = None + tool_pid = None + tool_cgroups = None returnvalue = 0 ru_child = None self._termination_reason = None @@ -894,7 +895,7 @@ def preSubprocess(): logging.debug("Starting process.") try: - pid, result_fn = self._start_execution( + tool_pid, tool_cgroups, result_fn = self._start_execution( args=args, stdin=stdin, stdout=outputFile, @@ -912,14 +913,21 @@ def preSubprocess(): ) with self.SUB_PROCESS_PIDS_LOCK: - self.SUB_PROCESS_PIDS.add(pid) + self.SUB_PROCESS_PIDS.add(tool_pid) timelimitThread = self._setup_cgroup_time_limit( - hardtimelimit, softtimelimit, walltimelimit, cgroups, cores, pid + hardtimelimit, + softtimelimit, + walltimelimit, + tool_cgroups, + cores, + tool_pid, + ) + oomThread = self._setup_cgroup_memory_limit_thread( + memlimit, tool_cgroups, tool_pid ) - oomThread = self._setup_cgroup_memory_limit_thread(memlimit, cgroups, pid) file_hierarchy_limit_thread = self._setup_file_hierarchy_limit( - files_count_limit, files_size_limit, temp_dir, cgroups, pid + files_count_limit, files_size_limit, temp_dir, tool_cgroups, tool_pid ) # wait until process has terminated @@ -932,7 +940,7 @@ def preSubprocess(): logging.debug("Process terminated, exit code %s.", returnvalue) with self.SUB_PROCESS_PIDS_LOCK: - self.SUB_PROCESS_PIDS.discard(pid) + self.SUB_PROCESS_PIDS.discard(tool_pid) if timelimitThread: timelimitThread.cancel() @@ -945,7 +953,8 @@ def preSubprocess(): # Make sure to kill all processes if there are still some # (needs to come early to avoid accumulating more CPU time) - cgroups.kill_all_tasks() + if tool_cgroups: + tool_cgroups.kill_all_tasks() # normally subprocess closes file, we do this again after all tasks terminated outputFile.close() @@ -953,8 +962,10 @@ def preSubprocess(): errorFile.close() # measurements are not relevant in case of failure, but need to come before cgroup cleanup - self._get_cgroup_measurements(cgroups, ru_child, result) + if tool_cgroups: + self._get_cgroup_measurements(tool_cgroups, ru_child, result) logging.debug("Cleaning up cgroups.") + cgroups.kill_all_tasks() # currently necessary for removing child cgroups cgroups.remove() self._cleanup_temp_dir(temp_dir) diff --git a/benchexec/test_runexecutor.py b/benchexec/test_runexecutor.py index 671a122df..a2b4d7001 100644 --- a/benchexec/test_runexecutor.py +++ b/benchexec/test_runexecutor.py @@ -43,6 +43,7 @@ def setUpClass(cls): cls.echo = shutil.which("echo") or "/bin/echo" cls.sleep = shutil.which("sleep") or "/bin/sleep" cls.cat = shutil.which("cat") or "/bin/cat" + cls.grep = shutil.which("grep") or "/bin/grep" def setUp(self, *args, **kwargs): with self.skip_if_logs( @@ -1151,6 +1152,28 @@ def test_path_with_space(self): finally: shutil.rmtree(temp_dir) + def test_cpuinfo_with_lxcfs(self): + if not os.path.exists("/var/lib/lxcfs/proc"): + self.skipTest("missing lxcfs") + result, output = self.execute_run( + self.grep, "^processor", "/proc/cpuinfo", cores=[0] + ) + self.check_result_keys(result) + self.check_exitcode(result, 0, "exit code for reading cpuinfo is not zero") + cpus = [int(line.split()[2]) for line in output if line.startswith("processor")] + self.assertListEqual(cpus, [0], "Unexpected CPU cores visible in container") + + def test_sys_cpu_with_lxcfs(self): + if not os.path.exists("/var/lib/lxcfs/proc"): + self.skipTest("missing lxcfs") + result, output = self.execute_run( + self.cat, "/sys/devices/system/cpu/online", cores=[0] + ) + self.check_result_keys(result) + self.check_exitcode(result, 0, "exit code for reading online CPUs is not zero") + cpus = util.parse_int_list(output[-1]) + self.assertListEqual(cpus, [0], "Unexpected CPU cores online in container") + def test_uptime_with_lxcfs(self): if not os.path.exists("/var/lib/lxcfs/proc"): self.skipTest("missing lxcfs") diff --git a/benchexec/tools/super_prove.py b/benchexec/tools/super_prove.py new file mode 100644 index 000000000..ea192f3d5 --- /dev/null +++ b/benchexec/tools/super_prove.py @@ -0,0 +1,56 @@ +# This file is part of BenchExec, a framework for reliable benchmarking: +# https://github.com/sosy-lab/benchexec +# +# SPDX-FileCopyrightText: 2007-2020 Dirk Beyer +# +# SPDX-License-Identifier: Apache-2.0 + +import benchexec.result as result +import benchexec.tools.template +from pathlib import Path + + +class Tool(benchexec.tools.template.BaseTool2): + """ + Tool info for super_prove: A portfolio model checker based on ABC + - project URL: https://github.com/berkeley-abc/super_prove + - build repository: https://github.com/sterin/super-prove-build + """ + + REQUIRED_PATHS = ["bin/", "lib/"] + + def executable(self, tool_locator): + return tool_locator.find_executable("super_prove.sh", subdir="bin") + + def name(self): + return "super_prove" + + def project_url(self): + return "https://github.com/berkeley-abc/super_prove" + + def version(self, executable): + version_file = Path(executable).parent.parent / "VERSION.txt" + if not version_file.is_file(): + return "" + with version_file.open() as f: + version = f.readline().strip() + return version + + def program_files(self, executable): + return self._program_files_from_executable( + executable, self.REQUIRED_PATHS, parent_dir=True + ) + + def cmdline(self, executable, options, task, rlimits): + return [executable, *options, task.single_input_file] + + def determine_result(self, run): + """ + @return: status of super_prove after executing a run + """ + if run.output: + if run.output[0] == "0": + return result.RESULT_TRUE_PROP + elif run.output[0] == "1": + return result.RESULT_FALSE_PROP + return result.RESULT_UNKNOWN diff --git a/doc/DEVELOPMENT.md b/doc/DEVELOPMENT.md index 35d27ee0f..e1d46a543 100644 --- a/doc/DEVELOPMENT.md +++ b/doc/DEVELOPMENT.md @@ -58,6 +58,20 @@ To run the test suite of BenchExec, use the following command: python3 -m pytest +Specific tests can be selected with `-k TEST_NAME_SUBSTRING` +and debug logs can be captured with `--log-level DEBUG`. + +A container with the necessary environment and permissions +for executing the tests can be started with: + + podman run --rm -it \ + --security-opt unmask=/sys/fs/cgroup --cgroups=split \ + --security-opt unmask=/proc/* --security-opt seccomp=unconfined \ + -v /var/lib/lxcfs:/var/lib/lxcfs:ro --device /dev/fuse \ + -v $(pwd):$(pwd):rw -w $(pwd) \ + registry.gitlab.com/sosy-lab/software/benchexec/test:python-3.12 \ + test/setup_cgroupsv2_in_container.sh bash + We also check our code using the static-analysis tools [flake8](http://flake8.pycqa.org) and [ruff](https://github.com/astral-sh/ruff/). If you find a rule that should not be enforced in your opinion, diff --git a/doc/INSTALL.md b/doc/INSTALL.md index a53a4ac7e..cc0d2ead7 100644 --- a/doc/INSTALL.md +++ b/doc/INSTALL.md @@ -120,7 +120,7 @@ and install [cpu-energy-meter], [fuse-overlayfs], [libseccomp2], [LXCFS], and [p ### Containerized Environments -Please refer to the [dedicated guide](doc/benchexec-in-container.md) for the +Please refer to the [dedicated guide](benchexec-in-container.md) for the necessary steps to install BenchExec inside a container. **IMPORTANT**: In any case, cgroups with all relevant controllers need to be diff --git a/doc/benchexec-in-container.md b/doc/benchexec-in-container.md index 08a400f08..d6df4faa3 100644 --- a/doc/benchexec-in-container.md +++ b/doc/benchexec-in-container.md @@ -122,7 +122,7 @@ Dockerfile is located to build the container. Start the image with ``` -podman run --cgroups=split \ +podman run --security-opt unmask=/sys/fs/cgroup --cgroups=split \ --security-opt unmask=/proc/* \ --security-opt seccomp=unconfined \ -it diff --git a/pyproject.toml b/pyproject.toml index 6be7e209c..acd0770cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ exclude = [ '**/test_*/**.py', ] -[tool.ruff] +[tool.ruff.lint] # TODO: Enable more checks. #select = ["ALL"] ignore = [ @@ -65,7 +65,7 @@ ignore = [ 'E501', 'I001', ] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] 'benchexec/test*.py' = [ # wildcard imports significantly shorten test code, 'F405', @@ -73,4 +73,4 @@ ignore = [ [tool.pytest.ini_options] python_files = ["test_*.py", "test_integration/__init__.py", "test.py"] -norecursedirs = ["contrib/p4/docker_files", "build"] +norecursedirs = ["contrib/p4/docker_files", "build", "benchexec/tablegenerator/react-table"] diff --git a/test/Dockerfile.python-3.10 b/test/Dockerfile.python-3.10 index 825aa9e5e..c881c0c02 100644 --- a/test/Dockerfile.python-3.10 +++ b/test/Dockerfile.python-3.10 @@ -19,6 +19,7 @@ FROM python:3.10 # because these images do not use the Python installed via apt. RUN apt-get update && apt-get install -y \ + fuse-overlayfs \ libsystemd-dev \ lxcfs \ sudo \ @@ -30,4 +31,5 @@ RUN pip install \ lxml \ pystemd \ pytest \ + pytest-cov \ pyyaml diff --git a/test/Dockerfile.python-3.11 b/test/Dockerfile.python-3.11 index 825b67d09..06cadd9bb 100644 --- a/test/Dockerfile.python-3.11 +++ b/test/Dockerfile.python-3.11 @@ -19,6 +19,7 @@ FROM python:3.11 # because these images do not use the Python installed via apt. RUN apt-get update && apt-get install -y \ + fuse-overlayfs \ libsystemd-dev \ lxcfs \ sudo \ @@ -30,4 +31,5 @@ RUN pip install \ lxml \ pystemd \ pytest \ + pytest-cov \ pyyaml diff --git a/test/Dockerfile.python-3.12 b/test/Dockerfile.python-3.12 new file mode 100644 index 000000000..87e99b6da --- /dev/null +++ b/test/Dockerfile.python-3.12 @@ -0,0 +1,35 @@ +# This file is part of BenchExec, a framework for reliable benchmarking: +# https://github.com/sosy-lab/benchexec +# +# SPDX-FileCopyrightText: 2007-2024 Dirk Beyer +# +# SPDX-License-Identifier: Apache-2.0 + +# This is a Docker image for running the tests. +# It should be pushed to registry.gitlab.com/sosy-lab/software/benchexec/test +# and will be used by CI as declared in .gitlab-ci.yml. +# +# Commands for updating the image: +# docker build --pull -t registry.gitlab.com/sosy-lab/software/benchexec/test:python-3.12 - < test/Dockerfile.python-3.12 +# docker push registry.gitlab.com/sosy-lab/software/benchexec/test + +FROM python:3.12 + +# Cannot use apt package python3-pystemd here +# because these images do not use the Python installed via apt. + +RUN apt-get update && apt-get install -y \ + fuse-overlayfs \ + libsystemd-dev \ + lxcfs \ + sudo \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install \ + coloredlogs \ + "coverage[toml] >= 5.0" \ + lxml \ + pystemd \ + pytest \ + pytest-cov \ + pyyaml diff --git a/test/Dockerfile.python-3.8 b/test/Dockerfile.python-3.8 index 276b79774..5a0616ef8 100644 --- a/test/Dockerfile.python-3.8 +++ b/test/Dockerfile.python-3.8 @@ -19,6 +19,7 @@ FROM python:3.8 # because these images do not use the Python installed via apt. RUN apt-get update && apt-get install -y \ + fuse-overlayfs \ libsystemd-dev \ lxcfs \ sudo \ @@ -30,4 +31,5 @@ RUN pip install \ lxml \ pystemd \ pytest \ + pytest-cov \ pyyaml diff --git a/test/Dockerfile.python-3.9 b/test/Dockerfile.python-3.9 index ad464d9ff..21c656572 100644 --- a/test/Dockerfile.python-3.9 +++ b/test/Dockerfile.python-3.9 @@ -19,6 +19,7 @@ FROM python:3.9 # because these images do not use the Python installed via apt. RUN apt-get update && apt-get install -y \ + fuse-overlayfs \ libsystemd-dev \ lxcfs \ sudo \ @@ -30,4 +31,5 @@ RUN pip install \ lxml \ pystemd \ pytest \ + pytest-cov \ pyyaml diff --git a/test/setup_cgroupsv2_in_container.sh b/test/setup_cgroupsv2_in_container.sh new file mode 100755 index 000000000..1dc0bc389 --- /dev/null +++ b/test/setup_cgroupsv2_in_container.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# This file is part of BenchExec, a framework for reliable benchmarking: +# https://github.com/sosy-lab/benchexec +# +# SPDX-FileCopyrightText: 2007-2024 Dirk Beyer +# +# SPDX-License-Identifier: Apache-2.0 + +# This script can be run inside a container to prepare cgroups for BenchExec. +# If parameters are passed, it will execute them afterwards. + +set -eu + +# Create new sub-cgroups +# Note: While "init" can be renamed, the name "benchexec" is important +mkdir -p /sys/fs/cgroup/init /sys/fs/cgroup/benchexec +# Move all processes to that cgroup +while read pid; do + echo $pid > /sys/fs/cgroup/init/cgroup.procs +done < /sys/fs/cgroup/cgroup.procs + +# Enable controllers in subtrees for benchexec to use +for controller in $(cat /sys/fs/cgroup/cgroup.controllers); do + echo "+$controller" > /sys/fs/cgroup/cgroup.subtree_control + echo "+$controller" > /sys/fs/cgroup/benchexec/cgroup.subtree_control +done + +if [ ! -z "${1:-}" ]; then + exec "$@" +fi