Commit ed47917
authored
feat(windows): ship MSIX instead of appx; add headless smoke test (#451)
* windows: ship MSIX instead of appx; add headless smoke test
- installer/appxmanifest.xml: ProcessorArchitecture amd64 (was x86, wrong
for the 64-bit PyInstaller build), newer Windows 10 min/max baseline,
updated comments to reflect MSIX.
- install-windows.ps1: emit friture-$version.msix via MakeAppx (was .appx),
save the manifest as AppxManifest.xml (MSIX canonical name).
- build.yml: add 'Verify app launches' smoke-test step for Windows
(frozen dist/friture/friture.exe, offscreen Qt, full-init assertion via
the cross-platform smoke_test.py), a platformdirs-based log capture/upload,
switch the artifact/upload from .appx to .msix, and add MSIX to the Store
release files. (Store ingestion signs the package; CI builds an unsigned
MSIX for packaging validation only.)
* smoke_test: cross-platform timeout kill (Windows needs proc.kill fallback)
os.killpg/signal.SIGKILL are POSIX-only; on Windows they raise
AttributeError. Extract _kill_proc() that tries the process-group kill on
POSIX and falls back to proc.kill() otherwise, so the Windows smoke test
can terminate a hung release build instead of crashing the harness.
* appxmanifest: ProcessorArchitecture x64 (MSIX schema forbids 'amd64')
* audiobackend: tolerate no default output device at startup
sounddevice.query_devices(kind='output') raises PortAudioError ('Error
querying device -1') on a headless host with no default output device,
which crashed AudioBackend() -> Friture() before init completed. Mirror
the existing guard in get_input_devices() so a missing default output
device degrades to an empty device list instead of raising.
* audiobackend, settings: avoid startup crash with no audio device (headless)
On a host with no default input/output device (e.g. the Windows CI
runner, unlike Linux which loads a PulseAudio monitor source), the
frozen app could not start:
* get_readable_devices_list() / get_readable_output_devices_list()
called sounddevice.query_devices(kind=...) unconditionally and, when
it raised PortAudioError, logged via logger.exception() -- emitting a
'Traceback' line that trips the smoke test's FATAL_MARKERS. Guard the
kind= calls and log at debug instead; mirror get_input_devices()'s
early return when there are no devices.
* SettingsDialog popped a blocking QMessageBox.critical() + sys.exit(1)
on the empty device list. Under the offscreen Qt platform (the CI
smoke test / any headless host) that modal blocks forever with no
user to dismiss it, preventing init from reaching 'Init finished' and
before any QML is shown. In headless mode, log a warning and continue
with an empty device set instead; interactive desktop users keep the
existing message+exit behaviour.
Neither change affects machines that have a real audio input device.
* settings: use QApplication.instance().platformName() (PyQt6 regressed the static call)
PyQt6 moved QGuiApplication.platformName() from a static method to an
instance method, so QtCore.QCoreApplication.platformName() raised
AttributeError during SettingsDialog construction on a headless Windows
runner, crashing Friture() before full init. Use the existing
QApplication.instance() pattern (already used elsewhere in the codebase)
to query the offscreen platform.
* settings: don't crash on a stale/unknown saved themePreference at startup
SettingsDialog.restoreState() did themeButtonGroup.button(id).setChecked(True)
with the id read back from QSettings (AudioBackend group). On a system whose
stored value is absent, stale, or outside {0,1,2}, button() returns None and
QML/.setChecked raises AttributeError: 'NoneType' object has no attribute
'setChecked' -- crashing Friture() during restoreAppState() before
'Init finished'.
This reproduces on a fresh Windows CI runner with no audio (unlike Linux,
whose PulseAudio monitor source provides a default input): the headless
no-device path runs SettingsDialog.__init__ (which returns early, leaving
the comboBox empty) and restoreState is still invoked from the analyzer.
Guard the button(id) lookups (both theme and input-type groups): if no
button carries the stored id, fall back to the System (0) button instead
of crashing. Real interactive users see no behaviour change (their stored
0/1/2 still maps to a button); only a bad/sentinel id is rescued.
* settings: assign theme-button IDs before the no-audio early return
Root cause of the Windows headless startup crash (now visible in the
smoke log as 'No theme button for id 0'):
SettingsDialog.__init__ early-returns in the no-input-device / offscreen
branch, which on a headless Windows runner is the common path (unlike
Linux, which loads a PulseAudio monitor source as its default input).
That early return happened BEFORE the themeButtonGroup.setId(...) calls,
so on the no-device path the button group had no IDs -- restoreState()'s
button(0) returned None, and .setChecked() raised AttributeError.
Move the idToggled connect + the three setId(0/1/2) calls to right after
setupUi(), before the device-list early return, so the button group is
always populated. The defensive None-guard in restoreState() stays as
defense-in-depth. No behaviour change for interactive desktop users
(their button IDs were already set).
* Cleanup
* Refine comment
* Log PortAudio error details
* Do not log the stacktrace to avoid confusing the smoke test1 parent c18356d commit ed47917
6 files changed
Lines changed: 129 additions & 40 deletions
File tree
- .github/workflows
- friture
- installer
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
44 | | - | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
45 | 76 | | |
46 | 77 | | |
47 | | - | |
48 | | - | |
| 78 | + | |
| 79 | + | |
49 | 80 | | |
50 | 81 | | |
51 | 82 | | |
| |||
105 | 136 | | |
106 | 137 | | |
107 | 138 | | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
108 | 154 | | |
109 | 155 | | |
110 | 156 | | |
111 | 157 | | |
112 | | - | |
113 | | - | |
| 158 | + | |
| 159 | + | |
114 | 160 | | |
115 | 161 | | |
116 | 162 | | |
| |||
176 | 222 | | |
177 | 223 | | |
178 | 224 | | |
| 225 | + | |
179 | 226 | | |
180 | 227 | | |
181 | 228 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | | - | |
| 47 | + | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
51 | | - | |
| 50 | + | |
| 51 | + | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
60 | 60 | | |
61 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
144 | | - | |
145 | | - | |
| 144 | + | |
| 145 | + | |
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
| |||
166 | 166 | | |
167 | 167 | | |
168 | 168 | | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
169 | 176 | | |
170 | | - | |
171 | | - | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
172 | 183 | | |
173 | 184 | | |
174 | 185 | | |
| |||
218 | 229 | | |
219 | 230 | | |
220 | 231 | | |
221 | | - | |
222 | | - | |
| 232 | + | |
| 233 | + | |
223 | 234 | | |
224 | 235 | | |
225 | 236 | | |
| |||
243 | 254 | | |
244 | 255 | | |
245 | 256 | | |
246 | | - | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
247 | 265 | | |
248 | 266 | | |
249 | 267 | | |
250 | | - | |
| 268 | + | |
251 | 269 | | |
252 | 270 | | |
253 | 271 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
55 | 62 | | |
56 | 63 | | |
57 | 64 | | |
58 | | - | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
59 | 73 | | |
60 | 74 | | |
61 | 75 | | |
| |||
85 | 99 | | |
86 | 100 | | |
87 | 101 | | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | 102 | | |
96 | 103 | | |
97 | 104 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | | - | |
9 | | - | |
| 8 | + | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
14 | | - | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
15 | 17 | | |
16 | 18 | | |
17 | 19 | | |
| |||
25 | 27 | | |
26 | 28 | | |
27 | 29 | | |
28 | | - | |
| 30 | + | |
29 | 31 | | |
30 | 32 | | |
31 | 33 | | |
| |||
37 | 39 | | |
38 | 40 | | |
39 | 41 | | |
40 | | - | |
| 42 | + | |
41 | 43 | | |
42 | 44 | | |
43 | 45 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
37 | 51 | | |
38 | 52 | | |
39 | 53 | | |
| |||
122 | 136 | | |
123 | 137 | | |
124 | 138 | | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
| 139 | + | |
130 | 140 | | |
131 | 141 | | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
132 | 145 | | |
133 | 146 | | |
134 | 147 | | |
| |||
0 commit comments