Skip to content

Commit 4e7e53a

Browse files
committed
Changed tests which use the command_queue
to use waits, changed the mocked_panda_standard_responses to wait until the MockedAsyncioClient is set up before proceeding with the test.
1 parent f8fa658 commit 4e7e53a

File tree

4 files changed

+94
-32
lines changed

4 files changed

+94
-32
lines changed

Diff for: tests/fixtures/mocked_panda.py

+21-7
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,15 @@ def __eq__(self, o):
162162

163163
class MockedAsyncioClient:
164164
def __init__(
165-
self, response_handler: ResponseHandler, command_queue: Optional[Queue] = None
165+
self,
166+
response_handler: ResponseHandler,
167+
child_conn: Optional[Connection] = None,
168+
command_queue: Optional[Queue] = None,
166169
) -> None:
167170
self.response_handler = response_handler
168171
self.command_queue = command_queue
172+
self.child_conn = child_conn
173+
self.introspect_ran_already = False
169174

170175
async def connect(self):
171176
"""Connect does nothing"""
@@ -175,6 +180,17 @@ async def send(self, command: Command[T], *args: float) -> T:
175180
"""Returns the response, args may include timeout"""
176181
if self.command_queue:
177182
self.command_queue.put(command_to_key(command))
183+
184+
if (
185+
self.child_conn
186+
and not self.introspect_ran_already
187+
and isinstance(command, GetChanges)
188+
):
189+
self.introspect_ran_already = True
190+
191+
# Now the panda has set up, tell the test to start
192+
self.child_conn.send("R")
193+
178194
response = self.response_handler(command)
179195
return response
180196

