From 8c4e1b56b5c8c75e18af3eb04c8da2931f1a0706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fou=C3=A9?= Date: Fri, 12 Jun 2026 13:57:48 +0200 Subject: [PATCH] feat(windows): icon, windowed exe, no-admin installer - PyInstaller exe built windowed (console=False) with package/testium.ico as the embedded icon (BMP entries for shell compatibility). - Suppress stray subprocess console windows in the frozen Windows build via paths.no_window_kwargs() (CREATE_NO_WINDOW); wheel/source unchanged. Applied to py_process, lua_process, bins probes, sys_app_path_win. - New per-user Inno Setup installer (package/innosetup/): no admin, version-scoped AppId/dir so versions install side-by-side, one Start Menu entry per version, .ico shipped for shortcut/uninstall icons. - DESIGN.md + release_note.txt updated. Co-Authored-By: Claude Opus 4.8 (1M context) --- DESIGN.md | 16 ++- package/innosetup/build.ps1 | 31 +++++ package/innosetup/testium.iss | 127 +++++++++++++++++++ package/pyinstaller/testium.spec | 4 +- package/testium.ico | Bin 0 -> 361102 bytes release_note.txt | 1 + src/testium/interpreter/utils/bins.py | 3 +- src/testium/interpreter/utils/lua_process.py | 3 +- src/testium/interpreter/utils/paths.py | 9 ++ src/testium/interpreter/utils/py_process.py | 3 +- 10 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 package/innosetup/build.ps1 create mode 100644 package/innosetup/testium.iss create mode 100644 package/testium.ico diff --git a/DESIGN.md b/DESIGN.md index 1d66d17..ae8ec58 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -226,7 +226,8 @@ Four distribution channels coexist, all sharing the single `src/testium/` packag | Channel | Where | Build | Notes | |---------|-------|-------|-------| | Wheel (`pip install`) | `src/pyproject.toml` | `python -m build` | Vanilla Python package; entry point `testium = "testium:main"`. | -| PyInstaller binary | `package/pyinstaller/` | `build.sh` | Single ~130 MB binary. `py_func`, `runtime`, `lua_func` bundled at `_MEIPASS` root so the **host** Python can find them when launched as `python3 py_func`. `api`/`interpreter` are **not** exposed (subprocess isolation). | +| PyInstaller binary | `package/pyinstaller/` | `build.sh` | Single ~130 MB binary. `py_func`, `runtime`, `lua_func` bundled at `_MEIPASS` root so the **host** Python can find them when launched as `python3 py_func`. `api`/`interpreter` are **not** exposed (subprocess isolation). Built windowed (`console=False`) with `package/testium.ico` as the exe icon — see "Windows frozen build". | +| Windows installer | `package/innosetup/` | `build.ps1` (Inno Setup 6) | Wraps the PyInstaller exe. Per-user, **no admin** (`PrivilegesRequired=lowest`, installs under `%LOCALAPPDATA%`). Version-scoped `AppId` + install dir so versions coexist side-by-side; one Start Menu entry per version. | | Flatpak | `package/flatpak/` | `build.sh` (uses `flatpak-builder`) | KDE 6.10 runtime. The bundled Python runs only the main process; `py_func` / `lua_func` MUST run under the **host** interpreter (no Python/Lua bundled). Produces a distributable `.flatpak` bundle. | | AppImage | `package/appimage/` | `build.sh` (Debian Bookworm container via Podman/Docker) | Bundles Python 3.11 for the main process; `py_func` / `lua_func` MUST run under the **host** interpreter. Build runs in a container so it works on Arch / any non-Debian host. | @@ -257,6 +258,19 @@ The bundled Python (Flatpak's runtime python, AppImage's `python3.11`) is reserv - If the host has no python3/lua, `ensure()` raises `ETUMRuntimeError` at test load with the candidate list — no silent fallback to a bundled interpreter. - `py_process.py` additionally pops `PYTHONUSERBASE` (set to `/var/data/python` by the Flatpak runtime, which would hide `~/.local/lib/...`). +### Windows frozen build: no console, hidden subprocess windows + +The PyInstaller exe is built **windowed** (`console=False` in `testium.spec`) so a +double-click doesn't open a console. The catch: a windowed process has **no console +to inherit**, so every console subprocess it spawns (the `py_func`/`lua_func` host +Python bridges — the otherwise-permanent window — plus the `where`/`which`/`--version` +probes) opens its **own** console window. `paths.no_window_kwargs()` returns +`{"creationflags": CREATE_NO_WINDOW}` on a frozen Windows build (and `{}` everywhere +else, so the **wheel/source** keeps its console and child output stays visible). It is +applied at every spawn site: `py_process.py`, `lua_process.py`, `bins._run_probe`, +`paths.sys_app_path_win`. `termconsole.py` is intentionally exempt (it already hides +`cmd.exe` via `STARTUPINFO`). + ### Declarative test item parameters Each `TestItem` subclass declares its accepted parameters as a class attribute `PARAMS = ParamSet(Param(...), ...)` (`interpreter/utils/param_decl.py`). The descriptor carries the parameter name, *kind* (`SCALAR` — the default and may be omitted; `LIST`; `BLOCK`; `Enum("a", "b", ...)`), `required` flag, `default`, and free-form `doc`. There is **no Python type** in the descriptor on purpose: most parameter values are expressions (`$(...)` / `<| ... |>`) whose effective type is only known after expansion, so a static type would be misleading. Post-expansion `validate=lambda v: ...` callbacks are available as an opt-in for the rare cases where a runtime check is warranted (e.g. a specific format). diff --git a/package/innosetup/build.ps1 b/package/innosetup/build.ps1 new file mode 100644 index 0000000..f652774 --- /dev/null +++ b/package/innosetup/build.ps1 @@ -0,0 +1,31 @@ +# Build the Testium installer from testium.iss (needs Inno Setup 6 / ISCC.exe). +# Install ISCC without admin: winget install --id JRSoftware.InnoSetup -e + +$ErrorActionPreference = 'Stop' +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +# The PyInstaller exe must exist first. +$exe = Join-Path $scriptDir '..\pyinstaller\dist\testium.exe' +if (-not (Test-Path $exe)) { + throw "PyInstaller build not found: $exe`nRun package\pyinstaller\build first." +} + +# Locate ISCC.exe: PATH, then the usual install dirs. +$iscc = (Get-Command ISCC.exe -ErrorAction SilentlyContinue).Source +if (-not $iscc) { + foreach ($p in @( + "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe", + "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe", + "$env:ProgramFiles\Inno Setup 6\ISCC.exe")) { + if (Test-Path $p) { $iscc = $p; break } + } +} +if (-not $iscc) { + throw "ISCC.exe not found. Install Inno Setup 6:`n winget install --id JRSoftware.InnoSetup -e" +} + +Write-Host "Using ISCC: $iscc" +& $iscc (Join-Path $scriptDir 'testium.iss') +if ($LASTEXITCODE -ne 0) { throw "ISCC failed with exit code $LASTEXITCODE" } + +Write-Host "`nInstaller built in: $(Join-Path $scriptDir 'dist')" diff --git a/package/innosetup/testium.iss b/package/innosetup/testium.iss new file mode 100644 index 0000000..4ee6e8a --- /dev/null +++ b/package/innosetup/testium.iss @@ -0,0 +1,127 @@ +; Inno Setup script: wraps the PyInstaller testium.exe into a per-user installer. +; Build with Inno Setup 6: ISCC.exe testium.iss (or ./build.ps1). + +#define MyAppName "Testium" +#define MyAppExeName "testium.exe" +#define MyAppPublisher "Testium" +#define MyAppURL "https://github.com/" + +; Read version from src/VERSION so the installer never drifts from the build. +#define VerFile FileOpen("..\..\src\VERSION") +#define MyAppVersion Trim(FileRead(VerFile)) +#expr FileClose(VerFile) +#if MyAppVersion == "" + #error Could not read version from ..\..\src\VERSION +#endif + +[Setup] +; Version-scoped AppId: each version is a distinct app, installable side-by-side. +AppId={{B7E6F1C2-9A4D-4E3B-8F71-7C2D5A6E0B14}_{#MyAppVersion} +AppName={#MyAppName} {#MyAppVersion} +AppVerName={#MyAppName} {#MyAppVersion} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +UninstallDisplayName={#MyAppName} {#MyAppVersion} +WizardStyle=modern +; Per-version install dir so versions never overwrite each other. +DefaultDirName={autopf}\{#MyAppName}\{#MyAppVersion} +; Shared "Testium" Start Menu folder; shortcuts below are named per version. +DefaultGroupName={#MyAppName} +UninstallDisplayIcon={app}\testium.ico +DisableProgramGroupPage=yes +; Per-user install, no admin ever: installs under %LOCALAPPDATA%, no UAC prompt. +PrivilegesRequired=lowest +ArchitecturesInstallIn64BitMode=x64compatible +OutputDir=dist +OutputBaseFilename=testium-{#MyAppVersion}-setup +SetupIconFile=..\testium.ico +Compression=lzma2/max +SolidCompression=yes +; Tell Explorer to refresh the environment after a PATH change. +ChangesEnvironment=yes + +[Languages] +Name: "french"; MessagesFile: "compiler:Languages\French.isl" +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked +; PATH off by default: the exe is windowed (console=False), so CLI shows no output. +Name: "addtopath"; Description: "Ajouter Testium au PATH (usage en ligne de commande)"; Flags: unchecked + +[Files] +Source: "..\pyinstaller\dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +; Ship the .ico so shortcuts/uninstall reference it directly, not the embedded one. +Source: "..\testium.ico"; DestDir: "{app}"; Flags: ignoreversion + +[Icons] +; Per-version names so each install shows separately in the Start Menu. +Name: "{group}\{#MyAppName} {#MyAppVersion}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\testium.ico" +Name: "{group}\{cm:UninstallProgram,{#MyAppName} {#MyAppVersion}}"; Filename: "{uninstallexe}" +Name: "{autodesktop}\{#MyAppName} {#MyAppVersion}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\testium.ico"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: nowait postinstall skipifsilent + +[Code] +const + EnvKey = 'Environment'; + +// True if Param is not already a full segment of the per-user PATH. +function NeedsAddPath(Param: string): Boolean; +var + OrigPath: string; +begin + if not RegQueryStringValue(HKEY_CURRENT_USER, EnvKey, 'Path', OrigPath) then + begin + Result := True; + exit; + end; + Result := Pos(';' + Uppercase(Param) + ';', ';' + Uppercase(OrigPath) + ';') = 0; +end; + +// On install: append {app} to the per-user PATH if the task is selected. +procedure CurStepChanged(CurStep: TSetupStep); +var + Path: string; +begin + if CurStep = ssPostInstall then + begin + if WizardIsTaskSelected('addtopath') and NeedsAddPath(ExpandConstant('{app}')) then + begin + if not RegQueryStringValue(HKEY_CURRENT_USER, EnvKey, 'Path', Path) then + Path := ''; + if (Path <> '') and (Copy(Path, Length(Path), 1) <> ';') then + Path := Path + ';'; + Path := Path + ExpandConstant('{app}'); + RegWriteStringValue(HKEY_CURRENT_USER, EnvKey, 'Path', Path); + end; + end; +end; + +// On uninstall: strip {app} back out of the per-user PATH. +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +var + Path, AppDir, Segment: string; + P: Integer; +begin + if CurUninstallStep = usUninstall then + begin + if RegQueryStringValue(HKEY_CURRENT_USER, EnvKey, 'Path', Path) then + begin + AppDir := ExpandConstant('{app}'); + Segment := ';' + AppDir; + P := Pos(Uppercase(Segment), Uppercase(Path)); + if P > 0 then + Delete(Path, P, Length(Segment)) + else + begin + P := Pos(Uppercase(AppDir) + ';', Uppercase(Path)); + if P = 1 then + Delete(Path, 1, Length(AppDir) + 1); + end; + RegWriteStringValue(HKEY_CURRENT_USER, EnvKey, 'Path', Path); + end; + end; +end; diff --git a/package/pyinstaller/testium.spec b/package/pyinstaller/testium.spec index 6d2de79..b7b476d 100644 --- a/package/pyinstaller/testium.spec +++ b/package/pyinstaller/testium.spec @@ -94,11 +94,11 @@ exe = EXE( upx=not os.environ.get("TESTIUM_NO_UPX"), upx_exclude=[], runtime_tmpdir=None, - console=True, + console=False, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, - ico='../testium.png' + ico='../testium.ico' ) diff --git a/package/testium.ico b/package/testium.ico new file mode 100644 index 0000000000000000000000000000000000000000..28752b26713381c3641035324bb97e69d06124a3 GIT binary patch literal 361102 zcmeF42b^P7y~ndV$;>VwsGwlS`mE31@aZc06bsgePl~|q&ZNvN3y7#-8B`Qd>=g@G zz%GhnMZhkI@MwaFROy{9Q*x8d`~LptoSU0WGLy_?W-_yr&*$de+}wN4|NQ&;pZ_^0 zm0FhSOWpUrDL%WV9-mI7-oe!_yZGNLQ>hcVx9hI{H_!d~KB?5xp5}i)CY8GDVX4%M zU+jN#e&Ft@)KN$I-<1BM59nGS%1@R-%9-__2#12WezI;LI3ykN3Tehk9aCdc}O}ReKVac}D_kj* zpDzxjv&CVxgEqY(lZ&FBZ1i$)eMIW8%C?kuW#&-cUtg&hEYKq*9AzsRf8M4i7yUQy z-OTY@aDKUPL%k=dj!dq6e0p8w2kC6|7210Mu$>#~j?dtpvVTOI$2tE5nAY?Dch#Qo z4CNk{d3)t5&S#}_#dl}cRelRh)K@I%8S4JD(JCK(4A|ev`{&!c&{Dian_iMWtgxq?3^n|@w`_`mi@pXfJ}tUH$9*#S@wbAb_j3Ik z%B^^ON7Vl<{(JJ2b$EIrgD(O`$@xct@dWVuEz0-?*TMc@6A_cocF!?${0bnktaI}A^Q!8Z^y@QqiK|f!ZS-%CIt~?JI z1`RINGcdj;ZinPFhm4~4z~PO+`8@pxjbA{W`)6{cy=l`LaJQ$)RUMzC4=NQhL!~18 z`&`=g8Pl=((!Htv%0s0yc>m3Ylflx4Bp;wpFG7!j0}k{LFx+hAtKFiZ>Iw2&DDKAd zm+O7x^NL>SoLuw;gEL?HzS)Uvsb4w=+Gcg1dVf#Z5jY=(A0yuTXHt*q8T&{l@xdOg zqRfv#n*(~CoZPb_JMwVu-NwD!^$a@vV``6G=SzRE@^jG$e5vyPo_cF#$KFEajlhni z>f?FD*FiaN=_z1Sa;1M6zVpSyj3&A0gcP=jHr|*TsytY4Qr^X;SH(~8DgRw7oox6m zjQ^+M$LUe03snbpO7;_(IT0Lf0oGp`PhqRz=iR}@J%(e-=RfFL{vh?d7rS(1FZ9ji z#-9qUz?qj<0h0WD&-oX?f2ir?eDrVdjm?hsq?{qj-)wdw`3~16(+5|+!2f>`FYx=n zxc*FfsB$u}o|HbQateHRWT3Zj>|`h!rQMhEdw=R&ml@hpk^Zsb9iOaFdwTPwcbOf^ zm$qcG(G6aQ6isf$@9-J*Tpqx}F8`0on|vARm3;YV*_qzMD_dl%2`r+R6bQ!w>r1Y-Bb%*`9TL9s4GBPhZ?O8V*+8YJ9e?a-jIn zc$UAV&+z*$0)}S>e(5Vz9tG}qgO6@B-a=nld8#AX4!ha-3tFPv%fB%^J@apR&*NBf z$PwU@enwYQm-$Rqnw||hN4`${ZYtGV7<&Tt=r4vV-i0U6xBvBA@)PvcK=FNsTltEG z@<~a5HGZ6Td$VJY2ae+?`ySJSxoF(;{c$lY@e8`3KF^oHehBYI@T~mnd+>Wtz{lRR zH&=Nqdhnro8r^k#Mt%>ztoiPO2O8?XNpTwL$QGYV9lsE4renn~D(j#!_O`N0?J~F} z(?jKpWHYVodfBm?bzg~R(1|YtPhZ0R`~#leY;v2IKWc6GyxI0WDt%T$;ywMApR_B- zi#cLHi4Io7{uIwl_$134s_clZ7=VXlucQATgYQM_1 zaapU*ZZ6C5L(MOG`^O%q_EWZO%|>W@4LtUL9G4)AHy9l1m&opkcL%ZQh0+!D z7g@>1dv*NSyvkH+ukZDw_PcpobbUXx`yq9|AO3mCiZzu-n2tSEHq4iot?VEgftN4x z+^q(K^oDfnQ28cw2YpcgX+>58&ez^^f7-i-w$QgLe-8Zq#kIF|CvCo+>no}Ampz5@ zQR$)L^R0$em2`RtTQgX`(d4nP#c+aO{lnBg_c+@S$`3Fl&jCK!+o96?z~R5F9i|h6 zTeXQp?bAK+y!-+2_+a@O;Yoc;J&aze?Hm(?XQ(ih8M2WZ~GrV$>e^I#eE&9riH(Yi0jtlm8kS z_x!OkxA`IXn^#+1Iea7eUutWi^f$^p68-Q(^{LU7+elxE&sU;POlM=Gv6a6QjAjQ0 z%NM8KDjx~IVX%BJv_dz4heGKBdtY=2I#SobfE@ka@T$6{O9#sr_6${=-bmtVl52F& zv*{79I360G<(N7{44!Itq$@PyybtYq-tzDCV{zWTi5c`%Z zT`9j(&l_ywEzvt)`kj?YTuE)!HTQnuc>=$p?DYJ>_>kG?qxS4DefC7#?VfPr=?fjd zf~`L@H9Wi=8r(s7WrvH>fqE9YpFsIr=qHb6`p2K-?v+0QKI|GF=BExXzWuT@lD}MZ zjGk@!QT-klnOyOg@{yrS-sE;jK5+C`wI6uSbN!6y+bWCS`|$jkpd`<@z0hB^<)Tn#z4rYc@Za z_GgWL(rfUAbOtb8rTQpyJ+#2rt~}f77kz-4I`{1@l=dP%_P^3=J?kp36i-XelP=|lGjz(IO0vc??e7&^JRm;e^0LbA;XVis-lnX>yUkd&PhMBZ*cT6#zzIoa#3<{ z48QMIb=3bA)8F(zJw2=b3-YUHWXJGHzDpa4VU$iwA6)q^y!afh)i(ndYLm$-@nH0R zKH9jVP$44jKE3&||KNYu{yaRL9e;+NH~z~Phb?ZCEB(}-_BadgPIiV**Lb}^oycjx zG5!2x{jy^*{S{l_L%T0j97BC@ZwEG{%vPEXv36JM=AQUMu<7h+YjJa@m+I>u5pRU| zk=cVx{-kq>2kSn#+Z!JEb_(7CcFDJ7df48P3{Cd&|MOmdllB)C^D}w^FLnM2o+ybg zOwSmOEiV4;lzafinGZpx3dAv`j#x}V(UwIQg*qHIWbONzc?9mRc2;OP( zNe`5A#!re750$0w504);IDkcZz1fF4s8_LB)hS+={Tx?32EF}XfgdNnPMND5&m)uX z_b{0MNO`}q*g*YDqTev@0FPvy=RSCn^`CkyDt5Jc;4$%*zWN@@Xf|c59*K{$gix*be&GAFU9{Bp851-D=(!l zcA2#&^g#p*dKQ|h$FvbA;JpbS$B$JS(^v5Q z#D95FOj{v>)Mez47p1a6@Jv#9$Q z2Cv1z@R4(j^X5wDpvQg%e{w{B;ofCN2QQxnyZL2B(Pc~EYv?i}y3juBlNe+PJhcgF zgU3BS=yxIKCui0vj#IuAydROtR-Tcvm`j7#bc-<^*Y7PcCR}EW*!3rjHctOQ2lRk^ zlR@GEdB!?}{`Y#eNy)9c6>| zD>g-aID6FW=rr08UF)=_aj}1zUFiW@K5Fsgu=qv^Pc%>gT&{BUk5XO zg2AlVst)LGzJtTJo)J&z(6!)q&yCr$x<^~!k$=;UBdy;_o!0Ld#P?J?UHR(!M8;3Z z4Km@z6IyA*@Y&{*!-KB^FZVdSP?qU(>wCC1cv!4X(<{-v@V;_1F7(G0z;dU(7ii%4 z9lF5lt{qpXZx_&pe1F!pfw-QF2iX`t{Q3i)@%nobUh+IcKU%Qkn?Q5JF>pIPy0L2b z8yK&*cp77$(CKN&^aG%E)elx#jJx4u{zq>ZK(Jjtp@*x?aP3ght#1I>& z+#?NV`9vGkSIv%p&e}QLq|f7Zyz$IXc_X~)eLI64UzsvYF3Z0MhW||+UhiWEEKYv5 zu{q_)6iR#R1C%CQe=x+D?P|&Top7C{2E8gmHyAlbv`k8 z4{>6}bW3lEU(to|eb7JqJb#9miu_&I|5WTPTpQjJwAZ*6@hs%vBCAg{9V%U`{)LJL z?hhL;seZL#5PB$P9PFIOx1M!lgudO9)xa}~OPcN||7g1~=Fo}^S)on7RBw_8gfr!J z4C3p-C&>2o8XM8u(80w=4VUN%$x*KK7t=}BR|@#xj83DScfvPLf2j?Ey%2pqhQ#8T z^0}jzV9ONa_$hV#*be%}I&<#~Vm-g-yuZDd#8X_5E~qoX2d+&Dz_bhLw z08Ol)=k=ZBK8&3t-sj%IrYD^aE(vzizp853%Zxwc+kDl0aOo4tMDox*^~2CZdUa~O zO*=yQd;F8Ukm#!xC(4=55w2b?p9Nl%ttlgi;Fs9S0e9cmm2K}U#(y64K~}Isq7(kE z@wIQG#c6y!Jo8D~r}D9_M}TY3$n?(7Ye&9&q1Q53ul|%#W%K_#yrJO`E=B zydXXbb|G7Qr~XshEv}%xaNgpj7X0>Nn_pIUW!Xydnc$aO6)%)dqCMh$=UXsNLhj%9 zRWdMMWcE-x*vpOXV~1a-m@oG8O{Oo}fj=LeYi0Bkw;wA182p`5_1jwDDKBdlv&cmj zo1!hw@0JY6c9Z86>rL^n^awV^U%hvGT9 zKF^+2u8sO>>R)D~*Xv%0zu0xtFWM+NDE6luJg({cy{*ba)Q9cXcn1GR)(^r?hq!>n z3#GTR&@aT}e5{e@Pq0|9`hmhj@{liQbc`%FM)7xkE2fI>aCu;@!B4}yEL(qu_$x3m zUMUiW6zu4#@Jb9YRHbIxWXvf{qOFpKq?~1Pvs*s0B zt{n9n{Y6*oLhNgz=dG+D*Nx7yZ}#CVzEut_u?}FQPeggbxonO4+scb_yz@u*j>*K2 zY#bwB^7le{{t5CfQtK-_ll!(~&B0hz;4?jmJgu{`lkkU1rg6qDp6}oWe&L+)Qtnx9 z9{4-miUiNd+ohgIe2!NV{^WHn5aPey^^9ZTV~FlfH=r||?_zm`+-IDsw2#Ilcy5pQ z$Vac>zt>ql;Q{y$_VIOjUV<;&n5FT&@Lz)VrC%y`Bmc!-QLcTCJU#JozI18KKQT>t zO18#)InI(Z9rB@arCY7t;#1LA=N$d>^_8;5=QNgM{3}0EZK^-?yyP7lW3YfrI|um5 zZLi8O{UF*wn?D4vpjXM=*T>)>_r~}uy1DA>H^IAL=VRk^>RaXIqnNGd%x~R8mX@pE z0A8t6_r2eteu_U_A88Eu7=w+zsWL;nI><8jgD>H1KRN^*^m4TcT3=xAfPd<^+2lkv z4Y{}b9!|Ti|I!#7|1($MTll8&dRkw845DrLJ#Of;pg+miH2Qe`6QSSHbL7CwSAd7etMcR3Mo05E?n({a zyQ2fSisz3wxHr0LoX2F$`MKts`Tx;1|3p`uCAjE^?(69!d173s zls9+-J(~jGy0_N#xjSo%rS9Hy#lGN-s1z^dI^kUO3eXIf2-l<3C~} z(N!j+%FhozqAxeJv3Kp*6X2;!Q;f;>3`Toc9nw|gP|=sTS^Xh<)@;{AFq_|z+;C`& zSpMG-^VfvWeCcQLkt?0fy%VN(br=eGFo%*v-3x4WdrQbKMwfc%}yIZ zV`FR5`?Qs~md0BZGcQ>jV5mGI+SqG`6SaHVpPJZmPj>XB)OCmR{ix6PPiVsi>LhQI zx;&pWuhGNfX(Zp#44P{uH>v_e%Ih z(oOV})TgvIOsU7?HkiJ|hP~72FSUWP3zUs6U`*+&sqBWGVsO=tcJvcGBfNr>bB#|0 zv*p@&*ySfl2jX+bIw#rgxZ+7JJZ|aFe$eVr?r`vtWuJ%2*V3M~HM}X>={?ouuAN_D zcmp@+QuN%rkfj zM}?~bV^8E~3cvI(_EaB2`No=m8pj`8fBauwFC<|1{GzgK&P#E<)owiFd3p*kC;i)x zk-0gU;v%1cJ|z=Y4}^Ag2~RohWcmJ4~73vL5^MDS$G!@SbO9zN@rOt|6cmoJBOn26Hixs z4n1~F4|5$>6h`8l&KdK2sARHpf);wr%~d0Zz|9Xi2RYFEdd3Qj2I@C$fiL15fxg`6 zuF&qyDdv{M)fmoO<8?VfFXlLkBhC}zM$7Ywc(1ij&|yiq=<6w`5#5W-e^&ig^w*Wp zN;yR0KlzRt+rNkV*Psv1#}ED{V@&Vt$yHvgSV>LY!et#kYyO|FA9XMxC2n(F z{qg;|xJb}kIRyA+=h`>`aSm%Q{Xpn<4YdBV`d#W9@%to>Z^0M8KoJ$wgnu{1JT5ofL~qWtqo2$wx5=^0D&Bkj+aoTsZ#% zpB9<0!}Eq@W3apleaTWkUhmE(FNp4xuYL{i{XD&1`Oxm8IX%Gr4q*8nx}LF7ct2%ng0gmg|T1pK-Cwt{vx(t z@%lpPH*UTF?+(wBPonmVr+vN%^ZlO%d|%~ob2kNtXcBar^q1sTN6_z*U;Qt6*MSZM zXE#vB4|r~EJm(;R3azQa|KE!3i0Oxz7o1;dyimHOFZ7$eFP-gUkrQ^am1lLuI1sN{juA1y z_f~PxdXwFg;4JiKwGNQvE4Iynwu!UjclvkGMQsfB+|h(JmKZ-^eG861a>({yfR6k( z*W?58yvo-*!i(bH4u8t$c4N`}5AU6(K3Ql<-)hF9H1AsPC2?RhaB&IZI)~Wv!)?lf ze_nG0O}`4Kt}V5E?%EyG3fz;YTly2v9-|zX%ph}PH4fg$7l4DFFYO)am-=(hD}W1#f^G za;Ux(>jh$d)P34S9H5^HygyXlYJB)qCnc|G?wGa=xL%`C%QU zzW&N1v5$9vCvR6{nKin>t8R{XJq`k&4vstmp4S+rv&~5yO758J4E`)v%xIM0YxyVi z3Emqra>xL0{;Bj`oQvk#d|uW z7wZq*mkzh_MRL&MHCYmHOoa-;9qL3+q8HpcMv{drI?;c~X;mIBI#c7}K@PZ<9I!5? zJr>#!UUKWVkY}wiP4CmGkD;=O!^H0--y3$ksj&;{lh3R994@v~e>PnLE?s_3(blF> zp+<`UM2lx%Z%XTM6ryh%UWMC%(i$rP8k^6d@!8mJ%O;AiS-_ulhu8aeX0S3(wP(`U5~0Y2Z)LYeqtZXJ}^j#0nn zpO9M#{C->ynR0UsZt;Bzcm`RK{2)gLPN22Rzq>+XJa*s48+c36tIWl{t@h4^B3n=eHZF9OX$aOZl(D;Npd!@fj zR(QtCh{jV56i;Kul6JRsK5U&A{!!aI)&A+Ux&9q5%Ua*p+RNGw(s4Ehrnbd;(Dak! z0RNRWk@#R2zZJ%9d@;lQU@iCOMdnEbxx#qPbVBISQ?wtMgB)w3v!0LMChz4Xj=n(;$_|;n+W^l!9Q-gIhdd}J(AHL}JU!IPT4Yb> zp7p=Y|J5J9jw=7NexqAQr_sD8^p13o9nSy3PtyEL6-E0`cRZ)qZdB}D<6|5%v5Vpz z=|Zg!DOi~&0WNO#Jnn3P;&;T*WGkpw__yE4!YyuIh2y_o*1s_Lq(l9DlhE#P25)OD zH$3>bdbZhNauh`?*HrMqWTRL^-F#K^8>6^yHIw%HcePem7)SMdR<#47hqZ~EIg8uS ze&;X!Rd9thTWH(araO4HH!-)&o-r8(Pg>JISNRlvvhq4K-|+hr4&`U_A7%ZNc;aer zKQ+JZc<(!y?Wt*huxql5&5rQQ1byM?C*3Vr<9cEpK>KMgaQ=w)-%dM?X25@%Ya8Fn zch5(+n5+m_tXE|JdAX0Do8E=ldf@s($LXJhC+MgwYdASRb2?DAK;sWumy@*_L+lxT z{CarZ)*&?C$@2>H1?|5H?FZMAGtollMh7oy9WLg8Ji&4_B$HaVUu%;(pH%Wy<$s>n z`V7(&8b{lfypD;-#+_wHY#x{I|HisGG(xcOv2lwUKLCCw_DcJ2gfD+?x&a!KgAn&4 zZCxkzV=zX*8bA>LR*NpzbS6N!_6tlGQ)F|p8eWa=#H}} zUv`l7Of1(yIWQsTLG*LHu&(0gxq~m4ORoPsjwU;+pEK_4w#RuAR(N^0hV4fW;B!3` zKD*1&$IWxlyD|L*r|`=UdF!J~2k8($>Cm<0(#3@?vp6lU!7snH z`@&1p{k* zDfZEmb*O+AKJ+tR**jQzFZ}vZ@O><{=ScW6$C&!wS|18~vr8z(;4ypLdi#;7C)xW+ z*nMW}u9s^Y|GTwo%x`q> z#N*K(w^KE#t^YgPeor5A_-P08ENHLkOUbkE|AcjVR4+V;9=MP>G~*!`1sf3dmvDVJ z!G7cKc-mIg#>E+imp<_Q?+Eb#f=;F$MXYu`MCKt2Nz**pI>KJK{|6u<~@Z_fGvM)K6cZc+Z9Q zH+?I7zl*U0@yWAN|ew0FOVXL*x(P z55zIC*{}uma832w!=2iTeqeG>tr#|R0r~ap(YBHE+pM_wzeM4}8T_O966nFxEqB$$ zkuQqSGMaifDw-`m#Iy+t+F9oDULD zk*-iYL47Lrd<%NbVtT$^ErH;lh4=f>g)h-N=z&wbAB--wn1vfJBR?=bu-UCa-%&o8 z0#aX>Ydh=wnx0pj-}=gaox$?=tN9bq+jK$`y4P3d^{8@@>^;Umke%x+2h+w=JPza= z50!7HpZk0(!f4WB9rbm#@>^~2x-C7x9wM6O=-Tdm9J5pMZ(ZIF`}f6bqX*mAN#^8&=Wurl+OR6*(B7->&|k`BdUH^DB@A;!%pF4VGPwfXdT5o>onZpX!f9 zoAoGT^UpxW-T1zX9R}aT`GcYD&fj-^*X+32vKiCF;{d!KWqrzIj--pPxx7X4b7FZA z(8Fg>6A$JR+Fop|v-=`)UcL%`-B_jb2_639HhbQ8Yss3gM=tB}0%68XTj;J@kAw`f%slo@hawJ2%d}PU7^z#}55$orRN-Gwb4M zzmxxExR`96Y{w_2=jpGX0gin?C`sqKYtE;0>ahU+UW0ye|)@*z-bpErG z2l0aP@4RkwcyRqM$%SZx-ok%o{n+B!;OGm?TX+vX+dGJbz7Kz64}&j^-wjswHrzJj z`7j?t_}7{eIiL6K=;IYg$67qGAdds_G5zHufnkH=SK~+TkBC+#moCpn`bu^y;6pgk zq4nx>(Y@w(o2^FH7%#*3VhkI+YD^^PI`O)X&Dp!m!Ay*ycdlPLvBeYbBy60YFY|Te z(CZ+z$@n3`6ZApNhk|F-hUzh{{v~;|=5wMeop0~fS5+TWb0gGlH#Z%ekr#X&b!B6q zyl#qr&)EgEf(Jr%8k1ohLHi-P@fG7w!-d-6`qE}IgPeGNv3QH$BPz5X-D&)y*p++? zwY$I^M6DSf&zGkD1RVk#bAA>K2^wgwhvqq+#K* z+9u*6KR{lzX6(7>(7clo-?~J7bKc%&71rTFk7?~Vi(BN#>Bld}hrJSBMrWztVmbmD zA?HSGx`Z*IKvV4{aijwZs$@1l@QHXJA%Agg zvLo8-Sn&bYc>=e`E7t*h|BQIp-{7mOIkYx`#%IC5(LwuyvHoau6L#wF+&fGB$9rFb zX2Z*|F_wD}#}eY&+=b|3JPBM&pV`BJWTSpO61>X}LbEl<(#N5f=9aN1E%cSml^&Me zl^)e^jg8otfY(7rf39^;x=H`*S-qoob?82O@rgfr7_YW9bql5aEQhIH7sYyDi5EJ< z2wv9BKk_9qSC+XpFNP+cKu#~fMz}tr;^WpQ7tQ3C%72oK$M#phWse*5Dg1re=;V7r zM+DvwU#K3PnX`_sb^(0%NsTFzGq6KVo5dSJpV$1c^kZraK=(TRjvPJ{x*ub9RP>b( zEqa?g8;xa4C!w)){d5oG3wVS0m-AoUT12W}{2;x-TI<-4uV@@2b?Egg<2J@RA-=P8 z(G&wg-u(mEb8!1+;z7TJcCN24JuW&1Iy98$>C(BK_q?t!e~xSMfb0c4As&^T(E2Gg zO|7>VHGgj9Ka#lF>-v?@xSxIxc?#fE`qqs@RAt=tr)v9@LD$#tL_()Ko>3X9Pf3{(zjy{CGTFve?^`t z9_x=!8dHz4JNbp?p)of~d;)K1pURWqi7TPmsPUnfU0@O&oo;VO57azs`o-~q^g)jG zph7;2piJt)$)R4@|Auy|AIDx-^xrJ!6dYST(D1H3g4q*Q{=VfTggvtBTkgi~)6h_B za`f7HI5Ebgc)_Z~Z~dp=1_S!1(FdN;8ZYqLI{4va_~B;b#VRlOex=WuuAy-&dcpL{ z7T3m8o|2I?s?j0R*yfG{_{Dpt`}}?&4luwiQB!+_8dv*2p=03 z-Fl}bCn_uOfnd;Y@lJ?2yL*ktt%>_--Aj1kM0nKY7#h6LI{5x=^MHR>WBS+}?A|5u zSeR()@wCv$J1M-rO{`t=3i4R)vGFg{gV7bp`9~N(d3Mqtn6xa(vG$KU=#K3*S9v9C z=h)c0)?w5biT2Ereb950e#{4P`XKN_;0e83!-Ev0d_{5uu~)y~TwS+m+O`3^A|8^h z!|y;BeBSmZ6wHf>2Rt5$%kO7CI59iq=~QARZ$f8p(~v~qU;8AGgR>ic-Fo!JCy1|n z7rXFVbn?a6;~T);y&M}kM$BewEnXL^y@zM6CqBq}r94k=Gq_c}@Ex-C#w%K@v(ZO% zH{6>Zb~-}8>v(~f#B-_hi|~QVS&;7%e7$z{f$J|AKgkD12fB4PV?EUf&;@eO@J4oLj8_ zpz^0{7c_P*nDL1y@30siKff^kz0fZ3&yc&Ol;cLKWZZCXsFnmvp4|=Qp>W3K~ zI4kZG`sPl)>c-Q}HLhfQe zcaEL<5<128_1u^Nb+rkfcwN3>p?rS4PnPMPBp?gm+UtLVl)XDyKkoqYWd3S#7U^Y& zYbT?fip92t>p&ONA1Nw~1)qMZCeKw3lKh+B$aNhbXf8H7?W}5CLwQl5 zt`^VYO?VQ2`DXH@US>E<=p6gqwO`150#@RCjN>p~E1FBUE0=G&{!+VmZ2x)L4(FTu zIh4i=%%?J%;-lF7n}B<3r`K`ye89XJ-f{D?rqy2bfP9(3@)-WJ%k7@dyyO7uT%1kj zT}4`-20gF5)b6i7vxF z-%9-AgIc#HMt?F-X_b5h_d)-yLl15?{ns`Rn9PLv{O_<54cB$PRj6z}&Yt$_-!bO- zJEt?J!S56B-iEBq)w_tMI{y<>a&2q&98Fqq{FwTd6wSO9+6psb_5?Z#E)EG-TQks z&ZqL*pb4~d{-U!B&Oi8dW{qE~N%RQ1AVDkaBl~?4x6&aS-%|Ur0q_HbT93AXgWH*Pn1c zi8vyA(nX)*-A~$?bzbPpeHA|aagX-xV*fP`_Q{6kp}*(*eD>R9lmE%US zgPvxb2V3_8=ySNOf0xX!ZP-Mwmjq*@BgF0+zROL$!(IW3SN$YXs<0^wa@H{}7Pr`S&s(l_n_elp7O1CBE1loF7ab2Chi21fO z>>&p}*VxU{~uKk$z!K+x_VGe-qfMc>vY?^GWQtbOAIHA874O?a!f4u-P7;skNhzp>J@7 z=^L{-E#S@Ugy*q5Yd31_JH|(YhRpPqhqqZ}01FEBwACqsD#x*6h@x${*;EDP-_qpeO=%KelD{{EpK0RhD0uMK`-DWeOlX$yO z{!7@yBIdWicdSWI``hOM^>-*6UG*IUcUZnahw&72)|$#A6!&m`tHmW`m)ktNeGk4| zfql-HSCu07yNYh$7hX)Zt9R$+N-zXnlK?>bwP6F_kFNTc$!*miZLAN}Czc)Ta^%vP3>6QpDK`P{^e;Uv7|TUQ&osR++txPS#V1}L z3f4i^yBI7T7|VeG90nhO*QO8fEWYV+k%ZCBSvUZlc#hF+GCl}%+F3_OdpQ}Pf%aYx z(062=g^Js+#e9tx8(c&%;Pv)`|1Z~ip7kJ`H+ zdslVdoZ$f6E~JlmAkfa>9$t>$_$KJ!_N|hBYt(;Ke_#FnA?7dU#@WY&kN94nfNRz| zWbDuFRn}6U10E5tza&(<7mYpcGj-3yHF zG4SC6_*Z%Q*)7|1D}V0EmiF@g{Sl`y^qXG4s86i#Gb9gPA3^$b3u32F&Fg+xzv-y zH+xdz&%Fo#!0oN;e8g%$*?54Mnbum%j{mQbr;Z1RZ@B$wTkaEJ2NWM1Ds54|t(BRC z$!@gx<=xLUzc(sniiMfKV<9-sOx`O+c$gtM;e5BX_& z-e6DAUFGS$Ir&%{Pu}Cm;H;LR_=mH*E&5#ID`XBk zaD}bq?Pa*VrVDo*_%`%g|AAd;FSe^biRt^&`F-?lt?qi7vNl$77mNMxk;)>ld0|*KKZ+juHTxLf*S9B@hsITVe&bK@G=LBLb@=`?#Y~hFjIBHtpYd4sH2bjT zS8M+|jt_7g6(9NX(Rv0yi07jdc>hPlkbaMSJ)1u3SLwI+S9Oo-o1>3tse_N@gXG9} zMK++l&xgSW5PqHSc5TY<&tUJwbQpO7eWh{m(4sG3yruXs`SFz}n=E@6T4=SCedcLF ze`FtDRXU|1trNb7@v`$juz9E1JPlQrkb$Okhjy|zo9$B}{HU)W9-i)@G9)u9N7t;o zhJXH12ZN(WQ+Q_MkA(f9|3B=;|9~r&E#d=$lRegL9f_a=0&cRb!>Iq`=j(7^Pao+3 zJ)bT8)SA-@9rU1oj`ffBHQcpK`&jOOo+97V>TH?kkVl?{&;A6>Bo}V4xyi=zr~1#~ zN%Eqj$T*J5-B`1hngKb7Uxp9Idm3XvK5sJ~4SsbsmUp5-WqKRjh$e;~_RTSxwrB^m zb_ViGU9YtATHyi3yX5od$NvcZu~EWb3%n*d%a_i%f0(n`4($aT`wpY;uk&LpRX=&E zx?B2RezEDF;SRmTYv5(IL4OZ4Ewy%mF9%h&YAI zOhzNYh85(^nmb>5! zWQ)UwFY!tkgJu0>^UH1Se0^IR{FV;D7p8yoKUP*FIt`y~eKh8qyxQ%5pjdB%vcf&_ zsCF*7e zSf@0RLlks0c~B1jt=IvtPqNWpY)n-?Uj4_@3EBT>%!7f}^=I+ZpmG4ByH*@nd6*Ry z_&8Z^p#4B`(CFIl*N3lpHo&qq(_ctazktKWS&OH88jM>XEF1mSY=Gti2l{Kh=)p4U zA+_kIDc&$hTsY*5dDCFsSA-CN6Zy%ozvyyo!E22tH&XB_ggLN0NE4KjI|W3oC$PDrTWN0!S`UC z4_~QiejB>PVv(i4ieDTofrgza1(*YeRL@KFuj0aAc^P3Zrsp9)bkL|=SuGH!blt}%V9_+`FyqwUG)d`0WasoyQT-de0C?gv~nK^~!{?bV_< zUUS<|tOwc8MQ$C#dR^{+OV^u!sQ8iAbW4tF#SIJ>OY{$zs6Cq=wLTep5_~Pl68rwR z^wEX>nieN5z@y;qR`M!uhPg6RfwHub63<-Q#Fs^ERnF=v-p z+f1KE{SEu0OXzQ~bm5oBE4jumC$EQfLD0X2%3cPkr@!+3pxuov#q2~!8*488-RTdN zgzSljl!wrW_PR&z3cPo_=EpcJ1-j{VU5igE+XKh-*a++qu{QCG*38hSo?q;yzCtV=Aex=EtNHl~_BLO6Aa>w3e5x_^)eRW01A2xk^0}JG zZ@nFGWA(jZ9y#lBA=}?{@`|0Wp+B;^K-UhKCOp;j(!LGnk(jd z(E-Sso9i*``&94&vD<(8@iWm(y0^*Ud^yTE!)ySu3GL2uaUJYK&3i%j7w@ulSFO2$ zX8Z{HM|&|j{Q>__KG*Q-sSZ1d=O>^m{5Qw)U-g~(X}9CqxZUD;$r3p^Ysdf7K%Q?M zApR2!$P@IrmOk~_F+za35ceL~{BxON*o%$(D zxjP<@$PxNe@dOW}^gmtp8m8A8}xsTbx<=s*6Mw=*TIIZck`nH{pn9n zl$n6Lvewz?9{L;Fz?XD=zVvrnM_Km*esjc(C+ow-`VG8^uJUp6nwOzIr22ls(FB|- zKfOZ#V-GzSXl~c5ob4eN{NXAN1Fp+kR^*E>wC5(3ok0Ifdx;lMJ@zyazD6+D-3#=L zw&3%+Je9ETYN51xqTdsq??U=}T%!LE&t%J=H2mVf*%^6+u77ArhLj6s@r2^n^e(mz z8L6Ya@_XF+7k33ak?`Z3-v{r(L+4lP)H{}IGZxQvp44^*`hU;Kzxs~NB|lC&%DmWM!m}HUi_@e>|*){+dH)R!Qkl==6}fsdszhbPfoOFjqpSpmA7Z{ zq{@mW^!IZ~N*7oAe4buDmrwaLj$R>lK)G(6403HZ`nB==JI169zC3?0Kk&QO56d&= zX=6~>5uL>k(>bbT6g_|58vC$8jta$RJNSd`>UR1Ie-5AM-PgMD0B(7HW^8Aj`QcN> zf}xX>?F}idabfEr)$4z7uko8)=|_%g2|9Xy!=C1A>a(0cizD1#T`M*5o-3&KJJ2Gx z`8k>&Z?p>iaK(^xXw12tBfO__bUjoaB~HHDDi*(ao>P2+wljtr_;(@DKY_jwSMlx1 zu%;1xIr^|ker_WFJ|_|yz6cyN^8cJ4A7aJBzOSk31I@Cmx23p={0Zq$$*I#Tm!(c{ z@#(;?38))gVoy@3ZHaMEj`r<31im|zW02o#Xw&|j-`etlU8jFLNZZ&$qLiHwhYIv( zjaFc;%X#Q#{oe?6y60u=Aojt|NB20J7TVE>{s~%u{^p>zKGM zc!2jV@-YV=H*h)woc<|(F2L3d^l;zPB>e37Imn6%4)W4J{v_f&E?ybi{eZVF^#7V7 z#nYd)&7l7ctc6B zQ*^oZ-m{_wqds1Cv_}s#2UkhI!P6fY>{<8KI{gzbP5aWr*UJ0Ym+zSj)Zi-cK_mJH zJhxK%G@oe${+b#*ec}$oIM$!e<@gFA-_y`(%w0Z*lC5wFIPfg$% z;ZgopzVwGGY;%CKxH4Z?cEe8cYkHN>uKW}BX|cI~+ILC^bCndIoM`hTFO$*VRiu5( z(3$%p8?V!xq802XskP$TI>^K8%~l?dpYj6b)1~v3=WV-R#l`gE_Ox*!y~l@keV|49 zhd$u>y^4bx-JqY-4NeCV6Jk$`+8CIxhuG=c>UbvU|6PQh@NwTcqQ93L;O}SsoZGC= zZ+%hCm$ZF;*!zk7!IW1iAIb5lc(v|mK!4+T+CgqEzUj~48}oC$omC%Idy#E!>u~S5 zy>2bxZ}2#njQ-kJSL>t( z-(O{Wn*pA0F*!5#ZL=M1uzdVzpuKWpHC`#d#pGD+piRQB+Nwiy46W}gzNn{t6Z!Y? zLVV#N+ru!#BUFdtrYdh*hx(lX2FfzHOXJ#C%C)Tx+y(k)i47+EWO4bSo^TfIz|EL) zpVpqsMa(VLZ{bV_&q@D>IRioe`+B&4>;(E7J}5(Tt>0GJsqHqz1a0#o#6Jq9w_Dq7 zEH}|k!6n`dM`&L|+$7qrJ;C$|@nSbOQu>4#RbBa3rrw>-p|b2f#?H4?G;hM{HN9h3 zQ-1~eGd4WYrwOpupH1FJv~MPPdtu(6qrKXPyz~sz%^webMvi=x34BHy91o55Wh;+1 zpr`B4Ox@z@6Ta=x_apG6=sbn)tH(qAZ`z@@XQMrR8!V3j8hvJBo9o}5$#3sJ6Ib)~ zdK+48YSXJgf9;#;d^vB+Cxgf6UsRds>~GYz&Bb!!ai*&LyS$lPbYkuEsz*GmGFu)0 zY1*3h{s{EXM#mWa^Q^BI%jab6Z}OjyW3dj#n+*^@UPzwQCnmPJNg30t-_5Vdj~`(5 z`gXO1jOo&!_=n2J@2Ioq-j~mn)>{3myg%pV8GdO%f8&8LUt!SRYo`tN!Zg0BZFg%^ z+a4q{po{DT`H(&NvDX+hp8ohr$IUAJtzPs_e_fxu6_f-V6iNrTCI6oOvh&1BW3*<-nE?+lC+8N?RK}<9Wc~(wtNE?}D$d zcE$(yLq9T;G~?#=4&Vg+|}F<)*jy3N+2H6+df{R4c|M?AB@ni{9tS{C`zMr4_J^tP_l zDDB5KPw_b|l+qliFLD%*4hyRaiiT>W!(C#a> zH?PRloAb8A=}tfXANl!WdLlvp5dUfBYg_D~;$la0(FYKC^&hP(f6#^p^c6-QrF@jW zb(Ke^*0X1y)=1V`B$MmcYTSAO$v?K^qZ9k(K{gX-@8cp1=Q#NXd4;hj!24Qj(8cnk zCzS^+eAx&3uX4JO=lY$lXcR~BV~2S^cIfd}Po7!(ExNWfmSTKe9gC^wi-kaczrJ!} zOk?ssu;}mNO>PYrr|XCx`*m2{mZ=i zlMp{tT-&W1K`w z&hKhWlO%nr^k*K>yB#5r4L%kN&a>{2>l+l!m&iw-wr5QCJ+ILpMAuw7A#Wh)0r`;f zsYCq0++vaj`{)XZ9Yd)^hHNpZaEY7 z81(o2#=POvjmFMa)b=wy{TVa)vAyQY4pl9vbGOrfmDk-N_SGhT z;!N~+Z?JWRYVePIOSfl>7x{5iTZ2mZl%D?NW1VgB5b_uH^mC5TH9@B&>+?QFh#~s- zYN}fZKf0R!!S}1k_q)RE@luy(;xXCHeZ2!h+W`0 z)+oZxH==*&V`SNv$d92;sBa7e8@m4*=G0 zfuCHC12MA{c~y=FM1RM3#0Px7VUzVolvhGOlepCD>|GD<#Cp5&=zr#a7JP1BJ?aBbu^)%|cUkyFu>$r3ierEQCsW27Esnf}waU)2YC>P7zRqs` z9opKB_CNN;{W?&MeDTmHvorKpT#p>Mntk|c@b7g1IvbmE33EWV$QIDw`=LGNd_c1S z=!a)8-tTkA5;`VXXAZptzfH&`tf{k$f6NvA!}xDDdZS@uDqR`+RGNoZu=QXk>GOHo zsBfS#uYBoe7WWNh)T7_mExqmhK$}CkX~i19eq?}6zG6PQ*y#siib=YM>riK|^h>Lz zS)KJTbj$Bmu78Eh%=Y-di^=B3YjZA|;UU)3Wj8L}%9w8)(<`|(M8dd!qdGjDu<^ez z`bq}`UtTg1+kDDA~DDe1gj1K32 zSzpb?eBC_RDeF{zRw+uc_KmN@`SOh_@$}bTCFF)|h6l>nfPbV8(jGt5AMm1iR0VRb zG;Y+WA7C(fy`%A_RmMBb>um(feBKM~boqZB$S-VSCndkc-RNt--P2~0{6BA_o&G)F z{4~vFos|C2z>WEw7yS3?xe0{flHZ1j4=&s4cn9{0p*p3DA%H+mir zTuuA{`Ud2-z+Y}{vzFp9@R{nNKL~G~XLIy(rQg~;`JNs(@m`_o^Ecx`8*?tRA4%zk z)LZY}(aP{`u=aNAR~66C#}^*6zO#HN9U9~CIYZMr>-|s``_$oYU)9#SW8VMoTRr+H z@cI)gtAKvd-Y*mAV7i~Z0Q^2k+EbvFPiQ}L^lASn>rj?@3ZpNxS3UiWKWse6#{C-9 z9dsTtW8*^63HJU%(E&c*p*4Z%KYbP%`Z>1nOmO}~{NJx2|DWdgRGY`=Dd#(s@e9sB z>M4|u?{Hwh{2h6=4VuiTz6SL;JjZdj3B||%W2@H|U%)=9 zmzX}1&NZDJ`H<#WQ-zv5e=2S|5V{hwj}G+jEn}45zdM=(>D# zHU8@kHlFBp%w!OB#ec$q$%3bm^l!K~opao7%8?w!ze8*7eyy);^@soK&f!1p9jv`? z57l^| zw5OjjV%ytie1H2Z=m6vy+OiI`&BL0+50F3LY!WuLI<^?<+3L@p_TZIuYCnq|A3@&6 zStIHE-fYRu4-}4?(vtVXf1ESxN^8^W{F;2>{&aEdWgR+y%yf$OeN~yp1L`ZbxOce4 zJ8hq{F?@#>)(f02zzgj?Pdo4_|I+#!ZeCryuXTDb`U?2o##%NRJrla1b65aAE7#WT zJr6tnbq|NLU*g$Z)Y^WI)+fY96-qay`pM7MXI5agk{7@ixUI^U%ja-wQ>#y}*puwI z*-n0+1rN9}*-3Hc=`|Ujw2PTgmhwPzrJLiJMbH7P8-d+npTJi3t@n1I5S>py^1cR2 zD8F5}7W(tU-jB+Ty{j6}k*%ydd>kLS`CDpm(=OKRNqJxv5aa7(j`+*aQ`fFsvpMVm z?P7-6=u5s2lIVxlwIj4k?NJ;>IpelZtk;3F0Csm4NqfC-mClY1na{4hznaGKz@z5@ z)?zuIIT{Z4Q^gEB-FUC7h+nW?U-WG&UVNc`(b~%Wf&V(&ueM=4s*V?wm!v*HzIdwh zCAJbCkgoIk4mmp9_182Pw^VHlD!_&`Vort zUDRzBs;RJK);n^#C~v2izU}0kbKy-XlB zlR*1$Ry;-Xorg*ri6Oi3qj~qCJP)WpkY{aj7x$k$E>vxg_XkSn^c^s|t5GH3YYs^? zD^*!LBs27tZS1amk@J0^VQBw!&VdJ*mldzSGpF;e)F#)i7;k|Gi92H3*r#l&@u7r% zlWlW)?P~lBHwRI;3uVTYERc&3EB4@$5BE;;;zrHhi+0nYdHp-SKh1pSS6MaPATuFm zs5zrU+7pz1p{>o4pmlva;@@7c6-va2N`;ux-O#QWn5iz1iB+y&rzhRospa3AKD4>k zjmBzQ!A;#Wj`oTX+?YDp+s%4;oC?(0`rrG5%o#hdrM^u}f7+;aX-r$XARm2aW&h@f z8ko`pU8@VAxp*|er&=GBFe->I`ZZ2{K7hsBTecxSIBbk}?E&u?|@mfUto z-v@q>t#orbK4-0|hoOT_oPJSWCMW<}9#i#sqKWaX(60J(@NpQ^l+UbnJz{uJTxkJkCq+(X3-2g{e?BkZ4I4U8BH@j+51<3Bol z5!#}4yw{cPGQH+xT{g?FLl)@W;d;UfmE_t)d|+`o;m!X1j!l)`%Do3{)fq8PI=g~A?ty6RCX-)A2&#h z2L;PQ`WW6U2Nj>SpT6+8=}hgt+Em{K9Mqj<4|Ir+T+HAqejmlY#ntr<0&0eXUg!kA zd>PsUIbXU3eI(knr^ni9tJx6e2P5|%u_i4@+6Q8A_P#3Xp6<@t{WqIEm~K48_<;U& zu)TSU8=MYp)}-RtbfRuus($f=J9`}}x;8rM-VN__M*i#8P_E;b7(X$aQrn+bEp(C|^iTopU0&;mbb5(zXy?GnQ zf5l@90ftu2-qzPV8sb~Iu{XG}PFqK}^*+s1ZL$CHBgE5#rHJ@Z&gwV0>4XleY0doo z@OnVDl=bC4ZMs}-mJihujvC3Ex0SM&^2x*p$}>QYkk>niEB}h}KSf{iojuvHJ#C$v zp~{YoJ?^0WK2rPLye<1kZEyR>XfLUTA5R1Rr>uV&ou-iGa-g_{Za>SZ1jZNA#*rFA4`j8o4GfuEB6Af9mjG_4VPC;hh@iP^B%x~&s? z19jg@-0700d5WHQ$%A^ogLfJ}%tYF|_@HQ?FQ3)7cJwiZ@gOI&fd1|%5_$4%T*=*L!Z2+1lbGmfezJaI+wHVE54SG&a=LR@9)R>3Vh3YTG$x& zl$>lXY$H9DyeFB5f2>bbDibsQ0KN!*370XT&r1sD}dp=u}%*V*AyGY<*Wq;+Nig{rZ?~L_<{JRWT~}W!z686x;FV&zu~%+)|R)Ar@8wP%PResN!E%ny1RXcjqa?Cl&}0K z&T&y$##?n|hdQ?Qvur|A_SpA2JxKm>0slb0fpF@41ljKpfA5U1U~5@1_ccHI3L`@j z4&4a0%GPAEeW=8b?lAoXJOM`bq`;P)LLSg=F-i4)C?mePbkPt4!c!9e!N z!io5Wx10Wr`*rAc=PUUBgJ?2^zHjuuw*mC?jx(6*eE{7w`YlUoE`!-8o4@1YcJ6oS zo=i@8AeAS_?MnJN@y(@+)-ZrRsHX#S4>V7b_`%n(sn?mzi5H|B)TgsGOGSVDhTuE6 z*!mQF!Sn01Mo)A>mG|oOwAbC}^XJ%JwAPO+myq?peK5m+fv-fKGHinu z@P%T1_Y!{@=Wx2f*$w?3;lGVMhyIT4pda`Q1GX6*8{Nl7bsODn!j49ci7+io?dtKL zPW^HFlpX%Y(y2=^klF99bUHi!Q26ud@a4-n_J)twa~#9yIscgB1g<~GF~s%Wl=o_$eGW44@V+&h zT%4jy@v{Wh5(XAJ23D-OcX@AadM zX4+?!uX+@_QoYR0h_2>175(r&bkQEb^(gewPU+m{CEc*lnQsaHImN)THJg@UqtltS zo3}xRUWvW^9LJ@|+07iX+4M)c{?J;qcW~fuMC4c$4@YM`jq5&gZ(3V-a!#?nRM`>+ z<`)CY`#0U6Jy{Mw*1v(>r1sSVPJkR zkjZYm4|1?OHj#dHN%p;wye#^A=mB(H^dWNVUYZ#gA?mdBS;D~L!~n9sBQdk*(oa4K zc^~0uHAiUC>j968apI4^VXWrO=!J(aJ8w&RJ^cLdF2``iCO>$0P6m+U!!mOG??X)QSY-aT zC3`<>_P&)q2jhy-56CfiII)7InBmqWi1X0a^yH zAoGhcCvP$9En+duk;oNa(%V0_Vz&8YsrqUKpwI9EE0{>Bf0Rp#P$w_XRha%CAp6_Q}#K?=$DY|b(~*? z{5~IB^>}gzcJ5t0>h^c;05&oMW7|>YF4Xf_^uu$g=atlx<@YDJ{yFoFG;T3V?d+;@ zrT2c{lO2Cn2hh4y#3IB1>o>j%S?qK^zU=T1DEBC2?hQQyV{Q$}F2W!Fv0|Dp#Mc-E zPR%(y8<>@^xJdOvbW%Dy_NXo*!4fF*hympCVetPCJHYSiD_oBMeG0nd4Vi(B+wprj z*9$6F1^+j6IQtvWm8=V7u?4LGve5Ox&9rOJ zp6uvSY+xQ`ajvUQXGb20?EY?A{F`IF*?jSB98c{jY+RJ`J{3Kd=SH?c4?N5CXD&K- zT6pXp-J#gPnVEs&QlDTd!Y_jV;o1G6_r^AOR(Af^*!hE5qy1rv00kXyLMA`{2yk>T z$8W%^V(tsjFVLFdjPd$kF#1F~H};@( zZnVk1f1QVKF_tL&rL&{pG5QdARcv%2`30lcijSesw#xq9V&X^m@R9J|bsUX-Qr3Bg zM?Vj}pT9i2dG6oPOld%wu^n=f^Qd^@zUg*v_fd_imTk|E>ki zhpb(^J^1}^#zub9ln%}4Gr#vQCV%27i%A}~rnbnBqu<_Su8zg64qBcc*`bp-$*kS9 z6Mc0zpOg2RfFiU z*CJ=nB)-|T^J3Ej+AEd(Beza)W1ZYSUS_i#)+Q?+np(SYsSnsmL7C;Ukp0Ia7xi&I z@>-%F_YRCdJ)OIEM)V}+wG%lrPh!97S0H!qho8@+Z27s0C+nC^o}8@gP=677`*&bE z5nK!atNthj?`rB>5Bir->=v!{=u`%woAo#00YGMAA;-$ zyRW_fZb63+kk_(9D&M{yXZ~L=ym}wTzmcKnGaSl?k*q6DIG?iKj%>)^Q10amsZ_C?|&`1;rI)k_$!ZhH>cw$K>fd?EJf1j=H}pZ(vunm4B{H;ivue&6rF z*D>(-^P8Y{R&OuQZp?u17ehPsk>*&g+p4b!jK|I@GL{OTHU^OQW8ve^akM>#(=#wC zTeuH0&s@5#dW{eA8cXE)72ifIP4#bTYyKST^6zMSP%zIHev^;f&9-NrU}KNaT=X^U-b=}an3LFIUw&*E`selN z0b-q9N|Ri4D?W?c54iK(yj0>WVW4-Qm`M+eJOrNpEWD-u@RHoOCS$^r*5v*he7`Tp z{fMQ`YVJHSiaugDucgiq&zUD3*i;9NF#qE%>He{KiJ#1ph%C}#KS7^9`d@6ydGK+U z$C{e*TQ~8)Vj}0F2M%SNye+@0T?Uh`;b7i{?LFL0I1nsbuqB^Kz2)CaI-p&`&cXBP z{H7g{`*$Gs*FdXo4g?ibAd*^5LMu*Oj?`Y3+;)0VBhb5?93afp{g+snI853sLkZtS70@aNJq zvx9;3z$kNeqPN2vAs=Uv$kl@LUC5n_{*E3{j?}iDrZzNQ!Eq#X9_cbYaC6_l_@g@A z-lg)U#sGc99K0}#>-Q}<8SEC_cdh!!EAnGIO-;r2oMkbA}Tx`p4D;Al(><?Yy5sm<`ymvw2mM#g7MwZ1sxeTpbyxKAMkFjC)q?d`(9H9wCnkPg?p z&?SGr3;1`AWoR7Qe^NX!6B%Ibj%D})j|KK_7#rS#K02H>b)=s&6TEc$yS@G6kAr4f z8)Dw|_7V*9N(0t#i3-dMoY7bT^O<*|oS%0AEw$eG`fiuM`Gk)c-)o>#SNLpQz%NVs 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 PV}uPg(qMmg7J>f-&1C?n literal 0 HcmV?d00001 diff --git a/release_note.txt b/release_note.txt index af077c5..d69ce43 100644 --- a/release_note.txt +++ b/release_note.txt @@ -7,6 +7,7 @@ version 0.2.3 wrapper (run.bat). - Resolved python_bin / lua_bin are now published into the global dict, so test scripts can read them via $(python_bin) / $(lua_bin). +- Windows: new per-user installer (no admin). version 0.2.2 ============== diff --git a/src/testium/interpreter/utils/bins.py b/src/testium/interpreter/utils/bins.py index 796bf77..78af2f7 100644 --- a/src/testium/interpreter/utils/bins.py +++ b/src/testium/interpreter/utils/bins.py @@ -25,7 +25,7 @@ import subprocess import tempfile import api.testium as tm -from interpreter.utils.paths import sys_app_path_lin, sys_app_path_win +from interpreter.utils.paths import sys_app_path_lin, sys_app_path_win, no_window_kwargs from runtime.tum_except import ETUMRuntimeError @@ -272,6 +272,7 @@ def _run_probe(cmd): r = subprocess.run( cmd, capture_output=True, text=True, encoding=tm.sys_encoding(), timeout=10, env=_probe_env(), + **no_window_kwargs(), ) except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired): return None diff --git a/src/testium/interpreter/utils/lua_process.py b/src/testium/interpreter/utils/lua_process.py index 59185f0..78d2e65 100644 --- a/src/testium/interpreter/utils/lua_process.py +++ b/src/testium/interpreter/utils/lua_process.py @@ -4,7 +4,7 @@ import subprocess import api.testium as tm from runtime.jrpc import JsonRpcClient -from interpreter.utils.paths import subproc_path +from interpreter.utils.paths import subproc_path, no_window_kwargs from runtime.tum_except import ETUMRuntimeError from interpreter.utils import bins from interpreter.utils.proc_drain import drain_and_read_port, wait_for_port @@ -114,6 +114,7 @@ class LuaProcessBase: stdout=subprocess.PIPE, stderr=subprocess.PIPE, restore_signals=False, + **no_window_kwargs(), **popen_kwargs, ) # Forward subprocess output to the log and read the startup port sentinel. diff --git a/src/testium/interpreter/utils/paths.py b/src/testium/interpreter/utils/paths.py index 719788a..e76e7ea 100644 --- a/src/testium/interpreter/utils/paths.py +++ b/src/testium/interpreter/utils/paths.py @@ -8,6 +8,14 @@ import subprocess import api.testium as tm +def no_window_kwargs(): + # Hide stray child consoles in the frozen Windows GUI exe (console=False has + # no console to inherit). The wheel/source keeps its console, so leave it. + if sys.platform == "win32" and getattr(sys, "frozen", False): + return {"creationflags": subprocess.CREATE_NO_WINDOW} + return {} + + def testium_path(): if getattr(sys, 'frozen', False): @@ -54,6 +62,7 @@ def sys_app_path_win(app_name): text=True, encoding="oem", timeout=10, + **no_window_kwargs(), ) data = result.stdout except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired): diff --git a/src/testium/interpreter/utils/py_process.py b/src/testium/interpreter/utils/py_process.py index 2991f10..b673bd5 100644 --- a/src/testium/interpreter/utils/py_process.py +++ b/src/testium/interpreter/utils/py_process.py @@ -4,7 +4,7 @@ import subprocess from runtime.jrpc import JsonRpcClient import api.testium as tm from runtime.tum_except import ETUMRuntimeError -from interpreter.utils.paths import testium_path, subproc_path +from interpreter.utils.paths import testium_path, subproc_path, no_window_kwargs from interpreter.utils import bins from interpreter.utils.proc_drain import drain_and_read_port, wait_for_port @@ -97,6 +97,7 @@ class PyProcessBase: stdout=subprocess.PIPE, stderr=subprocess.PIPE, restore_signals=False, + **no_window_kwargs(), **popen_kwargs, ) # Forward subprocess output to the log and read the startup port sentinel.