summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2014-12-18 16:04:28 +0100
committerVictor Stinner <victor.stinner@gmail.com>2014-12-18 16:04:28 +0100
commit9d2945293f1369bc834cf4c28fbb7ab7bf603f4d (patch)
tree89a49926c43abb1fbb8ffc7e49c1390c066b69f8
parent8716a4c45cdee7d524cce1bef9906bb09dfa85e5 (diff)
parent7cbe31d0c9cddac8c2cc8d4fe1c16588761f97d4 (diff)
downloadtrollius-9d2945293f1369bc834cf4c28fbb7ab7bf603f4d.tar.gz
Merge Tulip into Trollius
-rw-r--r--.hgignore2
-rw-r--r--release.py266
-rw-r--r--tests/test_base_events.py7
-rw-r--r--tests/test_events.py9
-rw-r--r--tests/test_futures.py5
-rw-r--r--tests/test_subprocess.py10
-rw-r--r--tests/test_tasks.py6
-rw-r--r--tests/test_windows_utils.py8
-rw-r--r--trollius/base_events.py2
-rw-r--r--trollius/events.py6
-rw-r--r--trollius/test_support.py314
-rw-r--r--trollius/windows_events.py2
12 files changed, 470 insertions, 167 deletions
diff --git a/.hgignore b/.hgignore
index 9414876..186576f 100644
--- a/.hgignore
+++ b/.hgignore
@@ -14,4 +14,4 @@ dist$
.*\.egg-info$
# Directory created by the "tox" command (ex: tox -e py27)
-.tox
+\.tox$
diff --git a/release.py b/release.py
new file mode 100644
index 0000000..f39d1e1
--- /dev/null
+++ b/release.py
@@ -0,0 +1,266 @@
+"""
+Script to upload 32 bits and 64 bits wheel packages for Python 3.3 on Windows.
+
+Usage: "python release.py HG_TAG" where HG_TAG is a Mercurial tag, usually
+a version number like "3.4.2".
+
+Modify manually the dry_run attribute to upload files.
+
+It requires the Windows SDK 7.1 on Windows 64 bits and the aiotest module.
+"""
+import contextlib
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import textwrap
+
+PY3 = (sys.version_info >= (3,))
+HG = 'hg'
+_PYTHON_VERSIONS = [(3, 3)]
+PYTHON_VERSIONS = []
+for pyver in _PYTHON_VERSIONS:
+ PYTHON_VERSIONS.append((pyver, 32))
+ PYTHON_VERSIONS.append((pyver, 64))
+SDK_ROOT = r"C:\Program Files\Microsoft SDKs\Windows"
+BATCH_FAIL_ON_ERROR = "@IF %errorlevel% neq 0 exit /b %errorlevel%"
+
+class Release(object):
+ def __init__(self):
+ root = os.path.dirname(__file__)
+ self.root = os.path.realpath(root)
+ # Set these attributes to True to run also register sdist upload
+ self.register = False
+ self.sdist = False
+ self.dry_run = True
+ self.test = True
+ self.aiotest = True
+
+ @contextlib.contextmanager
+ def _popen(self, args, **kw):
+ env2 = kw.pop('env', {})
+ env = dict(os.environ)
+ # Force the POSIX locale
+ env['LC_ALL'] = 'C'
+ env.update(env2)
+ print('+ ' + ' '.join(args))
+ if PY3:
+ kw['universal_newlines'] = True
+ proc = subprocess.Popen(args, env=env, **kw)
+ with proc:
+ yield proc
+
+ def get_output(self, *args, **kw):
+ with self._popen(args, stdout=subprocess.PIPE, **kw) as proc:
+ stdout, stderr = proc.communicate()
+ return stdout
+
+ def run_command(self, *args, **kw):
+ with self._popen(args, **kw) as proc:
+ exitcode = proc.wait()
+ if exitcode:
+ sys.exit(exitcode)
+
+ def get_local_changes(self):
+ status = self.get_output(HG, 'status')
+ return [line for line in status.splitlines()
+ if not line.startswith("?")]
+
+ def remove_directory(self, name):
+ path = os.path.join(self.root, name)
+ if os.path.exists(path):
+ print("Remove directory: %s" % name)
+ shutil.rmtree(path)
+
+ def remove_file(self, name):
+ path = os.path.join(self.root, name)
+ if os.path.exists(path):
+ print("Remove file: %s" % name)
+ os.unlink(path)
+
+ def windows_sdk_setenv(self, pyver, bits):
+ if pyver >= (3, 3):
+ sdkver = "v7.1"
+ else:
+ sdkver = "v7.0"
+ setenv = os.path.join(SDK_ROOT, sdkver, 'Bin', 'SetEnv.cmd')
+ if not os.path.exists(setenv):
+ print("Unable to find Windows SDK %s for Python %s.%s"
+ % (sdkver, pyver[0], pyver[1]))
+ print("Please download and install it")
+ print("%s does not exists" % setenv)
+ sys.exit(1)
+ if bits == 64:
+ arch = '/x64'
+ else:
+ arch = '/x86'
+ return ["CALL", setenv, "/release", arch]
+
+ def get_python(self, version, bits):
+ if bits == 32:
+ python = 'c:\\Python%s%s_32bit\\python.exe' % version
+ else:
+ python = 'c:\\Python%s%s\\python.exe' % version
+ if not os.path.exists(python):
+ print("Unable to find python%s.%s" % version)
+ print("%s does not exists" % python)
+ sys.exit(1)
+ code = (
+ 'import platform, sys; '
+ 'print("{ver.major}.{ver.minor} {bits}".format('
+ 'ver=sys.version_info, '
+ 'bits=platform.architecture()[0]))'
+ )
+ stdout = self.get_output(python, '-c', code)
+ stdout = stdout.rstrip()
+ expected = "%s.%s %sbit" % (version[0], version[1], bits)
+ if stdout != expected:
+ print("Python version or architecture doesn't match")
+ print("got %r, expected %r" % (stdout, expected))
+ print(python)
+ sys.exit(1)
+ return python
+
+ def quote(self, arg):
+ if not re.search("[ '\"]", arg):
+ return arg
+ # FIXME: should we escape "?
+ return '"%s"' % arg
+
+ def quote_args(self, args):
+ return ' '.join(self.quote(arg) for arg in args)
+
+ def cleanup(self):
+ self.remove_directory('build')
+ self.remove_directory('dist')
+ self.remove_file('_overlapped.pyd')
+ self.remove_file(os.path.join('asyncio', '_overlapped.pyd'))
+
+ def sdist_upload(self):
+ self.cleanup()
+ self.run_command(sys.executable, 'setup.py', 'sdist', 'upload')
+
+ def runtests(self, pyver, bits):
+ pythonstr = "%s.%s (%s bits)" % (pyver[0], pyver[1], bits)
+ python = self.get_python(pyver, bits)
+ dbg_env = {'PYTHONASYNCIODEBUG': '1'}
+
+ self.build(pyver, bits, 'build')
+ if bits == 64:
+ arch = 'win-amd64'
+ else:
+ arch = 'win32'
+ build_dir = 'lib.%s-%s.%s' % (arch, pyver[0], pyver[1])
+ src = os.path.join(self.root, 'build', build_dir, 'asyncio', '_overlapped.pyd')
+ dst = os.path.join(self.root, 'asyncio', '_overlapped.pyd')
+ shutil.copyfile(src, dst)
+
+ args = (python, 'runtests.py', '-r')
+ print("Run runtests.py in release mode with %s" % pythonstr)
+ self.run_command(*args)
+
+ print("Run runtests.py in debug mode with %s" % pythonstr)
+ self.run_command(*args, env=dbg_env)
+
+ if self.aiotest:
+ args = (python, 'run_aiotest.py')
+ print("Run aiotest in release mode with %s" % pythonstr)
+ self.run_command(*args)
+
+ print("Run aiotest in debug mode with %s" % pythonstr)
+ self.run_command(*args, env=dbg_env)
+
+ def build(self, pyver, bits, *cmds):
+ self.cleanup()
+
+ setenv = self.windows_sdk_setenv(pyver, bits)
+
+ python = self.get_python(pyver, bits)
+
+ cmd = [python, 'setup.py'] + list(cmds)
+
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".bat", delete=False) as temp:
+ print("CD %s" % self.quote(self.root), file=temp)
+ print(self.quote_args(setenv), file=temp)
+ print(BATCH_FAIL_ON_ERROR, file=temp)
+ print("", file=temp)
+ print("SET DISTUTILS_USE_SDK=1", file=temp)
+ print("SET MSSDK=1", file=temp)
+ print(self.quote_args(cmd), file=temp)
+ print(BATCH_FAIL_ON_ERROR, file=temp)
+
+ try:
+ self.run_command(temp.name)
+ finally:
+ os.unlink(temp.name)
+
+ def test_wheel(self, pyver, bits):
+ self.build(pyver, bits, 'bdist_wheel')
+
+ def publish_wheel(self, pyver, bits):
+ self.build(pyver, bits, 'bdist_wheel', 'upload')
+
+ def main(self):
+ try:
+ pos = sys.argv[1:].index('--ignore')
+ except ValueError:
+ ignore = False
+ else:
+ ignore = True
+ del sys.argv[1+pos]
+ if len(sys.argv) != 2:
+ print("usage: %s hg_tag" % sys.argv[0])
+ sys.exit(1)
+
+ print("Directory: %s" % self.root)
+ os.chdir(self.root)
+
+ if not ignore:
+ lines = self.get_local_changes()
+ else:
+ lines = ()
+ if lines:
+ print("ERROR: Found local changes")
+ for line in lines:
+ print(line)
+ print("")
+ print("Revert local changes")
+ print("or use the --ignore command line option")
+ sys.exit(1)
+
+ hg_tag = sys.argv[1]
+ self.run_command(HG, 'up', hg_tag)
+
+ if self.test:
+ for pyver, bits in PYTHON_VERSIONS:
+ self.runtests(pyver, bits)
+
+ for pyver, bits in PYTHON_VERSIONS:
+ self.test_wheel(pyver, bits)
+
+ if self.dry_run:
+ sys.exit(0)
+
+ if self.register:
+ self.run_command(sys.executable, 'setup.py', 'register')
+
+ if self.sdist:
+ self.sdist_upload()
+
+ for pyver, bits in PYTHON_VERSIONS:
+ self.publish_wheel(pyver, bits)
+
+ print("")
+ if self.register:
+ print("Publish version %s" % hg_tag)
+ print("Uploaded:")
+ if self.sdist:
+ print("- sdist")
+ for pyver, bits in PYTHON_VERSIONS:
+ print("- Windows wheel %s bits package for Python %s.%s"
+ % (bits, pyver[0], pyver[1]))
+
+if __name__ == "__main__":
+ Release().main()
diff --git a/tests/test_base_events.py b/tests/test_base_events.py
index d5c6ffb..83a1810 100644
--- a/tests/test_base_events.py
+++ b/tests/test_base_events.py
@@ -15,9 +15,14 @@ from trollius import constants
from trollius import test_utils
from trollius.py33_exceptions import BlockingIOError
from trollius.test_utils import mock
-from trollius import test_support as support # IPV6_ENABLED, gc_collect
from trollius.time_monotonic import time_monotonic
from trollius.test_support import assert_python_ok
+try:
+ from test.script_helper import assert_python_ok
+ from test import support
+except ImportError:
+ from trollius import test_support as support
+ from trollius.test_support import assert_python_ok
MOCK_ANY = mock.ANY
diff --git a/tests/test_events.py b/tests/test_events.py
index b2e6750..c834af9 100644
--- a/tests/test_events.py
+++ b/tests/test_events.py
@@ -40,7 +40,10 @@ from trollius.py33_exceptions import (wrap_error,
FileNotFoundError)
from trollius.test_utils import mock
from trollius.time_monotonic import time_monotonic
-from trollius import test_support as support # find_unused_port, IPV6_ENABLED, TEST_HOME_DIR
+try:
+ from test import support # find_unused_port, IPV6_ENABLED, TEST_HOME_DIR
+except ImportError:
+ from trollius import test_support as support
def data_file(filename):
@@ -2288,14 +2291,14 @@ class PolicyTests(test_utils.TestCase):
def test_get_event_loop_after_set_none(self):
policy = asyncio.DefaultEventLoopPolicy()
policy.set_event_loop(None)
- self.assertRaises(AssertionError, policy.get_event_loop)
+ self.assertRaises(RuntimeError, policy.get_event_loop)
@mock.patch('trollius.events.threading.current_thread')
def test_get_event_loop_thread(self, m_current_thread):
def f():
policy = asyncio.DefaultEventLoopPolicy()
- self.assertRaises(AssertionError, policy.get_event_loop)
+ self.assertRaises(RuntimeError, policy.get_event_loop)
th = threading.Thread(target=f)
th.start()
diff --git a/tests/test_futures.py b/tests/test_futures.py
index 643b0a0..387e552 100644
--- a/tests/test_futures.py
+++ b/tests/test_futures.py
@@ -12,8 +12,11 @@ import unittest
import trollius as asyncio
from trollius import compat
from trollius import test_utils
-from trollius import test_support as support # gc_collect
from trollius.test_utils import mock
+try:
+ from test import support # gc_collect
+except ImportError:
+ from trollius import test_support as support
def get_thread_ident():
diff --git a/tests/test_subprocess.py b/tests/test_subprocess.py
index db5d08a..c65a313 100644
--- a/tests/test_subprocess.py
+++ b/tests/test_subprocess.py
@@ -6,11 +6,17 @@ import signal
import sys
import unittest
from trollius import From, Return
-from trollius import test_support as support
from trollius.test_utils import mock
+from trollius.py33_exceptions import BrokenPipeError, ConnectionResetError
+
if sys.platform != 'win32':
from trollius import unix_events
-from trollius.py33_exceptions import BrokenPipeError, ConnectionResetError
+
+try:
+ from test import support # PIPE_MAX_SIZE
+except ImportError:
+ from trollius import test_support as support
+
# Program blocking
PROGRAM_BLOCKED = [sys.executable, '-c', 'import time; time.sleep(3600)']
diff --git a/tests/test_tasks.py b/tests/test_tasks.py
index e1a8a27..8d3f776 100644
--- a/tests/test_tasks.py
+++ b/tests/test_tasks.py
@@ -14,6 +14,12 @@ from trollius import test_support as support
from trollius import test_utils
from trollius.test_support import assert_python_ok
from trollius.test_utils import mock
+try:
+ from test import support # gc_collect
+ from test.script_helper import assert_python_ok
+except ImportError:
+ from trollius import test_support as support
+ from trollius.test_support import assert_python_ok
PY33 = (sys.version_info >= (3, 3))
diff --git a/tests/test_windows_utils.py b/tests/test_windows_utils.py
index ac6d736..f40f863 100644
--- a/tests/test_windows_utils.py
+++ b/tests/test_windows_utils.py
@@ -14,7 +14,10 @@ from trollius import test_utils
from trollius import windows_utils
from trollius.test_support import IPV6_ENABLED
from trollius.test_utils import mock
-import trollius.test_support as support
+try:
+ from test import support # gc_collect, IPV6_ENABLED
+except ImportError:
+ from trollius import test_support as support
class WinsocketpairTests(unittest.TestCase):
@@ -29,7 +32,8 @@ class WinsocketpairTests(unittest.TestCase):
ssock, csock = windows_utils.socketpair()
self.check_winsocketpair(ssock, csock)
- @test_utils.skipUnless(IPV6_ENABLED, 'IPv6 not supported or enabled')
+ @test_utils.skipUnless(support.IPV6_ENABLED,
+ 'IPv6 not supported or enabled')
def test_winsocketpair_ipv6(self):
ssock, csock = windows_utils.socketpair(family=socket.AF_INET6)
self.check_winsocketpair(ssock, csock)
diff --git a/trollius/base_events.py b/trollius/base_events.py
index 2236196..fe5f0a2 100644
--- a/trollius/base_events.py
+++ b/trollius/base_events.py
@@ -425,7 +425,7 @@ class BaseEventLoop(events.AbstractEventLoop):
"""
try:
current = events.get_event_loop()
- except AssertionError:
+ except RuntimeError:
return
if current is not self:
raise RuntimeError(
diff --git a/trollius/events.py b/trollius/events.py
index 66fd49a..fa723e3 100644
--- a/trollius/events.py
+++ b/trollius/events.py
@@ -553,9 +553,9 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
not self._local._set_called and
isinstance(threading.current_thread(), threading._MainThread)):
self.set_event_loop(self.new_event_loop())
- assert self._local._loop is not None, \
- ('There is no current event loop in thread %r.' %
- threading.current_thread().name)
+ if self._local._loop is None:
+ raise RuntimeError('There is no current event loop in thread %r.'
+ % threading.current_thread().name)
return self._local._loop
def set_event_loop(self, loop):
diff --git a/trollius/test_support.py b/trollius/test_support.py
index 8bb4fba..eff2101 100644
--- a/trollius/test_support.py
+++ b/trollius/test_support.py
@@ -1,19 +1,118 @@
+# Subset of test.support from CPython 3.5, just what we need to run asyncio
+# test suite. The cde is copied from CPython 3.5 to not depend on the test
+# module because it is rarely installed.
+
+# Ignore symbol TEST_HOME_DIR: test_events works without it
+
from __future__ import absolute_import
import functools
import gc
-import os.path
+import os
import platform
import re
import socket
import subprocess
import sys
import time
+
from trollius import test_utils
-# TEST_HOME_DIR refers to the top level directory of the "test" package
-# that contains Python's regression test suite
-TEST_SUPPORT_DIR = os.path.dirname(os.path.abspath(__file__))
-TEST_HOME_DIR = os.path.dirname(TEST_SUPPORT_DIR)
+# A constant likely larger than the underlying OS pipe buffer size, to
+# make writes blocking.
+# Windows limit seems to be around 512 B, and many Unix kernels have a
+# 64 KiB pipe buffer size or 16 * PAGE_SIZE: take a few megs to be sure.
+# (see issue #17835 for a discussion of this number).
+PIPE_MAX_SIZE = 4 * 1024 * 1024 + 1
+
+def strip_python_stderr(stderr):
+ """Strip the stderr of a Python process from potential debug output
+ emitted by the interpreter.
+
+ This will typically be run on the result of the communicate() method
+ of a subprocess.Popen object.
+ """
+ stderr = re.sub(br"\[\d+ refs, \d+ blocks\]\r?\n?", b"", stderr).strip()
+ return stderr
+
+
+# Executing the interpreter in a subprocess
+def _assert_python(expected_success, *args, **env_vars):
+ if '__isolated' in env_vars:
+ isolated = env_vars.pop('__isolated')
+ else:
+ isolated = not env_vars
+ cmd_line = [sys.executable]
+ if sys.version_info >= (3, 3):
+ cmd_line.extend(('-X', 'faulthandler'))
+ if isolated and sys.version_info >= (3, 4):
+ # isolated mode: ignore Python environment variables, ignore user
+ # site-packages, and don't add the current directory to sys.path
+ cmd_line.append('-I')
+ elif not env_vars:
+ # ignore Python environment variables
+ cmd_line.append('-E')
+ # Need to preserve the original environment, for in-place testing of
+ # shared library builds.
+ env = os.environ.copy()
+ # But a special flag that can be set to override -- in this case, the
+ # caller is responsible to pass the full environment.
+ if env_vars.pop('__cleanenv', None):
+ env = {}
+ env.update(env_vars)
+ cmd_line.extend(args)
+ p = subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ env=env)
+ try:
+ out, err = p.communicate()
+ finally:
+ subprocess._cleanup()
+ p.stdout.close()
+ p.stderr.close()
+ rc = p.returncode
+ err = strip_python_stderr(err)
+ if (rc and expected_success) or (not rc and not expected_success):
+ raise AssertionError(
+ "Process return code is %d, "
+ "stderr follows:\n%s" % (rc, err.decode('ascii', 'ignore')))
+ return rc, out, err
+
+
+def assert_python_ok(*args, **env_vars):
+ """
+ Assert that running the interpreter with `args` and optional environment
+ variables `env_vars` succeeds (rc == 0) and return a (return code, stdout,
+ stderr) tuple.
+
+ If the __cleanenv keyword is set, env_vars is used a fresh environment.
+
+ Python is started in isolated mode (command line option -I),
+ except if the __isolated keyword is set to False.
+ """
+ return _assert_python(True, *args, **env_vars)
+
+
+is_jython = sys.platform.startswith('java')
+
+def gc_collect():
+ """Force as many objects as possible to be collected.
+
+ In non-CPython implementations of Python, this is needed because timely
+ deallocation is not guaranteed by the garbage collector. (Even in CPython
+ this can be the case in case of reference cycles.) This means that __del__
+ methods may be called later than expected and weakrefs may remain alive for
+ longer than expected. This function tries its best to force all garbage
+ objects to disappear.
+ """
+ gc.collect()
+ if is_jython:
+ time.sleep(0.1)
+ gc.collect()
+ gc.collect()
+
+
+HOST = "127.0.0.1"
+HOSTv6 = "::1"
def _is_ipv6_enabled():
@@ -22,7 +121,7 @@ def _is_ipv6_enabled():
sock = None
try:
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
- sock.bind(("::1", 0))
+ sock.bind((HOSTv6, 0))
return True
except OSError:
pass
@@ -34,56 +133,6 @@ def _is_ipv6_enabled():
IPV6_ENABLED = _is_ipv6_enabled()
-# A constant likely larger than the underlying OS pipe buffer size, to
-# make writes blocking.
-# Windows limit seems to be around 512 B, and many Unix kernels have a
-# 64 KiB pipe buffer size or 16 * PAGE_SIZE: take a few megs to be sure.
-# (see issue #17835 for a discussion of this number).
-PIPE_MAX_SIZE = 4 * 1024 * 1024 + 1
-
-
-class TestFailed(Exception):
- """Test failed."""
-
-
-def bind_port(sock, host="127.0.0.1"):
- """Bind the socket to a free port and return the port number. Relies on
- ephemeral ports in order to ensure we are using an unbound port. This is
- important as many tests may be running simultaneously, especially in a
- buildbot environment. This method raises an exception if the sock.family
- is AF_INET and sock.type is SOCK_STREAM, *and* the socket has SO_REUSEADDR
- or SO_REUSEPORT set on it. Tests should *never* set these socket options
- for TCP/IP sockets. The only case for setting these options is testing
- multicasting via multiple UDP sockets.
-
- Additionally, if the SO_EXCLUSIVEADDRUSE socket option is available (i.e.
- on Windows), it will be set on the socket. This will prevent anyone else
- from bind()'ing to our host/port for the duration of the test.
- """
-
- if sock.family == socket.AF_INET and sock.type == socket.SOCK_STREAM:
- if hasattr(socket, 'SO_REUSEADDR'):
- if sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) == 1:
- raise TestFailed("tests should never set the SO_REUSEADDR " \
- "socket option on TCP/IP sockets!")
- if hasattr(socket, 'SO_REUSEPORT'):
- try:
- if sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT) == 1:
- raise TestFailed("tests should never set the SO_REUSEPORT " \
- "socket option on TCP/IP sockets!")
- except EnvironmentError:
- # Python's socket module was compiled using modern headers
- # thus defining SO_REUSEPORT but this process is running
- # under an older kernel that does not support SO_REUSEPORT.
- pass
- if hasattr(socket, 'SO_EXCLUSIVEADDRUSE'):
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
-
- sock.bind((host, 0))
- port = sock.getsockname()[1]
- return port
-
-
def find_unused_port(family=socket.AF_INET, socktype=socket.SOCK_STREAM):
"""Returns an unused port that should be suitable for binding. This is
achieved by creating a temporary socket with the same family and type as
@@ -146,38 +195,55 @@ def find_unused_port(family=socket.AF_INET, socktype=socket.SOCK_STREAM):
del tempsock
return port
+def bind_port(sock, host=HOST):
+ """Bind the socket to a free port and return the port number. Relies on
+ ephemeral ports in order to ensure we are using an unbound port. This is
+ important as many tests may be running simultaneously, especially in a
+ buildbot environment. This method raises an exception if the sock.family
+ is AF_INET and sock.type is SOCK_STREAM, *and* the socket has SO_REUSEADDR
+ or SO_REUSEPORT set on it. Tests should *never* set these socket options
+ for TCP/IP sockets. The only case for setting these options is testing
+ multicasting via multiple UDP sockets.
-is_jython = sys.platform.startswith('java')
-
+ Additionally, if the SO_EXCLUSIVEADDRUSE socket option is available (i.e.
+ on Windows), it will be set on the socket. This will prevent anyone else
+ from bind()'ing to our host/port for the duration of the test.
+ """
-def gc_collect():
- """Force as many objects as possible to be collected.
+ if sock.family == socket.AF_INET and sock.type == socket.SOCK_STREAM:
+ if hasattr(socket, 'SO_REUSEADDR'):
+ if sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) == 1:
+ raise TestFailed("tests should never set the SO_REUSEADDR " \
+ "socket option on TCP/IP sockets!")
+ if hasattr(socket, 'SO_REUSEPORT'):
+ try:
+ if sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT) == 1:
+ raise TestFailed("tests should never set the SO_REUSEPORT " \
+ "socket option on TCP/IP sockets!")
+ except OSError:
+ # Python's socket module was compiled using modern headers
+ # thus defining SO_REUSEPORT but this process is running
+ # under an older kernel that does not support SO_REUSEPORT.
+ pass
+ if hasattr(socket, 'SO_EXCLUSIVEADDRUSE'):
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
- In non-CPython implementations of Python, this is needed because timely
- deallocation is not guaranteed by the garbage collector. (Even in CPython
- this can be the case in case of reference cycles.) This means that __del__
- methods may be called later than expected and weakrefs may remain alive for
- longer than expected. This function tries its best to force all garbage
- objects to disappear.
- """
- gc.collect()
- if is_jython:
- time.sleep(0.1)
- gc.collect()
- gc.collect()
+ sock.bind((host, 0))
+ port = sock.getsockname()[1]
+ return port
-def _requires_unix_version(sysname, min_version):
- """Decorator raising SkipTest if the OS is `sysname` and the version is less
- than `min_version`.
+def requires_mac_ver(*min_version):
+ """Decorator raising SkipTest if the OS is Mac OS X and the OS X
+ version if less than min_version.
- For example, @_requires_unix_version('FreeBSD', (7, 2)) raises SkipTest if
- the FreeBSD version is less than 7.2.
+ For example, @requires_mac_ver(10, 5) raises SkipTest if the OS X version
+ is lesser than 10.5.
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
- if platform.system() == sysname:
- version_txt = platform.release().split('-', 1)[0]
+ if sys.platform == 'darwin':
+ version_txt = platform.mac_ver()[0]
try:
version = tuple(map(int, version_txt.split('.')))
except ValueError:
@@ -186,34 +252,25 @@ def _requires_unix_version(sysname, min_version):
if version < min_version:
min_version_txt = '.'.join(map(str, min_version))
raise test_utils.SkipTest(
- "%s version %s or higher required, not %s"
- % (sysname, min_version_txt, version_txt))
+ "Mac OS X %s or higher required, not %s"
+ % (min_version_txt, version_txt))
return func(*args, **kw)
wrapper.min_version = min_version
return wrapper
return decorator
-def requires_freebsd_version(*min_version):
- """Decorator raising SkipTest if the OS is FreeBSD and the FreeBSD version is
- less than `min_version`.
-
- For example, @requires_freebsd_version(7, 2) raises SkipTest if the FreeBSD
- version is less than 7.2.
- """
- return _requires_unix_version('FreeBSD', min_version)
-
-def requires_mac_ver(*min_version):
- """Decorator raising SkipTest if the OS is Mac OS X and the OS X
- version if less than min_version.
+def _requires_unix_version(sysname, min_version):
+ """Decorator raising SkipTest if the OS is `sysname` and the version is less
+ than `min_version`.
- For example, @requires_mac_ver(10, 5) raises SkipTest if the OS X version
- is lesser than 10.5.
+ For example, @_requires_unix_version('FreeBSD', (7, 2)) raises SkipTest if
+ the FreeBSD version is less than 7.2.
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
- if sys.platform == 'darwin':
- version_txt = platform.mac_ver()[0]
+ if platform.system() == sysname:
+ version_txt = platform.release().split('-', 1)[0]
try:
version = tuple(map(int, version_txt.split('.')))
except ValueError:
@@ -222,65 +279,18 @@ def requires_mac_ver(*min_version):
if version < min_version:
min_version_txt = '.'.join(map(str, min_version))
raise test_utils.SkipTest(
- "Mac OS X %s or higher required, not %s"
- % (min_version_txt, version_txt))
+ "%s version %s or higher required, not %s"
+ % (sysname, min_version_txt, version_txt))
return func(*args, **kw)
wrapper.min_version = min_version
return wrapper
return decorator
+def requires_freebsd_version(*min_version):
+ """Decorator raising SkipTest if the OS is FreeBSD and the FreeBSD version is
+ less than `min_version`.
-def strip_python_stderr(stderr):
- """Strip the stderr of a Python process from potential debug output
- emitted by the interpreter.
-
- This will typically be run on the result of the communicate() method
- of a subprocess.Popen object.
- """
- stderr = re.sub(br"\[\d+ refs, \d+ blocks\]\r?\n?", b"", stderr).strip()
- return stderr
-
-# Executing the interpreter in a subprocess
-def _assert_python(expected_success, *args, **env_vars):
- cmd_line = [sys.executable]
- if not env_vars:
- # ignore Python environment variables
- cmd_line.append('-E')
- # Need to preserve the original environment, for in-place testing of
- # shared library builds.
- env = os.environ.copy()
- # But a special flag that can be set to override -- in this case, the
- # caller is responsible to pass the full environment.
- if env_vars.pop('__cleanenv', None):
- env = {}
- env.update(env_vars)
- cmd_line.extend(args)
- p = subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- env=env)
- try:
- out, err = p.communicate()
- finally:
- subprocess._cleanup()
- p.stdout.close()
- p.stderr.close()
- rc = p.returncode
- err = strip_python_stderr(err)
- if (rc and expected_success) or (not rc and not expected_success):
- raise AssertionError(
- "Process return code is %d, "
- "stderr follows:\n%s" % (rc, err.decode('ascii', 'ignore')))
- return rc, out, err
-
-def assert_python_ok(*args, **env_vars):
- """
- Assert that running the interpreter with `args` and optional environment
- variables `env_vars` succeeds (rc == 0) and return a (return code, stdout,
- stderr) tuple.
-
- If the __cleanenv keyword is set, env_vars is used a fresh environment.
-
- Python is started in isolated mode (command line option -I),
- except if the __isolated keyword is set to False.
+ For example, @requires_freebsd_version(7, 2) raises SkipTest if the FreeBSD
+ version is less than 7.2.
"""
- return _assert_python(True, *args, **env_vars)
+ return _requires_unix_version('FreeBSD', min_version)
diff --git a/trollius/windows_events.py b/trollius/windows_events.py
index 7319dff..ef9bf04 100644
--- a/trollius/windows_events.py
+++ b/trollius/windows_events.py
@@ -390,7 +390,7 @@ class IocpProactor(object):
wrap_error(ov.getresult)
return pipe
- # FIXME: Tulip issue 196: why to we neeed register=False?
+ # FIXME: Tulip issue 196: why do we need register=False?
# See also the comment in the _register() method
return self._register(ov, pipe, finish_accept_pipe,
register=False)