Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: typecheck all tests #2328

Merged
merged 3 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 0 additions & 3 deletions .github/workflows/test_downstream.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,5 @@ jobs:
New-Item -ItemType Directory -Force -Path "${{ env.TARGET_RELEASE }}"
Move-Item -Path "pixi_bin/pixi-${{ matrix.arch.target }}${{ matrix.arch.extension }}" -Destination "${{ env.TARGET_RELEASE }}/pixi.exe"
shell: pwsh
- name: Typecheck integration tests
if: runner.os == 'Linux'
run: ${{ env.TARGET_RELEASE }}/pixi${{ matrix.arch.extension }} run typecheck-integration
- name: Run integration tests
run: ${{ env.TARGET_RELEASE }}/pixi${{ matrix.arch.extension }} run test-integration-ci
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ repos:
language: system
types_or: [python, pyi]
require_serial: true
# Typecheck python tests
- id: typecheck-tests
name: typecheck-tests
entry: pixi run typecheck-tests
language: system
types_or: [python, pyi]
pass_filenames: false
# typos
- id: typos
name: typos
Expand Down
2 changes: 1 addition & 1 deletion pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ test-integration-dev = { cmd = "pytest --numprocesses=auto --durations=10 tests/
# you can also pass a specific test function, like this:
# /path/to/test.py::test_function
test-specific-test = { cmd = "pytest", depends-on = ["build"] }
typecheck-integration = "mypy --strict tests/integration"
typecheck-tests = "mypy --strict tests --exclude pypi-indexes"
update-integration-test-data = { cmd = "python update-channels.py", cwd = "tests/integration/test_data" }

[feature.dev.dependencies]
Expand Down
2 changes: 1 addition & 1 deletion tests/pypi_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


class ProxyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
def do_GET(self) -> None:
# Check for basic authentication
if self.headers.get("Authorization") is None:
self.send_response(401)
Expand Down
2 changes: 1 addition & 1 deletion tests/run_all_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def run_test_in_subfolders(
return results


def print_summary(results: Results, pixi_exec: Path):
def print_summary(results: Results, pixi_exec: Path) -> None:
summary_text = f"║ ✅ {len(results.succeeded):<10} 🚀 {len(results.installed):<10} ❌ {len(results.failed):<10} 🤷 {len(results.skipped):<10} ║"

# Calculate the actual length of the line, considering the emojis as single characters
Expand Down
12 changes: 7 additions & 5 deletions tests/wheel_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
from generate_summaries import terminal_summary, markdown_summary
from typing import Any
from helpers import setup_stdout_stderr_logging
from generate_summaries import terminal_summary, markdown_summary
import pytest


def pytest_configure(config):
def pytest_configure(config: pytest.Config) -> None:
setup_stdout_stderr_logging()


def pytest_addoption(parser):
def pytest_addoption(parser: pytest.Parser) -> None:
# Used to override the default path to the pixi executable
parser.addoption("--pixi-exec", action="store", help="Path to the pixi executable")


def pytest_terminal_summary(terminalreporter, exitstatus, config):
def pytest_terminal_summary(terminalreporter: Any, exitstatus: int, config: pytest.Config) -> None:
"""
At the end of the test session, generate a summary report.
"""
terminal_summary()


def pytest_sessionfinish(session, exitstatus):
def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
"""
At the end of the test session, generate a `.summary.md` report. That contains the
same information as the terminal summary.
Expand Down
4 changes: 2 additions & 2 deletions tests/wheel_tests/generate_summaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from read_wheels import read_wheel_file


def terminal_summary():
def terminal_summary() -> None:
# Read aggregated results from the shared file
results_file = RESULTS_FILE
if not results_file.exists():
Expand Down Expand Up @@ -79,7 +79,7 @@ def terminal_summary():
console.print(summary_panel)


def markdown_summary():
def markdown_summary() -> None:
if not RESULTS_FILE.exists():
return

Expand Down
10 changes: 7 additions & 3 deletions tests/wheel_tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def run(args: list[StrPath], cwd: StrPath | None = None) -> None:
proc.check_returncode()


def add_system_requirements(manifest_path: pathlib.Path, system_requirements: dict[str, Any]):
def add_system_requirements(
manifest_path: pathlib.Path, system_requirements: dict[str, Any]
) -> None:
"""
Add system requirements to the manifest file
add something like this:
Expand All @@ -34,7 +36,7 @@ def add_system_requirements(manifest_path: pathlib.Path, system_requirements: di
tomli_w.dump(manifest, f)


def setup_stdout_stderr_logging():
def setup_stdout_stderr_logging() -> None:
"""
Set up the logging directory
"""
Expand All @@ -44,7 +46,9 @@ def setup_stdout_stderr_logging():
file.unlink()


def log_called_process_error(name: str, err: subprocess.CalledProcessError, std_err_only=False):
def log_called_process_error(
name: str, err: subprocess.CalledProcessError, std_err_only: bool = False
) -> None:
"""
Log the output of a subprocess that failed
has the option to log only the stderr
Expand Down
2 changes: 1 addition & 1 deletion tests/wheel_tests/read_wheels.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def target_iter(self) -> Iterable[str]:
return []

@classmethod
def __from_toml(cls, spec: dict[str, str] | str) -> Self:
def __from_toml(cls, spec: dict[str, Any] | str) -> Self:
if isinstance(spec, str):
return cls(version=spec, extras=None, target=None, system_requirements=None)
if isinstance(spec, dict):
Expand Down
22 changes: 15 additions & 7 deletions tests/wheel_tests/record_results.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from dataclasses import dataclass, field
from pathlib import Path
import tomllib
from typing import Any
import tomli_w
from filelock import FileLock

Expand All @@ -9,7 +11,13 @@
LOCK_FILE = RESULTS_FILE.with_suffix(".lock")


def record_result(test_id: str, name: str, outcome: str, duration: float, details: str):
@dataclass
class Test:
id: str
results: list[dict[str, Any]] = field(default_factory=list)


def record_result(test_id: str, name: str, outcome: str, duration: float, details: str) -> None:
"""
Collects test status after each test run, compatible with pytest-xdist.
"""
Expand All @@ -19,25 +27,25 @@ def record_result(test_id: str, name: str, outcome: str, duration: float, detail
lock = FileLock(str(LOCK_FILE))

with lock:
test = {"id": test_id, "results": []}
test = Test(id=test_id)

# Get the existing results
if RESULTS_FILE.exists():
with RESULTS_FILE.open("rb") as f:
data = tomllib.load(f)
# If this doesn't hold, don't use the recorded data
if "id" in data and data["id"] == test_id:
test = data
test = Test(id=data["id"], results=data["results"])

# Append the new result
# if we are in the same session
if test["id"] == test_id:
test["results"].append(result)
if test.id == test_id:
test.results.append(result)
# The data is from a different session
# so we overwrite the data
else:
test["results"] = [result]
test.results = [result]

# Write the results back to the file
with RESULTS_FILE.open("wb") as f:
tomli_w.dump(test, f)
tomli_w.dump(test.__dict__, f)
14 changes: 7 additions & 7 deletions tests/wheel_tests/test_common_wheels.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


@pytest.mark.flaky(reruns=5, reruns_delay=1, condition=sys.platform.startswith("win32"))
def test_wheel(pixi: str, package: Package, testrun_uid: str, tmp_path: pathlib.Path):
def test_wheel(pixi: str, package: Package, testrun_uid: str, tmp_path: pathlib.Path) -> None:
"""
Create a temporary directory and install the wheel in it.
The `testrun_uid` is a unique identifier for the test run
Expand All @@ -31,7 +31,7 @@ def test_wheel(pixi: str, package: Package, testrun_uid: str, tmp_path: pathlib.
run([pixi, "add", "--no-progress", "--manifest-path", manifest_path, "python==3.12.*"])

# Add the wheel to the project
run_args = [
run_args: list[str | os.PathLike[str]] = [
pixi,
"-vvv",
"add",
Expand All @@ -44,6 +44,7 @@ def test_wheel(pixi: str, package: Package, testrun_uid: str, tmp_path: pathlib.

# Add for another platform, if specified
for platform in package.spec.target_iter():
assert isinstance(package.spec.target, str)
run_args.extend(["--platform", package.spec.target])

run(run_args)
Expand All @@ -60,7 +61,7 @@ def test_wheel(pixi: str, package: Package, testrun_uid: str, tmp_path: pathlib.
raise e


def pytest_generate_tests(metafunc):
def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
"""
This generates the test for the wheels by reading the wheels from the toml specification
creates a test for each entry in the toml file
Expand All @@ -71,10 +72,10 @@ def pytest_generate_tests(metafunc):


@pytest.fixture(scope="session")
def pixi(pytestconfig):
def pixi(pytestconfig: pytest.Config) -> pathlib.Path:
# The command line argument overrides the default path
if pytestconfig.getoption("pixi_exec"):
return pytestconfig.getoption("pixi_exec")
return pathlib.Path(pytestconfig.getoption("pixi_exec"))

# Check pixi environment variable
project_root = os.environ.get("PIXI_PROJECT_ROOT")
Expand All @@ -84,8 +85,7 @@ def pixi(pytestconfig):
# Check if the target directory exists
# This assertion is for the type checker
assert project_root
project_root = pathlib.Path(project_root)
target_dir = project_root.joinpath("target-pixi/release")
target_dir = pathlib.Path(project_root).joinpath("target-pixi/release")
if not target_dir.exists():
pytest.exit("pixi executable not found, run `pixi r build` first")

Expand Down
4 changes: 2 additions & 2 deletions tests/wheel_tests/test_read_wheels.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from read_wheels import Package, PackageSpec, WheelTest


def test_spec_to_add_cmd():
def test_spec_to_add_cmd() -> None:
assert Package("foo", PackageSpec()).to_add_cmd() == "foo"
assert Package("foo", PackageSpec("1.0")).to_add_cmd() == "foo==1.0"
assert Package("foo", PackageSpec("1.0", "bar")).to_add_cmd() == "foo[bar]==1.0"


def test_wheel_test_from_str():
def test_wheel_test_from_str() -> None:
toml = """
foo = "*"
bar = { version = "1.0", extras = "baz", target = "linux-64" }
Expand Down
Loading