Ausgabe der neuen DB Einträge

This commit is contained in:
hubobel 2022-01-02 21:50:48 +01:00
parent bad48e1627
commit cfbbb9ee3d
2399 changed files with 843193 additions and 43 deletions

View file

@ -0,0 +1,3 @@
"""
Unit tests for L{twisted.python}.
"""

View file

@ -0,0 +1,56 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
import inspect
from twisted.python.deprecate import _passedSignature
from twisted.trial.unittest import SynchronousTestCase
class KeywordOnlyTests(SynchronousTestCase):
"""
Keyword only arguments (PEP 3102).
"""
def checkPassed(self, func, *args, **kw):
"""
Test an invocation of L{passed} with the given function, arguments, and
keyword arguments.
@param func: A function whose argspec to pass to L{_passed}.
@type func: A callable.
@param args: The arguments which could be passed to L{func}.
@param kw: The keyword arguments which could be passed to L{func}.
@return: L{_passed}'s return value
@rtype: L{dict}
"""
return _passedSignature(inspect.signature(func), args, kw)
def test_passedKeywordOnly(self):
"""
Keyword only arguments follow varargs.
They are specified in PEP 3102.
"""
def func1(*a, b=True):
"""
b is a keyword-only argument, with a default value.
"""
def func2(*a, b=True, c, d, e):
"""
b, c, d, e are keyword-only arguments.
b has a default value.
"""
self.assertEqual(self.checkPassed(func1, 1, 2, 3),
dict(a=(1, 2, 3), b=True))
self.assertEqual(self.checkPassed(func1, 1, 2, 3, b=False),
dict(a=(1, 2, 3), b=False))
self.assertEqual(self.checkPassed(func2,
1, 2, b=False, c=1, d=2, e=3),
dict(a=(1, 2), b=False, c=1, d=2, e=3))
self.assertRaises(TypeError, self.checkPassed,
func2, 1, 2, b=False, c=1, d=2)

View file

@ -0,0 +1,28 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
A module that is deprecated, used by L{twisted.python.test.test_deprecate} for
testing purposes.
"""
from __future__ import division, absolute_import
from incremental import Version
from twisted.python.deprecate import deprecatedModuleAttribute
# Known module-level attributes.
DEPRECATED_ATTRIBUTE = 42
ANOTHER_ATTRIBUTE = 'hello'
version = Version('Twisted', 8, 0, 0)
message = 'Oh noes!'
deprecatedModuleAttribute(
version,
message,
__name__,
'DEPRECATED_ATTRIBUTE')

View file

@ -0,0 +1,55 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Facilities for helping test code which interacts with Python's module system
to load code.
"""
from __future__ import division, absolute_import
import sys
from twisted.python.filepath import FilePath
class TwistedModulesMixin(object):
"""
A mixin for C{twisted.trial.unittest.SynchronousTestCase} providing useful
methods for manipulating Python's module system.
"""
def replaceSysPath(self, sysPath):
"""
Replace sys.path, for the duration of the test, with the given value.
"""
originalSysPath = sys.path[:]
def cleanUpSysPath():
sys.path[:] = originalSysPath
self.addCleanup(cleanUpSysPath)
sys.path[:] = sysPath
def replaceSysModules(self, sysModules):
"""
Replace sys.modules, for the duration of the test, with the given value.
"""
originalSysModules = sys.modules.copy()
def cleanUpSysModules():
sys.modules.clear()
sys.modules.update(originalSysModules)
self.addCleanup(cleanUpSysModules)
sys.modules.clear()
sys.modules.update(sysModules)
def pathEntryWithOnePackage(self, pkgname="test_package"):
"""
Generate a L{FilePath} with one package, named C{pkgname}, on it, and
return the L{FilePath} of the path entry.
"""
entry = FilePath(self.mktemp())
pkg = entry.child("test_package")
pkg.makedirs()
pkg.child("__init__.py").setContent(b"")
return entry

View file

@ -0,0 +1,39 @@
# -*- test-case-name: twisted.python.test.test_sendmsg -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
import sys
import os
import socket
from struct import unpack
from twisted.python.sendmsg import recvmsg
def recvfd(socketfd):
"""
Receive a file descriptor from a L{sendmsg} message on the given C{AF_UNIX}
socket.
@param socketfd: An C{AF_UNIX} socket, attached to another process waiting
to send sockets via the ancillary data mechanism in L{send1msg}.
@param fd: C{int}
@return: a 2-tuple of (new file descriptor, description).
@rtype: 2-tuple of (C{int}, C{bytes})
"""
ourSocket = socket.fromfd(socketfd, socket.AF_UNIX, socket.SOCK_STREAM)
data, ancillary, flags = recvmsg(ourSocket)
[(cmsgLevel, cmsgType, packedFD)] = ancillary
# cmsgLevel and cmsgType really need to be SOL_SOCKET / SCM_RIGHTS, but
# since those are the *only* standard values, there's not much point in
# checking.
[unpackedFD] = unpack("i", packedFD)
return (unpackedFD, data)
if __name__ == '__main__':
fd, description = recvfd(int(sys.argv[1]))
os.write(fd, b"Test fixture data: " + description + b".\n")
os.close(fd)

View file

