Skip to content

Commit 9041015

Browse files
committed
Add test for Sweep2D queue with save_data=True
Introduces a new test and supporting mock code to verify that wait_for_sweep does not exit early when running a SweepQueue with three Sweep2D sweeps using save_data=True. The test ensures all sweeps complete and the queue reaches the expected final state, addressing a potential race condition.
1 parent 8134b6f commit 9041015

File tree

2 files changed

+131
-0
lines changed

2 files changed

+131
-0
lines changed

tests/e2e/helpers/mock_qcodes.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,46 @@ def __repr__(self):
203203
print(f"sweep_queue.state() = {sweep_queue.state()}")
204204
print(f"sweep_queue.current_sweep = {sweep_queue.current_sweep}")
205205
"""
206+
207+
# Setup code for MeasureIt SweepQueue with Sweep2D and save_data=True
208+
# This tests a more realistic scenario with 2D sweeps that save data
209+
MEASUREIT_SWEEP2D_QUEUE_SETUP = """
210+
import time
211+
from qcodes.instrument_drivers.mock_instruments import MockParabola
212+
from qcodes.instrument import Instrument
213+
from measureit import Sweep2D
214+
from measureit.tools import ensure_qt
215+
from measureit.tools.sweep_queue import SweepQueue, DatabaseEntry
216+
from measureit.config import get_path
217+
ensure_qt()
218+
Instrument.close_all()
219+
instr = MockParabola(name=f"test_instr_{int(time.time())}")
220+
# 3 Sweep2D with save_data=True - small sweeps for faster testing
221+
# Sweep2D expects: in_params=[param, start, stop, step], out_params=[param, start, stop, step]
222+
# outer: 3 points, inner: 3 points, total 9 points per sweep
223+
s1 = Sweep2D([instr.x, 0, 2, 1], [instr.y, 0, 2, 1], inter_delay=0.05, save_data=True)
224+
s2 = Sweep2D([instr.x, 0, 2, 1], [instr.y, 0, 2, 1], inter_delay=0.05, save_data=True)
225+
s3 = Sweep2D([instr.x, 0, 2, 1], [instr.y, 0, 2, 1], inter_delay=0.05, save_data=True)
226+
# Setup database entries for each sweep (save_data=True requires DatabaseEntry)
227+
db_name = "e2e_test_sweep2d.db"
228+
db_path = str(get_path("databases") / db_name)
229+
exp_name = f"e2e_test_{int(time.time())}"
230+
db_entry1 = DatabaseEntry(db_path, exp_name, "sweep2d_s1")
231+
db_entry2 = DatabaseEntry(db_path, exp_name, "sweep2d_s2")
232+
db_entry3 = DatabaseEntry(db_path, exp_name, "sweep2d_s3")
233+
sweep_queue = SweepQueue()
234+
# Each sweep needs its own database entry
235+
sweep_queue += (db_entry1, s1)
236+
sweep_queue += (db_entry2, s2)
237+
sweep_queue += (db_entry3, s3)
238+
print("Sweep2D SweepQueue setup complete")
239+
print(f"s1.progressState.is_queued = {s1.progressState.is_queued}")
240+
print(f"s2.progressState.is_queued = {s2.progressState.is_queued}")
241+
print(f"s3.progressState.is_queued = {s3.progressState.is_queued}")
242+
"""
243+
244+
# Code to start the sweep2D queue (run after MEASUREIT_SWEEP2D_QUEUE_SETUP)
245+
MEASUREIT_START_SWEEP2D_QUEUE = """
246+
sweep_queue.start()
247+
print("Sweep2D SweepQueue started")
248+
"""

tests/e2e/test_06_optional_features.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
DYNAMIC_TOOLS,
2121
MEASUREIT_SWEEP_QUEUE_SETUP,
2222
MEASUREIT_START_QUEUE,
23+
MEASUREIT_SWEEP2D_QUEUE_SETUP,
24+
MEASUREIT_START_SWEEP2D_QUEUE,
2325
)
2426

2527

@@ -918,3 +920,89 @@ def test_wait_for_sweep_with_running_queue(self, mcp_server_measureit_sweepqueue
918920
assert (
919921
"sweep_queue" in content or success
920922
), f"wait_for_sweep should find sweep_queue: {content}"
923+
924+
@pytest.mark.p1
925+
def test_sweep2d_queue_wait_for_sweep_no_early_exit(
926+
self, mcp_server_measureit_sweepqueue
927+
):
928+
"""OF-054: Test wait_for_sweep does not exit early with 3 Sweep2D (save_data=True).
929+
930+
This test verifies that wait_for_sweep correctly waits for all 3 Sweep2D
931+
operations to complete when using save_data=True, and does not exit
932+
prematurely after the first or second sweep finishes.
933+
934+
The test:
935+
1. Sets up 3 Sweep2D with save_data=True
936+
2. Starts the SweepQueue
937+
3. Calls wait_for_sweep with sufficient timeout
938+
4. Verifies the queue completed all sweeps (state should be "idle" or "done")
939+
5. Verifies no premature exit (timed_out should be False)
940+
"""
941+
page = mcp_server_measureit_sweepqueue["page"]
942+
base_url = mcp_server_measureit_sweepqueue["url"]
943+
944+
# Create 3 Sweep2D with save_data=True
945+
run_cell(page, MEASUREIT_SWEEP2D_QUEUE_SETUP)
946+
page.wait_for_timeout(1000)
947+
948+
# Verify setup completed
949+
output = get_cell_output(page)
950+
assert "is_queued = True" in output, f"is_queued not set properly: {output}"
951+
952+
# Start the sweep queue and IMMEDIATELY call wait_for_sweep (no delay)
953+
# This tests the race condition where the queue might still be in "pending" state
954+
# when wait_for_sweep checks the status for the first time
955+
run_cell(page, MEASUREIT_START_SWEEP2D_QUEUE)
956+
# NO delay here - we want to catch the timing bug where "pending" is misinterpreted
957+
958+
# Wait for sweep with sufficient timeout (30s should be enough for 3 small sweeps)
959+
# Each Sweep2D has 9 points with 0.05s delay = ~0.45s per sweep
960+
# Plus save_data overhead, 30s is generous
961+
result = call_mcp_tool(
962+
base_url,
963+
"measureit_wait_for_sweep",
964+
{
965+
"variable_name": "sweep_queue",
966+
"timeout": 30,
967+
"kill": True,
968+
"detailed": True,
969+
},
970+
)
971+
success, content = parse_tool_result(result)
972+
973+
# Verify wait_for_sweep did not timeout (no early exit)
974+
assert (
975+
"timed_out" not in content or '"timed_out": false' in content.lower()
976+
), f"wait_for_sweep should not timeout - may have exited early: {content}"
977+
978+
# Verify the queue completed (state should be idle after completion and kill)
979+
# The sweep should have finished successfully
980+
assert (
981+
success or "sweep_queue" in content
982+
), f"wait_for_sweep should complete successfully: {content}"
983+
984+
# Verify no error occurred during the sweep
985+
if "sweep_error" in content:
986+
# If there's a sweep_error, this is a different issue (not early exit)
987+
assert (
988+
'"sweep_error": false' in content.lower()
989+
or "sweep_error" not in content
990+
), f"Sweep completed with error (not early exit issue): {content}"
991+
992+
# CRITICAL: Verify no premature exit by checking the final state
993+
# If wait_for_sweep exited early with "pending" state, it would show that
994+
assert (
995+
'"state": "pending"' not in content.lower()
996+
), f"wait_for_sweep exited with 'pending' state - this indicates early exit bug: {content}"
997+
998+
# Also check that not_started is not in the response
999+
assert (
1000+
"not_started" not in content.lower()
1001+
), f"wait_for_sweep returned not_started - queue was not properly waited for: {content}"
1002+
1003+
# Verify the queue is now idle (all sweeps completed)
1004+
# After all sweeps complete and kill is called, state should be "idle" or "killed"
1005+
assert (
1006+
'"state": "idle"' in content.lower()
1007+
or '"state": "killed"' in content.lower()
1008+
), f"Queue should be idle/killed after completion, got: {content}"

0 commit comments

Comments
 (0)