# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for parts of our release automation system.
"""
import os
from pkg_resources import parse_requirements
from setuptools.dist import Distribution
import twisted
from twisted.trial.unittest import TestCase
from twisted.python import _setup, filepath
from twisted.python.compat import _PY3
from twisted.python._setup import (
BuildPy3,
getSetupArgs,
ConditionalExtension,
_EXTRAS_REQUIRE,
)
class SetupTests(TestCase):
"""
Tests for L{getSetupArgs}.
"""
def test_conditionalExtensions(self):
"""
Will return the arguments with a custom build_ext which knows how to
check whether they should be built.
"""
good_ext = ConditionalExtension("whatever", ["whatever.c"],
condition=lambda b: True)
bad_ext = ConditionalExtension("whatever", ["whatever.c"],
condition=lambda b: False)
args = getSetupArgs(extensions=[good_ext, bad_ext])
# ext_modules should be set even though it's not used. See comment
# in getSetupArgs
self.assertEqual(args["ext_modules"], [good_ext, bad_ext])
cmdclass = args["cmdclass"]
build_ext = cmdclass["build_ext"]
builder = build_ext(Distribution())
builder.prepare_extensions()
self.assertEqual(builder.extensions, [good_ext])
def test_win32Definition(self):
"""
When building on Windows NT, the WIN32 macro will be defined as 1 on
the extensions.
"""
ext = ConditionalExtension("whatever", ["whatever.c"],
define_macros=[("whatever", 2)])
args = getSetupArgs(extensions=[ext])
builder = args["cmdclass"]["build_ext"](Distribution())
self.patch(os, "name", "nt")
builder.prepare_extensions()
self.assertEqual(ext.define_macros, [("whatever", 2), ("WIN32", 1)])
class OptionalDependenciesTests(TestCase):
"""
Tests for L{_EXTRAS_REQUIRE}
"""
def test_distributeTakesExtrasRequire(self):
"""
Setuptools' Distribution object parses and stores its C{extras_require}
argument as an attribute.
Requirements for install_requires/setup_requires can specified as:
* a single requirement as a string, such as:
{'im_an_extra_dependency': 'thing'}
* a series of requirements as a list, such as:
{'im_an_extra_dependency': ['thing']}
* a series of requirements as a multi-line string, such as:
{'im_an_extra_dependency': '''
thing
'''}
The extras need to be parsed with pkg_resources.parse_requirements(),
which returns a generator.
"""
extras = dict(im_an_extra_dependency=["thing"])
attrs = dict(extras_require=extras)
distribution = Distribution(attrs)
def canonicalizeExtras(myExtras):
parsedExtras = {}
for name, val in myExtras.items():
parsedExtras[name] = list(parse_requirements(val))
return parsedExtras
self.assertEqual(
canonicalizeExtras(extras),
canonicalizeExtras(distribution.extras_require)
)
def test_extrasRequireDictContainsKeys(self):
"""
L{_EXTRAS_REQUIRE} contains options for all documented extras: C{dev},
C{tls}, C{conch}, C{soap}, C{serial}, C{all_non_platform},
C{osx_platform}, and C{windows_platform}.
"""
self.assertIn('dev', _EXTRAS_REQUIRE)
self.assertIn('tls', _EXTRAS_REQUIRE)
self.assertIn('conch', _EXTRAS_REQUIRE)
self.assertIn('soap', _EXTRAS_REQUIRE)
self.assertIn('serial', _EXTRAS_REQUIRE)
self.assertIn('all_non_platform', _EXTRAS_REQUIRE)
self.assertIn('osx_platform', _EXTRAS_REQUIRE)
self.assertIn('windows_platform', _EXTRAS_REQUIRE)
self.assertIn('http2', _EXTRAS_REQUIRE)
def test_extrasRequiresDevDeps(self):
"""
L{_EXTRAS_REQUIRE}'s C{dev} extra contains setuptools requirements for
the tools required for Twisted development.
"""
deps = _EXTRAS_REQUIRE['dev']
self.assertIn('pyflakes >= 1.0.0', deps)
self.assertIn('twisted-dev-tools >= 0.0.2', deps)
self.assertIn('python-subunit', deps)
self.assertIn('sphinx >= 1.3.1', deps)
if not _PY3:
self.assertIn('twistedchecker >= 0.4.0', deps)
self.assertIn('pydoctor >= 16.2.0', deps)
def test_extrasRequiresTlsDeps(self):
"""
L{_EXTRAS_REQUIRE}'s C{tls} extra contains setuptools requirements for
the packages required to make Twisted's transport layer security fully
work for both clients and servers.
"""
deps = _EXTRAS_REQUIRE['tls']
self.assertIn('pyopenssl >= 16.0.0', deps)
self.assertIn('service_identity', deps)
self.assertIn('idna >= 0.6, != 2.3', deps)
def test_extrasRequiresConchDeps(self):
"""
L{_EXTRAS_REQUIRE}'s C{conch} extra contains setuptools requirements
for the packages required to make Twisted Conch's secure shell server
work.
"""
deps = _EXTRAS_REQUIRE['conch']
self.assertIn('pyasn1', deps)
self.assertIn('cryptography >= 1.5', deps)
self.assertIn('appdirs >= 1.4.0', deps)
def test_extrasRequiresSoapDeps(self):
"""
L{_EXTRAS_REQUIRE}' C{soap} extra contains setuptools requirements for
the packages required to make the C{twisted.web.soap} module function.
"""
self.assertIn(
'soappy',
_EXTRAS_REQUIRE['soap']
)
def test_extrasRequiresSerialDeps(self):
"""
L{_EXTRAS_REQUIRE}'s C{serial} extra contains setuptools requirements
for the packages required to make Twisted's serial support work.
"""
self.assertIn(
'pyserial >= 3.0',
_EXTRAS_REQUIRE['serial']
)
def test_extrasRequiresHttp2Deps(self):
"""
L{_EXTRAS_REQUIRE}'s C{http2} extra contains setuptools requirements
for the packages required to make Twisted HTTP/2 support work.
"""
deps = _EXTRAS_REQUIRE['http2']
self.assertIn('h2 >= 3.0, < 4.0', deps)
self.assertIn('priority >= 1.1.0, < 2.0', deps)
def test_extrasRequiresAllNonPlatformDeps(self):
"""
L{_EXTRAS_REQUIRE}'s C{all_non_platform} extra contains setuptools
requirements for all of Twisted's optional dependencies which work on
all supported operating systems.
"""
deps = _EXTRAS_REQUIRE['all_non_platform']
self.assertIn('pyopenssl >= 16.0.0', deps)
self.assertIn('service_identity', deps)
self.assertIn('idna >= 0.6, != 2.3', deps)
self.assertIn('pyasn1', deps)
self.assertIn('cryptography >= 1.5', deps)
self.assertIn('soappy', deps)
self.assertIn('pyserial >= 3.0', deps)
self.assertIn('appdirs >= 1.4.0', deps)
self.assertIn('h2 >= 3.0, < 4.0', deps)
self.assertIn('priority >= 1.1.0, < 2.0', deps)
def test_extrasRequiresOsxPlatformDeps(self):
"""
L{_EXTRAS_REQUIRE}'s C{osx_platform} extra contains setuptools
requirements for all of Twisted's optional dependencies usable on the
Mac OS X platform.
"""
deps = _EXTRAS_REQUIRE['osx_platform']
self.assertIn('pyopenssl >= 16.0.0', deps)
self.assertIn('service_identity', deps)
self.assertIn('idna >= 0.6, != 2.3', deps)
self.assertIn('pyasn1', deps)
self.assertIn('cryptography >= 1.5', deps)
self.assertIn('soappy', deps)
self.assertIn('pyserial >= 3.0', deps)
self.assertIn('h2 >= 3.0, < 4.0', deps)
self.assertIn('priority >= 1.1.0, < 2.0', deps)
self.assertIn('pyobjc-core', deps)
def test_extrasRequiresWindowsPlatformDeps(self):
"""
L{_EXTRAS_REQUIRE}'s C{windows_platform} extra contains setuptools
requirements for all of Twisted's optional dependencies usable on the
Microsoft Windows platform.
"""
deps = _EXTRAS_REQUIRE['windows_platform']
self.assertIn('pyopenssl >= 16.0.0', deps)
self.assertIn('service_identity', deps)
self.assertIn('idna >= 0.6, != 2.3', deps)
self.assertIn('pyasn1', deps)
self.assertIn('cryptography >= 1.5', deps)
self.assertIn('soappy', deps)
self.assertIn('pyserial >= 3.0', deps)
self.assertIn('h2 >= 3.0, < 4.0', deps)
self.assertIn('priority >= 1.1.0, < 2.0', deps)
self.assertIn('pypiwin32', deps)
class FakeModule(object):
"""
A fake module, suitable for dependency injection in testing.
"""
def __init__(self, attrs):
"""
Initializes a fake module.
@param attrs: The attrs that will be accessible on the module.
@type attrs: C{dict} of C{str} (Python names) to objects
"""
self._attrs = attrs
def __getattr__(self, name):
"""
Gets an attribute of this fake module from its attrs.
@raise AttributeError: When the requested attribute is missing.
"""
try:
return self._attrs[name]
except KeyError:
raise AttributeError()
fakeCPythonPlatform = FakeModule({"python_implementation": lambda: "CPython"})
fakeOtherPlatform = FakeModule({"python_implementation": lambda: "lvhpy"})
class WithPlatformTests(TestCase):
"""
Tests for L{_checkCPython} when used with a (fake) C{platform} module.
"""
def test_cpython(self):
"""
L{_checkCPython} returns C{True} when C{platform.python_implementation}
says we're running on CPython.
"""
self.assertTrue(_setup._checkCPython(platform=fakeCPythonPlatform))
def test_other(self):
"""
L{_checkCPython} returns C{False} when C{platform.python_implementation}
says we're not running on CPython.
"""
self.assertFalse(_setup._checkCPython(platform=fakeOtherPlatform))
class BuildPy3Tests(TestCase):
"""
Tests for L{BuildPy3}.
"""
maxDiff = None
if not _PY3:
skip = "BuildPy3 setuptools command used with Python 3 only."
def test_find_package_modules(self):
"""
Will filter the found modules excluding the modules listed in
L{twisted.python.dist3}.
"""
distribution = Distribution()
distribution.script_name = 'setup.py'
distribution.script_args = 'build_py'
builder = BuildPy3(distribution)
# Rig the dist3 data so that we can reduce the scope of this test and
# reduce the risk of getting false failures, while doing a minimum
# level of patching.
self.patch(
_setup,
'notPortedModules',
[
"twisted.spread.test.test_pbfailure",
],
)
twistedPackageDir = filepath.FilePath(twisted.__file__).parent()
packageDir = twistedPackageDir.child("spread").child("test")
result = builder.find_package_modules('twisted.spread.test',
packageDir.path)
self.assertEqual(sorted([
('twisted.spread.test', '__init__',
packageDir.child('__init__.py').path),
('twisted.spread.test', 'test_banana',
packageDir.child('test_banana.py').path),
('twisted.spread.test', 'test_jelly',
packageDir.child('test_jelly.py').path),
('twisted.spread.test', 'test_pb',
packageDir.child('test_pb.py').path),
]),
sorted(result),
)