Coverage for tests / test_launch.py: 100%
94 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
1import io 1acfgbde
2import subprocess 1acfgbde
3from unittest.mock import Mock, patch 1acfgbde
5import pytest 1acfgbde
6import typer 1acfgbde
8from tests.utils import needs_linux, needs_macos, needs_windows 1acfgbde
10url = "http://example.com" 1acfgbde
13@pytest.mark.parametrize( 1acfgbde
14 "system, command",
15 [
16 ("Darwin", "open"),
17 ("Linux", "xdg-open"),
18 ("FreeBSD", "xdg-open"),
19 ],
20)
21def test_launch_url_unix(system: str, command: str): 1acfgbde
22 with ( 1ab
23 patch("platform.system", return_value=system),
24 patch("shutil.which", return_value=True),
25 patch("subprocess.Popen") as mock_popen,
26 ):
27 typer.launch(url) 1acfgbde
29 mock_popen.assert_called_once_with( 1acfgbde
30 [command, url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
31 )
34def test_launch_url_windows(): 1acfgbde
35 with ( 1ab
36 patch("platform.system", return_value="Windows"),
37 patch("webbrowser.open") as mock_webbrowser_open,
38 ):
39 typer.launch(url) 1acfgbde
41 mock_webbrowser_open.assert_called_once_with(url) 1acfgbde
44def test_launch_url_no_xdg_open(): 1acfgbde
45 with ( 1ab
46 patch("platform.system", return_value="Linux"),
47 patch("shutil.which", return_value=None),
48 patch("webbrowser.open") as mock_webbrowser_open,
49 ):
50 typer.launch(url) 1acfgbde
52 mock_webbrowser_open.assert_called_once_with(url) 1acfgbde
55def test_launch_url_linux_xdg_open_does_not_wait_by_default(): 1acfgbde
56 proc = Mock() 1acfgbde
58 with ( 1ab
59 patch("platform.system", return_value="Linux"),
60 patch("shutil.which", return_value=True),
61 patch("subprocess.Popen", return_value=proc) as mock_popen,
62 ):
63 result = typer.launch(url) 1acfgbde
65 assert result == 0 1acfgbde
66 mock_popen.assert_called_once_with( 1acfgbde
67 ["xdg-open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
68 )
69 proc.wait.assert_not_called() 1acfgbde
72def test_launch_url_linux_xdg_open_waits_when_requested(): 1acfgbde
73 proc = Mock() 1acfgbde
74 proc.wait.return_value = 42 1acfgbde
76 with ( 1ab
77 patch("platform.system", return_value="Linux"),
78 patch("shutil.which", return_value=True),
79 patch("subprocess.Popen", return_value=proc) as mock_popen,
80 ):
81 result = typer.launch(url, wait=True) 1acfgbde
83 assert result == 42 1acfgbde
84 mock_popen.assert_called_once_with( 1acfgbde
85 ["xdg-open", url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
86 )
87 proc.wait.assert_called_once_with() 1acfgbde
90@pytest.fixture 1acfgbde
91def allow_dev_null(monkeypatch): 1acfgbde
92 real_open = open 1bde
94 def fake_open(path, *args, **kwargs): 1bde
95 if path == "/dev/null": 1bde
96 return io.StringIO() 1bde
97 return real_open(path, *args, **kwargs) # pragma: no cover
99 monkeypatch.setattr("builtins.open", fake_open) 1bde
102@needs_macos 1acfgbde
103def test_open_url_macos(monkeypatch, allow_dev_null): 1acfgbde
104 recorded: list[list[str]] = [] 1bde
106 class Proc: 1bde
107 def wait(self) -> int: 1bde
108 return 42 1bde
110 def fake_popen(args, **kwargs): 1bde
111 recorded.append(list(args)) 1bde
112 return Proc() 1bde
114 monkeypatch.setattr(subprocess, "Popen", fake_popen) 1bde
116 assert typer.launch("/path/to/file", wait=True, locate=True) == 42 1bde
117 assert recorded[0][:3] == ["open", "-W", "-R"] 1bde
118 assert recorded[0][-1] == "/path/to/file" 1bde
121@needs_windows 1acfgbde
122def test_launch_files_windows(monkeypatch): 1acfgbde
123 calls: list[list[str]] = [] 1fg
125 def fake_call(args): 1fg
126 calls.append(list(args)) 1fg
127 return 0 1fg
129 monkeypatch.setattr(subprocess, "call", fake_call) 1fg
131 assert typer.launch("C:/Tools/readme.txt", wait=True, locate=False) == 0 1fg
132 assert typer.launch("file:///C:/tmp/a.txt", wait=False, locate=True) == 0 1fg
133 assert calls.pop(0) == ["start", "/WAIT", "", "C:/Tools/readme.txt"] 1fg
134 assert calls.pop(0) == ["explorer", "/select,/C:/tmp/a.txt"] 1fg
136 monkeypatch.setattr(subprocess, "call", lambda a: (_ for _ in ()).throw(OSError())) 1fg
137 assert typer.launch("D:/no/such/file.txt", wait=False, locate=False) == 127 1fg
140@needs_linux 1acfgbde
141def test_open_url_linux_wait(monkeypatch): 1acfgbde
142 class Proc: 1ac
143 def __init__(self, code: int = 0) -> None: 1ac
144 self._code = code 1ac
146 def wait(self) -> int: 1ac
147 return self._code 1ac
149 monkeypatch.setattr(subprocess, "Popen", lambda *a, **k: Proc(7)) 1ac
151 assert typer.launch("/file", wait=True, locate=False) == 7 1ac
154@needs_linux 1acfgbde
155def test_open_url_linux_locate(monkeypatch): 1acfgbde
156 recorded: list[list[str]] = [] 1ac
158 class Proc: 1ac
159 def wait(self) -> int: 1ac
160 return 0 # pragma: no cover
162 def fake_popen(args, **kwargs): 1ac
163 recorded.append(list(args)) 1ac
164 return Proc() 1ac
166 monkeypatch.setattr(subprocess, "Popen", fake_popen) 1ac
168 assert typer.launch("/tmp/sub/file.txt", wait=False, locate=True) == 0 1ac
169 assert recorded[-1] == ["xdg-open", "/tmp/sub"] 1ac
172@needs_windows 1acfgbde
173def test_launch_file(): 1acfgbde
174 with (
175 patch("typer._click._termui_impl.sys.platform", "win32"),
176 patch("typer._click._termui_impl.WIN", True),
177 patch("typer._click._termui_impl.CYGWIN", False),
178 patch("subprocess.call", return_value=0) as call_mock,
179 ):
180 result = typer.launch("C:/tmp/file.txt", locate=True) 1fg
182 assert result == 0 1fg
183 call_mock.assert_called_once_with(["explorer", "/select,C:/tmp/file.txt"]) 1fg