@ -0,0 +1,42 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for the data directory support.
"""
from __future__ import division, absolute_import
try:
from twisted.python import _appdirs
except ImportError:
_appdirs = None
from twisted.trial import unittest
class AppdirsTests(unittest.TestCase):
"""
Tests for L{_appdirs}.
"""
if not _appdirs:
skip = "appdirs package not installed"
def test_moduleName(self):
"""
Calling L{appdirs.getDataDirectory} will return a user data directory
in the system convention, with the module of the caller as the
subdirectory.
"""
res = _appdirs.getDataDirectory()
self.assertTrue(res.endswith("twisted.python.test.test_appdirs"))
def test_manual(self):
"""
Calling L{appdirs.getDataDirectory} with a C{moduleName} argument will
make a data directory with that name instead.
"""
res = _appdirs.getDataDirectory("foo.bar.baz")
self.assertTrue(res.endswith("foo.bar.baz"))

View file

@ -0,0 +1,898 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Test cases for Twisted component architecture.
"""
from __future__ import division, absolute_import
from functools import wraps
from zope.interface import Interface, implementer, Attribute
from zope.interface.adapter import AdapterRegistry
from twisted.python.compat import comparable, cmp
from twisted.trial import unittest
from twisted.python import components
from twisted.python.components import _addHook, _removeHook, proxyForInterface
class Compo(components.Componentized):
num = 0
def inc(self):
self.num = self.num + 1
return self.num
class IAdept(Interface):
def adaptorFunc():
raise NotImplementedError()
class IElapsed(Interface):
def elapsedFunc():
"""
1!
"""
@implementer(IAdept)
class Adept(components.Adapter):
def __init__(self, orig):
self.original = orig
self.num = 0
def adaptorFunc(self):
self.num = self.num + 1
return self.num, self.original.inc()
@implementer(IElapsed)
class Elapsed(components.Adapter):
def elapsedFunc(self):
return 1
class AComp(components.Componentized):
pass
class BComp(AComp):
pass
class CComp(BComp):
pass
class ITest(Interface):
pass
class ITest2(Interface):
pass
class ITest3(Interface):
pass
class ITest4(Interface):
pass
@implementer(ITest, ITest3, ITest4)
class Test(components.Adapter):
def __init__(self, orig):
pass
@implementer(ITest2)
class Test2(object):
temporaryAdapter = 1
def __init__(self, orig):
pass
class RegistryUsingMixin(object):
"""
Mixin for test cases which modify the global registry somehow.
"""
def setUp(self):
"""
Configure L{twisted.python.components.registerAdapter} to mutate an
alternate registry to improve test isolation.
"""
# Create a brand new, empty registry and put it onto the components
# module where registerAdapter will use it. Also ensure that it goes
# away at the end of the test.
scratchRegistry = AdapterRegistry()
self.patch(components, 'globalRegistry', scratchRegistry)
# Hook the new registry up to the adapter lookup system and ensure that
# association is also discarded after the test.
hook = _addHook(scratchRegistry)
self.addCleanup(_removeHook, hook)
class ComponentizedTests(unittest.SynchronousTestCase, RegistryUsingMixin):
"""
Simple test case for caching in Componentized.
"""
def setUp(self):
RegistryUsingMixin.setUp(self)
components.registerAdapter(Test, AComp, ITest)
components.registerAdapter(Test, AComp, ITest3)
components.registerAdapter(Test2, AComp, ITest2)
def testComponentized(self):
components.registerAdapter(Adept, Compo, IAdept)
components.registerAdapter(Elapsed, Compo, IElapsed)
c = Compo()
assert c.getComponent(IAdept).adaptorFunc() == (1, 1)
assert c.getComponent(IAdept).adaptorFunc() == (2, 2)
assert IElapsed(IAdept(c)).elapsedFunc() == 1
def testInheritanceAdaptation(self):
c = CComp()
co1 = c.getComponent(ITest)
co2 = c.getComponent(ITest)
co3 = c.getComponent(ITest2)
co4 = c.getComponent(ITest2)
assert co1 is co2
assert co3 is not co4
c.removeComponent(co1)
co5 = c.getComponent(ITest)
co6 = c.getComponent(ITest)
assert co5 is co6
assert co1 is not co5
def testMultiAdapter(self):
c = CComp()
co1 = c.getComponent(ITest)
co3 = c.getComponent(ITest3)
co4 = c.getComponent(ITest4)
self.assertIsNone(co4)
self.assertIs(co1, co3)
def test_getComponentDefaults(self):
"""
Test that a default value specified to Componentized.getComponent if
there is no component for the requested interface.
"""
componentized = components.Componentized()
default = object()
self.assertIs(
componentized.getComponent(ITest, default),
default)
self.assertIs(
componentized.getComponent(ITest, default=default),
default)
self.assertIs(
componentized.getComponent(ITest),
None)
def test_setAdapter(self):
"""
C{Componentized.setAdapter} sets a component for an interface by
wrapping the instance with the given adapter class.
"""
componentized = components.Componentized()
componentized.setAdapter(IAdept, Adept)
component = componentized.getComponent(IAdept)
self.assertEqual(component.original, componentized)
self.assertIsInstance(component, Adept)
def test_addAdapter(self):
"""
C{Componentized.setAdapter} adapts the instance by wrapping it with
given adapter class, then stores it using C{addComponent}.
"""
componentized = components.Componentized()
componentized.addAdapter(Adept, ignoreClass=True)
component = componentized.getComponent(IAdept)
self.assertEqual(component.original, componentized)
self.assertIsInstance(component, Adept)
def test_setComponent(self):
"""
C{Componentized.setComponent} stores the given component using the
given interface as the key.
"""
componentized = components.Componentized()
obj = object()
componentized.setComponent(ITest, obj)
self.assertIs(componentized.getComponent(ITest), obj)
def test_unsetComponent(self):
"""
C{Componentized.setComponent} removes the cached component for the
given interface.
"""
componentized = components.Componentized()
obj = object()
componentized.setComponent(ITest, obj)
componentized.unsetComponent(ITest)
self.assertIsNone(componentized.getComponent(ITest))
def test_reprableComponentized(self):
"""
C{ReprableComponentized} has a C{__repr__} that lists its cache.
"""
rc = components.ReprableComponentized()
rc.setComponent(ITest, "hello")
result = repr(rc)
self.assertIn("ITest", result)
self.assertIn("hello", result)
class AdapterTests(unittest.SynchronousTestCase):
"""Test adapters."""
def testAdapterGetComponent(self):
o = object()
a = Adept(o)
self.assertRaises(components.CannotAdapt, ITest, a)
self.assertIsNone(ITest(a, None))
class IMeta(Interface):
pass
@implementer(IMeta)
class MetaAdder(components.Adapter):
def add(self, num):
return self.original.num + num
@implementer(IMeta)
class BackwardsAdder(components.Adapter):
def add(self, num):
return self.original.num - num
class MetaNumber(object):
"""
Integer wrapper for Interface adaptation tests.
"""
def __init__(self, num):
self.num = num
class ComponentNumber(components.Componentized):
def __init__(self):
self.num = 0
components.Componentized.__init__(self)
@implementer(IMeta)
class ComponentAdder(components.Adapter):
"""
Adder for componentized adapter tests.
"""
def __init__(self, original):
components.Adapter.__init__(self, original)
self.num = self.original.num
def add(self, num):
self.num += num
return self.num
class IAttrX(Interface):
"""
Base interface for test of adapter with C{__cmp__}.
"""
def x():
"""
Return a value.
"""
class IAttrXX(Interface):
"""
Adapted interface for test of adapter with C{__cmp__}.
"""
def xx():
"""
Return a tuple of values.
"""
@implementer(IAttrX)
class Xcellent(object):
"""
L{IAttrX} implementation for test of adapter with C{__cmp__}.
"""
def x(self):
"""
Return a value.
@return: a value
"""
return 'x!'
@comparable
class DoubleXAdapter(object):
"""
Adapter with __cmp__.
"""
num = 42
def __init__(self, original):
self.original = original
def xx(self):
return (self.original.x(), self.original.x())
def __cmp__(self, other):
return cmp(self.num, other.num)
class MetaInterfaceTests(RegistryUsingMixin, unittest.SynchronousTestCase):
def test_basic(self):
"""
Registered adapters can be used to adapt classes to an interface.
"""
components.registerAdapter(MetaAdder, MetaNumber, IMeta)
n = MetaNumber(1)
self.assertEqual(IMeta(n).add(1), 2)
def testComponentizedInteraction(self):
components.registerAdapter(ComponentAdder, ComponentNumber, IMeta)
c = ComponentNumber()
IMeta(c).add(1)
IMeta(c).add(1)
self.assertEqual(IMeta(c).add(1), 3)
def testAdapterWithCmp(self):
# Make sure that a __cmp__ on an adapter doesn't break anything
components.registerAdapter(DoubleXAdapter, IAttrX, IAttrXX)
xx = IAttrXX(Xcellent())
self.assertEqual(('x!', 'x!'), xx.xx())
class RegistrationTests(RegistryUsingMixin, unittest.SynchronousTestCase):
"""
Tests for adapter registration.
"""
def _registerAdapterForClassOrInterface(self, original):
"""
Register an adapter with L{components.registerAdapter} for the given
class or interface and verify that the adapter can be looked up with
L{components.getAdapterFactory}.
"""
adapter = lambda o: None
components.registerAdapter(adapter, original, ITest)
self.assertIs(
components.getAdapterFactory(original, ITest, None),
adapter)
def test_registerAdapterForClass(self):
"""
Test that an adapter from a class can be registered and then looked
up.
"""
class TheOriginal(object):
pass
return self._registerAdapterForClassOrInterface(TheOriginal)
def test_registerAdapterForInterface(self):
"""
Test that an adapter from an interface can be registered and then
looked up.
"""
return self._registerAdapterForClassOrInterface(ITest2)
def _duplicateAdapterForClassOrInterface(self, original):
"""
Verify that L{components.registerAdapter} raises L{ValueError} if the
from-type/interface and to-interface pair is not unique.
"""
firstAdapter = lambda o: False
secondAdapter = lambda o: True
components.registerAdapter(firstAdapter, original, ITest)
self.assertRaises(
ValueError,
components.registerAdapter,
secondAdapter, original, ITest)
# Make sure that the original adapter is still around as well
self.assertIs(
components.getAdapterFactory(original, ITest, None),
firstAdapter)
def test_duplicateAdapterForClass(self):
"""
Test that attempting to register a second adapter from a class
raises the appropriate exception.
"""
class TheOriginal(object):
pass
return self._duplicateAdapterForClassOrInterface(TheOriginal)
def test_duplicateAdapterForInterface(self):
"""
Test that attempting to register a second adapter from an interface
raises the appropriate exception.
"""
return self._duplicateAdapterForClassOrInterface(ITest2)
def _duplicateAdapterForClassOrInterfaceAllowed(self, original):
"""
Verify that when C{components.ALLOW_DUPLICATES} is set to C{True}, new
adapter registrations for a particular from-type/interface and
to-interface pair replace older registrations.
"""
firstAdapter = lambda o: False
secondAdapter = lambda o: True
class TheInterface(Interface):
pass
components.registerAdapter(firstAdapter, original, TheInterface)
components.ALLOW_DUPLICATES = True
try:
components.registerAdapter(secondAdapter, original, TheInterface)
self.assertIs(
components.getAdapterFactory(original, TheInterface, None),
secondAdapter)
finally:
components.ALLOW_DUPLICATES = False
# It should be rejected again at this point
self.assertRaises(
ValueError,
components.registerAdapter,
firstAdapter, original, TheInterface)
self.assertIs(
components.getAdapterFactory(original, TheInterface, None),
secondAdapter)
def test_duplicateAdapterForClassAllowed(self):
"""
Test that when L{components.ALLOW_DUPLICATES} is set to a true
value, duplicate registrations from classes are allowed to override
the original registration.
"""
class TheOriginal(object):
pass
return self._duplicateAdapterForClassOrInterfaceAllowed(TheOriginal)
def test_duplicateAdapterForInterfaceAllowed(self):
"""
Test that when L{components.ALLOW_DUPLICATES} is set to a true
value, duplicate registrations from interfaces are allowed to
override the original registration.
"""
class TheOriginal(Interface):
pass
return self._duplicateAdapterForClassOrInterfaceAllowed(TheOriginal)
def _multipleInterfacesForClassOrInterface(self, original):
"""
Verify that an adapter can be registered for multiple to-interfaces at a
time.
"""
adapter = lambda o: None
components.registerAdapter(adapter, original, ITest, ITest2)
self.assertIs(
components.getAdapterFactory(original, ITest, None), adapter)
self.assertIs(
components.getAdapterFactory(original, ITest2, None), adapter)
def test_multipleInterfacesForClass(self):
"""
Test the registration of an adapter from a class to several
interfaces at once.
"""
class TheOriginal(object):
pass
return self._multipleInterfacesForClassOrInterface(TheOriginal)
def test_multipleInterfacesForInterface(self):
"""
Test the registration of an adapter from an interface to several
interfaces at once.
"""
return self._multipleInterfacesForClassOrInterface(ITest3)
def _subclassAdapterRegistrationForClassOrInterface(self, original):
"""
Verify that a new adapter can be registered for a particular
to-interface from a subclass of a type or interface which already has an
adapter registered to that interface and that the subclass adapter takes
precedence over the base class adapter.
"""
firstAdapter = lambda o: True
secondAdapter = lambda o: False
class TheSubclass(original):
pass
components.registerAdapter(firstAdapter, original, ITest)
components.registerAdapter(secondAdapter, TheSubclass, ITest)
self.assertIs(
components.getAdapterFactory(original, ITest, None),
firstAdapter)
self.assertIs(
components.getAdapterFactory(TheSubclass, ITest, None),
secondAdapter)
def test_subclassAdapterRegistrationForClass(self):
"""
Test that an adapter to a particular interface can be registered
from both a class and its subclass.
"""
class TheOriginal(object):
pass
return self._subclassAdapterRegistrationForClassOrInterface(TheOriginal)
def test_subclassAdapterRegistrationForInterface(self):
"""
Test that an adapter to a particular interface can be registered
from both an interface and its subclass.
"""
return self._subclassAdapterRegistrationForClassOrInterface(ITest2)
class IProxiedInterface(Interface):
"""
An interface class for use by L{proxyForInterface}.
"""
ifaceAttribute = Attribute("""
An example declared attribute, which should be proxied.""")
def yay(*a, **kw):
"""
A sample method which should be proxied.
"""
class IProxiedSubInterface(IProxiedInterface):
"""
An interface that derives from another for use with L{proxyForInterface}.
"""
def boo(self):
"""
A different sample method which should be proxied.
"""
@implementer(IProxiedInterface)
class Yayable(object):
"""
A provider of L{IProxiedInterface} which increments a counter for
every call to C{yay}.
@ivar yays: The number of times C{yay} has been called.
"""
def __init__(self):
self.yays = 0
self.yayArgs = []
def yay(self, *a, **kw):
"""
Increment C{self.yays}.
"""
self.yays += 1
self.yayArgs.append((a, kw))
return self.yays
@implementer(IProxiedSubInterface)
class Booable(object):
"""
An implementation of IProxiedSubInterface
"""
yayed = False
booed = False
def yay(self):
"""
Mark the fact that 'yay' has been called.
"""
self.yayed = True
def boo(self):
"""
Mark the fact that 'boo' has been called.1
"""
self.booed = True
class IMultipleMethods(Interface):
"""
An interface with multiple methods.
"""
def methodOne():
"""
The first method. Should return 1.
"""
def methodTwo():
"""
The second method. Should return 2.
"""
class MultipleMethodImplementor(object):
"""
A precise implementation of L{IMultipleMethods}.
"""
def methodOne(self):
"""
@return: 1
"""
return 1
def methodTwo(self):
"""
@return: 2
"""
return 2
class ProxyForInterfaceTests(unittest.SynchronousTestCase):
"""
Tests for L{proxyForInterface}.
"""
def test_original(self):
"""
Proxy objects should have an C{original} attribute which refers to the
original object passed to the constructor.
"""
original = object()
proxy = proxyForInterface(IProxiedInterface)(original)
self.assertIs(proxy.original, original)
def test_proxyMethod(self):
"""
The class created from L{proxyForInterface} passes methods on an
interface to the object which is passed to its constructor.
"""
klass = proxyForInterface(IProxiedInterface)
yayable = Yayable()
proxy = klass(yayable)
proxy.yay()
self.assertEqual(proxy.yay(), 2)
self.assertEqual(yayable.yays, 2)
def test_decoratedProxyMethod(self):
"""
Methods of the class created from L{proxyForInterface} can be used with
the decorator-helper L{functools.wraps}.
"""
base = proxyForInterface(IProxiedInterface)
class klass(base):
@wraps(base.yay)
def yay(self):
self.original.yays += 1
return base.yay(self)
original = Yayable()
yayable = klass(original)
yayable.yay()
self.assertEqual(2, original.yays)
def test_proxyAttribute(self):
"""
Proxy objects should proxy declared attributes, but not other
attributes.
"""
yayable = Yayable()
yayable.ifaceAttribute = object()
proxy = proxyForInterface(IProxiedInterface)(yayable)
self.assertIs(proxy.ifaceAttribute, yayable.ifaceAttribute)
self.assertRaises(AttributeError, lambda: proxy.yays)
def test_proxySetAttribute(self):
"""
The attributes that proxy objects proxy should be assignable and affect
the original object.
"""
yayable = Yayable()
proxy = proxyForInterface(IProxiedInterface)(yayable)
thingy = object()
proxy.ifaceAttribute = thingy
self.assertIs(yayable.ifaceAttribute, thingy)
def test_proxyDeleteAttribute(self):
"""
The attributes that proxy objects proxy should be deletable and affect
the original object.
"""
yayable = Yayable()
yayable.ifaceAttribute = None
proxy = proxyForInterface(IProxiedInterface)(yayable)
del proxy.ifaceAttribute
self.assertFalse(hasattr(yayable, 'ifaceAttribute'))
def test_multipleMethods(self):
"""
[Regression test] The proxy should send its method calls to the correct
method, not the incorrect one.
"""
multi = MultipleMethodImplementor()
proxy = proxyForInterface(IMultipleMethods)(multi)
self.assertEqual(proxy.methodOne(), 1)
self.assertEqual(proxy.methodTwo(), 2)
def test_subclassing(self):
"""
It is possible to subclass the result of L{proxyForInterface}.
"""
class SpecializedProxy(proxyForInterface(IProxiedInterface)):
"""
A specialized proxy which can decrement the number of yays.
"""
def boo(self):
"""
Decrement the number of yays.
"""
self.original.yays -= 1
yayable = Yayable()
special = SpecializedProxy(yayable)
self.assertEqual(yayable.yays, 0)
special.boo()
self.assertEqual(yayable.yays, -1)
def test_proxyName(self):
"""
The name of a proxy class indicates which interface it proxies.
"""
proxy = proxyForInterface(IProxiedInterface)
self.assertEqual(
proxy.__name__,
"(Proxy for "
"twisted.python.test.test_components.IProxiedInterface)")
def test_implements(self):
"""
The resulting proxy implements the interface that it proxies.
"""
proxy = proxyForInterface(IProxiedInterface)
self.assertTrue(IProxiedInterface.implementedBy(proxy))
def test_proxyDescriptorGet(self):
"""
_ProxyDescriptor's __get__ method should return the appropriate
attribute of its argument's 'original' attribute if it is invoked with
an object. If it is invoked with None, it should return a false
class-method emulator instead.
For some reason, Python's documentation recommends to define
descriptors' __get__ methods with the 'type' parameter as optional,
despite the fact that Python itself never actually calls the descriptor
that way. This is probably do to support 'foo.__get__(bar)' as an
idiom. Let's make sure that the behavior is correct. Since we don't
actually use the 'type' argument at all, this test calls it the
idiomatic way to ensure that signature works; test_proxyInheritance
verifies the how-Python-actually-calls-it signature.
"""
class Sample(object):
called = False
def hello(self):
self.called = True
fakeProxy = Sample()
testObject = Sample()
fakeProxy.original = testObject
pd = components._ProxyDescriptor("hello", "original")
self.assertEqual(pd.__get__(fakeProxy), testObject.hello)
fakeClassMethod = pd.__get__(None)
fakeClassMethod(fakeProxy)
self.assertTrue(testObject.called)
def test_proxyInheritance(self):
"""
Subclasses of the class returned from L{proxyForInterface} should be
able to upcall methods by reference to their superclass, as any normal
Python class can.
"""
class YayableWrapper(proxyForInterface(IProxiedInterface)):
"""
This class does not override any functionality.
"""
class EnhancedWrapper(YayableWrapper):
"""
This class overrides the 'yay' method.
"""
wrappedYays = 1
def yay(self, *a, **k):
self.wrappedYays += 1
return YayableWrapper.yay(self, *a, **k) + 7
yayable = Yayable()
wrapper = EnhancedWrapper(yayable)
self.assertEqual(wrapper.yay(3, 4, x=5, y=6), 8)
self.assertEqual(yayable.yayArgs,
[((3, 4), dict(x=5, y=6))])
def test_interfaceInheritance(self):
"""
Proxies of subinterfaces generated with proxyForInterface should allow
access to attributes of both the child and the base interfaces.
"""
proxyClass = proxyForInterface(IProxiedSubInterface)
booable = Booable()
proxy = proxyClass(booable)
proxy.yay()
proxy.boo()
self.assertTrue(booable.yayed)
self.assertTrue(booable.booed)
def test_attributeCustomization(self):
"""
The original attribute name can be customized via the
C{originalAttribute} argument of L{proxyForInterface}: the attribute
should change, but the methods of the original object should still be
callable, and the attributes still accessible.
"""
yayable = Yayable()
yayable.ifaceAttribute = object()
proxy = proxyForInterface(
IProxiedInterface, originalAttribute='foo')(yayable)
self.assertIs(proxy.foo, yayable)
# Check the behavior
self.assertEqual(proxy.yay(), 1)
self.assertIs(proxy.ifaceAttribute, yayable.ifaceAttribute)
thingy = object()
proxy.ifaceAttribute = thingy
self.assertIs(yayable.ifaceAttribute, thingy)
del proxy.ifaceAttribute
self.assertFalse(hasattr(yayable, 'ifaceAttribute'))

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,55 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.dist3}.
"""
from __future__ import absolute_import, division
import os
import twisted
from twisted.trial.unittest import TestCase
from twisted.python.compat import _PY3
from twisted.python._setup import notPortedModules
class ModulesToInstallTests(TestCase):
"""
Tests for L{notPortedModules}.
"""
def test_exist(self):
"""
All modules listed in L{notPortedModules} exist on Py2.
"""
root = os.path.dirname(os.path.dirname(twisted.__file__))
for module in notPortedModules:
segments = module.split(".")
segments[-1] += ".py"
path = os.path.join(root, *segments)
alternateSegments = module.split(".") + ["__init__.py"]
packagePath = os.path.join(root, *alternateSegments)
self.assertTrue(os.path.exists(path) or
os.path.exists(packagePath),
"Module {0} does not exist".format(module))
def test_notexist(self):
"""
All modules listed in L{notPortedModules} do not exist on Py3.
"""
root = os.path.dirname(os.path.dirname(twisted.__file__))
for module in notPortedModules:
segments = module.split(".")
segments[-1] += ".py"
path = os.path.join(root, *segments)
alternateSegments = module.split(".") + ["__init__.py"]
packagePath = os.path.join(root, *alternateSegments)
self.assertFalse(os.path.exists(path) or
os.path.exists(packagePath),
"Module {0} exists".format(module))
if _PY3:
test_exist.skip = "Only on Python 2"
else:
test_notexist.skip = "Only on Python 3"

View file

@ -0,0 +1,413 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.fakepwd}.
"""
try:
import pwd
except ImportError:
pwd = None
try:
import spwd
except ImportError:
spwd = None
import os
from operator import getitem
from twisted.trial.unittest import TestCase
from twisted.python.fakepwd import UserDatabase, ShadowDatabase
SYSTEM_UID_MAX = 999
def findInvalidUID():
"""
By convention, UIDs less than 1000 are reserved for the system. A system
which allocated every single one of those UIDs would likely have practical
problems with allocating new ones, so let's assume that we'll be able to
find one. (If we don't, this will wrap around to negative values and
I{eventually} find something.)
@return: a user ID which does not exist on the local system. Or, on
systems without a L{pwd} module, return C{SYSTEM_UID_MAX}.
"""
guess = SYSTEM_UID_MAX
if pwd is not None:
while True:
try:
pwd.getpwuid(guess)
except KeyError:
break
else:
guess -= 1
return guess
INVALID_UID = findInvalidUID()
class UserDatabaseTestsMixin(object):
"""
L{UserDatabaseTestsMixin} defines tests which apply to any user database
implementation. Subclasses should mix it in, implement C{setUp} to create
C{self.database} bound to a user database instance, and implement
C{getExistingUserInfo} to return information about a user (such information
should be unique per test method).
"""
def test_getpwuid(self):
"""
I{getpwuid} accepts a uid and returns the user record associated with
it.
"""
for i in range(2):
# Get some user which exists in the database.
username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
# Now try to look it up and make sure the result is correct.
entry = self.database.getpwuid(uid)
self.assertEqual(entry.pw_name, username)
self.assertEqual(entry.pw_passwd, password)
self.assertEqual(entry.pw_uid, uid)
self.assertEqual(entry.pw_gid, gid)
self.assertEqual(entry.pw_gecos, gecos)
self.assertEqual(entry.pw_dir, dir)
self.assertEqual(entry.pw_shell, shell)
def test_noSuchUID(self):
"""
I{getpwuid} raises L{KeyError} when passed a uid which does not exist
in the user database.
"""
self.assertRaises(KeyError, self.database.getpwuid, INVALID_UID)
def test_getpwnam(self):
"""
I{getpwnam} accepts a username and returns the user record associated
with it.
"""
for i in range(2):
# Get some user which exists in the database.
username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
# Now try to look it up and make sure the result is correct.
entry = self.database.getpwnam(username)
self.assertEqual(entry.pw_name, username)
self.assertEqual(entry.pw_passwd, password)
self.assertEqual(entry.pw_uid, uid)
self.assertEqual(entry.pw_gid, gid)
self.assertEqual(entry.pw_gecos, gecos)
self.assertEqual(entry.pw_dir, dir)
self.assertEqual(entry.pw_shell, shell)
def test_noSuchName(self):
"""
I{getpwnam} raises L{KeyError} when passed a username which does not
exist in the user database.
"""
self.assertRaises(
KeyError, self.database.getpwnam,
'no' 'such' 'user' 'exists' 'the' 'name' 'is' 'too' 'long' 'and' 'has'
'\1' 'in' 'it' 'too')
def test_recordLength(self):
"""
The user record returned by I{getpwuid}, I{getpwnam}, and I{getpwall}
has a length.
"""
db = self.database
username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
for entry in [db.getpwuid(uid), db.getpwnam(username), db.getpwall()[0]]:
self.assertIsInstance(len(entry), int)
self.assertEqual(len(entry), 7)
def test_recordIndexable(self):
"""
The user record returned by I{getpwuid}, I{getpwnam}, and I{getpwall}
is indexable, with successive indexes starting from 0 corresponding to
the values of the C{pw_name}, C{pw_passwd}, C{pw_uid}, C{pw_gid},
C{pw_gecos}, C{pw_dir}, and C{pw_shell} attributes, respectively.
"""
db = self.database
username, password, uid, gid, gecos, dir, shell = self.getExistingUserInfo()
for entry in [db.getpwuid(uid), db.getpwnam(username), db.getpwall()[0]]:
self.assertEqual(entry[0], username)
self.assertEqual(entry[1], password)
self.assertEqual(entry[2], uid)
self.assertEqual(entry[3], gid)
self.assertEqual(entry[4], gecos)
self.assertEqual(entry[5], dir)
self.assertEqual(entry[6], shell)
self.assertEqual(len(entry), len(list(entry)))
self.assertRaises(IndexError, getitem, entry, 7)
class UserDatabaseTests(TestCase, UserDatabaseTestsMixin):
"""
Tests for L{UserDatabase}.
"""
def setUp(self):
"""
Create a L{UserDatabase} with no user data in it.
"""
self.database = UserDatabase()
self._counter = SYSTEM_UID_MAX + 1
def getExistingUserInfo(self):
"""
Add a new user to C{self.database} and return its information.
"""
self._counter += 1
suffix = '_' + str(self._counter)
username = 'username' + suffix
password = 'password' + suffix
uid = self._counter
gid = self._counter + 1000
gecos = 'gecos' + suffix
dir = 'dir' + suffix
shell = 'shell' + suffix
self.database.addUser(username, password, uid, gid, gecos, dir, shell)
return (username, password, uid, gid, gecos, dir, shell)
def test_addUser(self):
"""
L{UserDatabase.addUser} accepts seven arguments, one for each field of
a L{pwd.struct_passwd}, and makes the new record available via
L{UserDatabase.getpwuid}, L{UserDatabase.getpwnam}, and
L{UserDatabase.getpwall}.
"""
username = 'alice'
password = 'secr3t'
uid = 123
gid = 456
gecos = 'Alice,,,'
home = '/users/alice'
shell = '/usr/bin/foosh'
db = self.database
db.addUser(username, password, uid, gid, gecos, home, shell)
for [entry] in [[db.getpwuid(uid)], [db.getpwnam(username)],
db.getpwall()]:
self.assertEqual(entry.pw_name, username)
self.assertEqual(entry.pw_passwd, password)
self.assertEqual(entry.pw_uid, uid)
self.assertEqual(entry.pw_gid, gid)
self.assertEqual(entry.pw_gecos, gecos)
self.assertEqual(entry.pw_dir, home)
self.assertEqual(entry.pw_shell, shell)
class PwdModuleTests(TestCase, UserDatabaseTestsMixin):
"""
L{PwdModuleTests} runs the tests defined by L{UserDatabaseTestsMixin}
against the built-in C{pwd} module. This serves to verify that
L{UserDatabase} is really a fake of that API.
"""
if pwd is None:
skip = "Cannot verify UserDatabase against pwd without pwd"
else:
database = pwd
def setUp(self):
self._users = iter(self.database.getpwall())
self._uids = set()
def getExistingUserInfo(self):
"""
Read and return the next record from C{self._users}, filtering out
any records with previously seen uid values (as these cannot be
found with C{getpwuid} and only cause trouble).
"""
while True:
entry = next(self._users)
uid = entry.pw_uid
if uid not in self._uids:
self._uids.add(uid)
return entry
class ShadowDatabaseTestsMixin(object):
"""
L{ShadowDatabaseTestsMixin} defines tests which apply to any shadow user
database implementation. Subclasses should mix it in, implement C{setUp} to
create C{self.database} bound to a shadow user database instance, and
implement C{getExistingUserInfo} to return information about a user (such
information should be unique per test method).
"""
def test_getspnam(self):
"""
L{getspnam} accepts a username and returns the user record associated
with it.
"""
for i in range(2):
# Get some user which exists in the database.
(username, password, lastChange, min, max, warn, inact, expire,
flag) = self.getExistingUserInfo()
entry = self.database.getspnam(username)
self.assertEqual(entry.sp_nam, username)
self.assertEqual(entry.sp_pwd, password)
self.assertEqual(entry.sp_lstchg, lastChange)
self.assertEqual(entry.sp_min, min)
self.assertEqual(entry.sp_max, max)
self.assertEqual(entry.sp_warn, warn)
self.assertEqual(entry.sp_inact, inact)
self.assertEqual(entry.sp_expire, expire)
self.assertEqual(entry.sp_flag, flag)
def test_noSuchName(self):
"""
I{getspnam} raises L{KeyError} when passed a username which does not
exist in the user database.
"""
self.assertRaises(KeyError, self.database.getspnam, "alice")
def test_recordLength(self):
"""
The shadow user record returned by I{getspnam} and I{getspall} has a
length.
"""
db = self.database
username = self.getExistingUserInfo()[0]
for entry in [db.getspnam(username), db.getspall()[0]]:
self.assertIsInstance(len(entry), int)
self.assertEqual(len(entry), 9)
def test_recordIndexable(self):
"""
The shadow user record returned by I{getpwnam} and I{getspall} is
indexable, with successive indexes starting from 0 corresponding to the
values of the C{sp_nam}, C{sp_pwd}, C{sp_lstchg}, C{sp_min}, C{sp_max},
C{sp_warn}, C{sp_inact}, C{sp_expire}, and C{sp_flag} attributes,
respectively.
"""
db = self.database
(username, password, lastChange, min, max, warn, inact, expire,
flag) = self.getExistingUserInfo()
for entry in [db.getspnam(username), db.getspall()[0]]:
self.assertEqual(entry[0], username)
self.assertEqual(entry[1], password)
self.assertEqual(entry[2], lastChange)
self.assertEqual(entry[3], min)
self.assertEqual(entry[4], max)
self.assertEqual(entry[5], warn)
self.assertEqual(entry[6], inact)
self.assertEqual(entry[7], expire)
self.assertEqual(entry[8], flag)
self.assertEqual(len(entry), len(list(entry)))
self.assertRaises(IndexError, getitem, entry, 9)
class ShadowDatabaseTests(TestCase, ShadowDatabaseTestsMixin):
"""
Tests for L{ShadowDatabase}.
"""
def setUp(self):
"""
Create a L{ShadowDatabase} with no user data in it.
"""
self.database = ShadowDatabase()
self._counter = 0
def getExistingUserInfo(self):
"""
Add a new user to C{self.database} and return its information.
"""
self._counter += 1
suffix = '_' + str(self._counter)
username = 'username' + suffix
password = 'password' + suffix
lastChange = self._counter + 1
min = self._counter + 2
max = self._counter + 3
warn = self._counter + 4
inact = self._counter + 5
expire = self._counter + 6
flag = self._counter + 7
self.database.addUser(username, password, lastChange, min, max, warn,
inact, expire, flag)
return (username, password, lastChange, min, max, warn, inact,
expire, flag)
def test_addUser(self):
"""
L{UserDatabase.addUser} accepts seven arguments, one for each field of
a L{pwd.struct_passwd}, and makes the new record available via
L{UserDatabase.getpwuid}, L{UserDatabase.getpwnam}, and
L{UserDatabase.getpwall}.
"""
username = 'alice'
password = 'secr3t'
lastChange = 17
min = 42
max = 105
warn = 12
inact = 3
expire = 400
flag = 3
db = self.database
db.addUser(username, password, lastChange, min, max, warn, inact,
expire, flag)
for [entry] in [[db.getspnam(username)], db.getspall()]:
self.assertEqual(entry.sp_nam, username)
self.assertEqual(entry.sp_pwd, password)
self.assertEqual(entry.sp_lstchg, lastChange)
self.assertEqual(entry.sp_min, min)
self.assertEqual(entry.sp_max, max)
self.assertEqual(entry.sp_warn, warn)
self.assertEqual(entry.sp_inact, inact)
self.assertEqual(entry.sp_expire, expire)
self.assertEqual(entry.sp_flag, flag)
class SPwdModuleTests(TestCase, ShadowDatabaseTestsMixin):
"""
L{SPwdModuleTests} runs the tests defined by L{ShadowDatabaseTestsMixin}
against the built-in C{spwd} module. This serves to verify that
L{ShadowDatabase} is really a fake of that API.
"""
if spwd is None:
skip = "Cannot verify ShadowDatabase against spwd without spwd"
elif os.getuid() != 0:
skip = "Cannot access shadow user database except as root"
else:
database = spwd
def setUp(self):
self._users = iter(self.database.getspall())
def getExistingUserInfo(self):
"""
Read and return the next record from C{self._users}.
"""
return next(self._users)

View file

