Skip to content

Commit c53b94d

Browse files
Lukas Geigerclaude
andcommitted
test: macOS-Source-Smoke und CI-Job ergänzt (P2)
tests/macos_platform_smoke.py: offscreen Hauptfenster, Finder-open -R und macOS-Terminal-Pfad mit echten Umlauten. CI: macos-smoke-Job in linux-platform-smoke.yml (umbenannt in platform-smoke) ergänzt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 39dfff2 commit c53b94d

3 files changed

Lines changed: 227 additions & 1 deletion

File tree

.github/workflows/linux-platform-smoke.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: linux-platform-smoke
1+
name: platform-smoke
22

33
on:
44
push:
@@ -48,3 +48,26 @@ jobs:
4848
4949
- name: Run Linux platform smoke
5050
run: python tests/linux_platform_smoke.py
51+
52+
macos-smoke:
53+
runs-on: macos-latest
54+
env:
55+
PYTEST_DISABLE_PLUGIN_AUTOLOAD: "1"
56+
QT_QPA_PLATFORM: offscreen
57+
PYTHONIOENCODING: utf-8
58+
steps:
59+
- name: Checkout
60+
uses: actions/checkout@v6
61+
62+
- name: Setup Python
63+
uses: actions/setup-python@v6
64+
with:
65+
python-version: "3.12"
66+
67+
- name: Install dependencies
68+
run: |
69+
python -m pip install --upgrade pip
70+
python -m pip install -r requirements.txt pytest
71+
72+
- name: Run macOS platform smoke
73+
run: python tests/macos_platform_smoke.py

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
1010
- EXE neu gebaut 2026-06-01 (PyInstaller, `CodeBox.spec``C:\_Local_DEV\codex_build\codebox`); 11/12 Tests grün (1 skipped), Smoke-Test bestanden. Vorherige EXE: 2026-05-28.
1111

1212
### Hinzugefügt
13+
- macOS-Source-Smoke für offscreen App-Start, Dateiöffnung, Terminalpfad,
14+
Projektbaum-`open -R` (Finder) und lokale Python-Run-Commands.
15+
CI-Job `macos-smoke` in `linux-platform-smoke.yml` ergänzt.
1316
- Linux-Source-Smoke für offscreen App-Start, Dateiöffnung, Terminalpfad,
1417
Projektbaum-`xdg-open` und lokale Python-Run-Commands.
1518
- Regressionstest für Startup-Dateiübergabe per `--open` und positionalem Pfad.

