@@ -104,6 +104,7 @@ def test_strip_quotes(self, input_path: str, expected: str) -> None:
104104 """Quote stripping handles various quote patterns correctly."""
105105 assert _strip_quotes (input_path ) == expected
106106
107+ @pytest .mark .skipif (sys .platform == "win32" , reason = "Unix-style PATH separator test" )
107108 @pytest .mark .parametrize (
108109 "path_value,expected" ,
109110 [
@@ -115,8 +116,24 @@ def test_strip_quotes(self, input_path: str, expected: str) -> None:
115116 (":::" , []), # Only separators
116117 ],
117118 )
118- def test_split_path (self , path_value : str , expected : list [str ]) -> None :
119- """PATH splitting handles quotes, empty entries, and whitespace."""
119+ def test_split_path_unix (self , path_value : str , expected : list [str ]) -> None :
120+ """PATH splitting handles quotes, empty entries, and whitespace on Unix."""
121+ assert _split_path (path_value ) == expected
122+
123+ @pytest .mark .skipif (sys .platform != "win32" , reason = "Windows-style PATH separator test" )
124+ @pytest .mark .parametrize (
125+ "path_value,expected" ,
126+ [
127+ ("C:\\ bin;C:\\ tools" , ["C:\\ bin" , "C:\\ tools" ]),
128+ ('"C:\\ bin";C:\\ tools' , ["C:\\ bin" , "C:\\ tools" ]),
129+ ("C:\\ bin;;C:\\ tools" , ["C:\\ bin" , "C:\\ tools" ]), # Empty entry removed
130+ (" C:\\ bin ; C:\\ tools " , ["C:\\ bin" , "C:\\ tools" ]), # Whitespace
131+ ("" , []),
132+ (";;;" , []), # Only separators
133+ ],
134+ )
135+ def test_split_path_windows (self , path_value : str , expected : list [str ]) -> None :
136+ """PATH splitting handles quotes, empty entries, and whitespace on Windows."""
120137 assert _split_path (path_value ) == expected
121138
122139
@@ -224,8 +241,9 @@ def test_find_external_promptfoo_when_found(self, monkeypatch: pytest.MonkeyPatc
224241 result = _find_external_promptfoo ()
225242 assert result == promptfoo_path
226243
227- def test_find_external_promptfoo_prevents_recursion (self , monkeypatch : pytest .MonkeyPatch ) -> None :
228- """Filters out wrapper directory from PATH to prevent recursion."""
244+ @pytest .mark .skipif (sys .platform == "win32" , reason = "Unix-specific recursion test" )
245+ def test_find_external_promptfoo_prevents_recursion_unix (self , monkeypatch : pytest .MonkeyPatch ) -> None :
246+ """Filters out wrapper directory from PATH to prevent recursion on Unix."""
229247 wrapper_path = "/home/user/.local/bin/promptfoo"
230248 real_promptfoo = "/usr/local/bin/promptfoo"
231249
@@ -246,6 +264,29 @@ def mock_which(cmd: str, path: Optional[str] = None) -> Optional[str]:
246264 result = _find_external_promptfoo ()
247265 assert result == real_promptfoo
248266
267+ @pytest .mark .skipif (sys .platform != "win32" , reason = "Windows-specific recursion test" )
268+ def test_find_external_promptfoo_prevents_recursion_windows (self , monkeypatch : pytest .MonkeyPatch ) -> None :
269+ """Filters out wrapper directory from PATH to prevent recursion on Windows."""
270+ wrapper_path = "C:\\ Users\\ user\\ AppData\\ Local\\ Programs\\ Python\\ Python312\\ Scripts\\ promptfoo.exe"
271+ real_promptfoo = "C:\\ npm\\ prefix\\ promptfoo.cmd"
272+
273+ monkeypatch .setattr (sys , "argv" , [wrapper_path ])
274+ monkeypatch .setenv ("PATH" , "C:\\ Users\\ user\\ AppData\\ Local\\ Programs\\ Python\\ Python312\\ Scripts;C:\\ npm\\ prefix" )
275+
276+ def mock_which (cmd : str , path : Optional [str ] = None ) -> Optional [str ]:
277+ if cmd != "promptfoo" :
278+ return None
279+ if path is None :
280+ return wrapper_path
281+ # When called with filtered PATH, return the real one
282+ if "Python312\\ Scripts" not in path :
283+ return real_promptfoo
284+ return None
285+
286+ monkeypatch .setattr ("shutil.which" , mock_which )
287+ result = _find_external_promptfoo ()
288+ assert result == real_promptfoo
289+
249290
250291class TestShellRequirement :
251292 """Test Windows shell requirement detection for .bat/.cmd files."""
@@ -427,9 +468,12 @@ def test_main_exits_when_neither_external_nor_npx_available(
427468 self , monkeypatch : pytest .MonkeyPatch , capsys : pytest .CaptureFixture
428469 ) -> None :
429470 """Exits with error when neither external promptfoo nor npx found."""
471+ # Use platform-appropriate path for node
472+ node_path = "C:\\ Program Files\\ nodejs\\ node.exe" if sys .platform == "win32" else "/usr/bin/node"
473+
430474 monkeypatch .setattr (sys , "argv" , ["promptfoo" , "eval" ])
431475 monkeypatch .setattr ("shutil.which" , lambda cmd , path = None : {
432- "node" : "/usr/bin/node"
476+ "node" : node_path
433477 }.get (cmd ))
434478
435479 with pytest .raises (SystemExit ) as exc_info :
0 commit comments