95 lines
4.4 KiB
Python
95 lines
4.4 KiB
Python
"""Exercise the actual frozen guard in isolated child processes, without a GUI."""
|
|
from pathlib import Path
|
|
import json, os, subprocess, sys, tempfile, time, types, uuid
|
|
from PyInstaller.archive.readers import CArchiveReader
|
|
|
|
|
|
def load_guard(exe):
|
|
pyz = CArchiveReader(str(exe)).open_embedded_archive('PYZ.pyz')
|
|
module = types.ModuleType('desktop_instance')
|
|
module.__file__ = str(exe)
|
|
sys.modules[module.__name__] = module
|
|
exec(pyz.extract(module.__name__), module.__dict__)
|
|
return module.DesktopInstanceGuard
|
|
|
|
|
|
def worker(exe, folder, namespace, number, action):
|
|
assert namespace.startswith('frozen-instance-check-')
|
|
folder = Path(folder)
|
|
guard = load_guard(exe)(namespace=namespace)
|
|
(folder / ('ready-' + number)).touch()
|
|
deadline = time.monotonic() + 40
|
|
while not (folder / 'go').exists():
|
|
if time.monotonic() > deadline: raise RuntimeError('start timed out')
|
|
time.sleep(.01)
|
|
primary = guard.acquire()
|
|
temp = folder / ('role-' + number + '.tmp')
|
|
temp.write_text(json.dumps({'primary': primary, 'pid': os.getpid()}))
|
|
temp.replace(folder / ('role-' + number))
|
|
try:
|
|
if action == 'signal':
|
|
guard.request_activation()
|
|
return
|
|
while not (folder / 'stop').exists():
|
|
if primary and guard.consume_activation():
|
|
(folder / ('activated-' + number)).touch()
|
|
if time.monotonic() > deadline: raise RuntimeError('stop timed out')
|
|
time.sleep(.01)
|
|
finally:
|
|
guard.close()
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) > 1 and sys.argv[1] == '--worker':
|
|
return worker(*sys.argv[2:])
|
|
artifact = Path(__file__).parent
|
|
exe = Path('C:/wechat_rpa/dist/ZhenAI-WeCom-Assistant-v1.4.21/ZhenAI-WeCom-Assistant-v1.4.21.exe')
|
|
namespace = 'frozen-instance-check-' + uuid.uuid4().hex
|
|
children = []
|
|
with tempfile.TemporaryDirectory(prefix='frozen-instance-check-', dir=artifact) as raw:
|
|
folder = Path(raw)
|
|
def launch(number, action='hold'):
|
|
child = subprocess.Popen([sys.executable, '-B', '-X', 'utf8', str(Path(__file__).resolve()),
|
|
'--worker', str(exe), str(folder), namespace, str(number), action],
|
|
cwd=folder, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
creationflags=subprocess.CREATE_NO_WINDOW)
|
|
children.append(child)
|
|
return child
|
|
def wait(predicate, label):
|
|
deadline = time.monotonic() + 20
|
|
while not predicate():
|
|
if time.monotonic() > deadline: raise AssertionError(label + ' timed out')
|
|
time.sleep(.02)
|
|
try:
|
|
for number in range(6): launch(number)
|
|
wait(lambda: all((folder / f'ready-{n}').exists() for n in range(6)), 'readiness')
|
|
(folder / 'go').touch()
|
|
wait(lambda: all((folder / f'role-{n}').exists() for n in range(6)), 'election')
|
|
roles = [json.loads((folder / f'role-{n}').read_text()) for n in range(6)]
|
|
owners = [n for n, role in enumerate(roles) if role['primary']]
|
|
assert len(owners) == 1, roles
|
|
owner = owners[0]
|
|
signaler = launch(6, 'signal')
|
|
signaler.communicate(timeout=15)
|
|
assert signaler.returncode == 0
|
|
assert not json.loads((folder / 'role-6').read_text())['primary']
|
|
wait(lambda: (folder / f'activated-{owner}').exists(), 'existing instance activation')
|
|
# Only terminate our own synthetic mutex holder, never a user process.
|
|
children[owner].kill()
|
|
children[owner].communicate(timeout=5)
|
|
launch(7)
|
|
wait(lambda: (folder / 'role-7').exists(), 'crash recovery')
|
|
assert json.loads((folder / 'role-7').read_text())['primary']
|
|
report = {'version':'1.4.21','actualFrozenModule':True,'parallelLaunches':6,
|
|
'primaryInstances':1,'activationPassed':True,'crashRecoveryPassed':True,
|
|
'realGuiOpened':False,'realMessagesSent':0,'namespaceIsolated':True}
|
|
finally:
|
|
(folder / 'stop').touch()
|
|
for child in children:
|
|
try: child.communicate(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
child.kill(); child.communicate(timeout=5)
|
|
(artifact / 'frozen-instance-processes.json').write_text(json.dumps(report, indent=2), encoding='utf-8')
|
|
print(json.dumps(report))
|
|
|
|
if __name__ == '__main__': main() |