tests/macos_platform_smoke.py

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""Reproduzierbarer macOS-Plattform-Smoke für CodeBox.
4+
5+
Der Smoke deckt den macOS-Desktop-Pfad ab:
6+
- offscreen Start des PySide6-Hauptfensters
7+
- Dateiöffnen mit echten Umlauten ohne LSP-Zwang
8+
- macOS-Pfad für Projektbaum/`open -R`
9+
- macOS-Terminalpfad mit `bash`
10+
- lokale Run-Command-Auslösung für Python-Dateien
11+
"""
12+
13+
from __future__ import annotations
14+
15+
import os
16+
import sys
17+
import tempfile
18+
from pathlib import Path
19+
from unittest import mock
20+
21+
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
22+
23+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
24+
sys.path.insert(0, str(PROJECT_ROOT))
25+
26+
from PySide6.QtWidgets import QApplication
27+
28+
from features.project_view import ProjectView
29+
from features.terminal import TerminalWidget
30+
import features.terminal as terminal_module
31+
from ui.main_window import MainWindow
32+
from version import format_window_title
33+
34+
35+
class SmokeFailure(RuntimeError):
36+
pass
37+
38+
39+
def _assert(condition: bool, message: str) -> None:
40+
if not condition:
41+
raise SmokeFailure(message)
42+
43+
44+
def _ensure_app() -> QApplication:
45+
return QApplication.instance() or QApplication([])
46+
47+
48+
class _DummySignal:
49+
def __init__(self) -> None:
50+
self._callbacks = []
51+
52+
def connect(self, callback) -> None:
53+
self._callbacks.append(callback)
54+
55+
56+
class _FakeQProcess:
57+
class ProcessState:
58+
NotRunning = "not_running"
59+
Running = "running"
60+
61+
def __init__(self, parent=None) -> None:
62+
self.parent = parent
63+
self.cwd = None
64+
self.start_args = None
65+
self.writes: list[bytes] = []
66+
self._state = self.ProcessState.NotRunning
67+
self.readyReadStandardOutput = _DummySignal()
68+
self.readyReadStandardError = _DummySignal()
69+
self.finished = _DummySignal()
70+
71+
def setWorkingDirectory(self, cwd: str) -> None:
72+
self.cwd = cwd
73+
74+
def start(self, program: str, args: list[str]) -> None:
75+
self.start_args = (program, args)
76+
self._state = self.ProcessState.Running
77+
78+
def write(self, data: bytes) -> None:
79+
self.writes.append(data)
80+
81+
def state(self):
82+
return self._state
83+
84+
def kill(self) -> None:
85+
self._state = self.ProcessState.NotRunning
86+
87+
def waitForFinished(self, _timeout: int) -> bool:
88+
self._state = self.ProcessState.NotRunning
89+
return True
90+
91+
92+
def _exercise_window_open_and_run() -> None:
93+
print("Test 1: Offscreen-Hauptfenster öffnet Datei und löst Run-Command aus")
94+
app = _ensure_app()
95+
with tempfile.TemporaryDirectory(prefix="codebox-macos-window-") as tmpdir_str:
96+
tmpdir = Path(tmpdir_str)
97+
project_dir = tmpdir / "Projekt Übersicht"
98+
project_dir.mkdir(parents=True)
99+
script_path = project_dir / "überblick.py"
100+
script_path.write_text("print('Grüße aus macOS')\n", encoding="utf-8")
101+
102+
with mock.patch("features.terminal.TerminalWidget._start_shell", lambda self: None):
103+
window = MainWindow()
104+
try:
105+
window._lsp_manager.get_client = lambda _language: None
106+
window.show()
107+
app.processEvents()
108+
109+
_assert(window.windowTitle() == format_window_title(), window.windowTitle())
110+
opened_tab = window.open_path(script_path)
111+
app.processEvents()
112+
113+
_assert(opened_tab is not None, "Datei wurde nicht geöffnet.")
114+
_assert(
115+
opened_tab.editor.toPlainText() == "print('Grüße aus macOS')\n",
116+
"Editorinhalt stimmt nicht.",
117+
)
118+
_assert(window.windowTitle() == format_window_title(script_path), window.windowTitle())
119+
_assert(window.lang_label.text() == "Python", window.lang_label.text())
120+
_assert(window.project_view._root_path == project_dir, window.project_view._root_path)
121+
_assert(window.terminal.cwd_label.text() == str(project_dir), window.terminal.cwd_label.text())
122+
_assert(window.output.run_btn.isEnabled(), "Run-Button blieb deaktiviert.")
123+
124+
captured: list[list[str]] = []
125+
window.output.run_command = lambda command: captured.append(command)
126+
window.run_current()
127+
128+
_assert(captured == [["python", "-u", str(script_path)]], repr(captured))
129+
finally:
130+
window.close()
131+
app.processEvents()
132+
print("PASS: Dateiöffnung, Umlaute und Run-Command funktionieren\n")
133+
134+
135+
def _exercise_macos_reveal_in_finder() -> None:
136+
print("Test 2: Projektbaum verwendet unter macOS open -R")
137+
with tempfile.TemporaryDirectory(prefix="codebox-macos-explorer-") as tmpdir_str:
138+
tmpdir = Path(tmpdir_str)
139+
project_dir = tmpdir / "Projekt Übersicht"
140+
project_dir.mkdir(parents=True)
141+
file_path = project_dir / "überblick.py"
142+
file_path.write_text("print('ok')\n", encoding="utf-8")
143+
144+
panel = ProjectView()
145+
with mock.patch.object(sys, "platform", "darwin"), mock.patch("subprocess.Popen") as popen_mock:
146+
panel._reveal_in_explorer(file_path)
147+
148+
_assert(
149+
popen_mock.call_args.args[0] == ["open", "-R", str(file_path)],
150+
repr(popen_mock.call_args),
151+
)
152+
print("PASS: macOS-Finder-Pfad ist korrekt\n")
153+
154+
155+
def _exercise_macos_terminal() -> None:
156+
print("Test 3: Terminal nutzt unter macOS bash und cd")
157+
app = _ensure_app()
158+
with tempfile.TemporaryDirectory(prefix="codebox-macos-terminal-") as tmpdir_str:
159+
tmpdir = Path(tmpdir_str)
160+
start_dir = tmpdir / "Start Ä"
161+
next_dir = tmpdir / "Ziel Ö"
162+
start_dir.mkdir(parents=True)
163+
next_dir.mkdir(parents=True)
164+
165+
with mock.patch.object(terminal_module, "QProcess", _FakeQProcess), mock.patch.object(
166+
terminal_module.sys,
167+
"platform",
168+
"darwin",
169+
):
170+
widget = TerminalWidget(working_dir=str(start_dir))
171+
try:
172+
app.processEvents()
173+
items = [widget.shell_combo.itemText(i) for i in range(widget.shell_combo.count())]
174+
_assert(items == ["bash", "zsh", "sh"], repr(items))
175+
_assert(widget.process.start_args == ("bash", ["--norc"]), repr(widget.process.start_args))
176+
_assert(widget.process.cwd == str(start_dir), widget.process.cwd)
177+
178+
widget.set_working_dir(str(next_dir))
179+
_assert(widget.cwd_label.text() == str(next_dir), widget.cwd_label.text())
180+
_assert(
181+
widget.process.writes[-1] == f'cd "{next_dir}"\n'.encode("utf-8"),
182+
repr(widget.process.writes),
183+
)
184+
finally:
185+
widget.close()
186+
app.processEvents()
187+
print("PASS: macOS-Terminalpfad ist korrekt\n")
188+
189+
190+
def main() -> int:
191+
print("=== CodeBox macOS Platform Smoke ===\n")
192+
_exercise_window_open_and_run()
193+
_exercise_macos_reveal_in_finder()
194+
_exercise_macos_terminal()
195+
print("=== ALL TESTS PASSED ===")
196+
return 0
197+
198+
199+
if __name__ == "__main__":
200+
raise SystemExit(main())

0 commit comments

Comments
 (0)