#!/usr/bin/env python3 """Manual-test worker client reference for ../autool. This script is intentionally self-contained on the control side. To run it from the worker repository, place it under ../autool and execute it with that repo's Python environment so imports from batch_run.py are available. """ import json import socket import sys import time import uuid from pathlib import Path from typing import Any, Dict, Optional AUTOOL_ROOT = Path(__file__).resolve().parent if (AUTOOL_ROOT / "batch_run.py").exists(): sys.path.insert(0, str(AUTOOL_ROOT)) def _load_worker_bits(): from batch_run import TestExecutor from result_codes import ErrorInfo, InfraError from utils_android.TaskWorker import TaskWorker from utils_android.TaskWorker.task_monitor import TaskMonitor from utils_android.TaskWorker.worker_integrated import _channel_name return TestExecutor, ErrorInfo, InfraError, TaskWorker, TaskMonitor, _channel_name def _status_prompt() -> Dict[str, str]: print("\n采集状态:") print(" 1. 采集正常结束") print(" 2. 采集失败:应用闪退/无法打开") print(" 3. 其他原因") choice = input("请输入数字: ").strip() if choice == "1": return {"status_code": "normal", "status_label": "采集正常结束", "failure_reason": ""} if choice == "2": return {"status_code": "app_crash_or_open_failed", "status_label": "采集失败:应用闪退/无法打开", "failure_reason": "应用闪退/无法打开"} detail = input("请描述其他原因: ").strip() return {"status_code": "other", "status_label": "其他原因", "failure_reason": detail} def _request_assignment(worker, tester_name: str, run_kind: str, tier_label: str = "") -> Optional[Dict[str, Any]]: *_, _channel_name = _load_worker_bits() request = { "worker_id": worker.worker_id, "tester_name": tester_name, "run_kind": run_kind, "tier_label": tier_label, } response = worker._send_request( publish_channel=_channel_name("manual_test:request"), response_channel=_channel_name(f"manual_test:response:{worker.worker_id}"), request_data=request, timeout=30, ) if not response: return None if response.get("error"): raise RuntimeError(response["error"]) return response.get("assignment") def _publish_result(worker, payload: Dict[str, Any]) -> Dict[str, Any]: *_, _channel_name = _load_worker_bits() response = worker._send_request( publish_channel=_channel_name("manual_test:result"), response_channel=_channel_name(f"manual_test:result:response:{worker.worker_id}"), request_data=payload, timeout=30, ) return response or {"ok": False, "error": "manual result response timed out"} def run_manual_task(executor, worker, assignment: Dict[str, Any], tester_name: str) -> Dict[str, Any]: _, ErrorInfo, InfraError, _, TaskMonitor, _ = _load_worker_bits() task_payload = dict(assignment.get("task_payload") or {}) task_payload.setdefault("app_name", assignment.get("app_name") or assignment["package_name"]) task_payload.setdefault("package_name", assignment["package_name"]) task_payload["task_key"] = f"manual_{assignment['assignment_id']}_{assignment['package_name']}" task_payload["keep_app_installed"] = True task_payload.setdefault("available_sources", ["local", "google_play", "apkpure"]) worker.current_task = task_payload ctx = executor._create_task_context(task_payload) mon = TaskMonitor(worker, ctx.task_key) started_at = time.time() pcap_start_status = "not_started" traffic_synced = False log_synced = False try: mon.start_task() next_task = executor._prepare_download(ctx, mon, worker) if next_task is not None: raise RuntimeError("download preparation returned next task") next_task = executor._execute_download(ctx, mon, worker) if next_task is not None: raise RuntimeError("download failed or returned next task") collect_result = executor._prepare_collect(ctx, mon, worker) if collect_result is not None: pcap_start_status = "failed" print("[WARN] PcapDroid 自动开启失败,请手动开启抓包后继续。") else: pcap_start_status = "started" input("\n请开始手动测试应用。测试完成后按 Enter 停止抓包并上传结果...") status_payload = _status_prompt() try: executor.pcap.stop_capture() except Exception as exc: print(f"[WARN] 停止 PcapDroid 失败: {exc}") try: traffic_synced = bool(executor.data.sync_latest_traffic_file(ctx.package_name)) except Exception as exc: print(f"[WARN] 同步流量失败: {exc}") try: mon.finish_stage("collect", status="success" if status_payload["status_code"] == "normal" else "failed") mon.finish_task( "success" if status_payload["status_code"] == "normal" else "failed", failed_stage=None if status_payload["status_code"] == "normal" else "collect", error_type=None if status_payload["status_code"] == "normal" else "BUSINESS_ERROR/0", error_message=status_payload["failure_reason"], ) except Exception: pass return { **status_payload, "assignment_id": assignment["assignment_id"], "package_name": assignment["package_name"], "run_kind": assignment["run_kind"], "source_run_kind": assignment.get("source_run_kind", "ranking"), "tester_name": tester_name, "worker_id": worker.worker_id, "task_key": ctx.task_key, "download_source": ctx.download_source or "", "traffic_synced": traffic_synced, "log_synced": log_synced, "pcap_start_status": pcap_start_status, "total_duration_seconds": round(time.time() - started_at, 2), } except Exception as exc: return { "assignment_id": assignment["assignment_id"], "package_name": assignment["package_name"], "run_kind": assignment["run_kind"], "source_run_kind": assignment.get("source_run_kind", "ranking"), "tester_name": tester_name, "worker_id": worker.worker_id, "task_key": task_payload["task_key"], "status_code": "worker_error", "status_label": "Worker 执行失败", "failure_reason": str(exc), "pcap_start_status": pcap_start_status, "traffic_synced": traffic_synced, "log_synced": log_synced, "total_duration_seconds": round(time.time() - started_at, 2), } def main() -> int: TestExecutor, _, _, TaskWorker, _, _ = _load_worker_bits() tester_name = input("请输入测试人姓名: ").strip() if not tester_name: print("测试人姓名不能为空") return 2 run_kind = input("请输入人工测试类型 [manual_real/manual_block] (默认 manual_real): ").strip() or "manual_real" tier_label = input("指定档位可选,如 T1/5;直接回车不限: ").strip() worker = TaskWorker() worker.ip_address = worker._get_ip_address() worker.mac_address = worker._get_mac_address() worker.hostname = socket.gethostname() worker.worker_id = f"{worker.ip_address}_{worker.mac_address}" print(f"Worker ID: {worker.worker_id}") executor = TestExecutor() if not executor.setup(): print("设备初始化失败") return 1 while True: assignment = _request_assignment(worker, tester_name, run_kind, tier_label=tier_label) if not assignment: print("暂无人工测试任务") return 0 print(f"\n领取任务: {assignment['app_name']} ({assignment['package_name']}) [{assignment.get('tier_label', '')}]") result_payload = run_manual_task(executor, worker, assignment, tester_name) response = _publish_result(worker, result_payload) print("上传结果:", json.dumps(response, ensure_ascii=False)) again = input("继续领取下一个任务?[Y/n]: ").strip().lower() if again == "n": return 0 if __name__ == "__main__": raise SystemExit(main())