dsh-labscript 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,239 +1,309 @@
1
- """Self-contained tests for the python bridge core (mock backend only).
2
-
3
- Run: python python/tests/run_tests.py
4
- No pytest, no labscript, no hardware: numpy and h5py are enough.
5
- """
6
- import json
7
- import os
8
- import shutil
9
- import sys
10
- import tempfile
11
-
12
- HERE = os.path.dirname(os.path.abspath(__file__))
13
- sys.path.insert(0, os.path.dirname(HERE))
14
- EXAMPLES = os.path.join(os.path.dirname(os.path.dirname(HERE)), "examples")
15
-
16
- import bridge
17
-
18
-
19
- def make_config(tmpdir):
20
- return {
21
- "backend": "mock-labscript",
22
- "mock": {"stateDir": os.path.join(tmpdir, "mock")},
23
- "panelHistoryDir": os.path.join(tmpdir, "history"),
24
- "calibrationLog": os.path.join(tmpdir, "calibration.jsonl"),
25
- "engage": {"timeoutS": 10, "pollS": 0.05, "settleS": 0.05},
26
- "postprocess": {"entry": "postprocess_demo:analyze", "pythonPaths": [EXAMPLES]},
27
- }
28
-
29
-
30
- def protocol_raw():
31
- return {
32
- "name": "demo_scan",
33
- "labscript": "/tmp/mock_sequence.py",
34
- "set": {"amplitude": 0.5},
35
- "scan": {"duration_test_us": "np.linspace(0, 2, 3)"},
36
- "reps": 2,
37
- "postprocess": "demo.peaks",
38
- "finish": {
39
- "restore": True,
40
- "panel": {"duration_test_us": "fit.center"},
41
- "history": {"demo.peaks.center_us": "fit.center"},
42
- },
43
- }
44
-
45
-
46
- def test_scan_expressions():
47
- assert bridge.eval_scan_expr("np.arange(0, 1, 0.25)").size == 4
48
- assert bridge.eval_scan_expr("np.linspace(0, 2, 5)").size == 5
49
- assert bridge.eval_scan_expr([0, 1, 2]).size == 3
50
- assert bridge.eval_scan_expr(3).size == 1
51
- for bad in ["os.system('x')", "__import__('os')", "np.ones(3)" ]:
52
- try:
53
- bridge.eval_scan_expr(bad)
54
- except bridge.BridgeError as exc:
55
- assert exc.code == "BAD_SCAN_EXPR", exc.message
56
- else:
57
- raise AssertionError("expected rejection for " + bad)
58
-
59
-
60
- def test_mock_status_and_set():
61
- tmp = tempfile.mkdtemp(prefix="dsh-labscript-py-")
62
- try:
63
- cfg = make_config(tmp)
64
- status = bridge.cmd_status(cfg, {})
65
- assert status["n_shots"] == 1
66
- out = bridge.cmd_set(cfg, {"values": {"amplitude": 0.7}, "reps": 2, "note": "test"})
67
- assert out["n_shots"] == 2
68
- assert out["missing"] == []
69
- assert out["snapshot"]["name"].endswith(".json")
70
- assert out["readback"]["amplitude"] == 0.7
71
- raw = bridge.cmd_status(cfg, {"raw": True})
72
- assert raw["raw_globals"]["amplitude"] == "0.7"
73
- restored = bridge.cmd_restore(cfg, {"snapshot": "latest"})
74
- assert restored["restored_from"]["file"]
75
- assert bridge.cmd_status(cfg, {})["n_shots"] == 1
76
- finally:
77
- shutil.rmtree(tmp, ignore_errors=True)
78
-
79
-
80
- def test_protocol_end_to_end():
81
- tmp = tempfile.mkdtemp(prefix="dsh-labscript-protocol-")
82
- try:
83
- cfg = make_config(tmp)
84
- planned = bridge.cmd_protocol_plan(cfg, {"protocol": protocol_raw()})
85
- assert planned["plan"]["expected_n_shots"] == 6
86
- started = bridge.cmd_protocol_start(cfg, {"protocol": protocol_raw(), "note": "cal demo"})
87
- assert started["n_shots_at_submit"] == 6
88
- run_folder = started["engage"]["run_folder"]
89
- waited = bridge.wait_for_run(cfg, run_folder, 6, 10)
90
- assert waited["complete"] is True, waited
91
- assert waited["n_h5"] == 6
92
- final = bridge.cmd_protocol_finalize(cfg, {
93
- "protocol": protocol_raw(),
94
- "run_folder": run_folder,
95
- "snapshot": started["snapshot"],
96
- })
97
- assert final["postprocess"]["fit"]["center"] == 2.5
98
- assert final["restore"]["restored"] is True
99
- panel = bridge.cmd_status(cfg, {})["globals"]
100
- assert panel["duration_test_us"] == 2.5
101
- assert panel["amplitude"] == 0.5
102
- with open(cfg["calibrationLog"], "r", encoding="utf-8") as handle:
103
- rows = [json.loads(line) for line in handle if line.strip()]
104
- assert len(rows) == 1
105
- assert rows[0]["key"] == "demo.peaks.center_us"
106
- assert rows[0]["value"] == 2.5
107
- events = os.path.join(cfg["panelHistoryDir"], "events.jsonl")
108
- assert os.path.isfile(events)
109
- assert len(open(events, encoding="utf-8").readlines()) >= 2
110
- finally:
111
- shutil.rmtree(tmp, ignore_errors=True)
112
-
113
-
114
- def test_hard_limits():
115
- tmp = tempfile.mkdtemp(prefix="dsh-labscript-limits-")
116
- try:
117
- cfg = make_config(tmp)
118
- limits_path = os.path.join(tmp, "limits.json")
119
- with open(limits_path, "w", encoding="utf-8") as handle:
120
- json.dump({"limits": {"amplitude": {"max": 0.4}}, "run_limits": {"max_total_shots": 4}}, handle)
121
- cfg["hardLimitsFile"] = limits_path
122
- raw = protocol_raw()
123
- try:
124
- bridge.cmd_protocol_plan(cfg, {"protocol": raw})
125
- except bridge.BridgeError as exc:
126
- assert exc.code == "HARD_LIMIT_EXCEEDED", exc.message
127
- findings = exc.detail["findings"]
128
- assert any(item["kind"] == "over_max" for item in findings)
129
- else:
130
- raise AssertionError("expected hard limit rejection")
131
- raw["set"] = {}
132
- raw["scan"] = {"duration_test_us": "np.linspace(0, 2, 3)"}
133
- raw["reps"] = 3
134
- try:
135
- bridge.cmd_protocol_plan(cfg, {"protocol": raw})
136
- except bridge.BridgeError as exc:
137
- findings = exc.detail["findings"]
138
- assert any(item["kind"] == "over_max_total_shots" for item in findings)
139
- else:
140
- raise AssertionError("expected shot limit rejection")
141
- finally:
142
- shutil.rmtree(tmp, ignore_errors=True)
143
-
144
-
145
- def test_array_values_through_set():
146
- tmp = tempfile.mkdtemp(prefix="dsh-labscript-array-")
147
- try:
148
- cfg = make_config(tmp)
149
- out = bridge.cmd_set(cfg, {"values": {"duration_test_us": [0.0, 0.5, 1.0]}, "note": "array set"})
150
- assert out["n_shots"] == 3, out
151
- raw = out["raw_readback"]["duration_test_us"]
152
- assert raw.startswith("np.linspace(") or raw.startswith("np.array(["), raw
153
- raw = bridge.cmd_status(cfg, {"raw": True})
154
- assert raw["globals"] is not None
155
- finally:
156
- shutil.rmtree(tmp, ignore_errors=True)
157
-
158
-
159
- def test_data_reading():
160
- tmp = tempfile.mkdtemp(prefix="dsh-labscript-data-")
161
- try:
162
- cfg = make_config(tmp)
163
- started = bridge.cmd_protocol_start(cfg, {"protocol": protocol_raw()})
164
- run_folder = started["engage"]["run_folder"]
165
- data = bridge.cmd_data(cfg, {"run_folder": run_folder, "scan": "duration_test_us"})
166
- assert data["n_h5"] == 6
167
- assert data["scan_table"]["n_points"] == 6
168
- assert data["globals"]["amplitude"] == 0.5
169
- keys = data["scan_table"]["rows"][0].keys()
170
- assert any(str(key).startswith("demo.") for key in keys), list(keys)
171
- finally:
172
- shutil.rmtree(tmp, ignore_errors=True)
173
-
174
-
175
- def test_selective_restore():
176
- tmp = tempfile.mkdtemp(prefix="dsh-labscript-selective-")
177
- try:
178
- cfg = make_config(tmp)
179
- bridge.cmd_set(cfg, {"values": {"amplitude": 0.61}})
180
- second = bridge.cmd_set(cfg, {"values": {"do_acquire": False}})
181
- bridge.cmd_set(cfg, {"values": {"amplitude": 0.66}})
182
- restored = bridge.cmd_restore(cfg, {"snapshot": second["snapshot"]["name"]})
183
- panel = bridge.cmd_status(cfg, {})["globals"]
184
- assert restored["selective"] is True, restored
185
- assert panel["amplitude"] == 0.66, panel
186
- assert panel["do_acquire"] is True, panel
187
- finally:
188
- shutil.rmtree(tmp, ignore_errors=True)
189
-
190
-
191
- def test_wait_diagnosis_compiled_not_executed():
192
- import h5py
193
- tmp = tempfile.mkdtemp(prefix="dsh-labscript-diag-")
194
- try:
195
- cfg = make_config(tmp)
196
- cfg["engage"] = {"timeoutS": 5, "pollS": 0.05, "settleS": 0.1, "stallS": 0.3}
197
- run_dir = os.path.join(tmp, "compiled_run")
198
- os.makedirs(run_dir, exist_ok=True)
199
- with h5py.File(os.path.join(run_dir, "shot_0000.h5"), "w") as handle:
200
- group = handle.create_group("globals")
201
- group.attrs["dummy_iterator"] = 0
202
- result = bridge.wait_for_run(cfg, run_dir, 1, 4)
203
- assert result["complete"] is False, result
204
- assert result["diagnosis"] == "compiled_not_executed", result
205
- assert result["executed"] is False, result
206
- finally:
207
- shutil.rmtree(tmp, ignore_errors=True)
208
-
209
-
210
- def test_doctor_mock():
211
- tmp = tempfile.mkdtemp(prefix="dsh-labscript-doctor-")
212
- try:
213
- cfg = make_config(tmp)
214
- out = bridge.cmd_doctor(cfg, {})
215
- names = dict((check["name"], check["status"]) for check in out["checks"])
216
- assert names.get("python") == "ok", names
217
- assert names.get("numpy") == "ok", names
218
- assert names.get("h5py") == "ok", names
219
- assert names.get("backend") == "ok", names
220
- finally:
221
- shutil.rmtree(tmp, ignore_errors=True)
222
-
223
-
224
- def main():
225
- tests = [value for name, value in sorted(globals().items()) if name.startswith("test_")]
226
- failures = 0
227
- for test in tests:
228
- try:
229
- test()
230
- print("ok " + test.__name__)
231
- except Exception as exc: # noqa: BLE001
232
- failures += 1
233
- print("FAIL " + test.__name__ + ": " + repr(exc))
234
- print(str(len(tests)) + " tests, " + str(failures) + " failures")
235
- return 1 if failures else 0
236
-
237
-
238
- if __name__ == "__main__":
239
- sys.exit(main())
1
+ """Self-contained tests for the python bridge core (mock backend only).
2
+
3
+ Run: python python/tests/run_tests.py
4
+ No pytest, no labscript, no hardware: numpy and h5py are enough.
5
+ """
6
+ import json
7
+ import os
8
+ import shutil
9
+ import sys
10
+ import tempfile
11
+
12
+ HERE = os.path.dirname(os.path.abspath(__file__))
13
+ sys.path.insert(0, os.path.dirname(HERE))
14
+ EXAMPLES = os.path.join(os.path.dirname(os.path.dirname(HERE)), "examples")
15
+
16
+ import bridge
17
+
18
+
19
+ def make_config(tmpdir):
20
+ return {
21
+ "backend": "mock-labscript",
22
+ "mock": {"stateDir": os.path.join(tmpdir, "mock")},
23
+ "panelHistoryDir": os.path.join(tmpdir, "history"),
24
+ "calibrationLog": os.path.join(tmpdir, "calibration.jsonl"),
25
+ "engage": {"timeoutS": 10, "pollS": 0.05, "settleS": 0.05},
26
+ "postprocess": {"entry": "postprocess_demo:analyze", "pythonPaths": [EXAMPLES]},
27
+ }
28
+
29
+
30
+ def protocol_raw():
31
+ return {
32
+ "name": "demo_scan",
33
+ "labscript": "/tmp/mock_sequence.py",
34
+ "set": {"amplitude": 0.5},
35
+ "scan": {"duration_test_us": "np.linspace(0, 2, 3)"},
36
+ "reps": 2,
37
+ "postprocess": "demo.peaks",
38
+ "finish": {
39
+ "restore": True,
40
+ "panel": {"duration_test_us": "fit.center"},
41
+ "history": {"demo.peaks.center_us": "fit.center"},
42
+ },
43
+ }
44
+
45
+
46
+ def test_scan_expressions():
47
+ assert bridge.eval_scan_expr("np.arange(0, 1, 0.25)").size == 4
48
+ assert bridge.eval_scan_expr("np.linspace(0, 2, 5)").size == 5
49
+ assert bridge.eval_scan_expr([0, 1, 2]).size == 3
50
+ assert bridge.eval_scan_expr(3).size == 1
51
+ for bad in ["os.system('x')", "__import__('os')", "np.ones(3)" ]:
52
+ try:
53
+ bridge.eval_scan_expr(bad)
54
+ except bridge.BridgeError as exc:
55
+ assert exc.code == "BAD_SCAN_EXPR", exc.message
56
+ else:
57
+ raise AssertionError("expected rejection for " + bad)
58
+
59
+
60
+ def test_mock_status_and_set():
61
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-py-")
62
+ try:
63
+ cfg = make_config(tmp)
64
+ status = bridge.cmd_status(cfg, {})
65
+ assert status["n_shots"] == 1
66
+ out = bridge.cmd_set(cfg, {"values": {"amplitude": 0.7}, "reps": 2, "note": "test"})
67
+ assert out["n_shots"] == 2
68
+ assert out["missing"] == []
69
+ assert out["snapshot"]["name"].endswith(".json")
70
+ assert out["readback"]["amplitude"] == 0.7
71
+ raw = bridge.cmd_status(cfg, {"raw": True})
72
+ assert raw["raw_globals"]["amplitude"] == "0.7"
73
+ restored = bridge.cmd_restore(cfg, {"snapshot": "latest"})
74
+ assert restored["restored_from"]["file"]
75
+ assert bridge.cmd_status(cfg, {})["n_shots"] == 1
76
+ finally:
77
+ shutil.rmtree(tmp, ignore_errors=True)
78
+
79
+
80
+ def test_protocol_end_to_end():
81
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-protocol-")
82
+ try:
83
+ cfg = make_config(tmp)
84
+ planned = bridge.cmd_protocol_plan(cfg, {"protocol": protocol_raw()})
85
+ assert planned["plan"]["expected_n_shots"] == 6
86
+ started = bridge.cmd_protocol_start(cfg, {"protocol": protocol_raw(), "note": "cal demo"})
87
+ assert started["n_shots_at_submit"] == 6
88
+ run_folder = started["engage"]["run_folder"]
89
+ waited = bridge.wait_for_run(cfg, run_folder, 6, 10)
90
+ assert waited["complete"] is True, waited
91
+ assert waited["n_h5"] == 6
92
+ final = bridge.cmd_protocol_finalize(cfg, {
93
+ "protocol": protocol_raw(),
94
+ "run_folder": run_folder,
95
+ "snapshot": started["snapshot"],
96
+ })
97
+ assert final["postprocess"]["fit"]["center"] == 2.5
98
+ assert final["restore"]["restored"] is True
99
+ panel = bridge.cmd_status(cfg, {})["globals"]
100
+ assert panel["duration_test_us"] == 2.5
101
+ assert panel["amplitude"] == 0.5
102
+ with open(cfg["calibrationLog"], "r", encoding="utf-8") as handle:
103
+ rows = [json.loads(line) for line in handle if line.strip()]
104
+ assert len(rows) == 1
105
+ assert rows[0]["key"] == "demo.peaks.center_us"
106
+ assert rows[0]["value"] == 2.5
107
+ events = os.path.join(cfg["panelHistoryDir"], "events.jsonl")
108
+ assert os.path.isfile(events)
109
+ assert len(open(events, encoding="utf-8").readlines()) >= 2
110
+ finally:
111
+ shutil.rmtree(tmp, ignore_errors=True)
112
+
113
+
114
+ def test_hard_limits():
115
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-limits-")
116
+ try:
117
+ cfg = make_config(tmp)
118
+ limits_path = os.path.join(tmp, "limits.json")
119
+ with open(limits_path, "w", encoding="utf-8") as handle:
120
+ json.dump({"limits": {"amplitude": {"max": 0.4}}, "run_limits": {"max_total_shots": 4}}, handle)
121
+ cfg["hardLimitsFile"] = limits_path
122
+ raw = protocol_raw()
123
+ try:
124
+ bridge.cmd_protocol_plan(cfg, {"protocol": raw})
125
+ except bridge.BridgeError as exc:
126
+ assert exc.code == "HARD_LIMIT_EXCEEDED", exc.message
127
+ findings = exc.detail["findings"]
128
+ assert any(item["kind"] == "over_max" for item in findings)
129
+ else:
130
+ raise AssertionError("expected hard limit rejection")
131
+ raw["set"] = {}
132
+ raw["scan"] = {"duration_test_us": "np.linspace(0, 2, 3)"}
133
+ raw["reps"] = 3
134
+ try:
135
+ bridge.cmd_protocol_plan(cfg, {"protocol": raw})
136
+ except bridge.BridgeError as exc:
137
+ findings = exc.detail["findings"]
138
+ assert any(item["kind"] == "over_max_total_shots" for item in findings)
139
+ else:
140
+ raise AssertionError("expected shot limit rejection")
141
+ finally:
142
+ shutil.rmtree(tmp, ignore_errors=True)
143
+
144
+
145
+ def test_boolean_hard_limits():
146
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-limits-bool-")
147
+ try:
148
+ cfg = make_config(tmp)
149
+ limits_path = os.path.join(tmp, "limits.json")
150
+ with open(limits_path, "w", encoding="utf-8") as handle:
151
+ json.dump({"limits": {"KeepMOT": {"max": 0}, "do_acquire": {"min": 1}}}, handle)
152
+ cfg["hardLimitsFile"] = limits_path
153
+ for values, kind in (({"KeepMOT": True}, "over_max"), ({"do_acquire": False}, "under_min")):
154
+ try:
155
+ bridge.cmd_set(cfg, {"values": values})
156
+ except bridge.BridgeError as exc:
157
+ assert exc.code == "HARD_LIMIT_EXCEEDED", exc.message
158
+ assert any(item["kind"] == kind for item in exc.detail["findings"]), exc.detail
159
+ else:
160
+ raise AssertionError("expected boolean hard-limit rejection for " + str(values))
161
+ ok = bridge.cmd_set(cfg, {"values": {"KeepMOT": False, "do_acquire": True}})
162
+ assert ok["n_shots"] >= 1
163
+ finally:
164
+ shutil.rmtree(tmp, ignore_errors=True)
165
+
166
+
167
+ def test_script_name_check():
168
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-names-")
169
+ try:
170
+ path = os.path.join(tmp, "sequence.py")
171
+ with open(path, "w", encoding="utf-8") as handle:
172
+ handle.write("freq = 1.0\namplitude = 0.5\n# duration_old_us is a comment\n")
173
+ info = bridge.script_name_check(path, ["freq", "old_duration_us", "amplitude"])
174
+ assert info["checked"] is True, info
175
+ assert info["missing"] == ["old_duration_us"], info
176
+ assert isinstance(info["suggestions"]["old_duration_us"], list)
177
+ missing_path = bridge.script_name_check(os.path.join(tmp, "nope.py"), ["freq"])
178
+ assert missing_path["checked"] is False
179
+ finally:
180
+ shutil.rmtree(tmp, ignore_errors=True)
181
+
182
+
183
+ def test_hard_limits_min_max():
184
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-limits-mm-")
185
+ try:
186
+ cfg = make_config(tmp)
187
+ limits_path = os.path.join(tmp, "limits.json")
188
+ with open(limits_path, "w", encoding="utf-8") as handle:
189
+ json.dump({"limits": {
190
+ "amplitude": {"min": 0.2, "max": 0.8},
191
+ "duration_test_us": {"min": 0.5, "max": 2.0},
192
+ }}, handle)
193
+ cfg["hardLimitsFile"] = limits_path
194
+ for value, kind in ((0.1, "under_min"), (0.9, "over_max")):
195
+ try:
196
+ bridge.cmd_set(cfg, {"values": {"amplitude": value}})
197
+ except bridge.BridgeError as exc:
198
+ assert exc.code == "HARD_LIMIT_EXCEEDED", exc.message
199
+ assert any(item["kind"] == kind for item in exc.detail["findings"]), exc.detail
200
+ else:
201
+ raise AssertionError("expected " + kind + " rejection")
202
+ good = bridge.cmd_set(cfg, {"values": {"amplitude": 0.5}})
203
+ assert good["n_shots"] >= 1
204
+ try:
205
+ bridge.cmd_set(cfg, {"values": {"duration_test_us": "np.linspace(0, 3, 3)"}, "raw": True})
206
+ except bridge.BridgeError as exc:
207
+ kinds = [item["kind"] for item in exc.detail["findings"]]
208
+ assert "under_min_scan" in kinds and "over_max_scan" in kinds, kinds
209
+ else:
210
+ raise AssertionError("expected scan min/max rejection")
211
+ finally:
212
+ shutil.rmtree(tmp, ignore_errors=True)
213
+
214
+
215
+ def test_array_values_through_set():
216
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-array-")
217
+ try:
218
+ cfg = make_config(tmp)
219
+ out = bridge.cmd_set(cfg, {"values": {"duration_test_us": [0.0, 0.5, 1.0]}, "note": "array set"})
220
+ assert out["n_shots"] == 3, out
221
+ raw = out["raw_readback"]["duration_test_us"]
222
+ assert raw.startswith("np.linspace(") or raw.startswith("np.array(["), raw
223
+ raw = bridge.cmd_status(cfg, {"raw": True})
224
+ assert raw["globals"] is not None
225
+ finally:
226
+ shutil.rmtree(tmp, ignore_errors=True)
227
+
228
+
229
+ def test_data_reading():
230
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-data-")
231
+ try:
232
+ cfg = make_config(tmp)
233
+ started = bridge.cmd_protocol_start(cfg, {"protocol": protocol_raw()})
234
+ run_folder = started["engage"]["run_folder"]
235
+ data = bridge.cmd_data(cfg, {"run_folder": run_folder, "scan": "duration_test_us"})
236
+ assert data["n_h5"] == 6
237
+ assert data["scan_table"]["n_points"] == 6
238
+ assert data["globals"]["amplitude"] == 0.5
239
+ keys = data["scan_table"]["rows"][0].keys()
240
+ assert any(str(key).startswith("demo.") for key in keys), list(keys)
241
+ finally:
242
+ shutil.rmtree(tmp, ignore_errors=True)
243
+
244
+
245
+ def test_selective_restore():
246
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-selective-")
247
+ try:
248
+ cfg = make_config(tmp)
249
+ bridge.cmd_set(cfg, {"values": {"amplitude": 0.61}})
250
+ second = bridge.cmd_set(cfg, {"values": {"do_acquire": False}})
251
+ bridge.cmd_set(cfg, {"values": {"amplitude": 0.66}})
252
+ restored = bridge.cmd_restore(cfg, {"snapshot": second["snapshot"]["name"]})
253
+ panel = bridge.cmd_status(cfg, {})["globals"]
254
+ assert restored["selective"] is True, restored
255
+ assert panel["amplitude"] == 0.66, panel
256
+ assert panel["do_acquire"] is True, panel
257
+ finally:
258
+ shutil.rmtree(tmp, ignore_errors=True)
259
+
260
+
261
+ def test_wait_diagnosis_compiled_not_executed():
262
+ import h5py
263
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-diag-")
264
+ try:
265
+ cfg = make_config(tmp)
266
+ cfg["engage"] = {"timeoutS": 5, "pollS": 0.05, "settleS": 0.1, "stallS": 0.3}
267
+ run_dir = os.path.join(tmp, "compiled_run")
268
+ os.makedirs(run_dir, exist_ok=True)
269
+ with h5py.File(os.path.join(run_dir, "shot_0000.h5"), "w") as handle:
270
+ group = handle.create_group("globals")
271
+ group.attrs["dummy_iterator"] = 0
272
+ result = bridge.wait_for_run(cfg, run_dir, 1, 4)
273
+ assert result["complete"] is False, result
274
+ assert result["diagnosis"] == "compiled_not_executed", result
275
+ assert result["executed"] is False, result
276
+ finally:
277
+ shutil.rmtree(tmp, ignore_errors=True)
278
+
279
+
280
+ def test_doctor_mock():
281
+ tmp = tempfile.mkdtemp(prefix="dsh-labscript-doctor-")
282
+ try:
283
+ cfg = make_config(tmp)
284
+ out = bridge.cmd_doctor(cfg, {})
285
+ names = dict((check["name"], check["status"]) for check in out["checks"])
286
+ assert names.get("python") == "ok", names
287
+ assert names.get("numpy") == "ok", names
288
+ assert names.get("h5py") == "ok", names
289
+ assert names.get("backend") == "ok", names
290
+ finally:
291
+ shutil.rmtree(tmp, ignore_errors=True)
292
+
293
+
294
+ def main():
295
+ tests = [value for name, value in sorted(globals().items()) if name.startswith("test_")]
296
+ failures = 0
297
+ for test in tests:
298
+ try:
299
+ test()
300
+ print("ok " + test.__name__)
301
+ except Exception as exc: # noqa: BLE001
302
+ failures += 1
303
+ print("FAIL " + test.__name__ + ": " + repr(exc))
304
+ print(str(len(tests)) + " tests, " + str(failures) + " failures")
305
+ return 1 if failures else 0
306
+
307
+
308
+ if __name__ == "__main__":
309
+ sys.exit(main())