@@ -259,15 +275,13 @@ def ioc_wrapper(
259275

260276
async def inner_wrapper():
261277
create_softioc(
262-
MockedAsyncioClient(response_handler, command_queue=command_queue),
278+
MockedAsyncioClient(
279+
response_handler, child_conn=child_conn, command_queue=command_queue
280+
),
263281
test_prefix,
264282
bobfile_dir,
265283
)
266284

267-
# mocked_interactive_ioc.assert_called_once()
268-
269-
child_conn.send("R") # "Ready"
270-
271285
# Leave this process running until its torn down by pytest
272286
await asyncio.Event().wait()
273287

@@ -320,7 +334,7 @@ def create_subprocess_ioc_and_responses(
320334
with caplog.at_level(logging.WARNING):
321335
with caplog_workaround():
322336
ctx = get_multiprocessing_context()
323-
command_queue: Queue = ctx.Queue(1000)
337+
command_queue: Queue = ctx.Queue()
324338
parent_conn, child_conn = ctx.Pipe()
325339
p = ctx.Process(
326340
target=ioc_wrapper,

Diff for: tests/test_hdf_ioc.py

+28-12
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import asyncio
44
import logging
5-
import time
65
from asyncio import CancelledError
6+
from multiprocessing.connection import Connection
77
from pathlib import Path
88
from typing import AsyncGenerator, Generator
99
from uuid import uuid4
@@ -19,6 +19,7 @@
1919
Rows,
2020
custom_logger,
2121
get_multiprocessing_context,
22+
select_and_recv,
2223
)
2324
from mock.mock import AsyncMock, MagicMock, patch
2425
from pandablocks.asyncio import AsyncioClient
@@ -215,7 +216,9 @@ async def hdf5_controller(clear_records: None, standard_responses) -> AsyncGener
215216
await asyncio.sleep(0)
216217

217218

218-
def subprocess_func(namespace_prefix: str, standard_responses) -> None:
219+
def subprocess_func(
220+
namespace_prefix: str, standard_responses, child_conn: Connection
221+
) -> None:
219222
"""Function to start the HDF5 IOC"""
220223

221224
async def wrapper():
@@ -225,6 +228,7 @@ async def wrapper():
225228
dispatcher = asyncio_dispatcher.AsyncioDispatcher()
226229
builder.LoadDatabase()
227230
softioc.iocInit(dispatcher)
231+
child_conn.send("R")
228232

229233
# Leave this coroutine running until it's torn down by pytest
230234
await asyncio.Event().wait()
@@ -240,12 +244,17 @@ def hdf5_subprocess_ioc_no_logging_check(
240244
"""Create an instance of HDF5 class in its own subprocess, then start the IOC.
241245
Note you probably want to use `hdf5_subprocess_ioc` instead."""
242246
ctx = get_multiprocessing_context()
243-
p = ctx.Process(target=subprocess_func, args=(NAMESPACE_PREFIX, standard_responses))
247+
parent_conn, child_conn = ctx.Pipe()
248+
p = ctx.Process(
249+
target=subprocess_func, args=(NAMESPACE_PREFIX, standard_responses, child_conn)
250+
)
244251
try:
245252
p.start()
246-
time.sleep(3) # Give IOC some time to start up
253+
select_and_recv(parent_conn) # Wait for IOC to start up
247254
yield
248255
finally:
256+
child_conn.close()
257+
parent_conn.close()
249258
p.terminate()
250259
p.join(10)
251260
# Should never take anywhere near 10 seconds to terminate, it's just there
@@ -261,16 +270,23 @@ def hdf5_subprocess_ioc(
261270
with caplog.at_level(logging.WARNING):
262271
with caplog_workaround():
263272
ctx = get_multiprocessing_context()
273+
parent_conn, child_conn = ctx.Pipe()
264274
p = ctx.Process(
265-
target=subprocess_func, args=(NAMESPACE_PREFIX, standard_responses)
275+
target=subprocess_func,
276+
args=(NAMESPACE_PREFIX, standard_responses, child_conn),
266277
)
267-
p.start()
268-
time.sleep(3) # Give IOC some time to start up
269-
yield
270-
p.terminate()
271-
p.join(10)
272-
# Should never take anywhere near 10 seconds to terminate, it's just there
273-
# to ensure the test doesn't hang indefinitely during cleanup
278+
try:
279+
p.start()
280+
select_and_recv(parent_conn) # Wait for IOC to start up
281+
yield
282+
finally:
283+
child_conn.close()
284+
parent_conn.close()
285+
p.terminate()
286+
p.join(10)
287+
# Should never take anywhere near 10 seconds to terminate,
288+
# it's just there to ensure the test doesn't hang indefinitely
289+
# during cleanup
274290

275291
# We expect all tests to pass without warnings (or worse) logged.
276292
assert (

Diff for: tests/test_ioc_system.py

+45-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import os
3+
from multiprocessing import Queue
34
from pathlib import Path
45
from typing import List, OrderedDict
56

@@ -211,6 +212,9 @@ async def test_create_softioc_time_panda_changes(mocked_panda_standard_responses
211212
TEST_PREFIX + ":PULSE1:DELAY.DRVL",
212213
drvl_queue.put,
213214
)
215+
# The units value changes from ms to s in the test Client, which causes
216+
# the DRVL value to change from 8e-06 to 8e-09, consistent to ms to s.
217+
214218
assert await asyncio.wait_for(drvl_queue.get(), TIMEOUT) == 8e-06
215219
assert await asyncio.wait_for(egu_queue.get(), TIMEOUT) == "s"
216220
assert await asyncio.wait_for(units_queue.get(), TIMEOUT) == "s"
@@ -307,6 +311,11 @@ async def test_bobfiles_created(mocked_panda_standard_responses):
307311
assert len(old_files) == len(new_files)
308312

309313

314+
def multiprocessing_queue_to_list(queue: Queue):
315+
queue.put(None)
316+
return list(iter(queue.get, None))
317+
318+
310319
@pytest.mark.asyncio
311320
async def test_create_softioc_record_update_send_to_panda(
312321
mocked_panda_standard_responses,
@@ -318,12 +327,27 @@ async def test_create_softioc_record_update_send_to_panda(
318327
response_handler,
319328
command_queue,
320329
) = mocked_panda_standard_responses
330+
try:
331+
trig_queue = asyncio.Queue()
332+
m1 = camonitor(TEST_PREFIX + ":PCAP1:TRIG_EDGE", trig_queue.put, datatype=str)
333+
334+
# Wait for all the dummy changes to finish
335+
assert await asyncio.wait_for(trig_queue.get(), TIMEOUT) == "Falling"
336+
assert await asyncio.wait_for(trig_queue.get(), TIMEOUT) == "Either"
321337

322-
await asyncio.sleep(1.5)
323-
await caput(TEST_PREFIX + ":PCAP1:TRIG_EDGE", "Falling", wait=True, timeout=TIMEOUT)
324-
await asyncio.sleep(1.5)
325-
command_queue.put(None)
326-
commands_recieved_by_panda = list(iter(command_queue.get, None))
338+
# Verify the pv has been put to
339+
await caput(
340+
TEST_PREFIX + ":PCAP1:TRIG_EDGE", "Falling", wait=True, timeout=TIMEOUT
341+
)
342+
assert await asyncio.wait_for(trig_queue.get(), TIMEOUT) == "Falling"
343+
finally:
344+
m1.close()
345+
346+
# Check the panda recieved the translated command
347+
commands_recieved_by_panda = multiprocessing_queue_to_list(command_queue)
348+
from pprint import pprint
349+
350+
pprint(commands_recieved_by_panda)
327351
assert (
328352
command_to_key(Put(field="PCAP1.TRIG_EDGE", value="Falling"))
329353
in commands_recieved_by_panda
@@ -343,11 +367,21 @@ async def test_create_softioc_arm_disarm(
343367
command_queue,
344368
) = mocked_panda_standard_responses
345369

346-
await asyncio.sleep(1)
347-
await caput(TEST_PREFIX + ":PCAP:ARM", 1, wait=True, timeout=TIMEOUT)
348-
await caput(TEST_PREFIX + ":PCAP:ARM", 0, wait=True, timeout=TIMEOUT)
349-
await asyncio.sleep(1)
350-
command_queue.put(None)
351-
commands_recieved_by_panda = list(iter(command_queue.get, None))
370+
try:
371+
arm_queue = asyncio.Queue()
372+
m1 = camonitor(TEST_PREFIX + ":PCAP:ARM", arm_queue.put, datatype=str)
373+
assert await asyncio.wait_for(arm_queue.get(), TIMEOUT) == "0"
374+
375+
# Put PVs and check the ioc sets the values
376+
await caput(TEST_PREFIX + ":PCAP:ARM", "1", wait=True, timeout=TIMEOUT)
377+
assert await asyncio.wait_for(arm_queue.get(), TIMEOUT) == "1"
378+
await caput(TEST_PREFIX + ":PCAP:ARM", "0", wait=True, timeout=TIMEOUT)
379+
assert await asyncio.wait_for(arm_queue.get(), TIMEOUT) == "0"
380+
381+
finally:
382+
m1.close()
383+
384+
# Check the panda recieved the translated commands
385+
commands_recieved_by_panda = multiprocessing_queue_to_list(command_queue)
352386
assert command_to_key(Arm()) in commands_recieved_by_panda
353387
assert command_to_key(Disarm()) in commands_recieved_by_panda

Diff for: tests/test_tables.py

-2
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,6 @@ async def test_create_softioc_table_update_send_to_panda(
195195
command_queue,
196196
) = mocked_panda_standard_responses
197197

198-
await asyncio.sleep(1)
199198
await caput(TEST_PREFIX + ":SEQ1:TABLE:MODE", "EDIT", wait=True, timeout=TIMEOUT)
200199

201200
await caput(
@@ -204,7 +203,6 @@ async def test_create_softioc_table_update_send_to_panda(
204203

205204
await caput(TEST_PREFIX + ":SEQ1:TABLE:MODE", "SUBMIT", wait=True, timeout=TIMEOUT)
206205

207-
await asyncio.sleep(1)
208206
command_queue.put(None)
209207
commands_recieved_by_panda = list(iter(command_queue.get, None))
210208
assert (

0 commit comments

Comments
 (0)