From 75e6984ec8c6fa196ad78c11f454da506d7c8ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Thu, 4 Sep 2025 16:44:04 -0700 Subject: [PATCH] test/refactor: use test deque to avoid quadratic iteration Extracted from https://github.com/bitcoin/bitcoin/pull/33141#discussion_r2323012972. In Python, list `pop(0)` is linear, so consuming all items is quadratic. Switched to `collections.deque` with `popleft()` to express FIFO intent and avoid the O(n^2) path. Behavior is unchanged; for a few hundred items the perf impact is likely negligible. Ref: https://docs.python.org/3/tutorial/datastructures.html#using-lists-as-queues > While appends and pops from the end of list are fast, doing inserts or pops > from the beginning of a list is slow (because all of the other elements have > to be shifted by one). Co-authored-by: maflcko <6399679+maflcko@users.noreply.github.com> --- test/functional/test_runner.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 9dd10d1bfbe..c4cf0fdaae5 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -452,8 +452,8 @@ def main(): print("Re-compile with the -DBUILD_DAEMON=ON build option") sys.exit(1) - # Build list of tests - test_list = [] + # Build tests + test_list = deque() if tests: # Individual tests have been specified. Run specified tests that exist # in the ALL_SCRIPTS list. Accept names with or without a .py extension. @@ -472,7 +472,7 @@ def main(): script = script + ".py" if ".py" not in script else script matching_scripts = [s for s in ALL_SCRIPTS if s.startswith(script)] if matching_scripts: - test_list.extend(matching_scripts) + test_list += matching_scripts else: print("{}WARNING!{} Test '{}' not found in full test list.".format(BOLD[1], BOLD[0], test)) elif args.extended: @@ -507,7 +507,7 @@ def main(): remove_tests([test for test in test_list if test.split('.py')[0] == exclude_test.split('.py')[0]]) if args.filter: - test_list = list(filter(re.compile(args.filter).search, test_list)) + test_list = deque(filter(re.compile(args.filter).search, test_list)) if not test_list: print("No valid test scripts specified. Check that your test is in one " @@ -724,7 +724,7 @@ class TestHandler: def get_next(self): while len(self.jobs) < self.num_jobs and self.test_list: # Add tests - test = self.test_list.pop(0) + test = self.test_list.popleft() portseed = len(self.test_list) portseed_arg = ["--portseed={}".format(portseed)] log_stdout = tempfile.SpooledTemporaryFile(max_size=2**16) @@ -752,8 +752,7 @@ class TestHandler: ] fut = self.executor.submit(proc_wait, task) self.jobs[fut] = test - if not self.jobs: - raise IndexError('pop from empty list') + assert self.jobs # Must not be empty here # Print remaining running jobs when all jobs have been started. if not self.test_list: