diff --git a/skills/tester/scripts/mobile_bundle.py b/skills/tester/scripts/mobile_bundle.py index 484c0b9..3420bbe 100755 --- a/skills/tester/scripts/mobile_bundle.py +++ b/skills/tester/scripts/mobile_bundle.py @@ -219,6 +219,28 @@ def validate_preflight_matrix(root: Path, config_path: Path, bindings: dict[str, raise EnvironmentFailure(f"{platform} 绑定到 {expected_node},必须使用 contract runner;禁止在协调节点执行") +def preflight_remote_artifacts(config_path: Path, requested: list[str]) -> dict[str, str]: + """Return caller-declared artifact paths readable by remote tester nodes.""" + try: + import yaml + + raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {} + except (OSError, ValueError, ImportError) as exc: + raise EnvironmentFailure(f"无法读取 preflight matrix: {config_path}: {exc}") from exc + rows = raw.get("matrix") if isinstance(raw, dict) else None + if not isinstance(rows, list): + raise EnvironmentFailure("preflight matrix 缺少 matrix;不能解析远程产物路径") + by_platform = {str(row.get("platform", "")).lower(): row for row in rows if isinstance(row, dict)} + paths: dict[str, str] = {} + for platform in requested: + row = by_platform.get(platform) or {} + artifact = row.get("artifact") if isinstance(row, dict) else None + remote_path = artifact.get("remote_path") if isinstance(artifact, dict) else None + if isinstance(remote_path, str) and remote_path.strip(): + paths[platform] = remote_path.strip() + return paths + + def local_path(locator: str) -> Path | None: parsed = urllib.parse.urlsplit(locator) if parsed.scheme in ("http", "https"): @@ -551,6 +573,7 @@ def execute(args: argparse.Namespace) -> int: "product_test": {"status": "not-run", "failure_class": "automatic-repair-failed", "note": "准备完成后由 tester catalog 驱动真实移动 session;本工具不以 API 替代移动测试"}, "platforms": {}, } + remote_artifacts: dict[str, str] = {} try: locators = parse_artifacts(args.artifacts) missing = [platform for platform in requested if platform not in locators] @@ -596,6 +619,7 @@ def execute(args: argparse.Namespace) -> int: } else: validate_preflight_matrix(context.root, preflight_config, bindings, requested) + remote_artifacts = preflight_remote_artifacts(preflight_config, requested) preflight_result = run_test_preflight( preflight_config, mode=args.preflight_mode, @@ -676,15 +700,17 @@ def execute(args: argparse.Namespace) -> int: }) if network.get("status") != "ready": raise EnvironmentFailure(str(network.get("observed") or network)) - artifact_path = Path(result["artifact"]["path"]) - if platform == "ios" and node_name != "local": - raise EnvironmentFailure("远程 iOS 安装必须在 matrix.artifact.remote_path 声明 Mac 可读产物;不能把协调机路径当成 Mac 路径") + artifact_path = result["artifact"]["path"] + if node_name != "local": + artifact_path = remote_artifacts.get(platform, "") + if not artifact_path: + raise EnvironmentFailure(f"远程 {platform} 安装必须在 matrix.artifact.remote_path 声明节点可读产物") installed = agent_runner(context.root, { "operation": "install", "platform": platform, "node": node_name, "binding": binding_id, - "artifact": str(artifact_path), + "artifact": artifact_path, "identity": identity, }) if installed.get("status") != "ready": diff --git a/skills/tester/tests/test_mobile_bundle.py b/skills/tester/tests/test_mobile_bundle.py index 53c2140..b83c49a 100755 --- a/skills/tester/tests/test_mobile_bundle.py +++ b/skills/tester/tests/test_mobile_bundle.py @@ -116,6 +116,18 @@ matrix: ["android", "ios"], ) + def test_remote_artifact_path_comes_from_preflight_matrix(self): + with tempfile.TemporaryDirectory() as temp: + config = Path(temp) / "matrix.yaml" + config.write_text( + "matrix:\n - platform: ios\n artifact:\n remote_path: /Users/tester/app.ipa\n", + encoding="utf-8", + ) + self.assertEqual( + bundle.preflight_remote_artifacts(config, ["ios"]), + {"ios": "/Users/tester/app.ipa"}, + ) + def test_new_worktree_each_run(self): with tempfile.TemporaryDirectory() as temp: repo, worktrees = Path(temp) / "repo", Path(temp) / "worktrees"