universal-wasm-loader 2.0.0__tar.gz → 2.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/PKG-INFO +1 -1
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/pyproject.toml +1 -1
- universal_wasm_loader-2.0.2/tests/test_github_release.py +164 -0
- universal_wasm_loader-2.0.2/tests/test_pypi_files.py +115 -0
- universal_wasm_loader-2.0.2/tests/test_release_script.py +58 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/.gitignore +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/LICENSE +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/README.md +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/src/universal_wasm_loader/__init__.py +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/src/universal_wasm_loader/_abi.py +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/src/universal_wasm_loader/_loader.py +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/src/universal_wasm_loader/_wit_parser.py +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/__init__.py +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/conftest.py +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/booleans_50.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/booleans_50.wit +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/fixtures.json +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/imports_50.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/imports_50.wit +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/init_check.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/init_check.wat +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/math_50.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/math_50.wit +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/strings_50.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/strings_50.wit +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/wasi_io_50.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/fixtures/wasi_io_50.wit +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/py_fixtures/versioned.wat +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/test_fixtures.py +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/test_loader.py +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/test_refusals.py +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/tests/test_wit_parser.py +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/LICENSE +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/README.md +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/booleans_50.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/booleans_50.wit +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/fixtures.json +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/imports_50.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/imports_50.wit +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/init_check.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/init_check.wat +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/loader.test.js +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/math_50.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/math_50.wit +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/strings_50.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/strings_50.wit +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/wasi_io_50.wasm +0 -0
- {universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/tests/wasi_io_50.wit +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: universal-wasm-loader
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Universal WASM loader for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/synapticforge1/universalWasmLoader-py
|
|
6
6
|
Project-URL: Repository, https://github.com/synapticforge1/universalWasmLoader-py
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""scripts/github_release.py against an in-process mock of GitHub's REST API (no network)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import importlib.util
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
from collections.abc import Iterator
|
|
9
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from types import ModuleType
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
SCRIPT = Path(__file__).parent.parent / "scripts" / "github_release.py"
|
|
17
|
+
REPO, TAG, SHA, OTHER = "o/r", "v2.0.2", "a" * 40, "b" * 40
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _load() -> ModuleType:
|
|
21
|
+
spec = importlib.util.spec_from_file_location("github_release", SCRIPT)
|
|
22
|
+
assert spec is not None and spec.loader is not None
|
|
23
|
+
mod = importlib.util.module_from_spec(spec)
|
|
24
|
+
sys.modules["github_release"] = mod
|
|
25
|
+
spec.loader.exec_module(mod)
|
|
26
|
+
return mod
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
gr = _load()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Mock:
|
|
33
|
+
"""GitHub state + a log of (method, path, auth, body)."""
|
|
34
|
+
|
|
35
|
+
def __init__(self) -> None:
|
|
36
|
+
self.tags: dict[str, dict[str, str]] = {} # tag -> ref object
|
|
37
|
+
self.tag_objects: dict[str, dict[str, str]] = {} # annotated tag sha -> object
|
|
38
|
+
self.releases: dict[str, dict[str, Any]] = {}
|
|
39
|
+
self.log: list[tuple[str, str, str | None, Any]] = []
|
|
40
|
+
self.post_status = 201
|
|
41
|
+
|
|
42
|
+
def writes(self) -> list[tuple[str, str, str | None, Any]]:
|
|
43
|
+
return [e for e in self.log if e[0] != "GET"]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.fixture
|
|
47
|
+
def mock() -> Iterator[tuple[Mock, str]]:
|
|
48
|
+
state = Mock()
|
|
49
|
+
|
|
50
|
+
class Handler(BaseHTTPRequestHandler):
|
|
51
|
+
def log_message(self, *_: Any) -> None:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
def _send(self, code: int, doc: Any = None) -> None:
|
|
55
|
+
raw = b"" if doc is None else json.dumps(doc).encode()
|
|
56
|
+
self.send_response(code)
|
|
57
|
+
self.send_header("Content-Type", "application/json")
|
|
58
|
+
self.send_header("Content-Length", str(len(raw)))
|
|
59
|
+
self.end_headers()
|
|
60
|
+
self.wfile.write(raw)
|
|
61
|
+
|
|
62
|
+
def do_GET(self) -> None: # noqa: N802
|
|
63
|
+
state.log.append(("GET", self.path, self.headers.get("Authorization"), None))
|
|
64
|
+
p = self.path
|
|
65
|
+
if p.startswith(f"/repos/{REPO}/git/ref/tags/"):
|
|
66
|
+
obj = state.tags.get(p.rsplit("/", 1)[1])
|
|
67
|
+
return self._send(200, {"object": obj}) if obj else self._send(404)
|
|
68
|
+
if p.startswith(f"/repos/{REPO}/git/tags/"):
|
|
69
|
+
obj = state.tag_objects.get(p.rsplit("/", 1)[1])
|
|
70
|
+
return self._send(200, {"object": obj}) if obj else self._send(404)
|
|
71
|
+
if p.startswith(f"/repos/{REPO}/releases/tags/"):
|
|
72
|
+
rel = state.releases.get(p.rsplit("/", 1)[1])
|
|
73
|
+
return self._send(200, rel) if rel else self._send(404)
|
|
74
|
+
self._send(404)
|
|
75
|
+
|
|
76
|
+
def do_POST(self) -> None: # noqa: N802
|
|
77
|
+
body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
|
|
78
|
+
state.log.append(("POST", self.path, self.headers.get("Authorization"), body))
|
|
79
|
+
if self.path != f"/repos/{REPO}/releases" or state.post_status != 201:
|
|
80
|
+
return self._send(state.post_status, {"message": "nope"})
|
|
81
|
+
rel = {"id": 7, "tag_name": body["tag_name"], "draft": False,
|
|
82
|
+
"html_url": f"https://example.invalid/{body['tag_name']}"}
|
|
83
|
+
state.releases[body["tag_name"]] = rel
|
|
84
|
+
self._send(201, rel)
|
|
85
|
+
|
|
86
|
+
server = HTTPServer(("127.0.0.1", 0), Handler)
|
|
87
|
+
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
88
|
+
thread.start()
|
|
89
|
+
try:
|
|
90
|
+
yield state, f"http://127.0.0.1:{server.server_port}"
|
|
91
|
+
finally:
|
|
92
|
+
server.shutdown()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _run(base: str, token: str | None = "t0k", dry: bool = False, tag: str = TAG) -> int:
|
|
96
|
+
return int(gr.run(REPO, tag, SHA, dry, gr.Api(base, token)))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_creates_the_release_then_reads_it_back(mock: tuple[Mock, str]) -> None:
|
|
100
|
+
state, base = mock
|
|
101
|
+
state.tags[TAG] = {"type": "commit", "sha": SHA}
|
|
102
|
+
assert _run(base) == 0
|
|
103
|
+
(method, path, auth, body), = state.writes()
|
|
104
|
+
assert (method, path, auth) == ("POST", f"/repos/{REPO}/releases", "Bearer t0k")
|
|
105
|
+
assert body["tag_name"] == TAG and body["generate_release_notes"] is True
|
|
106
|
+
assert "target_commitish" not in body # the tag already exists; nothing to target
|
|
107
|
+
assert "pypi.org/project/universal-wasm-loader/2.0.2/" in body["body"]
|
|
108
|
+
assert state.log[-2][1].endswith(f"/releases/tags/{TAG}") # read back after the POST
|
|
109
|
+
assert state.log[-1][1].endswith(f"/git/ref/tags/{TAG}") # tag re-checked last
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_rerun_is_safe_existing_release_means_done(mock: tuple[Mock, str]) -> None:
|
|
113
|
+
state, base = mock
|
|
114
|
+
state.tags[TAG] = {"type": "commit", "sha": SHA}
|
|
115
|
+
state.releases[TAG] = {"tag_name": TAG, "draft": False, "html_url": "x"}
|
|
116
|
+
assert _run(base) == 0
|
|
117
|
+
assert state.writes() == []
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_annotated_tag_is_peeled(mock: tuple[Mock, str]) -> None:
|
|
121
|
+
state, base = mock
|
|
122
|
+
state.tags[TAG] = {"type": "tag", "sha": "c" * 40}
|
|
123
|
+
state.tag_objects["c" * 40] = {"type": "commit", "sha": SHA}
|
|
124
|
+
assert _run(base) == 0
|
|
125
|
+
assert len(state.writes()) == 1
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@pytest.mark.parametrize("case", ["missing tag", "moved tag", "annotated tag moved"])
|
|
129
|
+
def test_refuses_with_no_write(mock: tuple[Mock, str], case: str) -> None:
|
|
130
|
+
state, base = mock
|
|
131
|
+
if case == "moved tag":
|
|
132
|
+
state.tags[TAG] = {"type": "commit", "sha": OTHER}
|
|
133
|
+
elif case == "annotated tag moved":
|
|
134
|
+
state.tags[TAG] = {"type": "tag", "sha": "c" * 40}
|
|
135
|
+
state.tag_objects["c" * 40] = {"type": "commit", "sha": OTHER}
|
|
136
|
+
assert _run(base) == 1
|
|
137
|
+
assert state.writes() == []
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_dry_run_and_no_token_send_no_write(mock: tuple[Mock, str]) -> None:
|
|
141
|
+
state, base = mock
|
|
142
|
+
state.tags[TAG] = {"type": "commit", "sha": SHA}
|
|
143
|
+
assert _run(base, dry=True) == 0
|
|
144
|
+
assert _run(base, token=None) == 1
|
|
145
|
+
assert state.writes() == []
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_a_failed_post_is_a_failure(
|
|
149
|
+
mock: tuple[Mock, str], monkeypatch: pytest.MonkeyPatch
|
|
150
|
+
) -> None:
|
|
151
|
+
state, base = mock
|
|
152
|
+
state.tags[TAG] = {"type": "commit", "sha": SHA}
|
|
153
|
+
state.post_status = 422
|
|
154
|
+
with pytest.raises(gr.ApiError, match="HTTP 422"):
|
|
155
|
+
_run(base)
|
|
156
|
+
monkeypatch.setenv("GITHUB_API_URL", base) # main() must hit the mock, never GitHub
|
|
157
|
+
monkeypatch.setenv("GITHUB_TOKEN", "t0k")
|
|
158
|
+
assert gr.main(["--repo", REPO, "--tag", TAG, "--sha", SHA]) == 1 # ApiError -> exit 1
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_malformed_tag_sends_no_request(mock: tuple[Mock, str]) -> None:
|
|
162
|
+
state, base = mock
|
|
163
|
+
assert _run(base, tag="main") == 1
|
|
164
|
+
assert state.log == []
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""scripts/pypi_files.py: which dist files PyPI still needs (no network: urlopen is stubbed)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import importlib.util
|
|
5
|
+
import io
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
import urllib.error
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from types import ModuleType
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
SCRIPT = Path(__file__).parent.parent / "scripts" / "pypi_files.py"
|
|
16
|
+
PKG, VER = "universal-wasm-loader", "2.0.0"
|
|
17
|
+
WHL, SDIST = "universal_wasm_loader-2.0.0-py3-none-any.whl", "universal_wasm_loader-2.0.0.tar.gz"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _load() -> ModuleType:
|
|
21
|
+
spec = importlib.util.spec_from_file_location("pypi_files", SCRIPT)
|
|
22
|
+
assert spec is not None and spec.loader is not None
|
|
23
|
+
mod = importlib.util.module_from_spec(spec)
|
|
24
|
+
sys.modules["pypi_files"] = mod
|
|
25
|
+
spec.loader.exec_module(mod)
|
|
26
|
+
return mod
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
pf = _load()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_nothing_on_pypi_uploads_everything() -> None:
|
|
33
|
+
upload, _ = pf.plan({WHL: "a", SDIST: "b"}, None)
|
|
34
|
+
assert sorted(upload) == sorted([SDIST, WHL])
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_complete_on_pypi_uploads_nothing() -> None:
|
|
38
|
+
upload, notes = pf.plan({WHL: "a", SDIST: "b"}, {WHL: "a", SDIST: "b"})
|
|
39
|
+
assert upload == []
|
|
40
|
+
assert all("same bytes" in n for n in notes)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_half_published_uploads_only_the_missing_file() -> None:
|
|
44
|
+
# The wheel went up, the sdist upload failed: a re-run sends the sdist only.
|
|
45
|
+
upload, _ = pf.plan({WHL: "a", SDIST: "b"}, {WHL: "a"})
|
|
46
|
+
assert upload == [SDIST]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_rebuilt_file_with_other_bytes_is_skipped_not_reuploaded() -> None:
|
|
50
|
+
upload, notes = pf.plan({WHL: "rebuilt", SDIST: "b"}, {WHL: "first"})
|
|
51
|
+
assert upload == [SDIST]
|
|
52
|
+
assert any("DIFFERENT bytes" in n and WHL in n for n in notes)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_local_files_refuses_another_version(tmp_path: Path) -> None:
|
|
56
|
+
(tmp_path / WHL).write_bytes(b"w")
|
|
57
|
+
(tmp_path / "universal_wasm_loader-1.1.1.tar.gz").write_bytes(b"s")
|
|
58
|
+
with pytest.raises(ValueError, match="not a dist of universal-wasm-loader 2.0.0"):
|
|
59
|
+
pf.local_files(tmp_path, PKG, VER)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_local_files_refuses_an_empty_dist(tmp_path: Path) -> None:
|
|
63
|
+
with pytest.raises(ValueError, match="no dist files"):
|
|
64
|
+
pf.local_files(tmp_path, PKG, VER)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_local_files_hashes(tmp_path: Path) -> None:
|
|
68
|
+
(tmp_path / WHL).write_bytes(b"w")
|
|
69
|
+
(tmp_path / SDIST).write_bytes(b"s")
|
|
70
|
+
got = pf.local_files(tmp_path, PKG, VER)
|
|
71
|
+
assert set(got) == {WHL, SDIST}
|
|
72
|
+
assert len(got[WHL]) == 64
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _http_error(code: int) -> urllib.error.HTTPError:
|
|
76
|
+
return urllib.error.HTTPError("https://pypi.org/x", code, "x", {}, None) # type: ignore[arg-type]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_remote_404_means_no_files(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
80
|
+
def fake(*_: Any, **__: Any) -> Any:
|
|
81
|
+
raise _http_error(404)
|
|
82
|
+
|
|
83
|
+
monkeypatch.setattr(pf.urllib.request, "urlopen", fake)
|
|
84
|
+
assert pf.remote_files(PKG, VER) is None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.mark.parametrize("code", [403, 500, 503])
|
|
88
|
+
def test_remote_other_status_is_an_error_never_not_published(
|
|
89
|
+
monkeypatch: pytest.MonkeyPatch, code: int
|
|
90
|
+
) -> None:
|
|
91
|
+
def fake(*_: Any, **__: Any) -> Any:
|
|
92
|
+
raise _http_error(code)
|
|
93
|
+
|
|
94
|
+
monkeypatch.setattr(pf.urllib.request, "urlopen", fake)
|
|
95
|
+
with pytest.raises(pf.PyPIError, match=f"HTTP {code}"):
|
|
96
|
+
pf.remote_files(PKG, VER)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_remote_unreachable_is_an_error(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
100
|
+
def fake(*_: Any, **__: Any) -> Any:
|
|
101
|
+
raise urllib.error.URLError("no route")
|
|
102
|
+
|
|
103
|
+
monkeypatch.setattr(pf.urllib.request, "urlopen", fake)
|
|
104
|
+
with pytest.raises(pf.PyPIError, match="could not reach PyPI"):
|
|
105
|
+
pf.remote_files(PKG, VER)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_remote_200_lists_files(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
109
|
+
body = {"urls": [{"filename": WHL, "digests": {"sha256": "a"}}]}
|
|
110
|
+
|
|
111
|
+
def fake(*_: Any, **__: Any) -> Any:
|
|
112
|
+
return io.BytesIO(json.dumps(body).encode())
|
|
113
|
+
|
|
114
|
+
monkeypatch.setattr(pf.urllib.request, "urlopen", fake)
|
|
115
|
+
assert pf.remote_files(PKG, VER) == {WHL: "a"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""scripts/release.py helpers: the ls-remote read-back and the Release-page owner item (H30)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import importlib.util
|
|
5
|
+
import sys
|
|
6
|
+
import urllib.parse
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from types import ModuleType
|
|
9
|
+
|
|
10
|
+
SCRIPTS = Path(__file__).parent.parent / "scripts"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _load() -> ModuleType:
|
|
14
|
+
if str(SCRIPTS) not in sys.path:
|
|
15
|
+
sys.path.insert(0, str(SCRIPTS)) # release.py imports _common from its own folder
|
|
16
|
+
spec = importlib.util.spec_from_file_location("release_script", SCRIPTS / "release.py")
|
|
17
|
+
assert spec is not None and spec.loader is not None
|
|
18
|
+
mod = importlib.util.module_from_spec(spec)
|
|
19
|
+
spec.loader.exec_module(mod)
|
|
20
|
+
return mod
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
rel = _load()
|
|
24
|
+
|
|
25
|
+
LS_REMOTE = (
|
|
26
|
+
"21e680a433345a63e653fd920ad113332c8bf1d9\tHEAD\n"
|
|
27
|
+
"21e680a433345a63e653fd920ad113332c8bf1d9\trefs/heads/main\n"
|
|
28
|
+
"c41c8b9f0b112e41840f3c6841f80cdb9657d841\trefs/tags/v2.0.0\n"
|
|
29
|
+
"21e680a433345a63e653fd920ad113332c8bf1d9\trefs/tags/v2.0.0^{}\n"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_remote_refs_keeps_peeled_tags_apart() -> None:
|
|
34
|
+
refs = rel.remote_refs(LS_REMOTE)
|
|
35
|
+
assert refs["refs/heads/main"].startswith("21e680a")
|
|
36
|
+
assert refs["refs/tags/v2.0.0"].startswith("c41c8b9") # the annotated tag object
|
|
37
|
+
assert refs["refs/tags/v2.0.0^{}"].startswith("21e680a") # the commit it peels to
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_release_page_url_for_each_github_remote_form() -> None:
|
|
41
|
+
for remote in (
|
|
42
|
+
"https://github.com/synapticforge1/universalWasmLoader-py.git",
|
|
43
|
+
"https://github.com/synapticforge1/universalWasmLoader-py",
|
|
44
|
+
"git@github.com:synapticforge1/universalWasmLoader-py.git",
|
|
45
|
+
"ssh://git@github.com/synapticforge1/universalWasmLoader-py.git",
|
|
46
|
+
):
|
|
47
|
+
url = rel.release_page_url(remote, "v2.0.1", "notes & more?")
|
|
48
|
+
assert url is not None, remote
|
|
49
|
+
base, _, query = url.partition("?")
|
|
50
|
+
assert base == "https://github.com/synapticforge1/universalWasmLoader-py/releases/new"
|
|
51
|
+
assert urllib.parse.parse_qs(query) == {
|
|
52
|
+
"tag": ["v2.0.1"], "title": ["v2.0.1"], "body": ["notes & more?"]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_release_page_url_none_for_other_hosts() -> None:
|
|
57
|
+
assert rel.release_page_url("/tmp/scratch-bare.git", "v1", "x") is None
|
|
58
|
+
assert rel.release_page_url("https://gitlab.com/a/b.git", "v1", "x") is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/src/universal_wasm_loader/__init__.py
RENAMED
|
File without changes
|
{universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/src/universal_wasm_loader/_abi.py
RENAMED
|
File without changes
|
{universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/src/universal_wasm_loader/_loader.py
RENAMED
|
File without changes
|
{universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/src/universal_wasm_loader/_wit_parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{universal_wasm_loader-2.0.0 → universal_wasm_loader-2.0.2}/upstream/universalwasmloader-js/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|