@ -0,0 +1,44 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.htmlizer}.
"""
from io import BytesIO
from twisted.trial.unittest import TestCase
from twisted.python.htmlizer import filter
class FilterTests(TestCase):
"""
Tests for L{twisted.python.htmlizer.filter}.
"""
def test_empty(self):
"""
If passed an empty input file, L{filter} writes a I{pre} tag containing
only an end marker to the output file.
"""
input = BytesIO(b"")
output = BytesIO()
filter(input, output)
self.assertEqual(
output.getvalue(),
b'<pre><span class="py-src-endmarker"></span></pre>\n')
def test_variable(self):
"""
If passed an input file containing a variable access, L{filter} writes
a I{pre} tag containing a I{py-src-variable} span containing the
variable.
"""
input = BytesIO(b"foo\n")
output = BytesIO()
filter(input, output)
self.assertEqual(
output.getvalue(),
b'<pre><span class="py-src-variable">foo</span>'
b'<span class="py-src-newline">\n'
b'</span><span class="py-src-endmarker"></span></pre>\n')

View file

@ -0,0 +1,121 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python._inotify}.
"""
from twisted.trial.unittest import TestCase
from twisted.python.filepath import FilePath
from twisted.python.runtime import platform
if platform.supportsINotify():
from ctypes import c_int, c_uint32, c_char_p
from twisted.python import _inotify
from twisted.python._inotify import (
INotifyError, initializeModule, init, add)
else:
_inotify = None
class INotifyTests(TestCase):
"""
Tests for L{twisted.python._inotify}.
"""
if _inotify is None:
skip = "This platform doesn't support INotify."
def test_missingInit(self):
"""
If the I{libc} object passed to L{initializeModule} has no
C{inotify_init} attribute, L{ImportError} is raised.
"""
class libc:
def inotify_add_watch(self):
pass
def inotify_rm_watch(self):
pass
self.assertRaises(ImportError, initializeModule, libc())
def test_missingAdd(self):
"""
If the I{libc} object passed to L{initializeModule} has no
C{inotify_add_watch} attribute, L{ImportError} is raised.
"""
class libc:
def inotify_init(self):
pass
def inotify_rm_watch(self):
pass
self.assertRaises(ImportError, initializeModule, libc())
def test_missingRemove(self):
"""
If the I{libc} object passed to L{initializeModule} has no
C{inotify_rm_watch} attribute, L{ImportError} is raised.
"""
class libc:
def inotify_init(self):
pass
def inotify_add_watch(self):
pass
self.assertRaises(ImportError, initializeModule, libc())
def test_setTypes(self):
"""
If the I{libc} object passed to L{initializeModule} has all of the
necessary attributes, it sets the C{argtypes} and C{restype} attributes
of the three ctypes methods used from libc.
"""
class libc:
def inotify_init(self):
pass
inotify_init = staticmethod(inotify_init)
def inotify_rm_watch(self):
pass
inotify_rm_watch = staticmethod(inotify_rm_watch)
def inotify_add_watch(self):
pass
inotify_add_watch = staticmethod(inotify_add_watch)
c = libc()
initializeModule(c)
self.assertEqual(c.inotify_init.argtypes, [])
self.assertEqual(c.inotify_init.restype, c_int)
self.assertEqual(c.inotify_rm_watch.argtypes, [c_int, c_int])
self.assertEqual(c.inotify_rm_watch.restype, c_int)
self.assertEqual(
c.inotify_add_watch.argtypes, [c_int, c_char_p, c_uint32])
self.assertEqual(c.inotify_add_watch.restype, c_int)
def test_failedInit(self):
"""
If C{inotify_init} returns a negative number, L{init} raises
L{INotifyError}.
"""
class libc:
def inotify_init(self):
return -1
self.patch(_inotify, 'libc', libc())
self.assertRaises(INotifyError, init)
def test_failedAddWatch(self):
"""
If C{inotify_add_watch} returns a negative number, L{add}
raises L{INotifyError}.
"""
class libc:
def inotify_add_watch(self, fd, path, mask):
return -1
self.patch(_inotify, 'libc', libc())
self.assertRaises(INotifyError, add, 3, FilePath('/foo'), 0)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,236 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.runtime}.
"""
from __future__ import division, absolute_import
import sys
from twisted.python.reflect import namedModule
from twisted.trial.util import suppress as SUPRESS
from twisted.trial.unittest import SynchronousTestCase
from twisted.python.runtime import Platform, shortPythonVersion
class PythonVersionTests(SynchronousTestCase):
"""
Tests the shortPythonVersion method.
"""
def test_shortPythonVersion(self):
"""
Verify if the Python version is returned correctly.
"""
ver = shortPythonVersion().split('.')
for i in range(3):
self.assertEqual(int(ver[i]), sys.version_info[i])
class PlatformTests(SynchronousTestCase):
"""
Tests for the default L{Platform} initializer.
"""
isWinNTDeprecationMessage = ('twisted.python.runtime.Platform.isWinNT was '
'deprecated in Twisted 13.0. Use Platform.isWindows instead.')
def test_isKnown(self):
"""
L{Platform.isKnown} returns a boolean indicating whether this is one of
the L{runtime.knownPlatforms}.
"""
platform = Platform()
self.assertTrue(platform.isKnown())
def test_isVistaConsistency(self):
"""
Verify consistency of L{Platform.isVista}: it can only be C{True} if
L{Platform.isWinNT} and L{Platform.isWindows} are C{True}.
"""
platform = Platform()
if platform.isVista():
self.assertTrue(platform.isWinNT())
self.assertTrue(platform.isWindows())
self.assertFalse(platform.isMacOSX())
def test_isMacOSXConsistency(self):
"""
L{Platform.isMacOSX} can only return C{True} if L{Platform.getType}
returns C{'posix'}.
"""
platform = Platform()
if platform.isMacOSX():
self.assertEqual(platform.getType(), 'posix')
def test_isLinuxConsistency(self):
"""
L{Platform.isLinux} can only return C{True} if L{Platform.getType}
returns C{'posix'} and L{sys.platform} starts with C{"linux"}.
"""
platform = Platform()
if platform.isLinux():
self.assertTrue(sys.platform.startswith("linux"))
def test_isWinNT(self):
"""
L{Platform.isWinNT} can return only C{False} or C{True} and can not
return C{True} if L{Platform.getType} is not C{"win32"}.
"""
platform = Platform()
isWinNT = platform.isWinNT()
self.assertIn(isWinNT, (False, True))
if platform.getType() != "win32":
self.assertFalse(isWinNT)
test_isWinNT.suppress = [SUPRESS(category=DeprecationWarning,
message=isWinNTDeprecationMessage)]
def test_isWinNTDeprecated(self):
"""
L{Platform.isWinNT} is deprecated in favor of L{platform.isWindows}.
"""
platform = Platform()
platform.isWinNT()
warnings = self.flushWarnings([self.test_isWinNTDeprecated])
self.assertEqual(len(warnings), 1)
self.assertEqual(
warnings[0]['message'], self.isWinNTDeprecationMessage)
def test_supportsThreads(self):
"""
L{Platform.supportsThreads} returns C{True} if threads can be created in
this runtime, C{False} otherwise.
"""
# It's difficult to test both cases of this without faking the threading
# module. Perhaps an adequate test is to just test the behavior with
# the current runtime, whatever that happens to be.
try:
namedModule('threading')
except ImportError:
self.assertFalse(Platform().supportsThreads())
else:
self.assertTrue(Platform().supportsThreads())
class ForeignPlatformTests(SynchronousTestCase):
"""
Tests for L{Platform} based overridden initializer values.
"""
def test_getType(self):
"""
If an operating system name is supplied to L{Platform}'s initializer,
L{Platform.getType} returns the platform type which corresponds to that
name.
"""
self.assertEqual(Platform('nt').getType(), 'win32')
self.assertEqual(Platform('ce').getType(), 'win32')
self.assertEqual(Platform('posix').getType(), 'posix')
self.assertEqual(Platform('java').getType(), 'java')
def test_isMacOSX(self):
"""
If a system platform name is supplied to L{Platform}'s initializer, it
is used to determine the result of L{Platform.isMacOSX}, which returns
C{True} for C{"darwin"}, C{False} otherwise.
"""
self.assertTrue(Platform(None, 'darwin').isMacOSX())
self.assertFalse(Platform(None, 'linux2').isMacOSX())
self.assertFalse(Platform(None, 'win32').isMacOSX())
def test_isLinux(self):
"""
If a system platform name is supplied to L{Platform}'s initializer, it
is used to determine the result of L{Platform.isLinux}, which returns
C{True} for values beginning with C{"linux"}, C{False} otherwise.
"""
self.assertFalse(Platform(None, 'darwin').isLinux())
self.assertTrue(Platform(None, 'linux').isLinux())
self.assertTrue(Platform(None, 'linux2').isLinux())
self.assertTrue(Platform(None, 'linux3').isLinux())
self.assertFalse(Platform(None, 'win32').isLinux())
class DockerPlatformTests(SynchronousTestCase):
"""
Tests for L{twisted.python.runtime.Platform.isDocker}.
"""
def test_noChecksOnLinux(self):
"""
If the platform is not Linux, C{isDocker()} always returns L{False}.
"""
platform = Platform(None, 'win32')
self.assertFalse(platform.isDocker())
def test_noCGroups(self):
"""
If the platform is Linux, and the cgroups file in C{/proc} does not
exist, C{isDocker()} returns L{False}
"""
platform = Platform(None, 'linux')
self.assertFalse(platform.isDocker(_initCGroupLocation="fakepath"))
def test_cgroupsSuggestsDocker(self):
"""
If the platform is Linux, and the cgroups file (faked out here) exists,
and one of the paths starts with C{/docker/}, C{isDocker()} returns
C{True}.
"""
cgroupsFile = self.mktemp()
with open(cgroupsFile, 'wb') as f:
# real cgroups file from inside a Debian 7 docker container
f.write(b"""10:debug:/
9:net_prio:/
8:perf_event:/docker/104155a6453cb67590027e397dc90fc25a06a7508403c797bc89ea43adf8d35f
7:net_cls:/
6:freezer:/docker/104155a6453cb67590027e397dc90fc25a06a7508403c797bc89ea43adf8d35f
5:devices:/docker/104155a6453cb67590027e397dc90fc25a06a7508403c797bc89ea43adf8d35f
4:blkio:/docker/104155a6453cb67590027e397dc90fc25a06a7508403c797bc89ea43adf8d35f
3:cpuacct:/docker/104155a6453cb67590027e397dc90fc25a06a7508403c797bc89ea43adf8d35f
2:cpu:/docker/104155a6453cb67590027e397dc90fc25a06a7508403c797bc89ea43adf8d35f
1:cpuset:/docker/104155a6453cb67590027e397dc90fc25a06a7508403c797bc89ea43adf8d35f""")
platform = Platform(None, 'linux')
self.assertTrue(platform.isDocker(_initCGroupLocation=cgroupsFile))
def test_cgroupsSuggestsRealSystem(self):
"""
If the platform is Linux, and the cgroups file (faked out here) exists,
and none of the paths starts with C{/docker/}, C{isDocker()} returns
C{False}.
"""
cgroupsFile = self.mktemp()
with open(cgroupsFile, 'wb') as f:
# real cgroups file from a Fedora 17 system
f.write(b"""9:perf_event:/
8:blkio:/
7:net_cls:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct,cpu:/
2:cpuset:/
1:name=systemd:/system""")
platform = Platform(None, 'linux')
self.assertFalse(platform.isDocker(_initCGroupLocation=cgroupsFile))

View file

@ -0,0 +1,793 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.sendmsg}.
"""
import sys
import errno
import warnings
from os import devnull, pipe, read, close, pathsep
from struct import pack
from socket import SOL_SOCKET, AF_INET, AF_INET6, socket, error
try:
from socket import AF_UNIX, socketpair
except ImportError:
nonUNIXSkip = "Platform does not support AF_UNIX sockets"
else:
nonUNIXSkip = None
from twisted.internet import reactor
from twisted.internet.defer import Deferred, inlineCallbacks
from twisted.internet.error import ProcessDone
from twisted.internet.protocol import ProcessProtocol
from twisted.python.compat import _PY3, intToBytes, bytesEnviron
from twisted.python.filepath import FilePath
from twisted.python.runtime import platform
from twisted.trial.unittest import TestCase
if platform.isLinux():
from socket import MSG_DONTWAIT
dontWaitSkip = None
else:
# It would be nice to be able to test flags on more platforms, but finding
# a flag that works *at all* is somewhat challenging.
dontWaitSkip = "MSG_DONTWAIT is only known to work as intended on Linux"
try:
from twisted.python.sendmsg import sendmsg, recvmsg
from twisted.python.sendmsg import SCM_RIGHTS, getSocketFamily
except ImportError:
importSkip = "Platform doesn't support sendmsg."
else:
importSkip = None
try:
from twisted.python.sendmsg import send1msg, recv1msg
from twisted.python.sendmsg import getsockfam
except ImportError:
CModuleImportSkip = "Cannot import twisted.python.sendmsg"
else:
CModuleImportSkip = None
class _FDHolder(object):
"""
A wrapper around a FD that will remember if it has been closed or not.
"""
def __init__(self, fd):
self._fd = fd
def fileno(self):
"""
Return the fileno of this FD.
"""
return self._fd
def close(self):
"""
Close the FD. If it's already been closed, do nothing.
"""
if self._fd:
close(self._fd)
self._fd = None
def __del__(self):
"""
If C{self._fd} is unclosed, raise a warning.
"""
if self._fd:
if not _PY3:
ResourceWarning = Warning
warnings.warn("FD %s was not closed!" % (self._fd,),
ResourceWarning)
self.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def _makePipe():
"""
Create a pipe, and return the two FDs wrapped in L{_FDHolders}.
"""
r, w = pipe()
return (_FDHolder(r), _FDHolder(w))
class ExitedWithStderr(Exception):
"""
A process exited with some stderr.
"""
def __str__(self):
"""
Dump the errors in a pretty way in the event of a subprocess traceback.
"""
result = b'\n'.join([b''] + list(self.args))
if _PY3:
result = repr(result)
return result
class StartStopProcessProtocol(ProcessProtocol):
"""
An L{IProcessProtocol} with a Deferred for events where the subprocess
starts and stops.
@ivar started: A L{Deferred} which fires with this protocol's
L{IProcessTransport} provider when it is connected to one.
@ivar stopped: A L{Deferred} which fires with the process output or a
failure if the process produces output on standard error.
@ivar output: A C{str} used to accumulate standard output.
@ivar errors: A C{str} used to accumulate standard error.
"""
def __init__(self):
self.started = Deferred()
self.stopped = Deferred()
self.output = b''
self.errors = b''
def connectionMade(self):
self.started.callback(self.transport)
def outReceived(self, data):
self.output += data
def errReceived(self, data):
self.errors += data
def processEnded(self, reason):
if reason.check(ProcessDone):
self.stopped.callback(self.output)
else:
self.stopped.errback(ExitedWithStderr(
self.errors, self.output))
def _spawn(script, outputFD):
"""
Start a script that is a peer of this test as a subprocess.
@param script: the module name of the script in this directory (no
package prefix, no '.py')
@type script: C{str}
@rtype: L{StartStopProcessProtocol}
"""
pyExe = FilePath(sys.executable).asBytesMode().path
env = bytesEnviron()
env[b"PYTHONPATH"] = FilePath(
pathsep.join(sys.path)).asBytesMode().path
sspp = StartStopProcessProtocol()
reactor.spawnProcess(
sspp, pyExe, [
pyExe,
FilePath(__file__).sibling(script + ".py").asBytesMode().path,
intToBytes(outputFD),
],
env=env,
childFDs={0: "w", 1: "r", 2: "r", outputFD: outputFD}
)
return sspp
class BadList(list):
"""
A list which cannot be iterated sometimes.
This is a C{list} subclass to get past the type check in L{send1msg}, not
as an example of how real programs might want to interact with L{send1msg}
(or anything else). A custom C{list} subclass makes it easier to trigger
certain error cases in the implementation.
@ivar iterate: A flag which indicates whether an instance of L{BadList}
will allow iteration over itself or not. If C{False}, an attempt to
iterate over the instance will raise an exception.
"""
iterate = True
def __iter__(self):
"""
Allow normal list iteration, or raise an exception.
If C{self.iterate} is C{True}, it will be flipped to C{False} and then
normal iteration will proceed. If C{self.iterate} is C{False},
L{RuntimeError} is raised instead.
"""
if self.iterate:
self.iterate = False
return super(BadList, self).__iter__()
raise RuntimeError("Something bad happened")
class WorseList(list):
"""
A list which at first gives the appearance of being iterable, but then
raises an exception.
See L{BadList} for a warning about not writing code like this.
"""
def __iter__(self):
"""
Return an iterator which will raise an exception as soon as C{next} is
called on it.
"""
class BadIterator(object):
def next(self):
raise RuntimeError("This is a really bad case.")
return BadIterator()
class CModuleSendmsgTests(TestCase):
"""
Tests for sendmsg extension module and associated file-descriptor sending
functionality.
"""
if nonUNIXSkip is not None:
skip = nonUNIXSkip
elif CModuleImportSkip is not None:
skip = CModuleImportSkip
def setUp(self):
"""
Create a pair of UNIX sockets.
"""
self.input, self.output = socketpair(AF_UNIX)
def tearDown(self):
"""
Close the sockets opened by setUp.
"""
self.input.close()
self.output.close()
def test_sendmsgBadArguments(self):
"""
The argument types accepted by L{send1msg} are:
1. C{int}
2. read-only character buffer
3. C{int}
4. sequence
The 3rd and 4th arguments are optional. If fewer than two arguments or
more than four arguments are passed, or if any of the arguments passed
are not compatible with these types, L{TypeError} is raised.
"""
# Exercise the wrong number of arguments cases
self.assertRaises(TypeError, send1msg)
self.assertRaises(TypeError, send1msg, 1)
self.assertRaises(TypeError, send1msg,
1, "hello world", 2, [], object())
# Exercise the wrong type of arguments cases
self.assertRaises(TypeError, send1msg, object(), "hello world", 2, [])
self.assertRaises(TypeError, send1msg, 1, object(), 2, [])
self.assertRaises(TypeError, send1msg, 1, "hello world", object(), [])
self.assertRaises(TypeError, send1msg, 1, "hello world", 2, object())
def test_badAncillaryIter(self):
"""
If iteration over the ancillary data list fails (at the point of the
C{__iter__} call), the exception with which it fails is propagated to
the caller of L{send1msg}.
"""
badList = BadList()
badList.append((1, 2, "hello world"))
badList.iterate = False
self.assertRaises(RuntimeError, send1msg, 1, "hello world", 2, badList)
# Hit the second iteration
badList.iterate = True
self.assertRaises(RuntimeError, send1msg, 1, "hello world", 2, badList)
def test_badAncillaryNext(self):
"""
If iteration over the ancillary data list fails (at the point of a
C{next} call), the exception with which it fails is propagated to the
caller of L{send1msg}.
"""
worseList = WorseList()
self.assertRaises(RuntimeError, send1msg,
1, "hello world", 2,worseList)
def test_sendmsgBadAncillaryItem(self):
"""
The ancillary data list contains three-tuples with element types of:
1. C{int}
2. C{int}
3. read-only character buffer
If a tuple in the ancillary data list does not elements of these types,
L{TypeError} is raised.
"""
# Exercise the wrong number of arguments cases
self.assertRaises(TypeError, send1msg, 1, "hello world", 2, [()])
self.assertRaises(TypeError, send1msg, 1, "hello world", 2, [(1,)])
self.assertRaises(TypeError, send1msg, 1, "hello world", 2, [(1, 2)])
self.assertRaises(
TypeError,
send1msg, 1, "hello world", 2, [(1, 2, "goodbye", object())])
# Exercise the wrong type of arguments cases
exc = self.assertRaises(
TypeError, send1msg, 1, "hello world", 2, [object()])
self.assertEqual(
"send1msg argument 3 expected list of tuple, "
"got list containing object",
str(exc))
self.assertRaises(
TypeError,
send1msg, 1, "hello world", 2, [(object(), 1, "goodbye")])
self.assertRaises(
TypeError,
send1msg, 1, "hello world", 2, [(1, object(), "goodbye")])
self.assertRaises(
TypeError,
send1msg, 1, "hello world", 2, [(1, 1, object())])
def test_syscallError(self):
"""
If the underlying C{sendmsg} call fails, L{send1msg} raises
L{socket.error} with its errno set to the underlying errno value.
"""
with open(devnull) as probe:
fd = probe.fileno()
exc = self.assertRaises(error, send1msg, fd, "hello, world")
self.assertEqual(exc.args[0], errno.EBADF)
def test_syscallErrorWithControlMessage(self):
"""
The behavior when the underlying C{sendmsg} call fails is the same
whether L{send1msg} is passed ancillary data or not.
"""
with open(devnull) as probe:
fd = probe.fileno()
exc = self.assertRaises(
error, send1msg, fd, "hello, world", 0, [(0, 0, "0123")])
self.assertEqual(exc.args[0], errno.EBADF)
def test_roundtrip(self):
"""
L{recv1msg} will retrieve a message sent via L{send1msg}.
"""
message = "hello, world!"
self.assertEqual(
len(message),
send1msg(self.input.fileno(), message, 0))
result = recv1msg(fd=self.output.fileno())
self.assertEqual(result, (message, 0, []))
def test_roundtripEmptyAncillary(self):
"""
L{send1msg} treats an empty ancillary data list the same way it treats
receiving no argument for the ancillary parameter at all.
"""
send1msg(self.input.fileno(), "hello, world!", 0, [])
result = recv1msg(fd=self.output.fileno())
self.assertEqual(result, ("hello, world!", 0, []))
def test_flags(self):
"""
The C{flags} argument to L{send1msg} is passed on to the underlying
C{sendmsg} call, to affect it in whatever way is defined by those
flags.
"""
# Just exercise one flag with simple, well-known behavior. MSG_DONTWAIT
# makes the send a non-blocking call, even if the socket is in blocking
# mode. See also test_flags in RecvmsgTests
for i in range(8 * 1024):
try:
send1msg(self.input.fileno(), "x" * 1024, MSG_DONTWAIT)
except error as e:
self.assertEqual(e.args[0], errno.EAGAIN)
break
else:
self.fail(
"Failed to fill up the send buffer, "
"or maybe send1msg blocked for a while")
if dontWaitSkip is not None:
test_flags.skip = dontWaitSkip
def test_wrongTypeAncillary(self):
"""
L{send1msg} will show a helpful exception message when given the wrong
type of object for the 'ancillary' argument.
"""
error = self.assertRaises(TypeError,
send1msg, self.input.fileno(),
"hello, world!", 0, 4321)
self.assertEqual(str(error),
"send1msg argument 3 expected list, got int")
@inlineCallbacks
def test_sendSubProcessFD(self):
"""
Calling L{sendsmsg} with SOL_SOCKET, SCM_RIGHTS, and a platform-endian
packed file descriptor number should send that file descriptor to a
different process, where it can be retrieved by using L{recv1msg}.
"""
sspp = _spawn("cmodulepullpipe", self.output.fileno())
yield sspp.started
pipeOut, pipeIn = _makePipe()
self.addCleanup(pipeOut.close)
self.addCleanup(pipeIn.close)
with pipeIn:
send1msg(
self.input.fileno(), "blonk", 0,
[(SOL_SOCKET, SCM_RIGHTS, pack("i", pipeIn.fileno()))])
yield sspp.stopped
self.assertEqual(read(pipeOut.fileno(), 1024),
"Test fixture data: blonk.\n")
# Make sure that the pipe is actually closed now.
self.assertEqual(read(pipeOut.fileno(), 1024), "")
def test_sendmsgTwoAncillaryDoesNotSegfault(self):
"""
L{sendmsg} with two FDs in two separate ancillary entries
does not segfault.
"""
ancillary = [
(SOL_SOCKET, SCM_RIGHTS, pack("i", self.input.fileno())),
(SOL_SOCKET, SCM_RIGHTS, pack("i", self.output.fileno())),
]
try:
send1msg(self.input.fileno(), b"some data", 0, ancillary)
except error:
# Ok as long as it doesn't segfault.
pass
class CModuleRecvmsgTests(TestCase):
"""
Tests for L{recv1msg} (primarily error handling cases).
"""
if CModuleImportSkip is not None:
skip = CModuleImportSkip
def test_badArguments(self):
"""
The argument types accepted by L{recv1msg} are:
1. C{int}
2. C{int}
3. C{int}
4. C{int}
The 2nd, 3rd, and 4th arguments are optional. If fewer than one
argument or more than four arguments are passed, or if any of the
arguments passed are not compatible with these types, L{TypeError} is
raised.
"""
# Exercise the wrong number of arguments cases
self.assertRaises(TypeError, recv1msg)
self.assertRaises(TypeError, recv1msg, 1, 2, 3, 4, object())
# Exercise the wrong type of arguments cases
self.assertRaises(TypeError, recv1msg, object(), 2, 3, 4)
self.assertRaises(TypeError, recv1msg, 1, object(), 3, 4)
self.assertRaises(TypeError, recv1msg, 1, 2, object(), 4)
self.assertRaises(TypeError, recv1msg, 1, 2, 3, object())
def test_cmsgSpaceOverflow(self):
"""
L{recv1msg} raises L{OverflowError} if passed a value for the
C{cmsg_size} argument which exceeds C{SOCKLEN_MAX}.
"""
self.assertRaises(OverflowError, recv1msg, 0, 0, 0, 0x7FFFFFFF)
def test_syscallError(self):
"""
If the underlying C{recvmsg} call fails, L{recv1msg} raises
L{socket.error} with its errno set to the underlying errno value.
"""
with open(devnull) as probe:
fd = probe.fileno()
exc = self.assertRaises(error, recv1msg, fd)
self.assertEqual(exc.args[0], errno.EBADF)
def test_flags(self):
"""
The C{flags} argument to L{recv1msg} is passed on to the underlying
C{recvmsg} call, to affect it in whatever way is defined by those
flags.
"""
# See test_flags in SendmsgTests
reader, writer = socketpair(AF_UNIX)
exc = self.assertRaises(
error, recv1msg, reader.fileno(), MSG_DONTWAIT)
self.assertEqual(exc.args[0], errno.EAGAIN)
if dontWaitSkip is not None:
test_flags.skip = dontWaitSkip
class CModuleGetSocketFamilyTests(TestCase):
"""
Tests for L{getsockfam}, a helper which reveals the address family of an
arbitrary socket.
"""
if CModuleImportSkip is not None:
skip = CModuleImportSkip
def _socket(self, addressFamily):
"""
Create a new socket using the given address family and return that
socket's file descriptor. The socket will automatically be closed when
the test is torn down.
"""
s = socket(addressFamily)
self.addCleanup(s.close)
return s.fileno()
def test_badArguments(self):
"""
L{getsockfam} accepts a single C{int} argument. If it is called in
some other way, L{TypeError} is raised.
"""
self.assertRaises(TypeError, getsockfam)
self.assertRaises(TypeError, getsockfam, 1, 2)
self.assertRaises(TypeError, getsockfam, object())
def test_syscallError(self):
"""
If the underlying C{getsockname} call fails, L{getsockfam} raises
L{socket.error} with its errno set to the underlying errno value.
"""
with open(devnull) as probe:
fd = probe.fileno()
exc = self.assertRaises(error, getsockfam, fd)
self.assertEqual(errno.EBADF, exc.args[0])
def test_inet(self):
"""
When passed the file descriptor of a socket created with the C{AF_INET}
address family, L{getsockfam} returns C{AF_INET}.
"""
self.assertEqual(AF_INET, getsockfam(self._socket(AF_INET)))
def test_inet6(self):
"""
When passed the file descriptor of a socket created with the
C{AF_INET6} address family, L{getsockfam} returns C{AF_INET6}.
"""
self.assertEqual(AF_INET6, getsockfam(self._socket(AF_INET6)))
def test_unix(self):
"""
When passed the file descriptor of a socket created with the C{AF_UNIX}
address family, L{getsockfam} returns C{AF_UNIX}.
"""
self.assertEqual(AF_UNIX, getsockfam(self._socket(AF_UNIX)))
if nonUNIXSkip is not None:
test_unix.skip = nonUNIXSkip
class SendmsgTests(TestCase):
"""
Tests for the Python2/3 compatible L{sendmsg} interface.
"""
if importSkip is not None:
skip = importSkip
def setUp(self):
"""
Create a pair of UNIX sockets.
"""
self.input, self.output = socketpair(AF_UNIX)
def tearDown(self):
"""
Close the sockets opened by setUp.
"""
self.input.close()
self.output.close()
def test_syscallError(self):
"""
If the underlying C{sendmsg} call fails, L{send1msg} raises
L{socket.error} with its errno set to the underlying errno value.
"""
self.input.close()
exc = self.assertRaises(error, sendmsg, self.input, b"hello, world")
self.assertEqual(exc.args[0], errno.EBADF)
def test_syscallErrorWithControlMessage(self):
"""
The behavior when the underlying C{sendmsg} call fails is the same
whether L{sendmsg} is passed ancillary data or not.
"""
self.input.close()
exc = self.assertRaises(
error, sendmsg, self.input, b"hello, world", [(0, 0, b"0123")], 0)
self.assertEqual(exc.args[0], errno.EBADF)
def test_roundtrip(self):
"""
L{recvmsg} will retrieve a message sent via L{sendmsg}.
"""
message = b"hello, world!"
self.assertEqual(
len(message),
sendmsg(self.input, message))
result = recvmsg(self.output)
self.assertEqual(result.data, b"hello, world!")
self.assertEqual(result.flags, 0)
self.assertEqual(result.ancillary, [])
def test_shortsend(self):
"""
L{sendmsg} returns the number of bytes which it was able to send.
"""
message = b"x" * 1024 * 1024 * 16
self.input.setblocking(False)
sent = sendmsg(self.input, message)
# Sanity check - make sure the amount of data we sent was less than the
# message, but not the whole message, as we should have filled the send
# buffer. This won't work if the send buffer is large enough for
# message, though.
self.assertTrue(sent < len(message))
received = recvmsg(self.output, len(message))
self.assertEqual(len(received[0]), sent)
def test_roundtripEmptyAncillary(self):
"""
L{sendmsg} treats an empty ancillary data list the same way it treats
receiving no argument for the ancillary parameter at all.
"""
sendmsg(self.input, b"hello, world!", [], 0)
result = recvmsg(self.output)
self.assertEqual(result, (b"hello, world!", [], 0))
def test_flags(self):
"""
The C{flags} argument to L{sendmsg} is passed on to the underlying
C{sendmsg} call, to affect it in whatever way is defined by those
flags.
"""
# Just exercise one flag with simple, well-known behavior. MSG_DONTWAIT
# makes the send a non-blocking call, even if the socket is in blocking
# mode. See also test_flags in RecvmsgTests
for i in range(8 * 1024):
try:
sendmsg(self.input, b"x" * 1024, flags=MSG_DONTWAIT)
except error as e:
self.assertEqual(e.args[0], errno.EAGAIN)
break
else:
self.fail(
"Failed to fill up the send buffer, "
"or maybe send1msg blocked for a while")
if dontWaitSkip is not None:
test_flags.skip = dontWaitSkip
@inlineCallbacks
def test_sendSubProcessFD(self):
"""
Calling L{sendmsg} with SOL_SOCKET, SCM_RIGHTS, and a platform-endian
packed file descriptor number should send that file descriptor to a
different process, where it can be retrieved by using L{recv1msg}.
"""
sspp = _spawn("pullpipe", self.output.fileno())
yield sspp.started
pipeOut, pipeIn = _makePipe()
self.addCleanup(pipeOut.close)
self.addCleanup(pipeIn.close)
with pipeIn:
sendmsg(
self.input, b"blonk",
[(SOL_SOCKET, SCM_RIGHTS, pack("i", pipeIn.fileno()))])
yield sspp.stopped
self.assertEqual(read(pipeOut.fileno(), 1024),
b"Test fixture data: blonk.\n")
# Make sure that the pipe is actually closed now.
self.assertEqual(read(pipeOut.fileno(), 1024), b"")
class GetSocketFamilyTests(TestCase):
"""
Tests for L{getSocketFamily}.
"""
if importSkip is not None:
skip = importSkip
def _socket(self, addressFamily):
"""
Create a new socket using the given address family and return that
socket's file descriptor. The socket will automatically be closed when
the test is torn down.
"""
s = socket(addressFamily)
self.addCleanup(s.close)
return s
def test_inet(self):
"""
When passed the file descriptor of a socket created with the C{AF_INET}
address family, L{getSocketFamily} returns C{AF_INET}.
"""
self.assertEqual(AF_INET, getSocketFamily(self._socket(AF_INET)))
def test_inet6(self):
"""
When passed the file descriptor of a socket created with the
C{AF_INET6} address family, L{getSocketFamily} returns C{AF_INET6}.
"""
self.assertEqual(AF_INET6, getSocketFamily(self._socket(AF_INET6)))
def test_unix(self):
"""
When passed the file descriptor of a socket created with the C{AF_UNIX}
address family, L{getSocketFamily} returns C{AF_UNIX}.
"""
self.assertEqual(AF_UNIX, getSocketFamily(self._socket(AF_UNIX)))
if nonUNIXSkip is not None:
test_unix.skip = nonUNIXSkip

