Skip to content

Commit 29eb6fa

Browse files
committed
Fix setup assistant post-install validation
1 parent ba2425b commit 29eb6fa

2 files changed

Lines changed: 201 additions & 12 deletions

File tree

app/ui/setup_tab.py

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -637,9 +637,10 @@ def apply_selected_pdk_candidate(self) -> None:
637637
f"The detected PDK at {sky130a_path} was adopted.\n",
638638
)
639639
)
640-
self._set_step(self.STEP_APPLY)
640+
self._route_after_pdk_ready()
641641
self._finish_activity(True, pick(self.lang, "PDK listo para usar", "PDK ready to use"))
642642
return
643+
self._route_after_pdk_ready()
643644
self._finish_activity(True, pick(self.lang, "El PDK ya estaba aplicado", "The PDK was already applied"))
644645

645646
def install_managed_pdk(self) -> None:
@@ -658,7 +659,7 @@ def install_managed_pdk(self) -> None:
658659
self.send_status.emit(pick(self.lang, "PDK gestionado listo", "Managed PDK ready"))
659660
self.refresh_detection()
660661
self.refresh_validation()
661-
self._set_step(self.STEP_APPLY)
662+
self._route_after_pdk_ready()
662663
self._finish_activity(True, pick(self.lang, "PDK gestionado instalado", "Managed PDK installed"))
663664
return
664665
self.log.append(pick(self.lang, f"{result.message}\n", f"{result.message}\n"))
@@ -828,6 +829,7 @@ def _on_finished(self, code: int, status: str) -> None:
828829
self.refresh_detection()
829830
self.refresh_validation()
830831
self._apply_detected_defaults(automatic=True)
832+
self._route_after_tools_ready()
831833
self._finish_activity(True, pick(self.lang, "Instalación lista", "Installation ready"))
832834
return
833835

@@ -842,7 +844,7 @@ def _on_finished(self, code: int, status: str) -> None:
842844
self.send_status.emit(pick(self.lang, "Build de PDK listo", "PDK build ready"))
843845
self.refresh_detection()
844846
self.refresh_validation()
845-
self._set_step(self.STEP_APPLY)
847+
self._route_after_pdk_ready()
846848
self._finish_activity(True, pick(self.lang, "Build de PDK listo", "PDK build ready"))
847849
return
848850

@@ -858,7 +860,7 @@ def _on_finished(self, code: int, status: str) -> None:
858860
self.refresh_detection()
859861
self.refresh_validation()
860862
self._apply_detected_defaults(automatic=True)
861-
self._set_step(self.STEP_APPLY)
863+
self._route_after_pdk_ready()
862864
self._finish_activity(True, pick(self.lang, "Bundle PDK listo", "PDK bundle ready"))
863865
return
864866

@@ -889,18 +891,46 @@ def _on_finished(self, code: int, status: str) -> None:
889891
return
890892

891893
if action == "install_tools":
894+
self._handle_install_tools_failure(code, status)
895+
return
896+
897+
self._finish_activity(code == 0, pick(self.lang, "Acción completada", "Action finished"))
898+
899+
def _handle_install_tools_failure(self, code: int, status: str) -> None:
900+
self.log.append(
901+
pick(
902+
self.lang,
903+
f"\nEl instalador terminó con advertencia/error (exit={code}, status={status}). Revalidando lo que quedó instalado.\n",
904+
f"\nThe installer ended with a warning/error (exit={code}, status={status}). Revalidating what is installed now.\n",
905+
)
906+
)
907+
self.refresh_detection()
908+
self.refresh_validation()
909+
self._apply_detected_defaults(automatic=True)
910+
diagnosis = self._last_diagnosis
911+
if diagnosis is not None and self._tools_ready(diagnosis):
892912
self.log.append(
893913
pick(
894914
self.lang,
895-
f"\nEl proceso terminó con error (exit={code}, status={status}). Revisa el log; si falló `pkexec` o `apt`, el `.venv` no fue tocado.\n",
896-
f"\nThe process ended with an error (exit={code}, status={status}). Review the log; if `pkexec` or `apt` failed, `.venv` was not modified.\n",
915+
"Las tools base ya se detectan correctamente; se conserva el log porque el proceso reportó error al salir.\n",
916+
"The base tools are now detected correctly; the log is kept because the process reported an error on exit.\n",
897917
)
898918
)
899-
self.send_status.emit(pick(self.lang, "Setup falló", "Setup failed"))
900-
self._finish_activity(False, pick(self.lang, "Instalación falló", "Installation failed"))
919+
self.send_status.emit(pick(self.lang, "Tools detectadas", "Tools detected"))
920+
self._route_after_tools_ready()
921+
self._finish_activity(True, pick(self.lang, "Tools detectadas", "Tools detected"))
901922
return
902923

