Skip to content

Commit

Permalink
Add a real scenario
Browse files Browse the repository at this point in the history
- Add description support to `Scenario`s
- Add test suite for building and viewing all scenarios in `scenarios/`
- Remove duplicate `dist/` directory from `build/<package>/`
- Include reason for invalid scenario errors
  • Loading branch information
zanieb committed Dec 13, 2023
1 parent 4896b91 commit 0bf3e64
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 37 deletions.
17 changes: 17 additions & 0 deletions scenarios/requires-does-not-exist.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "requires-does-not-exist",
"description": "Package `a` requires any version of package `b` which does not exist",
"root": "a",
"packages": {
"a": {
"versions": {
"1.0.0": {
"requires_python": ">=3.7",
"requires": [
"b"
]
}
}
}
}
}
25 changes: 15 additions & 10 deletions src/packse/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import shutil
import subprocess
from pathlib import Path

from contextlib import contextmanager
from typing import Generator
from packse.error import (
BuildError,
DestinationAlreadyExists,
Expand All @@ -27,7 +28,7 @@ def build(targets: list[Path], rm_destination: bool):
try:
load_scenario(target)
except Exception as exc:
raise InvalidScenario(target) from exc
raise InvalidScenario(target, reason=str(exc)) from exc

# Then build each one
for target in targets:
Expand Down Expand Up @@ -118,23 +119,27 @@ def build_scenario_package(
)

try:
dists = build_package_distributions(package_destination)
for dist in build_package_distributions(package_destination):
shared_path = dist_destination / dist.name
logger.info(
"Linked distribution to %s", shared_path.relative_to(work_dir)
)
shared_path.hardlink_to(dist)
except subprocess.CalledProcessError as exc:
raise BuildError(
f"Building {package_destination.relative_to(work_dir)} with hatch failed",
exc.output.decode(),
)

for dist in dists:
shared_path = dist_destination / dist.name
logger.info("Linked distribution to %s", shared_path.relative_to(work_dir))
shared_path.hardlink_to(dist)


def build_package_distributions(target: Path) -> tuple[Path]:
def build_package_distributions(target: Path) -> Generator[Path, None, None]:
"""
Build package distributions, yield each built distribution path, then delete the distribution folder.
"""
subprocess.check_output(
["hatch", "build"],
cwd=target,
stderr=subprocess.STDOUT,
)
return tuple((target / "dist").iterdir())
yield from (target / "dist").iterdir()
shutil.rmtree(target / "dist")
4 changes: 2 additions & 2 deletions src/packse/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ def __init__(self, destination: Path) -> None:


class InvalidScenario(UserError):
def __init__(self, path: Path) -> None:
message = f"File at '{path}' is not a valid scenario"
def __init__(self, path: Path, reason: str) -> None:
message = f"File at '{path}' is not a valid scenario: {reason}"
super().__init__(message)


Expand Down
5 changes: 5 additions & 0 deletions src/packse/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ class Scenario(msgspec.Struct):
The template to use for scenario packages.
"""

description: str | None = None
"""
The description of the scenario
"""

def hash(self) -> str:
"""
Return a hash of the scenario contents
Expand Down
2 changes: 1 addition & 1 deletion src/packse/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def view(targets: list[Path]):
try:
load_scenario(target)
except Exception as exc:
raise InvalidScenario(target) from exc
raise InvalidScenario(target, reason=str(exc)) from exc

# Then view each one
for target in targets:
Expand Down
26 changes: 3 additions & 23 deletions tests/__snapshots__/test_build.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
dict({
'exit_code': 0,
'filesystem': dict({
'build/example-9e723676/example-9e723676-a-1.0.0/dist/example_9e723676_a-1.0.0-py3-none-any.whl': 'md5:38bdcf701bb74c615cda5cd8dd4d4ce3',
'build/example-9e723676/example-9e723676-a-1.0.0/dist/example_9e723676_a-1.0.0.tar.gz': 'md5:cf5d79fbc9777d41a69801f79edff4de',
'build/example-9e723676/example-9e723676-a-1.0.0/pyproject.toml': '''
[build-system]
requires = ["hatchling"]
Expand All @@ -24,8 +22,6 @@
__version__ = "1.0.0"

''',
'build/example-9e723676/example-9e723676-b-1.0.0/dist/example_9e723676_b-1.0.0-py3-none-any.whl': 'md5:8af703510c999651652071adbf469c20',
'build/example-9e723676/example-9e723676-b-1.0.0/dist/example_9e723676_b-1.0.0.tar.gz': 'md5:9bcf4c09b49d56fc6cf3ecdfeeac4ba0',
'build/example-9e723676/example-9e723676-b-1.0.0/pyproject.toml': '''
[build-system]
requires = ["hatchling"]
Expand Down Expand Up @@ -54,17 +50,11 @@
├── build
│ └── example-9e723676
│ ├── example-9e723676-a-1.0.0
│ │ ├── dist
│ │ │ ├── example_9e723676_a-1.0.0-py3-none-any.whl
│ │ │ └── example_9e723676_a-1.0.0.tar.gz
│ │ ├── pyproject.toml
│ │ └── src
│ │ └── example_9e723676_a
│ │ └── __init__.py
│ └── example-9e723676-b-1.0.0
│ ├── dist
│ │ ├── example_9e723676_b-1.0.0-py3-none-any.whl
│ │ └── example_9e723676_b-1.0.0.tar.gz
│ ├── pyproject.toml
│ └── src
│ └── example_9e723676_b
Expand All @@ -76,7 +66,7 @@
├── example_9e723676_b-1.0.0-py3-none-any.whl
└── example_9e723676_b-1.0.0.tar.gz

12 directories, 12 files
10 directories, 8 files

''',
}),
Expand Down Expand Up @@ -110,8 +100,6 @@
dict({
'exit_code': 0,
'filesystem': dict({
'build/example-9e723676/example-9e723676-a-1.0.0/dist/example_9e723676_a-1.0.0-py3-none-any.whl': 'md5:38bdcf701bb74c615cda5cd8dd4d4ce3',
'build/example-9e723676/example-9e723676-a-1.0.0/dist/example_9e723676_a-1.0.0.tar.gz': 'md5:cf5d79fbc9777d41a69801f79edff4de',
'build/example-9e723676/example-9e723676-a-1.0.0/pyproject.toml': '''
[build-system]
requires = ["hatchling"]
Expand All @@ -131,8 +119,6 @@
__version__ = "1.0.0"

''',
'build/example-9e723676/example-9e723676-b-1.0.0/dist/example_9e723676_b-1.0.0-py3-none-any.whl': 'md5:8af703510c999651652071adbf469c20',
'build/example-9e723676/example-9e723676-b-1.0.0/dist/example_9e723676_b-1.0.0.tar.gz': 'md5:9bcf4c09b49d56fc6cf3ecdfeeac4ba0',
'build/example-9e723676/example-9e723676-b-1.0.0/pyproject.toml': '''
[build-system]
requires = ["hatchling"]
Expand Down Expand Up @@ -161,17 +147,11 @@
├── build
│ └── example-9e723676
│ ├── example-9e723676-a-1.0.0
│ │ ├── dist
│ │ │ ├── example_9e723676_a-1.0.0-py3-none-any.whl
│ │ │ └── example_9e723676_a-1.0.0.tar.gz
│ │ ├── pyproject.toml
│ │ └── src
│ │ └── example_9e723676_a
│ │ └── __init__.py
│ └── example-9e723676-b-1.0.0
│ ├── dist
│ │ ├── example_9e723676_b-1.0.0-py3-none-any.whl
│ │ └── example_9e723676_b-1.0.0.tar.gz
│ ├── pyproject.toml
│ └── src
│ └── example_9e723676_b
Expand All @@ -183,7 +163,7 @@
├── example_9e723676_b-1.0.0-py3-none-any.whl
└── example_9e723676_b-1.0.0.tar.gz

12 directories, 12 files
10 directories, 8 files

''',
}),
Expand Down Expand Up @@ -246,7 +226,7 @@
dict({
'exit_code': 1,
'stderr': '''
File at '$PWD/test.json' is not a valid scenario.
File at '$PWD/test.json' is not a valid scenario: Input data was truncated.

''',
'stdout': '',
Expand Down
83 changes: 83 additions & 0 deletions tests/__snapshots__/test_scenarios.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# serializer version: 1
# name: test_build_all_scenarios[requires-does-not-exist]
dict({
'exit_code': 0,
'filesystem': dict({
'build/requires-does-not-exist-1888d2a8/requires-does-not-exist-1888d2a8-a-1.0.0/pyproject.toml': '''
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build]
sources = ["src"]

[project]
name = "requires-does-not-exist-1888d2a8-a"
version = "1.0.0"
dependencies = ["requires-does-not-exist-1888d2a8-b"]
requires-python = ">=3.7"

''',
'build/requires-does-not-exist-1888d2a8/requires-does-not-exist-1888d2a8-a-1.0.0/src/requires_does_not_exist_1888d2a8_a/__init__.py': '''
__version__ = "1.0.0"

''',
'dist/requires-does-not-exist-1888d2a8/requires_does_not_exist_1888d2a8_a-1.0.0-py3-none-any.whl': 'md5:0ede0ef9fce35b3b22369a4091b6d73d',
'dist/requires-does-not-exist-1888d2a8/requires_does_not_exist_1888d2a8_a-1.0.0.tar.gz': 'md5:77825e5070d89109cb92e004c7adf79b',
'tree': '''
test_build_all_scenarios_requi0
├── build
│ └── requires-does-not-exist-1888d2a8
│ └── requires-does-not-exist-1888d2a8-a-1.0.0
│ ├── pyproject.toml
│ └── src
│ └── requires_does_not_exist_1888d2a8_a
│ └── __init__.py
└── dist
└── requires-does-not-exist-1888d2a8
├── requires_does_not_exist_1888d2a8_a-1.0.0-py3-none-any.whl
└── requires_does_not_exist_1888d2a8_a-1.0.0.tar.gz

7 directories, 4 files

''',
}),
'stderr': '''
INFO:root:Building 'requires-does-not-exist-1888d2a8' in directory 'build/requires-does-not-exist-1888d2a8'
INFO:packse.template:Creating requires-does-not-exist-1888d2a8-a-1.0.0
INFO:packse.template:Creating requires-does-not-exist-1888d2a8-a-1.0.0/pyproject.toml
INFO:packse.template:Creating requires-does-not-exist-1888d2a8-a-1.0.0/src
INFO:packse.template:Creating requires-does-not-exist-1888d2a8-a-1.0.0/src/requires_does_not_exist_1888d2a8_a
INFO:packse.template:Creating requires-does-not-exist-1888d2a8-a-1.0.0/src/requires_does_not_exist_1888d2a8_a/__init__.py
INFO:packse.build:Building build/requires-does-not-exist-1888d2a8/requires-does-not-exist-1888d2a8-a-1.0.0 with hatch
INFO:packse.build:Linked distribution to dist/requires-does-not-exist-1888d2a8/requires_does_not_exist_1888d2a8_a-1.0.0.tar.gz
INFO:packse.build:Linked distribution to dist/requires-does-not-exist-1888d2a8/requires_does_not_exist_1888d2a8_a-1.0.0-py3-none-any.whl

''',
'stdout': '''
requires-does-not-exist-1888d2a8-a

''',
})
# ---
# name: test_view_all_scenarios[requires-does-not-exist]
dict({
'exit_code': 0,
'filesystem': dict({
'tree': '''
test_view_all_scenarios_requir0

0 directories

''',
}),
'stderr': '',
'stdout': '''
requires-does-not-exist-1888d2a8
└── a-1.0.0
└── requires b


''',
})
# ---
2 changes: 1 addition & 1 deletion tests/__snapshots__/test_view.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
dict({
'exit_code': 1,
'stderr': '''
File at '$PWD/test.json' is not a valid scenario.
File at '$PWD/test.json' is not a valid scenario: Input data was truncated.

''',
'stdout': '',
Expand Down
36 changes: 36 additions & 0 deletions tests/test_scenarios.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Tests all included scenarios
"""

import pytest
from packse import __development_base_path__

from .common import snapshot_command

EXCLUDE = frozenset(("example.json",))
ALL_SCENARIOS = tuple(
sorted(
path
for path in (__development_base_path__ / "scenarios").iterdir()
if path.is_file() and path.name.endswith(".json") and path.name not in EXCLUDE
)
)
ALL_SCENARIO_IDS = tuple(path.name.removesuffix(".json") for path in ALL_SCENARIOS)


@pytest.mark.parametrize(
"target",
ALL_SCENARIOS,
ids=ALL_SCENARIO_IDS,
)
@pytest.mark.usefixtures("tmpcwd")
def test_build_all_scenarios(snapshot, target):
assert (
snapshot_command(["build", str(target)], snapshot_filesystem=True) == snapshot
)


@pytest.mark.parametrize("target", ALL_SCENARIOS, ids=ALL_SCENARIO_IDS)
@pytest.mark.usefixtures("tmpcwd")
def test_view_all_scenarios(snapshot, target):
assert snapshot_command(["view", str(target)], snapshot_filesystem=True) == snapshot

0 comments on commit 0bf3e64

Please sign in to comment.