Skip to content

Commit 6deb834

Browse files
committed
Merge branch 'main' into run-fix-nx-command-error
* main: Remove the use of `sh` in tests (theskumar#612) # Conflicts: # tests/test_cli.py
2 parents ceeb066 + 43340da commit 6deb834

File tree

6 files changed

+139
-108
lines changed

6 files changed

+139
-108
lines changed

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ click
33
ipython
44
pytest-cov
55
pytest>=3.9
6-
sh>=2
76
tox
87
wheel
98
ruff

tests/test_cli.py

Lines changed: 60 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,13 @@
11
import os
2-
import subprocess
3-
import sys
42
from pathlib import Path
5-
from typing import Optional, Sequence
3+
from typing import Optional
64

75
import pytest
86

97
import dotenv
108
from dotenv.cli import cli as dotenv_cli
119
from dotenv.version import __version__
12-
13-
if sys.platform != "win32":
14-
import sh
15-
16-
17-
def invoke_sub(args: Sequence[str]) -> subprocess.CompletedProcess:
18-
"""
19-
Invoke the `dotenv` CLI in a subprocess.
20-
21-
This is necessary to test subcommands like `dotenv run` that replace the
22-
current process.
23-
"""
24-
25-
return subprocess.run(
26-
["dotenv", *args],
27-
capture_output=True,
28-
text=True,
29-
)
10+
from tests.test_lib import check_process, run_dotenv
3011

3112

3213
@pytest.mark.parametrize(
@@ -192,122 +173,123 @@ def test_set_no_file(cli):
192173
assert "Missing argument" in result.output
193174

194175

195-
@pytest.mark.skipif(sys.platform == "win32", reason="sh module doesn't support Windows")
196176
def test_get_default_path(tmp_path):
197-
with sh.pushd(tmp_path):
198-
(tmp_path / ".env").write_text("a=b")
177+
(tmp_path / ".env").write_text("A=x")
199178

200-
result = sh.dotenv("get", "a")
179+
result = run_dotenv(["get", "A"], cwd=tmp_path)
201180

202-
assert result == "b\n"
181+
check_process(result, exit_code=0, stdout="x\n")
203182

204183

205-
@pytest.mark.skipif(sys.platform == "win32", reason="sh module doesn't support Windows")
206184
def test_run(tmp_path):
207-
with sh.pushd(tmp_path):
208-
(tmp_path / ".env").write_text("a=b")
185+
(tmp_path / ".env").write_text("A=x")
209186

210-
result = sh.dotenv("run", "printenv", "a")
187+
result = run_dotenv(["run", "printenv", "A"], cwd=tmp_path)
211188

212-
assert result == "b\n"
189+
check_process(result, exit_code=0, stdout="x\n")
213190

214191

215-
@pytest.mark.skipif(sys.platform == "win32", reason="sh module doesn't support Windows")
216192
def test_run_with_existing_variable(tmp_path):
217-
with sh.pushd(tmp_path):
218-
(tmp_path / ".env").write_text("a=b")
219-
env = dict(os.environ)
220-
env.update({"LANG": "en_US.UTF-8", "a": "c"})
193+
(tmp_path / ".env").write_text("A=x")
194+
env = dict(os.environ)
195+
env.update({"LANG": "en_US.UTF-8", "A": "y"})
221196

222-
result = sh.dotenv("run", "printenv", "a", _env=env)
197+
result = run_dotenv(["run", "printenv", "A"], cwd=tmp_path, env=env)
223198

224-
assert result == "b\n"
199+
check_process(result, exit_code=0, stdout="x\n")
225200

226201

227-
@pytest.mark.skipif(sys.platform == "win32", reason="sh module doesn't support Windows")
228202
def test_run_with_existing_variable_not_overridden(tmp_path):
229-
with sh.pushd(tmp_path):
230-
(tmp_path / ".env").write_text("a=b")
231-
env = dict(os.environ)
232-
env.update({"LANG": "en_US.UTF-8", "a": "c"})
203+
(tmp_path / ".env").write_text("A=x")
204+
env = dict(os.environ)
205+
env.update({"LANG": "en_US.UTF-8", "A": "C"})
233206

234-
result = sh.dotenv("run", "--no-override", "printenv", "a", _env=env)
207+
result = run_dotenv(
208+
["run", "--no-override", "printenv", "A"], cwd=tmp_path, env=env
209+
)
235210

236-
assert result == "c\n"
211+
check_process(result, exit_code=0, stdout="C\n")
237212

238213

239-
@pytest.mark.skipif(sys.platform == "win32", reason="sh module doesn't support Windows")
240214
def test_run_with_none_value(tmp_path):
241-
with sh.pushd(tmp_path):
242-
(tmp_path / ".env").write_text("a=b\nc")
215+
(tmp_path / ".env").write_text("A=x\nc")
243216

244-
result = sh.dotenv("run", "printenv", "a")
217+
result = run_dotenv(["run", "printenv", "A"], cwd=tmp_path)
245218

246-
assert result == "b\n"
219+
check_process(result, exit_code=0, stdout="x\n")
247220

248221

249-
@pytest.mark.skipif(sys.platform == "win32", reason="sh module doesn't support Windows")
250-
def test_run_with_other_env(dotenv_path):
251-
dotenv_path.write_text("a=b")
222+
def test_run_with_other_env(dotenv_path, tmp_path):
223+
dotenv_path.write_text("A=x")
252224

253-
result = sh.dotenv("--file", dotenv_path, "run", "printenv", "a")
225+
result = run_dotenv(
226+
["--file", str(dotenv_path), "run", "printenv", "A"],
227+
cwd=tmp_path,
228+
)
254229

255-
assert result == "b\n"
230+
check_process(result, exit_code=0, stdout="x\n")
256231

257232

258-
def test_run_without_cmd(cli):
259-
result = cli.invoke(dotenv_cli, ["run"])
233+
def test_run_without_cmd(tmp_path):
234+
result = run_dotenv(["run"], cwd=tmp_path)
260235

261-
assert result.exit_code == 2
262-
assert "Invalid value for '-f'" in result.output
236+
check_process(result, exit_code=2)
237+
assert "Invalid value for '-f'" in result.stderr
263238

264239

265-
def test_run_with_invalid_cmd(cli, dotenv_path):
266-
result = cli.invoke(dotenv_cli, ["--file", dotenv_path, "run", "i_do_not_exist"])
240+
def test_run_with_invalid_cmd(dotenv_path, tmp_path):
241+
result = run_dotenv(
242+
["--file", str(dotenv_path), "run", "i_do_not_exist"],
243+
cwd=tmp_path,
244+
)
267245

268-
assert result.exit_code == 1
269-
assert "Command not found: i_do_not_exist" in result.output
246+
check_process(result, exit_code=1)
247+
assert "Command not found: i_do_not_exist" in result.stderr
270248

271249

272-
def test_run_with_env_missing_and_invalid_cmd(cli):
250+
def test_run_with_env_missing_and_invalid_cmd(tmp_path):
273251
"""
274252
Check that an .env file missing takes precedence over a command not found error.
275253
"""
276254

277-
result = cli.invoke(dotenv_cli, ["run", "i_do_not_exist"])
255+
result = run_dotenv(["run", "i_do_not_exist"], cwd=tmp_path)
278256

279-
assert result.exit_code == 2
280-
assert "Invalid value for '-f'" in result.output
257+
check_process(result, exit_code=2)
258+
assert "Invalid value for '-f'" in result.stderr
281259

282260

283-
def test_run_with_version(cli):
284-
result = cli.invoke(dotenv_cli, ["--version"])
261+
def test_run_with_version(tmp_path):
262+
result = run_dotenv(["--version"], cwd=tmp_path)
285263

286-
assert result.exit_code == 0
287-
assert result.output.strip().endswith(__version__)
264+
check_process(result, exit_code=0)
265+
assert result.stdout.strip().endswith(__version__)
288266

289267

290-
def test_run_with_command_flags(dotenv_path):
268+
def test_run_with_command_flags(dotenv_path, tmp_path):
291269
"""
292270
Check that command flags passed after `dotenv run` are not interpreted.
293271
294272
Here, we want to run `printenv --version`, not `dotenv --version`.
295273
"""
296274

297-
result = invoke_sub(["--file", dotenv_path, "run", "printenv", "--version"])
275+
result = run_dotenv(
276+
["--file", str(dotenv_path), "run", "printenv", "--version"],
277+
cwd=tmp_path,
278+
)
298279

299-
assert result.returncode == 0
280+
check_process(result, exit_code=0)
300281
assert result.stdout.strip().startswith("printenv ")
301282

302283

303-
def test_run_with_dotenv_and_command_flags(cli, dotenv_path):
284+
def test_run_with_dotenv_and_command_flags(dotenv_path, tmp_path):
304285
"""
305286
Check that dotenv flags supersede command flags.
306287
"""
307288

308-
result = invoke_sub(
309-
["--version", "--file", dotenv_path, "run", "printenv", "--version"]
289+
result = run_dotenv(
290+
["--version", "--file", str(dotenv_path), "run", "printenv", "--version"],
291+
cwd=tmp_path,
310292
)
311293

312-
assert result.returncode == 0
294+
check_process(result, exit_code=0)
313295
assert result.stdout.strip().startswith("dotenv, version")

tests/test_lib.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import subprocess
2+
from pathlib import Path
3+
from typing import Sequence
4+
5+
6+
def run_dotenv(
7+
args: Sequence[str],
8+
cwd: str | Path | None = None,
9+
env: dict | None = None,
10+
) -> subprocess.CompletedProcess:
11+
"""
12+
Run the `dotenv` CLI in a subprocess with the given arguments.
13+
"""
14+
15+
process = subprocess.run(
16+
["dotenv", *args],
17+
capture_output=True,
18+
text=True,
19+
cwd=cwd,
20+
env=env,
21+
)
22+
23+
return process
24+
25+
26+
def check_process(
27+
process: subprocess.CompletedProcess,
28+
exit_code: int,
29+
stdout: str | None = None,
30+
):
31+
"""
32+
Check that the process completed with the expected exit code and output.
33+
34+
This provides better error messages than directly checking the attributes.
35+
"""
36+
37+
assert process.returncode == exit_code, (
38+
f"Unexpected exit code {process.returncode} (expected {exit_code})\n"
39+
f"stdout:\n{process.stdout}\n"
40+
f"stderr:\n{process.stderr}"
41+
)
42+
43+
if stdout is not None:
44+
assert process.stdout == stdout, (
45+
f"Unexpected output: {process.stdout.strip()!r} (expected {stdout!r})"
46+
)

tests/test_main.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
import os
44
import stat
5+
import subprocess
56
import sys
67
import textwrap
78
from unittest import mock
@@ -10,9 +11,6 @@
1011

1112
import dotenv
1213

13-
if sys.platform != "win32":
14-
import sh
15-
1614

1715
def test_set_key_no_file(tmp_path):
1816
nx_path = tmp_path / "nx"
@@ -483,7 +481,6 @@ def test_load_dotenv_file_stream(dotenv_path):
483481
assert os.environ == {"a": "b"}
484482

485483

486-
@pytest.mark.skipif(sys.platform == "win32", reason="sh module doesn't support Windows")
487484
def test_load_dotenv_in_current_dir(tmp_path):
488485
dotenv_path = tmp_path / ".env"
489486
dotenv_path.write_bytes(b"a=b")
@@ -499,9 +496,14 @@ def test_load_dotenv_in_current_dir(tmp_path):
499496
)
500497
os.chdir(tmp_path)
501498

502-
result = sh.Command(sys.executable)(code_path)
499+
result = subprocess.run(
500+
[sys.executable, str(code_path)],
501+
capture_output=True,
502+
text=True,
503+
check=True,
504+
)
503505

504-
assert result == "b\n"
506+
assert result.stdout == "b\n"
505507

506508

507509
def test_dotenv_values_file(dotenv_path):

0 commit comments

Comments
 (0)