mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-09-02 07:35:26 +00:00
Set timeout for WatchLog per-instance rather than per-call
To simplify usage of multiple wait_for_*() calls, configure the timeout value for the WatchLog instance, rather than specifying it for each call. This is a preparation/cleanup for implementing multiple wait_for_*() calls in subsequent commits.
This commit is contained in:
@@ -183,19 +183,23 @@ class NamedInstance:
|
|||||||
debug(f"update of zone {zone} to server {self.ip} successful")
|
debug(f"update of zone {zone} to server {self.ip} successful")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def watch_log_from_start(self) -> WatchLogFromStart:
|
def watch_log_from_start(
|
||||||
|
self, timeout: float = WatchLogFromStart.DEFAULT_TIMEOUT
|
||||||
|
) -> WatchLogFromStart:
|
||||||
"""
|
"""
|
||||||
Return an instance of the `WatchLogFromStart` context manager for this
|
Return an instance of the `WatchLogFromStart` context manager for this
|
||||||
`named` instance's log file.
|
`named` instance's log file.
|
||||||
"""
|
"""
|
||||||
return WatchLogFromStart(self.log.path)
|
return WatchLogFromStart(self.log.path, timeout)
|
||||||
|
|
||||||
def watch_log_from_here(self) -> WatchLogFromHere:
|
def watch_log_from_here(
|
||||||
|
self, timeout: float = WatchLogFromHere.DEFAULT_TIMEOUT
|
||||||
|
) -> WatchLogFromHere:
|
||||||
"""
|
"""
|
||||||
Return an instance of the `WatchLogFromHere` context manager for this
|
Return an instance of the `WatchLogFromHere` context manager for this
|
||||||
`named` instance's log file.
|
`named` instance's log file.
|
||||||
"""
|
"""
|
||||||
return WatchLogFromHere(self.log.path)
|
return WatchLogFromHere(self.log.path, timeout)
|
||||||
|
|
||||||
def reconfigure(self) -> None:
|
def reconfigure(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@@ -65,9 +65,12 @@ class WatchLog(abc.ABC):
|
|||||||
by the `NamedInstance` class (see below for recommended usage patterns).
|
by the `NamedInstance` class (see below for recommended usage patterns).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path: str) -> None:
|
DEFAULT_TIMEOUT = 10.0
|
||||||
|
|
||||||
|
def __init__(self, path: str, timeout: float = DEFAULT_TIMEOUT) -> None:
|
||||||
"""
|
"""
|
||||||
`path` is the path to the log file to watch.
|
`path` is the path to the log file to watch.
|
||||||
|
`timeout` is the number of seconds (float) to wait for each wait call.
|
||||||
|
|
||||||
Every instance of this class must call one of the `wait_for_*()`
|
Every instance of this class must call one of the `wait_for_*()`
|
||||||
methods exactly once or else an `Exception` is thrown.
|
methods exactly once or else an `Exception` is thrown.
|
||||||
@@ -78,29 +81,32 @@ class WatchLog(abc.ABC):
|
|||||||
...
|
...
|
||||||
isctest.log.watchlog.WatchLogException: wait_for_*() was not called
|
isctest.log.watchlog.WatchLogException: wait_for_*() was not called
|
||||||
|
|
||||||
>>> with WatchLogFromHere("/dev/null") as watcher:
|
>>> with WatchLogFromHere("/dev/null", timeout=0.1) as watcher:
|
||||||
... try:
|
... try:
|
||||||
... watcher.wait_for_line("foo", timeout=0.1)
|
... watcher.wait_for_line("foo")
|
||||||
... except TimeoutError:
|
... except TimeoutError:
|
||||||
... pass
|
... pass
|
||||||
... try:
|
... try:
|
||||||
... watcher.wait_for_lines({"bar": 42}, timeout=0.1)
|
... watcher.wait_for_lines({"bar": 42})
|
||||||
... except TimeoutError:
|
... except TimeoutError:
|
||||||
... pass
|
... pass
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
isctest.log.watchlog.WatchLogException: wait_for_*() was already called
|
isctest.log.watchlog.WatchLogException: wait_for_*() was already called
|
||||||
|
|
||||||
>>> with WatchLogFromHere("/dev/null") as watcher:
|
>>> with WatchLogFromHere("/dev/null", timeout=0.0) as watcher:
|
||||||
... watcher.wait_for_line("foo", timeout=0)
|
... watcher.wait_for_line("foo")
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
AssertionError: Do not use this class unless you want to WAIT for something.
|
isctest.log.watchlog.WatchLogException: timeout must be greater than 0
|
||||||
"""
|
"""
|
||||||
self._fd = None # type: Optional[TextIO]
|
self._fd = None # type: Optional[TextIO]
|
||||||
self._path = path
|
self._path = path
|
||||||
self._wait_function_called = False
|
self._wait_function_called = False
|
||||||
self._linebuf = ""
|
self._linebuf = ""
|
||||||
|
if timeout <= 0.0:
|
||||||
|
raise WatchLogException("timeout must be greater than 0")
|
||||||
|
self._timeout = timeout
|
||||||
|
|
||||||
def _readline(self) -> Optional[str]:
|
def _readline(self) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
@@ -131,14 +137,14 @@ class WatchLog(abc.ABC):
|
|||||||
return
|
return
|
||||||
yield line
|
yield line
|
||||||
|
|
||||||
def wait_for_line(self, string: str, timeout: int = 10) -> None:
|
def wait_for_line(self, string: str) -> None:
|
||||||
"""
|
"""
|
||||||
Block execution until a line containing the provided `string` appears
|
Block execution until a line containing the provided `string` appears
|
||||||
in the log file. Return `None` once the line is found or raise a
|
in the log file. Return `None` once the line is found or raise a
|
||||||
`TimeoutError` after `timeout` seconds (default: 10) if `string` does
|
`TimeoutError` after timeout if `string` does not appear in the log
|
||||||
not appear in the log file (strings and regular expressions are
|
file (strings and regular expressions are supported). (Catching this
|
||||||
supported). (Catching this exception is discouraged as it indicates
|
exception is discouraged as it indicates that the test code did not
|
||||||
that the test code did not behave as expected.)
|
behave as expected.)
|
||||||
|
|
||||||
Recommended use:
|
Recommended use:
|
||||||
|
|
||||||
@@ -162,13 +168,13 @@ class WatchLog(abc.ABC):
|
|||||||
>>> with tempfile.NamedTemporaryFile("w") as file:
|
>>> with tempfile.NamedTemporaryFile("w") as file:
|
||||||
... print("foo", file=file, flush=True)
|
... print("foo", file=file, flush=True)
|
||||||
... with WatchLogFromStart(file.name) as watcher:
|
... with WatchLogFromStart(file.name) as watcher:
|
||||||
... retval = watcher.wait_for_line("foo", timeout=1)
|
... retval = watcher.wait_for_line("foo")
|
||||||
>>> print(retval)
|
>>> print(retval)
|
||||||
None
|
None
|
||||||
>>> with tempfile.NamedTemporaryFile("w") as file:
|
>>> with tempfile.NamedTemporaryFile("w") as file:
|
||||||
... with WatchLogFromStart(file.name) as watcher:
|
... with WatchLogFromStart(file.name) as watcher:
|
||||||
... print("foo", file=file, flush=True)
|
... print("foo", file=file, flush=True)
|
||||||
... retval = watcher.wait_for_line("foo", timeout=1)
|
... retval = watcher.wait_for_line("foo")
|
||||||
>>> print(retval)
|
>>> print(retval)
|
||||||
None
|
None
|
||||||
|
|
||||||
@@ -178,8 +184,8 @@ class WatchLog(abc.ABC):
|
|||||||
>>> import tempfile
|
>>> import tempfile
|
||||||
>>> with tempfile.NamedTemporaryFile("w") as file:
|
>>> with tempfile.NamedTemporaryFile("w") as file:
|
||||||
... print("foo", file=file, flush=True)
|
... print("foo", file=file, flush=True)
|
||||||
... with WatchLogFromHere(file.name) as watcher:
|
... with WatchLogFromHere(file.name, timeout=0.1) as watcher:
|
||||||
... watcher.wait_for_line("foo", timeout=1) #doctest: +ELLIPSIS
|
... watcher.wait_for_line("foo") #doctest: +ELLIPSIS
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TimeoutError: Timeout reached watching ...
|
TimeoutError: Timeout reached watching ...
|
||||||
@@ -187,15 +193,13 @@ class WatchLog(abc.ABC):
|
|||||||
... print("foo", file=file, flush=True)
|
... print("foo", file=file, flush=True)
|
||||||
... with WatchLogFromHere(file.name) as watcher:
|
... with WatchLogFromHere(file.name) as watcher:
|
||||||
... print("foo", file=file, flush=True)
|
... print("foo", file=file, flush=True)
|
||||||
... retval = watcher.wait_for_line("foo", timeout=1)
|
... retval = watcher.wait_for_line("foo")
|
||||||
>>> print(retval)
|
>>> print(retval)
|
||||||
None
|
None
|
||||||
"""
|
"""
|
||||||
return self._wait_for({string: None}, timeout)
|
return self._wait_for({string: None})
|
||||||
|
|
||||||
def wait_for_lines(
|
def wait_for_lines(self, strings: Dict[Union[str, Pattern], Any]) -> None:
|
||||||
self, strings: Dict[Union[str, Pattern], Any], timeout: int = 10
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Block execution until a line of interest appears in the log file. This
|
Block execution until a line of interest appears in the log file. This
|
||||||
function is a "multi-match" variant of `wait_for_line()` which is
|
function is a "multi-match" variant of `wait_for_line()` which is
|
||||||
@@ -205,10 +209,9 @@ class WatchLog(abc.ABC):
|
|||||||
`strings` is a `dict` associating each string to look for with the
|
`strings` is a `dict` associating each string to look for with the
|
||||||
value this function should return when that string is found in the log
|
value this function should return when that string is found in the log
|
||||||
file (strings and regular expressions are supported). If none of the
|
file (strings and regular expressions are supported). If none of the
|
||||||
`strings` being looked for appear in the log file after `timeout`
|
`strings` being looked for appear in the log file after timeout, a
|
||||||
seconds, a `TimeoutError` is raised. (Catching this exception is
|
`TimeoutError` is raised. (Catching this exception is discouraged as
|
||||||
discouraged as it indicates that the test code did not behave as
|
it indicates that the test code did not behave as expected.)
|
||||||
expected.)
|
|
||||||
|
|
||||||
Since `strings` is a `dict` and preserves key order (in CPython 3.6 as
|
Since `strings` is a `dict` and preserves key order (in CPython 3.6 as
|
||||||
implementation detail, since 3.7 by language design), each line is
|
implementation detail, since 3.7 by language design), each line is
|
||||||
@@ -241,28 +244,28 @@ class WatchLog(abc.ABC):
|
|||||||
>>> with tempfile.NamedTemporaryFile("w") as file:
|
>>> with tempfile.NamedTemporaryFile("w") as file:
|
||||||
... print("foo", file=file, flush=True)
|
... print("foo", file=file, flush=True)
|
||||||
... with WatchLogFromStart(file.name) as watcher:
|
... with WatchLogFromStart(file.name) as watcher:
|
||||||
... retval1 = watcher.wait_for_lines(triggers, timeout=1)
|
... retval1 = watcher.wait_for_lines(triggers)
|
||||||
... with WatchLogFromHere(file.name) as watcher:
|
... with WatchLogFromHere(file.name) as watcher:
|
||||||
... print("bar", file=file, flush=True)
|
... print("bar", file=file, flush=True)
|
||||||
... retval2 = watcher.wait_for_lines(triggers, timeout=1)
|
... retval2 = watcher.wait_for_lines(triggers)
|
||||||
>>> print(retval1)
|
>>> print(retval1)
|
||||||
42
|
42
|
||||||
>>> print(retval2)
|
>>> print(retval2)
|
||||||
1337
|
1337
|
||||||
"""
|
"""
|
||||||
return self._wait_for(strings, timeout)
|
return self._wait_for(strings)
|
||||||
|
|
||||||
def _wait_for(self, patterns: Dict[Union[str, Pattern], Any], timeout: int) -> Any:
|
def _wait_for(self, patterns: Dict[Union[str, Pattern], Any]) -> Any:
|
||||||
"""
|
"""
|
||||||
Block execution until one of the `strings` being looked for appears in
|
Block execution until one of the `strings` being looked for appears in
|
||||||
the log file. Raise a `TimeoutError` if none of the `strings` being
|
the log file. Raise a `TimeoutError` if none of the `strings` being
|
||||||
looked for are found in the log file for `timeout` seconds.
|
looked for are found in the log file after timeout.
|
||||||
"""
|
"""
|
||||||
if self._wait_function_called:
|
if self._wait_function_called:
|
||||||
raise WatchLogException("wait_for_*() was already called")
|
raise WatchLogException("wait_for_*() was already called")
|
||||||
self._wait_function_called = True
|
self._wait_function_called = True
|
||||||
assert timeout, "Do not use this class unless you want to WAIT for something."
|
|
||||||
deadline = time.monotonic() + timeout
|
deadline = time.monotonic() + self._timeout
|
||||||
while time.monotonic() < deadline:
|
while time.monotonic() < deadline:
|
||||||
for line in self._readlines():
|
for line in self._readlines():
|
||||||
for string, retval in patterns.items():
|
for string, retval in patterns.items():
|
||||||
|
@@ -75,9 +75,6 @@ def test_xferquota(named_port, servers):
|
|||||||
f"transfer of 'changing/IN' from 10.53.0.1#{named_port}: "
|
f"transfer of 'changing/IN' from 10.53.0.1#{named_port}: "
|
||||||
f"Transfer completed: .*\\(serial 2\\)"
|
f"Transfer completed: .*\\(serial 2\\)"
|
||||||
)
|
)
|
||||||
with servers["ns2"].watch_log_from_start() as watcher:
|
with servers["ns2"].watch_log_from_start(timeout=30) as watcher:
|
||||||
watcher.wait_for_line(
|
watcher.wait_for_line(pattern)
|
||||||
pattern,
|
|
||||||
timeout=30,
|
|
||||||
)
|
|
||||||
query_and_compare(a_msg)
|
query_and_compare(a_msg)
|
||||||
|
Reference in New Issue
Block a user