22
33import json
44import os
5- from types import SimpleNamespace
65
76import pytest
87
1211from wildedge .runtime import runner as runtime_runner
1312
1413
15- def test_cli_run_script_invokes_runner (monkeypatch ):
16- captured = {}
14+ def _fake_execle (captured : dict ):
15+ def _execle (path , * args ): # type: ignore[no-untyped-def]
16+ # Last positional arg is the env dict (os.execle convention).
17+ captured ["path" ] = path
18+ captured ["argv" ] = list (args [:- 1 ])
19+ captured ["env" ] = args [- 1 ]
1720
18- def fake_run (cmd , env , check ): # type: ignore[no-untyped-def]
19- captured ["cmd" ] = cmd
20- captured ["env" ] = env
21- captured ["check" ] = check
22- return SimpleNamespace (returncode = 0 )
21+ return _execle
2322
24- monkeypatch .setattr (cli .subprocess , "run" , fake_run )
23+
24+ def test_cli_run_execs_command_with_env (monkeypatch ):
25+ captured : dict = {}
26+ monkeypatch .setattr (cli .os , "execle" , _fake_execle (captured ))
27+ monkeypatch .setattr (cli .shutil , "which" , lambda cmd : f"/usr/bin/{ cmd } " )
2528
2629 rc = cli .main (
2730 [
@@ -32,24 +35,16 @@ def fake_run(cmd, env, check): # type: ignore[no-untyped-def]
3235 "1.2.3" ,
3336 "--debug" ,
3437 "--" ,
35- "python " ,
36- "app.py " ,
37- "--foo " ,
38- "bar " ,
38+ "gunicorn " ,
39+ "myapp.wsgi:app " ,
40+ "--workers " ,
41+ "4 " ,
3942 ]
4043 )
4144
4245 assert rc == 0
43- assert captured ["cmd" ][1 :8 ] == [
44- "-m" ,
45- "wildedge.runtime.runner" ,
46- "--mode" ,
47- "script" ,
48- "--target" ,
49- "app.py" ,
50- "--" ,
51- ]
52- assert captured ["cmd" ][8 :] == ["--foo" , "bar" ]
46+ assert captured ["path" ] == "/usr/bin/gunicorn"
47+ assert captured ["argv" ] == ["/usr/bin/gunicorn" , "myapp.wsgi:app" , "--workers" , "4" ]
5348 assert (
5449 captured ["env" ][bootstrap .RUN_DSN_ENV ]
5550 == "https://secret@ingest.wildedge.dev/key"
@@ -64,62 +59,86 @@ def fake_run(cmd, env, check): # type: ignore[no-untyped-def]
6459 )
6560
6661
67- def test_cli_run_sets_no_propagate_and_strict (monkeypatch ):
68- captured = {}
62+ def test_cli_run_prepends_autoload_to_pythonpath (monkeypatch ):
63+ captured : dict = {}
64+ monkeypatch .setattr (cli .os , "execle" , _fake_execle (captured ))
65+ monkeypatch .setattr (cli .shutil , "which" , lambda cmd : f"/usr/bin/{ cmd } " )
66+ monkeypatch .delenv ("PYTHONPATH" , raising = False )
67+
68+ cli .main (["run" , "--" , "gunicorn" , "myapp.wsgi:app" ])
69+
70+ autoload_dir = str (cli .Path (__file__ ).parent .parent / "wildedge" / "autoload" )
71+ assert captured ["env" ]["PYTHONPATH" ] == autoload_dir
72+
73+
74+ def test_cli_run_preserves_existing_pythonpath (monkeypatch ):
75+ captured : dict = {}
76+ monkeypatch .setattr (cli .os , "execle" , _fake_execle (captured ))
77+ monkeypatch .setattr (cli .shutil , "which" , lambda cmd : f"/usr/bin/{ cmd } " )
78+ monkeypatch .setenv ("PYTHONPATH" , "/existing/path" )
6979
70- def fake_run (cmd , env , check ): # type: ignore[no-untyped-def]
71- captured ["cmd" ] = cmd
72- captured ["env" ] = env
73- captured ["check" ] = check
74- return SimpleNamespace (returncode = 0 )
80+ cli .main (["run" , "--" , "gunicorn" , "myapp.wsgi:app" ])
7581
76- monkeypatch .setattr (cli .subprocess , "run" , fake_run )
82+ pythonpath = captured ["env" ]["PYTHONPATH" ]
83+ assert pythonpath .endswith (os .pathsep + "/existing/path" )
84+
85+
86+ def test_cli_run_sets_no_propagate_and_strict (monkeypatch ):
87+ captured : dict = {}
88+ monkeypatch .setattr (cli .os , "execle" , _fake_execle (captured ))
89+ monkeypatch .setattr (cli .shutil , "which" , lambda cmd : f"/usr/bin/{ cmd } " )
7790
7891 rc = cli .main (
7992 [
8093 "run" ,
8194 "--strict-integrations" ,
8295 "--no-propagate" ,
8396 "--" ,
84- "python" ,
85- "-m" ,
86- "pkg.main" ,
87- "--foo" ,
97+ "gunicorn" ,
98+ "myapp.wsgi:app" ,
8899 ]
89100 )
90101
91102 assert rc == 0
92- assert captured ["cmd" ][1 :8 ] == [
93- "-m" ,
94- "wildedge.runtime.runner" ,
95- "--mode" ,
96- "module" ,
97- "--target" ,
98- "pkg.main" ,
99- "--" ,
100- ]
101103 assert captured ["env" ][bootstrap .RUN_PROPAGATE_ENV ] == "0"
102104 assert captured ["env" ][bootstrap .RUN_STRICT_INTEGRATIONS_ENV ] == "1"
103105
104106
105107def test_cli_run_sets_print_startup_report (monkeypatch ):
106- captured = {}
108+ captured : dict = {}
109+ monkeypatch .setattr (cli .os , "execle" , _fake_execle (captured ))
110+ monkeypatch .setattr (cli .shutil , "which" , lambda cmd : f"/usr/bin/{ cmd } " )
107111
108- def fake_run (cmd , env , check ): # type: ignore[no-untyped-def]
109- captured ["env" ] = env
110- return SimpleNamespace (returncode = 0 )
112+ rc = cli .main (["run" , "--print-startup-report" , "--" , "gunicorn" , "myapp.wsgi:app" ])
111113
112- monkeypatch .setattr (cli .subprocess , "run" , fake_run )
113- rc = cli .main (["run" , "--print-startup-report" , "--" , "python" , "app.py" ])
114114 assert rc == 0
115115 assert captured ["env" ][bootstrap .RUN_PRINT_STARTUP_REPORT_ENV ] == "1"
116116
117117
118- def test_cli_rejects_non_python_command (capsys ):
119- rc = cli .main (["run" , "--" , "bash" , "script.sh" ])
120- captured = capsys .readouterr ()
121- assert rc == 2
122- assert "unsupported command format" in captured .err
118+ def test_cli_run_returns_127_for_missing_command (capsys , monkeypatch ):
119+ monkeypatch .setattr (cli .shutil , "which" , lambda cmd : None )
120+ monkeypatch .setattr (cli .Path , "is_file" , lambda self : False )
121+ rc = cli .main (["run" , "--" , "nonexistent-command" ])
122+ assert rc == 127
123+ assert "command not found" in capsys .readouterr ().err
124+
125+
126+ def test_cli_run_wraps_python_script_with_interpreter (monkeypatch , tmp_path ):
127+ script = tmp_path / "app.py"
128+ script .write_text ("pass" )
129+ captured : dict = {}
130+ monkeypatch .setattr (cli .os , "execle" , _fake_execle (captured ))
131+ monkeypatch .setattr (
132+ cli .shutil ,
133+ "which" ,
134+ lambda cmd : None if cmd .endswith (".py" ) else f"/usr/bin/{ cmd } " ,
135+ )
136+
137+ rc = cli .main (["run" , "--" , str (script )])
138+
139+ assert rc == 0
140+ assert captured ["argv" ][1 ] == str (script )
141+ assert "python" in captured ["path" ].lower ()
123142
124143
125144def test_install_runtime_requires_dsn (monkeypatch ):
@@ -145,6 +164,9 @@ def flush(self, timeout): # type: ignore[no-untyped-def]
145164 def close (self ): # type: ignore[no-untyped-def]
146165 pass
147166
167+ def _register_at_fork (self ): # type: ignore[no-untyped-def]
168+ pass
169+
148170 monkeypatch .setattr (bootstrap , "WildEdge" , FakeWildEdge )
149171 monkeypatch .setenv (bootstrap .RUN_DSN_ENV , "https://secret@ingest.wildedge.dev/key" )
150172 monkeypatch .delenv (bootstrap .RUN_FLUSH_TIMEOUT_ENV , raising = False )
@@ -177,6 +199,9 @@ def flush(self, timeout): # type: ignore[no-untyped-def]
177199 def close (self ): # type: ignore[no-untyped-def]
178200 events .append (("close" , "" ))
179201
202+ def _register_at_fork (self ): # type: ignore[no-untyped-def]
203+ pass
204+
180205 monkeypatch .setattr (bootstrap , "WildEdge" , FakeWildEdge )
181206 monkeypatch .setenv (bootstrap .RUN_DSN_ENV , "https://secret@ingest.wildedge.dev/key" )
182207 monkeypatch .setenv (bootstrap .RUN_APP_VERSION_ENV , "2.0.0" )
@@ -205,6 +230,9 @@ def __init__(self, *, dsn, app_version, debug): # type: ignore[no-untyped-def]
205230 def instrument (self , name ): # type: ignore[no-untyped-def]
206231 raise RuntimeError ("boom" )
207232
233+ def _register_at_fork (self ): # type: ignore[no-untyped-def]
234+ pass
235+
208236 monkeypatch .setattr (bootstrap , "WildEdge" , FakeWildEdge )
209237 monkeypatch .setenv (bootstrap .RUN_DSN_ENV , "https://secret@ingest.wildedge.dev/key" )
210238 monkeypatch .setenv (bootstrap .RUN_INTEGRATIONS_ENV , "onnx" )
@@ -361,6 +389,9 @@ def flush(self, timeout): # type: ignore[no-untyped-def]
361389 def close (self ): # type: ignore[no-untyped-def]
362390 pass
363391
392+ def _register_at_fork (self ): # type: ignore[no-untyped-def]
393+ pass
394+
364395 monkeypatch .setattr (bootstrap , "WildEdge" , FakeWildEdge )
365396 monkeypatch .setattr (
366397 bootstrap ,
0 commit comments