Grotti 3.6.1: Abgebrochene Läufe bleiben im Protokoll, Spulenwechsel bucht den laufenden Druck nicht vorzeitig ab, und der Pi öffnet große Dateien nicht mehr parallel.
This commit is contained in:
1 parent
2b1e36634a
commit
5081212212
23 files changed
+559
-65
No files matched your search
@@ -85,7 +85,20 @@ class ChooseSpoolTests(unittest.TestCase):
|
||||
self.assertTrue(jobs._spool_valid_for_end(spool, end))
|
||||
|
||||
|
||||
class UndoDeductionTests(unittest.TestCase):
|
||||
def test_reopens_last_segment(self):
|
||||
conn = MagicMock()
|
||||
conn.execute.return_value.fetchall.return_value = [
|
||||
{"spool_id": 7, "weight_g": 10},
|
||||
]
|
||||
run = {"id": 42, "deducted": True}
|
||||
self.assertTrue(jobs.undo_deduction(conn, run))
|
||||
sqls = [str(call.args[0]) for call in conn.execute.call_args_list]
|
||||
self.assertTrue(any("UPDATE run_segments" in sql and "mm_end = NULL" in sql for sql in sqls))
|
||||
|
||||
|
||||
class SteuernAliasTests(unittest.TestCase):
|
||||
|
||||
@patch("grotti.moonraker.gcode_script", return_value={"ok": True})
|
||||
def test_value_alias_for_temp(self, gcode):
|
||||
moonraker.steuern("10.0.0.1", "nozzle", {"value": 210})
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
"""ACE-Startabschnitte und Spulenwechsel 0→jetzt."""
|
||||
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from grotti import store
|
||||
from grotti.watcher import StateWatcher
|
||||
|
||||
|
||||
class PrevSpoolForSlotTests(unittest.TestCase):
|
||||
def test_prefers_old_at_slot(self):
|
||||
conn = mock.Mock()
|
||||
live = {"id": 1, "filename": "a.gcode", "spool_id": 99}
|
||||
got = store.prev_spool_id_for_slot(
|
||||
conn, "h", 4, live, 21, old_rows=[{"id": 18}],
|
||||
)
|
||||
self.assertEqual(got, 18)
|
||||
|
||||
def test_ace_map_send_when_old_empty(self):
|
||||
conn = mock.Mock()
|
||||
live = {"id": 33805, "filename": "x.gcode", "spool_id": None}
|
||||
with mock.patch(
|
||||
"grotti.store.latest_send",
|
||||
return_value={
|
||||
"ace_map": [{"paint_index": 0, "ams_index": 3}],
|
||||
"spool_id": 18,
|
||||
},
|
||||
):
|
||||
got = store.prev_spool_id_for_slot(conn, "h", 4, live, 21, old_rows=[])
|
||||
self.assertEqual(got, 18)
|
||||
|
||||
def test_last_segment_on_slot(self):
|
||||
conn = mock.Mock()
|
||||
live = {"id": 7, "filename": "x.gcode", "spool_id": None}
|
||||
with mock.patch("grotti.store.latest_send", return_value={"ace_map": [], "spool_id": None}), \
|
||||
mock.patch(
|
||||
"grotti.store.list_run_segments",
|
||||
return_value=[
|
||||
{"spool_id": 10, "ace_slot": 2, "mm_start": 0, "mm_end": 100},
|
||||
{"spool_id": 18, "ace_slot": 4, "mm_start": 0, "mm_end": 500},
|
||||
],
|
||||
):
|
||||
got = store.prev_spool_id_for_slot(conn, "h", 4, live, 21, old_rows=[])
|
||||
self.assertEqual(got, 18)
|
||||
|
||||
|
||||
class EnsureStartSegmentAceTests(unittest.TestCase):
|
||||
def test_opens_mapped_slots_with_spool(self):
|
||||
watcher = StateWatcher("postgresql://x", {})
|
||||
conn = mock.Mock()
|
||||
live = {"filament_used": 0}
|
||||
with mock.patch("grotti.watcher.store.list_run_segments", return_value=[]), \
|
||||
mock.patch(
|
||||
"grotti.watcher.store.latest_send",
|
||||
return_value={"ace_map": [{"paint_index": 0, "ams_index": 3}]},
|
||||
), \
|
||||
mock.patch(
|
||||
"grotti.watcher.store.spool_at_slot",
|
||||
return_value={"id": 18},
|
||||
) as at_slot, \
|
||||
mock.patch("grotti.watcher.store.open_segment") as open_seg:
|
||||
watcher._ensure_start_segment(conn, "h", "a.gcode", live, 33805)
|
||||
at_slot.assert_called_once_with(conn, "h", 4)
|
||||
open_seg.assert_called_once_with(conn, 33805, 18, 4, 0.0)
|
||||
|
||||
def test_ace_without_spool_opens_nothing(self):
|
||||
watcher = StateWatcher("postgresql://x", {})
|
||||
conn = mock.Mock()
|
||||
with mock.patch("grotti.watcher.store.list_run_segments", return_value=[]), \
|
||||
mock.patch(
|
||||
"grotti.watcher.store.latest_send",
|
||||
return_value={"ace_map": [{"paint_index": 0, "ams_index": 3}]},
|
||||
), \
|
||||
mock.patch("grotti.watcher.store.spool_at_slot", return_value=None), \
|
||||
mock.patch("grotti.watcher.store.open_segment") as open_seg:
|
||||
watcher._ensure_start_segment(conn, "h", "a.gcode", {"filament_used": 0}, 1)
|
||||
open_seg.assert_not_called()
|
||||
|
||||
|
||||
class SpoolLoadFallbackSegmentsTests(unittest.TestCase):
|
||||
"""ACE ohne Segment + Einlegen ohne old → 0→jetzt + neuer offener Abschnitt."""
|
||||
|
||||
def test_creates_closed_and_open_segment(self):
|
||||
conn = mock.Mock()
|
||||
live = {"id": 33805, "filename": "x.gcode", "spool_id": None, "state": "in_progress"}
|
||||
opened = []
|
||||
closed = []
|
||||
|
||||
def open_segment(_c, run_id, spool_id, ace_slot, mm_start):
|
||||
opened.append((run_id, spool_id, ace_slot, mm_start))
|
||||
return True
|
||||
|
||||
def close_open_segments(_c, run_id, ace_slot, mm_end):
|
||||
closed.append((run_id, ace_slot, mm_end))
|
||||
# Erster Aufruf: nichts offen; zweiter: nach open_segment
|
||||
return 1 if opened else 0
|
||||
|
||||
with mock.patch("grotti.store.close_open_segments", side_effect=close_open_segments), \
|
||||
mock.patch("grotti.store.open_segment", side_effect=open_segment), \
|
||||
mock.patch(
|
||||
"grotti.store.prev_spool_id_for_slot",
|
||||
return_value=18,
|
||||
):
|
||||
# Logik wie in spool_load (Auszug)
|
||||
slot = 4
|
||||
spool_id = 21
|
||||
mm_now = 5291.0
|
||||
old = []
|
||||
closed_n = store.close_open_segments(conn, live["id"], slot, mm_now)
|
||||
if not closed_n and old:
|
||||
pass
|
||||
elif not closed_n and mm_now > 0:
|
||||
prev_id = store.prev_spool_id_for_slot(
|
||||
conn, "h", slot, live, spool_id, old_rows=old,
|
||||
)
|
||||
if prev_id:
|
||||
store.open_segment(conn, live["id"], prev_id, slot, 0)
|
||||
store.close_open_segments(conn, live["id"], slot, mm_now)
|
||||
store.open_segment(conn, live["id"], spool_id, slot, mm_now)
|
||||
|
||||
self.assertEqual(opened[0], (33805, 18, 4, 0))
|
||||
self.assertEqual(closed[-1], (33805, 4, 5291.0))
|
||||
self.assertEqual(opened[-1], (33805, 21, 4, 5291.0))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -137,6 +137,69 @@ class WatcherOfflineTests(unittest.TestCase):
|
||||
kinds = [call.args[3] for call in emit.call_args_list]
|
||||
self.assertEqual(kinds, ["abbruch", "offline", "start"])
|
||||
|
||||
def test_other_file_without_offline_releases_the_old_run(self):
|
||||
live = {
|
||||
"host": HOST,
|
||||
"name": "Kobra S1",
|
||||
"state": "printing",
|
||||
"ok": True,
|
||||
"filename": "andere.gcode",
|
||||
"weight": 0,
|
||||
}
|
||||
old = {"id": 42, "filename": FILE}
|
||||
new = {"id": 99, "filename": "andere.gcode"}
|
||||
seen = {"n": 0}
|
||||
|
||||
def live_run(_conn, _host, filename=""):
|
||||
seen["n"] += 1
|
||||
if seen["n"] <= 3:
|
||||
return old
|
||||
return None
|
||||
|
||||
with mock.patch.object(self.watcher, "_emit") as emit, \
|
||||
mock.patch.object(self.watcher, "_track_print", return_value=new), \
|
||||
mock.patch.object(self.watcher, "_ensure_start_segment"), \
|
||||
mock.patch.object(self.watcher, "_check_spool", return_value=False), \
|
||||
mock.patch("grotti.watcher.store.find_live_run", side_effect=live_run), \
|
||||
mock.patch("grotti.watcher.store.abandon_live_runs") as abandon, \
|
||||
mock.patch("grotti.watcher.jobs.sync_history") as sync:
|
||||
self.watcher._one(mock.Mock(), live)
|
||||
sync.assert_called_once()
|
||||
abandon.assert_called()
|
||||
self.assertIn("abbruch", [call.args[3] for call in emit.call_args_list])
|
||||
self.assertIn("start", [call.args[3] for call in emit.call_args_list])
|
||||
|
||||
def test_cancel_event_survives_deleted_live_run(self):
|
||||
self.watcher._cancel_hits[HOST] = 2
|
||||
live = {
|
||||
"host": HOST,
|
||||
"name": "Kobra S1",
|
||||
"state": "cancelled",
|
||||
"ok": True,
|
||||
"filename": FILE,
|
||||
}
|
||||
dangling = {"id": 42, "filename": FILE, "state": "in_progress"}
|
||||
|
||||
def row(_conn, run_id):
|
||||
if run_id == 77:
|
||||
return {"id": 77, "filename": FILE}
|
||||
return None
|
||||
|
||||
with mock.patch("grotti.watcher.store.find_live_run", side_effect=[dangling, None]), \
|
||||
mock.patch("grotti.watcher.store.find_open_history_run", return_value=None), \
|
||||
mock.patch("grotti.watcher.store.get_run_row", side_effect=row), \
|
||||
mock.patch("grotti.watcher.store.latest_run_id", return_value=77), \
|
||||
mock.patch("grotti.watcher.store.abandon_live_runs"), \
|
||||
mock.patch("grotti.watcher.store.set_bed_needs_clear"), \
|
||||
mock.patch("grotti.watcher.store.set_printer_active_run"), \
|
||||
mock.patch("grotti.watcher.jobs.sync_history") as sync, \
|
||||
mock.patch("grotti.watcher.notify.record") as record:
|
||||
self.watcher._one(mock.Mock(), live)
|
||||
sync.assert_called_once()
|
||||
record.assert_called()
|
||||
self.assertEqual(record.call_args.kwargs["run_id"], 77)
|
||||
self.assertEqual(record.call_args.args[4], "abbruch")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user