Files
Giacomo Vercesi 773245b893 pypeline/cli: use dataclass for ctx.obj
Add typing to the `ctx.obj` object used to store pypeline-specific data
while parsing the command-line in click.
2026-03-31 17:00:48 +02:00

181 lines
4.3 KiB
Python

#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
import functools
import shutil
import sys
import tarfile
from pathlib import Path
import pytest
import yaml
from click.testing import CliRunner
from revng.pypeline.cli.context import ContextObject
from revng.pypeline.main import pype
ROOT = Path(__file__).resolve().parent
ENV_FILES = ["pipeline.yml", "pipebox.py", "model.yml"]
@pytest.fixture
def runner() -> CliRunner:
return CliRunner()
@pytest.fixture(params=["yaml", "tar"])
def container_format(request):
return request.param
def setup_env(test_func):
"""Decorator to setup an isolated filesystem for CLI tests.
This won't pass the arguments to the pipebox."""
@functools.wraps(test_func)
def wrapped(runner: CliRunner, *args, **kwargs):
# This is just a chdir to a tmp folder
with runner.isolated_filesystem():
# Create the necessary files inside the new filesystem
for file in ENV_FILES:
shutil.copyfile(ROOT / file, file)
# Run the test
test_func(runner, *args, **kwargs)
return wrapped
def run_partial(runner: CliRunner, **kwargs):
"""Convenience partial function to pre-fill common args"""
def run(*args: str):
result = runner.invoke(cli=pype, args=args, **kwargs)
if result.exit_code != 0:
print(result.output, file=sys.stderr)
assert result.exit_code == 0
return result
return run
def is_tar_file(path: Path) -> bool:
return tarfile.is_tarfile(path)
def is_yaml_file(path: Path) -> bool:
try:
with open(path, "r") as f:
yaml.safe_load(f)
return True
except (yaml.YAMLError, FileNotFoundError):
return False
def check_file_format(path: Path, expected_format: str):
if expected_format == "yaml":
assert is_yaml_file(path), f"File {path} is not a valid yaml file"
elif expected_format == "tar":
assert is_tar_file(path), f"File {path} is not a valid tar file"
else:
raise ValueError(f"Unknown format: {expected_format}")
@setup_env
def test_pipeline(runner: CliRunner, container_format: str):
run = run_partial(
runner,
obj=ContextObject.make(pipebox_args=["first pipebox arg", "second pipebox arg"]),
)
# First call some pipes to manually generate some containers
run(
"pipeline", "run-pipe", "GeneratorPipe", f"--format={container_format}", "model.yml", "out1"
)
check_file_format(Path("out1"), container_format)
run(
"pipeline",
"run-pipe",
"InPlacePipe",
f"--format={container_format}",
"model.yml",
"out1",
"out2",
)
check_file_format(Path("out2"), container_format)
run(
"pipeline",
"run-pipe",
"ToLowerKindPipe",
f"--format={container_format}",
"model.yml",
"out2",
"out3",
)
check_file_format(Path("out3"), container_format)
# Call a couple of analyses
run(
"pipeline",
"run-analysis",
"NullAnalysis",
"model.yml",
"out1",
)
run(
"pipeline",
"run-analysis",
"PurgeAllAnalysis",
"model.yml",
"out1",
)
run(
"pipeline",
"run-analysis",
"AddStuffAnalysis",
"model.yml",
"out1",
)
@setup_env
def test_project(runner: CliRunner, container_format: str):
run = run_partial(
runner,
obj=ContextObject.make(pipebox_args=["first pipebox arg", "second pipebox arg"]),
)
# Compute artifacts
run(
"project",
"artifact",
"ChildArtifact",
f"--format={container_format}",
"-o",
"child_artifact",
)
check_file_format(Path("child_artifact"), container_format)
run(
"project",
"artifact",
"RootArtifact",
f"--format={container_format}",
"-o",
"root_artifact",
)
check_file_format(Path("root_artifact"), container_format)
# Run analysis
run("project", "analyze", "NullAnalysis")
run("project", "analyze", "PurgeAllAnalysis")
run("project", "analyze", "AddStuffAnalysis")
# Run an analysis list
run("project", "analyze", "all_analyses")