diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-19 21:28:54 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-19 21:28:54 +0200 |
| commit | 993950b7733ba9300915955ec6e51e1f21dad806 (patch) | |
| tree | d8800305748d96581f3bea06eeb9b1a1da979a49 /tests | |
| parent | 5609a744ef5e89c4d57a5239199a6afe5497590e (diff) | |
| download | sb-builder-993950b7733ba9300915955ec6e51e1f21dad806.tar.gz sb-builder-993950b7733ba9300915955ec6e51e1f21dad806.zip | |
Results tab with build logs; count only real packages
sbb-remote 0.4.0: results lists slackrepo's last attempt per package
(newest first, with built version and whether a log dir exists), and
build-log returns the end of a package's build.log (512 KB) plus its
resource.log, accepting only category/pkg paths. Result counts skip
the legacy buildresults rows keyed by package file name: the 213
"bad" on the Dashboard matched no package.
GUI: a Results tab (chips per result, table sorted by attempt time,
detail pane, log viewer window that keeps the failure at the end),
opened from the ribbon or by clicking a result in the Dashboard bar or
legend, filtered to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_gui.py | 49 | ||||
| -rw-r--r-- | tests/test_remote.py | 44 |
2 files changed, 89 insertions, 4 deletions
diff --git a/tests/test_gui.py b/tests/test_gui.py index fb0044c..d77df20 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -29,7 +29,7 @@ import unittest # noqa: E402 ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") sys.path.insert(0, ROOT) -from PyQt6.QtCore import QCoreApplication, QEvent, QObject, QProcess, pyqtSignal # noqa: E402 +from PyQt6.QtCore import QCoreApplication, QEvent, QObject, QPointF, QProcess, pyqtSignal # noqa: E402 from PyQt6.QtGui import QColor # noqa: E402 from PyQt6.QtWidgets import QApplication # noqa: E402 @@ -146,6 +146,9 @@ INVENTORY = {"columns": COLS, "rows": [ inv_row("pentesting/nmap", built="7.9", result="bad"), inv_row("network/curl"), ]} +RESULTS = {"rows": [["games/pcsx2", "failed", 1789992366, None, True], # newest first, as the helper + ["development/whisper.cpp", "aborted", 1789753147, None, False], + ["misc/nuclei", "ok", 1788978222, "3.11.0", True]]} PREVIEW = {"id": "20260101-000000-slackrepo", "kind": "slackrepo", "status": "ok", "args": ["update", "--preview", "nuclei"], "argv": [], "created": 0, "start": 0, "end": 1} @@ -287,7 +290,11 @@ class SmokeTest(unittest.TestCase): remote = FakeRemote({"version": ({"version": VERSION, "overlays": ["personal"]}, None), "stats": (STATS, None), "jobs": ({"jobs": []}, None), "gpg-status": ({"unlocked": True, "key": "K"}, None), - "inventory": (INVENTORY, None), **answers}) + "inventory": (INVENTORY, None), "results": (RESULTS, None), + "build-log": ({"build": "\x1b[1mERROR\x1b[0m: make failed\n", + "truncated": True, "mtime": 0, + "resource": "peak memory 3308M\n"}, None), + **answers}) w = MainWindow(config.load("/nonexistent")[0], remote, FakeNotifier()) self.addCleanup(w.deleteLater) self.addCleanup(lambda: w.jobs_tab and w.jobs_tab.stop()) @@ -434,7 +441,8 @@ class SmokeTest(unittest.TestCase): def test_ribbon_opens_tabs(self): w, _ = self.window() w.poll_jobs() - self.assertEqual([a.text() for a in w.ribbon.actions()], ["Dashboard", "Packages", "Jobs"]) + self.assertEqual([a.text() for a in w.ribbon.actions()], + ["Dashboard", "Packages", "Results", "Jobs"]) self.assertEqual([m.text() for m in w.menuBar().actions()], ["&File"]) w.ribbon.actions()[1].trigger() self.assertIs(w.tabs.currentWidget(), w.packages_tab) @@ -453,6 +461,41 @@ class SmokeTest(unittest.TestCase): prev.activated.emit() self.assertEqual(w.tabs.currentIndex(), 2) + def test_results_tab(self): + w, remote = self.window() + w.poll_jobs() + tab = w.open_results() + tab.table.selectRow(2) # nuclei, ok + tab.filter_to("failed") + self.assertEqual(tab.detail.text(), "") # hidden by the filter: no stale detail + self.assertIs(w.tabs.currentWidget(), tab) + self.assertEqual(tab.visible_paths(), ["games/pcsx2"]) + tab.chips["failed"].setChecked(False) + self.assertEqual(tab.visible_paths(), ["games/pcsx2", "development/whisper.cpp", "misc/nuclei"]) + tab.table.selectRow(1) # whisper.cpp: no log dir + self.assertFalse(tab.log_btn.isEnabled()) + tab.table.selectRow(0) + self.assertTrue(tab.log_btn.isEnabled()) + tab.show_log() + self.assertIn(("build-log", "games/pcsx2"), remote.calls) + viewer = tab.viewers[-1] + self.assertIn("ERROR: make failed", viewer.text.toPlainText()) # ANSI stripped + self.assertIn("peak memory", viewer.text.toPlainText()) + self.assertIn("truncated", viewer.text.toPlainText()) + self.assertTrue(viewer.text.toPlainText().endswith("make failed\n")) # failure stays last + viewer.close() + + def test_dashboard_results_clicks(self): + w, _ = self.window(stats=(dict(STATS, slackrepo={"results": {"ok": 3, "failed": 1}, "built": 3}), None)) + w.poll_jobs() + w.dashboard.legend.linkActivated.emit("failed") + self.assertIs(w.tabs.currentWidget(), w.results_tab) + self.assertEqual(w.results_tab.visible_paths(), ["games/pcsx2"]) + bar = w.dashboard.bar + bar.resize(400, 14) + hit = bar.label_at(QPointF(5, 5)) + self.assertEqual(hit, "ok") + def test_log_carriage_return(self): w, _ = self.window() jobs = w.open_jobs() diff --git a/tests/test_remote.py b/tests/test_remote.py index 2f273fb..85e642b 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -446,14 +446,56 @@ class StatsTest(Base): create table buildresults (itemid text primary key, time text, result text); create table revisions (itemid text, dep text, deplist text, version text, built text, rev text, os text, hintcksum text, primary key (itemid, dep)); - insert into buildresults values ('a', '1', 'ok'), ('b', '1', 'ok'), ('c', '1', 'failed'); + insert into buildresults values ('cat/a', '1', 'ok'), ('cat/b', '1', 'ok'), + ('cat/c', '1', 'failed'), ('ghost', '1', 'bad'), ('c-1.0-x86_64-1_x', '1', 'bad'); insert into revisions values ('cat/a', '/', '/', '1', '1', 'r', 'o', '/'), ('cat/a', 'dep', '/', '1', '1', 'r', 'o', '/'); """) db.close() + # legacy keys without a category/ match no package and are not counted self.assertEqual(sbb.cmd_stats(self.cfg)["slackrepo"], {"results": {"ok": 2, "failed": 1}, "built": 1}) +class ResultsTest(Base): + def setUp(self): + super().setUp() + logdir = os.path.join(self.tmp, "logs") + self.cfg["slackrepo_logdir"] = logdir + db = sqlite3.connect(self.cfg["slackrepo_db"]) + db.executescript(""" + create table buildresults (itemid text primary key, time text, result text); + create table revisions (itemid text, dep text, deplist text, version text, + built text, rev text, os text, hintcksum text, primary key (itemid, dep)); + insert into buildresults values ('cat/a', '100', 'ok'), ('cat/c', '300', 'failed'), + ('ghost', '200', 'bad'); + insert into revisions values ('cat/a', '/', '/', '1.0', '100', 'r', 'o', '/'); + """) + db.close() + os.makedirs(os.path.join(logdir, "cat", "c")) + with open(os.path.join(logdir, "cat", "c", "build.log"), "w") as f: + f.write("x" * 100 + "\nERROR: make failed\n") + with open(os.path.join(logdir, "cat", "c", "resource.log"), "w") as f: + f.write("peak memory 3308M\n") + + def test_rows(self): + self.assertEqual(sbb.cmd_results(self.cfg), {"rows": [ + ["cat/c", "failed", 300, None, True], + ["cat/a", "ok", 100, "1.0", False]]}) + + def test_build_log_tail(self): + with mock.patch.object(sbb, "LOG_TAIL", 20): + log = sbb.cmd_build_log(self.cfg, "cat/c") + self.assertEqual(log["build"], "\nERROR: make failed\n"[-20:]) + self.assertTrue(log["truncated"]) + self.assertEqual(log["resource"], "peak memory 3308M\n") + self.assertIsInstance(log["mtime"], int) + + def test_build_log_rejects(self): + for bad in ("../etc/passwd", "cat/../../x", "cat", "cat/a", "/etc/x"): + with self.subTest(bad=bad), self.assertRaises(sbb.Error): + sbb.cmd_build_log(self.cfg, bad) + + if __name__ == "__main__": unittest.main() |
