Files
dlp-io/tests/test_dlp_io_packaging.py
T
p40000043244@byd.com 316f307034 ci: 3.8/3.9 仅提供纯 Python wheel, CI 矩阵回退 3.10-3.14
Windows runner 未安装 Python 3.8/3.9 (run 258 失败于 Python 3.8 is required)。
requires-python 维持 >=3.8: 3.8/3.9 用户安装 py3-none-any wheel, 功能一致;
pyd wheel 覆盖 runner 预装的 3.10-3.14。
2026-08-03 16:10:06 +08:00

178 lines
5.5 KiB
Python

from __future__ import annotations
import importlib
import shutil
import subprocess
import sys
import tarfile
import zipfile
from dataclasses import dataclass
from enum import Enum
from pathlib import Path, PurePosixPath
import pytest
import yaml
from packaging.specifiers import SpecifierSet
from packaging.utils import canonicalize_name, parse_wheel_filename
from packaging.version import Version
import dlp_io
try:
import tomllib
except ModuleNotFoundError: # Python 3.10
import tomli as tomllib
PROJECT_ROOT = Path(__file__).resolve().parents[1]
WORKFLOW_PATH = PROJECT_ROOT / ".gitea" / "workflows" / "publish-dlp-io.yaml"
class DynamicField(Enum):
VERSION = "version"
class PackageName(Enum):
DLP_IO = canonicalize_name("dlp-io")
class PackageClassifier(Enum):
WINDOWS = "Operating System :: Microsoft :: Windows"
class WorkflowStep(Enum):
CLONE_TAG = "Clone immutable package tag"
TEST_MATRIX = "Test Python 3.10-3.14"
BUILD = "Build and check distribution"
SMOKE = "Smoke test release artifacts"
RELEASE = "Create Gitea release with wheels"
PYPI = "Publish packages to Gitea PyPI"
@dataclass(frozen=True)
class BuiltDistributions:
wheel: Path
sdist: Path
def _load_pyproject() -> dict:
with (PROJECT_ROOT / "pyproject.toml").open("rb") as file:
return tomllib.load(file)
def _load_workflow() -> dict:
with WORKFLOW_PATH.open(encoding="utf-8") as file:
return yaml.safe_load(file)
def _copy_source_tree(source: Path) -> None:
source.mkdir()
shutil.copy2(PROJECT_ROOT / "pyproject.toml", source)
shutil.copy2(PROJECT_ROOT / "MANIFEST.in", source)
shutil.copy2(PROJECT_ROOT / "setup.py", source)
shutil.copy2(PROJECT_ROOT / "dlp_io.py", source)
shutil.copytree(PROJECT_ROOT / "docs", source / "docs")
@pytest.fixture(scope="module")
def built_distributions(tmp_path_factory: pytest.TempPathFactory) -> BuiltDistributions:
root = tmp_path_factory.mktemp("dlp-io-build")
source = root / "source"
_copy_source_tree(source)
dist = root / "dist"
completed = subprocess.run(
[
sys.executable,
"-m",
"build",
"--no-isolation",
"--outdir",
str(dist),
],
cwd=source,
check=False,
capture_output=True,
)
assert completed.returncode == 0
wheels = tuple(dist.glob("*.whl"))
sdists = tuple(dist.glob("*.tar.gz"))
assert len(wheels) == 1
assert len(sdists) == 1
return BuiltDistributions(wheel=wheels[0], sdist=sdists[0])
def test_pyproject_declares_structured_public_package_contract() -> None:
config = _load_pyproject()
project = config["project"]
assert PackageName(canonicalize_name(project["name"])) is PackageName.DLP_IO
assert SpecifierSet(project["requires-python"]) == SpecifierSet(">=3.8")
assert {DynamicField(item) for item in project["dynamic"]} == {
DynamicField.VERSION
}
known_classifier_values = {item.value for item in PackageClassifier}
known_classifiers = {
PackageClassifier(item)
for item in project["classifiers"]
if item in known_classifier_values
}
assert PackageClassifier.WINDOWS in known_classifiers
version_ref = config["tool"]["setuptools"]["dynamic"]["version"]["attr"]
module_name, attribute_name = version_ref.rsplit(".", 1)
declared_version = getattr(importlib.import_module(module_name), attribute_name)
assert Version(declared_version) == Version(dlp_io.__version__)
def test_wheel_contains_only_installable_dlp_io_runtime(
built_distributions: BuiltDistributions,
) -> None:
name, version, build, tags = parse_wheel_filename(built_distributions.wheel.name)
with zipfile.ZipFile(built_distributions.wheel) as archive:
members = {PurePosixPath(item) for item in archive.namelist()}
assert PackageName(name) is PackageName.DLP_IO
assert version == Version(dlp_io.__version__)
assert build == ()
assert tags
assert PurePosixPath("dlp_io.py") in members
assert not [item for item in members if item.suffix == ".pyd"]
member_roots = {Path(item.parts[0]) for item in members}
assert member_roots.isdisjoint({Path("tests"), Path("Lib")})
assert PurePosixPath(".env") not in members
def test_sdist_contains_package_sources_and_public_readme_only(
built_distributions: BuiltDistributions,
) -> None:
with tarfile.open(built_distributions.sdist, mode="r:gz") as archive:
members = {
PurePosixPath(*PurePosixPath(item.name).parts[1:])
for item in archive.getmembers()
if item.isfile()
}
assert PurePosixPath("pyproject.toml") in members
assert PurePosixPath("setup.py") in members
assert PurePosixPath("docs/DLP_IO_LIBRARY.md") in members
assert PurePosixPath("dlp_io.py") in members
member_roots = {Path(item.parts[0]) for item in members}
assert Path("tests") not in member_roots
assert PurePosixPath("README.md") not in members
assert PurePosixPath(".env") not in members
def test_publish_workflow_tests_builds_and_releases_tagged_distributions() -> None:
document = _load_workflow()
trigger = document.get("on", document.get(True))
assert set(trigger["push"]["tags"]) == {"dlp-io-v*.*.*"}
assert "workflow_dispatch" in trigger
job = document["jobs"]["publish"]
assert job["runs-on"] == "windows-latest"
assert Path(job["defaults"]["run"]["working-directory"]) == Path("repo")
step_names = [step["name"] for step in job["steps"]]
assert step_names == [step.value for step in WorkflowStep]