Skip to content

Commit b5a719e

Browse files
committed
munet: add new readline and waitline functions
These functions can be used with popen processes during tests to wait for output (or timeout) Signed-off-by: Christian Hopps <chopps@labn.net>
1 parent 2e88b79 commit b5a719e

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed

munet/testing/util.py

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
"""Utility functions useful when using munet testing functionailty in pytest."""
99
import asyncio
1010
import datetime
11+
import fcntl
1112
import functools
1213
import logging
14+
import os
15+
import re
16+
import select
1317
import sys
1418
import time
1519

1620
from ..base import BaseMunet
21+
from ..base import Timeout
1722
from ..cli import async_cli
1823

1924

@@ -23,6 +28,7 @@
2328

2429

2530
async def async_pause_test(desc=""):
31+
"""Pause the running of a test offering options for CLI or PDB."""
2632
isatty = sys.stdout.isatty()
2733
if not isatty:
2834
desc = f" for {desc}" if desc else ""
@@ -49,11 +55,12 @@ async def async_pause_test(desc=""):
4955

5056

5157
def pause_test(desc=""):
58+
"""Pause the running of a test offering options for CLI or PDB."""
5259
asyncio.run(async_pause_test(desc))
5360

5461

5562
def retry(retry_timeout, initial_wait=0, retry_sleep=2, expected=True):
56-
"""decorator: retry while functions return is not None or raises an exception.
63+
"""Retry decorated function until it returns None, raises an exception, or timeout.
5764
5865
* `retry_timeout`: Retry for at least this many seconds; after waiting
5966
initial_wait seconds
@@ -116,3 +123,91 @@ def func_retry(*args, **kwargs):
116123
return func_retry
117124

118125
return _retry
126+
127+
128+
def readline(f, timeout=None):
129+
"""Read a line or timeout.
130+
131+
This function will take over the file object, the file object should not be used
132+
outside of calling this function once you begin.
133+
134+
Return: A line, remaining buffer if EOF (subsequent calls will return ""), or None
135+
for timeout.
136+
"""
137+
fd = f.fileno()
138+
if not hasattr(f, "munet_non_block_set"):
139+
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
140+
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
141+
f.munet_non_block_set = True
142+
f.munet_lines = []
143+
f.munet_buf = ""
144+
145+
if f.munet_lines:
146+
return f.munet_lines.pop(0)
147+
148+
timeout = Timeout(timeout)
149+
remaining = timeout.remaining()
150+
while remaining > 0:
151+
ready, _, _ = select.select([fd], [], [], remaining)
152+
if not ready:
153+
return None
154+
155+
c = f.read()
156+
if c is None:
157+
logging.error("munet readline: unexpected None during read")
158+
return None
159+
160+
if not c:
161+
logging.debug("munet readline: got eof")
162+
c = f.munet_buf
163+
f.munet_buf = ""
164+
return c
165+
166+
f.munet_buf += c
167+
while "\n" in f.munet_buf:
168+
a, f.munet_buf = f.munet_buf.split("\n", 1)
169+
f.munet_lines.append(a + "\n")
170+
171+
if f.munet_lines:
172+
return f.munet_lines.pop(0)
173+
174+
remaining = timeout.remaining()
175+
return None
176+
177+
178+
def waitline(f, regex, timeout=120):
179+
"""Match a regex within lines from a file with a timeout.
180+
181+
This function will take over the file object (by calling `readline` above), the file
182+
object should not be used outside of calling these functions once you begin.
183+
184+
Return: the match object or None.
185+
"""
186+
timeo = Timeout(timeout)
187+
while not timeo.is_expired():
188+
line = readline(f, timeo.remaining())
189+
if line is None:
190+
break
191+
192+
if line == "":
193+
logging.warning("waitline: got eof while matching '%s'", regex)
194+
return None
195+
196+
assert line[-1] == "\n"
197+
line = line[:-1]
198+
if not line:
199+
continue
200+
201+
logging.debug("waitline: searching: '%s' for '%s'", line, regex)
202+
m = re.search(regex, line)
203+
if m:
204+
logging.debug("waitline: matched '%s'", m.group(0))
205+
return m
206+
207+
logging.warning(
208+
"Timeout while getting output matching '%s' within %ss (actual %ss)",
209+
regex,
210+
timeout,
211+
timeo.elapsed(),
212+
)
213+
return None

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "munet"
3-
version = "0.15.2"
3+
version = "0.15.3"
44
description = "A package to facilitate network simulations"
55
authors = ["Christian Hopps <chopps@labn.net>"]
66
license = "GPL-2.0-or-later"

0 commit comments

Comments
 (0)