Skip to content

Commit 8c3e97f

Browse files
Pedro Algarvios0undt3ch
authored andcommitted
Added the pytest.mark.skip_on_env marker.
Fixes #14 Signed-off-by: Pedro Algarvio <palgarvio@vmware.com>
1 parent 91e3257 commit 8c3e97f

9 files changed

Lines changed: 307 additions & 0 deletions

File tree

.pylint-spelling-words

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ cwd
1717
darwin
1818
dns
1919
eg
20+
eq
2021
favicon
2122
favicons
2223
freebsd
@@ -40,6 +41,7 @@ macos
4041
mtime
4142
namespace
4243
namespaced
44+
ne
4345
netbsd
4446
noqa
4547
nox
@@ -67,5 +69,6 @@ sys
6769
toml
6870
towncrier
6971
utils
72+
varname
7073
virtualenvs
7174
vmware

changelog/14.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added the ``pytest.mark.skip_on_env`` marker.

docs/ref/pytestskipmarkers/plugin.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,41 @@ Markers
565565
566566
567567
568+
.. _markers.skip_on_env:
569+
570+
``skip_on_env``
571+
===============
572+
573+
.. py:decorator:: pytest.mark.skip_on_env(envvar, present=True, eq=None, ne=None)
574+
575+
:keyword str varname: The environment variable to check
576+
:keyword bool present:
577+
When ``True``, skip if variable is present in the environment.
578+
When ``False``, skip if variable is not present in the environment.
579+
:keyword str eq: Skips when the variable is present in the environment and matches this value.
580+
:keyword str ne: Skips when the variable is present in the environment and does not match this value.
581+
:keyword str reason: The skip reason
582+
583+
Skip test based on the presence/absence of `envvar` in the environment and it's value.
584+
585+
.. code-block:: python
586+
587+
@pytest.mark.skip_on_env("FLAKY_TEST")
588+
def test_func():
589+
assert True
590+
591+
592+
@pytest.mark.skip_on_env("FLAKY_TEST", eq="1")
593+
def test_func():
594+
assert True
595+
596+
597+
@pytest.mark.skip_on_env("FLAKY_TEST", present=False)
598+
def test_func():
599+
assert True
600+
601+
602+
568603
.. _markers.skip_on_platforms:
569604

570605
``skip_on_platforms``

src/pytestskipmarkers/downgraded/plugin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,7 @@ def pytest_configure(config: 'Config') -> None:
131131
'markers',
132132
'skip_unless_on_platforms(windows=False, linux=False, darwin=False, sunos=False, smartos=False, freebsd=False, netbsd=False, openbsd=False, aix=False, aarch64=False, spawning=False, photonos=False): Pass True to one or more keywords to get the test skipped unless matched.',
133133
)
134+
config.addinivalue_line(
135+
'markers',
136+
'skip_on_env(envvar, present=True, eq=None, ne=None): Skip test based on environment variables presence and contents.',
137+
)

src/pytestskipmarkers/downgraded/utils/markers.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,63 @@ def skip_if_no_remote_network() -> Optional[str]:
168168
return None
169169

170170

