Files
Artemis-Plugins/tests/test_package_rust3_compat.py
T

216 lines
8.7 KiB
Python

"""Offline packager regressions; Go compilation and SDK admission are separate."""
import importlib.util
import json
import tempfile
import unittest
import zipfile
from pathlib import Path
from unittest.mock import patch
SPEC = importlib.util.spec_from_file_location(
"package_rust3_compat",
Path(__file__).resolve().parents[1] / "scripts/package_rust3_compat.py",
)
packager = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(packager)
class Rust3PackagerTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.root = Path(self.temporary.name)
self.plugin = self.root / "device-test"
self.plugin.mkdir()
self.manifest = {
"schema_version": 2,
"id": "artemis-device-test",
"version": packager.DEVICE_TEST_VERSION,
"artemis_version": packager.HOST_RANGE,
"license": "Apache-2.0",
}
(self.plugin / "plugin.json").write_bytes(packager.encoded(self.manifest))
for name in ("LICENSE", "SOURCE.md", "VERIFICATION.md", "server.py"):
(self.plugin / name).write_bytes((name + "\n").encode())
self.update_market()
def update_market(self):
hashes = {
path.relative_to(self.plugin).as_posix(): packager.sha256(path.read_bytes())
for path in self.plugin.rglob("*")
if path.is_file() and path.name != "MARKET.json"
}
fingerprint = packager.sha256(
json.dumps(
hashes, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode()
)
(self.plugin / "MARKET.json").write_bytes(
packager.encoded(
{
"plugin_id": self.manifest["id"],
"plugin_version": self.manifest["version"],
"content_sha256": fingerprint,
}
)
)
def assemble(self, name, tag=packager.DEFAULT_RELEASE_TAG):
output = self.root / name
catalog = self.root / (name + "-catalog.json")
with (
patch.object(
packager,
"base_runtime",
return_value=({}, {"plugin_version": packager.BASE_VERSION}),
),
patch.object(
packager,
"build_adapter",
return_value=(b"adapter fixture", b"Go license fixture", "go fixture"),
),
patch.object(
packager.subprocess, "check_output", side_effect=["f" * 40, b""]
),
patch("builtins.print"),
):
packager.main(
[
"--cua-base-archive",
str(self.root / "base.zip"),
"--go",
str(self.root / "go.exe"),
"--output",
str(output),
"--catalog-output",
str(catalog),
"--device-test-source",
str(self.plugin),
"--release-tag",
tag,
]
)
return output, catalog
def test_three_archives_catalog_and_bundle_bind_the_same_final_bytes(self):
output, catalog_path = self.assemble("candidate")
catalog = json.loads(catalog_path.read_bytes())
mapping = json.loads((output / "bundle-map.json").read_bytes())
self.assertEqual(len(catalog["plugins"]), 3)
self.assertEqual(len(mapping["packages"]), 3)
self.assertEqual(
mapping["catalog_sha256"], packager.sha256(catalog_path.read_bytes())
)
self.assertEqual(
(output / mapping["catalog"]).read_bytes(), catalog_path.read_bytes()
)
for entry, record in zip(catalog["plugins"], mapping["packages"], strict=True):
archive = output / record["file"]
self.assertEqual(entry["sha256"], packager.sha256(archive.read_bytes()))
self.assertEqual(record["sha256"], entry["sha256"])
self.assertEqual(record["archive_url"], entry["archive_url"])
self.assertIn(
"/" + packager.DEFAULT_RELEASE_TAG + "/", entry["archive_url"]
)
self.assertTrue(entry["archive_url"].startswith(packager.RELEASES + "/"))
with zipfile.ZipFile(archive) as package:
self.assertEqual(
json.loads(package.read("plugin.json")), entry["manifest"]
)
device = mapping["packages"][2]
self.assertEqual(device["version"], "0.5.0+artemis.7")
with zipfile.ZipFile(output / device["file"]) as package:
self.assertEqual(
package.read("server.py"), (self.plugin / "server.py").read_bytes()
)
self.assertEqual(
package.read("MARKET.json"), (self.plugin / "MARKET.json").read_bytes()
)
sums = (output / "SHA256SUMS.txt").read_text("ascii").splitlines()
self.assertEqual(len(sums), 3)
def test_repeated_candidate_is_deterministic_and_custom_tag_is_shared(self):
first, _ = self.assemble("first", "rust3-preview-test")
second, _ = self.assemble("second", "rust3-preview-test")
for name in ("SHA256SUMS.txt", "catalog-v5.json", "bundle-map.json"):
self.assertEqual((first / name).read_bytes(), (second / name).read_bytes())
catalog = json.loads((first / "catalog-v5.json").read_bytes())
self.assertTrue(
all("/rust3-preview-test/" in e["archive_url"] for e in catalog["plugins"])
)
def test_rejects_stale_market_hash_and_mismatched_version(self):
(self.plugin / "server.py").write_bytes(b"changed")
with self.assertRaisesRegex(ValueError, "MARKET identity or content hash"):
packager.device_test_files(self.plugin)
self.manifest["version"] = "0.5.0+artemis.6"
(self.plugin / "plugin.json").write_bytes(packager.encoded(self.manifest))
self.update_market()
with self.assertRaisesRegex(ValueError, "identity or compatibility"):
packager.device_test_files(self.plugin)
def test_rejects_missing_distribution_material_and_runtime_files(self):
(self.plugin / "LICENSE").unlink()
with self.assertRaisesRegex(ValueError, "distribution material"):
packager.device_test_files(self.plugin)
(self.plugin / "LICENSE").write_bytes(b"license")
(self.plugin / ".env").write_bytes(b"private fixture")
with self.assertRaisesRegex(ValueError, "Non-distributable"):
packager.device_test_files(self.plugin)
def test_source_change_during_build_fails_before_any_archive(self):
output = self.root / "changing"
def change_source(*_):
(self.plugin / "server.py").write_bytes(b"changed during build")
return b"adapter", b"license", "fixture"
with (
patch.object(packager, "base_runtime", return_value=({}, {})),
patch.object(packager, "build_adapter", side_effect=change_source),
self.assertRaisesRegex(ValueError, "MARKET identity or content hash"),
):
packager.main(
[
"--cua-base-archive",
"base.zip",
"--go",
"go.exe",
"--output",
str(output),
"--catalog-output",
str(self.root / "catalog.json"),
"--device-test-source",
str(self.plugin),
]
)
self.assertEqual(list(output.glob("*.zip")), [])
def test_refuses_existing_outputs_and_unsafe_release_tags(self):
output, catalog = self.assemble("existing")
original = catalog.read_bytes()
with self.assertRaisesRegex(ValueError, "new or empty"):
packager.main(
[
"--cua-base-archive",
"base.zip",
"--go",
"go.exe",
"--output",
str(output),
"--catalog-output",
str(catalog),
]
)
self.assertEqual(catalog.read_bytes(), original)
with self.assertRaisesRegex(ValueError, "Refusing to overwrite an archive"):
packager.write_zip(output / "task-review-1.1.1.zip", {"file": b"changed"})
for tag in ("../main", "https://invalid", "", "x#fragment", "x y"):
with self.subTest(tag=tag), self.assertRaises(ValueError):
packager.release_url(tag, "plugin.zip")
if __name__ == "__main__":
unittest.main()