## What is a test?
-A single test is a directory in `tests`. It has to contain `Dockerfile`. The container created by the `Dockerfile` has to have an executable called `/test` in its file system. The `Dockerfile` must be configured to execute systemd on container startup. The `/test` executable is then called manually by the testing tool.
+A single test is a directory in `tests`. It has to contain `Dockerfile`, which defines the used system. The `Dockerfile` must be configured to execute systemd on container startup.
-Exit code of the `/test` script determines the result of a test. 0 means test successfull, non-zero unsuccessful.
+The test directory is mounted to `/test`, the root of the git repository is mounted to `/repo`. Both are read-only mounts.
+
+The test starts with the execution of `/test/run` executable. Its exit code determines the result of a test. 0 means test successfull, non-zero unsuccessful.
## How does the integration tool work?
The tool launches a Podman subprocess which exposes a HTTP API. This API is then used to control the containers.
-For each directory in `tests/`, the testing tool builds the container, starts it, exec's `/test` and observes its result. After that, it issues `systemctl poweroff` and waits until the container turns itself off.
+For each directory in `tests/`, the testing tool builds the container, starts it, exec's `/test/run` and observes its result. After that, it issues `systemctl poweroff` and waits until the container turns itself off.
Because building the container is slow (even with Podman's caching), we skip it if it's not needed. The testing tool creates a `.contentshash` file within each test directory, which contains a hash of all content. The container is rebuilt only when the hash changes (or the file is missing).
import subprocess
import signal
import uuid
-from typing import Optional, List, BinaryIO
+from typing import Optional, List, BinaryIO, Dict
import shutil
import tarfile
import os
import hashlib
from _hashlib import HASH as Hash
-from pathlib import Path
+from pathlib import Path, PurePath
from typing import Union
# create hashfile for future caching
self._create_hashfile(context_dir, current_hash)
- def _api_create_container(self, image: str) -> str:
+ def _api_create_container(
+ self, image: str, bind_mount_ro: Dict[PurePath, PurePath] = {}
+ ) -> str:
response = requests.post(
self._create_url("libpod/containers/create"),
- json={"image": image, "remove": True, "systemd": "true"},
+ json={
+ "image": image,
+ "remove": True,
+ "systemd": "true",
+ "mounts": [
+ {
+ "destination": str(destination),
+ "options": ["ro"],
+ "source": str(source),
+ "type": "bind",
+ }
+ for source, destination in bind_mount_ro.items()
+ ],
+ },
)
response.raise_for_status()
return response.json()["Id"]
)
response.raise_for_status()
- def start_temporary_and_wait(self, image: str, command: List[str]) -> int:
+ def start_temporary_and_wait(
+ self, image: str, command: List[str], bind_mount_ro: Dict[PurePath, PurePath] = {}
+ ) -> int:
# start the container
- container_id = self._api_create_container(image)
+ container_id = self._api_create_container(image, bind_mount_ro)
self._api_start_container(container_id)
# the container is booting, let's give it some time
RESET = "\033[0m"
+def _get_git_root() -> PurePath:
+ result = subprocess.run("git rev-parse --show-toplevel", shell=True, capture_output=True)
+ return PurePath(str(result.stdout, encoding='utf8').strip())
+
class TestRunner:
_TEST_DIRECTORY = "tests"
- _TEST_ENTRYPOINT = ["/test"]
+ _TEST_ENTRYPOINT = ["/test/run"]
@staticmethod
def _list_tests() -> List[Path]:
manager.build_image(test_path, image)
print("\tRunning...")
exit_code = manager.start_temporary_and_wait(
- image, TestRunner._TEST_ENTRYPOINT
+ image,
+ TestRunner._TEST_ENTRYPOINT,
+ bind_mount_ro={
+ _get_git_root(): PurePath('/repo'),
+ test_path.absolute(): '/test'
+ }
)
if exit_code == 0:
print(f"\t{Colors.GREEN}Test succeeded{Colors.RESET}")