Skip to content

Commit 6349254

Browse files
committed
Add publish-free Windows MSIX build and acceptance evidence
1 parent d71238e commit 6349254

2 files changed

Lines changed: 178 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Build and validate without releases, tags, Store uploads or signing changes.
2+
# Read-only repository permissions keep this distinct from windows-msix.yml.
3+
name: Windows MSIX validation (artifact only)
4+
on:
5+
workflow_dispatch:
6+
pull_request:
7+
branches: [main]
8+
paths:
9+
- ".github/workflows/windows-artifact.yml"
10+
permissions:
11+
contents: read
12+
jobs:
13+
msix:
14+
runs-on: windows-latest
15+
timeout-minutes: 90
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.13"
21+
- name: Install build dependencies
22+
run: python -m pip install -r requirements.txt pyinstaller pytest pyflakes
23+
- name: Windows offline tests and real ConPTY
24+
shell: pwsh
25+
env:
26+
QT_QPA_PLATFORM: offscreen
27+
MEDHA_REAL_CONPTY_TEST: "1"
28+
run: |
29+
python -m pyflakes medha
30+
if ($LASTEXITCODE -ne 0) { throw "Lint failed" }
31+
python -m pytest -q -rs
32+
if ($LASTEXITCODE -ne 0) { throw "Windows tests failed" }
33+
- name: Verify source GUI startup
34+
shell: pwsh
35+
run: |
36+
$env:MEDHA_SMOKE_RESULT = Join-Path $env:RUNNER_TEMP "source-gui.json"
37+
python -m medha --smoke-gui
38+
if ($LASTEXITCODE -ne 0) { throw "Source GUI startup failed" }
39+
- name: Download offline toolchains (GCC, JDK 25, Python) + checksums
40+
shell: pwsh
41+
env:
42+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43+
run: packaging\windows\fetch_toolchains.ps1
44+
- name: Build MSIX (includes MEDHA.exe --selftest with bundled GCC)
45+
shell: pwsh
46+
run: packaging\windows\build_msix.ps1
47+
- name: Source Windows interactive acceptance with bundled GCC
48+
shell: pwsh
49+
run: |
50+
$env:PATH = "$pwd/toolchains/mingw64/bin;" + $env:PATH
51+
python tools/windows_acceptance.py
52+
if ($LASTEXITCODE -ne 0) { throw "Windows acceptance failed" }
53+
- name: Collect startup log
54+
if: always()
55+
shell: pwsh
56+
run: |
57+
New-Item -ItemType Directory -Force dist | Out-Null
58+
$log = Join-Path $env:LOCALAPPDATA "MEDHA\startup.log"
59+
if (Test-Path $log) { Copy-Item $log dist/startup.log }
60+
- uses: actions/upload-artifact@v4
61+
if: always()
62+
with:
63+
name: medha-windows-validation
64+
path: |
65+
dist/*.msix
66+
dist/*.zip
67+
dist/gui-smoke.*
68+
dist/startup.log
69+
dist/acceptance/
70+
toolchains/MANIFEST.json

‎tools/windows_acceptance.py‎

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""Real Windows source-app acceptance with bundled compilers; never publishes.
2+
3+
This complements the frozen GUI/selftest in build_msix.ps1. It does not claim
4+
MSIX installation or manual desktop acceptance. Evidence is saved in dist/.
5+
"""
6+
import json
7+
from pathlib import Path
8+
import shutil
9+
import sys
10+
import tempfile
11+
import time
12+
13+
ROOT = Path(__file__).resolve().parents[1]
14+
sys.path.insert(0, str(ROOT))
15+
16+
17+
def main():
18+
assert sys.platform == 'win32', 'Run on Windows to validate ConPTY'
19+
from PySide6.QtCore import Qt
20+
from PySide6.QtTest import QTest
21+
from PySide6.QtWidgets import QApplication
22+
from medha import settings
23+
from medha.app import MedhaMainWindow
24+
25+
output = ROOT / 'dist' / 'acceptance'
26+
output.mkdir(parents=True, exist_ok=True)
27+
report = {'scope': 'Windows source GUI with bundled toolchains', 'passed': [],
28+
'pending': ['MSIX install/uninstall', 'installed-package interactive acceptance',
29+
'extension install/uninstall', 'manual Windows desktop review']}
30+
app = QApplication([])
31+
32+
def wait(predicate, timeout=60):
33+
deadline = time.monotonic() + timeout
34+
while time.monotonic() < deadline:
35+
app.processEvents()
36+
if predicate():
37+
return
38+
QTest.qWait(30)
39+
raise AssertionError('Acceptance check timed out')
40+
41+
# Temporary settings and source files avoid changing user work or Store assets.
42+
with tempfile.TemporaryDirectory(prefix='medha-acceptance-') as folder:
43+
project = Path(folder)
44+
settings.settings_path = lambda: str(project / 'settings.json')
45+
window = MedhaMainWindow(str(project))
46+
window.resize(1920, 1080)
47+
window.show()
48+
try:
49+
terminal = window.terminal
50+
assert type(terminal._pty).__name__ == '_WindowsConPtyProcess'
51+
wait(lambda: terminal._pty._shell_started)
52+
# Clearing before each command distinguishes output from old results.
53+
terminal.clear()
54+
terminal.execute_command('dir')
55+
wait(lambda: 'Directory of' in terminal.output_edit.toPlainText())
56+
report['passed'].append('ConPTY prompt and dir')
57+
terminal.clear()
58+
terminal.execute_command('ping -t 127.0.0.1')
59+
wait(lambda: 'TTL=' in terminal.output_edit.toPlainText())
60+
QTest.keyClick(terminal.input_line, Qt.Key.Key_C, Qt.KeyboardModifier.ControlModifier)
61+
wait(lambda: 'Ping statistics' in terminal.output_edit.toPlainText())
62+
terminal.clear()
63+
terminal.execute_command('dir')
64+
wait(lambda: 'Directory of' in terminal.output_edit.toPlainText())
65+
report['passed'].append('ping Ctrl+C and shell survives')
66+
67+
source = project / 'scanf.c'
68+
source.write_text('#include <stdio.h>\nint main(void){int n; if(scanf("%d",&n)!=1)return 2; printf("ANSWER=%d\\n",n*2); return 0;}\n')
69+
window.open_file_path(str(source))
70+
captured = []
71+
codes = []
72+
window.runner.output.connect(captured.append)
73+
window.runner.finished.connect(lambda code, diagnostics: codes.append(code))
74+
window.runner.run_file(str(source))
75+
wait(lambda: window.runner.stage == 'run')
76+
terminal.input_line.setText('21')
77+
terminal.send_input()
78+
wait(lambda: bool(codes))
79+
assert codes[-1] == 0 and 'ANSWER=42' in ''.join(captured)
80+
report['passed'].append('C build/run and scanf through terminal input')
81+
82+
turbo = project / 'shapes.c'
83+
shutil.copyfile(ROOT / 'examples/turboc/02_lines_and_shapes.c', turbo)
84+
window.open_file_path(str(turbo))
85+
window.runner.run_file(str(turbo))
86+
wait(lambda: window.runner._decoder is not None and window.runner._decoder.frames > 0)
87+
window.dock_turbo.show()
88+
QTest.qWait(300)
89+
assert window.grab().save(str(output / 'turbo-windows.png'))
90+
window.runner.write_stdin('\n')
91+
wait(lambda: window.runner.stage == 'idle')
92+
assert codes[-1] == 0
93+
report['passed'].append('Turbo C graphics produced a decoded framebuffer')
94+
report['status'] = 'PASS'
95+
except Exception:
96+
import traceback
97+
report['status'] = 'FAIL'
98+
report['error'] = traceback.format_exc()
99+
raise
100+
finally:
101+
(output / 'results.json').write_text(json.dumps(report, indent=2), encoding='utf-8')
102+
window.close()
103+
app.processEvents()
104+
return 0
105+
106+
107+
if __name__ == '__main__':
108+
sys.exit(main())

0 commit comments

Comments
 (0)