forked from lightwave-lab/lightlab
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtest_labstate.py
390 lines (312 loc) · 13.4 KB
/
test_labstate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
'''Tests whether the functionality of the laboratory module is working properly.'''
import pytest
import lightlab.laboratory.state as labstate
from lightlab.laboratory.instruments import LocalHost, Host, Bench, Instrument, Keithley
from lightlab.laboratory.devices import Device
from lightlab.laboratory.experiments import Experiment
from lightlab.laboratory.virtualization import DualFunction
from lightlab.equipment.lab_instruments import Keithley_2400_SM
import json
import time
import os
import logging
from freezegun import freeze_time
logging.disable(logging.CRITICAL)
filename = 'test_{}.json'.format(int(time.time()))
labstate._filename = filename
# Shared objects
Host1 = LocalHost(name="Host1")
Host2 = Host(name="Host2")
Bench1 = Bench(name="Bench1")
Bench2 = Bench(name="Bench2")
def open_error(self):
raise RuntimeError("self.open() function being called upon initialization.")
__GETCURRENTTEST = 0.123
Keithley_2400_SM.startup = lambda self: True
Keithley_2400_SM.getCurrent = lambda self: __GETCURRENTTEST
Keithley_2400_SM.open = open_error
instrument1 = Keithley(name="keithley1", bench=Bench1, host=Host1,
ports=["front_source", "rear_source"], _driver_class=Keithley_2400_SM)
instrument2 = Instrument(name="instrument2", bench=Bench1, host=Host1, ports=["port1", "channel"])
device1 = Device(name="device1", bench=Bench1, ports=["input", "output"])
device2 = Device(name="device2", bench=Bench1, ports=["input", "output"])
@pytest.fixture()
def lab(request, monkeypatch):
lab = labstate.LabState(filename=filename)
lab.updateHost(Host1)
lab.updateHost(Host2)
lab.updateBench(Bench1, Bench2)
lab.insertInstrument(instrument1)
lab.insertInstrument(instrument2)
lab.insertDevice(device1)
lab.insertDevice(device2)
connections1 = [{device1: "input", instrument1: "rear_source"},
{device1: "output", instrument2: "port1"}]
lab.updateConnections(*connections1)
lab._saveState(save_backup=False)
monkeypatch.setattr(labstate, 'initializing', False)
monkeypatch.setattr(labstate, 'lab', lab)
def delete_file():
os.remove(filename)
request.addfinalizer(delete_file)
return lab
def test_insert(lab):
Bench1.addInstrument(instrument1, instrument2)
assert instrument1 in Bench1
assert instrument2 in Bench1
with pytest.raises(TypeError):
Bench1.addInstrument(device1)
def test_duplicate_insert(lab):
instrument2bis = Instrument(name="instrument2", bench=Bench2, host=Host1, ports=["port11", "channel"])
with pytest.raises(RuntimeError):
lab.insertInstrument(instrument2bis) # does not allow insertion of duplicate!
def test_instantiate(lab):
'''Initializing LabState with two instruments, devices, and interconnections,
asserts if connections are properly made'''
assert Bench1 in lab.benches.values()
assert Bench2 in lab.benches.values()
assert len(lab.benches) == 2
assert Host1 in lab.hosts.values()
assert Host2 in lab.hosts.values()
assert len(lab.hosts) == 2
assert instrument1 in Bench1
assert instrument1 in Bench1.instruments
assert instrument1.bench == Bench1
assert instrument1 in Bench1.instruments
assert instrument1 in Host1.instruments
assert instrument1.host == Host1
assert instrument1 in lab.instruments
assert instrument2 in lab.instruments
assert instrument2 in Bench1
assert instrument2 in Bench1.instruments
assert len(lab.instruments) == 2
assert device1 in lab.devices
assert device1 in Bench1
assert device2 in Bench1
assert len(lab.devices) == 2
assert {device1: "input", instrument1: "rear_source"} in lab.connections
assert {device1: "output", instrument2: "port1"} in lab.connections
assert {device1: "input", instrument1: "front_source"} not in lab.connections
assert {device1: "output", instrument2: "channel"} not in lab.connections
def test_change_bench(lab):
assert instrument2.bench == Bench1
instrument2.bench = None
assert instrument2 in lab.instruments
assert instrument2 not in Bench1.instruments
instrument2.bench = Bench2
assert instrument2 in lab.instruments
assert instrument2 in Bench2.instruments
instrument2.bench = Bench1
assert instrument2.bench == Bench1
def test_change_bench_alternative(lab):
assert instrument2.bench == Bench1
Bench2.addInstrument(instrument2)
assert instrument2 in lab.instruments
assert instrument2 in Bench2.instruments
Bench2.removeInstrument(instrument2)
assert instrument2 not in Bench2
assert instrument2 in lab.instruments
Bench1.addInstrument(instrument2)
assert instrument2.bench == Bench1
def test_change_bench_device(lab):
assert device2.bench == Bench1
device2.bench = None
assert device2 in lab.devices
assert device2 not in Bench1.devices
device2.bench = Bench2
assert device2 in lab.devices
assert device2 in Bench2.devices
device2.bench = Bench1
assert device2.bench == Bench1
def test_change_bench_device_alternative(lab):
assert device2.bench == Bench1
Bench2.addDevice(device2)
assert device2 in lab.devices
assert device2 in Bench2.devices
Bench2.removeDevice(device2)
assert device2 not in Bench2
assert device2 in lab.devices
Bench1.addDevice(device2)
assert device2.bench == Bench1
def test_change_host(lab):
assert instrument2.host == Host1
instrument2.host = None
assert instrument2 in lab.instruments
assert instrument2 not in Host1.instruments
instrument2.host = Host2
assert instrument2 in lab.instruments
assert instrument2 in Host2.instruments
instrument2.host = Host1
assert instrument2.host == Host1
def test_bench_iteration(lab):
benches_items = []
benches_names = []
for bench_name, bench in lab.benches.items():
device_names = [str(device) for device in bench.devices]
instrument_names = [str(instrument) for instrument in bench.instruments]
assert bench_name == bench.name
benches_items.append(bench)
benches_names.append(bench_name)
benches_values = []
for bench in lab.benches.values():
benches_values.append(bench)
assert benches_items == benches_values
assert benches_names == [bench.name for bench in benches_values]
def test_display():
Bench1.display()
Bench2.display()
Host1.display()
Host2.display()
instrument1.display()
instrument2.display()
device1.display()
device2.display()
def test_delete(lab):
lab.instruments.remove(instrument2)
assert instrument2 not in lab.instruments
def test_savestate(lab):
h1 = lab.hosts["Host1"]
h1.mac_address = "test_savestate"
lab.updateHost(h1)
lab.saveState(filename, save_backup=False)
@freeze_time("2023-02-21")
def test_reloadlabstate(lab):
''' Saves and reloads LabState and asserts equality '''
lab2 = labstate.LabState.loadState(filename=filename)
assert lab == lab2
def test_instrument_method_from_frozen(lab):
lab2 = labstate.LabState.loadState(filename=filename)
keithley = lab2.instruments_dict["keithley1"]
assert keithley.getCurrent() == __GETCURRENTTEST
def test_corruptreload_extratext(lab):
''' Saves Labstate, corrupts JSON file, reloads from file, and test exception
'''
# Testing appending text to end
with open(filename, 'a') as file:
file.write("extra text")
with pytest.raises(json.decoder.JSONDecodeError):
labstate.LabState.loadState(filename) # , message="fail to detect extraneous words outside JSON"
def test_corruptreload_changecontent(lab):
''' Saves Labstate, corrupts JSON file, reloads from file, and test exception
'''
# Testing changing json content
with open(filename, 'r') as file:
frozen_json = file.read()
json_state = json.loads(frozen_json)
json_state["py/state"]["benches"]["py/state"]["list"][0]["py/state"]["name"] = "Bench3"
refrozen_json = json.dumps(json_state, sort_keys=True, indent=4)
with open(filename, 'w') as file:
file.write(refrozen_json)
with pytest.raises(json.decoder.JSONDecodeError):
labstate.LabState.loadState(filename) # , message="corruption within file was not detected"
def test_update(lab):
''' Updates information on hosts, benches or instruments'''
new_host2 = Host(name="Host2", hostname='foo')
old_host2 = lab.hosts["Host2"]
lab.hosts["Host2"] = new_host2
assert lab.hosts["Host2"] == new_host2
assert lab.hosts["Host2"] != old_host2 # old_host2 got de-referenced
# WARNING! non-trivial effect true for hosts, benches and instruments:
lab.hosts["Host3"] = new_host2 # this will rename new_host2 to "Host3"
assert lab.hosts["Host3"] == new_host2
assert new_host2.name == "Host3"
assert lab.hosts["Host3"].name == "Host3"
assert "Host2" not in lab.hosts.keys()
del lab.hosts["Host3"]
assert new_host2 not in lab.hosts
def test_update_connections(lab):
''' Updates connections and test whether old connections are pruned
'''
# Changing ports in instrument
change_connections = [{device1: "output", instrument2: "channel"}]
lab.updateConnections(*change_connections)
assert {device1: "output", instrument2: "port1"} not in lab.connections
assert {device1: "output", instrument2: "channel"} in lab.connections
# Changing instruments
change_connections = [{device1: "output", instrument1: "front_source"}]
lab.updateConnections(*change_connections)
assert {device1: "output", instrument2: "channel"} not in lab.connections
assert {device1: "output", instrument1: "front_source"} in lab.connections
# Using invalid port (behavior: do not effect any change and throw error.)
change_connections = [{device1: "output", instrument1: "front_sourcez"}]
with pytest.raises(RuntimeError): # , message="unidentified port not detected")
lab.updateConnections(*change_connections)
assert {device1: "output", instrument1: "front_sourcez"} not in lab.connections
assert {device1: "output", instrument1: "front_source"} in lab.connections
def test_experiment_valid(lab):
class TestExperiment(Experiment):
warmed_up = None
cooled_down = None
def startup(self, lab):
self.lab = lab
self.registerInstruments(instrument1, instrument2, host=Host1, bench=Bench1)
connections1 = [{device1: "input", instrument1: "rear_source"},
{device1: "output", instrument2: "port1"}]
self.registerConnections(*connections1)
def hardware_warmup(self):
self.warmed_up = True
self.cooled_down = False
def hardware_cooldown(self):
self.warmed_up = False
self.cooled_down = True
@DualFunction
def test_func(self, x):
return x ** 2
@test_func.hardware
def test_func(self, x):
return x
test_experiment = TestExperiment(lab=lab)
with test_experiment.asVirtual():
assert 16 == test_experiment.test_func(4) # Testing virtualization
assert test_experiment.valid
# Testing hardware warmup and cooldown
assert test_experiment.warmed_up is None
assert test_experiment.cooled_down is None
with test_experiment.asReal():
assert test_experiment.warmed_up
assert not test_experiment.cooled_down
assert 4 == test_experiment.test_func(4) # Testing hardware function
with test_experiment.asVirtual():
assert 16 == test_experiment.test_func(4) # Testing temporary virtualization
assert 4 == test_experiment.test_func(4)
assert not test_experiment.warmed_up
assert test_experiment.cooled_down
def test_experiment_invalid_connection(lab):
class TestExperiment(Experiment):
def startup(self, lab):
self.lab = lab
self.registerInstruments(instrument1, instrument2, host=Host1, bench=Bench1)
connections1 = [{device1: "input", instrument1: "rear_source"},
{device1: "output", instrument2: "channel"}]
self.registerConnections(*connections1)
test_experiment = TestExperiment(lab=lab)
assert not test_experiment.is_valid(reset=True)
def test_remove_instrument_by_name(lab):
assert len(lab.instruments) == 2
lab.deleteInstrumentFromName("instrument2")
assert instrument2 not in lab.instruments
def test_overwriting(lab):
''' Special cases when there are instrumentation_servers '''
old_remote = Host2
updated_remote = Host(name="Host2", foo=1)
lab.updateHost(updated_remote)
assert lab.hosts["Host2"] != old_remote
assert lab.hosts["Host2"] == updated_remote
old_server = Host1
updated_server = LocalHost(name="Host1")
updated_server.mac_address = 'test_overwriting'
lab.updateHost(updated_server) # should replace entry for 'Host1'
assert lab.hosts["Host1"] != old_server
assert lab.hosts["Host1"] == updated_server
second_server = LocalHost(name="Another Host")
lab.updateHost(second_server)
assert lab.hosts["Host1"] == updated_server
with pytest.raises(KeyError):
lab.hosts["Another Host"]
def test_readonly(lab):
instrument3 = Instrument(name="instrument3", bench=None, host=None, ports=["port11", "channel"])
with pytest.raises(RuntimeError):
Host2.instruments.append(instrument3)
Bench2.instruments.append(instrument3)
Host1.instruments.dict['instrument1'] = instrument3
Bench1.instruments.dict['instrument1'] = instrument3