Skip to content

Commit

Permalink
bpo-40280: Skip socket, fork, subprocess tests on Emscripten (GH-31986)
Browse files Browse the repository at this point in the history
- Add requires_fork and requires_subprocess to more tests
- Skip extension import tests if dlopen is not available
- Don't assume that _testcapi is a shared extension
- Skip a lot of socket tests that don't work on Emscripten
- Skip mmap tests, mmap emulation is incomplete
- venv does not work yet
- Cannot get libc from executable

The "entire" test suite is now passing on Emscripten with EMSDK from git head (91 suites are skipped).
  • Loading branch information
tiran authored Mar 22, 2022
1 parent a25a985 commit deeaac4
Show file tree
Hide file tree
Showing 52 changed files with 238 additions and 23 deletions.
2 changes: 1 addition & 1 deletion Lib/test/lock_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from test.support import threading_helper


requires_fork = unittest.skipUnless(hasattr(os, 'fork'),
requires_fork = unittest.skipUnless(support.has_fork_support,
"platform doesn't support fork "
"(no _at_fork_reinit method)")

Expand Down
3 changes: 2 additions & 1 deletion Lib/test/pythoninfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import re
import sys
import traceback
import unittest
import warnings


Expand Down Expand Up @@ -615,7 +616,7 @@ def collect_resource(info_add):
def collect_test_socket(info_add):
try:
from test import test_socket
except ImportError:
except (ImportError, unittest.SkipTest):
return

# all check attributes like HAVE_SOCKET_CAN
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"requires_IEEE_754", "requires_zlib",
"has_fork_support", "requires_fork",
"has_subprocess_support", "requires_subprocess",
"has_socket_support", "requires_working_socket",
"anticipate_failure", "load_package_tests", "detect_api_mismatch",
"check__all__", "skip_if_buggy_ucrt_strfptime",
"check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer",
Expand Down Expand Up @@ -520,6 +521,21 @@ def requires_subprocess():
"""Used for subprocess, os.spawn calls, fd inheritance"""
return unittest.skipUnless(has_subprocess_support, "requires subprocess support")

# Emscripten's socket emulation has limitation. WASI doesn't have sockets yet.
has_socket_support = not is_emscripten and not is_wasi

def requires_working_socket(*, module=False):
"""Skip tests or modules that require working sockets
Can be used as a function/class decorator or to skip an entire module.
"""
msg = "requires socket support"
if module:
if not has_socket_support:
raise unittest.SkipTest(msg)
else:
return unittest.skipUnless(has_socket_support, msg)

# Does strftime() support glibc extension like '%4Y'?
has_strftime_extensions = False
if sys.platform != "win32":
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_asyncgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import contextlib

from test.support.import_helper import import_module
from test.support import gc_collect
from test.support import gc_collect, requires_working_socket
asyncio = import_module("asyncio")


requires_working_socket(module=True)

_no_default = object()


Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_asynchat.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import asynchat
import asyncore

support.requires_working_socket(module=True)

HOST = socket_helper.HOST
SERVER_QUIT = b'QUIT\n'

Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_asyncio/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os
from test import support
from test.support import load_package_tests
from test.support import import_helper

support.requires_working_socket(module=True)

# Skip tests if we don't have concurrent.futures.
import_helper.import_module('concurrent.futures')
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_asyncore.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
if support.PGO:
raise unittest.SkipTest("test is not helpful for PGO")

support.requires_working_socket(module=True)

import warnings
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_contextlib_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from test.test_contextlib import TestBaseExitStack

support.requires_working_socket(module=True)

def _async_test(func):
"""Decorator to turn an async function into a test case."""
Expand Down
7 changes: 6 additions & 1 deletion Lib/test/test_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
import types
import contextlib


if not support.has_subprocess_support:
raise unittest.SkipTest("test_CLI requires subprocess support.")


# NOTE: There are some additional tests relating to interaction with
# zipimport in the test_zipimport_support test module.

Expand Down Expand Up @@ -455,7 +460,7 @@ def basics(): r"""
>>> tests = finder.find(sample_func)
>>> print(tests) # doctest: +ELLIPSIS
[<DocTest sample_func from test_doctest.py:28 (1 example)>]
[<DocTest sample_func from test_doctest.py:33 (1 example)>]
The exact name depends on how test_doctest was invoked, so allow for
leading path components.
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_docxmlrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import sys
import threading
import unittest
from test import support

support.requires_working_socket(module=True)

def make_request_and_skipIf(condition, reason):
# If we skip the test, we have to make a request because
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_ftplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import asyncore
import asynchat

support.requires_working_socket(module=True)

TIMEOUT = support.LOOPBACK_TIMEOUT
DEFAULT_ENCODING = 'utf-8'
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_httplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from test.support import socket_helper
from test.support import warnings_helper

support.requires_working_socket(module=True)

here = os.path.dirname(__file__)
# Self-signed cert file for 'localhost'
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_httpservers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from test.support import os_helper
from test.support import threading_helper

support.requires_working_socket(module=True)

class NoLogRequestHandler:
def log_message(self, *args):
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
import socket

from test.support import (verbose,
run_with_tz, run_with_locale, cpython_only)
run_with_tz, run_with_locale, cpython_only,
requires_working_socket)
from test.support import hashlib_helper
from test.support import threading_helper
from test.support import warnings_helper
Expand All @@ -23,6 +24,8 @@
except ImportError:
ssl = None

support.requires_working_socket(module=True)

CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "keycert3.pem")
CAFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "pycacert.pem")

Expand Down
16 changes: 13 additions & 3 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from test.support import os_helper
from test.support import (
STDLIB_DIR, is_jython, swap_attr, swap_item, cpython_only)
STDLIB_DIR, is_jython, swap_attr, swap_item, cpython_only, is_emscripten)
from test.support.import_helper import (
forget, make_legacy_pyc, unlink, unload, DirsOnSysPath, CleanImport)
from test.support.os_helper import (
Expand Down Expand Up @@ -101,8 +101,17 @@ def test_from_import_missing_attr_has_name_and_so_path(self):
with self.assertRaises(ImportError) as cm:
from _testcapi import i_dont_exist
self.assertEqual(cm.exception.name, '_testcapi')
self.assertEqual(cm.exception.path, _testcapi.__file__)
self.assertRegex(str(cm.exception), r"cannot import name 'i_dont_exist' from '_testcapi' \(.*\.(so|pyd)\)")
if hasattr(_testcapi, "__file__"):
self.assertEqual(cm.exception.path, _testcapi.__file__)
self.assertRegex(
str(cm.exception),
r"cannot import name 'i_dont_exist' from '_testcapi' \(.*\.(so|pyd)\)"
)
else:
self.assertEqual(
str(cm.exception),
"cannot import name 'i_dont_exist' from '_testcapi' (unknown location)"
)

def test_from_import_missing_attr_has_name(self):
with self.assertRaises(ImportError) as cm:
Expand Down Expand Up @@ -525,6 +534,7 @@ class FilePermissionTests(unittest.TestCase):

@unittest.skipUnless(os.name == 'posix',
"test meaningful only on posix systems")
@unittest.skipIf(is_emscripten, "Emscripten's umask is a stub.")
def test_creation_mode(self):
mask = 0o022
with temp_umask(mask), _ready_to_import() as (name, path):
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_importlib/extension/test_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ class FinderTests(abc.FinderTests):

"""Test the finder for extension modules."""

def setUp(self):
if not self.machinery.EXTENSION_SUFFIXES:
raise unittest.SkipTest("Requires dynamic loading support.")

def find_spec(self, fullname):
importer = self.machinery.FileFinder(util.EXTENSIONS.path,
(self.machinery.ExtensionFileLoader,
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_importlib/extension/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
import importlib
from test.support.script_helper import assert_python_failure


class LoaderTests(abc.LoaderTests):

"""Test load_module() for extension modules."""

def setUp(self):
if not self.machinery.EXTENSION_SUFFIXES:
raise unittest.SkipTest("Requires dynamic loading support.")
self.loader = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
util.EXTENSIONS.file_path)

Expand Down Expand Up @@ -91,6 +94,8 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
# Test loading extension modules with multi-phase initialization (PEP 489).

def setUp(self):
if not self.machinery.EXTENSION_SUFFIXES:
raise unittest.SkipTest("Requires dynamic loading support.")
self.name = '_testmultiphase'
finder = self.machinery.FileFinder(None)
self.spec = importlib.util.find_spec(self.name)
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_json/test_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from test.support.script_helper import assert_python_ok


@support.requires_subprocess()
class TestTool(unittest.TestCase):
data = """
Expand Down
12 changes: 12 additions & 0 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
except ImportError:
pass


class BaseTest(unittest.TestCase):

"""Base class for logging tests."""
Expand Down Expand Up @@ -626,6 +627,9 @@ def test_path_objects(self):
os.unlink(fn)

@unittest.skipIf(os.name == 'nt', 'WatchedFileHandler not appropriate for Windows.')
@unittest.skipIf(
support.is_emscripten, "Emscripten cannot fstat unlinked files."
)
def test_race(self):
# Issue #14632 refers.
def remove_loop(fname, tries):
Expand Down Expand Up @@ -1058,6 +1062,7 @@ class TestUnixDatagramServer(TestUDPServer):

# - end of server_helper section

@support.requires_working_socket()
class SMTPHandlerTest(BaseTest):
# bpo-14314, bpo-19665, bpo-34092: don't wait forever
TIMEOUT = support.LONG_TIMEOUT
Expand Down Expand Up @@ -1681,6 +1686,7 @@ def test_defaults_do_no_interpolation(self):
os.unlink(fn)


@support.requires_working_socket()
class SocketHandlerTest(BaseTest):

"""Test for SocketHandler objects."""
Expand Down Expand Up @@ -1795,6 +1801,7 @@ def tearDown(self):
SocketHandlerTest.tearDown(self)
os_helper.unlink(self.address)

@support.requires_working_socket()
class DatagramHandlerTest(BaseTest):

"""Test for DatagramHandler."""
Expand Down Expand Up @@ -1876,6 +1883,7 @@ def tearDown(self):
DatagramHandlerTest.tearDown(self)
os_helper.unlink(self.address)

@support.requires_working_socket()
class SysLogHandlerTest(BaseTest):

"""Test for SysLogHandler using UDP."""
Expand Down Expand Up @@ -1985,6 +1993,7 @@ def tearDown(self):
self.server_class.address_family = socket.AF_INET
super(IPv6SysLogHandlerTest, self).tearDown()

@support.requires_working_socket()
class HTTPHandlerTest(BaseTest):
"""Test for HTTPHandler."""

Expand Down Expand Up @@ -3261,6 +3270,7 @@ def setup_via_listener(self, text, verify=None):
logging.config.stopListening()
threading_helper.join_thread(t)

@support.requires_working_socket()
def test_listen_config_10_ok(self):
with support.captured_stdout() as output:
self.setup_via_listener(json.dumps(self.config10))
Expand All @@ -3280,6 +3290,7 @@ def test_listen_config_10_ok(self):
('ERROR', '4'),
], stream=output)

