test_env.py 4.91 KB
#!/usr/bin/env python3
"""
环境预检脚本 — 验证 Pixel 本地运行所需的所有依赖。
在 Ubuntu proot 容器内执行。
"""
import subprocess, shutil, sys, os, json

REPORT = {"timestamp": None, "checks": {}, "overall": None}

def check(name, cmd, expect_in_stdout=None, expect_returncode=0):
    """Run a command and report success/failure."""
    try:
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
        ok = r.returncode == expect_returncode
        if expect_in_stdout and ok:
            ok = expect_in_stdout in r.stdout
        stdout = r.stdout.strip()[:200] if r.stdout else ""
        stderr = r.stderr.strip()[:200] if r.stderr else ""
        REPORT["checks"][name] = {
            "ok": ok,
            "returncode": r.returncode,
            "stdout": stdout,
            "stderr": stderr
        }
        status = "[OK]" if ok else "[FAIL]"
        print(f"  {status} {name}")
        if not ok:
            print(f"       stdout: {stdout}")
            print(f"       stderr: {stderr}")
        return ok
    except Exception as e:
        REPORT["checks"][name] = {"ok": False, "error": str(e)}
        print(f"  [FAIL] {name}: {e}")
        return False

def main():
    print("=" * 60)
    print("WeChat 本地闭环 — 环境预检")
    print("=" * 60)

    all_ok = True

    # 1. su (root)
    print("\n[1/8] Root 权限 (su)")
    all_ok &= check("su", ["su", "-c", "id"], expect_in_stdout="uid=0")

    # 2. sqlcipher
    print("\n[2/8] SQLCipher 解密工具")
    all_ok &= check("sqlcipher", ["sqlcipher", "--version"])

    # 3. sqlite3
    print("\n[3/8] SQLite3 查询工具")
    all_ok &= check("sqlite3", ["sqlite3", "--version"])

    # 4. am (Activity Manager)
    print("\n[4/8] Android am 命令")
    # am without args may return non-zero but still print help; check output only
    r = subprocess.run(["/system/bin/am"], capture_output=True, text=True, timeout=5)
    am_ok = "Activity manager" in r.stdout or "activity" in r.stdout.lower()
    status = "[OK]" if am_ok else "[FAIL]"
    detail = r.stdout[:60].replace("\n", " ")
    print(f"  {status} am")
    if detail:
        print(f"       {detail}")
    REPORT["checks"]["am"] = {"ok": am_ok, "stdout": r.stdout[:100]}
    all_ok &= am_ok

    # 5. frida Python 包 (optional - only needed for key capture)
    print("\n[5/8] Frida Python 包 (可选)")
    try:
        import frida
        print(f"  [OK] frida {frida.__version__}")
        REPORT["checks"]["frida_python"] = {"ok": True, "version": frida.__version__}
    except ImportError:
        print("  [WARN] frida 未安装 (仅影响 key 捕获,不影响核心功能)")
        print("       如需安装: pip install frida==17.9.10 (可能需要预编译 wheel)")
        REPORT["checks"]["frida_python"] = {"ok": False, "optional": True}
        # Don't fail overall for optional dependency

    # 6. frida-server 运行状态 (optional)
    print("\n[6/8] Frida Server 运行状态 (可选)")
    ok = check("frida-server", ["su", "-c", "ps -A | grep frida-server"], expect_in_stdout="frida-server")
    if not ok:
        print("       [WARN] frida-server 未运行,仅影响 key 重捕获")
        REPORT["checks"]["frida_server"]["optional"] = True
    # Don't fail overall for optional dependency

    # 7. 微信进程
    print("\n[7/8] 微信进程")
    ok = check("wechat_process", ["su", "-c", "pgrep -x com.tencent.mm"])
    all_ok &= ok

    # 8. 工作目录可写
    print("\n[8/8] 工作目录可写")
    work_dir = os.path.expanduser("~/wxmoment")
    try:
        os.makedirs(work_dir, exist_ok=True)
        test_file = os.path.join(work_dir, ".write_test")
        with open(test_file, "w") as f:
            f.write("ok")
        os.remove(test_file)
        print(f"  [OK] {work_dir} 可写")
        REPORT["checks"]["workdir"] = {"ok": True, "path": work_dir}
    except Exception as e:
        print(f"  [FAIL] {work_dir} 不可写: {e}")
        REPORT["checks"]["workdir"] = {"ok": False, "error": str(e)}
        all_ok = False

    # 额外:检查 /sdcard/hermes 可写
    print("\n[Extra] /sdcard/hermes 目录")
    try:
        os.makedirs("/sdcard/hermes", exist_ok=True)
        print("  [OK] /sdcard/hermes 可写")
        REPORT["checks"]["sdcard_hermes"] = {"ok": True}
    except Exception as e:
        print(f"  [WARN] /sdcard/hermes 可能不可写: {e}")
        REPORT["checks"]["sdcard_hermes"] = {"ok": False, "warn": True}

    # Summary
    REPORT["overall"] = "PASS" if all_ok else "FAIL"
    REPORT["timestamp"] = subprocess.check_output(["date", "-Iseconds"]).decode().strip()

    print("\n" + "=" * 60)
    print(f"结果: {REPORT['overall']}")
    print("=" * 60)

    # Save report
    report_path = "test_report_env.json"
    with open(report_path, "w") as f:
        json.dump(REPORT, f, indent=2, ensure_ascii=False)
    print(f"\n详细报告已保存: {report_path}")

    return 0 if all_ok else 1

if __name__ == "__main__":
    sys.exit(main())