1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
import contextlib
import datetime
import functools
import pathlib
import platform
import re
import secrets
import subprocess
import sys
from types import SimpleNamespace
import pytest
import requests
from twine import __main__ as dunder_main
from twine import cli
pytestmark = [pytest.mark.enable_socket]
skip_if_windows = pytest.mark.skipif(
platform.system() == "Windows",
reason="pytest-services fixtures don't support Windows",
)
run = functools.partial(subprocess.run, check=True)
@pytest.fixture(scope="session")
def sampleproject_dist(tmp_path_factory: pytest.TempPathFactory):
checkout = tmp_path_factory.mktemp("sampleproject", numbered=False)
tag = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")
run(["git", "clone", "https://github.com/pypa/sampleproject", str(checkout)])
pyproject = checkout / "pyproject.toml"
pyproject.write_text(
pyproject.read_text()
.replace(
'name = "sampleproject"',
'name = "twine-sampleproject"',
)
.replace(
'version = "3.0.0"',
f'version = "3.0.0post{tag}"',
)
)
run([sys.executable, "-m", "build", "--sdist"], cwd=checkout)
[dist, *_] = (checkout / "dist").glob("*")
# NOTE: newer versions of setuptools (invoked via build) adhere to PEP 625,
# causing the dist name to be `twine_sampleproject` instead of
# `twine-sampleproject`. Both are allowed here for now, but the hyphenated
# version can be removed eventually.
# See: https://github.com/pypa/setuptools/issues/3593
assert dist.name in (
f"twine-sampleproject-3.0.0.post{tag}.tar.gz",
f"twine_sampleproject-3.0.0.post{tag}.tar.gz",
)
return dist
sampleproject_token = (
"pypi-AgENdGVzdC5weXBpLm9yZwIkNDgzYTFhMjEtMzEwYi00NT"
"kzLTkwMzYtYzc1Zjg4NmFiMjllAAJEeyJwZXJtaXNzaW9ucyI6IH"
"sicHJvamVjdHMiOiBbInR3aW5lLXNhbXBsZXByb2plY3QiXX0sIC"
"J2ZXJzaW9uIjogMX0AAAYg2kBZ1tN8lj8dlmL3ScoVvr_pvQE0t"
"6PKqigoYJKvw3M"
)
@pytest.mark.xfail(reason="service is unreliable (#684)")
def test_pypi_upload(sampleproject_dist):
command = [
"upload",
"--repository-url",
"https://test.pypi.org/legacy/",
"--username",
"__token__",
"--password",
sampleproject_token,
str(sampleproject_dist),
]
cli.dispatch(command)
@pytest.mark.xfail(reason="service is unreliable (#684)")
def test_pypi_error(sampleproject_dist, monkeypatch, capsys):
command = [
"twine",
"upload",
"--repository-url",
"https://test.pypi.org/legacy/",
"--username",
"foo",
"--password",
"bar",
str(sampleproject_dist),
]
monkeypatch.setattr(sys, "argv", command)
message = (
r"HTTPError: 403 Forbidden from https://test\.pypi\.org/legacy/"
+ r".+authentication information"
)
error = dunder_main.main()
assert error
captured = capsys.readouterr()
assert re.search(message, captured.out, re.DOTALL)
@pytest.fixture(
params=[
"twine-1.5.0.tar.gz",
"twine-1.5.0-py2.py3-none-any.whl",
"twine-1.6.5.tar.gz",
"twine-1.6.5-py2.py3-none-any.whl",
]
)
def uploadable_dist(request):
return pathlib.Path(__file__).parent / "fixtures" / request.param
@pytest.fixture(scope="session")
def devpi_server(request, port_getter, watcher_getter, tmp_path_factory):
server_dir = tmp_path_factory.mktemp("devpi")
username = "foober"
password = secrets.token_urlsafe()
port = port_getter()
url = f"http://localhost:{port}/"
repo = f"{url}/{username}/dev/"
run(["devpi-init", "--serverdir", server_dir, "--root-passwd", password])
def ready():
with contextlib.suppress(Exception):
return requests.get(url)
watcher_getter(
name="devpi-server",
arguments=["--port", str(port), "--serverdir", server_dir],
checker=ready,
# Needed for the correct execution order of finalizers
request=request,
)
def devpi_run(cmd):
return run(["devpi", "--clientdir", server_dir / "client", *cmd])
devpi_run(["use", url + "root/pypi/"])
devpi_run(["user", "--create", username, f"password={password}"])
devpi_run(["login", username, "--password", password])
devpi_run(["index", "-c", "dev"])
return SimpleNamespace(url=repo, username=username, password=password)
@skip_if_windows
def test_devpi_upload(devpi_server, uploadable_dist):
command = [
"upload",
"--repository-url",
devpi_server.url,
"--username",
devpi_server.username,
"--password",
devpi_server.password,
str(uploadable_dist),
]
cli.dispatch(command)
@pytest.fixture(scope="session")
def pypiserver_instance(request, port_getter, watcher_getter, tmp_path_factory):
port = port_getter()
url = f"http://localhost:{port}/"
def ready():
with contextlib.suppress(Exception):
return requests.get(url)
watcher_getter(
name="pypi-server",
arguments=[
"--port",
str(port),
# allow anonymous uploads
"-P",
".",
"-a",
".",
tmp_path_factory.mktemp("packages"),
],
checker=ready,
# Needed for the correct execution order of finalizers
request=request,
)
return SimpleNamespace(url=url)
@skip_if_windows
def test_pypiserver_upload(pypiserver_instance, uploadable_dist):
command = [
"upload",
"--repository-url",
pypiserver_instance.url,
"--username",
"any",
"--password",
"any",
str(uploadable_dist),
]
cli.dispatch(command)
|