Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/gpu-benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:
run: python3.12 -m pip install --force-reinstall --no-deps dist/*.whl

- name: Validate installed wheel import
run: python3.12 tools/ci/check_installed_wheel.py --expected-version 0.4.0
run: python3.12 tools/ci/check_installed_wheel.py --expected-version auto

- name: Run GPU performance benchmarks
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/gpu-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ jobs:
run: python3.12 -m pip install --force-reinstall --no-deps dist/*.whl

- name: Validate installed wheel import
run: python3.12 tools/ci/check_installed_wheel.py --expected-version 0.4.0
run: python3.12 tools/ci/check_installed_wheel.py --expected-version auto

- name: Run GPU accuracy tests
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/triton-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
run: python -m pip install --force-reinstall --no-deps dist/*.whl

- name: Validate installed wheel import
run: python tools/ci/check_installed_wheel.py --expected-version 0.4.0
run: python tools/ci/check_installed_wheel.py --expected-version auto

- name: Run triton smoke tests
run: FLAGSPARSE_TRITON_SMOKE=1 pytest tests/ci -q
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

PYTHON ?= python
DIST_DIR ?= dist
EXPECTED_VERSION ?= 0.4.0
EXPECTED_VERSION ?= auto

.PHONY: help ci check ci-deps compile format-check lint lint-src pre-commit-check build install-wheel validate-wheel test-ci smoke triton-smoke triton-deps gpu-env-check gpu-benchmark release-check release clean

Expand Down
5 changes: 4 additions & 1 deletion tests/ci/test_installed_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
from tempfile import TemporaryDirectory

PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))

from tools.ci.check_installed_wheel import project_version # noqa: E402


def test_installed_wheel_import_resolves_outside_repo_tree():
Expand All @@ -44,7 +47,7 @@ def test_installed_wheel_import_resolves_outside_repo_tree():

lines = [line.strip() for line in (proc.stdout or "").splitlines() if line.strip()]
assert lines, proc.stdout
assert lines[0] == "0.4.0"
assert lines[0] == project_version(PROJECT_ROOT)
module_path = Path(lines[1]).resolve()
assert PROJECT_ROOT not in module_path.parents
assert module_path.is_file()
10 changes: 9 additions & 1 deletion tests/ci/test_package_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@

"""CPU-only smoke tests for CI packaging and public exports."""

import re

import flagsparse


def test_package_version_is_exposed():
assert flagsparse.__version__ == "0.4.0"
# __version__ comes from the installed metadata. The exact value is checked
# against pyproject.toml by tools/ci/check_installed_wheel.py on the wheel; here
# it only has to be a real, parseable version -- "0.0.0" means metadata was not found.
assert flagsparse.__version__ != "0.0.0"
assert re.fullmatch(
r"\d+(\.\d+)+((a|b|rc|\.dev|\.post)\d+)*", flagsparse.__version__
)


def test_public_exports_are_listed():
Expand Down
35 changes: 32 additions & 3 deletions tests/ci/test_packaging_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@
"""CPU-only packaging metadata checks."""

import re
import sys
from pathlib import Path

import pytest

import flagsparse

PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))

from tools.ci.check_installed_wheel import normalize_version # noqa: E402


def _read_text(path):
Expand Down Expand Up @@ -57,9 +63,11 @@ def _extract_python_requires_from_setup_py():


def test_package_version_matches_metadata():
version = flagsparse.__version__
assert version == _extract_version_from_pyproject()
assert version == _extract_version_from_setup_py()
declared = _extract_version_from_pyproject()
# The two declarations are compared as written; the installed package reports
# the PEP 440 form of them (0.4.0-dev1 is installed as 0.4.0.dev1).
assert declared == _extract_version_from_setup_py()
assert flagsparse.__version__ == normalize_version(declared)


def test_python_requires_matches_metadata():
Expand All @@ -75,3 +83,24 @@ def test_license_metadata_is_apache_2():
assert 'license = "Apache-2.0"' in pyproject
assert 'license-files = ["LICENSE"]' in pyproject
assert "Apache (Version 2.0)" in _read_text("README.md")


@pytest.mark.parametrize(
"raw,expected",
[
("0.4.0", "0.4.0"),
("0.4.0-dev1", "0.4.0.dev1"),
("0.4.0.dev1", "0.4.0.dev1"),
("1.2.3rc1", "1.2.3rc1"),
],
)
def test_declared_versions_normalize_like_a_built_wheel(raw, expected):
assert normalize_version(raw) == expected


def test_normalization_survives_a_missing_packaging_module(monkeypatch):
# packaging is not a declared dependency; the fallback must agree for the common case.
monkeypatch.setitem(sys.modules, "packaging", None)
monkeypatch.setitem(sys.modules, "packaging.version", None)
assert normalize_version("0.4.0-dev1") == "0.4.0.dev1"
assert normalize_version("0.4.0") == "0.4.0"
36 changes: 35 additions & 1 deletion tools/ci/check_installed_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import argparse
import os
import pathlib
import re
import subprocess
import sys
from dataclasses import dataclass
Expand All @@ -34,6 +35,33 @@ class WheelImportCheck:
module_path: pathlib.Path


def normalize_version(raw: str) -> str:
"""The PEP 440 form a built wheel reports for a version written in pyproject.toml.

``0.4.0-dev1`` is written that way but installs as ``0.4.0.dev1``; comparing the
raw text is what broke the wheel check when the dev version landed.
"""
try:
from packaging.version import Version

return str(Version(raw))
except ImportError: # packaging is not a declared dependency
return re.sub(r"[-_.]?(dev|a|b|rc)(\d+)", r".\1\2", raw)


def project_version(project_root: Optional[pathlib.Path] = None) -> str:
"""The version pyproject.toml declares, normalized as a wheel reports it.

pyproject.toml stays the only place the version lives.
"""
root = project_root or pathlib.Path(__file__).resolve().parents[2]
text = (root / "pyproject.toml").read_text(encoding="utf-8")
match = re.search(r'^\[project\]\s.*?^version\s*=\s*"([^"]+)"', text, re.S | re.M)
if match is None:
raise AssertionError("no [project] version in pyproject.toml")
return normalize_version(match.group(1))


def validate_installed_wheel(
expected_version: Optional[str] = None,
) -> WheelImportCheck:
Expand Down Expand Up @@ -62,6 +90,8 @@ def validate_installed_wheel(
raise AssertionError(proc.stdout)

version = lines[0]
if expected_version == "auto":
expected_version = project_version(project_root)
if expected_version is not None and version != expected_version:
raise AssertionError(f"expected version {expected_version!r}, got {version!r}")

Expand All @@ -76,7 +106,11 @@ def validate_installed_wheel(

def main(argv: Optional[List[str]] = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--expected-version", default=None)
parser.add_argument(
"--expected-version",
default=None,
help="version the installed wheel must report, or 'auto' for pyproject.toml's",
)
args = parser.parse_args(argv)

result = validate_installed_wheel(expected_version=args.expected_version)
Expand Down
Loading