Repository: pyinstaller/pyinstaller

Issue:
Replace skipif_xxx for platform- and version-specific tests by markers 
From https://github.com/pyinstaller/pyinstaller/pull/1426/files#r37605892 by @codewarrior0 

> [Actually, there's something about this in the py.test documentation](https://pytest.org/latest/example/markers.html#marking-platform-specific-tests-with-pytest)
> 
> Adding a `runtest_setup` hook doesn't look like a big deal. They describe how to create custom marks that say "only runs on OS X" and similar, and then use the hook function to skip tests depending on the custom marks instead of doing everything through `skipif`. This way, you can stack the `osx` mark with the `skipif` mark without worrying about two `skipif` marks replacing one another.



Discussion:

Resolving patch:
diff --git a/PyInstaller/utils/conftest.py b/PyInstaller/utils/conftest.py
--- a/PyInstaller/utils/conftest.py
+++ b/PyInstaller/utils/conftest.py
@@ -66,6 +66,9 @@
 _EXE_TIMEOUT = 30  # In sec.
 # Number of retries we should attempt if the executable times out.
 _MAX_RETRIES = 2
+# All currently supported platforms
+SUPPORTED_OSES = {"darwin", "linux", "win32"}
+
 
 # Code
 # ====
@@ -84,6 +87,22 @@ def SCRIPT_DIR(request):
     return py.path.local(_get_script_dir(request))
 
 
+def pytest_runtest_setup(item):
+    """Markers to skip tests based on the current platform.
+    https://pytest.org/en/stable/example/markers.html#marking-platform-specific-tests-with-pytest
+
+    Available markers: see setup.cfg [tool:pytest] markers
+        - @pytest.mark.darwin (macOS)
+        - @pytest.mark.linux (GNU/Linux)
+        - @pytest.mark.win32 (Windows)
+    """
+    supported_platforms = SUPPORTED_OSES.intersection(
+        mark.name for mark in item.iter_markers())
+    plat = sys.platform
+    if supported_platforms and plat not in supported_platforms:
+        pytest.skip("only runs on %s" % plat)
+
+
 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
 def pytest_runtest_makereport(item, call):
     # execute all other hooks to obtain the report object


Test patch:
diff --git a/PyInstaller/utils/tests.py b/PyInstaller/utils/tests.py
--- a/PyInstaller/utils/tests.py
+++ b/PyInstaller/utils/tests.py
@@ -25,17 +25,11 @@
 import pytest
 from _pytest.runner import Skipped
 
-from PyInstaller.compat import is_darwin, is_win, is_linux
+from PyInstaller.compat import is_win
 
 # Wrap some pytest decorators to be consistent in tests.
 parametrize = pytest.mark.parametrize
 skipif = pytest.mark.skipif
-skipif_notwin = skipif(not is_win, reason='requires Windows')
-skipif_notosx = skipif(not is_darwin, reason='requires Mac OS X')
-skipif_notlinux = skipif(not is_linux, reason='requires GNU/Linux')
-skipif_win = skipif(is_win, reason='does not run on Windows')
-skipif_linux = skipif(is_win, reason='does not run on GNU/Linux')
-skipif_winorosx = skipif(is_win or is_darwin, reason='does not run on Windows or Mac OS X')
 xfail = pytest.mark.xfail
 
 def _check_for_compiler():
diff --git a/news/1427.tests.rst b/news/1427.tests.rst
new file mode 100644
--- /dev/null
+++ b/news/1427.tests.rst
@@ -0,0 +1 @@
+Replace ``skipif_xxx`` for platform-specific tests by markers.
diff --git a/tests/functional/test_apple_events.py b/tests/functional/test_apple_events.py
--- a/tests/functional/test_apple_events.py
+++ b/tests/functional/test_apple_events.py
@@ -20,12 +20,16 @@
 import subprocess
 import time
 
+# Third-party imports
+# -------------------
+import pytest
+
 # Local imports
 # -------------
-from PyInstaller.utils.tests import importorskip, skipif_notosx
+from PyInstaller.utils.tests import importorskip
 
 
-@skipif_notosx
+@pytest.mark.darwin
 def test_osx_custom_protocol_handler(tmpdir, pyi_builder_spec):
     tmpdir = str(tmpdir)  # Fix for Python 3.5
     app_path = os.path.join(tmpdir, 'dist',
@@ -55,7 +59,7 @@ def test_osx_custom_protocol_handler(tmpdir, pyi_builder_spec):
     assert log_lines and log_lines[-1] == url, 'Invalid arg appended'
 
 
-@skipif_notosx
+@pytest.mark.darwin
 @importorskip('PyQt5')
 def test_osx_event_forwarding(tmpdir, pyi_builder_spec):
     tmpdir = str(tmpdir)  # Fix for Python 3.5
diff --git a/tests/functional/test_basic.py b/tests/functional/test_basic.py
--- a/tests/functional/test_basic.py
+++ b/tests/functional/test_basic.py
@@ -23,16 +23,15 @@
 # Local imports
 # -------------
 from PyInstaller.compat import is_darwin, is_win, is_py37
-from PyInstaller.utils.tests import importorskip, skipif, skipif_win, \
-    skipif_winorosx, skipif_notwin, skipif_notosx, skipif_no_compiler, \
-    skipif_notlinux, xfail
+from PyInstaller.utils.tests import importorskip, skipif, \
+    skipif_no_compiler, xfail
 
 
 def test_run_from_path_environ(pyi_builder):
     pyi_builder.test_script('pyi_absolute_python_path.py', run_from_path=True)
 
 
-@skipif_winorosx
+@pytest.mark.linux
 def test_absolute_ld_library_path(pyi_builder):
     pyi_builder.test_script('pyi_absolute_ld_library_path.py')
 
@@ -41,7 +40,7 @@ def test_absolute_python_path(pyi_builder):
     pyi_builder.test_script('pyi_absolute_python_path.py')
 
 
-@skipif_notlinux
+@pytest.mark.linux
 @skipif(not os.path.exists('/proc/self/status'),
         reason='/proc/self/status does not exist')
 @pytest.mark.parametrize("symlink_name",
@@ -332,7 +331,9 @@ def MyEXE(*args, **kwargs):
     _, err = capsys.readouterr()
     assert "'import warnings' failed" not in err
 
-@skipif_win
+
+@pytest.mark.darwin
+@pytest.mark.linux
 def test_python_makefile(pyi_builder):
     pyi_builder.test_script('pyi_python_makefile.py')
 
@@ -395,7 +396,8 @@ def test_time_module(pyi_builder):
         """)
 
 
-@skipif_win
+@pytest.mark.darwin
+@pytest.mark.linux
 def test_time_module_localized(pyi_builder, monkeypatch):
     # This checks that functions 'time.ctime()' and 'time.strptime()'
     # use the same locale. There was an issue with bootloader where
@@ -499,7 +501,7 @@ def test_pywin32_win32ui(pyi_builder):
         """)
 
 
-@skipif_notwin
+@pytest.mark.win32
 def test_renamed_exe(pyi_builder):
     _old_find_executables = pyi_builder._find_executables
     def _find_executables(name):
@@ -518,10 +520,12 @@ def _find_executables(name):
 def test_spec_with_utf8(pyi_builder_spec):
     pyi_builder_spec.test_spec('spec-with-utf8.spec')
 
-@skipif_notosx
+
+@pytest.mark.darwin
 def test_osx_override_info_plist(pyi_builder_spec):
     pyi_builder_spec.test_spec('pyi_osx_override_info_plist.spec')
 
+
 def test_hook_collect_submodules(pyi_builder, script_dir):
     # This is designed to test the operation of
     # PyInstaller.utils.hook.collect_submodules. To do so:
diff --git a/tests/functional/test_import.py b/tests/functional/test_import.py
--- a/tests/functional/test_import.py
+++ b/tests/functional/test_import.py
@@ -19,7 +19,7 @@
 
 from PyInstaller.compat import is_darwin, is_win
 from PyInstaller.utils.tests import skipif, importorskip, \
-    skipif_notwin, skipif_no_compiler, xfail, has_compiler
+    skipif_no_compiler, xfail, has_compiler
 
 # :todo: find a way to get this from `conftest` or such
 # Directory with testing modules used in some tests.
@@ -316,7 +316,7 @@ def test_ctypes_CDLL_find_library__gs(pyi_builder):
             params = pytest.param(*params, marks=skipif_no_compiler(params))
         elif funcname in ("WinDLL", "OleDLL"):
             # WinDLL, OleDLL only work on windows.
-            params = pytest.param(*params, marks=skipif_notwin(params))
+            params = pytest.param(*params, marks=pytest.mark.win32)
         parameters.append(params)
 
 @pytest.mark.parametrize("funcname,test_id", parameters, ids=ids)
diff --git a/tests/functional/test_libraries.py b/tests/functional/test_libraries.py
--- a/tests/functional/test_libraries.py
+++ b/tests/functional/test_libraries.py
@@ -23,7 +23,7 @@
 # -------------
 from PyInstaller.compat import is_win, is_darwin, is_linux, is_64bits
 from PyInstaller.utils.hooks import is_module_satisfies
-from PyInstaller.utils.tests import importorskip, xfail, skipif, skipif_win
+from PyInstaller.utils.tests import importorskip, xfail, skipif
 
 # :todo: find a way to get this from `conftest` or such
 # Directory with testing modules used in some tests.
diff --git a/tests/functional/test_path_encodings.py b/tests/functional/test_path_encodings.py
--- a/tests/functional/test_path_encodings.py
+++ b/tests/functional/test_path_encodings.py
@@ -16,8 +16,6 @@
 
 import pytest
 
-from PyInstaller.utils.tests import skipif_win, skipif_winorosx, skipif_notwin
-
 
 def test_ascii_path(pyi_builder):
     distdir = pyi_builder._distdir
@@ -28,7 +26,7 @@ def test_ascii_path(pyi_builder):
     pyi_builder.test_script('pyi_path_encoding.py')
 
 
-@skipif_winorosx
+@pytest.mark.linux
 def test_linux_non_unicode_path(pyi_builder, monkeypatch):
     # If we set the locale to 'C', mbstowcs should be completely useless. This
     # test verifies that _Py_char2wchar will decode the "undecodable" bytes and
@@ -45,7 +43,9 @@ def test_linux_non_unicode_path(pyi_builder, monkeypatch):
 
     pyi_builder.test_script('pyi_path_encoding.py')
 
-@skipif_win
+
+@pytest.mark.darwin
+@pytest.mark.linux
 def test_osx_linux_unicode_path(pyi_builder, monkeypatch):
     # Mac and Linux should handle 'unicode' type filenames without problem.
     distdir = pyi_builder._distdir
@@ -60,7 +60,7 @@ def test_osx_linux_unicode_path(pyi_builder, monkeypatch):
     pyi_builder.test_script('pyi_path_encoding.py')
 
 
-@skipif_notwin
+@pytest.mark.win32
 def test_win_codepage_path(pyi_builder, monkeypatch):
     distdir = pyi_builder._distdir
     # Create some bytes and decode with the current codepage to get a filename that
@@ -77,7 +77,8 @@ def test_win_codepage_path(pyi_builder, monkeypatch):
 
     pyi_builder.test_script('pyi_path_encoding.py')
 
-@skipif_notwin
+
+@pytest.mark.win32
 def test_win_codepage_path_disabled_shortfilename(pyi_builder, monkeypatch):
     distdir = pyi_builder._distdir
     # Create some bytes and decode with the current codepage to get a filename that
@@ -105,7 +106,7 @@ def test_win_codepage_path_disabled_shortfilename(pyi_builder, monkeypatch):
     pyi_builder.test_script('pyi_path_encoding.py')
 
 
-@skipif_notwin
+@pytest.mark.win32
 def test_win_non_codepage_path(pyi_builder, monkeypatch):
     distdir = pyi_builder._distdir
     # Both eastern European and Japanese characters - no codepage should encode this.
@@ -125,12 +126,13 @@ def test_win_non_codepage_path(pyi_builder, monkeypatch):
 
     pyi_builder.test_script('pyi_path_encoding.py')
 
-@skipif_notwin
+
+@pytest.mark.win32
 def test_win_py3_no_shortpathname(pyi_builder):
     pyi_builder.test_script('pyi_win_py3_no_shortpathname.py')
 
 
-@skipif_notwin
+@pytest.mark.win32
 def test_win_TEMP_has_shortpathname(pyi_builder, monkeypatch, tmp_path):
     """Test if script if pass if $TMP holds a short path name"""
     tmp = tmp_path / "longlongfilename" / "xxx"
diff --git a/tests/functional/test_signals.py b/tests/functional/test_signals.py
--- a/tests/functional/test_signals.py
+++ b/tests/functional/test_signals.py
@@ -17,7 +17,6 @@
 # Third-party imports
 # -------------------
 import pytest
-from PyInstaller.utils.tests import skipif_win
 
 signals = sorted([
     key for key in dir(signal)
@@ -25,7 +24,8 @@
 ])
 
 
-@skipif_win
+@pytest.mark.darwin
+@pytest.mark.linux
 @pytest.mark.parametrize('signame', signals)
 @pytest.mark.parametrize('ignore', [True, False])
 def test_signal_handled(pyi_builder, signame, ignore):
diff --git a/tests/functional/test_updating_manifest.py b/tests/functional/test_updating_manifest.py
--- a/tests/functional/test_updating_manifest.py
+++ b/tests/functional/test_updating_manifest.py
@@ -12,8 +12,7 @@
 
 __author__ = 'Suzumizaki-Kimitaka(鈴見咲 君高)'
 
-from PyInstaller.utils.tests import skipif_notwin
-
+import pytest
 
 test_manifest_which_uses_non_ascii = \
     r'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@@ -41,7 +40,7 @@
     '''
 
 
-@skipif_notwin
+@pytest.mark.win32
 def test_reading_manifest(tmpdir):
     """Check not using invalid encoding when reading XML manifest files
 
@@ -51,7 +50,7 @@ def test_reading_manifest(tmpdir):
     We check here not to use local encoding against UTF-8 manifest file.
     """
     # This import only works on Windows. Place it here, protected by the
-    # ``@skipif_notwin`` decorator.
+    # `@pytest.mark.win32`` decorator.
     from PyInstaller.utils.win32 import winmanifest
 
     # We create the XML file written with UTF-8 as Microsoft tools do.
diff --git a/tests/unit/test_TOC.py b/tests/unit/test_TOC.py
--- a/tests/unit/test_TOC.py
+++ b/tests/unit/test_TOC.py
@@ -12,8 +12,9 @@
 # This contains tests for the class:``TOC``, see
 # https://pyinstaller.readthedocs.io/en/latest/advanced-topics.html#the-toc-and-tree-classes
 
+import pytest
+
 from PyInstaller.building.datastruct import TOC
-from PyInstaller.utils.tests import skipif_notwin
 
 
 ELEMS1 = (
@@ -262,7 +263,7 @@ def test_rsub_non_existing():
 # The following tests verify that case-insensitive comparisons are used on Windows
 # and only for appropriate TOC entry types
 
-@skipif_notwin
+@pytest.mark.win32
 def test_append_other_case_mixed():
     # If a binary file is added with the same filename as an existing pymodule,
     # it should not be added.
@@ -274,7 +275,7 @@ def test_append_other_case_mixed():
     assert toc == expected
 
 
-@skipif_notwin
+@pytest.mark.win32
 def test_append_other_case_pymodule():
     # python modules should not use C-I comparisons. Both 'encodings' and
     # 'EnCodIngs' should be added.
@@ -286,7 +287,7 @@ def test_append_other_case_pymodule():
     assert toc == expected
 
 
-@skipif_notwin
+@pytest.mark.win32
 def test_append_other_case_binary():
     # binary files should use C-I comparisons. 'LiBrEADlInE.so.6' should not be added.
     toc = TOC(ELEMS1)
@@ -295,7 +296,7 @@ def test_append_other_case_binary():
     assert toc == expected
 
 
-@skipif_notwin
+@pytest.mark.win32
 def test_insert_other_case_mixed():
     # If a binary file is added with the same filename as an existing pymodule,
     # it should not be added
@@ -307,7 +308,7 @@ def test_insert_other_case_mixed():
     assert toc == expected
 
 
-@skipif_notwin
+@pytest.mark.win32
 def test_insert_other_case_pymodule():
     # python modules should not use C-I comparisons. Both 'encodings' and
     # 'EnCodIngs' should be added.
@@ -319,7 +320,7 @@ def test_insert_other_case_pymodule():
     assert toc == expected
 
 
-@skipif_notwin
+@pytest.mark.win32
 def test_insert_other_case_binary():
     # binary files should use C-I comparisons. 'LiBrEADlInE.so.6' should not be added.
     toc = TOC(ELEMS1)
diff --git a/tests/unit/test_depend_utils.py b/tests/unit/test_depend_utils.py
--- a/tests/unit/test_depend_utils.py
+++ b/tests/unit/test_depend_utils.py
@@ -83,7 +83,7 @@ def locate_library(loader=ctypes.util.find_library):
     utils.scan_code_for_ctypes(co)
 
 
-@pytest.mark.skipif(not is_unix, reason="requires a Unix System")
+@pytest.mark.linux
 def test_ldconfig_cache():
     utils.load_ldconfig_cache()
     libpath = None
diff --git a/tests/unit/test_modulegraph_more.py b/tests/unit/test_modulegraph_more.py
--- a/tests/unit/test_modulegraph_more.py
+++ b/tests/unit/test_modulegraph_more.py
@@ -21,7 +21,7 @@
 import pytest
 
 from PyInstaller.lib.modulegraph import modulegraph
-from PyInstaller.utils.tests import xfail, skipif, skipif_win
+from PyInstaller.utils.tests import xfail
 
 def _import_and_get_node(tmpdir, module_name, path=None):
     script = tmpdir.join('script.py')
@@ -225,7 +225,9 @@ def test_nspackage_pep420(tmpdir):
 # :todo: test_namespace_setuptools
 # :todo: test_namespace_pkg_resources
 
-@skipif_win
+
+@pytest.mark.darwin
+@pytest.mark.linux
 def test_symlinks(tmpdir):
     base_dir = tmpdir.join('base').ensure(dir=True)
     p1_init = tmpdir.join('p1', '__init__.py').ensure()