View file

@ -0,0 +1,401 @@
# 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 SynchronousTestCase
from twisted.python import _setup, filepath
from twisted.python.compat import _PY3
from twisted.python._setup import (
BuildPy3,
getSetupArgs,
_longDescriptionArgsFromReadme,
ConditionalExtension,
_EXTRAS_REQUIRE,
)
class SetupTests(SynchronousTestCase):
"""
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], readme=None)
# 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], readme=None)
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(SynchronousTestCase):
"""
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{macos_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('macos_platform', _EXTRAS_REQUIRE)
self.assertIn('osx_platform', _EXTRAS_REQUIRE) # Compat for macOS
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 >= 18.1.0', 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 >= 2.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 >= 18.1.0', deps)
self.assertIn('idna >= 0.6, != 2.3', deps)
self.assertIn('pyasn1', deps)
self.assertIn('cryptography >= 2.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_extrasRequiresMacosPlatformDeps(self):
"""
L{_EXTRAS_REQUIRE}'s C{macos_platform} extra contains setuptools
requirements for all of Twisted's optional dependencies usable on the
macOS platform.
"""
deps = _EXTRAS_REQUIRE['macos_platform']
self.assertIn('pyopenssl >= 16.0.0', deps)
self.assertIn('service_identity >= 18.1.0', deps)
self.assertIn('idna >= 0.6, != 2.3', deps)
self.assertIn('pyasn1', deps)
self.assertIn('cryptography >= 2.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_extrasRequireMacOSXPlatformDeps(self):
"""
L{_EXTRAS_REQUIRE}'s C{osx_platform} is an alias to C{macos_platform}.
"""
self.assertEqual(_EXTRAS_REQUIRE['macos_platform'],
_EXTRAS_REQUIRE['osx_platform'])
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 >= 18.1.0', deps)
self.assertIn('idna >= 0.6, != 2.3', deps)
self.assertIn('pyasn1', deps)
self.assertIn('cryptography >= 2.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('pywin32 != 226', 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(SynchronousTestCase):
"""
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(SynchronousTestCase):
"""
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),
)
class LongDescriptionTests(SynchronousTestCase):
"""
Tests for C{_getLongDescriptionArgs()}
Note that the validity of the reStructuredText syntax is tested separately
using L{twine check} in L{tox.ini}.
"""
def test_generate(self):
"""
L{_longDescriptionArgsFromReadme()} outputs a L{long_description} in
reStructuredText format. Local links are transformed into absolute ones
that point at the Twisted GitHub repository.
"""
path = self.mktemp()
with open(path, 'w') as f:
f.write('\n'.join([
'Twisted',
'=======',
'',
'Changes: `NEWS <NEWS.rst>`_.',
"Read `the docs <https://twistedmatrix.com/documents/>`_.\n",
]))
self.assertEqual({
'long_description': '''\
Twisted
=======
Changes: `NEWS <https://github.com/twisted/twisted/blob/trunk/NEWS.rst>`_.
Read `the docs <https://twistedmatrix.com/documents/>`_.
''',
'long_description_content_type': 'text/x-rst',
}, _longDescriptionArgsFromReadme(path))

View file

@ -0,0 +1,625 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Test cases for twisted.python._shellcomp
"""
from __future__ import division, absolute_import
import sys
from io import BytesIO
from twisted.trial import unittest
from twisted.python import _shellcomp, usage, reflect
from twisted.python.usage import Completions, Completer, CompleteFiles
from twisted.python.usage import CompleteList
class ZshScriptTestMeta(type):
"""
Metaclass of ZshScriptTestMixin.
"""
def __new__(cls, name, bases, attrs):
def makeTest(cmdName, optionsFQPN):
def runTest(self):
return test_genZshFunction(self, cmdName, optionsFQPN)
return runTest
# add test_ methods to the class for each script
# we are testing.
if 'generateFor' in attrs:
for cmdName, optionsFQPN in attrs['generateFor']:
test = makeTest(cmdName, optionsFQPN)
attrs['test_genZshFunction_' + cmdName] = test
return type.__new__(cls, name, bases, attrs)
class ZshScriptTestMixin(object):
"""
Integration test helper to show that C{usage.Options} classes can have zsh
completion functions generated for them without raising errors.
In your subclasses set a class variable like so:
# | cmd name | Fully Qualified Python Name of Options class |
#
generateFor = [('conch', 'twisted.conch.scripts.conch.ClientOptions'),
('twistd', 'twisted.scripts.twistd.ServerOptions'),
]
Each package that contains Twisted scripts should contain one TestCase
subclass which also inherits from this mixin, and contains a C{generateFor}
list appropriate for the scripts in that package.
"""
__metaclass__ = ZshScriptTestMeta
def test_genZshFunction(self, cmdName, optionsFQPN):
"""
Generate completion functions for given twisted command - no errors
should be raised
@type cmdName: C{str}
@param cmdName: The name of the command-line utility e.g. 'twistd'
@type optionsFQPN: C{str}
@param optionsFQPN: The Fully Qualified Python Name of the C{Options}
class to be tested.
"""
outputFile = BytesIO()
self.patch(usage.Options, '_shellCompFile', outputFile)
# some scripts won't import or instantiate because of missing
# dependencies (pyOpenSSL, etc) so we have to skip them.
try:
o = reflect.namedAny(optionsFQPN)()
except Exception as e:
raise unittest.SkipTest("Couldn't import or instantiate "
"Options class: %s" % (e,))
try:
o.parseOptions(["", "--_shell-completion", "zsh:2"])
except ImportError as e:
# this can happen for commands which don't have all
# the necessary dependencies installed. skip test.
# skip
raise unittest.SkipTest("ImportError calling parseOptions(): %s", (e,))
except SystemExit:
pass # expected
else:
self.fail('SystemExit not raised')
outputFile.seek(0)
# test that we got some output
self.assertEqual(1, len(outputFile.read(1)))
outputFile.seek(0)
outputFile.truncate()
# now, if it has sub commands, we have to test those too
if hasattr(o, 'subCommands'):
for (cmd, short, parser, doc) in o.subCommands:
try:
o.parseOptions([cmd, "", "--_shell-completion",
"zsh:3"])
except ImportError as e:
# this can happen for commands which don't have all
# the necessary dependencies installed. skip test.
raise unittest.SkipTest("ImportError calling parseOptions() "
"on subcommand: %s", (e,))
except SystemExit:
pass # expected
else:
self.fail('SystemExit not raised')
outputFile.seek(0)
# test that we got some output
self.assertEqual(1, len(outputFile.read(1)))
outputFile.seek(0)
outputFile.truncate()
# flushed because we don't want DeprecationWarnings to be printed when
# running these test cases.
self.flushWarnings()
class ZshTests(unittest.TestCase):
"""
Tests for zsh completion code
"""
def test_accumulateMetadata(self):
"""
Are `compData' attributes you can place on Options classes
picked up correctly?
"""
opts = FighterAceExtendedOptions()
ag = _shellcomp.ZshArgumentsGenerator(opts, 'ace', BytesIO())
descriptions = FighterAceOptions.compData.descriptions.copy()
descriptions.update(FighterAceExtendedOptions.compData.descriptions)
self.assertEqual(ag.descriptions, descriptions)
self.assertEqual(ag.multiUse,
set(FighterAceOptions.compData.multiUse))
self.assertEqual(ag.mutuallyExclusive,
FighterAceOptions.compData.mutuallyExclusive)
optActions = FighterAceOptions.compData.optActions.copy()
optActions.update(FighterAceExtendedOptions.compData.optActions)
self.assertEqual(ag.optActions, optActions)
self.assertEqual(ag.extraActions,
FighterAceOptions.compData.extraActions)
def test_mutuallyExclusiveCornerCase(self):
"""
Exercise a corner-case of ZshArgumentsGenerator.makeExcludesDict()
where the long option name already exists in the `excludes` dict being
built.
"""
class OddFighterAceOptions(FighterAceExtendedOptions):
# since "fokker", etc, are already defined as mutually-
# exclusive on the super-class, defining them again here forces
# the corner-case to be exercised.
optFlags = [['anatra', None,
'Select the Anatra DS as your dogfighter aircraft']]
compData = Completions(
mutuallyExclusive=[['anatra', 'fokker', 'albatros',
'spad', 'bristol']])
opts = OddFighterAceOptions()
ag = _shellcomp.ZshArgumentsGenerator(opts, 'ace', BytesIO())
expected = {
'albatros': set(['anatra', 'b', 'bristol', 'f',
'fokker', 's', 'spad']),
'anatra': set(['a', 'albatros', 'b', 'bristol',
'f', 'fokker', 's', 'spad']),
'bristol': set(['a', 'albatros', 'anatra', 'f',
'fokker', 's', 'spad']),
'fokker': set(['a', 'albatros', 'anatra', 'b',
'bristol', 's', 'spad']),
'spad': set(['a', 'albatros', 'anatra', 'b',
'bristol', 'f', 'fokker'])}
self.assertEqual(ag.excludes, expected)
def test_accumulateAdditionalOptions(self):
"""
We pick up options that are only defined by having an
appropriately named method on your Options class,
e.g. def opt_foo(self, foo)
"""
opts = FighterAceExtendedOptions()
ag = _shellcomp.ZshArgumentsGenerator(opts, 'ace', BytesIO())
self.assertIn('nocrash', ag.flagNameToDefinition)
self.assertIn('nocrash', ag.allOptionsNameToDefinition)
self.assertIn('difficulty', ag.paramNameToDefinition)
self.assertIn('difficulty', ag.allOptionsNameToDefinition)
def test_verifyZshNames(self):
"""
Using a parameter/flag name that doesn't exist
will raise an error
"""
class TmpOptions(FighterAceExtendedOptions):
# Note typo of detail
compData = Completions(optActions={'detaill' : None})
self.assertRaises(ValueError, _shellcomp.ZshArgumentsGenerator,
TmpOptions(), 'ace', BytesIO())
class TmpOptions2(FighterAceExtendedOptions):
# Note that 'foo' and 'bar' are not real option
# names defined in this class
compData = Completions(
mutuallyExclusive=[("foo", "bar")])
self.assertRaises(ValueError, _shellcomp.ZshArgumentsGenerator,
TmpOptions2(), 'ace', BytesIO())
def test_zshCode(self):
"""
Generate a completion function, and test the textual output
against a known correct output
"""
outputFile = BytesIO()
self.patch(usage.Options, '_shellCompFile', outputFile)
self.patch(sys, 'argv', ["silly", "", "--_shell-completion", "zsh:2"])
opts = SimpleProgOptions()
self.assertRaises(SystemExit, opts.parseOptions)
self.assertEqual(testOutput1, outputFile.getvalue())
def test_zshCodeWithSubs(self):
"""
Generate a completion function with subcommands,
and test the textual output against a known correct output
"""
outputFile = BytesIO()
self.patch(usage.Options, '_shellCompFile', outputFile)
self.patch(sys, 'argv', ["silly2", "", "--_shell-completion", "zsh:2"])
opts = SimpleProgWithSubcommands()
self.assertRaises(SystemExit, opts.parseOptions)
self.assertEqual(testOutput2, outputFile.getvalue())
def test_incompleteCommandLine(self):
"""
Completion still happens even if a command-line is given
that would normally throw UsageError.
"""
outputFile = BytesIO()
self.patch(usage.Options, '_shellCompFile', outputFile)
opts = FighterAceOptions()
self.assertRaises(SystemExit, opts.parseOptions,
["--fokker", "server", "--unknown-option",
"--unknown-option2",
"--_shell-completion", "zsh:5"])
outputFile.seek(0)
# test that we got some output
self.assertEqual(1, len(outputFile.read(1)))
def test_incompleteCommandLine_case2(self):
"""
Completion still happens even if a command-line is given
that would normally throw UsageError.
The existence of --unknown-option prior to the subcommand
will break subcommand detection... but we complete anyway
"""
outputFile = BytesIO()
self.patch(usage.Options, '_shellCompFile', outputFile)
opts = FighterAceOptions()
self.assertRaises(SystemExit, opts.parseOptions,
["--fokker", "--unknown-option", "server",
"--list-server", "--_shell-completion", "zsh:5"])
outputFile.seek(0)
# test that we got some output
self.assertEqual(1, len(outputFile.read(1)))
outputFile.seek(0)
outputFile.truncate()
def test_incompleteCommandLine_case3(self):
"""
Completion still happens even if a command-line is given
that would normally throw UsageError.
Break subcommand detection in a different way by providing
an invalid subcommand name.
"""
outputFile = BytesIO()
self.patch(usage.Options, '_shellCompFile', outputFile)
opts = FighterAceOptions()
self.assertRaises(SystemExit, opts.parseOptions,
["--fokker", "unknown-subcommand",
"--list-server", "--_shell-completion", "zsh:4"])
outputFile.seek(0)
# test that we got some output
self.assertEqual(1, len(outputFile.read(1)))
def test_skipSubcommandList(self):
"""
Ensure the optimization which skips building the subcommand list
under certain conditions isn't broken.
"""
outputFile = BytesIO()
self.patch(usage.Options, '_shellCompFile', outputFile)
opts = FighterAceOptions()
self.assertRaises(SystemExit, opts.parseOptions,
["--alba", "--_shell-completion", "zsh:2"])
outputFile.seek(0)
# test that we got some output
self.assertEqual(1, len(outputFile.read(1)))
def test_poorlyDescribedOptMethod(self):
"""
Test corner case fetching an option description from a method docstring
"""
opts = FighterAceOptions()
argGen = _shellcomp.ZshArgumentsGenerator(opts, 'ace', None)
descr = argGen.getDescription('silly')
# docstring for opt_silly is useless so it should just use the
# option name as the description
self.assertEqual(descr, 'silly')
def test_brokenActions(self):
"""
A C{Completer} with repeat=True may only be used as the
last item in the extraActions list.
"""
class BrokenActions(usage.Options):
compData = usage.Completions(
extraActions=[usage.Completer(repeat=True),
usage.Completer()]
)
outputFile = BytesIO()
opts = BrokenActions()
self.patch(opts, '_shellCompFile', outputFile)
self.assertRaises(ValueError, opts.parseOptions,
["", "--_shell-completion", "zsh:2"])
def test_optMethodsDontOverride(self):
"""
opt_* methods on Options classes should not override the
data provided in optFlags or optParameters.
"""
class Options(usage.Options):
optFlags = [['flag', 'f', 'A flag']]
optParameters = [['param', 'p', None, 'A param']]
def opt_flag(self):
""" junk description """
def opt_param(self, param):
""" junk description """
opts = Options()
argGen = _shellcomp.ZshArgumentsGenerator(opts, 'ace', None)
self.assertEqual(argGen.getDescription('flag'), 'A flag')
self.assertEqual(argGen.getDescription('param'), 'A param')
class EscapeTests(unittest.TestCase):
def test_escape(self):
"""
Verify _shellcomp.escape() function
"""
esc = _shellcomp.escape
test = "$"
self.assertEqual(esc(test), "'$'")
test = 'A--\'$"\\`--B'
self.assertEqual(esc(test), '"A--\'\\$\\"\\\\\\`--B"')
class CompleterNotImplementedTests(unittest.TestCase):
"""
Test that using an unknown shell constant with SubcommandAction
raises NotImplementedError
The other Completer() subclasses are tested in test_usage.py
"""
def test_unknownShell(self):
"""
Using an unknown shellType should raise NotImplementedError
"""
action = _shellcomp.SubcommandAction()
self.assertRaises(NotImplementedError, action._shellCode,
None, "bad_shell_type")
class FighterAceServerOptions(usage.Options):
"""
Options for FighterAce 'server' subcommand
"""
optFlags = [['list-server', None,
'List this server with the online FighterAce network']]
optParameters = [['packets-per-second', None,
'Number of update packets to send per second', '20']]
class FighterAceOptions(usage.Options):
"""
Command-line options for an imaginary `Fighter Ace` game
"""
optFlags = [['fokker', 'f',
'Select the Fokker Dr.I as your dogfighter aircraft'],
['albatros', 'a',
'Select the Albatros D-III as your dogfighter aircraft'],
['spad', 's',
'Select the SPAD S.VII as your dogfighter aircraft'],
['bristol', 'b',
'Select the Bristol Scout as your dogfighter aircraft'],
['physics', 'p',
'Enable secret Twisted physics engine'],
['jam', 'j',
'Enable a small chance that your machine guns will jam!'],
['verbose', 'v',
'Verbose logging (may be specified more than once)'],
]
optParameters = [['pilot-name', None, "What's your name, Ace?",
'Manfred von Richthofen'],
['detail', 'd',
'Select the level of rendering detail (1-5)', '3'],
]
subCommands = [['server', None, FighterAceServerOptions,
'Start FighterAce game-server.'],
]
compData = Completions(
descriptions={'physics' : 'Twisted-Physics',
'detail' : 'Rendering detail level'},
multiUse=['verbose'],
mutuallyExclusive=[['fokker', 'albatros', 'spad',
'bristol']],
optActions={'detail' : CompleteList(['1' '2' '3'
'4' '5'])},
extraActions=[CompleteFiles(descr='saved game file to load')]
)
def opt_silly(self):
# A silly option which nobody can explain
""" """
class FighterAceExtendedOptions(FighterAceOptions):
"""
Extend the options and zsh metadata provided by FighterAceOptions.
_shellcomp must accumulate options and metadata from all classes in the
hiearchy so this is important to test.
"""
optFlags = [['no-stalls', None,
'Turn off the ability to stall your aircraft']]
optParameters = [['reality-level', None,
'Select the level of physics reality (1-5)', '5']]
compData = Completions(
descriptions={'no-stalls' : 'Can\'t stall your plane'},
optActions={'reality-level' :
Completer(descr='Physics reality level')}
)
def opt_nocrash(self):
"""
Select that you can't crash your plane
"""
def opt_difficulty(self, difficulty):
"""
How tough are you? (1-10)
"""
def _accuracyAction():
# add tick marks just to exercise quoting
return CompleteList(['1', '2', '3'], descr='Accuracy\'`?')
class SimpleProgOptions(usage.Options):
"""
Command-line options for a `Silly` imaginary program
"""
optFlags = [['color', 'c', 'Turn on color output'],
['gray', 'g', 'Turn on gray-scale output'],
['verbose', 'v',
'Verbose logging (may be specified more than once)'],
]
optParameters = [['optimization', None, '5',
'Select the level of optimization (1-5)'],
['accuracy', 'a', '3',
'Select the level of accuracy (1-3)'],
]
compData = Completions(
descriptions={'color' : 'Color on',
'optimization' : 'Optimization level'},
multiUse=['verbose'],
mutuallyExclusive=[['color', 'gray']],
optActions={'optimization' : CompleteList(['1', '2', '3', '4', '5'],
descr='Optimization?'),
'accuracy' : _accuracyAction},
extraActions=[CompleteFiles(descr='output file')]
)
def opt_X(self):
"""
usage.Options does not recognize single-letter opt_ methods
"""
class SimpleProgSub1(usage.Options):
optFlags = [['sub-opt', 's', 'Sub Opt One']]
class SimpleProgSub2(usage.Options):
optFlags = [['sub-opt', 's', 'Sub Opt Two']]
class SimpleProgWithSubcommands(SimpleProgOptions):
optFlags = [['some-option'],
['other-option', 'o']]
optParameters = [['some-param'],
['other-param', 'p'],
['another-param', 'P', 'Yet Another Param']]
subCommands = [ ['sub1', None, SimpleProgSub1, 'Sub Command 1'],
['sub2', None, SimpleProgSub2, 'Sub Command 2']]
testOutput1 = b"""#compdef silly
_arguments -s -A "-*" \\
':output file (*):_files -g "*"' \\
"(--accuracy)-a[Select the level of accuracy (1-3)]:Accuracy'\`?:(1 2 3)" \\
"(-a)--accuracy=[Select the level of accuracy (1-3)]:Accuracy'\`?:(1 2 3)" \\
'(--color --gray -g)-c[Color on]' \\
'(--gray -c -g)--color[Color on]' \\
'(--color --gray -c)-g[Turn on gray-scale output]' \\
'(--color -c -g)--gray[Turn on gray-scale output]' \\
'--help[Display this help and exit.]' \\
'--optimization=[Optimization level]:Optimization?:(1 2 3 4 5)' \\
'*-v[Verbose logging (may be specified more than once)]' \\
'*--verbose[Verbose logging (may be specified more than once)]' \\
'--version[Display Twisted version and exit.]' \\
&& return 0
"""
# with sub-commands
testOutput2 = b"""#compdef silly2
_arguments -s -A "-*" \\
'*::subcmd:->subcmd' \\
':output file (*):_files -g "*"' \\
"(--accuracy)-a[Select the level of accuracy (1-3)]:Accuracy'\`?:(1 2 3)" \\
"(-a)--accuracy=[Select the level of accuracy (1-3)]:Accuracy'\`?:(1 2 3)" \\
'(--another-param)-P[another-param]:another-param:_files' \\
'(-P)--another-param=[another-param]:another-param:_files' \\
'(--color --gray -g)-c[Color on]' \\
'(--gray -c -g)--color[Color on]' \\
'(--color --gray -c)-g[Turn on gray-scale output]' \\
'(--color -c -g)--gray[Turn on gray-scale output]' \\
'--help[Display this help and exit.]' \\
'--optimization=[Optimization level]:Optimization?:(1 2 3 4 5)' \\
'(--other-option)-o[other-option]' \\
'(-o)--other-option[other-option]' \\
'(--other-param)-p[other-param]:other-param:_files' \\
'(-p)--other-param=[other-param]:other-param:_files' \\
'--some-option[some-option]' \\
'--some-param=[some-param]:some-param:_files' \\
'*-v[Verbose logging (may be specified more than once)]' \\
'*--verbose[Verbose logging (may be specified more than once)]' \\
'--version[Display Twisted version and exit.]' \\
&& return 0
local _zsh_subcmds_array
_zsh_subcmds_array=(
"sub1:Sub Command 1"
"sub2:Sub Command 2"
)
_describe "sub-command" _zsh_subcmds_array
"""

View file

@ -0,0 +1,151 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
from twisted.trial.unittest import TestCase
from twisted.python.failure import Failure
try:
import syslog as stdsyslog
except ImportError:
stdsyslog = None
else:
from twisted.python import syslog
class SyslogObserverTests(TestCase):
"""
Tests for L{SyslogObserver} which sends Twisted log events to the syslog.
"""
events = None
if stdsyslog is None:
skip = "syslog is not supported on this platform"
def setUp(self):
self.patch(syslog.SyslogObserver, 'openlog', self.openlog)
self.patch(syslog.SyslogObserver, 'syslog', self.syslog)
self.observer = syslog.SyslogObserver('SyslogObserverTests')
def openlog(self, prefix, options, facility):
self.logOpened = (prefix, options, facility)
self.events = []
def syslog(self, options, message):
self.events.append((options, message))
def test_emitWithoutMessage(self):
"""
L{SyslogObserver.emit} ignores events with an empty value for the
C{'message'} key.
"""
self.observer.emit({'message': (), 'isError': False, 'system': '-'})
self.assertEqual(self.events, [])
def test_emitCustomPriority(self):
"""
L{SyslogObserver.emit} uses the value of the C{'syslogPriority'} as the
syslog priority, if that key is present in the event dictionary.
"""
self.observer.emit({
'message': ('hello, world',), 'isError': False, 'system': '-',
'syslogPriority': stdsyslog.LOG_DEBUG})
self.assertEqual(
self.events,
[(stdsyslog.LOG_DEBUG, '[-] hello, world')])
def test_emitErrorPriority(self):
"""
L{SyslogObserver.emit} uses C{LOG_ALERT} if the event represents an
error.
"""
self.observer.emit({
'message': ('hello, world',), 'isError': True, 'system': '-',
'failure': Failure(Exception("foo"))})
self.assertEqual(
self.events,
[(stdsyslog.LOG_ALERT, '[-] hello, world')])
def test_emitCustomPriorityOverridesError(self):
"""
L{SyslogObserver.emit} uses the value of the C{'syslogPriority'} key if
it is specified even if the event dictionary represents an error.
"""
self.observer.emit({
'message': ('hello, world',), 'isError': True, 'system': '-',
'syslogPriority': stdsyslog.LOG_NOTICE,
'failure': Failure(Exception("bar"))})
self.assertEqual(
self.events,
[(stdsyslog.LOG_NOTICE, '[-] hello, world')])
def test_emitCustomFacility(self):
"""
L{SyslogObserver.emit} uses the value of the C{'syslogPriority'} as the
syslog priority, if that key is present in the event dictionary.
"""
self.observer.emit({
'message': ('hello, world',), 'isError': False, 'system': '-',
'syslogFacility': stdsyslog.LOG_CRON})
self.assertEqual(
self.events,
[(stdsyslog.LOG_INFO | stdsyslog.LOG_CRON, '[-] hello, world')])
def test_emitCustomSystem(self):
"""
L{SyslogObserver.emit} uses the value of the C{'system'} key to prefix
the logged message.
"""
self.observer.emit({'message': ('hello, world',), 'isError': False,
'system': 'nonDefaultSystem'})
self.assertEqual(
self.events,
[(stdsyslog.LOG_INFO, "[nonDefaultSystem] hello, world")])
def test_emitMessage(self):
"""
L{SyslogObserver.emit} logs the value of the C{'message'} key of the
event dictionary it is passed to the syslog.
"""
self.observer.emit({
'message': ('hello, world',), 'isError': False,
'system': '-'})
self.assertEqual(
self.events,
[(stdsyslog.LOG_INFO, "[-] hello, world")])
def test_emitMultilineMessage(self):
"""
Each line of a multiline message is emitted separately to the syslog.
"""
self.observer.emit({
'message': ('hello,\nworld',), 'isError': False,
'system': '-'})
self.assertEqual(
self.events,
[(stdsyslog.LOG_INFO, '[-] hello,'),
(stdsyslog.LOG_INFO, '[-] \tworld')])
def test_emitStripsTrailingEmptyLines(self):
"""
Trailing empty lines of a multiline message are omitted from the
messages sent to the syslog.
"""
self.observer.emit({
'message': ('hello,\nworld\n\n',), 'isError': False,
'system': '-'})
self.assertEqual(
self.events,
[(stdsyslog.LOG_INFO, '[-] hello,'),
(stdsyslog.LOG_INFO, '[-] \tworld')])

View file

@ -0,0 +1,176 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.systemd}.
"""
from __future__ import division, absolute_import
import os
from twisted.trial.unittest import TestCase
from twisted.python.systemd import ListenFDs
class InheritedDescriptorsMixin(object):
"""
Mixin for a L{TestCase} subclass which defines test methods for some kind of
systemd sd-daemon class. In particular, it defines tests for a
C{inheritedDescriptors} method.
"""
def test_inheritedDescriptors(self):
"""
C{inheritedDescriptors} returns a list of integers giving the file
descriptors which were inherited from systemd.
"""
sddaemon = self.getDaemon(7, 3)
self.assertEqual([7, 8, 9], sddaemon.inheritedDescriptors())
def test_repeated(self):
"""
Any subsequent calls to C{inheritedDescriptors} return the same list.
"""
sddaemon = self.getDaemon(7, 3)
self.assertEqual(
sddaemon.inheritedDescriptors(),
sddaemon.inheritedDescriptors())
class MemoryOnlyMixin(object):
"""
Mixin for a L{TestCase} subclass which creates creating a fake, in-memory
implementation of C{inheritedDescriptors}. This provides verification that
the fake behaves in a compatible way with the real implementation.
"""
def getDaemon(self, start, count):
"""
Invent C{count} new I{file descriptors} (actually integers, attached to
no real file description), starting at C{start}. Construct and return a
new L{ListenFDs} which will claim those integers represent inherited
file descriptors.
"""
return ListenFDs(range(start, start + count))
class EnvironmentMixin(object):
"""
Mixin for a L{TestCase} subclass which creates a real implementation of
C{inheritedDescriptors} which is based on the environment variables set by
systemd. To facilitate testing, this mixin will also create a fake
environment dictionary and add keys to it to make it look as if some
descriptors have been inherited.
"""
def initializeEnvironment(self, count, pid):
"""
Create a copy of the process environment and add I{LISTEN_FDS} and
I{LISTEN_PID} (the environment variables set by systemd) to it.
"""
result = os.environ.copy()
result['LISTEN_FDS'] = str(count)
result['LISTEN_PID'] = str(pid)
return result
def getDaemon(self, start, count):
"""
Create a new L{ListenFDs} instance, initialized with a fake environment
dictionary which will be set up as systemd would have set it up if
C{count} descriptors were being inherited. The descriptors will also
start at C{start}.
"""
fakeEnvironment = self.initializeEnvironment(count, os.getpid())
return ListenFDs.fromEnvironment(environ=fakeEnvironment, start=start)
class MemoryOnlyTests(MemoryOnlyMixin, InheritedDescriptorsMixin, TestCase):
"""
Apply tests to L{ListenFDs}, explicitly constructed with some fake file
descriptors.
"""
class EnvironmentTests(EnvironmentMixin, InheritedDescriptorsMixin, TestCase):
"""
Apply tests to L{ListenFDs}, constructed based on an environment dictionary.
"""
def test_secondEnvironment(self):
"""
Only a single L{Environment} can extract inherited file descriptors.
"""
fakeEnvironment = self.initializeEnvironment(3, os.getpid())
first = ListenFDs.fromEnvironment(environ=fakeEnvironment)
second = ListenFDs.fromEnvironment(environ=fakeEnvironment)
self.assertEqual(list(range(3, 6)), first.inheritedDescriptors())
self.assertEqual([], second.inheritedDescriptors())
def test_mismatchedPID(self):
"""
If the current process PID does not match the PID in the environment, no
inherited descriptors are reported.
"""
fakeEnvironment = self.initializeEnvironment(3, os.getpid() + 1)
sddaemon = ListenFDs.fromEnvironment(environ=fakeEnvironment)
self.assertEqual([], sddaemon.inheritedDescriptors())
def test_missingPIDVariable(self):
"""
If the I{LISTEN_PID} environment variable is not present, no inherited
descriptors are reported.
"""
fakeEnvironment = self.initializeEnvironment(3, os.getpid())
del fakeEnvironment['LISTEN_PID']
sddaemon = ListenFDs.fromEnvironment(environ=fakeEnvironment)
self.assertEqual([], sddaemon.inheritedDescriptors())
def test_nonIntegerPIDVariable(self):
"""
If the I{LISTEN_PID} environment variable is set to a string that cannot
be parsed as an integer, no inherited descriptors are reported.
"""
fakeEnvironment = self.initializeEnvironment(3, "hello, world")
sddaemon = ListenFDs.fromEnvironment(environ=fakeEnvironment)
self.assertEqual([], sddaemon.inheritedDescriptors())
def test_missingFDSVariable(self):
"""
If the I{LISTEN_FDS} environment variable is not present, no inherited
descriptors are reported.
"""
fakeEnvironment = self.initializeEnvironment(3, os.getpid())
del fakeEnvironment['LISTEN_FDS']
sddaemon = ListenFDs.fromEnvironment(environ=fakeEnvironment)
self.assertEqual([], sddaemon.inheritedDescriptors())
def test_nonIntegerFDSVariable(self):
"""
If the I{LISTEN_FDS} environment variable is set to a string that cannot
be parsed as an integer, no inherited descriptors are reported.
"""
fakeEnvironment = self.initializeEnvironment("hello, world", os.getpid())
sddaemon = ListenFDs.fromEnvironment(environ=fakeEnvironment)
self.assertEqual([], sddaemon.inheritedDescriptors())
def test_defaultEnviron(self):
"""
If the process environment is not explicitly passed to
L{Environment.__init__}, the real process environment dictionary is
used.
"""
self.patch(os, 'environ', {
'LISTEN_PID': str(os.getpid()),
'LISTEN_FDS': '5'})
sddaemon = ListenFDs.fromEnvironment()
self.assertEqual(list(range(3, 3 + 5)),
sddaemon.inheritedDescriptors())

View file

@ -0,0 +1,27 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.textattributes}.
"""
from twisted.trial import unittest
from twisted.python._textattributes import DefaultFormattingState
class DefaultFormattingStateTests(unittest.TestCase):
"""
Tests for L{twisted.python._textattributes.DefaultFormattingState}.
"""
def test_equality(self):
"""
L{DefaultFormattingState}s are always equal to other
L{DefaultFormattingState}s.
"""
self.assertEqual(
DefaultFormattingState(),
DefaultFormattingState())
self.assertNotEqual(
DefaultFormattingState(),
'hello')

