Skip to content

Commit 4d00247

Browse files
committed
Add Windows testing to CI
1 parent f54d29f commit 4d00247

File tree

5 files changed

+86
-16
lines changed

5 files changed

+86
-16
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ jobs:
1414
- ubuntu-latest
1515
python-version:
1616
["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", pypy3.9, pypy3.10]
17+
include:
18+
# Test lowest and highest Python versions on Windows
19+
- os: windows-latest
20+
python-version: "3.9"
21+
- os: windows-latest
22+
python-version: "3.14"
1723

1824
steps:
1925
- uses: actions/checkout@v6

tests/test_cli.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import os
2+
import sys
23
from pathlib import Path
34
from typing import Optional
45

56
import pytest
6-
import sh
77

88
import dotenv
99
from dotenv.cli import cli as dotenv_cli
1010
from dotenv.version import __version__
1111

12+
if not sys.platform.startswith("win"):
13+
import sh
14+
1215

1316
@pytest.mark.parametrize(
1417
"output_format,content,expected",
@@ -173,6 +176,9 @@ def test_set_no_file(cli):
173176
assert "Missing argument" in result.output
174177

175178

179+
@pytest.mark.skipif(
180+
sys.platform.startswith("win"), reason="sh module doesn't support Windows"
181+
)
176182
def test_get_default_path(tmp_path):
177183
with sh.pushd(tmp_path):
178184
(tmp_path / ".env").write_text("a=b")
@@ -182,6 +188,9 @@ def test_get_default_path(tmp_path):
182188
assert result == "b\n"
183189

184190

191+
@pytest.mark.skipif(
192+
sys.platform.startswith("win"), reason="sh module doesn't support Windows"
193+
)
185194
def test_run(tmp_path):
186195
with sh.pushd(tmp_path):
187196
(tmp_path / ".env").write_text("a=b")
@@ -191,6 +200,9 @@ def test_run(tmp_path):
191200
assert result == "b\n"
192201

193202

203+
@pytest.mark.skipif(
204+
sys.platform.startswith("win"), reason="sh module doesn't support Windows"
205+
)
194206
def test_run_with_existing_variable(tmp_path):
195207
with sh.pushd(tmp_path):
196208
(tmp_path / ".env").write_text("a=b")
@@ -202,6 +214,9 @@ def test_run_with_existing_variable(tmp_path):
202214
assert result == "b\n"
203215

204216

217+
@pytest.mark.skipif(
218+
sys.platform.startswith("win"), reason="sh module doesn't support Windows"
219+
)
205220
def test_run_with_existing_variable_not_overridden(tmp_path):
206221
with sh.pushd(tmp_path):
207222
(tmp_path / ".env").write_text("a=b")
@@ -213,6 +228,9 @@ def test_run_with_existing_variable_not_overridden(tmp_path):
213228
assert result == "c\n"
214229

215230

231+
@pytest.mark.skipif(
232+
sys.platform.startswith("win"), reason="sh module doesn't support Windows"
233+
)
216234
def test_run_with_none_value(tmp_path):
217235
with sh.pushd(tmp_path):
218236
(tmp_path / ".env").write_text("a=b\nc")
@@ -222,6 +240,9 @@ def test_run_with_none_value(tmp_path):
222240
assert result == "b\n"
223241

224242

243+
@pytest.mark.skipif(
244+
sys.platform.startswith("win"), reason="sh module doesn't support Windows"
245+
)
225246
def test_run_with_other_env(dotenv_path):
226247
dotenv_path.write_text("a=b")
227248

tests/test_ipython.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import os
2+
import sys
23
from unittest import mock
34

45
import pytest
56

67
pytest.importorskip("IPython")
78

89

10+
@pytest.mark.skipif(
11+
sys.platform.startswith("win"), reason="This test assumes case-sensitive variables"
12+
)
913
@mock.patch.dict(os.environ, {}, clear=True)
1014
def test_ipython_existing_variable_no_override(tmp_path):
1115
from IPython.terminal.embed import InteractiveShellEmbed
@@ -22,6 +26,9 @@ def test_ipython_existing_variable_no_override(tmp_path):
2226
assert os.environ == {"a": "c"}
2327

2428

29+
@pytest.mark.skipif(
30+
sys.platform.startswith("win"), reason="This test assumes case-sensitive variables"
31+
)
2532
@mock.patch.dict(os.environ, {}, clear=True)
2633
def test_ipython_existing_variable_override(tmp_path):
2734
from IPython.terminal.embed import InteractiveShellEmbed
@@ -38,6 +45,9 @@ def test_ipython_existing_variable_override(tmp_path):
3845
assert os.environ == {"a": "b"}
3946

4047

48+
@pytest.mark.skipif(
49+
sys.platform.startswith("win"), reason="This test assumes case-sensitive variables"
50+
)
4151
@mock.patch.dict(os.environ, {}, clear=True)
4252
def test_ipython_new_variable(tmp_path):
4353
from IPython.terminal.embed import InteractiveShellEmbed

tests/test_main.py

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import io
22
import logging
33
import os
4+
import stat
45
import sys
56
import textwrap
67
from unittest import mock
78

89
import pytest
9-
import sh
1010

1111
import dotenv
1212

13+
if not sys.platform.startswith("win"):
14+
import sh
15+
1316

1417
def test_set_key_no_file(tmp_path):
1518
nx_path = tmp_path / "nx"
@@ -62,15 +65,25 @@ def test_set_key_encoding(dotenv_path):
6265

6366

6467
@pytest.mark.skipif(
65-
os.geteuid() == 0, reason="Root user can access files even with 000 permissions."
68+
not sys.platform.startswith("win") and os.geteuid() == 0,
69+
reason="Root user can access files even with 000 permissions.",
6670
)
6771
def test_set_key_permission_error(dotenv_path):
68-
dotenv_path.chmod(0o000)
72+
if sys.platform.startswith("win"):
73+
# On Windows, make file read-only
74+
dotenv_path.chmod(stat.S_IREAD)
75+
else:
76+
# On Unix, remove all permissions
77+
dotenv_path.chmod(0o000)
6978

7079
with pytest.raises(PermissionError):
7180
dotenv.set_key(dotenv_path, "a", "b")
7281

73-
dotenv_path.chmod(0o600)
82+
# Restore permissions
83+
if sys.platform.startswith("win"):
84+
dotenv_path.chmod(stat.S_IWRITE | stat.S_IREAD)
85+
else:
86+
dotenv_path.chmod(0o600)
7487
assert dotenv_path.read_text() == ""
7588

7689

@@ -170,16 +183,6 @@ def test_unset_encoding(dotenv_path):
170183
assert dotenv_path.read_text(encoding=encoding) == ""
171184

172185

173-
@pytest.mark.skipif(
174-
os.geteuid() == 0, reason="Root user can access files even with 000 permissions."
175-
)
176-
def test_set_key_unauthorized_file(dotenv_path):
177-
dotenv_path.chmod(0o000)
178-
179-
with pytest.raises(PermissionError):
180-
dotenv.set_key(dotenv_path, "a", "x")
181-
182-
183186
def test_unset_non_existent_file(tmp_path):
184187
nx_path = tmp_path / "nx"
185188
logger = logging.getLogger("dotenv.main")
@@ -312,6 +315,9 @@ def test_load_dotenv_disabled_notification(dotenv_path, flag_value):
312315
)
313316