@support.requires_working_socket()
def test_listen_config_1_ok(self):
with support.captured_stdout() as output:
self.setup_via_listener(textwrap.dedent(ConfigFileTest.config1))
Expand All @@ -3294,6 +3305,7 @@ def test_listen_config_1_ok(self):
# Original logger output is empty.
self.assert_log_lines([])

@support.requires_working_socket()
def test_listen_verify(self):

def verify_fail(stuff):
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_mailbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -1061,7 +1061,7 @@ def test_add_and_close(self):
self.assertEqual(contents, f.read())
self._box = self._factory(self._path)

@unittest.skipUnless(hasattr(os, 'fork'), "Test needs fork().")
@support.requires_fork()
@unittest.skipUnless(hasattr(socket, 'socketpair'), "Test needs socketpair().")
def test_lock_conflict(self):
# Fork off a child process that will lock the mailbox temporarily,
Expand Down
10 changes: 9 additions & 1 deletion Lib/test/test_mmap.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from test.support import (requires, _2G, _4G, gc_collect, cpython_only)
from test.support import (
requires, _2G, _4G, gc_collect, cpython_only, is_emscripten
)
from test.support.import_helper import import_module
from test.support.os_helper import TESTFN, unlink
import unittest
Expand All @@ -21,6 +23,12 @@ def random_tagname(length=10):
suffix = ''.join(random.choices(string.ascii_uppercase, k=length))
return f'{tagname_prefix}_{suffix}'

# Python's mmap module dup()s the file descriptor. Emscripten's FS layer
# does not materialize file changes through a dupped fd to a new mmap.
if is_emscripten:
raise unittest.SkipTest("incompatible with Emscripten's mmap emulation.")


class MmapTests(unittest.TestCase):

def setUp(self):
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from contextlib import ExitStack, redirect_stdout
from io import StringIO
from test import support
from test.support import os_helper
# This little helper class is essential for testing pdb under doctest.
from test.test_doctest import _FakeInput
Expand Down Expand Up @@ -1363,6 +1364,7 @@ def test_pdb_issue_43318():
"""


@support.requires_subprocess()
class PdbTestCase(unittest.TestCase):
def tearDown(self):
os_helper.unlink(os_helper.TESTFN)
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_peg_generator/test_c_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def test_parse(self):
"""


@support.requires_subprocess()
class TestCParser(unittest.TestCase):
def setUp(self):
self._backup_config_vars = dict(sysconfig._CONFIG_VARS)
Expand Down
Loading

0 comments on commit deeaac4

Please sign in to comment.