Fixed cyclic dependencie AND swapped pytest-parallel for pytest-xdist to fix deadlock in tests

This commit is contained in:
Qubasa
2023-10-16 15:03:53 +02:00
parent 2ca54afe7f
commit 8cc1c2c4bd
25 changed files with 59 additions and 42 deletions

View File

@@ -11,7 +11,7 @@ from typing import Any, Optional, Tuple, get_origin
from clan_cli.dirs import machine_settings_file, specific_flake_dir from clan_cli.dirs import machine_settings_file, specific_flake_dir
from clan_cli.errors import ClanError from clan_cli.errors import ClanError
from clan_cli.flakes.types import FlakeName from clan_cli.types import FlakeName
from clan_cli.git import commit_file from clan_cli.git import commit_file
from clan_cli.nix import nix_eval from clan_cli.nix import nix_eval

View File

@@ -15,7 +15,7 @@ from clan_cli.dirs import (
from clan_cli.git import commit_file, find_git_repo_root from clan_cli.git import commit_file, find_git_repo_root
from clan_cli.nix import nix_eval from clan_cli.nix import nix_eval
from ..flakes.types import FlakeName from ..types import FlakeName
def verify_machine_config( def verify_machine_config(

View File

@@ -4,7 +4,7 @@ from pathlib import Path
from typing import Optional from typing import Optional
from .errors import ClanError from .errors import ClanError
from .flakes.types import FlakeName from .types import FlakeName
def _get_clan_flake_toplevel() -> Path: def _get_clan_flake_toplevel() -> Path:

View File

@@ -5,7 +5,7 @@ from typing import Dict
from ..async_cmd import CmdOut, run, runforcli from ..async_cmd import CmdOut, run, runforcli
from ..dirs import specific_flake_dir, specific_machine_dir from ..dirs import specific_flake_dir, specific_machine_dir
from ..errors import ClanError from ..errors import ClanError
from ..flakes.types import FlakeName from ..types import FlakeName
from ..nix import nix_shell from ..nix import nix_shell
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View File

@@ -1,5 +1,5 @@
from ..dirs import specific_machine_dir from ..dirs import specific_machine_dir
from ..flakes.types import FlakeName from ..types import FlakeName
def machine_has_fact(flake_name: FlakeName, machine: str, fact: str) -> bool: def machine_has_fact(flake_name: FlakeName, machine: str, fact: str) -> bool:

View File

@@ -3,7 +3,7 @@ import logging
import os import os
from ..dirs import machines_dir from ..dirs import machines_dir
from ..flakes.types import FlakeName from ..types import FlakeName
from .types import validate_hostname from .types import validate_hostname
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View File

@@ -5,7 +5,7 @@ from typing import Callable
from ..dirs import specific_flake_dir from ..dirs import specific_flake_dir
from ..errors import ClanError from ..errors import ClanError
from ..flakes.types import FlakeName from ..types import FlakeName
def get_sops_folder(flake_name: FlakeName) -> Path: def get_sops_folder(flake_name: FlakeName) -> Path:

View File

@@ -3,7 +3,7 @@ import os
from pathlib import Path from pathlib import Path
from ..errors import ClanError from ..errors import ClanError
from ..flakes.types import FlakeName from ..types import FlakeName
from ..machines.types import machine_name_type, validate_hostname from ..machines.types import machine_name_type, validate_hostname
from . import secrets from . import secrets
from .folders import ( from .folders import (

View File

@@ -1,6 +1,6 @@
import argparse import argparse
from ..flakes.types import FlakeName from ..types import FlakeName
from ..machines.types import machine_name_type, validate_hostname from ..machines.types import machine_name_type, validate_hostname
from . import secrets from . import secrets
from .folders import list_objects, remove_object, sops_machines_folder from .folders import list_objects, remove_object, sops_machines_folder

View File

@@ -8,7 +8,7 @@ from typing import IO
from .. import tty from .. import tty
from ..errors import ClanError from ..errors import ClanError
from ..flakes.types import FlakeName from ..types import FlakeName
from .folders import ( from .folders import (
list_objects, list_objects,
sops_groups_folder, sops_groups_folder,

View File

@@ -9,7 +9,7 @@ from typing import IO, Iterator
from ..dirs import user_config_dir from ..dirs import user_config_dir
from ..errors import ClanError from ..errors import ClanError
from ..flakes.types import FlakeName from ..types import FlakeName
from ..nix import nix_shell from ..nix import nix_shell
from .folders import sops_machines_folder, sops_users_folder from .folders import sops_machines_folder, sops_users_folder

View File

@@ -11,7 +11,7 @@ from clan_cli.nix import nix_shell
from ..dirs import specific_flake_dir from ..dirs import specific_flake_dir
from ..errors import ClanError from ..errors import ClanError
from ..flakes.types import FlakeName from ..types import FlakeName
from .folders import sops_secrets_folder from .folders import sops_secrets_folder
from .machines import add_machine, has_machine from .machines import add_machine, has_machine
from .secrets import decrypt_secret, encrypt_secret, has_secret from .secrets import decrypt_secret, encrypt_secret, has_secret

View File

@@ -1,6 +1,6 @@
import argparse import argparse
from ..flakes.types import FlakeName from ..types import FlakeName
from . import secrets from . import secrets
from .folders import list_objects, remove_object, sops_users_folder from .folders import list_objects, remove_object, sops_users_folder
from .sops import read_key, write_key from .sops import read_key, write_key

View File

@@ -10,7 +10,7 @@ from ...config.machine import (
set_config_for_machine, set_config_for_machine,
verify_machine_config, verify_machine_config,
) )
from ...flakes.types import FlakeName from ...types import FlakeName
from ...machines.create import create_machine as _create_machine from ...machines.create import create_machine as _create_machine
from ...machines.list import list_machines as _list_machines from ...machines.list import list_machines as _list_machines
from ..api_outputs import ( from ..api_outputs import (

View File

@@ -8,6 +8,7 @@
, openssh , openssh
, pytest , pytest
, pytest-cov , pytest-cov
, pytest-xdist
, pytest-subprocess , pytest-subprocess
, pytest-parallel , pytest-parallel
, pytest-timeout , pytest-timeout
@@ -45,7 +46,8 @@ let
pytest pytest
pytest-cov pytest-cov
pytest-subprocess pytest-subprocess
pytest-parallel # pytest-parallel
pytest-xdist
pytest-timeout pytest-timeout
openssh openssh
git git

View File

@@ -14,9 +14,13 @@ exclude = ["clan_cli.nixpkgs*"]
[tool.setuptools.package-data] [tool.setuptools.package-data]
clan_cli = [ "config/jsonschema/*", "webui/assets/**/*"] clan_cli = [ "config/jsonschema/*", "webui/assets/**/*"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
testpaths = "tests"
faulthandler_timeout = 60 faulthandler_timeout = 60
addopts = "--cov . --cov-report term --cov-report html:.reports/html --no-cov-on-fail --workers auto --durations 5" log_level = "DEBUG"
addopts = "--cov . --cov-report term --cov-report html:.reports/html --no-cov-on-fail -n auto --durations 5 --maxfail=1 --new-first"
norecursedirs = "tests/helpers" norecursedirs = "tests/helpers"
markers = [ "impure" ] markers = [ "impure" ]

View File

@@ -53,6 +53,7 @@ mkShell {
register-python-argcomplete --shell fish clan > $tmp_path/share/fish/vendor_completions.d/clan.fish register-python-argcomplete --shell fish clan > $tmp_path/share/fish/vendor_completions.d/clan.fish
register-python-argcomplete --shell bash clan > $tmp_path/share/bash-completion/completions/clan register-python-argcomplete --shell bash clan > $tmp_path/share/bash-completion/completions/clan
./bin/clan machines create example ./bin/clan flakes create example_clan
./bin/clan machines create example_machine example_clan
''; '';
} }

View File

@@ -8,7 +8,7 @@ import pytest
from root import CLAN_CORE from root import CLAN_CORE
from clan_cli.dirs import nixpkgs_source from clan_cli.dirs import nixpkgs_source
from clan_cli.flakes.types import FlakeName from clan_cli.types import FlakeName
# substitutes string sin a file. # substitutes string sin a file.
@@ -28,7 +28,7 @@ def substitute(
print(line, end="") print(line, end="")
class TestFlake(NamedTuple): class FlakeForTest(NamedTuple):
name: FlakeName name: FlakeName
path: Path path: Path
@@ -39,7 +39,7 @@ def create_flake(
clan_core_flake: Path | None = None, clan_core_flake: Path | None = None,
machines: list[str] = [], machines: list[str] = [],
remote: bool = False, remote: bool = False,
) -> Iterator[TestFlake]: ) -> Iterator[FlakeForTest]:
""" """
Creates a flake with the given name and machines. Creates a flake with the given name and machines.
The machine names map to the machines in ./test_machines The machine names map to the machines in ./test_machines
@@ -66,20 +66,20 @@ def create_flake(
with tempfile.TemporaryDirectory() as workdir: with tempfile.TemporaryDirectory() as workdir:
monkeypatch.chdir(workdir) monkeypatch.chdir(workdir)
monkeypatch.setenv("HOME", str(home)) monkeypatch.setenv("HOME", str(home))
yield TestFlake(flake_name, flake) yield FlakeForTest(flake_name, flake)
else: else:
monkeypatch.chdir(flake) monkeypatch.chdir(flake)
monkeypatch.setenv("HOME", str(home)) monkeypatch.setenv("HOME", str(home))
yield TestFlake(flake_name, flake) yield FlakeForTest(flake_name, flake)
@pytest.fixture @pytest.fixture
def test_flake(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestFlake]: def test_flake(monkeypatch: pytest.MonkeyPatch) -> Iterator[FlakeForTest]:
yield from create_flake(monkeypatch, FlakeName("test_flake")) yield from create_flake(monkeypatch, FlakeName("test_flake"))
@pytest.fixture @pytest.fixture
def test_flake_with_core(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestFlake]: def test_flake_with_core(monkeypatch: pytest.MonkeyPatch) -> Iterator[FlakeForTest]:
if not (CLAN_CORE / "flake.nix").exists(): if not (CLAN_CORE / "flake.nix").exists():
raise Exception( raise Exception(
"clan-core flake not found. This test requires the clan-core flake to be present" "clan-core flake not found. This test requires the clan-core flake to be present"
@@ -90,7 +90,7 @@ def test_flake_with_core(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestFlake]
@pytest.fixture @pytest.fixture
def test_flake_with_core_and_pass( def test_flake_with_core_and_pass(
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> Iterator[TestFlake]: ) -> Iterator[FlakeForTest]:
if not (CLAN_CORE / "flake.nix").exists(): if not (CLAN_CORE / "flake.nix").exists():
raise Exception( raise Exception(
"clan-core flake not found. This test requires the clan-core flake to be present" "clan-core flake not found. This test requires the clan-core flake to be present"

View File

@@ -1,13 +1,18 @@
import argparse import argparse
from clan_cli import create_parser from clan_cli import create_parser
import logging
import sys
import shlex
log = logging.getLogger(__name__)
class Cli: class Cli:
def __init__(self) -> None: def __init__(self) -> None:
self.parser = create_parser(prog="clan") self.parser = create_parser(prog="clan")
def run(self, args: list[str]) -> argparse.Namespace: def run(self, args: list[str]) -> argparse.Namespace:
cmd = shlex.join(["clan"] + args)
log.debug(f"Command: {cmd}")
parsed = self.parser.parse_args(args) parsed = self.parser.parse_args(args)
if hasattr(parsed, "func"): if hasattr(parsed, "func"):
parsed.func(parsed) parsed.func(parsed)

View File

@@ -1,8 +1,8 @@
from fixtures_flakes import TestFlake from fixtures_flakes import FlakeForTest
from clan_cli.config import machine from clan_cli.config import machine
def test_schema_for_machine(test_flake: TestFlake) -> None: def test_schema_for_machine(test_flake: FlakeForTest) -> None:
schema = machine.schema_for_machine(test_flake.name, "machine1") schema = machine.schema_for_machine(test_flake.name, "machine1")
assert "properties" in schema assert "properties" in schema

View File

@@ -3,23 +3,27 @@ from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Iterator from typing import TYPE_CHECKING, Iterator
import logging
import pytest import pytest
from cli import Cli from cli import Cli
from fixtures_flakes import FlakeForTest
from clan_cli.errors import ClanError from clan_cli.errors import ClanError
if TYPE_CHECKING: if TYPE_CHECKING:
from age_keys import KeyPair from age_keys import KeyPair
log = logging.getLogger(__name__)
def _test_identities( def _test_identities(
what: str, what: str,
test_flake: Path, test_flake: FlakeForTest,
capsys: pytest.CaptureFixture, capsys: pytest.CaptureFixture,
age_keys: list["KeyPair"], age_keys: list["KeyPair"],
) -> None: ) -> None:
cli = Cli() cli = Cli()
sops_folder = test_flake / "sops" sops_folder = test_flake.path / "sops"
cli.run(["secrets", what, "add", "foo", age_keys[0].pubkey]) cli.run(["secrets", what, "add", "foo", age_keys[0].pubkey])
assert (sops_folder / what / "foo" / "key.json").exists() assert (sops_folder / what / "foo" / "key.json").exists()
@@ -34,6 +38,7 @@ def _test_identities(
"-f", "-f",
"foo", "foo",
age_keys[0].privkey, age_keys[0].privkey,
test_flake.name,
] ]
) )
@@ -60,19 +65,19 @@ def _test_identities(
def test_users( def test_users(
test_flake: Path, capsys: pytest.CaptureFixture, age_keys: list["KeyPair"] test_flake: FlakeForTest, capsys: pytest.CaptureFixture, age_keys: list["KeyPair"]
) -> None: ) -> None:
_test_identities("users", test_flake, capsys, age_keys) _test_identities("users", test_flake, capsys, age_keys)
def test_machines( def test_machines(
test_flake: Path, capsys: pytest.CaptureFixture, age_keys: list["KeyPair"] test_flake: FlakeForTest, capsys: pytest.CaptureFixture, age_keys: list["KeyPair"]
) -> None: ) -> None:
_test_identities("machines", test_flake, capsys, age_keys) _test_identities("machines", test_flake, capsys, age_keys)
def test_groups( def test_groups(
test_flake: Path, capsys: pytest.CaptureFixture, age_keys: list["KeyPair"] test_flake: FlakeForTest, capsys: pytest.CaptureFixture, age_keys: list["KeyPair"]
) -> None: ) -> None:
cli = Cli() cli = Cli()
capsys.readouterr() # empty the buffer capsys.readouterr() # empty the buffer
@@ -100,7 +105,7 @@ def test_groups(
cli.run(["secrets", "groups", "remove-user", "group1", "user1"]) cli.run(["secrets", "groups", "remove-user", "group1", "user1"])
cli.run(["secrets", "groups", "remove-machine", "group1", "machine1"]) cli.run(["secrets", "groups", "remove-machine", "group1", "machine1"])
groups = os.listdir(test_flake / "sops" / "groups") groups = os.listdir(test_flake.path / "sops" / "groups")
assert len(groups) == 0 assert len(groups) == 0

View File

@@ -2,7 +2,7 @@ from typing import TYPE_CHECKING
import pytest import pytest
from cli import Cli from cli import Cli
from fixtures_flakes import TestFlake from fixtures_flakes import FlakeForTest
from clan_cli.machines.facts import machine_get_fact from clan_cli.machines.facts import machine_get_fact
from clan_cli.secrets.folders import sops_secrets_folder from clan_cli.secrets.folders import sops_secrets_folder
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
@pytest.mark.impure @pytest.mark.impure
def test_generate_secret( def test_generate_secret(
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
test_flake_with_core: TestFlake, test_flake_with_core: FlakeForTest,
age_keys: list["KeyPair"], age_keys: list["KeyPair"],
) -> None: ) -> None:
monkeypatch.chdir(test_flake_with_core.path) monkeypatch.chdir(test_flake_with_core.path)

View File

@@ -3,7 +3,7 @@ from pathlib import Path
import pytest import pytest
from cli import Cli from cli import Cli
from fixtures_flakes import TestFlake from fixtures_flakes import FlakeForTest
from clan_cli.machines.facts import machine_get_fact from clan_cli.machines.facts import machine_get_fact
from clan_cli.nix import nix_shell from clan_cli.nix import nix_shell
@@ -13,7 +13,7 @@ from clan_cli.ssh import HostGroup
@pytest.mark.impure @pytest.mark.impure
def test_upload_secret( def test_upload_secret(
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
test_flake_with_core_and_pass: TestFlake, test_flake_with_core_and_pass: FlakeForTest,
temporary_dir: Path, temporary_dir: Path,
host_group: HostGroup, host_group: HostGroup,
) -> None: ) -> None:

View File

@@ -5,18 +5,18 @@ from typing import TYPE_CHECKING, Iterator
import pytest import pytest
from api import TestClient from api import TestClient
from cli import Cli from cli import Cli
from fixtures_flakes import TestFlake, create_flake from fixtures_flakes import FlakeForTest, create_flake
from httpx import SyncByteStream from httpx import SyncByteStream
from root import CLAN_CORE from root import CLAN_CORE
from clan_cli.flakes.types import FlakeName from clan_cli.types import FlakeName
if TYPE_CHECKING: if TYPE_CHECKING:
from age_keys import KeyPair from age_keys import KeyPair
@pytest.fixture @pytest.fixture
def flake_with_vm_with_secrets(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestFlake]: def flake_with_vm_with_secrets(monkeypatch: pytest.MonkeyPatch) -> Iterator[FlakeForTest]:
yield from create_flake( yield from create_flake(
monkeypatch, monkeypatch,
FlakeName("test_flake_with_core_dynamic_machines"), FlakeName("test_flake_with_core_dynamic_machines"),
@@ -28,7 +28,7 @@ def flake_with_vm_with_secrets(monkeypatch: pytest.MonkeyPatch) -> Iterator[Test
@pytest.fixture @pytest.fixture
def remote_flake_with_vm_without_secrets( def remote_flake_with_vm_without_secrets(
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
) -> Iterator[TestFlake]: ) -> Iterator[FlakeForTest]:
yield from create_flake( yield from create_flake(
monkeypatch, monkeypatch,
FlakeName("test_flake_with_core_dynamic_machines"), FlakeName("test_flake_with_core_dynamic_machines"),