Post-merge / detect-type (push) Successful in 1m3s
Post-merge / release (push) Successful in 1m8s
Post-merge / validate-commit-msg (push) Successful in 1m9s
Post-merge / vikunja (push) Successful in 1m13s
Post-merge / badges (push) Successful in 1m19s
Post-merge / sync-wiki (push) Successful in 1m44s
Post-merge / publish (push) Successful in 1m3s
Post-merge / configure-repo (push) Successful in 1m18s
145 lines
5.2 KiB
Python
145 lines
5.2 KiB
Python
"""Tests for prune_runner_images.py."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from scripts.prune_runner_images import (
|
|
filter_runner_images,
|
|
list_docker_images,
|
|
main,
|
|
remove_images,
|
|
)
|
|
|
|
|
|
class TestListDockerImages:
|
|
"""Tests for list_docker_images()."""
|
|
|
|
@patch("scripts.prune_runner_images.subprocess.run")
|
|
def test_returns_images_from_docker(self, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(
|
|
stdout="repo1:tag1\nrepo2:tag2\n",
|
|
returncode=0,
|
|
)
|
|
result = list_docker_images()
|
|
assert result == ["repo1:tag1", "repo2:tag2"]
|
|
|
|
@patch("scripts.prune_runner_images.subprocess.run")
|
|
def test_filters_none_entries(self, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(
|
|
stdout="repo:tag\n<none>:<none>\nother:v1\n",
|
|
returncode=0,
|
|
)
|
|
result = list_docker_images()
|
|
assert result == ["repo:tag", "other:v1"]
|
|
|
|
@patch("scripts.prune_runner_images.subprocess.run")
|
|
def test_empty_output(self, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(stdout="", returncode=0)
|
|
result = list_docker_images()
|
|
assert result == []
|
|
|
|
@patch("scripts.prune_runner_images.subprocess.run")
|
|
def test_strips_whitespace(self, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(
|
|
stdout=" repo:tag \n\n other:v2 \n",
|
|
returncode=0,
|
|
)
|
|
result = list_docker_images()
|
|
assert result == ["repo:tag", "other:v2"]
|
|
|
|
|
|
class TestFilterRunnerImages:
|
|
"""Tests for filter_runner_images()."""
|
|
|
|
def test_matches_runner_images(self) -> None:
|
|
images = [
|
|
"git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest",
|
|
"git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-quality:latest",
|
|
"git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest",
|
|
]
|
|
result = filter_runner_images(images)
|
|
assert len(result) == 3
|
|
|
|
def test_excludes_non_runner_images(self) -> None:
|
|
images = [
|
|
"docker.io/library/python:3.12",
|
|
"docker.io/library/nginx:latest",
|
|
"git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest",
|
|
]
|
|
result = filter_runner_images(images)
|
|
assert len(result) == 1
|
|
assert "ci-base" in result[0]
|
|
|
|
def test_empty_list(self) -> None:
|
|
assert filter_runner_images([]) == []
|
|
|
|
def test_no_matches(self) -> None:
|
|
images = ["python:3.12", "nginx:latest"]
|
|
assert filter_runner_images(images) == []
|
|
|
|
|
|
class TestRemoveImages:
|
|
"""Tests for remove_images()."""
|
|
|
|
@patch("scripts.prune_runner_images.subprocess.run")
|
|
def test_removes_images(self, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(returncode=0, stderr="")
|
|
images = ["repo/ci-base:latest", "repo/ci-quality:latest"]
|
|
removed = remove_images(images)
|
|
assert removed == images
|
|
assert mock_run.call_count == 2
|
|
|
|
@patch("scripts.prune_runner_images.subprocess.run")
|
|
def test_dry_run_does_not_call_docker(self, mock_run: MagicMock) -> None:
|
|
images = ["repo/ci-base:latest"]
|
|
removed = remove_images(images, dry_run=True)
|
|
assert removed == images
|
|
mock_run.assert_not_called()
|
|
|
|
@patch("scripts.prune_runner_images.subprocess.run")
|
|
def test_failed_removal_not_in_result(self, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(returncode=1, stderr="image in use")
|
|
images = ["repo/ci-base:latest"]
|
|
removed = remove_images(images)
|
|
assert removed == []
|
|
|
|
@patch("scripts.prune_runner_images.subprocess.run")
|
|
def test_empty_list(self, mock_run: MagicMock) -> None:
|
|
removed = remove_images([])
|
|
assert removed == []
|
|
mock_run.assert_not_called()
|
|
|
|
|
|
class TestMain:
|
|
"""Tests for main()."""
|
|
|
|
@patch("scripts.prune_runner_images.list_docker_images")
|
|
@patch("scripts.prune_runner_images.remove_images")
|
|
def test_no_images(self, mock_remove: MagicMock, mock_list: MagicMock) -> None:
|
|
mock_list.return_value = []
|
|
assert main([]) == 0
|
|
mock_remove.assert_not_called()
|
|
|
|
@patch("scripts.prune_runner_images.list_docker_images")
|
|
@patch("scripts.prune_runner_images.remove_images")
|
|
def test_with_images(self, mock_remove: MagicMock, mock_list: MagicMock) -> None:
|
|
mock_list.return_value = [
|
|
"repo/runner-images/ci-base:latest",
|
|
"python:3.12",
|
|
]
|
|
mock_remove.return_value = ["repo/runner-images/ci-base:latest"]
|
|
assert main([]) == 0
|
|
mock_remove.assert_called_once()
|
|
|
|
@patch("scripts.prune_runner_images.list_docker_images")
|
|
@patch("scripts.prune_runner_images.remove_images")
|
|
def test_dry_run_flag(self, mock_remove: MagicMock, mock_list: MagicMock) -> None:
|
|
mock_list.return_value = ["repo/runner-images/ci-base:latest"]
|
|
mock_remove.return_value = ["repo/runner-images/ci-base:latest"]
|
|
assert main(["--dry-run"]) == 0
|
|
mock_remove.assert_called_once_with(
|
|
["repo/runner-images/ci-base:latest"],
|
|
dry_run=True,
|
|
)
|