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

1import io 1acfgbde

2import subprocess 1acfgbde

3from unittest.mock import Mock, patch 1acfgbde

4 

5import pytest 1acfgbde

6import typer 1acfgbde

7 

8from tests.utils import needs_linux, needs_macos, needs_windows 1acfgbde

9 

10url = "http://example.com" 1acfgbde

11 

12 

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

28 

29 mock_popen.assert_called_once_with( 1acfgbde

30 [command, url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT 

31 ) 

32 

33 

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

40 

41 mock_webbrowser_open.assert_called_once_with(url) 1acfgbde

42 

43 

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

51 

52 mock_webbrowser_open.assert_called_once_with(url) 1acfgbde

53 

54 

55def test_launch_url_linux_xdg_open_does_not_wait_by_default(): 1acfgbde

56 proc = Mock() 1acfgbde

57 

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

64 

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

70 

71 

72def test_launch_url_linux_xdg_open_waits_when_requested(): 1acfgbde

73 proc = Mock() 1acfgbde

74 proc.wait.return_value = 42 1acfgbde

75 

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

82 

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

88 

89 

90@pytest.fixture 1acfgbde

91def allow_dev_null(monkeypatch): 1acfgbde

92 real_open = open 1bde

93 

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 

98 

99 monkeypatch.setattr("builtins.open", fake_open) 1bde

100 

101 

102@needs_macos 1acfgbde

103def test_open_url_macos(monkeypatch, allow_dev_null): 1acfgbde

104 recorded: list[list[str]] = [] 1bde

105 

106 class Proc: 1bde

107 def wait(self) -> int: 1bde

108 return 42 1bde

109 

110 def fake_popen(args, **kwargs): 1bde

111 recorded.append(list(args)) 1bde

112 return Proc() 1bde

113 

114 monkeypatch.setattr(subprocess, "Popen", fake_popen) 1bde

115 

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

119 

120 

121@needs_windows 1acfgbde

122def test_launch_files_windows(monkeypatch): 1acfgbde

123 calls: list[list[str]] = [] 1fg

124 

125 def fake_call(args): 1fg

126 calls.append(list(args)) 1fg

127 return 0 1fg

128 

129 monkeypatch.setattr(subprocess, "call", fake_call) 1fg

130 

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

135 

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

138 

139 

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

145 

146 def wait(self) -> int: 1ac

147 return self._code 1ac

148 

149 monkeypatch.setattr(subprocess, "Popen", lambda *a, **k: Proc(7)) 1ac

150 

151 assert typer.launch("/file", wait=True, locate=False) == 7 1ac

152 

153 

154@needs_linux 1acfgbde

155def test_open_url_linux_locate(monkeypatch): 1acfgbde

156 recorded: list[list[str]] = [] 1ac

157 

158 class Proc: 1ac

159 def wait(self) -> int: 1ac

160 return 0 # pragma: no cover 

161 

162 def fake_popen(args, **kwargs): 1ac

163 recorded.append(list(args)) 1ac

164 return Proc() 1ac

165 

166 monkeypatch.setattr(subprocess, "Popen", fake_popen) 1ac

167 

168 assert typer.launch("/tmp/sub/file.txt", wait=False, locate=True) == 0 1ac

169 assert recorded[-1] == ["xdg-open", "/tmp/sub"] 1ac

170 

171 

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

181 

182 assert result == 0 1fg

183 call_mock.assert_called_once_with(["explorer", "/select,C:/tmp/file.txt"]) 1fg