from pathlib import Path import hashlib,json,os,sys,tempfile,types from unittest import mock from PyInstaller.archive.readers import CArchiveReader root=Path('C:/wechat_rpa') folder=Path('C:/kefu/deploy/review-contacts-20260917') app=root/'dist/ZhenAI-WeCom-Assistant-v1.4.23' exe=app/'ZhenAI-WeCom-Assistant-v1.4.23.exe' manifest=json.loads((folder/'build-source-manifest.json').read_text(encoding='utf-8')) assert sys.version_info[:2]==(3,12) assert all(hashlib.sha256((root/name).read_bytes()).hexdigest()==digest for name,digest in manifest.items()) archive=CArchiveReader(str(exe));pyz=archive.open_embedded_archive('PYZ.pyz') assert pyz.extract('review_assistant') assert pyz.extract('review_assistant_contacts') def code_fingerprint(code): if isinstance(code, types.CodeType): return (code.co_code, code.co_names, code.co_varnames, code.co_freevars, code.co_cellvars, code.co_argcount, code.co_posonlyargcount, code.co_kwonlyargcount, code.co_flags, code.co_exceptiontable, tuple(code_fingerprint(item) for item in code.co_consts)) if isinstance(code, tuple): return tuple(code_fingerprint(item) for item in code) return code checked_modules = ['review_assistant_contacts', 'wechat_gui_qt', 'app_version'] for module_name in checked_modules: source = root / (module_name + '.py') compiled = compile(source.read_text(encoding='utf-8-sig'), str(source), 'exec', optimize=1) assert code_fingerprint(pyz.extract(module_name)) == code_fingerprint(compiled), module_name sender=pyz.extract('wecom_native_sender') functions={item.co_name:item for item in sender.co_consts if isinstance(item,types.CodeType)} needed=['_file_identity','_path_file_identity','_hash_client_file'] assert all(name in functions for name in needed) assert 'stat' not in functions['_path_file_identity'].co_names assert 'fstat' in functions['_path_file_identity'].co_names assert '_path_file_identity' in functions['_hash_client_file'].co_names env={'Path':Path,'os':os,'hashlib':hashlib,'NativeUnavailable':type('NativeUnavailable',(RuntimeError,),{})} for name in needed:env[name]=types.FunctionType(functions[name],env) with tempfile.TemporaryDirectory(prefix='frozen-identity-',dir=folder) as temp: p=Path(temp)/'fixture.bin';content=b'packaged file identity regression\x00'*4096;p.write_bytes(content) os.utime(p,ns=(1_600_000_000_000_000_000,1_600_000_000_000_000_000)) with mock.patch.object(Path,'stat',side_effect=AssertionError('Path.stat must not be used for handle identity')): actual,identity=env['_hash_client_file'](p) assert actual==hashlib.sha256(content).hexdigest() assert identity[2]==len(content) private={'wxwork_keys.json','protocol_sends.sqlite3','pending_replies.protocol.json','pending_replies.json','conversations.json','backend_connection.json','runtime_settings.json','desktop_account.json','app_settings.json','review_assistant.sqlite3'} assert not any(p.name in private for p in app.rglob('*') if p.is_file()) for relative in ['assets/ui/console-app.js','assets/ui/zhenyang-ai-console.html','assets/ui/console-review.css','assets/ui/console-responsive.css']: assert (app/'_internal'/relative).read_bytes()==(root/relative).read_bytes(),relative assert (app/'_internal/python312.dll').is_file() report={'version':'1.4.23','sourceManifestMatches':True,'compiledModulesMatchSource':checked_modules,'frozenFileIdentityFixPresent':True,'frozenHashSyntheticRegression':'passed','python312Bundled':True,'privateFilesExcluded':True,'uiAssetsMatchSource':True,'runtime':sys.version,'realMessagesSent':0,'installed':False} (folder/'frozen-release-qa.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8') print(json.dumps(report,ensure_ascii=False))