View file

@ -0,0 +1,153 @@
# # Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python._tzhelper}.
"""
from os import environ
try:
from time import tzset
except ImportError:
tzset = None
from twisted.python._tzhelper import FixedOffsetTimeZone
from twisted.trial.unittest import TestCase, SkipTest
from datetime import timedelta
from time import mktime as mktime_real
# On some rare platforms (FreeBSD 8? I was not able to reproduce
# on FreeBSD 9) 'mktime' seems to always fail once tzset() has been
# called more than once in a process lifetime. I think this is
# just a platform bug, so let's work around it. -glyph
def mktime(t9):
"""
Call L{mktime_real}, and if it raises L{OverflowError}, catch it and raise
SkipTest instead.
@param t9: A time as a 9-item tuple.
@type t9: L{tuple}
@return: A timestamp.
@rtype: L{float}
"""
try:
return mktime_real(t9)
except OverflowError:
raise SkipTest(
"Platform cannot construct time zone for {0!r}"
.format(t9)
)
def setTZ(name):
"""
Set time zone.
@param name: a time zone name
@type name: L{str}
"""
if tzset is None:
return
if name is None:
try:
del environ["TZ"]
except KeyError:
pass
else:
environ["TZ"] = name
tzset()
def addTZCleanup(testCase):
"""
Add cleanup hooks to a test case to reset timezone to original value.
@param testCase: the test case to add the cleanup to.
@type testCase: L{unittest.TestCase}
"""
tzIn = environ.get("TZ", None)
@testCase.addCleanup
def resetTZ():
setTZ(tzIn)
class FixedOffsetTimeZoneTests(TestCase):
"""
Tests for L{FixedOffsetTimeZone}.
"""
def test_tzinfo(self):
"""
Test that timezone attributes respect the timezone as set by the
standard C{TZ} environment variable and L{tzset} API.
"""
if tzset is None:
raise SkipTest(
"Platform cannot change timezone; unable to verify offsets."
)
def testForTimeZone(name, expectedOffsetDST, expectedOffsetSTD):
setTZ(name)
localDST = mktime((2006, 6, 30, 0, 0, 0, 4, 181, 1))
localSTD = mktime((2007, 1, 31, 0, 0, 0, 2, 31, 0))
tzDST = FixedOffsetTimeZone.fromLocalTimeStamp(localDST)
tzSTD = FixedOffsetTimeZone.fromLocalTimeStamp(localSTD)
self.assertEqual(
tzDST.tzname(localDST),
"UTC{0}".format(expectedOffsetDST)
)
self.assertEqual(
tzSTD.tzname(localSTD),
"UTC{0}".format(expectedOffsetSTD)
)
self.assertEqual(tzDST.dst(localDST), timedelta(0))
self.assertEqual(tzSTD.dst(localSTD), timedelta(0))
def timeDeltaFromOffset(offset):
assert len(offset) == 5
sign = offset[0]
hours = int(offset[1:3])
minutes = int(offset[3:5])
if sign == "-":
hours = -hours
minutes = -minutes
else:
assert sign == "+"
return timedelta(hours=hours, minutes=minutes)
self.assertEqual(
tzDST.utcoffset(localDST),
timeDeltaFromOffset(expectedOffsetDST)
)
self.assertEqual(
tzSTD.utcoffset(localSTD),
timeDeltaFromOffset(expectedOffsetSTD)
)
addTZCleanup(self)
# UTC
testForTimeZone("UTC+00", "+0000", "+0000")
# West of UTC
testForTimeZone("EST+05EDT,M4.1.0,M10.5.0", "-0400", "-0500")
# East of UTC
testForTimeZone("CEST-01CEDT,M4.1.0,M10.5.0", "+0200", "+0100")
# No DST
testForTimeZone("CST+06", "-0600", "-0600")

View file

@ -0,0 +1,805 @@
# -*- test-case-name: twisted.python.test.test_url -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.url}.
"""
from __future__ import unicode_literals
from ..url import URL
unicode = type(u'')
from twisted.trial.unittest import SynchronousTestCase
theurl = "http://www.foo.com/a/nice/path/?zot=23&zut"
# Examples from RFC 3986 section 5.4, Reference Resolution Examples
relativeLinkBaseForRFC3986 = 'http://a/b/c/d;p?q'
relativeLinkTestsForRFC3986 = [
# "Normal"
#('g:h', 'g:h'), # Not supported: scheme with relative path
('g', 'http://a/b/c/g'),
('./g', 'http://a/b/c/g'),
('g/', 'http://a/b/c/g/'),
('/g', 'http://a/g'),
('//g', 'http://g'),
('?y', 'http://a/b/c/d;p?y'),
('g?y', 'http://a/b/c/g?y'),
('#s', 'http://a/b/c/d;p?q#s'),
('g#s', 'http://a/b/c/g#s'),
('g?y#s', 'http://a/b/c/g?y#s'),
(';x', 'http://a/b/c/;x'),
('g;x', 'http://a/b/c/g;x'),
('g;x?y#s', 'http://a/b/c/g;x?y#s'),
('', 'http://a/b/c/d;p?q'),
('.', 'http://a/b/c/'),
('./', 'http://a/b/c/'),
('..', 'http://a/b/'),
('../', 'http://a/b/'),
('../g', 'http://a/b/g'),
('../..', 'http://a/'),
('../../', 'http://a/'),
('../../g', 'http://a/g'),
# Abnormal examples
# ".." cannot be used to change the authority component of a URI.
('../../../g', 'http://a/g'),
('../../../../g', 'http://a/g'),
# Only include "." and ".." when they are only part of a larger segment,
# not by themselves.
('/./g', 'http://a/g'),
('/../g', 'http://a/g'),
('g.', 'http://a/b/c/g.'),
('.g', 'http://a/b/c/.g'),
('g..', 'http://a/b/c/g..'),
('..g', 'http://a/b/c/..g'),
# Unnecessary or nonsensical forms of "." and "..".
('./../g', 'http://a/b/g'),
('./g/.', 'http://a/b/c/g/'),
('g/./h', 'http://a/b/c/g/h'),
('g/../h', 'http://a/b/c/h'),
('g;x=1/./y', 'http://a/b/c/g;x=1/y'),
('g;x=1/../y', 'http://a/b/c/y'),
# Separating the reference's query and fragment components from the path.
('g?y/./x', 'http://a/b/c/g?y/./x'),
('g?y/../x', 'http://a/b/c/g?y/../x'),
('g#s/./x', 'http://a/b/c/g#s/./x'),
('g#s/../x', 'http://a/b/c/g#s/../x'),
# Not supported: scheme with relative path
#("http:g", "http:g"), # strict
#("http:g", "http://a/b/c/g"), # non-strict
]
_percentenc = lambda s: ''.join('%%%02X' % ord(c) for c in s)
class TestURL(SynchronousTestCase):
"""
Tests for L{URL}.
"""
def assertUnicoded(self, u):
"""
The given L{URL}'s components should be L{unicode}.
@param u: The L{URL} to test.
"""
self.assertTrue(isinstance(u.scheme, unicode)
or u.scheme is None, repr(u))
self.assertTrue(isinstance(u.host, unicode)
or u.host is None, repr(u))
for seg in u.path:
self.assertIsInstance(seg, unicode, repr(u))
for (k, v) in u.query:
self.assertIsInstance(k, unicode, repr(u))
self.assertTrue(v is None or isinstance(v, unicode), repr(u))
self.assertIsInstance(u.fragment, unicode, repr(u))
def assertURL(self, u, scheme, host, path, query,
fragment, port, userinfo=u''):
"""
The given L{URL} should have the given components.
@param u: The actual L{URL} to examine.
@param scheme: The expected scheme.
@param host: The expected host.
@param path: The expected path.
@param query: The expected query.
@param fragment: The expected fragment.
@param port: The expected port.
@param userinfo: The expected userinfo.
"""
actual = (u.scheme, u.host, u.path, u.query,
u.fragment, u.port, u.userinfo)
expected = (scheme, host, tuple(path), tuple(query),
fragment, port, u.userinfo)
self.assertEqual(actual, expected)
def test_initDefaults(self):
"""
L{URL} should have appropriate default values.
"""
def check(u):
self.assertUnicoded(u)
self.assertURL(u, u'http', u'', [], [], u'', 80, u'')
check(URL(u'http', u''))
check(URL(u'http', u'', [], []))
check(URL(u'http', u'', [], [], u''))
def test_init(self):
"""
L{URL} should accept L{unicode} parameters.
"""
u = URL(u's', u'h', [u'p'], [(u'k', u'v'), (u'k', None)], u'f')
self.assertUnicoded(u)
self.assertURL(u, u's', u'h', [u'p'], [(u'k', u'v'), (u'k', None)],
u'f', None)
self.assertURL(URL(u'http', u'\xe0', [u'\xe9'],
[(u'\u03bb', u'\u03c0')], u'\u22a5'),
u'http', u'\xe0', [u'\xe9'],
[(u'\u03bb', u'\u03c0')], u'\u22a5', 80)
def test_initPercent(self):
"""
L{URL} should accept (and not interpret) percent characters.
"""
u = URL(u's', u'%68', [u'%70'], [(u'%6B', u'%76'), (u'%6B', None)],
u'%66')
self.assertUnicoded(u)
self.assertURL(u,
u's', u'%68', [u'%70'],
[(u'%6B', u'%76'), (u'%6B', None)],
u'%66', None)
def test_repr(self):
"""
L{URL.__repr__} will display the canonical form of the URL, wrapped in
a L{URL.fromText} invocation, so that it is C{eval}-able but still easy
to read.
"""
self.assertEqual(
repr(URL(scheme=u'http', host=u'foo', path=[u'bar'],
query=[(u'baz', None), (u'k', u'v')],
fragment=u'frob')),
"URL.from_text(%s)" % (repr(u"http://foo/bar?baz&k=v#frob"),)
)
def test_fromText(self):
"""
Round-tripping L{URL.fromText} with C{str} results in an equivalent
URL.
"""
urlpath = URL.fromText(theurl)
self.assertEqual(theurl, urlpath.asText())
def test_roundtrip(self):
"""
L{URL.asText} should invert L{URL.fromText}.
"""
tests = (
"http://localhost",
"http://localhost/",
"http://localhost/foo",
"http://localhost/foo/",
"http://localhost/foo!!bar/",
"http://localhost/foo%20bar/",
"http://localhost/foo%2Fbar/",
"http://localhost/foo?n",
"http://localhost/foo?n=v",
"http://localhost/foo?n=/a/b",
"http://example.com/foo!@$bar?b!@z=123",
"http://localhost/asd?a=asd%20sdf/345",
"http://(%2525)/(%2525)?(%2525)&(%2525)=(%2525)#(%2525)",
"http://(%C3%A9)/(%C3%A9)?(%C3%A9)&(%C3%A9)=(%C3%A9)#(%C3%A9)",
)
for test in tests:
result = URL.fromText(test).asText()
self.assertEqual(test, result)
def test_equality(self):
"""
Two URLs decoded using L{URL.fromText} will be equal (C{==}) if they
decoded same URL string, and unequal (C{!=}) if they decoded different
strings.
"""
urlpath = URL.fromText(theurl)
self.assertEqual(urlpath, URL.fromText(theurl))
self.assertNotEqual(
urlpath,
URL.fromText('ftp://www.anotherinvaliddomain.com/'
'foo/bar/baz/?zot=21&zut')
)
def test_fragmentEquality(self):
"""
An URL created with the empty string for a fragment compares equal
to an URL created with an unspecified fragment.
"""
self.assertEqual(URL(fragment=u''), URL())
self.assertEqual(URL.fromText(u"http://localhost/#"),
URL.fromText(u"http://localhost/"))
def test_child(self):
"""
L{URL.child} appends a new path segment, but does not affect the query
or fragment.
"""
urlpath = URL.fromText(theurl)
self.assertEqual("http://www.foo.com/a/nice/path/gong?zot=23&zut",
urlpath.child(u'gong').asText())
self.assertEqual("http://www.foo.com/a/nice/path/gong%2F?zot=23&zut",
urlpath.child(u'gong/').asText())
self.assertEqual(
"http://www.foo.com/a/nice/path/gong%2Fdouble?zot=23&zut",
urlpath.child(u'gong/double').asText()
)
self.assertEqual(
"http://www.foo.com/a/nice/path/gong%2Fdouble%2F?zot=23&zut",
urlpath.child(u'gong/double/').asText()
)
def test_multiChild(self):
"""
L{URL.child} receives multiple segments as C{*args} and appends each in
turn.
"""
self.assertEqual(URL.fromText('http://example.com/a/b')
.child('c', 'd', 'e').asText(),
'http://example.com/a/b/c/d/e')
def test_childInitRoot(self):
"""
L{URL.child} of a L{URL} without a path produces a L{URL} with a single
path segment.
"""
childURL = URL(host=u"www.foo.com").child(u"c")
self.assertTrue(childURL.rooted)
self.assertEqual("http://www.foo.com/c", childURL.asText())
def test_sibling(self):
"""
L{URL.sibling} of a L{URL} replaces the last path segment, but does not
affect the query or fragment.
"""
urlpath = URL.fromText(theurl)
self.assertEqual(
"http://www.foo.com/a/nice/path/sister?zot=23&zut",
urlpath.sibling(u'sister').asText()
)
# Use an url without trailing '/' to check child removal.
theurl2 = "http://www.foo.com/a/nice/path?zot=23&zut"
urlpath = URL.fromText(theurl2)
self.assertEqual(
"http://www.foo.com/a/nice/sister?zot=23&zut",
urlpath.sibling(u'sister').asText()
)
def test_click(self):
"""
L{URL.click} interprets the given string as a relative URI-reference
and returns a new L{URL} interpreting C{self} as the base absolute URI.
"""
urlpath = URL.fromText(theurl)
# A null uri should be valid (return here).
self.assertEqual("http://www.foo.com/a/nice/path/?zot=23&zut",
urlpath.click("").asText())
# A simple relative path remove the query.
self.assertEqual("http://www.foo.com/a/nice/path/click",
urlpath.click("click").asText())
# An absolute path replace path and query.
self.assertEqual("http://www.foo.com/click",
urlpath.click("/click").asText())
# Replace just the query.
self.assertEqual("http://www.foo.com/a/nice/path/?burp",
urlpath.click("?burp").asText())
# One full url to another should not generate '//' between authority.
# and path
self.assertNotIn("//foobar",
urlpath.click('http://www.foo.com/foobar').asText())
# From a url with no query clicking a url with a query, the query
# should be handled properly.
u = URL.fromText('http://www.foo.com/me/noquery')
self.assertEqual('http://www.foo.com/me/17?spam=158',
u.click('/me/17?spam=158').asText())
# Check that everything from the path onward is removed when the click
# link has no path.
u = URL.fromText('http://localhost/foo?abc=def')
self.assertEqual(u.click('http://www.python.org').asText(),
'http://www.python.org')
def test_clickRFC3986(self):
"""
L{URL.click} should correctly resolve the examples in RFC 3986.
"""
base = URL.fromText(relativeLinkBaseForRFC3986)
for (ref, expected) in relativeLinkTestsForRFC3986:
self.assertEqual(base.click(ref).asText(), expected)
def test_clickSchemeRelPath(self):
"""
L{URL.click} should not accept schemes with relative paths.
"""
base = URL.fromText(relativeLinkBaseForRFC3986)
self.assertRaises(NotImplementedError, base.click, 'g:h')
self.assertRaises(NotImplementedError, base.click, 'http:h')
def test_cloneUnchanged(self):
"""
Verify that L{URL.replace} doesn't change any of the arguments it
is passed.
"""
urlpath = URL.fromText('https://x:1/y?z=1#A')
self.assertEqual(
urlpath.replace(urlpath.scheme,
urlpath.host,
urlpath.path,
urlpath.query,
urlpath.fragment,
urlpath.port),
urlpath)
self.assertEqual(
urlpath.replace(),
urlpath)
def test_clickCollapse(self):
"""
L{URL.click} collapses C{.} and C{..} according to RFC 3986 section
5.2.4.
"""
tests = [
['http://localhost/', '.', 'http://localhost/'],
['http://localhost/', '..', 'http://localhost/'],
['http://localhost/a/b/c', '.', 'http://localhost/a/b/'],
['http://localhost/a/b/c', '..', 'http://localhost/a/'],
['http://localhost/a/b/c', './d/e', 'http://localhost/a/b/d/e'],
['http://localhost/a/b/c', '../d/e', 'http://localhost/a/d/e'],
['http://localhost/a/b/c', '/./d/e', 'http://localhost/d/e'],
['http://localhost/a/b/c', '/../d/e', 'http://localhost/d/e'],
['http://localhost/a/b/c/', '../../d/e/',
'http://localhost/a/d/e/'],
['http://localhost/a/./c', '../d/e', 'http://localhost/d/e'],
['http://localhost/a/./c/', '../d/e', 'http://localhost/a/d/e'],
['http://localhost/a/b/c/d', './e/../f/../g',
'http://localhost/a/b/c/g'],
['http://localhost/a/b/c', 'd//e', 'http://localhost/a/b/d//e'],
]
for start, click, expected in tests:
actual = URL.fromText(start).click(click).asText()
self.assertEqual(
actual,
expected,
"{start}.click({click}) => {actual} not {expected}".format(
start=start,
click=repr(click),
actual=actual,
expected=expected,
)
)
def test_queryAdd(self):
"""
L{URL.add} adds query parameters.
"""
self.assertEqual(
"http://www.foo.com/a/nice/path/?foo=bar",
URL.fromText("http://www.foo.com/a/nice/path/")
.add(u"foo", u"bar").asText())
self.assertEqual(
"http://www.foo.com/?foo=bar",
URL(host=u"www.foo.com").add(u"foo", u"bar")
.asText())
urlpath = URL.fromText(theurl)
self.assertEqual(
"http://www.foo.com/a/nice/path/?zot=23&zut&burp",
urlpath.add(u"burp").asText())
self.assertEqual(
"http://www.foo.com/a/nice/path/?zot=23&zut&burp=xxx",
urlpath.add(u"burp", u"xxx").asText())
self.assertEqual(
"http://www.foo.com/a/nice/path/?zot=23&zut&burp=xxx&zing",
urlpath.add(u"burp", u"xxx").add(u"zing").asText())
# Note the inversion!
self.assertEqual(
"http://www.foo.com/a/nice/path/?zot=23&zut&zing&burp=xxx",
urlpath.add(u"zing").add(u"burp", u"xxx").asText())
# Note the two values for the same name.
self.assertEqual(
"http://www.foo.com/a/nice/path/?zot=23&zut&burp=xxx&zot=32",
urlpath.add(u"burp", u"xxx").add(u"zot", u'32')
.asText())
def test_querySet(self):
"""
L{URL.set} replaces query parameters by name.
"""
urlpath = URL.fromText(theurl)
self.assertEqual(
"http://www.foo.com/a/nice/path/?zot=32&zut",
urlpath.set(u"zot", u'32').asText())
# Replace name without value with name/value and vice-versa.
self.assertEqual(
"http://www.foo.com/a/nice/path/?zot&zut=itworked",
urlpath.set(u"zot").set(u"zut", u"itworked").asText()
)
# Q: what happens when the query has two values and we replace?
# A: we replace both values with a single one
self.assertEqual(
"http://www.foo.com/a/nice/path/?zot=32&zut",
urlpath.add(u"zot", u"xxx").set(u"zot", u'32').asText()
)
def test_queryRemove(self):
"""
L{URL.remove} removes all instances of a query parameter.
"""
url = URL.fromText(u"https://example.com/a/b/?foo=1&bar=2&foo=3")
self.assertEqual(
url.remove(u"foo"),
URL.fromText(u"https://example.com/a/b/?bar=2")
)
def test_empty(self):
"""
An empty L{URL} should serialize as the empty string.
"""
self.assertEqual(URL().asText(), u'')
def test_justQueryText(self):
"""
An L{URL} with query text should serialize as just query text.
"""
u = URL(query=[(u"hello", u"world")])
self.assertEqual(u.asText(), u'?hello=world')
def test_identicalEqual(self):
"""
L{URL} compares equal to itself.
"""
u = URL.fromText('http://localhost/')
self.assertEqual(u, u)
def test_similarEqual(self):
"""
URLs with equivalent components should compare equal.
"""
u1 = URL.fromText('http://localhost/')
u2 = URL.fromText('http://localhost/')
self.assertEqual(u1, u2)
def test_differentNotEqual(self):
"""
L{URL}s that refer to different resources are both unequal (C{!=}) and
also not equal (not C{==}).
"""
u1 = URL.fromText('http://localhost/a')
u2 = URL.fromText('http://localhost/b')
self.assertFalse(u1 == u2, "%r != %r" % (u1, u2))
self.assertNotEqual(u1, u2)
def test_otherTypesNotEqual(self):
"""
L{URL} is not equal (C{==}) to other types.
"""
u = URL.fromText('http://localhost/')
self.assertFalse(u == 42, "URL must not equal a number.")
self.assertFalse(u == object(), "URL must not equal an object.")
self.assertNotEqual(u, 42)
self.assertNotEqual(u, object())
def test_identicalNotUnequal(self):
"""
Identical L{URL}s are not unequal (C{!=}) to each other.
"""
u = URL.fromText('http://localhost/')
self.assertFalse(u != u, "%r == itself" % u)
def test_similarNotUnequal(self):
"""
Structurally similar L{URL}s are not unequal (C{!=}) to each other.
"""
u1 = URL.fromText('http://localhost/')
u2 = URL.fromText('http://localhost/')
self.assertFalse(u1 != u2, "%r == %r" % (u1, u2))
def test_differentUnequal(self):
"""
Structurally different L{URL}s are unequal (C{!=}) to each other.
"""
u1 = URL.fromText('http://localhost/a')
u2 = URL.fromText('http://localhost/b')
self.assertTrue(u1 != u2, "%r == %r" % (u1, u2))
def test_otherTypesUnequal(self):
"""
L{URL} is unequal (C{!=}) to other types.
"""
u = URL.fromText('http://localhost/')
self.assertTrue(u != 42, "URL must differ from a number.")
self.assertTrue(u != object(), "URL must be differ from an object.")
def test_asURI(self):
"""
L{URL.asURI} produces an URI which converts any URI unicode encoding
into pure US-ASCII and returns a new L{URL}.
"""
unicodey = ('http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/'
'\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}'
'?\N{LATIN SMALL LETTER A}\N{COMBINING ACUTE ACCENT}='
'\N{LATIN SMALL LETTER I}\N{COMBINING ACUTE ACCENT}'
'#\N{LATIN SMALL LETTER U}\N{COMBINING ACUTE ACCENT}')
iri = URL.fromText(unicodey)
uri = iri.asURI()
self.assertEqual(iri.host, '\N{LATIN SMALL LETTER E WITH ACUTE}.com')
self.assertEqual(iri.path[0],
'\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}')
self.assertEqual(iri.asText(), unicodey)
expectedURI = 'http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA'
actualURI = uri.asText()
self.assertEqual(actualURI, expectedURI,
'%r != %r' % (actualURI, expectedURI))
def test_asIRI(self):
"""
L{URL.asIRI} decodes any percent-encoded text in the URI, making it
more suitable for reading by humans, and returns a new L{URL}.
"""
asciiish = 'http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA'
uri = URL.fromText(asciiish)
iri = uri.asIRI()
self.assertEqual(uri.host, 'xn--9ca.com')
self.assertEqual(uri.path[0], '%C3%A9')
self.assertEqual(uri.asText(), asciiish)
expectedIRI = ('http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/'
'\N{LATIN SMALL LETTER E WITH ACUTE}'
'?\N{LATIN SMALL LETTER A WITH ACUTE}='
'\N{LATIN SMALL LETTER I WITH ACUTE}'
'#\N{LATIN SMALL LETTER U WITH ACUTE}')
actualIRI = iri.asText()
self.assertEqual(actualIRI, expectedIRI,
'%r != %r' % (actualIRI, expectedIRI))
def test_badUTF8AsIRI(self):
"""
Bad UTF-8 in a path segment, query parameter, or fragment results in
that portion of the URI remaining percent-encoded in the IRI.
"""
urlWithBinary = 'http://xn--9ca.com/%00%FF/%C3%A9'
uri = URL.fromText(urlWithBinary)
iri = uri.asIRI()
expectedIRI = ('http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/'
'%00%FF/'
'\N{LATIN SMALL LETTER E WITH ACUTE}')
actualIRI = iri.asText()
self.assertEqual(actualIRI, expectedIRI,
'%r != %r' % (actualIRI, expectedIRI))
def test_alreadyIRIAsIRI(self):
"""
A L{URL} composed of non-ASCII text will result in non-ASCII text.
"""
unicodey = ('http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/'
'\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}'
'?\N{LATIN SMALL LETTER A}\N{COMBINING ACUTE ACCENT}='
'\N{LATIN SMALL LETTER I}\N{COMBINING ACUTE ACCENT}'
'#\N{LATIN SMALL LETTER U}\N{COMBINING ACUTE ACCENT}')
iri = URL.fromText(unicodey)
alsoIRI = iri.asIRI()
self.assertEqual(alsoIRI.asText(), unicodey)
def test_alreadyURIAsURI(self):
"""
A L{URL} composed of encoded text will remain encoded.
"""
expectedURI = 'http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA'
uri = URL.fromText(expectedURI)
actualURI = uri.asURI().asText()
self.assertEqual(actualURI, expectedURI)
def test_userinfo(self):
"""
L{URL.fromText} will parse the C{userinfo} portion of the URI
separately from the host and port.
"""
url = URL.fromText(
'http://someuser:somepassword@example.com/some-segment@ignore'
)
self.assertEqual(url.authority(True),
'someuser:somepassword@example.com')
self.assertEqual(url.authority(False), 'someuser:@example.com')
self.assertEqual(url.userinfo, 'someuser:somepassword')
self.assertEqual(url.user, 'someuser')
self.assertEqual(url.asText(),
'http://someuser:@example.com/some-segment@ignore')
self.assertEqual(
url.replace(userinfo=u"someuser").asText(),
'http://someuser@example.com/some-segment@ignore'
)
def test_portText(self):
"""
L{URL.fromText} parses custom port numbers as integers.
"""
portURL = URL.fromText(u"http://www.example.com:8080/")
self.assertEqual(portURL.port, 8080)
self.assertEqual(portURL.asText(), u"http://www.example.com:8080/")
def test_mailto(self):
"""
Although L{URL} instances are mainly for dealing with HTTP, other
schemes (such as C{mailto:}) should work as well. For example,
L{URL.fromText}/L{URL.asText} round-trips cleanly for a C{mailto:} URL
representing an email address.
"""
self.assertEqual(URL.fromText(u"mailto:user@example.com").asText(),
u"mailto:user@example.com")
def test_queryIterable(self):
"""
When a L{URL} is created with a C{query} argument, the C{query}
argument is converted into an N-tuple of 2-tuples.
"""
url = URL(query=[[u'alpha', u'beta']])
self.assertEqual(url.query, ((u'alpha', u'beta'),))
def test_pathIterable(self):
"""
When a L{URL} is created with a C{path} argument, the C{path} is
converted into a tuple.
"""
url = URL(path=[u'hello', u'world'])
self.assertEqual(url.path, (u'hello', u'world'))
def test_invalidArguments(self):
"""
Passing an argument of the wrong type to any of the constructor
arguments of L{URL} will raise a descriptive L{TypeError}.
L{URL} typechecks very aggressively to ensure that its constitutent
parts are all properly immutable and to prevent confusing errors when
bad data crops up in a method call long after the code that called the
constructor is off the stack.
"""
class Unexpected(object):
def __str__(self):
return "wrong"
def __repr__(self):
return "<unexpected>"
defaultExpectation = "unicode" if bytes is str else "str"
def assertRaised(raised, expectation, name):
self.assertEqual(str(raised.exception),
"expected {} for {}, got {}".format(
expectation,
name, "<unexpected>"))
def check(param, expectation=defaultExpectation):
with self.assertRaises(TypeError) as raised:
URL(**{param: Unexpected()})
assertRaised(raised, expectation, param)
check("scheme")
check("host")
check("fragment")
check("rooted", "bool")
check("userinfo")
check("port", "int or NoneType")
with self.assertRaises(TypeError) as raised:
URL(path=[Unexpected(),])
assertRaised(raised, defaultExpectation, "path segment")
with self.assertRaises(TypeError) as raised:
URL(query=[(u"name", Unexpected()),])
assertRaised(raised, defaultExpectation + " or NoneType",
"query parameter value")
with self.assertRaises(TypeError) as raised:
URL(query=[(Unexpected(), u"value"),])
assertRaised(raised, defaultExpectation, "query parameter name")
# No custom error message for this one, just want to make sure
# non-2-tuples don't get through.
with self.assertRaises(TypeError):
URL(query=[Unexpected()])
with self.assertRaises(ValueError):
URL(query=[(u'k', u'v', u'vv')])
with self.assertRaises(ValueError):
URL(query=[(u'k',)])
url = URL.fromText("https://valid.example.com/")
with self.assertRaises(TypeError) as raised:
url.child(Unexpected())
assertRaised(raised, defaultExpectation, "path segment")
with self.assertRaises(TypeError) as raised:
url.sibling(Unexpected())
assertRaised(raised, defaultExpectation, "path segment")
with self.assertRaises(TypeError) as raised:
url.click(Unexpected())
assertRaised(raised, defaultExpectation, "relative URL")
def test_technicallyTextIsIterableBut(self):
"""
Technically, L{str} (or L{unicode}, as appropriate) is iterable, but
C{URL(path="foo")} resulting in C{URL.fromText("f/o/o")} is never what
you want.
"""
with self.assertRaises(TypeError) as raised:
URL(path=u'foo')
self.assertEqual(
str(raised.exception),
"expected iterable of text for path, not: {}"
.format(repr(u'foo'))
)
class URLDeprecationTests(SynchronousTestCase):
"""
L{twisted.python.url} is deprecated.
"""
def test_urlDeprecation(self):
"""
L{twisted.python.url} is deprecated since Twisted 17.5.0.
"""
from twisted.python import url
url
warningsShown = self.flushWarnings([self.test_urlDeprecation])
self.assertEqual(1, len(warningsShown))
self.assertEqual(
("twisted.python.url was deprecated in Twisted 17.5.0:"
" Please use hyperlink from PyPI instead."),
warningsShown[0]['message'])

