pytest: fix flake involving partial lines.
What changed, and why it matters
This is a fix for a flaky test helper, not a security issue. The change makes the test framework wait for a complete line of log output before reading it, preventing test failures caused by reading a partial line mid-write. It does not change Core Lightning's production code or affect real users' funds or node security.
No security action required. Treat as a normal test reliability improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit modifies contrib/pyln-testing/pyln/testing/utils.py, which is part of the Python testing infrastructure. It introduces a new helper readlines_wait_for_end() that reads complete lines from a file object and, if the last line lacks a trailing newline, waits briefly for more data before returning. This replaces direct calls to readlines() in logs_catchup(). The problem being fixed is a race where a log line is partially buffered, causing wait_for_log() to return an incomplete string that downstream test code (e.g., ast.literal_eval()) cannot parse. The fix is purely in test scaffolding.
Changed components
contrib/pyln-testing/pyln/testing/utils.pyTailableProc.logs_catchup()TailableProc.readlines_wait_for_end()Inspect captured patch +30 / −2
diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py
index ce2a1007..dd60020c 100644
--- a/contrib/pyln-testing/pyln/testing/utils.py
+++ b/contrib/pyln-testing/pyln/testing/utils.py
@@ -292,15 +292,43 @@ class TailableProc(object):
except Exception:
pass
+ def readlines_wait_for_end(self, f, timeout=TIMEOUT):
+ """Read all complete lines from file object `f`.
+
+ If the last line is incomplete (no trailing newline), wait briefly
+ for it to complete before returning.
+
+ Returns list of lines including trailing newline.
+ """
+ lines = []
+ cur = ''
+ start = time.time()
+
+ while True:
+ line = f.readline()
+
+ if not line:
+ if cur != '':
+ if time.time() - start > timeout:
+ raise TimeoutError(f"Incomplete line never finished: {cur}")
+ time.sleep(0.01)
+ continue
+ return lines
+
+ cur += line
+ if cur.endswith('\n'):
+ lines.append(cur)
+ cur = ''
+
def logs_catchup(self):
"""Save the latest stdout / stderr contents; return true if we got anything.
"""
- new_stdout = self.stdout_read.readlines()
+ new_stdout = self.readlines_wait_for_end(self.stdout_read)
if self.verbose:
for line in new_stdout:
sys.stdout.write("{}: {}".format(self.prefix, line))
self.logs += [l.rstrip() for l in new_stdout]
- new_stderr = self.stderr_read.readlines()
+ new_stderr = self.readlines_wait_for_end(self.stderr_read)
if self.verbose:
for line in new_stderr:
sys.stderr.write("{}-stderr: {}".format(self.prefix, line))
Why this scored 13/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.