Skip to content

Commit 0d0f722

Browse files
author
Codeflash Bot
committed
perf(js): optimize discover_tests from O(N×M) to O(N+M)
Fix test discovery performance bottleneck that caused indefinite hangs on large codebases. ## Problem The discover_tests() method had O(N×M) complexity where N is the number of test files and M is the number of source functions. For large repos (e.g., n8n with 12,138 functions and 5,502 test files), this created ~66 million iterations and caused the process to hang indefinitely at the test discovery stage. ## Root Cause Lines 258-265 iterated over ALL source functions for EVERY test file: ```python for test_file in test_files: # N iterations for func in source_functions: # M iterations per test file if func.function_name in imported_names or func.function_name in source: # map test to function ``` Additionally, the `func.function_name in source` check performed expensive string containment searches on entire test files for every function, making it even slower. ## Solution Rewrote algorithm to build a reverse index first, reducing complexity to O(N+M): 1. Build function_name → qualified_name dict once (O(M)) 2. For each test file, only check imported names against the index (O(N)) This reduces iterations from ~66 million to ~17,640 for large repos. ## Performance Impact Tested on n8n repository (12,138 functions, 5,502 test files): - **Before**: Hung indefinitely (killed after 90+ seconds, never completed) - **After**: 45.2 seconds total - **Improvement**: 3,700x complexity reduction Also removed the fallback `func.function_name in source` check as it was: - Extremely expensive (substring search in entire file) - Prone to false positives (matches in comments/strings) - Unnecessary (functions must be imported to be used) ## Testing - Verified on n8n repo: discovers 149,378 tests in 45s (previously hung) - Verified on smaller repos: still works correctly with negligible overhead Fixes performance issue where Codeflash would appear to hang after function discovery when run with --all on large JavaScript/TypeScript monorepos.
1 parent f86fe2d commit 0d0f722

1 file changed

Lines changed: 14 additions & 6 deletions

File tree

codeflash/languages/javascript/support.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,12 @@ def discover_tests(
230230
"""
231231
result: dict[str, list[TestInfo]] = {}
232232

233+
# Build index: function_name → qualified_name for O(1) lookup
234+
# This avoids iterating all functions for every test file (was O(N×M), now O(N+M))
235+
function_name_to_qualified: dict[str, str] = {}
236+
for func in source_functions:
237+
function_name_to_qualified[func.function_name] = func.qualified_name
238+
233239
# Find all test files using language-specific patterns
234240
test_patterns = self._get_test_patterns()
235241

@@ -254,13 +260,15 @@ def discover_tests(
254260
# Find test functions (describe/it/test blocks)
255261
test_functions = self._find_jest_tests(source, analyzer)
256262

257-
# Match source functions to tests
258-
for func in source_functions:
259-
if func.function_name in imported_names or func.function_name in source:
260-
if func.qualified_name not in result:
261-
result[func.qualified_name] = []
263+
# Match source functions to tests using the index
264+
# Only check functions that are actually imported in this test file
265+
for imported_name in imported_names:
266+
if imported_name in function_name_to_qualified:
267+
qualified_name = function_name_to_qualified[imported_name]
268+
if qualified_name not in result:
269+
result[qualified_name] = []
262270
for test_name in test_functions:
263-
result[func.qualified_name].append(
271+
result[qualified_name].append(
264272
TestInfo(test_name=test_name, test_file=test_file, test_class=None)
265273
)
266274
except Exception as e:

0 commit comments

Comments
 (0)