Tools/ChangeLog

 12021-04-22 Sam Sneddon <gsnedders@apple.com>
 2
 3 Add a conftest.py to run existing webkitpy tests in pytest
 4 https://bugs.webkit.org/show_bug.cgi?id=224687
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 * Scripts/webkitpy/common/system/executive_unittest.py:
 9 (ExecutiveTest.serial_test_run_in_parallel): Deal with the fact that pytest
 10 running the tests might be not be the same version as the autoinstalled version,
 11 and not API compatible.
 12 * Scripts/webkitpy/conftest.py: Added.
 13 (pytest_configure): Define the markers the plugins in conftest use
 14 (pytest_addoption): Add --run-integration to allow them to be disabled by default.
 15 (pytest_pycollect_makeitem): Rename serial/integration tests so pytest finds them.
 16 (pytest_collection_modifyitems): Mark tests as skipped when needed per the above.
 17 * Scripts/webkitpy/pytest.ini: Added.
 18 * Scripts/webkitpy/test/main_unittest.py:
 19 (TestStubs): Stop these from being picked up by pytest as tests.
 20 * Scripts/webkitpy/test/markers.py: Fix this so pytest is technically optional,
 21 even though it is always present because of the autoinstalled copy.
 22
1232021-04-22 Sam Sneddon <gsnedders@apple.com>
224
325 Ensure all non-local AutoInstalled libraries specify version

Tools/Scripts/webkitpy/common/system/executive_unittest.py

@@class ExecutiveTest(unittest.TestCase):
250250 cmd_line = [sys.executable, '-c', 'import time; time.sleep(%f); print("hello")' % DELAY_SECS]
251251 cwd = os.getcwd()
252252 commands = [tuple([cmd_line, cwd])] * NUM_PROCESSES
253  start = time.time()
254  command_outputs = Executive().run_in_parallel(commands, processes=NUM_PROCESSES)
255  done = time.time()
 253
 254 try:
 255 # we overwrite __main__ to be this to avoid any issues with
 256 # multiprocessing's spawning caused by multiple versions of pytest on
 257 # sys.path
 258 old_main = sys.modules["__main__"]
 259 sys.modules["__main__"] = sys.modules[__name__]
 260 start = time.time()
 261 command_outputs = Executive().run_in_parallel(commands, processes=NUM_PROCESSES)
 262 done = time.time()
 263 finally:
 264 sys.modules["__main__"] = old_main
 265
256266 self.assertTrue(done - start < NUM_PROCESSES * DELAY_SECS)
257267 self.assertEqual([output[1] for output in command_outputs], [b'hello\n'] * NUM_PROCESSES)
258268 self.assertEqual([], multiprocessing.active_children())

Tools/Scripts/webkitpy/conftest.py

 1# Copyright (C) 2021 Apple Inc. All rights reserved.
 2#
 3# Redistribution and use in source and binary forms, with or without
 4# modification, are permitted provided that the following conditions
 5# are met:
 6# 1. Redistributions of source code must retain the above copyright
 7# notice, this list of conditions and the following disclaimer.
 8# 2. Redistributions in binary form must reproduce the above copyright
 9# notice, this list of conditions and the following disclaimer in the
 10# documentation and/or other materials provided with the distribution.
 11#
 12# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
 13# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 14# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 15# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
 16# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 17# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 18# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 19# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 20# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 21# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 22
 23import warnings
 24import types
 25import sys
 26
 27import pytest
 28
 29
 30def pytest_configure(config):
 31 config.addinivalue_line("markers", "serial: tests that must be run in serial")
 32 config.addinivalue_line("markers", "integration: integration tests")
 33
 34
 35def pytest_addoption(parser):
 36 parser.addoption(
 37 "--run-integration",
 38 action="store_true",
 39 default=False,
 40 help="run integration tests",
 41 )
 42
 43
 44@pytest.hookimpl(tryfirst=True)
 45def pytest_pycollect_makeitem(collector, name, obj):
 46 try:
 47 ut = sys.modules["unittest"]
 48 if not issubclass(obj, ut.TestCase):
 49 return None
 50 except Exception:
 51 return None
 52
 53 if getattr(obj, "__pytest_no_rewrite__", False):
 54 return None
 55
 56 for attr_name in set(dir(obj)):
 57 serial = False
 58 integration = False
 59 if attr_name.startswith("serial_integration_test_"):
 60 serial = True
 61 integration = True
 62 elif attr_name.startswith("serial_test_"):
 63 serial = True
 64 elif attr_name.startswith("integration_test_"):
 65 integration = True
 66 else:
 67 continue
 68
 69 method = getattr(obj, attr_name)
 70 if not callable(method):
 71 continue
 72
 73 new_attr_name = "test_" + attr_name
 74
 75 existing_attr = getattr(obj, new_attr_name, None)
 76 if existing_attr:
 77 if method != existing_attr:
 78 warnings.warn(
 79 "attribute %r already defined on %r; %r might hide %r"
 80 % (new_attr_name, obj, method, existing_attr)
 81 )
 82
 83 if sys.version_info < (3,) and isinstance(method, types.MethodType):
 84 method = method.im_func
 85
 86 if serial:
 87 method = pytest.mark.serial(method)
 88
 89 if integration:
 90 method = pytest.mark.integration(method)
 91
 92 setattr(obj, new_attr_name, method)
 93
 94 return None
 95
 96
 97def pytest_collection_modifyitems(config, items):
 98 if hasattr(config, "workerinput"):
 99 skip_serial = pytest.mark.skip(reason="cannot run in parallel")
 100 for item in items:
 101 if "serial" in item.keywords:
 102 item.add_marker(skip_serial)
 103
 104 if not config.getoption("--run-integration"):
 105 skip_integration = pytest.mark.skip(
 106 reason="need --run-integration option to run"
 107 )
 108 for item in items:
 109 if "integration" in item.keywords:
 110 item.add_marker(skip_integration)

Tools/Scripts/webkitpy/pytest.ini

 1[pytest]
 2python_files=*_unittest.py *_integrationtest.py
 3xfail_strict=true
 4addopts = -rfEX --strict-markers -k 'not scm_unittest'
 5
 6filterwarnings =
 7 ignore:invalid escape sequence.*:DeprecationWarning
 8 ignore:Please use assert.* instead.:DeprecationWarning
 9 ignore:The 'warn' method is deprecated, use 'warning' instead:DeprecationWarning
 10 ignore:cannot collect test class 'Test[^']*' because it has a __init__ constructor:pytest.PytestCollectionWarning
 11 ignore:the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses:DeprecationWarning
 12 ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.[0-9]+ it will stop working:DeprecationWarning
 13 ignore:The readPlist function is deprecated, use load\(\) instead:DeprecationWarning
 14 ignore:inspect.getargspec\(\) is deprecated since Python 3.0, use inspect.signature\(\) or inspect.getfullargspec\(\):DeprecationWarning

Tools/Scripts/webkitpy/test/main_unittest.py

@@STUBS_CLASS = __name__ + ".TestStubs"
3838
3939
4040class TestStubs(unittest.TestCase):
 41 __pytest_no_rewrite__ = True
 42
4143 def test_empty(self):
4244 pass
4345

Tools/Scripts/webkitpy/test/markers.py

2222
2323import unittest
2424
 25try:
 26 import pytest
 27except ImportError:
 28 pass
 29
2530
2631def xfail(*args, **kwargs):
2732 """a pytest.mark.xfail-like wrapper for unittest.expectedFailure