View file

@ -0,0 +1,290 @@
# -*- test-case-name: twisted.python.test.test_urlpath -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.urlpath}.
"""
from twisted.trial import unittest
from twisted.python import urlpath
from twisted.python.compat import _PY3
class _BaseURLPathTests(object):
"""
Tests for instantiated L{urlpath.URLPath}s.
"""
def test_partsAreBytes(self):
"""
All of the attributes of L{urlpath.URLPath} should be L{bytes}.
"""
self.assertIsInstance(self.path.scheme, bytes)
self.assertIsInstance(self.path.netloc, bytes)
self.assertIsInstance(self.path.path, bytes)
self.assertIsInstance(self.path.query, bytes)
self.assertIsInstance(self.path.fragment, bytes)
def test_strReturnsStr(self):
"""
Calling C{str()} with a L{URLPath} will always return a L{str}.
"""
self.assertEqual(type(self.path.__str__()), str)
def test_mutabilityWithText(self, stringType=type(u"")):
"""
Setting attributes on L{urlpath.URLPath} should change the value
returned by L{str}.
@param stringType: a callable to parameterize this test for different
text types.
@type stringType: 1-argument callable taking L{unicode} and returning
L{str} or L{bytes}.
"""
self.path.scheme = stringType(u"https")
self.assertEqual(
str(self.path),
"https://example.com/foo/bar?yes=no&no=yes#footer")
self.path.netloc = stringType(u"another.example.invalid")
self.assertEqual(
str(self.path),
"https://another.example.invalid/foo/bar?yes=no&no=yes#footer")
self.path.path = stringType(u"/hello")
self.assertEqual(
str(self.path),
"https://another.example.invalid/hello?yes=no&no=yes#footer")
self.path.query = stringType(u"alpha=omega&opposites=same")
self.assertEqual(
str(self.path),
"https://another.example.invalid/hello?alpha=omega&opposites=same"
"#footer")
self.path.fragment = stringType(u"header")
self.assertEqual(
str(self.path),
"https://another.example.invalid/hello?alpha=omega&opposites=same"
"#header")
def test_mutabilityWithBytes(self):
"""
Same as L{test_mutabilityWithText} but for bytes.
"""
self.test_mutabilityWithText(lambda x: x.encode("ascii"))
def test_allAttributesAreBytes(self):
"""
A created L{URLPath} has bytes attributes.
"""
self.assertIsInstance(self.path.scheme, bytes)
self.assertIsInstance(self.path.netloc, bytes)
self.assertIsInstance(self.path.path, bytes)
self.assertIsInstance(self.path.query, bytes)
self.assertIsInstance(self.path.fragment, bytes)
def test_stringConversion(self):
"""
Calling C{str()} with a L{URLPath} will return the same URL that it was
constructed with.
"""
self.assertEqual(str(self.path),
"http://example.com/foo/bar?yes=no&no=yes#footer")
def test_childString(self):
"""
Calling C{str()} with a C{URLPath.child()} will return a URL which is
the child of the URL it was instantiated with.
"""
self.assertEqual(str(self.path.child(b'hello')),
"http://example.com/foo/bar/hello")
self.assertEqual(str(self.path.child(b'hello').child(b'')),
"http://example.com/foo/bar/hello/")
self.assertEqual(str(self.path.child(b'hello', keepQuery=True)),
"http://example.com/foo/bar/hello?yes=no&no=yes")
def test_siblingString(self):
"""
Calling C{str()} with a C{URLPath.sibling()} will return a URL which is
the sibling of the URL it was instantiated with.
"""
self.assertEqual(str(self.path.sibling(b'baz')),
'http://example.com/foo/baz')
self.assertEqual(str(self.path.sibling(b'baz', keepQuery=True)),
"http://example.com/foo/baz?yes=no&no=yes")
# The sibling of http://example.com/foo/bar/
# is http://example.comf/foo/bar/baz
# because really we are constructing a sibling of
# http://example.com/foo/bar/index.html
self.assertEqual(str(self.path.child(b'').sibling(b'baz')),
'http://example.com/foo/bar/baz')
def test_parentString(self):
"""
Calling C{str()} with a C{URLPath.parent()} will return a URL which is
the parent of the URL it was instantiated with.
"""
# .parent() should be equivalent to '..'
# 'foo' is the current directory, '/' is the parent directory
self.assertEqual(str(self.path.parent()),
'http://example.com/')
self.assertEqual(str(self.path.parent(keepQuery=True)),
'http://example.com/?yes=no&no=yes')
self.assertEqual(str(self.path.child(b'').parent()),
'http://example.com/foo/')
self.assertEqual(str(self.path.child(b'baz').parent()),
'http://example.com/foo/')
self.assertEqual(
str(self.path.parent().parent().parent().parent().parent()),
'http://example.com/')
def test_hereString(self):
"""
Calling C{str()} with a C{URLPath.here()} will return a URL which is
the URL that it was instantiated with, without any file, query, or
fragment.
"""
# .here() should be equivalent to '.'
self.assertEqual(str(self.path.here()), 'http://example.com/foo/')
self.assertEqual(str(self.path.here(keepQuery=True)),
'http://example.com/foo/?yes=no&no=yes')
self.assertEqual(str(self.path.child(b'').here()),
'http://example.com/foo/bar/')
def test_doubleSlash(self):
"""
Calling L{urlpath.URLPath.click} on a L{urlpath.URLPath} with a
trailing slash with a relative URL containing a leading slash will
result in a URL with a single slash at the start of the path portion.
"""
self.assertEqual(
str(self.path.click(b"/hello/world")).encode("ascii"),
b"http://example.com/hello/world"
)
def test_pathList(self):
"""
L{urlpath.URLPath.pathList} returns a L{list} of L{bytes}.
"""
self.assertEqual(
self.path.child(b"%00%01%02").pathList(),
[b"", b"foo", b"bar", b"%00%01%02"]
)
# Just testing that the 'copy' argument exists for compatibility; it
# was originally provided for performance reasons, and its behavioral
# contract is kind of nonsense (where is the state shared? who with?)
# so it doesn't actually *do* anything any more.
self.assertEqual(
self.path.child(b"%00%01%02").pathList(copy=False),
[b"", b"foo", b"bar", b"%00%01%02"]
)
self.assertEqual(
self.path.child(b"%00%01%02").pathList(unquote=True),
[b"", b"foo", b"bar", b"\x00\x01\x02"]
)
class BytesURLPathTests(_BaseURLPathTests, unittest.TestCase):
"""
Tests for interacting with a L{URLPath} created with C{fromBytes}.
"""
def setUp(self):
self.path = urlpath.URLPath.fromBytes(
b"http://example.com/foo/bar?yes=no&no=yes#footer")
def test_mustBeBytes(self):
"""
L{URLPath.fromBytes} must take a L{bytes} argument.
"""
with self.assertRaises(ValueError):
urlpath.URLPath.fromBytes(None)
with self.assertRaises(ValueError):
urlpath.URLPath.fromBytes(u"someurl")
def test_withoutArguments(self):
"""
An instantiation with no arguments creates a usable L{URLPath} with
default arguments.
"""
url = urlpath.URLPath()
self.assertEqual(str(url), "http://localhost/")
def test_partialArguments(self):
"""
Leaving some optional arguments unfilled makes a L{URLPath} with those
optional arguments filled with defaults.
"""
# Not a "full" URL given to fromBytes, no /
# / is filled in
url = urlpath.URLPath.fromBytes(b"http://google.com")
self.assertEqual(url.scheme, b"http")
self.assertEqual(url.netloc, b"google.com")
self.assertEqual(url.path, b"/")
self.assertEqual(url.fragment, b"")
self.assertEqual(url.query, b"")
self.assertEqual(str(url), "http://google.com/")
def test_nonASCIIBytes(self):
"""
L{URLPath.fromBytes} can interpret non-ASCII bytes as percent-encoded
"""
url = urlpath.URLPath.fromBytes(b"http://example.com/\xff\x00")
self.assertEqual(str(url), "http://example.com/%FF%00")
class StringURLPathTests(_BaseURLPathTests, unittest.TestCase):
"""
Tests for interacting with a L{URLPath} created with C{fromString} and a
L{str} argument.
"""
def setUp(self):
self.path = urlpath.URLPath.fromString(
"http://example.com/foo/bar?yes=no&no=yes#footer")
def test_mustBeStr(self):
"""
C{URLPath.fromString} must take a L{str} or L{unicode} argument.
"""
with self.assertRaises(ValueError):
urlpath.URLPath.fromString(None)
if _PY3:
with self.assertRaises(ValueError):
urlpath.URLPath.fromString(b"someurl")
class UnicodeURLPathTests(_BaseURLPathTests, unittest.TestCase):
"""
Tests for interacting with a L{URLPath} created with C{fromString} and a
L{unicode} argument.
"""
def setUp(self):
self.path = urlpath.URLPath.fromString(
u"http://example.com/foo/bar?yes=no&no=yes#footer")
def test_nonASCIICharacters(self):
"""
L{URLPath.fromString} can load non-ASCII characters.
"""
url = urlpath.URLPath.fromString(u"http://example.com/\xff\x00")
self.assertEqual(str(url), "http://example.com/%C3%BF%00")

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,176 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.versions}.
"""
from __future__ import division, absolute_import
import operator
from twisted.python.versions import getVersionString, IncomparableVersions
from twisted.python.versions import Version
from incremental import _inf
from twisted.trial.unittest import SynchronousTestCase as TestCase
class VersionsTests(TestCase):
def test_versionComparison(self):
"""
Versions can be compared for equality and order.
"""
va = Version("dummy", 1, 0, 0)
vb = Version("dummy", 0, 1, 0)
self.assertTrue(va > vb)
self.assertTrue(vb < va)
self.assertTrue(va >= vb)
self.assertTrue(vb <= va)
self.assertTrue(va != vb)
self.assertTrue(vb == Version("dummy", 0, 1, 0))
self.assertTrue(vb == vb)
def test_versionComparisonCaseInsensitive(self):
"""
Version packages are compared case insensitively.
"""
va = Version("twisted", 1, 0, 0)
vb = Version("Twisted", 0, 1, 0)
self.assertTrue(va > vb)
self.assertTrue(vb < va)
self.assertTrue(va >= vb)
self.assertTrue(vb <= va)
self.assertTrue(va != vb)
self.assertTrue(vb == Version("twisted", 0, 1, 0))
self.assertTrue(vb == Version("TWISted", 0, 1, 0))
self.assertTrue(vb == vb)
def test_comparingPrereleasesWithReleases(self):
"""
Prereleases are always less than versions without prereleases.
"""
va = Version("whatever", 1, 0, 0, prerelease=1)
vb = Version("whatever", 1, 0, 0)
self.assertTrue(va < vb)
self.assertFalse(va > vb)
self.assertNotEqual(vb, va)
def test_comparingPrereleases(self):
"""
The value specified as the prerelease is used in version comparisons.
"""
va = Version("whatever", 1, 0, 0, prerelease=1)
vb = Version("whatever", 1, 0, 0, prerelease=2)
self.assertTrue(va < vb)
self.assertTrue(vb > va)
self.assertTrue(va <= vb)
self.assertTrue(vb >= va)
self.assertTrue(va != vb)
self.assertTrue(vb == Version("whatever", 1, 0, 0, prerelease=2))
self.assertTrue(va == va)
def test_infComparison(self):
"""
L{_inf} is equal to L{_inf}.
This is a regression test.
"""
self.assertEqual(_inf, _inf)
def test_disallowBuggyComparisons(self):
"""
The package names of the Version objects need to be the same,
"""
self.assertRaises(IncomparableVersions,
operator.eq,
Version("dummy", 1, 0, 0),
Version("dumym", 1, 0, 0))
def test_notImplementedComparisons(self):
"""
Comparing a L{Version} to some other object type results in
C{NotImplemented}.
"""
va = Version("dummy", 1, 0, 0)
vb = ("dummy", 1, 0, 0) # a tuple is not a Version object
self.assertEqual(va.__cmp__(vb), NotImplemented)
def test_repr(self):
"""
Calling C{repr} on a version returns a human-readable string
representation of the version.
"""
self.assertEqual(repr(Version("dummy", 1, 2, 3)),
"Version('dummy', 1, 2, 3)")
def test_reprWithPrerelease(self):
"""
Calling C{repr} on a version with a prerelease returns a human-readable
string representation of the version including the prerelease.
"""
self.assertEqual(repr(Version("dummy", 1, 2, 3, prerelease=4)),
"Version('dummy', 1, 2, 3, release_candidate=4)")
def test_str(self):
"""
Calling C{str} on a version returns a human-readable string
representation of the version.
"""
self.assertEqual(str(Version("dummy", 1, 2, 3)),
"[dummy, version 1.2.3]")
def test_strWithPrerelease(self):
"""
Calling C{str} on a version with a prerelease includes the prerelease.
"""
self.assertEqual(str(Version("dummy", 1, 0, 0, prerelease=1)),
"[dummy, version 1.0.0rc1]")
def testShort(self):
self.assertEqual(Version('dummy', 1, 2, 3).short(), '1.2.3')
def test_getVersionString(self):
"""
L{getVersionString} returns a string with the package name and the
short version number.
"""
self.assertEqual(
'Twisted 8.0.0', getVersionString(Version('Twisted', 8, 0, 0)))
def test_getVersionStringWithPrerelease(self):
"""
L{getVersionString} includes the prerelease, if any.
"""
self.assertEqual(
getVersionString(Version("whatever", 8, 0, 0, prerelease=1)),
"whatever 8.0.0rc1")
def test_base(self):
"""
The L{base} method returns a very simple representation of the version.
"""
self.assertEqual(Version("foo", 1, 0, 0).base(), "1.0.0")
def test_baseWithPrerelease(self):
"""
The base version includes 'preX' for versions with prereleases.
"""
self.assertEqual(Version("foo", 1, 0, 0, prerelease=8).base(),
"1.0.0rc8")

