autool/manual_test_worker_client.py
2026-06-17 19:44:18 +08:00

202 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())