feat(tester): require caller artifact locators instead of --release
Mobile runs now take --artifact android|ios=<path-or-url> from the caller and create a fresh isolated worktree each time. Drop CI release/manifest lookup.
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
import hashlib
|
||||
import json
|
||||
import plistlib
|
||||
import subprocess
|
||||
@@ -15,49 +14,54 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
||||
import mobile_bundle as bundle
|
||||
|
||||
|
||||
def manifest(commit: str, apk: Path, ipa: Path) -> dict:
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"release": "ci-test",
|
||||
"commit": commit,
|
||||
"artifacts": {
|
||||
"android": {"url": str(apk), "sha256": hashlib.sha256(apk.read_bytes()).hexdigest(), "package": "page.writes"},
|
||||
"ios": {"url": str(ipa), "sha256": hashlib.sha256(ipa.read_bytes()).hexdigest(), "bundle_id": "page.writes"},
|
||||
},
|
||||
}
|
||||
def init_repo(root: Path) -> str:
|
||||
run = lambda args: subprocess.run(args, check=True, stdout=subprocess.DEVNULL)
|
||||
run(["git", "init", "-q", str(root)])
|
||||
run(["git", "-C", str(root), "config", "user.email", "tester@example.invalid"])
|
||||
run(["git", "-C", str(root), "config", "user.name", "Tester"])
|
||||
(root / "marker").write_text("x")
|
||||
run(["git", "-C", str(root), "add", "marker"])
|
||||
run(["git", "-C", str(root), "commit", "-qm", "bundle"])
|
||||
return subprocess.check_output(["git", "-C", str(root), "rev-parse", "HEAD"], text=True).strip()
|
||||
|
||||
|
||||
class MobileBundleTests(unittest.TestCase):
|
||||
def test_manifest_contract(self):
|
||||
def test_artifact_locator_is_required(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
root = Path(temp)
|
||||
apk, ipa = root / "a.apk", root / "a.ipa"
|
||||
apk.write_bytes(b"apk")
|
||||
ipa.write_bytes(b"ipa")
|
||||
bundle.validate_manifest(manifest("a" * 40, apk, ipa), "ci-test")
|
||||
with self.assertRaises(bundle.ManifestFailure):
|
||||
bundle.validate_manifest(manifest("a" * 40, apk, ipa), "wrong")
|
||||
root = Path(temp) / "repo"
|
||||
root.mkdir()
|
||||
init_repo(root)
|
||||
args = bundle.parser().parse_args(["run", "--platform", "android,ios", "--root", str(root)])
|
||||
self.assertEqual(bundle.execute(args), 1)
|
||||
runs = list((root / ".pouch" / "tester" / "runs").glob("*.yaml"))
|
||||
self.assertEqual(len(runs), 1)
|
||||
record = json.loads(runs[0].read_text(encoding="utf-8"))
|
||||
self.assertIn("缺少调用方产物位置", record["error"])
|
||||
|
||||
def test_exact_worktree_repairs_stale_commit(self):
|
||||
def test_parse_artifacts_accepts_file_and_url(self):
|
||||
parsed = bundle.parse_artifacts([
|
||||
"android=/tmp/app.apk",
|
||||
"ios=https://ci.example/app.ipa",
|
||||
])
|
||||
self.assertEqual(parsed["android"], "/tmp/app.apk")
|
||||
self.assertEqual(parsed["ios"], "https://ci.example/app.ipa")
|
||||
with self.assertRaises(bundle.ArtifactFailure):
|
||||
bundle.parse_artifacts(["android"])
|
||||
with self.assertRaises(bundle.ArtifactFailure):
|
||||
bundle.parse_artifacts(["android=/tmp/a.apk", "android=/tmp/b.apk"])
|
||||
|
||||
def test_new_worktree_each_run(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
repo, worktrees = Path(temp) / "repo", Path(temp) / "worktrees"
|
||||
repo.mkdir()
|
||||
run = lambda args: subprocess.run(args, check=True, stdout=subprocess.DEVNULL)
|
||||
run(["git", "init", "-q", str(repo)])
|
||||
run(["git", "-C", str(repo), "config", "user.email", "tester@example.invalid"])
|
||||
run(["git", "-C", str(repo), "config", "user.name", "Tester"])
|
||||
(repo / "file").write_text("one")
|
||||
run(["git", "-C", str(repo), "add", "file"])
|
||||
run(["git", "-C", str(repo), "commit", "-qm", "one"])
|
||||
first = subprocess.check_output(["git", "-C", str(repo), "rev-parse", "HEAD"], text=True).strip()
|
||||
(repo / "file").write_text("two")
|
||||
run(["git", "-C", str(repo), "commit", "-qam", "two"])
|
||||
second = subprocess.check_output(["git", "-C", str(repo), "rev-parse", "HEAD"], text=True).strip()
|
||||
path = worktrees / second[:12]
|
||||
run(["git", "-C", str(repo), "worktree", "add", "--detach", str(path), first])
|
||||
actual, commit = bundle.ensure_worktree(second, bundle.RuntimeContext(repo), worktrees)
|
||||
self.assertEqual(actual, path.resolve())
|
||||
self.assertEqual(commit, second)
|
||||
commit = init_repo(repo)
|
||||
first, first_commit = bundle.ensure_worktree(commit, bundle.RuntimeContext(repo), worktrees)
|
||||
second, second_commit = bundle.ensure_worktree(commit, bundle.RuntimeContext(repo), worktrees)
|
||||
self.assertEqual(first_commit, commit)
|
||||
self.assertEqual(second_commit, commit)
|
||||
self.assertNotEqual(first, second)
|
||||
self.assertTrue(first.is_dir())
|
||||
self.assertTrue(second.is_dir())
|
||||
|
||||
def test_ipa_identity_and_install(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
@@ -101,22 +105,27 @@ class MobileBundleTests(unittest.TestCase):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
root = Path(temp) / "repo"
|
||||
root.mkdir()
|
||||
subprocess.run(["git", "init", "-q", str(root)], check=True)
|
||||
subprocess.run(["git", "-C", str(root), "config", "user.email", "tester@example.invalid"], check=True)
|
||||
subprocess.run(["git", "-C", str(root), "config", "user.name", "Tester"], check=True)
|
||||
(root / "marker").write_text("x")
|
||||
subprocess.run(["git", "-C", str(root), "add", "marker"], check=True)
|
||||
subprocess.run(["git", "-C", str(root), "commit", "-qm", "bundle"], check=True)
|
||||
commit = subprocess.check_output(["git", "-C", str(root), "rev-parse", "HEAD"], text=True).strip()
|
||||
commit = init_repo(root)
|
||||
apk, ipa = Path(temp) / "a.apk", Path(temp) / "a.ipa"
|
||||
apk.write_bytes(b"apk")
|
||||
ipa.write_bytes(b"ipa")
|
||||
manifest_path = Path(temp) / "manifest.json"
|
||||
manifest_path.write_text(json.dumps(manifest(commit, apk, ipa)))
|
||||
args = bundle.parser().parse_args(["run", "--release", str(manifest_path), "--root", str(root), "--worktree-root", str(Path(temp) / "wt")])
|
||||
args = bundle.parser().parse_args([
|
||||
"run",
|
||||
"--artifact", f"android={apk}",
|
||||
"--artifact", f"ios={ipa}",
|
||||
"--root", str(root),
|
||||
"--worktree-root", str(Path(temp) / "wt"),
|
||||
])
|
||||
with mock.patch.object(bundle, "android_identity", return_value="page.writes"), mock.patch.object(bundle, "ios_identity", return_value="page.writes"), mock.patch.object(bundle, "android_preflight", return_value={"adb": "adb", "serial": "device"}), mock.patch.object(bundle, "ios_preflight", side_effect=bundle.EnvironmentFailure("Mac unavailable")), mock.patch.object(bundle, "check_network"), mock.patch.object(bundle, "install_android") as install:
|
||||
self.assertEqual(bundle.execute(args), 1)
|
||||
install.assert_not_called()
|
||||
self.assertEqual(args.commit, "")
|
||||
self.assertTrue(COMMIT_HEAD(root, commit))
|
||||
|
||||
|
||||
def COMMIT_HEAD(root: Path, commit: str) -> bool:
|
||||
actual = subprocess.check_output(["git", "-C", str(root), "rev-parse", "HEAD"], text=True).strip()
|
||||
return actual == commit
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user