314317

318+
@pytest.mark.skipif(
319+
sys.platform.startswith("win"), reason="This test assumes case-sensitive variables"
320+
)
315321
@pytest.mark.parametrize(
316322
"flag_value",
317323
[
@@ -395,6 +401,9 @@ def test_load_dotenv_no_file_verbose():
395401
)
396402

397403

404+
@pytest.mark.skipif(
405+
sys.platform.startswith("win"), reason="This test assumes case-sensitive variables"
406+
)
398407
@mock.patch.dict(os.environ, {"a": "c"}, clear=True)
399408
def test_load_dotenv_existing_variable_no_override(dotenv_path):
400409
dotenv_path.write_text("a=b")
@@ -405,6 +414,9 @@ def test_load_dotenv_existing_variable_no_override(dotenv_path):
405414
assert os.environ == {"a": "c"}
406415

407416

417+
@pytest.mark.skipif(
418+
sys.platform.startswith("win"), reason="This test assumes case-sensitive variables"
419+
)
408420
@mock.patch.dict(os.environ, {"a": "c"}, clear=True)
409421
def test_load_dotenv_existing_variable_override(dotenv_path):
410422
dotenv_path.write_text("a=b")
@@ -415,6 +427,9 @@ def test_load_dotenv_existing_variable_override(dotenv_path):
415427
assert os.environ == {"a": "b"}
416428

417429

430+
@pytest.mark.skipif(
431+
sys.platform.startswith("win"), reason="This test assumes case-sensitive variables"
432+
)
418433
@mock.patch.dict(os.environ, {"a": "c"}, clear=True)
419434
def test_load_dotenv_redefine_var_used_in_file_no_override(dotenv_path):
420435
dotenv_path.write_text('a=b\nd="${a}"')
@@ -425,6 +440,9 @@ def test_load_dotenv_redefine_var_used_in_file_no_override(dotenv_path):
425440
assert os.environ == {"a": "c", "d": "c"}
426441

427442

443+
@pytest.mark.skipif(
444+
sys.platform.startswith("win"), reason="This test assumes case-sensitive variables"
445+
)
428446
@mock.patch.dict(os.environ, {"a": "c"}, clear=True)
429447
def test_load_dotenv_redefine_var_used_in_file_with_override(dotenv_path):
430448
dotenv_path.write_text('a=b\nd="${a}"')
@@ -435,6 +453,9 @@ def test_load_dotenv_redefine_var_used_in_file_with_override(dotenv_path):
435453
assert os.environ == {"a": "b", "d": "b"}
436454

437455

456+
@pytest.mark.skipif(
457+
sys.platform.startswith("win"), reason="This test assumes case-sensitive variables"
458+
)
438459
@mock.patch.dict(os.environ, {}, clear=True)
439460
def test_load_dotenv_string_io_utf_8():
440461
stream = io.StringIO("a=à")
@@ -456,6 +477,9 @@ def test_load_dotenv_file_stream(dotenv_path):
456477
assert os.environ == {"a": "b"}
457478

458479

480+
@pytest.mark.skipif(
481+
sys.platform.startswith("win"), reason="sh module doesn't support Windows"
482+
)
459483
def test_load_dotenv_in_current_dir(tmp_path):
460484
dotenv_path = tmp_path / ".env"
461485
dotenv_path.write_bytes(b"a=b")
@@ -484,6 +508,9 @@ def test_dotenv_values_file(dotenv_path):
484508
assert result == {"a": "b"}
485509

486510

511+
@pytest.mark.skipif(
512+
sys.platform.startswith("win"), reason="This test assumes case-sensitive variables"
513+
)
487514
@pytest.mark.parametrize(
488515
"env,string,interpolate,expected",
489516
[

tests/test_zip_imports.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
from unittest import mock
66
from zipfile import ZipFile
77

8-
import sh
8+
import pytest
9+
10+
if not sys.platform.startswith("win"):
11+
import sh
912

1013

1114
def walk_to_root(path: str):
@@ -62,6 +65,9 @@ def test_load_dotenv_gracefully_handles_zip_imports_when_no_env_file(tmp_path):
6265
import child1.child2.test # noqa
6366

6467

68+
@pytest.mark.skipif(
69+
sys.platform.startswith("win"), reason="sh module doesn't support Windows"
70+
)
6571
def test_load_dotenv_outside_zip_file_when_called_in_zipfile(tmp_path):
6672
zip_file_path = setup_zipfile(
6773
tmp_path,

0 commit comments

Comments
 (0)