Skip to content

Commit

Permalink
Improve publish test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Dec 14, 2023
1 parent 1ec16c9 commit d990b79
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 21 deletions.
8 changes: 6 additions & 2 deletions src/packse/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,19 @@ def publish_package_distribution(target: Path, dry_run: bool) -> None:

start_time = time.time()
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
import os

output = subprocess.check_output(
command, stderr=subprocess.STDOUT, env=os.environ
)
except subprocess.CalledProcessError as exc:
output = exc.output.decode()
if "File already exists" in output:
raise PublishAlreadyExists(target.name)
if "HTTPError: 429 Too Many Requests" in output:
raise PublishRateLimit(target.name)
raise PublishToolError(
f"Publishing {target} with twine failed",
f"Publishing {target.name} with twine failed",
output,
)
else:
Expand Down
86 changes: 80 additions & 6 deletions tests/__snapshots__/test_publish.ambr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# serializer version: 1
# name: test_publish_example
# name: test_publish_example_dry_run
dict({
'exit_code': 0,
'stderr': '''
Expand All @@ -13,12 +13,86 @@

''',
'stdout': '''
Would execute: twine upload -r testpypi [PWD]/dist/example-0611cb74/example_0611cb74-0.0.0.tar.gz
Would execute: twine upload -r testpypi [PWD]/dist/example-0611cb74/example_0611cb74_a-1.0.0-py3-none-any.whl
Would execute: twine upload -r testpypi [PWD]/dist/example-0611cb74/example_0611cb74_a-1.0.0.tar.gz
Would execute: twine upload -r testpypi [PWD]/dist/example-0611cb74/example_0611cb74_b-1.0.0-py3-none-any.whl
Would execute: twine upload -r testpypi [PWD]/dist/example-0611cb74/example_0611cb74_b-1.0.0.tar.gz
Would execute: twine upload -r testpypi /private/var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/tmp8bvy1hji/dist/example-0611cb74/example_0611cb74-0.0.0.tar.gz
Would execute: twine upload -r testpypi /private/var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/tmp8bvy1hji/dist/example-0611cb74/example_0611cb74_a-1.0.0-py3-none-any.whl
Would execute: twine upload -r testpypi /private/var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/tmp8bvy1hji/dist/example-0611cb74/example_0611cb74_a-1.0.0.tar.gz
Would execute: twine upload -r testpypi /private/var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/tmp8bvy1hji/dist/example-0611cb74/example_0611cb74_b-1.0.0-py3-none-any.whl
Would execute: twine upload -r testpypi /private/var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/tmp8bvy1hji/dist/example-0611cb74/example_0611cb74_b-1.0.0.tar.gz

''',
})
# ---
# name: test_publish_example_twine_fails_with_already_exists
dict({
'exit_code': 1,
'stderr': '''
INFO:packse.publish:Publishing 1 target...
INFO:packse.publish:Publishing 'example-0611cb74'...
Publish for 'example_0611cb74-0.0.0.tar.gz' already exists

''',
'stdout': '',
})
# ---
# name: test_publish_example_twine_fails_with_rate_limit
dict({
'exit_code': 1,
'stderr': '''
INFO:packse.publish:Publishing 1 target...
INFO:packse.publish:Publishing 'example-0611cb74'...
Publish of 'example_0611cb74-0.0.0.tar.gz' failed due to rate limits

''',
'stdout': '',
})
# ---
# name: test_publish_example_twine_fails_with_unknown_error
dict({
'exit_code': 1,
'stderr': '''
INFO:packse.publish:Publishing 1 target...
INFO:packse.publish:Publishing 'example-0611cb74'...
Publishing example_0611cb74-0.0.0.tar.gz with twine failed:
<twine error message>


''',
'stdout': '',
})
# ---
# name: test_publish_example_twine_succeeds
dict({
'exit_code': 0,
'stderr': '''
INFO:packse.publish:Publishing 1 target...
INFO:packse.publish:Publishing 'example-0611cb74'...
DEBUG:packse.publish:Published example_0611cb74-0.0.0.tar.gz in [TIME]:

<twine happy message>

INFO:packse.publish:Published 'example_0611cb74-0.0.0.tar.gz'
DEBUG:packse.publish:Published example_0611cb74_a-1.0.0-py3-none-any.whl in [TIME]:

<twine happy message>

INFO:packse.publish:Published 'example_0611cb74_a-1.0.0-py3-none-any.whl'
DEBUG:packse.publish:Published example_0611cb74_a-1.0.0.tar.gz in [TIME]:

<twine happy message>

INFO:packse.publish:Published 'example_0611cb74_a-1.0.0.tar.gz'
DEBUG:packse.publish:Published example_0611cb74_b-1.0.0-py3-none-any.whl in [TIME]:

<twine happy message>

INFO:packse.publish:Published 'example_0611cb74_b-1.0.0-py3-none-any.whl'
DEBUG:packse.publish:Published example_0611cb74_b-1.0.0.tar.gz in [TIME]:

<twine happy message>

INFO:packse.publish:Published 'example_0611cb74_b-1.0.0.tar.gz'

''',
'stdout': '',
})
# ---
4 changes: 4 additions & 0 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def snapshot_command(
snapshot_filesystem: bool = False,
stderr: bool = True,
stdout: bool = True,
extra_filters: list[tuple[str, str]] | None = None,
) -> dict:
# By default, filter out absolute references to the working directory
filters = [
Expand All @@ -34,12 +35,15 @@ def snapshot_command(
"[TIME]",
),
]
if extra_filters:
filters += extra_filters

