增加部分付款和部分交货状态、工作台筛选、权限校验、跟进记录及 v4 数据迁移;补充 Windows EXE 构建与验收脚本。附 16 页 PDF 使用升级手册及企业微信详细配置说明、生成脚本和测试截图。验证:76 项回归测试、EXE 浏览器验收与 PDF 逐页校验通过。
66 lines
2.8 KiB
Python
66 lines
2.8 KiB
Python
"""Run an isolated copy of the EXE, with no Python installation on its PATH."""
|
|
import os
|
|
import shutil
|
|
import socket
|
|
import subprocess
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
|
|
class PackagedServer:
|
|
def __init__(self, executable, directory):
|
|
source = Path(executable).resolve(strict=True)
|
|
self.directory = Path(directory).resolve()
|
|
self.executable = self.directory / source.name
|
|
shutil.copy2(source, self.executable)
|
|
self.cwd = self.directory / '不同启动目录'
|
|
self.cwd.mkdir()
|
|
with socket.socket() as sock:
|
|
sock.bind(('127.0.0.1', 0))
|
|
self.port = sock.getsockname()[1]
|
|
self.base = 'http://127.0.0.1:%s' % self.port
|
|
self.process = None
|
|
self.log = None
|
|
self.http = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
|
|
|
def start(self):
|
|
env = {key: value for key, value in os.environ.items()
|
|
if not key.upper().startswith(('PO_', 'PYTHON', 'VIRTUAL_ENV'))}
|
|
windows = Path(env.get('SystemRoot', r'C:\Windows'))
|
|
env.update(PO_HOST='127.0.0.1', PO_OPEN_BROWSER='0',
|
|
PATH=os.pathsep.join((str(windows / 'System32'), str(windows))))
|
|
self.log = (self.directory / 'exe.log').open('ab')
|
|
self.process = subprocess.Popen([str(self.executable), str(self.port)],
|
|
cwd=self.cwd, env=env, stdin=subprocess.DEVNULL,
|
|
stdout=self.log, stderr=subprocess.STDOUT,
|
|
creationflags=subprocess.CREATE_NO_WINDOW)
|
|
deadline = time.monotonic() + 90
|
|
while time.monotonic() < deadline:
|
|
if self.process.poll() is not None:
|
|
raise RuntimeError('EXE exited during startup; see %s' % self.log.name)
|
|
try:
|
|
with self.http.open(self.base + '/login', timeout=1) as response:
|
|
if response.status == 200:
|
|
return
|
|
except (urllib.error.URLError, TimeoutError, ConnectionError):
|
|
pass
|
|
time.sleep(0.2)
|
|
raise TimeoutError('EXE startup timed out; see %s' % self.log.name)
|
|
|
|
def close(self):
|
|
if self.process is not None and self.process.poll() is None:
|
|
# A one-file PyInstaller app has a launcher and a server child.
|
|
# Terminate only the process tree created by this test.
|
|
subprocess.run(['taskkill', '/PID', str(self.process.pid), '/T', '/F'],
|
|
check=True, capture_output=True,
|
|
creationflags=subprocess.CREATE_NO_WINDOW)
|
|
self.process.wait(timeout=15)
|
|
if self.log:
|
|
self.log.close()
|
|
|
|
def restart(self):
|
|
self.close()
|
|
self.start()
|