903-
self._finish_activity(code == 0, pick(self.lang, "Acción completada", "Action finished"))
924+
self.log.append(
925+
pick(
926+
self.lang,
927+
"Siguen faltando tools después de revalidar. Revisa el log del instalador para ver el paquete o comando bloqueante.\n",
928+
"Tools are still missing after revalidation. Review the installer log for the blocking package or command.\n",
929+
)
930+
)
931+
self.send_status.emit(pick(self.lang, "Setup falló", "Setup failed"))
932+
self._set_step(self.STEP_TOOLS)
933+
self._finish_activity(False, pick(self.lang, "Instalación falló", "Installation failed"))
904934

905935
def _apply_detected_defaults(self, automatic: bool) -> None:
906936
self._begin_activity(
@@ -952,9 +982,38 @@ def _apply_detected_defaults(self, automatic: bool) -> None:
952982
)
953983
self._finish_activity(True, pick(self.lang, "Sin cambios pendientes", "No pending changes"))
954984

985+
@staticmethod
986+
def _tools_ready(diagnosis) -> bool:
987+
return all(tool.status in {"ok", "alias"} for tool in diagnosis.tools.values())
988+
989+
@staticmethod
990+
def _pdk_ready(diagnosis) -> bool:
991+
return diagnosis.pdk.status == "present"
992+
993+
def _route_after_pdk_ready(self) -> None:
994+
diagnosis = self._last_diagnosis
995+
if diagnosis is not None and not self._tools_ready(diagnosis):
996+
self.log.append(
997+
pick(
998+
self.lang,
999+
"PDK listo. Aún faltan tools base, continúa con el paso de instalación de tools.\n",
1000+
"PDK is ready. Base tools are still missing, continue with the tools installation step.\n",
1001+
)
1002+
)
1003+
self._set_step(self.STEP_TOOLS)
1004+
return
1005+
self._set_step(self.STEP_APPLY)
1006+
1007+
def _route_after_tools_ready(self) -> None:
1008+
diagnosis = self._last_diagnosis
1009+
if diagnosis is not None and self._pdk_ready(diagnosis):
1010+
self._set_step(self.STEP_APPLY)
1011+
return
1012+
self._set_step(self.STEP_PDK)
1013+
9551014
def _update_ready_state(self, diagnosis) -> None:
956-
tools_ready = all(tool.status in {"ok", "alias"} for tool in diagnosis.tools.values())
957-
pdk_ready = diagnosis.pdk.status == "present"
1015+
tools_ready = self._tools_ready(diagnosis)
1016+
pdk_ready = self._pdk_ready(diagnosis)
9581017
python_ready = not diagnosis.python_env.problems and diagnosis.python_env.requirements_ok
9591018

9601019
def summary(ready: bool, partial: bool = False) -> str:
@@ -1069,7 +1128,7 @@ def _sync_action_gates(self) -> None:
10691128
if self._active_operations > 0:
10701129
return
10711130
diagnosis = self._last_diagnosis
1072-
tools_ready = bool(diagnosis) and all(tool.status in {"ok", "alias"} for tool in diagnosis.tools.values())
1131+
tools_ready = bool(diagnosis) and self._tools_ready(diagnosis)
10731132

10741133
self.validate_btn.setEnabled(True)
10751134
self.refresh_detect_btn.setEnabled(True)

