qcodes 0.1.0__py3-none-any.whl
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.
- qcodes/__init__.py +65 -0
- qcodes/actions.py +207 -0
- qcodes/config/__init__.py +2 -0
- qcodes/config/config.py +396 -0
- qcodes/config/qcodesrc.json +11 -0
- qcodes/config/qcodesrc_schema.json +55 -0
- qcodes/data/__init__.py +0 -0
- qcodes/data/data_array.py +520 -0
- qcodes/data/data_set.py +803 -0
- qcodes/data/format.py +309 -0
- qcodes/data/gnuplot_format.py +366 -0
- qcodes/data/hdf5_format.py +385 -0
- qcodes/data/io.py +266 -0
- qcodes/data/location.py +166 -0
- qcodes/data/manager.py +163 -0
- qcodes/instrument/__init__.py +0 -0
- qcodes/instrument/base.py +630 -0
- qcodes/instrument/function.py +134 -0
- qcodes/instrument/ip.py +236 -0
- qcodes/instrument/metaclass.py +45 -0
- qcodes/instrument/mock.py +278 -0
- qcodes/instrument/parameter.py +1001 -0
- qcodes/instrument/remote.py +529 -0
- qcodes/instrument/server.py +189 -0
- qcodes/instrument/sweep_values.py +286 -0
- qcodes/instrument/visa.py +198 -0
- qcodes/instrument_drivers/AlazarTech/ATS.py +1001 -0
- qcodes/instrument_drivers/AlazarTech/ATS9870.py +266 -0
- qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py +143 -0
- qcodes/instrument_drivers/AlazarTech/__init__.py +0 -0
- qcodes/instrument_drivers/QuTech/IVVI.py +390 -0
- qcodes/instrument_drivers/QuTech/__init__.py +0 -0
- qcodes/instrument_drivers/__init__.py +0 -0
- qcodes/instrument_drivers/agilent/Agilent_34400A.py +155 -0
- qcodes/instrument_drivers/agilent/E8527D.py +79 -0
- qcodes/instrument_drivers/agilent/HP33210A.py +104 -0
- qcodes/instrument_drivers/agilent/__init__.py +0 -0
- qcodes/instrument_drivers/agilent/test_suite.py +96 -0
- qcodes/instrument_drivers/ithaco/Ithaco_1211.py +110 -0
- qcodes/instrument_drivers/ithaco/__init__.py +0 -0
- qcodes/instrument_drivers/oxford/__init__.py +0 -0
- qcodes/instrument_drivers/oxford/mercuryiPS.py +358 -0
- qcodes/instrument_drivers/oxford/triton.py +227 -0
- qcodes/instrument_drivers/rigol/DG4000.py +591 -0
- qcodes/instrument_drivers/rigol/__init__.py +0 -0
- qcodes/instrument_drivers/rohde_schwarz/SGS100A.py +99 -0
- qcodes/instrument_drivers/rohde_schwarz/ZNB20.py +154 -0
- qcodes/instrument_drivers/rohde_schwarz/__init__.py +0 -0
- qcodes/instrument_drivers/signal_hound/USB_SA124B.py +567 -0
- qcodes/instrument_drivers/signal_hound/__init__.py +0 -0
- qcodes/instrument_drivers/stanford_research/SR560.py +116 -0
- qcodes/instrument_drivers/stanford_research/SR830.py +314 -0
- qcodes/instrument_drivers/stanford_research/SR865.py +305 -0
- qcodes/instrument_drivers/stanford_research/__init__.py +0 -0
- qcodes/instrument_drivers/tektronix/AWG5014.py +1228 -0
- qcodes/instrument_drivers/tektronix/AWG520.py +659 -0
- qcodes/instrument_drivers/tektronix/Keithley_2000.py +195 -0
- qcodes/instrument_drivers/tektronix/Keithley_2600.py +83 -0
- qcodes/instrument_drivers/tektronix/Keithley_2700.py +395 -0
- qcodes/instrument_drivers/tektronix/__init__.py +0 -0
- qcodes/instrument_drivers/test.py +94 -0
- qcodes/instrument_drivers/weinschel/Weinschel_8320.py +21 -0
- qcodes/instrument_drivers/weinschel/__init__.py +0 -0
- qcodes/instrument_drivers/weinschel/test_suite.py +27 -0
- qcodes/loops.py +1009 -0
- qcodes/measure.py +155 -0
- qcodes/plots/__init__.py +0 -0
- qcodes/plots/base.py +280 -0
- qcodes/plots/colors.py +135 -0
- qcodes/plots/pyqtgraph.py +384 -0
- qcodes/plots/qcmatplotlib.py +206 -0
- qcodes/process/__init__.py +0 -0
- qcodes/process/helpers.py +60 -0
- qcodes/process/qcodes_process.py +72 -0
- qcodes/process/server.py +395 -0
- qcodes/process/stream_queue.py +152 -0
- qcodes/station.py +178 -0
- qcodes/test.py +119 -0
- qcodes/tests/__init__.py +0 -0
- qcodes/tests/common.py +7 -0
- qcodes/tests/data_mocks.py +173 -0
- qcodes/tests/instrument_mocks.py +327 -0
- qcodes/tests/py35_syntax.py +13 -0
- qcodes/tests/test_combined_par.py +156 -0
- qcodes/tests/test_command.py +120 -0
- qcodes/tests/test_config.py +222 -0
- qcodes/tests/test_data.py +668 -0
- qcodes/tests/test_deferred_operations.py +105 -0
- qcodes/tests/test_driver_testcase.py +58 -0
- qcodes/tests/test_format.py +414 -0
- qcodes/tests/test_hdf5formatter.py +333 -0
- qcodes/tests/test_helpers.py +630 -0
- qcodes/tests/test_instrument.py +981 -0
- qcodes/tests/test_instrument_server.py +156 -0
- qcodes/tests/test_json.py +48 -0
- qcodes/tests/test_location_provider.py +109 -0
- qcodes/tests/test_loop.py +825 -0
- qcodes/tests/test_measure.py +49 -0
- qcodes/tests/test_metadata.py +49 -0
- qcodes/tests/test_multiprocessing.py +471 -0
- qcodes/tests/test_nested_attrs.py +100 -0
- qcodes/tests/test_parameter.py +151 -0
- qcodes/tests/test_plots.py +44 -0
- qcodes/tests/test_sweep_values.py +159 -0
- qcodes/tests/test_validators.py +482 -0
- qcodes/tests/test_visa.py +184 -0
- qcodes/utils/__init__.py +0 -0
- qcodes/utils/command.py +176 -0
- qcodes/utils/deferred_operations.py +276 -0
- qcodes/utils/helpers.py +417 -0
- qcodes/utils/metadata.py +42 -0
- qcodes/utils/nested_attrs.py +177 -0
- qcodes/utils/reload_code.py +98 -0
- qcodes/utils/threading.py +72 -0
- qcodes/utils/timing.py +89 -0
- qcodes/utils/validators.py +393 -0
- qcodes/version.py +1 -0
- qcodes/widgets/__init__.py +0 -0
- qcodes/widgets/display.py +47 -0
- qcodes/widgets/widgets.css +77 -0
- qcodes/widgets/widgets.js +237 -0
- qcodes/widgets/widgets.py +215 -0
- qcodes-0.1.0.dist-info/DESCRIPTION.rst +165 -0
- qcodes-0.1.0.dist-info/METADATA +194 -0
- qcodes-0.1.0.dist-info/RECORD +128 -0
- qcodes-0.1.0.dist-info/WHEEL +5 -0
- qcodes-0.1.0.dist-info/metadata.json +1 -0
- qcodes-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from qcodes.instrument.parameter import ManualParameter
|
|
5
|
+
from qcodes.measure import Measure
|
|
6
|
+
|
|
7
|
+
from .instrument_mocks import MultiGetter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestMeasure(TestCase):
|
|
11
|
+
def setUp(self):
|
|
12
|
+
self.p1 = ManualParameter('P1', initial_value=1)
|
|
13
|
+
|
|
14
|
+
def test_simple_scalar(self):
|
|
15
|
+
data = Measure(self.p1).run_temp()
|
|
16
|
+
|
|
17
|
+
self.assertEqual(data.single_set.tolist(), [0])
|
|
18
|
+
self.assertEqual(data.P1.tolist(), [1])
|
|
19
|
+
self.assertEqual(len(data.arrays), 2, data.arrays)
|
|
20
|
+
|
|
21
|
+
self.assertNotIn('loop', data.metadata)
|
|
22
|
+
|
|
23
|
+
meta = data.metadata['measurement']
|
|
24
|
+
self.assertEqual(meta['__class__'], 'qcodes.measure.Measure')
|
|
25
|
+
self.assertEqual(len(meta['actions']), 1)
|
|
26
|
+
self.assertFalse(meta['background'])
|
|
27
|
+
self.assertFalse(meta['use_data_manager'])
|
|
28
|
+
self.assertFalse(meta['use_threads'])
|
|
29
|
+
|
|
30
|
+
ts_start = datetime.strptime(meta['ts_start'], '%Y-%m-%d %H:%M:%S')
|
|
31
|
+
ts_end = datetime.strptime(meta['ts_end'], '%Y-%m-%d %H:%M:%S')
|
|
32
|
+
self.assertGreaterEqual(ts_end, ts_start)
|
|
33
|
+
|
|
34
|
+
def test_simple_array(self):
|
|
35
|
+
data = Measure(MultiGetter(arr=(1.2, 3.4))).run_temp()
|
|
36
|
+
|
|
37
|
+
self.assertEqual(data.index0.tolist(), [0, 1])
|
|
38
|
+
self.assertEqual(data.arr.tolist(), [1.2, 3.4])
|
|
39
|
+
self.assertEqual(len(data.arrays), 2, data.arrays)
|
|
40
|
+
|
|
41
|
+
def test_array_and_scalar(self):
|
|
42
|
+
self.p1.set(42)
|
|
43
|
+
data = Measure(MultiGetter(arr=(5, 6)), self.p1).run_temp()
|
|
44
|
+
|
|
45
|
+
self.assertEqual(data.single_set.tolist(), [0])
|
|
46
|
+
self.assertEqual(data.P1.tolist(), [42])
|
|
47
|
+
self.assertEqual(data.index0.tolist(), [0, 1])
|
|
48
|
+
self.assertEqual(data.arr.tolist(), [5, 6])
|
|
49
|
+
self.assertEqual(len(data.arrays), 4, data.arrays)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
|
|
3
|
+
from qcodes.utils.metadata import Metadatable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestMetadatable(TestCase):
|
|
7
|
+
def test_load(self):
|
|
8
|
+
m = Metadatable()
|
|
9
|
+
self.assertEqual(m.metadata, {})
|
|
10
|
+
m.load_metadata({1: 2, 3: 4})
|
|
11
|
+
self.assertEqual(m.metadata, {1: 2, 3: 4})
|
|
12
|
+
m.load_metadata({1: 5})
|
|
13
|
+
self.assertEqual(m.metadata, {1: 5, 3: 4})
|
|
14
|
+
|
|
15
|
+
def test_init(self):
|
|
16
|
+
with self.assertRaises(TypeError):
|
|
17
|
+
Metadatable(metadata={2: 3}, not_metadata={4: 5})
|
|
18
|
+
|
|
19
|
+
m = Metadatable(metadata={2: 3})
|
|
20
|
+
self.assertEqual(m.metadata, {2: 3})
|
|
21
|
+
|
|
22
|
+
class HasSnapshotBase(Metadatable):
|
|
23
|
+
def snapshot_base(self, update=False):
|
|
24
|
+
return {'cheese': 'gruyere'}
|
|
25
|
+
|
|
26
|
+
class HasSnapshot(Metadatable):
|
|
27
|
+
# Users shouldn't do this... but we'll test its behavior
|
|
28
|
+
# for completeness
|
|
29
|
+
def snapshot(self, update=False):
|
|
30
|
+
return {'fruit': 'kiwi'}
|
|
31
|
+
|
|
32
|
+
def test_snapshot(self):
|
|
33
|
+
m = Metadatable(metadata={6: 7})
|
|
34
|
+
self.assertEqual(m.snapshot_base(), {})
|
|
35
|
+
self.assertEqual(m.snapshot(), {'metadata': {6: 7}})
|
|
36
|
+
del m.metadata[6]
|
|
37
|
+
self.assertEqual(m.snapshot(), {})
|
|
38
|
+
|
|
39
|
+
sb = self.HasSnapshotBase(metadata={7: 8})
|
|
40
|
+
self.assertEqual(sb.snapshot_base(), {'cheese': 'gruyere'})
|
|
41
|
+
self.assertEqual(sb.snapshot(),
|
|
42
|
+
{'cheese': 'gruyere', 'metadata': {7: 8}})
|
|
43
|
+
del sb.metadata[7]
|
|
44
|
+
self.assertEqual(sb.snapshot(), sb.snapshot_base())
|
|
45
|
+
|
|
46
|
+
s = self.HasSnapshot(metadata={8: 9})
|
|
47
|
+
self.assertEqual(s.snapshot(), {'fruit': 'kiwi'})
|
|
48
|
+
self.assertEqual(s.snapshot_base(), {})
|
|
49
|
+
self.assertEqual(s.metadata, {8: 9})
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
from unittest import TestCase, skipIf
|
|
2
|
+
import time
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
import multiprocessing as mp
|
|
6
|
+
from queue import Empty
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
|
|
9
|
+
import qcodes
|
|
10
|
+
from qcodes.process.helpers import set_mp_method, kill_queue
|
|
11
|
+
from qcodes.process.qcodes_process import QcodesProcess
|
|
12
|
+
from qcodes.process.stream_queue import get_stream_queue, _SQWriter
|
|
13
|
+
from qcodes.process.server import ServerManager, RESPONSE_OK, RESPONSE_ERROR
|
|
14
|
+
import qcodes.process.helpers as qcmp
|
|
15
|
+
from qcodes.utils.helpers import in_notebook, LogCapture
|
|
16
|
+
from qcodes.utils.timing import calibrate
|
|
17
|
+
|
|
18
|
+
BREAK_SIGNAL = '~~BREAK~~'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class sqtest_echo:
|
|
22
|
+
def __init__(self, name, delay=0.01, has_q=True):
|
|
23
|
+
self.q_out = mp.Queue()
|
|
24
|
+
self.q_err = mp.Queue()
|
|
25
|
+
self.p = QcodesProcess(target=sqtest_echo_f,
|
|
26
|
+
args=(name, delay, self.q_out, self.q_err,
|
|
27
|
+
has_q),
|
|
28
|
+
name=name)
|
|
29
|
+
self.p.start()
|
|
30
|
+
self.delay = delay
|
|
31
|
+
self.resp_delay = delay * 2 + 0.03
|
|
32
|
+
|
|
33
|
+
def send_out(self, msg):
|
|
34
|
+
self.q_out.put(msg)
|
|
35
|
+
time.sleep(self.resp_delay)
|
|
36
|
+
|
|
37
|
+
def send_err(self, msg):
|
|
38
|
+
self.q_err.put(msg)
|
|
39
|
+
time.sleep(self.resp_delay)
|
|
40
|
+
|
|
41
|
+
def halt(self):
|
|
42
|
+
if not (hasattr(self, 'p') and self.p.is_alive()):
|
|
43
|
+
return
|
|
44
|
+
self.q_out.put(BREAK_SIGNAL)
|
|
45
|
+
self.p.join()
|
|
46
|
+
time.sleep(self.resp_delay)
|
|
47
|
+
for q in ['q_out', 'q_err']:
|
|
48
|
+
if hasattr(self, q):
|
|
49
|
+
queue = getattr(self, q)
|
|
50
|
+
kill_queue(queue)
|
|
51
|
+
kill_queue(queue) # repeat just to make sure it doesn't error
|
|
52
|
+
|
|
53
|
+
def __del__(self):
|
|
54
|
+
self.halt()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def sqtest_echo_f(name, delay, q_out, q_err, has_q):
|
|
58
|
+
while True:
|
|
59
|
+
time.sleep(delay)
|
|
60
|
+
|
|
61
|
+
if not q_out.empty():
|
|
62
|
+
out = q_out.get()
|
|
63
|
+
|
|
64
|
+
if out == BREAK_SIGNAL:
|
|
65
|
+
# now test that disconnect works, and reverts to
|
|
66
|
+
# regular stdout and stderr
|
|
67
|
+
if has_q:
|
|
68
|
+
try:
|
|
69
|
+
get_stream_queue().disconnect()
|
|
70
|
+
except RuntimeError:
|
|
71
|
+
pass
|
|
72
|
+
print('stdout ', end='', flush=True)
|
|
73
|
+
print('stderr ', file=sys.stderr, end='', flush=True)
|
|
74
|
+
break
|
|
75
|
+
|
|
76
|
+
print(out, end='', flush=True)
|
|
77
|
+
|
|
78
|
+
if not q_err.empty():
|
|
79
|
+
print(q_err.get(), file=sys.stderr, end='', flush=True)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def sqtest_exception():
|
|
83
|
+
raise RuntimeError('Boo!')
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TestMpMethod(TestCase):
|
|
87
|
+
def test_set_mp_method(self):
|
|
88
|
+
start_method = mp.get_start_method()
|
|
89
|
+
self.assertIn(start_method, ('fork', 'spawn', 'forkserver'))
|
|
90
|
+
|
|
91
|
+
# multiprocessing's set_start_method is NOT idempotent
|
|
92
|
+
with self.assertRaises(RuntimeError):
|
|
93
|
+
mp.set_start_method(start_method)
|
|
94
|
+
|
|
95
|
+
# but ours is
|
|
96
|
+
set_mp_method(start_method)
|
|
97
|
+
|
|
98
|
+
# it will still error on gibberish, but different errors depending
|
|
99
|
+
# on whether you force or not
|
|
100
|
+
with self.assertRaises(RuntimeError):
|
|
101
|
+
set_mp_method('spoon')
|
|
102
|
+
with self.assertRaises(ValueError):
|
|
103
|
+
set_mp_method('spoon', force=True)
|
|
104
|
+
|
|
105
|
+
# change the error we look for to test strange error handling
|
|
106
|
+
mp_err_normal = qcmp.MP_ERR
|
|
107
|
+
qcmp.MP_ERR = 'who cares?'
|
|
108
|
+
with self.assertRaises(RuntimeError):
|
|
109
|
+
set_mp_method('start_method')
|
|
110
|
+
qcmp.MP_ERR = mp_err_normal
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestQcodesProcess(TestCase):
|
|
114
|
+
def setUp(self):
|
|
115
|
+
mp_stats = calibrate(quiet=True)
|
|
116
|
+
self.MP_START_DELAY = mp_stats['mp_start_delay']
|
|
117
|
+
self.MP_FINISH_DELAY = mp_stats['mp_finish_delay']
|
|
118
|
+
self.SLEEP_DELAY = mp_stats['sleep_delay']
|
|
119
|
+
self.BLOCKING_TIME = mp_stats['blocking_time']
|
|
120
|
+
self.sq = get_stream_queue()
|
|
121
|
+
|
|
122
|
+
@skipIf(getattr(qcodes, '_IN_NOTEBOOK', False),
|
|
123
|
+
'called from notebook')
|
|
124
|
+
def test_not_in_notebook(self):
|
|
125
|
+
# below we'll patch this to True, but make sure that it's False
|
|
126
|
+
# in the normal test runner.
|
|
127
|
+
self.assertEqual(in_notebook(), False)
|
|
128
|
+
|
|
129
|
+
# and make sure that processes run this way do not use the queue
|
|
130
|
+
with self.sq.lock:
|
|
131
|
+
p = sqtest_echo('hidden', has_q=False)
|
|
132
|
+
time.sleep(self.MP_START_DELAY)
|
|
133
|
+
p.send_out('should go to stdout;')
|
|
134
|
+
p.send_err('should go to stderr;')
|
|
135
|
+
p.halt()
|
|
136
|
+
|
|
137
|
+
self.assertEqual(self.sq.get(), '')
|
|
138
|
+
|
|
139
|
+
@patch('qcodes.process.qcodes_process.in_notebook')
|
|
140
|
+
def test_qcodes_process_exception(self, in_nb_patch):
|
|
141
|
+
in_nb_patch.return_value = True
|
|
142
|
+
|
|
143
|
+
with self.sq.lock:
|
|
144
|
+
name = 'Hamlet'
|
|
145
|
+
p = QcodesProcess(target=sqtest_exception, name=name)
|
|
146
|
+
|
|
147
|
+
initial_outs = (sys.stdout, sys.stderr)
|
|
148
|
+
|
|
149
|
+
# normally you call p.start(), but for this test we want
|
|
150
|
+
# the function to actually run in the main process
|
|
151
|
+
# it will run the actual target, but will print the exception
|
|
152
|
+
# (to the queue) rather than raising it.
|
|
153
|
+
p.run()
|
|
154
|
+
|
|
155
|
+
# output streams are back to how they started
|
|
156
|
+
self.assertEqual((sys.stdout, sys.stderr), initial_outs)
|
|
157
|
+
time.sleep(0.01)
|
|
158
|
+
exc_text = self.sq.get()
|
|
159
|
+
# but we have the exception in the queue
|
|
160
|
+
self.maxDiff = None
|
|
161
|
+
self.assertGreaterEqual(exc_text.count(name + ' ERR'), 5)
|
|
162
|
+
self.assertEqual(exc_text.count('Traceback'), 1, exc_text)
|
|
163
|
+
self.assertEqual(exc_text.count('RuntimeError'), 2)
|
|
164
|
+
self.assertEqual(exc_text.count('Boo!'), 2)
|
|
165
|
+
|
|
166
|
+
@patch('qcodes.process.qcodes_process.in_notebook')
|
|
167
|
+
def test_qcodes_process(self, in_nb_patch):
|
|
168
|
+
in_nb_patch.return_value = True
|
|
169
|
+
|
|
170
|
+
queue_format = re.compile(
|
|
171
|
+
'^\[\d\d:\d\d:\d\d\.\d\d\d p\d( ERR)?\] [^\[\]]*$')
|
|
172
|
+
|
|
173
|
+
with self.sq.lock:
|
|
174
|
+
p1 = sqtest_echo('p1')
|
|
175
|
+
p2 = sqtest_echo('p2')
|
|
176
|
+
time.sleep(self.MP_START_DELAY + p1.delay + p2.delay)
|
|
177
|
+
|
|
178
|
+
self.assertEqual(self.sq.get(), '')
|
|
179
|
+
|
|
180
|
+
procNames = ['<{}>'.format(name)
|
|
181
|
+
for name in ('p1', 'p2')]
|
|
182
|
+
|
|
183
|
+
reprs = [repr(p) for p in mp.active_children()]
|
|
184
|
+
for name in procNames:
|
|
185
|
+
self.assertIn(name, reprs)
|
|
186
|
+
|
|
187
|
+
# test each individual stream to send several messages on same
|
|
188
|
+
# and different lines
|
|
189
|
+
|
|
190
|
+
for sender, label, term in ([[p1.send_out, 'p1] ', ''],
|
|
191
|
+
[p1.send_err, 'p1 ERR] ', '\n'],
|
|
192
|
+
[p2.send_out, 'p2] ', '\n'],
|
|
193
|
+
[p2.send_err, 'p2 ERR] ', '']]):
|
|
194
|
+
sender('row row ')
|
|
195
|
+
sender('row your boat\n')
|
|
196
|
+
sender('gently down ')
|
|
197
|
+
time.sleep(0.01)
|
|
198
|
+
data = [line for line in self.sq.get().split('\n') if line]
|
|
199
|
+
expected = [
|
|
200
|
+
label + 'row row row your boat',
|
|
201
|
+
label + 'gently down '
|
|
202
|
+
]
|
|
203
|
+
# TODO - intermittent error here
|
|
204
|
+
self.assertEqual(len(data), len(expected), data)
|
|
205
|
+
for line, expected_line in zip(data, expected):
|
|
206
|
+
self.assertIsNotNone(queue_format.match(line), data)
|
|
207
|
+
self.assertEqual(line[14:], expected_line, data)
|
|
208
|
+
|
|
209
|
+
sender(' the stream' + term)
|
|
210
|
+
# no label/header as we're continuing the previous line
|
|
211
|
+
self.assertEqual(self.sq.get(), ' the stream' + term)
|
|
212
|
+
|
|
213
|
+
p1.send_out('marco')
|
|
214
|
+
p2.send_out('polo\n') # we don't see these single terminators
|
|
215
|
+
p1.send_out('marco\n') # when we change streams
|
|
216
|
+
p2.send_out('polo')
|
|
217
|
+
time.sleep(0.01)
|
|
218
|
+
|
|
219
|
+
data = self.sq.get().split('\n')
|
|
220
|
+
for line in data:
|
|
221
|
+
if line:
|
|
222
|
+
self.assertIsNotNone(queue_format.match(line))
|
|
223
|
+
|
|
224
|
+
data_msgs = [line[14:] for line in data]
|
|
225
|
+
expected = [
|
|
226
|
+
'',
|
|
227
|
+
'p1] marco',
|
|
228
|
+
'p2] polo',
|
|
229
|
+
'p1] marco',
|
|
230
|
+
'p2] polo'
|
|
231
|
+
]
|
|
232
|
+
self.assertEqual(data_msgs, expected)
|
|
233
|
+
|
|
234
|
+
# Some OS's start more processes just for fun... so don't test
|
|
235
|
+
# that p1 and p2 are the only ones.
|
|
236
|
+
# self.assertEqual(len(reprs), 2, reprs)
|
|
237
|
+
|
|
238
|
+
p1.halt()
|
|
239
|
+
p2.halt()
|
|
240
|
+
# both p1 and p2 should have finished now, and ended.
|
|
241
|
+
reprs = [repr(p) for p in mp.active_children()]
|
|
242
|
+
for name in procNames:
|
|
243
|
+
self.assertNotIn(name, reprs)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class TestStreamQueue(TestCase):
|
|
247
|
+
def test_connection(self):
|
|
248
|
+
sq = get_stream_queue()
|
|
249
|
+
sq.connect('')
|
|
250
|
+
# TODO: do we really want double-connect to raise? or maybe
|
|
251
|
+
# only raise if the process name changes?
|
|
252
|
+
with self.assertRaises(RuntimeError):
|
|
253
|
+
sq.connect('')
|
|
254
|
+
sq.disconnect()
|
|
255
|
+
with self.assertRaises(RuntimeError):
|
|
256
|
+
sq.disconnect()
|
|
257
|
+
|
|
258
|
+
def test_del(self):
|
|
259
|
+
sq = get_stream_queue()
|
|
260
|
+
self.assertTrue(hasattr(sq, 'queue'))
|
|
261
|
+
self.assertTrue(hasattr(sq, 'lock'))
|
|
262
|
+
self.assertIsNotNone(sq.instance)
|
|
263
|
+
|
|
264
|
+
sq.__del__()
|
|
265
|
+
|
|
266
|
+
self.assertFalse(hasattr(sq, 'queue'))
|
|
267
|
+
self.assertFalse(hasattr(sq, 'lock'))
|
|
268
|
+
self.assertIsNone(sq.instance)
|
|
269
|
+
|
|
270
|
+
sq.__del__() # just to make sure it doesn't error
|
|
271
|
+
|
|
272
|
+
# this is basically tested in TestQcodesProcess, but the test happens
|
|
273
|
+
# in a subprocess so coverage doesn't know about it. Anyway, there are
|
|
274
|
+
# a few edge cases left that we have to test locally.
|
|
275
|
+
def test_sq_writer(self):
|
|
276
|
+
sq = get_stream_queue()
|
|
277
|
+
with sq.lock:
|
|
278
|
+
sq_clearer = _SQWriter(sq, 'Someone else')
|
|
279
|
+
sq_clearer.write('Boo!\n')
|
|
280
|
+
|
|
281
|
+
# apparently we need a little delay to make sure the queue
|
|
282
|
+
# properly appears populated.
|
|
283
|
+
time.sleep(0.01)
|
|
284
|
+
|
|
285
|
+
sq.get()
|
|
286
|
+
sq_name = 'A Queue'
|
|
287
|
+
sqw = _SQWriter(sq, sq_name)
|
|
288
|
+
|
|
289
|
+
# flush should exist, but does nothing
|
|
290
|
+
sqw.flush()
|
|
291
|
+
|
|
292
|
+
lines = [
|
|
293
|
+
'Knock knock.\nWho\'s there?\n',
|
|
294
|
+
'Interrupting cow.\n',
|
|
295
|
+
'Interr-',
|
|
296
|
+
'MOO!\n',
|
|
297
|
+
''
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
for line in lines:
|
|
301
|
+
sqw.write(line)
|
|
302
|
+
|
|
303
|
+
time.sleep(0.01)
|
|
304
|
+
|
|
305
|
+
# _SQWriter doesn't do any transformations to the messages, beyond
|
|
306
|
+
# adding a time string and the stream name
|
|
307
|
+
for line in lines:
|
|
308
|
+
if not line:
|
|
309
|
+
self.assertEqual(sq.queue.empty(), True)
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
self.assertEqual(sq.queue.empty(), False)
|
|
313
|
+
timestr, stream_name, msg = sq.queue.get()
|
|
314
|
+
self.assertEqual(msg, line)
|
|
315
|
+
self.assertEqual(stream_name, sq_name)
|
|
316
|
+
|
|
317
|
+
# now test that even if the queue is unwatched the messages still
|
|
318
|
+
# go there. If we're feeling adventurous maybe we can test if
|
|
319
|
+
# something was actually printed.
|
|
320
|
+
sqw.MIN_READ_TIME = -1
|
|
321
|
+
new_message = 'should get printed\n'
|
|
322
|
+
sqw.write(new_message)
|
|
323
|
+
|
|
324
|
+
time.sleep(0.01)
|
|
325
|
+
|
|
326
|
+
self.assertEqual(sq.queue.empty(), False)
|
|
327
|
+
self.assertEqual(sq.queue.get()[2], new_message)
|
|
328
|
+
|
|
329
|
+
# test that an error in writing resets stdout and stderr
|
|
330
|
+
# nose uses its own stdout and stderr... so keep them (as we will
|
|
331
|
+
# force stdout and stderr to several other things) so we can put
|
|
332
|
+
# them back at the end
|
|
333
|
+
nose_stdout = sys.stdout
|
|
334
|
+
nose_stderr = sys.stderr
|
|
335
|
+
sys.stdout = _SQWriter(sq, 'mock_stdout')
|
|
336
|
+
sys.stderr = _SQWriter(sq, 'mock_stderr')
|
|
337
|
+
self.assertNotIn(sys.stdout, (sys.__stdout__, nose_stdout))
|
|
338
|
+
self.assertNotIn(sys.stderr, (sys.__stderr__, nose_stderr))
|
|
339
|
+
|
|
340
|
+
sqw.MIN_READ_TIME = 'not a number'
|
|
341
|
+
with self.assertRaises(TypeError):
|
|
342
|
+
sqw.write('trigger an error')
|
|
343
|
+
|
|
344
|
+
self.assertEqual(sys.stdout, sys.__stdout__)
|
|
345
|
+
self.assertEqual(sys.stderr, sys.__stderr__)
|
|
346
|
+
|
|
347
|
+
sys.stdout = nose_stdout
|
|
348
|
+
sys.stderr = nose_stderr
|
|
349
|
+
time.sleep(0.01)
|
|
350
|
+
sq.get()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class ServerManagerTest(ServerManager):
|
|
354
|
+
def _start_server(self):
|
|
355
|
+
# don't really start the server - we'll test its pieces separately,
|
|
356
|
+
# in the main process
|
|
357
|
+
pass
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class EmptyServer:
|
|
361
|
+
def __init__(self, query_queue, response_queue, extras):
|
|
362
|
+
query_queue.put('why?')
|
|
363
|
+
response_queue.put(extras)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class CustomError(Exception):
|
|
367
|
+
pass
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def delayed_put(queue, val, delay):
|
|
371
|
+
time.sleep(delay)
|
|
372
|
+
queue.put(val)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class TestServerManager(TestCase):
|
|
376
|
+
def check_error(self, manager, error_str, error_class):
|
|
377
|
+
manager._response_queue.put(RESPONSE_ERROR, error_str)
|
|
378
|
+
with self.assertRaises(error_class):
|
|
379
|
+
manager.ask('which way does the wind blow?')
|
|
380
|
+
|
|
381
|
+
def test_mechanics(self):
|
|
382
|
+
extras = 'super secret don\'t tell anyone'
|
|
383
|
+
|
|
384
|
+
sm = ServerManagerTest(name='test', server_class=EmptyServer,
|
|
385
|
+
shared_attrs=extras)
|
|
386
|
+
sm._run_server()
|
|
387
|
+
|
|
388
|
+
self.assertEqual(sm._query_queue.get(timeout=1), 'why?')
|
|
389
|
+
self.assertEqual(sm._response_queue.get(timeout=1), extras)
|
|
390
|
+
|
|
391
|
+
# builtin errors we propagate to the server
|
|
392
|
+
builtin_error_str = ('traceback\n lines\n and then\n'
|
|
393
|
+
' OSError: your hard disk went floppy.')
|
|
394
|
+
sm._response_queue.put((RESPONSE_ERROR, builtin_error_str))
|
|
395
|
+
with self.assertRaises(OSError):
|
|
396
|
+
sm.ask('which way does the wind blow?')
|
|
397
|
+
|
|
398
|
+
# non-built-in errors we fall back on RuntimeError
|
|
399
|
+
custom_error_str = ('traceback\nlines\nand then\n'
|
|
400
|
+
'CustomError: the Balrog is loose!')
|
|
401
|
+
extra_resp1 = 'should get tossed by the error checker'
|
|
402
|
+
extra_resp2 = 'so should this.'
|
|
403
|
+
sm._response_queue.put((RESPONSE_OK, extra_resp1))
|
|
404
|
+
sm._response_queue.put((RESPONSE_OK, extra_resp2))
|
|
405
|
+
sm._response_queue.put((RESPONSE_ERROR, custom_error_str))
|
|
406
|
+
|
|
407
|
+
# TODO: we have an intermittent failure below, but only when running
|
|
408
|
+
# the full test suite (including pyqt and matplotlib?), not if we
|
|
409
|
+
# run just this module, or at least not nearly as frequently.
|
|
410
|
+
time.sleep(0.2)
|
|
411
|
+
|
|
412
|
+
with LogCapture() as logs:
|
|
413
|
+
with self.assertRaises(RuntimeError):
|
|
414
|
+
sm.ask('something benign')
|
|
415
|
+
self.assertTrue(sm._response_queue.empty())
|
|
416
|
+
self.assertIn(extra_resp1, logs.value)
|
|
417
|
+
self.assertIn(extra_resp2, logs.value)
|
|
418
|
+
|
|
419
|
+
# extra responses to a query, only the last should be taken
|
|
420
|
+
extra_resp1 = 'boo!'
|
|
421
|
+
extra_resp2 = 'a barrel of monkeys!'
|
|
422
|
+
sm._response_queue.put((RESPONSE_OK, extra_resp1))
|
|
423
|
+
sm._response_queue.put((RESPONSE_OK, extra_resp2))
|
|
424
|
+
time.sleep(0.05)
|
|
425
|
+
p = mp.Process(target=delayed_put,
|
|
426
|
+
args=(sm._response_queue, (RESPONSE_OK, 42), 0.05))
|
|
427
|
+
p.start()
|
|
428
|
+
|
|
429
|
+
with LogCapture() as logs:
|
|
430
|
+
self.assertEqual(sm.ask('what is the answer'), 42)
|
|
431
|
+
self.assertIn(extra_resp1, logs.value)
|
|
432
|
+
self.assertIn(extra_resp2, logs.value)
|
|
433
|
+
|
|
434
|
+
# no response to a query
|
|
435
|
+
with self.assertRaises(Empty):
|
|
436
|
+
sm.ask('A sphincter says what?', timeout=0.05)
|
|
437
|
+
|
|
438
|
+
# test halting an unresponsive server
|
|
439
|
+
sm._server = mp.Process(target=time.sleep, args=(1000,))
|
|
440
|
+
sm._server.start()
|
|
441
|
+
|
|
442
|
+
self.assertIn(sm._server, mp.active_children())
|
|
443
|
+
|
|
444
|
+
with LogCapture() as logs:
|
|
445
|
+
sm.halt(0.01)
|
|
446
|
+
self.assertIn('ServerManager did not respond '
|
|
447
|
+
'to halt signal, terminated', logs.value)
|
|
448
|
+
|
|
449
|
+
self.assertNotIn(sm._server, mp.active_children())
|
|
450
|
+
|
|
451
|
+
def test_pathological_edge_cases(self):
|
|
452
|
+
# kill_queue should never fail
|
|
453
|
+
kill_queue(None)
|
|
454
|
+
|
|
455
|
+
# and halt should ignore AssertionErrors, which arise in
|
|
456
|
+
# subprocesses when trying to kill a different subprocess
|
|
457
|
+
sm = ServerManagerTest(name='test', server_class=None)
|
|
458
|
+
|
|
459
|
+
class HorribleProcess:
|
|
460
|
+
def is_alive(self):
|
|
461
|
+
raise AssertionError
|
|
462
|
+
|
|
463
|
+
def write(self):
|
|
464
|
+
raise AssertionError
|
|
465
|
+
|
|
466
|
+
def join(self):
|
|
467
|
+
raise AssertionError
|
|
468
|
+
|
|
469
|
+
sm._server = HorribleProcess()
|
|
470
|
+
|
|
471
|
+
sm.halt()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
from qcodes.utils.nested_attrs import NestedAttrAccess
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestNestedAttrAccess(TestCase):
|
|
6
|
+
def test_simple(self):
|
|
7
|
+
obj = NestedAttrAccess()
|
|
8
|
+
|
|
9
|
+
# before setting attr1
|
|
10
|
+
self.assertEqual(obj.getattr('attr1', 99), 99)
|
|
11
|
+
with self.assertRaises(AttributeError):
|
|
12
|
+
obj.getattr('attr1')
|
|
13
|
+
|
|
14
|
+
with self.assertRaises(TypeError):
|
|
15
|
+
obj.setattr('attr1')
|
|
16
|
+
|
|
17
|
+
self.assertFalse(hasattr(obj, 'attr1'))
|
|
18
|
+
|
|
19
|
+
# set it to a value
|
|
20
|
+
obj.setattr('attr1', 98)
|
|
21
|
+
self.assertTrue(hasattr(obj, 'attr1'))
|
|
22
|
+
|
|
23
|
+
self.assertEqual(obj.getattr('attr1', 99), 98)
|
|
24
|
+
self.assertEqual(obj.getattr('attr1'), 98)
|
|
25
|
+
|
|
26
|
+
# then delete it
|
|
27
|
+
obj.delattr('attr1')
|
|
28
|
+
|
|
29
|
+
with self.assertRaises(AttributeError):
|
|
30
|
+
obj.delattr('attr1')
|
|
31
|
+
|
|
32
|
+
with self.assertRaises(AttributeError):
|
|
33
|
+
obj.getattr('attr1')
|
|
34
|
+
|
|
35
|
+
# make and call a method
|
|
36
|
+
def f(a, b=0):
|
|
37
|
+
return a + b
|
|
38
|
+
|
|
39
|
+
obj.setattr('m1', f)
|
|
40
|
+
self.assertEqual(obj.callattr('m1', 4, 1), 5)
|
|
41
|
+
self.assertEqual(obj.callattr('m1', 21, b=42), 63)
|
|
42
|
+
|
|
43
|
+
def test_nested(self):
|
|
44
|
+
obj = NestedAttrAccess()
|
|
45
|
+
|
|
46
|
+
self.assertFalse(hasattr(obj, 'd1'))
|
|
47
|
+
|
|
48
|
+
with self.assertRaises(TypeError):
|
|
49
|
+
obj.setattr('d1')
|
|
50
|
+
|
|
51
|
+
# set one attribute that creates nesting
|
|
52
|
+
obj.setattr('d1', {'a': {1: 2, 'l': [5, 6]}})
|
|
53
|
+
|
|
54
|
+
# can't nest inside a non-container
|
|
55
|
+
with self.assertRaises(TypeError):
|
|
56
|
+
obj.setattr('d1["a"][1]["secret"]', 42)
|
|
57
|
+
|
|
58
|
+
# get the whole dict
|
|
59
|
+
self.assertEqual(obj.getattr('d1'), {'a': {1: 2, 'l': [5, 6]}})
|
|
60
|
+
self.assertEqual(obj.getattr('d1', 55), {'a': {1: 2, 'l': [5, 6]}})
|
|
61
|
+
|
|
62
|
+
# get parts
|
|
63
|
+
self.assertEqual(obj.getattr('d1["a"]'), {1: 2, 'l': [5, 6]})
|
|
64
|
+
self.assertEqual(obj.getattr('d1["a"][1]'), 2)
|
|
65
|
+
self.assertEqual(obj.getattr('d1["a"][1]', 3), 2)
|
|
66
|
+
with self.assertRaises(KeyError):
|
|
67
|
+
obj.getattr('d1["b"]')
|
|
68
|
+
|
|
69
|
+
# add an attribute inside, then delete it again
|
|
70
|
+
obj.setattr('d1["a"][2]', 4)
|
|
71
|
+
self.assertEqual(obj.getattr('d1'), {'a': {1: 2, 2: 4, 'l': [5, 6]}})
|
|
72
|
+
obj.delattr('d1["a"][2]')
|
|
73
|
+
self.assertEqual(obj.getattr('d1'), {'a': {1: 2, 'l': [5, 6]}})
|
|
74
|
+
self.assertEqual(obj.d1, {'a': {1: 2, 'l': [5, 6]}})
|
|
75
|
+
|
|
76
|
+
# list access
|
|
77
|
+
obj.setattr('d1["a"]["l"][0]', 7)
|
|
78
|
+
obj.callattr('d1["a"]["l"].extend', [5, 3])
|
|
79
|
+
obj.delattr('d1["a"]["l"][1]')
|
|
80
|
+
# while we're at it test single quotes
|
|
81
|
+
self.assertEqual(obj.getattr("d1['a']['l'][1]"), 5)
|
|
82
|
+
self.assertEqual(obj.d1['a']['l'], [7, 5, 3])
|
|
83
|
+
|
|
84
|
+
def test_bad_attr(self):
|
|
85
|
+
obj = NestedAttrAccess()
|
|
86
|
+
obj.d = {}
|
|
87
|
+
# this one works
|
|
88
|
+
obj.setattr('d["x"]', 1)
|
|
89
|
+
|
|
90
|
+
bad_attrs = [
|
|
91
|
+
'', '.', '[', 'x.', '[]', # simply malformed
|
|
92
|
+
'.x' # don't put a dot at the start
|
|
93
|
+
'["hi"]', # can't set an item at the top level
|
|
94
|
+
'd[x]', 'd["x]', 'd["x\']' # quoting errors
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
for attr in bad_attrs:
|
|
98
|
+
with self.subTest(attr=attr):
|
|
99
|
+
with self.assertRaises(ValueError):
|
|
100
|
+
obj.setattr(attr, 1)
|