View file

@ -0,0 +1,101 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Test cases covering L{twisted.python.zippath}.
"""
from __future__ import absolute_import, division
import os
import zipfile
from twisted.test.test_paths import AbstractFilePathTests
from twisted.python.zippath import ZipArchive
from twisted.python.filepath import _coerceToFilesystemEncoding
def zipit(dirname, zfname):
"""
Create a zipfile on zfname, containing the contents of dirname'
"""
dirname = _coerceToFilesystemEncoding('', dirname)
zfname = _coerceToFilesystemEncoding('', zfname)
with zipfile.ZipFile(zfname, "w") as zf:
for root, ignored, files, in os.walk(dirname):
for fname in files:
fspath = os.path.join(root, fname)
arcpath = os.path.join(root, fname)[len(dirname)+1:]
zf.write(fspath, arcpath)
class ZipFilePathTests(AbstractFilePathTests):
"""
Test various L{ZipPath} path manipulations as well as reprs for L{ZipPath}
and L{ZipArchive}.
"""
def setUp(self):
AbstractFilePathTests.setUp(self)
zipit(self.cmn, self.cmn + b'.zip')
self.nativecmn = _coerceToFilesystemEncoding('', self.cmn)
self.path = ZipArchive(self.cmn + b'.zip')
self.root = self.path
self.all = [x.replace(self.cmn, self.cmn + b'.zip')
for x in self.all]
def test_zipPathRepr(self):
"""
Make sure that invoking ZipPath's repr prints the correct class name
and an absolute path to the zip file.
"""
child = self.path.child("foo")
pathRepr = "ZipPath(%r)" % (
os.path.abspath(self.nativecmn + ".zip" + os.sep + 'foo'),)
# Check for an absolute path
self.assertEqual(repr(child), pathRepr)
# Create a path to the file rooted in the current working directory
relativeCommon = self.nativecmn.replace(os.getcwd() + os.sep,
"", 1) + ".zip"
relpath = ZipArchive(relativeCommon)
child = relpath.child("foo")
# Check using a path without the cwd prepended
self.assertEqual(repr(child), pathRepr)
def test_zipPathReprParentDirSegment(self):
"""
The repr of a ZipPath with C{".."} in the internal part of its path
includes the C{".."} rather than applying the usual parent directory
meaning.
"""
child = self.path.child("foo").child("..").child("bar")
pathRepr = "ZipPath(%r)" % (
self.nativecmn + ".zip" + os.sep.join(["", "foo", "..", "bar"]))
self.assertEqual(repr(child), pathRepr)
def test_zipArchiveRepr(self):
"""
Make sure that invoking ZipArchive's repr prints the correct class
name and an absolute path to the zip file.
"""
path = ZipArchive(self.nativecmn + '.zip')
pathRepr = 'ZipArchive(%r)' % (os.path.abspath(
self.nativecmn + '.zip'),)
# Check for an absolute path
self.assertEqual(repr(path), pathRepr)
# Create a path to the file rooted in the current working directory
relativeCommon = self.nativecmn.replace(os.getcwd() + os.sep,
"", 1) + ".zip"
relpath = ZipArchive(relativeCommon)
# Check using a path without the cwd prepended
self.assertEqual(repr(relpath), pathRepr)

View file

@ -0,0 +1,361 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.python.zipstream}
"""
import random
import struct
import zipfile
from hashlib import md5
from twisted.python import zipstream, filepath
from twisted.trial import unittest
class FileEntryMixin(object):
"""
File entry classes should behave as file-like objects
"""
def getFileEntry(self, contents):
"""
Return an appropriate zip file entry
"""
filename = self.mktemp()
with zipfile.ZipFile(filename, 'w', self.compression) as z:
z.writestr('content', contents)
z = zipstream.ChunkingZipFile(filename, 'r')
return z.readfile('content')
def test_isatty(self):
"""
zip files should not be ttys, so isatty() should be false
"""
with self.getFileEntry('') as fileEntry:
self.assertFalse(fileEntry.isatty())
def test_closed(self):
"""
The C{closed} attribute should reflect whether C{close()} has been
called.
"""
with self.getFileEntry('') as fileEntry:
self.assertFalse(fileEntry.closed)
self.assertTrue(fileEntry.closed)
def test_readline(self):
"""
C{readline()} should mirror L{file.readline} and return up to a single
delimiter.
"""
with self.getFileEntry(b'hoho\nho') as fileEntry:
self.assertEqual(fileEntry.readline(), b'hoho\n')
self.assertEqual(fileEntry.readline(), b'ho')
self.assertEqual(fileEntry.readline(), b'')
def test_next(self):
"""
Zip file entries should implement the iterator protocol as files do.
"""
with self.getFileEntry(b'ho\nhoho') as fileEntry:
self.assertEqual(fileEntry.next(), b'ho\n')
self.assertEqual(fileEntry.next(), b'hoho')
self.assertRaises(StopIteration, fileEntry.next)
def test_readlines(self):
"""
C{readlines()} should return a list of all the lines.
"""
with self.getFileEntry(b'ho\nho\nho') as fileEntry:
self.assertEqual(fileEntry.readlines(), [b'ho\n', b'ho\n', b'ho'])
def test_iteration(self):
"""
C{__iter__()} and C{xreadlines()} should return C{self}.
"""
with self.getFileEntry('') as fileEntry:
self.assertIs(iter(fileEntry), fileEntry)
self.assertIs(fileEntry.xreadlines(), fileEntry)
def test_readWhole(self):
"""
C{.read()} should read the entire file.
"""
contents = b"Hello, world!"
with self.getFileEntry(contents) as entry:
self.assertEqual(entry.read(), contents)
def test_readPartial(self):
"""
C{.read(num)} should read num bytes from the file.
"""
contents = "0123456789"
with self.getFileEntry(contents) as entry:
one = entry.read(4)
two = entry.read(200)
self.assertEqual(one, b"0123")
self.assertEqual(two, b"456789")
def test_tell(self):
"""
C{.tell()} should return the number of bytes that have been read so
far.
"""
contents = "x" * 100
with self.getFileEntry(contents) as entry:
entry.read(2)
self.assertEqual(entry.tell(), 2)
entry.read(4)
self.assertEqual(entry.tell(), 6)
class DeflatedZipFileEntryTests(FileEntryMixin, unittest.TestCase):
"""
DeflatedZipFileEntry should be file-like
"""
compression = zipfile.ZIP_DEFLATED
class ZipFileEntryTests(FileEntryMixin, unittest.TestCase):
"""
ZipFileEntry should be file-like
"""
compression = zipfile.ZIP_STORED
class ZipstreamTests(unittest.TestCase):
"""
Tests for twisted.python.zipstream
"""
def setUp(self):
"""
Creates junk data that can be compressed and a test directory for any
files that will be created
"""
self.testdir = filepath.FilePath(self.mktemp())
self.testdir.makedirs()
self.unzipdir = self.testdir.child('unzipped')
self.unzipdir.makedirs()
def makeZipFile(self, contents, directory=''):
"""
Makes a zip file archive containing len(contents) files. Contents
should be a list of strings, each string being the content of one file.
"""
zpfilename = self.testdir.child('zipfile.zip').path
with zipfile.ZipFile(zpfilename, 'w') as zpfile:
for i, content in enumerate(contents):
filename = str(i)
if directory:
filename = directory + "/" + filename
zpfile.writestr(filename, content)
return zpfilename
def test_invalidMode(self):
"""
A ChunkingZipFile opened in write-mode should not allow .readfile(),
and raise a RuntimeError instead.
"""
with zipstream.ChunkingZipFile(self.mktemp(), "w") as czf:
self.assertRaises(RuntimeError, czf.readfile, "something")
def test_closedArchive(self):
"""
A closed ChunkingZipFile should raise a L{RuntimeError} when
.readfile() is invoked.
"""
czf = zipstream.ChunkingZipFile(self.makeZipFile(["something"]), "r")
czf.close()
self.assertRaises(RuntimeError, czf.readfile, "something")
def test_invalidHeader(self):
"""
A zipfile entry with the wrong magic number should raise BadZipfile for
readfile(), but that should not affect other files in the archive.
"""
fn = self.makeZipFile(["test contents",
"more contents"])
with zipfile.ZipFile(fn, "r") as zf:
zeroOffset = zf.getinfo("0").header_offset
# Zero out just the one header.
with open(fn, "r+b") as scribble:
scribble.seek(zeroOffset, 0)
scribble.write(b'0' * 4)
with zipstream.ChunkingZipFile(fn) as czf:
self.assertRaises(zipfile.BadZipfile, czf.readfile, "0")
with czf.readfile("1") as zfe:
self.assertEqual(zfe.read(), b"more contents")
def test_filenameMismatch(self):
"""
A zipfile entry with a different filename than is found in the central
directory should raise BadZipfile.
"""
fn = self.makeZipFile([b"test contents",
b"more contents"])
with zipfile.ZipFile(fn, "r") as zf:
info = zf.getinfo("0")
info.filename = "not zero"
with open(fn, "r+b") as scribble:
scribble.seek(info.header_offset, 0)
scribble.write(info.FileHeader())
with zipstream.ChunkingZipFile(fn) as czf:
self.assertRaises(zipfile.BadZipfile, czf.readfile, "0")
with czf.readfile("1") as zfe:
self.assertEqual(zfe.read(), b"more contents")
def test_unsupportedCompression(self):
"""
A zipfile which describes an unsupported compression mechanism should
raise BadZipfile.
"""
fn = self.mktemp()
with zipfile.ZipFile(fn, "w") as zf:
zi = zipfile.ZipInfo("0")
zf.writestr(zi, "some data")
# Mangle its compression type in the central directory; can't do
# this before the writestr call or zipfile will (correctly) tell us
# not to pass bad compression types :)
zi.compress_type = 1234
with zipstream.ChunkingZipFile(fn) as czf:
self.assertRaises(zipfile.BadZipfile, czf.readfile, "0")
def test_extraData(self):
"""
readfile() should skip over 'extra' data present in the zip metadata.
"""
fn = self.mktemp()
with zipfile.ZipFile(fn, 'w') as zf:
zi = zipfile.ZipInfo("0")
extra_data = b"hello, extra"
zi.extra = (
struct.pack('<hh', 42, len(extra_data))
+ extra_data
)
zf.writestr(zi, b"the real data")
with zipstream.ChunkingZipFile(fn) as czf, czf.readfile("0") as zfe:
self.assertEqual(zfe.read(), b"the real data")
def test_unzipIterChunky(self):
"""
L{twisted.python.zipstream.unzipIterChunky} returns an iterator which
must be exhausted to completely unzip the input archive.
"""
numfiles = 10
contents = ['This is test file %d!' % i for i in range(numfiles)]
contents = [i.encode("ascii") for i in contents]
zpfilename = self.makeZipFile(contents)
list(zipstream.unzipIterChunky(zpfilename, self.unzipdir.path))
self.assertEqual(
set(self.unzipdir.listdir()),
set(map(str, range(numfiles))))
for child in self.unzipdir.children():
num = int(child.basename())
self.assertEqual(child.getContent(), contents[num])
def test_unzipIterChunkyDirectory(self):
"""
The path to which a file is extracted by L{zipstream.unzipIterChunky}
is determined by joining the C{directory} argument to C{unzip} with the
path within the archive of the file being extracted.
"""
numfiles = 10
contents = ['This is test file %d!' % i for i in range(numfiles)]
contents = [i.encode("ascii") for i in contents]
zpfilename = self.makeZipFile(contents, 'foo')
list(zipstream.unzipIterChunky(zpfilename, self.unzipdir.path))
fileContents = {str(num).encode("ascii") for num in range(numfiles)}
self.assertEqual(
set(self.unzipdir.child(b'foo').listdir()),
fileContents)
for child in self.unzipdir.child(b'foo').children():
num = int(child.basename())
self.assertEqual(child.getContent(), contents[num])
# XXX these tests are kind of gross and old, but I think unzipIterChunky is
# kind of a gross function anyway. We should really write an abstract
# copyTo/moveTo that operates on FilePath and make sure ZipPath can support
# it, then just deprecate / remove this stuff.
def _unzipIterChunkyTest(self, compression, chunksize, lower, upper):
"""
unzipIterChunky should unzip the given number of bytes per iteration.
"""
junk = b''
for n in range(1000):
num = round(random.random(), 12)
numEncoded = str(num).encode("ascii")
junk += b' '+numEncoded
junkmd5 = md5(junk).hexdigest()
tempdir = filepath.FilePath(self.mktemp())
tempdir.makedirs()
zfpath = tempdir.child('bigfile.zip').path
self._makebigfile(zfpath, compression, junk)
uziter = zipstream.unzipIterChunky(zfpath, tempdir.path,
chunksize=chunksize)
r = next(uziter)
# test that the number of chunks is in the right ballpark;
# this could theoretically be any number but statistically it
# should always be in this range
approx = lower < r < upper
self.assertTrue(approx)
for r in uziter:
pass
self.assertEqual(r, 0)
with tempdir.child("zipstreamjunk").open() as f:
newmd5 = md5(f.read()).hexdigest()
self.assertEqual(newmd5, junkmd5)
def test_unzipIterChunkyStored(self):
"""
unzipIterChunky should unzip the given number of bytes per iteration on
a stored archive.
"""
self._unzipIterChunkyTest(zipfile.ZIP_STORED, 500, 35, 45)
def test_chunkyDeflated(self):
"""
unzipIterChunky should unzip the given number of bytes per iteration on
a deflated archive.
"""
self._unzipIterChunkyTest(zipfile.ZIP_DEFLATED, 972, 23, 27)
def _makebigfile(self, filename, compression, junk):
"""
Create a zip file with the given file name and compression scheme.
"""
with zipfile.ZipFile(filename, 'w', compression) as zf:
for i in range(10):
fn = 'zipstream%d' % i
zf.writestr(fn, "")
zf.writestr('zipstreamjunk', junk)