Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Support marking modules.
  • Loading branch information
serhiy-storchaka committed Sep 11, 2023
commit 16ee29fe8c7dbae76ad7268b4c0e667574d86dfc
42 changes: 33 additions & 9 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,9 @@ def is_resource_enabled(resource):

def requires(resource, msg=None):
"""Raise ResourceDenied if the specified resource is not available."""
f = sys._getframe(1)
if f.f_globals is f.f_locals:
mark(f'requires_{resource}', globals=f.f_globals)
if not is_resource_enabled(resource):
if msg is None:
msg = "Use of the %r resource not enabled" % resource
Expand Down Expand Up @@ -530,22 +533,27 @@ def requires_fork():

def requires_subprocess():
"""Used for subprocess, os.spawn calls, fd inheritance"""
return skipUnless(has_subprocess_support, "requires subprocess support", label='requires_subprocess')
return skipUnless(has_subprocess_support, "requires subprocess support",
label='requires_subprocess')

# Emscripten's socket emulation and WASI sockets have limitations.
has_socket_support = not is_emscripten and not is_wasi

def requires_working_socket(*, module=False):
def requires_working_socket(*, module=False, globals=None):
"""Skip tests or modules that require working sockets

Can be used as a function/class decorator or to skip an entire module.
"""
label = 'requires_socket'
msg = "requires socket support"
if module:
if module or globals is not None:
if globals is None:
globals = sys._getframe(1).f_globals
mark(label, globals=globals)
if not has_socket_support:
raise unittest.SkipTest(msg)
else:
return skipUnless(has_socket_support, msg, label='requires_socket')
return skipUnless(has_socket_support, msg, label=label)

# Does strftime() support glibc extension like '%4Y'?
has_strftime_extensions = False
Expand Down Expand Up @@ -1006,9 +1014,18 @@ def wrapper(self):
#=======================================================================
# unittest integration.

def mark(label):
def mark(label, *, globals=None):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a similar API in one of my OS libs, users didn't like it at all :(

I think it would be better to use mark(..., *, module=False)

This way we can:

  1. Simplify the API for readers (even if our users are people working on CPython)
  2. Use frame hack to get the needed globals()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not like to depend on such hack it tests. If it does not work, many unrelated tests will not even be able to load.

Since it is an internal API used in limited number of places, simplicity for users is less important. support.mark('gui', globals=globals()) is not much worse than support.mark('gui', module=True)

"""Add a label to test.

To add a label to method or class, use it as a decorator.

To add a label to module, pass the globals() dict as the globals argument.
"""
if globals is not None:
globals[f'_label_{label}'] = True
return
def decorator(test):
setattr(test, label, True)
setattr(test, f'_label_{label}', True)
return test
return decorator

Expand All @@ -1026,11 +1043,12 @@ def skipIf(condition, reason, *, label):
return combine(unittest.skipIf(condition, reason), mark(label))

def requires_resource(resource):
label = 'requires_' + resource
if resource == 'gui' and not _is_gui_available():
return skipUnless(False, _is_gui_available.reason, label='requires_gui')
return skipUnless(False, _is_gui_available.reason, label=label)
return skipUnless(is_resource_enabled(resource),
f"resource {resource!r} is not enabled",
label='requires_' + resource)
label=label)

def cpython_only(test):
"""
Expand Down Expand Up @@ -1240,7 +1258,7 @@ def match_function(test_id):

def _check_obj_labels(obj, labels):
for label in labels:
if hasattr(obj, label):
if hasattr(obj, f'_label_{label}'):
return True
return False

Expand All @@ -1252,6 +1270,12 @@ def _check_test_labels(test, labels):
if _check_obj_labels(testMethod, labels):
return True
testMethod = getattr(testMethod, '__wrapped__', None)
try:
module = sys.modules[test.__class__.__module__]
if _check_obj_labels(module, labels):
return True
except KeyError:
pass
return False

def set_match_tests2(accept_labels=None, ignore_labels=None):
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/support/import_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import unittest
import warnings

from .. import support
from .os_helper import unlink


Expand Down Expand Up @@ -74,6 +75,9 @@ def import_module(name, deprecated=False, *, required_on=()):
compared against sys.platform.
"""
with _ignore_deprecated_imports(deprecated):
f = sys._getframe(1)
if f.f_globals is f.f_locals:
support.mark(f'requires_{name}', globals=f.f_globals)
try:
return importlib.import_module(name)
except ImportError as msg:
Expand Down
1 change: 1 addition & 0 deletions Lib/test/support/socket_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ def _is_ipv6_enabled():
_bind_nix_socket_error = None
def skip_unless_bind_unix_socket(test):
"""Decorator for tests requiring a functional bind() for unix sockets."""
test = support.mark('requires_unix_sockets')(test)
if not hasattr(socket, 'AF_UNIX'):
return unittest.skip('No UNIX Sockets')(test)
global _bind_nix_socket_error
Expand Down
10 changes: 7 additions & 3 deletions Lib/test/support/threading_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,14 +234,18 @@ def _can_start_thread() -> bool:

can_start_thread = _can_start_thread()

def requires_working_threading(*, module=False):
def requires_working_threading(*, module=False, globals=None):
"""Skip tests or modules that require working threading.

Can be used as a function/class decorator or to skip an entire module.
"""
label = 'requires_threading'
msg = "requires threading support"
if module:
if module or globals is not None:
if globals is None:
globals = sys._getframe(1).f_globals
support.mark(label, globals=globals)
if not can_start_thread:
raise unittest.SkipTest(msg)
else:
return unittest.skipUnless(can_start_thread, msg)
return support.skipUnless(can_start_thread, msg, label=label)