tests/test_setup_tab_routing.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"""Regression tests for setup assistant step routing."""
2+
3+
from __future__ import annotations
4+
5+
import sys
6+
import unittest
7+
from types import SimpleNamespace
8+
9+
10+
class _DummySignal:
11+
def __init__(self, *args, **kwargs) -> None:
12+
pass
13+
14+
def connect(self, *args, **kwargs) -> None:
15+
pass
16+
17+
def emit(self, *args, **kwargs) -> None:
18+
pass
19+
20+
21+
class _DummyWidget:
22+
def __init__(self, *args, **kwargs) -> None:
23+
pass
24+
25+
26+
class _DummyQProcess:
27+
NotRunning = 0
28+
Running = 1
29+
30+
class ExitStatus:
31+
pass
32+
33+
class ProcessError:
34+
FailedToStart = 0
35+
36+
37+
class _DummyQt:
38+
class AlignmentFlag:
39+
AlignCenter = 0
40+
41+
ItemIsEnabled = 1
42+
ItemIsSelectable = 2
43+
WA_StyledBackground = 3
44+
45+
46+
qtcore_stub = SimpleNamespace(
47+
QObject=_DummyWidget,
48+
QCoreApplication=object,
49+
QProcess=_DummyQProcess,
50+
QSettings=object,
51+
QTimer=object,
52+
Qt=_DummyQt,
53+
Signal=_DummySignal,
54+
)
55+
qtwidgets_stub = SimpleNamespace(
56+
QComboBox=_DummyWidget,
57+
QFrame=_DummyWidget,
58+
QGridLayout=_DummyWidget,
59+
QHBoxLayout=_DummyWidget,
60+
QLabel=_DummyWidget,
61+
QListWidget=_DummyWidget,
62+
QListWidgetItem=_DummyWidget,
63+
QProgressBar=_DummyWidget,
64+
QPushButton=_DummyWidget,
65+
QScrollArea=_DummyWidget,
66+
QSizePolicy=_DummyWidget,
67+
QStackedWidget=_DummyWidget,
68+
QTableWidget=_DummyWidget,
69+
QTableWidgetItem=_DummyWidget,
70+
QTextEdit=_DummyWidget,
71+
QVBoxLayout=_DummyWidget,
72+
QWidget=_DummyWidget,
73+
)
74+
sys.modules["PySide6"] = SimpleNamespace(QtCore=qtcore_stub, QtWidgets=qtwidgets_stub)
75+
sys.modules["PySide6.QtCore"] = qtcore_stub
76+
sys.modules["PySide6.QtWidgets"] = qtwidgets_stub
77+
78+
from app.ui.setup_tab import SetupTab
79+
80+
81+
class _Log:
82+
def __init__(self) -> None:
83+
self.lines: list[str] = []
84+
85+
def append(self, text: str) -> None:
86+
self.lines.append(text)
87+
88+
89+
class SetupTabRoutingTest(unittest.TestCase):
90+
def _tab_with_diagnosis(self, diagnosis: SimpleNamespace) -> SetupTab:
91+
tab = SetupTab.__new__(SetupTab)
92+
tab.lang = "es"
93+
tab.log = _Log()
94+
tab._last_diagnosis = diagnosis
95+
tab.selected_steps = []
96+
tab._set_step = tab.selected_steps.append
97+
return tab
98+
99+
def test_after_pdk_ready_routes_to_tools_when_tools_are_missing(self) -> None:
100+
diagnosis = SimpleNamespace(
101+
tools={
102+
"xschem": SimpleNamespace(status="missing"),
103+
"ngspice": SimpleNamespace(status="ok"),
104+
},
105+
pdk=SimpleNamespace(status="present"),
106+
)
107+
tab = self._tab_with_diagnosis(diagnosis)
108+
109+
tab._route_after_pdk_ready()
110+
111+
self.assertEqual(tab.selected_steps, [SetupTab.STEP_TOOLS])
112+
self.assertIn("faltan tools", tab.log.lines[-1])
113+
114+
def test_after_pdk_ready_routes_to_apply_when_tools_are_ready(self) -> None:
115+
diagnosis = SimpleNamespace(
116+
tools={
117+
"xschem": SimpleNamespace(status="ok"),
118+
"netgen": SimpleNamespace(status="alias"),
119+
},
120+
pdk=SimpleNamespace(status="present"),
121+
)
122+
tab = self._tab_with_diagnosis(diagnosis)
123+
124+
tab._route_after_pdk_ready()
125+
126+
self.assertEqual(tab.selected_steps, [SetupTab.STEP_APPLY])
127+
128+
129+
if __name__ == "__main__":
130+
unittest.main()

0 commit comments

Comments
 (0)