process = subprocess.run(
["packse"] + command,
cwd=working_directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=os.environ,
)
result = {
"exit_code": process.returncode,
Expand Down
159 changes: 146 additions & 13 deletions tests/test_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,156 @@

from packse import __development_base_path__

from .common import snapshot_command
from .common import snapshot_command, tmpchdir
import tempfile
import shutil
import packse.publish
import pytest
import stat
import re
import subprocess
from typing import Generator
from unittest.mock import MagicMock


def test_publish_example(snapshot, tmpcwd: Path):
@pytest.fixture(scope="module")
def scenario_dist() -> Generator[Path, None, None]:
target = __development_base_path__ / "scenarios" / "example.json"

# Build first
# TODO(zanieb): Since we're doing a dry run consider just constructing some fake files?
subprocess.check_call(
["packse", "build", str(target)],
cwd=tmpcwd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
with tempfile.TemporaryDirectory() as tmpdir:
subprocess.check_call(
["packse", "build", str(target)],
cwd=tmpdir,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

dists = list((Path(tmpdir) / "dist").iterdir())
assert len(dists) == 1
dist = dists[0]

yield dist


class MockBinary:
def __init__(self, path: Path) -> None:
self.path = path
self.callback = None
self._update_bin("")

def _prepare_text(self, text: str = None):
if not text:
return ""
# Escape single quotes
return text.replace("'", "\\'")

def _update_bin(self, content: str):
self.path.write_text("#!/usr/bin/env sh\n\n" + content + "\n")
self.path.chmod(self.path.stat().st_mode | stat.S_IEXEC)

def set_success(self, text: str | None = None):
text = self._prepare_text(text)
self._update_bin(f"echo '{text}'")

def set_error(self, text: str | None = None):
text = self._prepare_text(text)
self._update_bin(f"echo '{text}'; exit 1")


@pytest.fixture
def mock_twine(monkeypatch: pytest.MonkeyPatch) -> Generator[MockBinary, None, None]:
# Create a temp directory to register as a bin
with tempfile.TemporaryDirectory() as tmpdir:
mock = MockBinary(Path(tmpdir) / "twine")
mock.set_success()

# Add to the path
monkeypatch.setenv("PATH", tmpdir, prepend=":")
assert shutil.which("twine").startswith(tmpdir)

yield mock


def test_publish_example_dry_run(snapshot, scenario_dist: Path):
assert snapshot_command(["publish", "--dry-run", scenario_dist]) == snapshot


def test_publish_example_twine_succeeds(
snapshot, scenario_dist: Path, mock_twine: MockBinary
):
mock_twine.set_success("<twine happy message>")

assert (
snapshot_command(
["publish", scenario_dist, "-v"],
extra_filters=[(re.escape(str(scenario_dist)), "[DISTDIR]")],
)
== snapshot
)


def test_publish_example_twine_fails_with_unknown_error(
snapshot, scenario_dist: Path, mock_twine: MockBinary
):
mock_twine.set_error("<twine error message>")

assert (
snapshot_command(
["publish", scenario_dist, "-v"],
extra_filters=[(re.escape(str(scenario_dist)), "[DISTDIR]")],
)
== snapshot
)

dists = list((tmpcwd / "dist").iterdir())
assert len(dists) == 1
dist = dists[0]

assert snapshot_command(["publish", "--dry-run", dist]) == snapshot
def test_publish_example_twine_fails_with_rate_limit(
snapshot, scenario_dist: Path, mock_twine: MockBinary
):
mock_twine.set_error(
"""
Uploading distributions to https://test.pypi.org/legacy/
Uploading
requires_transitive_incompatible_with_root_version_5c1b7dc1_c-1.0.0-py3-none-any
.whl
25l
0% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/4.1 kB • --:-- • ?
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.1/4.1 kB • 00:00 • ?
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.1/4.1 kB • 00:00 • ?
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.1/4.1 kB • 00:00 • ?
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.1/4.1 kB • 00:00 • ?
25hWARNING Error during upload. Retry with the --verbose option for more details.
ERROR HTTPError: 429 Too Many Requests from https://test.pypi.org/legacy/
Too many new projects created
"""
)

assert (
snapshot_command(
["publish", scenario_dist, "-v"],
extra_filters=[(re.escape(str(scenario_dist)), "[DISTDIR]")],
)
== snapshot
)


def test_publish_example_twine_fails_with_already_exists(
snapshot, scenario_dist: Path, mock_twine: MockBinary
):
mock_twine.set_error(
"""
Uploading distributions to https://test.pypi.org/legacy/
Uploading example_9e723676_a-1.0.0.tar.gz
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.1/3.1 kB • 00:00 • ?
WARNING Error during upload. Retry with the --verbose option for more details.
ERROR HTTPError: 400 Bad Request from https://test.pypi.org/legacy/
File already exists. See https://test.pypi.org/help/#file-name-reuse for more information.
"""
)

assert (
snapshot_command(
["publish", scenario_dist, "-v"],
extra_filters=[(re.escape(str(scenario_dist)), "[DISTDIR]")],
)
== snapshot
)

0 comments on commit d990b79

Please sign in to comment.