Ausgabe der neuen DB Einträge
This commit is contained in:
parent
bad48e1627
commit
cfbbb9ee3d
2399 changed files with 843193 additions and 43 deletions
|
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
Unit tests for L{twisted.python}.
|
||||
"""
|
||||
|
|
@ -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)
|
||||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"))
|
||||
|
|
@ -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
|
|
@ -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"
|
||||
|
|
@ -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)
|
||||
|
|
@ -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')
|
||||
|
|
@ -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)
|
||||
1253
venv/lib/python3.9/site-packages/twisted/python/test/test_release.py
Normal file
1253
venv/lib/python3.9/site-packages/twisted/python/test/test_release.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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))
|
||||
|
|
@ -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
|
||||
|
|
@ -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))
|
||||
|
|
@ -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
|
||||
"""
|
||||
|
|
@ -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')])
|
||||
|
|
@ -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())
|
||||
|
|
@ -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')
|
||||
|
|
@ -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")
|
||||
805
venv/lib/python3.9/site-packages/twisted/python/test/test_url.py
Normal file
805
venv/lib/python3.9/site-packages/twisted/python/test/test_url.py
Normal 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'])
|
||||
|
|
@ -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")
|
||||
1169
venv/lib/python3.9/site-packages/twisted/python/test/test_util.py
Normal file
1169
venv/lib/python3.9/site-packages/twisted/python/test/test_util.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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")
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue