Skip to content

Commit 74455b5

Browse files
committed
test better
Signed-off-by: William Woodruff <william@astral.sh>
1 parent f155e9b commit 74455b5

4 files changed

Lines changed: 104 additions & 63 deletions

File tree

.github/workflows/test.yml

Lines changed: 2 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,7 @@ concurrency:
1414
permissions: {}
1515

1616
jobs:
17-
id-token:
18-
name: "Obtain cursed OIDC token"
19-
runs-on: ubuntu-latest
20-
outputs:
21-
id-token: ${{ steps.beacon.outputs.id-token }}
22-
23-
steps:
24-
- name: Obtain cursed OIDC token
25-
uses: sigstore-conformance/extremely-dangerous-public-oidc-beacon@4a8befcc16064dac9e97f210948d226e5c869bdc # v1.0.0
26-
27-
- name: Set output
28-
id: beacon
29-
# Note: base64 encode the token to avoid GitHub's ridiculous output value
30-
# filtering behavior.
31-
run: |
32-
token=$(base64 < ./oidc-token.txt)
33-
echo "id-token=${token}" >> ${GITHUB_OUTPUT}
34-
35-
attest-basic:
36-
name: "Selftest: basic attestation generation"
37-
needs: [id-token]
17+
test:
3818
runs-on: ubuntu-latest
3919

4020
permissions:
@@ -45,37 +25,4 @@ jobs:
4525
with:
4626
persist-credentials: false
4727

48-
- name: Get pypa/sampleproject
49-
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
50-
with:
51-
repository: pypa/sampleproject
52-
path: sampleproject
53-
persist-credentials: false
54-
55-
- name: setup uv
56-
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
57-
58-
- name: Build sampleproject distributions
59-
working-directory: sampleproject
60-
run: uv build
61-
62-
- name: Run attest-action
63-
uses: ./
64-
with:
65-
paths: sampleproject/dist/
66-
id-token: ${{ needs.id-token.outputs.id-token }}
67-
68-
all-tests-pass:
69-
name: "Ensure all selftests pass"
70-
if: always()
71-
72-
needs:
73-
- attest-basic
74-
75-
runs-on: ubuntu-latest
76-
77-
steps:
78-
- name: Ensure all selftests passed
79-
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
80-
with:
81-
jobs: ${{ toJSON(needs) }}
28+
- run: make test

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ lint:
1111
fix:
1212
uvx ruff format
1313
uvx ruff check --fix
14+
15+
.PHONY: test
16+
test:
17+
uvx --with=requests --with-requirements=action.py pytest test.py

action.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import logging
1111
import os
1212
import shlex
13+
from glob import glob
1314
from pathlib import Path
1415

1516
from pypi_attestations import Attestation, Distribution
@@ -28,7 +29,7 @@ def _get_input(name: str) -> str | None:
2829
return os.getenv(env)
2930

3031

31-
def _get_path_patterns() -> list[str]:
32+
def _get_path_patterns() -> set[str]:
3233
"""
3334
Retrieve and normalize the 'paths' input.
3435
@@ -45,28 +46,29 @@ def _get_path_patterns() -> list[str]:
4546
raise RuntimeError("No paths provided in 'paths' input")
4647

4748
# Normalize `foo/` to `foo/*`
48-
paths = [str(Path(p) / "*") if Path(p).is_dir() else p for p in paths]
49+
paths = [str(Path(p) / "*") if p.endswith(("/", "\\")) else p for p in paths]
4950

50-
return paths
51+
return set(paths)
5152

5253

53-
def _unroll_files(patterns: list[str]) -> list[Path]:
54+
def _unroll_files(patterns: set[str]) -> set[Path]:
5455
"""
5556
Given one or more path patterns (which may include glob patterns), unroll and
5657
return all matching files.
5758
"""
5859

59-
files = []
60+
files = set()
6061

6162
for pattern in patterns:
62-
for path in Path().glob(pattern):
63+
for path in glob(pattern):
64+
path = Path(path)
6365
if path.is_file():
64-
files.append(path)
66+
files.add(path)
6567

6668
return files
6769

6870

69-
def _collect_dists(patterns: list[str]) -> list[tuple[Path, Distribution]]:
71+
def _collect_dists(patterns: set[str]) -> list[tuple[Path, Distribution]]:
7072
"""
7173
Given one or more path patterns (which may include glob patterns), collect and
7274
return all Python distributions found at those paths.

test.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import subprocess
2+
import uuid
3+
from pathlib import Path
4+
5+
import pytest
6+
import requests
7+
from sigstore import oidc
8+
9+
import action
10+
11+
12+
@pytest.fixture(scope="session")
13+
def id_token() -> oidc.IdentityToken:
14+
resp = requests.get(
15+
"https://raw.githubusercontent.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/refs/heads/current-token/oidc-token.txt",
16+
params={"cachebuster": uuid.uuid4().hex},
17+
)
18+
resp.raise_for_status()
19+
id_token = resp.text.strip()
20+
return oidc.IdentityToken(id_token)
21+
22+
23+
@pytest.fixture
24+
def sampleproject(tmp_path: Path) -> Path:
25+
"""
26+
Create a sample Python project with a distribution file.
27+
"""
28+
29+
project_dir = tmp_path / "sampleproject"
30+
project_dir.mkdir()
31+
32+
pyproject = project_dir / "pyproject.toml"
33+
pyproject.write_text("""
34+
name = "astral-sh-attest-action-test-sampleproject"
35+
version = "0.1.0"
36+
description = "Who's wants to know?"
37+
requires-python = ">=3.10"
38+
""")
39+
40+
hello_py = project_dir / "hello.py"
41+
hello_py.write_text("""
42+
def main():
43+
print("Hello, world!")
44+
""")
45+
46+
return project_dir
47+
48+
49+
def test_get_input(monkeypatch: pytest.MonkeyPatch) -> None:
50+
monkeypatch.setenv("ATTEST_ACTION_INPUT_FOO", "expected")
51+
52+
assert action._get_input("foo") == "expected"
53+
54+
55+
def test_get_path_patterns(monkeypatch: pytest.MonkeyPatch) -> None:
56+
monkeypatch.setenv("ATTEST_ACTION_INPUT_PATHS", "dist/* another/** third/")
57+
58+
patterns = action._get_path_patterns()
59+
assert patterns == {"dist/*", "another/**", "third/*"}
60+
61+
# Deduplicates patterns / files.
62+
monkeypatch.setenv("ATTEST_ACTION_INPUT_PATHS", "dist/* dist/* another/")
63+
patterns = action._get_path_patterns()
64+
assert patterns == {"dist/*", "another/*"}
65+
66+
monkeypatch.setenv("ATTEST_ACTION_INPUT_PATHS", "a a b b c")
67+
patterns = action._get_path_patterns()
68+
assert patterns == {"a", "b", "c"}
69+
70+
71+
def test_attest(sampleproject: Path, id_token: oidc.IdentityToken) -> None:
72+
subprocess.run(["uv", "build"], cwd=sampleproject, check=True)
73+
dist_dir = sampleproject / "dist"
74+
75+
patterns = {str(dist_dir / "*")}
76+
77+
dists = action._collect_dists(patterns)
78+
assert len(dists) == 2 # sdist and wheel
79+
80+
action._attest(
81+
dists,
82+
id_token,
83+
overwrite=False,
84+
)
85+
86+
for dist_path, _ in dists:
87+
attestation_path = dist_path.with_name(f"{dist_path.name}.publish.attestation")
88+
assert attestation_path.exists()

0 commit comments

Comments
 (0)