Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ platforms = ["linux"]
description = "Development environment"
dependencies = [
"autopep8",
"jq",
"ruff",
"mypy",
"pytest",
Expand Down
65 changes: 60 additions & 5 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
from collections.abc import Mapping
from typing import IO, Any, Optional, Protocol, Union
import contextlib
import functools
import os
from collections.abc import Iterable, Iterator, Mapping
from typing import IO, Any, Callable, Optional, Protocol, TypeVar, Union, cast

import click.core
import click.testing
import jq as _jq

import tmt.__main__
import tmt.cli._root
from tmt._compat.typing import ParamSpec
from tmt.utils import Path

T = TypeVar('T')
P = ParamSpec('P')


def reset_common() -> None:
Expand All @@ -30,6 +39,52 @@ def reset_common() -> None:
klass.cli_invocation = None


@contextlib.contextmanager
def cwd(path: Path) -> Iterator[Path]:
"""
A context manager switching the current working directory to a given path.

.. warning::

Changing the current working directory can have unexpected
consequences in a multithreaded environment.
"""

cwd = Path.cwd()

os.chdir(path)

try:
yield path

finally:
os.chdir(cwd)


def with_cwd(path: Path) -> Callable[[Callable[P, T]], Callable[P, T]]:
"""
Decorate a test to have it run in the given path as its CWD.
"""

def _with_cwd(fn: Callable[P, T]) -> Callable[P, T]:
@functools.wraps(fn)
def __with_cwd(*args: P.args, **kwargs: P.kwargs) -> T:
with cwd(path):
return fn(*args, **kwargs)

return __with_cwd

return _with_cwd


def jq_all(data: Any, query: str) -> Iterable[Any]:
"""
Apply a jq filter on given data, and return the product.
"""

return cast(Iterable[Any], _jq.compile(query).input(data).all())


class RunTmt(Protocol):
"""
A type representing :py:meth:`CliRunner.invoke`.
Expand All @@ -40,7 +95,7 @@ class RunTmt(Protocol):

def __call__(
self,
*args: str,
*args: Union[str, Path],
command: Optional[click.BaseCommand] = None,
input: Optional[Union[str, bytes, IO[Any]]] = None,
env: Optional[Mapping[str, Optional[str]]] = None,
Expand All @@ -57,7 +112,7 @@ def __init__(self) -> None:

def invoke( # type: ignore[override]
self,
*args: str,
*args: Union[str, Path],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would refer not to extend this one, at the very least not inside this function

command: Optional[click.BaseCommand] = None,
input: Optional[Union[str, bytes, IO[Any]]] = None,
env: Optional[Mapping[str, Optional[str]]] = None,
Expand All @@ -73,7 +128,7 @@ def invoke( # type: ignore[override]

return super().invoke(
command,
args=args,
args=[str(arg) for arg in args],
input=input,
env=env,
catch_exceptions=catch_exceptions,
Expand Down
3 changes: 2 additions & 1 deletion tests/policy/main.fmf
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ summary: Verify tmt policies work as expected
test: ./test-test.sh

/plan:
test: ./test-plan.sh
framework: shell
test: $PYTEST ./test_plan.py
44 changes: 0 additions & 44 deletions tests/policy/test-plan.sh

This file was deleted.

63 changes: 63 additions & 0 deletions tests/policy/test_plan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from typing import TYPE_CHECKING

from tests import jq_all, with_cwd

import tmt.cli
from tmt.utils import Path, from_yaml

if TYPE_CHECKING:
from tests import RunTmt


TEST_DIR = Path(__file__).absolute().parent
DATA_DIR = TEST_DIR / 'data/plan'
POLICIES_DIR = TEST_DIR / 'policies'


@with_cwd(DATA_DIR)
def test_export_modified_plan(run_tmt: 'RunTmt') -> None:
"""
Verify a plan export is affected by the policy.

.. note::

Not doing anything complex, test-level policy tests cover plenty
of policy instructions and behavior. Focusing on plan-specific
modifications only.
"""

result = run_tmt(
'-vv',
'plan',
'export',
'--policy-file',
POLICIES_DIR / 'plan/plan.yaml',
)

assert f"Apply tmt policy '{POLICIES_DIR}/plan/plan.yaml' to plans." in result.stderr

plans_exported = from_yaml(result.stdout)

assert jq_all(plans_exported, '.[] | .discover') == [None], "Verify that discover key is empty"

assert jq_all(plans_exported, '.[] | .prepare | .[] | [.how, .order]') == [
['feature', 17],
['shell', None],
], 'Verify that prepare step contains two phases'

assert jq_all(plans_exported, '.[] | .contact') == [["xyzzy"]], (
'Verify that contact key was populated'
)


@with_cwd(DATA_DIR)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we maybe avoid this and use --root instead? We could even put it in the run_tmt fixture along with some plan selection and stuff.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd consider this a different problem. One that can be improved, but I tried to not invent too many new things.

def test_run_modified_plan(run_tmt: 'RunTmt') -> None:
"""
Verify a run is affected by the policy.
"""

result = run_tmt('-vv', 'run', '-a', '--policy-file', POLICIES_DIR / 'plan/simple.yaml')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing exit code verification. The original bash test expected exit code 3 (rlRun ... 3), but the new pytest test doesn't verify the exit code. The test should fail if the command doesn't exit with code 3.

result = run_tmt('-vv', 'run', '-a', '--policy-file', POLICIES_DIR / 'plan/simple.yaml')
assert result.exit_code == 3, "Expected exit code 3"
Suggested change
result = run_tmt('-vv', 'run', '-a', '--policy-file', POLICIES_DIR / 'plan/simple.yaml')
result = run_tmt('-vv', 'run', '-a', '--policy-file', POLICIES_DIR / 'plan/simple.yaml')
assert result.exit_code == 3, "Expected exit code 3"

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.


assert result.exit_code == tmt.cli.TmtExitCode.NO_RESULTS_FOUND
assert f"Apply tmt policy '{POLICIES_DIR}/plan/simple.yaml' to plans." in result.stderr
assert "No tests found, finishing plan." in result.stderr
Loading