171+
def skip_on_env(
172+
varname: str,
173+
present: bool = True,
174+
eq: Optional[str] = None,
175+
ne: Optional[str] = None,
176+
reason: Optional[str] = None,
177+
) -> Optional[str]:
178+
"""
179+
Helper function to check for environment variables.
180+
181+
If any of the checks match, return the skip reason.
182+
183+
Args:
184+
varname(str):
185+
The environment variable to check
186+
present(bool):
187+
When ``True``, skip if variable is present in the environment.
188+
When ``False``, skip if variable is not present in the environment.
189+
eq(str):
190+
Skips when the variable is present in the environment and matches this value.
191+
ne(str):
192+
Skips when the variable is present in the environment and does not match this value.
193+
reason(str):
194+
The custom reason message to use.
195+
196+
Returns:
197+
str: The skip reason
198+
None: Should not be skipped.
199+
"""
200+
if eq and ne:
201+
raise pytest.UsageError('Cannot pass both `eq` and `ne`.')
202+
if present is False and (eq or ne):
203+
raise pytest.UsageError('Cannot pass `present=False` and either `eq` or `ne`.')
204+
if present is False and varname not in os.environ:
205+
if not reason:
206+
reason = "The variable '{0}' is not present in the environ.".format(varname)
207+
return reason
208+
elif present is True and varname in os.environ:
209+
varname_value = os.environ[varname]
210+
if eq:
211+
if varname_value == eq:
212+
if not reason:
213+
reason = "'{0}' present in environ and '{1}=={2}'".format(
214+
varname, varname, eq
215+
)
216+
elif ne:
217+
if varname_value != ne:
218+
if not reason:
219+
reason = "'{0}' present in environ and '{1}!={2}'".format(
220+
varname, varname, eq
221+
)
222+
elif not reason:
223+
reason = "The variable '{0}' is present in the environ.".format(varname)
224+
return reason
225+
return None
226+
227+
171228
def evaluate_markers(item: 'Item') -> None:
172229
"""
173230
Fixtures injection based on markers or test skips based on CLI arguments.
@@ -695,3 +752,31 @@ def evaluate_markers(item: 'Item') -> None:
695752
raise pytest.UsageError(
696753
'Passed an invalid platform to skip_unless_on_platforms: {}'.format(exc)
697754
)
755+
skip_on_env_marker = item.get_closest_marker('skip_on_env')
756+
if skip_on_env_marker is not None:
757+
args = list(skip_on_env_marker.args)
758+
if not args:
759+
raise pytest.UsageError(
760+
"The 'skip_on_env' marker needs at least one argument to be passed, the environment variable name."
761+
)
762+
envvar = args.pop(0)
763+
if args:
764+
raise pytest.UsageError(
765+
"The 'skip_on_env' only accepts one argument, the environment variable name."
766+
)
767+
if not isinstance(envvar, str):
768+
raise pytest.UsageError(
769+
'The environment variable argument must be a string.'
770+
)
771+
kwargs = cast(Dict[str, Any], skip_on_env_marker.kwargs).copy()
772+
present = kwargs.pop('present', True)
773+
eq = kwargs.pop('eq', None)
774+
ne = kwargs.pop('ne', None)
775+
reason = kwargs.pop('reason', None)
776+
if kwargs:
777+
raise pytest.UsageError(
778+
"The 'skip_on_env' marker only accepts 'present', 'eq', 'ne' and 'reason' as keyword arguments."
779+
)
780+
skip_reason = skip_on_env(envvar, present=present, eq=eq, ne=ne, reason=reason)
781+
if skip_reason:
782+
raise pytest.skip.Exception(skip_reason, **exc_kwargs)

src/pytestskipmarkers/plugin.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,8 @@ def pytest_configure(config: "Config") -> None:
195195
"netbsd=False, openbsd=False, aix=False, aarch64=False, spawning=False, photonos=False): Pass True to "
196196
"one or more keywords to get the test skipped unless matched.",
197197
)
198+
config.addinivalue_line(
199+
"markers",
200+
"skip_on_env(envvar, present=True, eq=None, ne=None): Skip test based on environment variables presence "
201+
"and contents.",
202+
)

src/pytestskipmarkers/utils/markers.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,59 @@ def skip_if_no_remote_network() -> Optional[str]:
173173
return None
174174

175175

176+
def skip_on_env(
177+
varname: str,
178+
present: bool = True,
179+
eq: Optional[str] = None,
180+
ne: Optional[str] = None,
181+
reason: Optional[str] = None,
182+
) -> Optional[str]:
183+
"""
184+
Helper function to check for environment variables.
185+
186+
If any of the checks match, return the skip reason.
187+
188+
Args:
189+
varname(str):
190+
The environment variable to check
191+
present(bool):
192+
When ``True``, skip if variable is present in the environment.
193+
When ``False``, skip if variable is not present in the environment.
194+
eq(str):
195+
Skips when the variable is present in the environment and matches this value.
196+
ne(str):
197+
Skips when the variable is present in the environment and does not match this value.
198+
reason(str):
199+
The custom reason message to use.
200+
201+
Returns:
202+
str: The skip reason
203+
None: Should not be skipped.
204+
"""
205+
if eq and ne:
206+
raise pytest.UsageError("Cannot pass both `eq` and `ne`.")
207+
if present is False and (eq or ne):
208+
raise pytest.UsageError("Cannot pass `present=False` and either `eq` or `ne`.")
209+
if present is False and varname not in os.environ:
210+
if not reason:
211+
reason = f"The variable '{varname}' is not present in the environ."
212+
return reason
213+
elif present is True and varname in os.environ:
214+
varname_value = os.environ[varname]
215+
if eq:
216+
if varname_value == eq:
217+
if not reason:
218+
reason = f"'{varname}' present in environ and '{varname}=={eq}'"
219+
elif ne:
220+
if varname_value != ne:
221+
if not reason:
222+
reason = f"'{varname}' present in environ and '{varname}!={eq}'"
223+
elif not reason:
224+
reason = f"The variable '{varname}' is present in the environ."
225+
return reason
226+
return None
227+
228+
176229
def evaluate_markers(item: "Item") -> None:
177230
"""
178231
Fixtures injection based on markers or test skips based on CLI arguments.
@@ -659,3 +712,33 @@ def evaluate_markers(item: "Item") -> None:
659712
raise pytest.UsageError(
660713
"Passed an invalid platform to skip_unless_on_platforms: {}".format(exc)
661714
)
715+
716+
skip_on_env_marker = item.get_closest_marker("skip_on_env")
717+
if skip_on_env_marker is not None:
718+
args = list(skip_on_env_marker.args)
719+
if not args:
720+
raise pytest.UsageError(
721+
"The 'skip_on_env' marker needs at least one argument to be passed, the environment "
722+
"variable name."
723+
)
724+
envvar = args.pop(0)
725+
if args:
726+
raise pytest.UsageError(
727+
"The 'skip_on_env' only accepts one argument, the environment variable name."
728+
)
729+
if not isinstance(envvar, str):
730+
raise pytest.UsageError("The environment variable argument must be a string.")
731+
732+
kwargs = cast(Dict[str, Any], skip_on_env_marker.kwargs).copy()
733+
present = kwargs.pop("present", True)
734+
eq = kwargs.pop("eq", None)
735+
ne = kwargs.pop("ne", None)
736+
reason = kwargs.pop("reason", None)
737+
if kwargs:
738+
raise pytest.UsageError(
739+
"The 'skip_on_env' marker only accepts 'present', 'eq', 'ne' and 'reason' as "
740+
"keyword arguments."
741+
)
742+
skip_reason = skip_on_env(envvar, present=present, eq=eq, ne=ne, reason=reason)
743+
if skip_reason:
744+
raise pytest.skip.Exception(skip_reason, **exc_kwargs)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2021-2022 VMware, Inc.
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
"""
5+
Test the ``@pytest.mark.skip_on_env`` marker.
6+
"""
7+
import os
8+
from unittest import mock
9+
10+
11+
def test_skipped(pytester):
12+
pytester.makepyfile(
13+
"""
14+
import pytest
15+
16+
@pytest.mark.skip_on_env("FOOBAR")
17+
def test_one():
18+
assert True
19+
"""
20+
)
21+
with mock.patch.dict(os.environ, FOOBAR="1"):
22+
res = pytester.runpytest_inprocess()
23+
res.assert_outcomes(skipped=1)
24+
res.stdout.no_fnmatch_line("*PytestUnknownMarkWarning*")
25+
26+
27+
def test_not_skipped(pytester):
28+
pytester.makepyfile(
29+
"""
30+
import pytest
31+
32+
@pytest.mark.skip_on_env("FOOBAR", eq="2")
33+
def test_one():
34+
assert True
35+
"""
36+
)
37+
with mock.patch.dict(os.environ, FOOBAR="1"):
38+
res = pytester.runpytest_inprocess()
39+
res.assert_outcomes(passed=1)
40+
res.stdout.no_fnmatch_line("*PytestUnknownMarkWarning*")
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2021-2022 VMware, Inc.
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
"""
5+
Test the "skip_on_env" marker helper.
6+
"""
7+
import logging
8+
import os
9+
from unittest import mock
10+
11+
import pytest
12+
13+
import pytestskipmarkers.utils.markers as markers
14+
15+
log = logging.getLogger(__name__)
16+
17+
18+
def test_not_present_and_eq():
19+
with pytest.raises(pytest.UsageError):
20+
markers.skip_on_env("FOOBAR", present=False, eq="1")
21+
22+
23+
def test_not_present_and_ne():
24+
with pytest.raises(pytest.UsageError):
25+
markers.skip_on_env("FOOBAR", present=False, ne="1")
26+
27+
28+
def test_present_and_eq_and_ne():
29+
with pytest.raises(pytest.UsageError):
30+
markers.skip_on_env("FOOBAR", eq="1", ne="1")
31+
32+
33+
def test_present_true():
34+
with mock.patch.dict(os.environ, FOO="1"):
35+
assert markers.skip_on_env("FOO") is not None
36+
37+
38+
def test_present_false():
39+
assert markers.skip_on_env(__name__.upper(), present=False) is not None
40+
41+
42+
def test_present_true_and_eq():
43+
with mock.patch.dict(os.environ, FOO="1"):
44+
assert markers.skip_on_env("FOO", eq="1") is not None
45+
assert markers.skip_on_env("FOO", eq="2") is None
46+
47+
48+
def test_present_true_and_ne():
49+
with mock.patch.dict(os.environ, FOO="1"):
50+
assert markers.skip_on_env("FOO", ne="2") is not None
51+
assert markers.skip_on_env("FOO", ne="1") is None

0 commit comments

Comments
 (0)