77from __future__ import annotations
88
99import json
10+ import os
1011import subprocess
1112from pathlib import Path
1213
@@ -19,6 +20,7 @@ def compare_test_results(
1920 original_sqlite_path : Path ,
2021 candidate_sqlite_path : Path ,
2122 comparator_script : Path | None = None ,
23+ project_root : Path | None = None ,
2224) -> tuple [bool , list [TestDiff ]]:
2325 """Compare JavaScript test results using the Node.js comparator.
2426
@@ -32,6 +34,7 @@ def compare_test_results(
3234 original_sqlite_path: Path to SQLite database with original code results.
3335 candidate_sqlite_path: Path to SQLite database with candidate code results.
3436 comparator_script: Optional path to the comparison script.
37+ project_root: Project root directory where node_modules is installed.
3538
3639 Returns:
3740 Tuple of (all_equivalent, list of TestDiff objects).
@@ -50,29 +53,61 @@ def compare_test_results(
5053 logger .error (f"Candidate SQLite database not found: { candidate_sqlite_path } " )
5154 return False , []
5255
56+ # Determine working directory - should be where node_modules is installed
57+ # The script needs better-sqlite3 which is installed in the project's node_modules
58+ cwd = project_root or Path .cwd ()
59+
60+ # Set NODE_PATH to include the project's node_modules
61+ # This is needed because the script runs from the codeflash package directory,
62+ # but needs to resolve modules from the project's node_modules
63+ env = os .environ .copy ()
64+ node_modules_path = cwd / "node_modules"
65+ if node_modules_path .exists ():
66+ existing_node_path = env .get ("NODE_PATH" , "" )
67+ if existing_node_path :
68+ env ["NODE_PATH" ] = f"{ node_modules_path } :{ existing_node_path } "
69+ else :
70+ env ["NODE_PATH" ] = str (node_modules_path )
71+
5372 try :
5473 result = subprocess .run (
5574 ["node" , str (script_path ), str (original_sqlite_path ), str (candidate_sqlite_path )],
5675 check = False ,
5776 capture_output = True ,
5877 text = True ,
5978 timeout = 60 ,
79+ cwd = str (cwd ),
80+ env = env ,
6081 )
6182
62- # Parse the JSON output
83+ # Parse the JSON output first - errors are reported in JSON too
6384 try :
85+ if not result .stdout or not result .stdout .strip ():
86+ logger .error ("JavaScript comparator returned empty output" )
87+ if result .stderr :
88+ logger .error (f"stderr: { result .stderr } " )
89+ return False , []
6490 comparison = json .loads (result .stdout )
6591 except json .JSONDecodeError as e :
6692 logger .error (f"Failed to parse JavaScript comparator output: { e } " )
67- logger .debug (f"stdout: { result .stdout } " )
68- logger .debug (f"stderr: { result .stderr } " )
93+ logger .error (f"stdout: { result .stdout [:500 ] if result .stdout else '(empty)' } " )
94+ if result .stderr :
95+ logger .error (f"stderr: { result .stderr [:500 ]} " )
6996 return False , []
7097
71- # Check for errors
98+ # Check for errors in the JSON response
99+ # Exit code 0 = equivalent, 1 = not equivalent, 2 = setup error
72100 if comparison .get ("error" ):
73101 logger .error (f"JavaScript comparator error: { comparison ['error' ]} " )
74102 return False , []
75103
104+ # Check for unexpected exit codes (not 0 or 1)
105+ if result .returncode != 0 and result .returncode != 1 :
106+ logger .error (f"JavaScript comparator failed with exit code { result .returncode } " )
107+ if result .stderr :
108+ logger .error (f"stderr: { result .stderr } " )
109+ return False , []
110+
76111 # Convert diffs to TestDiff objects
77112 test_diffs : list [TestDiff ] = []
78113 for diff in comparison .get ("diffs" , []):
0 commit comments