JFIF   ( %!1!%)+...383,7(-.+  ++-+++++-++-++--+--+-+-------+-++-+--+---+++--+7+-+"F!1AQaq"2BRb#3Sr$CDsT&!Q1Aa"2Rbq ?򉄘ǷLR HR,nNb .&W)fJbMOYxj-\bT2(4CQ"qiC/ " %0Jl"e2V  0SDd2@TV^{cW&F͉x9#l,.XɳvRZ C8S 6ml!@!E! `FS!M #(d)Q lml1ml Ų&x(ʨ2NFmj@D<dN5UN˄uTB emLAy#` ` ` I!I 6āHBxL & J#7BQ.$hv h q+tC"EJ) 8R e2U2Y@j%6PF^4LnNBp"8)4JI-ֲvK ^؊)hz[T5˗",Rҥf8ڤS4ʘ!`D ` X+ L,(hl)*S##`6[`0*L T H*HA@I&&r1kr*r*)N$#L  1#ZFSl `[( ("((he`4 Ch [="A R / 0I`twCDcWh"i) cLad\BcLKHZ"ZEW$Ƚ@A~i^`S *A&h:+c Y6vϕGClRPs.`H`(@<$qDe pL@DpLX, E2MP A  `II m& AQ "AT rbg# g2!SiLj*3L \ G;TFL`K BMy 2S`YLh1 d >-"ZfD^Q DH" RAbEV#Lfq,(rETp64-IJ!*p4F$q;G8DQ/TKP2$jp3KW]FtLtƉ1ol]VBgػJH6 )h61GJR7Nj.Z4piJRDd]t]0dP]:N.b'⹙SvDSz]L,_#ugT&[~?cS^"{Bh{/=ۑxOk̳O59o dar793`)SeYM@\ "$E(Tm&)N2Ih)F5EDed(FS,Pa @!@#@lea HCD$11jCLJqcod S3yd*,lL+QEfsgW1nw)cT#dS HXkFJB"6(ʝH)H"#EZh:Y`khݳh%Sc<mlAko2]gDqQtro=3OƸU9_-t8UvW3sGəg*#:c)><"wc\ASmT|6Ę>9~#1Ƈ~ڒE1vVi# I MM#u$8W 5ǍfƬΜg*Qpi1ȩFOf۔S,/⎯(Lrմ`(Z LsbA \6 6dm[I=!r:REI.wgzG)ԇSbӑxuׇTyyL^e'x^ty4Z&eB]I|v59Jjhm;Ng񷫳n<ϞҼѝjk;׹DlY^ҍ\+x9V!j([cmS.NO6jxNζrm&oײizT$N>?~ Sl-:iڥk\at#E!CL`.O0a*w/WV7/r)DŽt7'Nĵ#7O1 ]{[/-2bA<$&Gm_4t)_>)mjG;V^'k59o>ɌM,ؾf9z6 4v_3T.5V/RD-5 %T5XTޫ4TaZ`U *ƱUƲ UG"5+sJJ2E9#܎kr2G3Bb,XM6H: ?@p!'\4V02aԙ) hbZ]:` ev3ʘ'}!ohȒ*TJjr[RFyQ*#{h{R]J]Lr-.D-.җfo$D ?X0%~1P.Og{cWϫ22&Ϭ_V.W3nmiOl}+!˫#`kR33aUb0-g:qmsέ+0HO|&nhOn+}n5QF_"gvLm/z'+r'n_oC语i|1}Gi|}_D~9JZ_%DVQp\koۅjAs~/c0ksUJi^W9W5!>?O:q|ˣSIB/&K<(lg(%Wg$|LW7vߤW߇q|jef3D H\S6(eJb*@&sTKTW/*@v:.N- @ITʓ1Zg&-eꓝM r]EMס{q$b]'7Z7N:O~lNlP7iͲk)$O^퉢<YSD*hr'Z#5e6t[Fdh AJǔP9P 1\R).Il+jI*,(ܢ22N*OwKFX gc?\mB7iA+εe8 "ġ/p5pW-$މ-[a 5ViAW/V{/&UsF./՞ҕ*)rZg.^_+gt_z-oAbqQn*WlHyZ*\TaEewlLR3ԹȭN}MM}aih"5ܕRT$:~'TcT|*)xGC>n+r{XU xuF"<~67у'fxlf`r3D*#Z1ђfH`2dIWo/qB| 63xxW6^m%Kvg>\>x>!H5Nr8J/FJ9Wx(Hou" S'kWاC\9ְ#^OaҮ+~gnkuЉ,aWU*1 읍jnb|e= :2.UL`Q}YS&gI.c=a`%j:C%2@^>])25/ܙ<lzwɛ)ݣS4h3=J tyϬ.E7 8ڞGZu\_JHsݢϑ}IZ"ӳ=X<Ɖ2{a:{7L+>V}c)*lo Yv&+|L;>+/Sj26K+澡*;>-s"}M2] Ig5aCL*r"&\} #^R.7_Mgf}.ߌy(}Z\gP&ʠHj%</{.]rߙQ`>;5g;u6dԛ %xb|oՋTJ5Ϥ(]XqP>f{Jk2,8'~ZU6tMQsg XKg^2ϓ3},[wo۴I|ܷ%[Ol\Pkr]Y//cg6U⧻/VПi8ys_n<\~cze!!H~x;QJZKȮ^ȧG|cS~8ji,Fo+,y~?pk)u /in3JmkX(Mj1N 4c Epc>BO *LfQO&` c;LjcYf 1ɻ)CLsY^Y5" lP/wuEln&dav,(;'W9ej ku`-KHI՟%ԁʁ 1\}?OjsF^Xn$Ё.օC>D:?I @aGE.ĩ1 $ et~T`߸Ir'RX.Zwc%~U=r>-UaFbǺ?R=Z?i'[ASS;siJrzy>nxu$[_B\4}:r'ҵj1_v-[;y?ֹ0I16 . M%4^!S&t ! h !zQð.bBT ?@]?CHq(rd!.$>/x+bnʎNN#w)` )*f!-ɂ\(طYLHzc`Uq7BfCcE0ԉ4Fم쏠ce5T r͸GVlФ?ѣ} mhrkly.Ts㷖)Mө S^%'g>wk%bP[}j~ǾV#K -Fgv켼ǨgɼeSz/6{M=BPZFu\Q75n3Iݤ.W9QfF{vJwF't[@iVj4G~KOnH߿_Do=.c.One?E+GfGN⧭H?4;u`ua|V-+j4?48n ɦ=-]puv&Jc}K>b%U x8pz6L8AXFsW]N55ҦbIWZQ7ï Ԗ3cjz匩ӺOTɖƴ%a'MI}cdR$ݚIζ̝ LIu>J3{^෠㜦˯xܿe\b"2y'x{ RDW b+o2KFhR0:U늞En>լRӉt Iڹ\ wշQEv"v;EJ)yl[5:F0=b4,\PqKtv4{bQz:>C7"8W#Zjdd| cjz%K %Z 9dD{=NFʳAƩtI)kS*s$`:A\ʬ*ֹ9{Nl|eJ١rQnM%z_#x_•TO><)kyD %GN<~y>vfǧB)F)c\lې(#\ h`fgfjTBdhhHL2Y0^ Y0^-"D!QaI15 m~ gՒd|;#gMn(P$l H.R2^PU")pN` N8󫅂OJ;^jz\uumJMF|ηq[]$Vrrt:Q^;QPkHՠ{]HwˆMuIr7!r&- j%"9LtUb56+^TWBqdhHAD7 HwKH^F3LIq #hK`]IWKiH?کǴeԥQ>g{^q^>HKoOB||8aݏS}{S_]ϸ/X~ܵw'OSPAf֩ܟ[>7 @[ֵ;G߇QU*Cթ *OKU^zz[fRnpcJX9u<iq8B]u8 ]I,;[G#2W.¸D8rPG Y%PBJ= wo;PJgx6;yB`3zZGPAͫy{5Nb_re*ONHR]Ji)U{Ӓ:qqɏ[mB4࢒I$ 2vpBADY`DIVAn"Bh$&&cMbdB 鮆wHR'E(ѸZA*H~{B M҅n\@N{7ISCp Vd( r+bg|ns:qg:|J|ɪV.UVaAS͓FyRuLѦT騬 `3􏳕{eo/Tz8DkW?,cl~TqLne֠[B*D +t 6˦S;5KjV3e WBrT.XSHm sl5F%NGM`Y )": J!W4]HTrPX2 QYɕ\m2VLd+`,^ѺiPztUGY6+cӧ6] U%u/ˈFOiB*nFF#ұJ Z/c')?Q͟5.8E~G6e<\?}GkhMFUظOqhEA - "`dQ#(4Ԧf VLmc@q5J8K; M^JZnn)9Zm\ qIJqS: i[9~Oaƒ]Z4F&+666( N]쁼LM(oyvUI/Χ[ھ]hTˉG".SeYgu;hRDtڬv=5 ׁqMS\Ȭi5D]1$*0UL1QY`QdLb[+z9";'yi`OT/4{@EZ'Y0>4I*d nM#5hі.vrM[]Ä;]\ʦS,叕DQZq0fӌI͋]TNK"#;?F;aURx_4WDm+F*0XJE@){ 1R-E2(@Qh l D rT.Q;[J;[`30`ɀ 2#=JeSsxRjG=`H rLJ@ Y$JaB2/x( "Id'6O0CI$:Ol+}I>[L|iK+]ZrH*2Aʶ uHRd)OrrbSx=5dmue1neܬ"e>Lw94勲u ҏ_4GuоJw]QtgSk(qW(6h|v= 1=P/\YZ|R>"*5W/ίR'o %R$5= .!VIRMf4*aR5nv% Usj:V Lj]Bn/TZ&.2„ܒBP)aYRʌW!#ErGf';tW$czI*\KI,c7Zc-ўj|p+-ђ{eg 2;R_{VLM]7sؒFmԻy853gҾqJG!E̤ӏqzs༿? U#R)ŧU(,>,&,-^e^۔.b EW^n<)\9.QeJuFiSh2"EL8yeCKQD\5R,D5.P]c1STt*ZFJ.T:N #%]M}khOe(͓iEMsɆ3( YF<"Ly^*[ry6.ɸm k݊iT%nM8 $Q#F# q 1*?% iS^4oܗ wWPS,aNޖxOxڽqp#F6&o,7LJuMΤK(Td{U Ƹf|q5U{3[FLNK6ӵQY5+'>Q3FSk).&:5z yZq/*q$d+Ge+$lO@Nڤy5eBvˌ䖥shS:JksgksF ꧸oi-FYxy9[Vȼĝ'_.[y2U*c?E+:TsWՀgOS> z75>ncߏ-Kz8ԋ,Ϧ70Z9_1h$Xiu10)0$+$! qsE4wRkh2*T.s%DH:`:=k.'WB{ ȮRGҷ7чVg)CHS}1ݍԳۂ<8g_4y*-Ml\]mZT)mJ~|k<6zWjf4'*u%RNRȉZA) .VLtp 4 V&mtJ#l˅;&{]8>TmhoLXOeD^_J>]jsSej﫦iOM SK([!Vc5zn-A@p]Ӄ \3kmK>#-sܧ?NLar@Js?…Xldny]݌E5•9.8hh69#7js׳R,'pqt:kgPhRԄ+ՕG9}="ֲ\kǁm R73pg$t3+o |o\]'ee5ɐ.7ѐ|ZعSF{qkx5-$Q h5*1yM$ 7)hJ2Kg`-hn*>)EYDIkBpȩAzfǪ>7O K#lߤg]:u~huُ۵u}(mjGIj܏6ES~/5CiRy|kVKGBޭ3;w /jꏈUu>iƪi:WRo'yr4C/?c:w!?\'?#Q:>u/?uEeuG*xY2)?־CAr*23_ץ}գk1%(_ _6aԗ _4 $ϗ+ϫɆzǾIgu?Y<#_xS>i\uɇ۽r}[ͫyRoWCC!H,iD։"Cj5 4] cTk2YZRBvRY~FqQt^RO-g"QP]Ih/t:ljs YӹqI] wqXp KV+8j} uu8PGP&zF:;8+ Sx9(. Q}:ƻWr,Ũ*'shfƧ-6__5,DH{* qp묘G MA}QRe{dyMucǨɾ7߈Avϩe͜jmUi p3\5,ާbf:o+7#ܾ~iU#up=}˄k{NV8m!ҌiptޜBvKi}!ש3UK)`igӞVMR'J[ky~g&6vǍ7ķ>uXd(3瓓[]QTTqnͮz1~_͓k俸0~Z1գ =18cL 5^lf^k^<ҲJɬcC-[^;J8j_q=WpeA_6 4.Ntc>Sv2Jf;G8. 5[,;ArSTˬmpmzjGe EoǩOgDWaGhz<|kT\$Q=u/ci˜S mN&Ok~'0,a} s + NC-G'(*>vw~&*wYG Ŷ K-L/$߮l/A/^:Z@X- Q-D2`@M2+w$Q"胊"47&+Dh'9Y* L7VhT+ -?K]Ik \Ϣgy) s v z)Z ˦2&ލ OjmG9@8F_u䊜r>3K%Yg-FFI]e+Kxkzװy"\Q4Ri'0+P=V&Sw3N/U|UEt*uS c M*tsBE 2ʃ@Kir(˫LRr璜Zy@].%NbXvz덟 hӰNMe#|g͒po9^licxB[e' {U? mlt%?霋ǒxZc X]ϗ15SeE{-Ӕi~DƯO|ë5a@G=%<ƧAs*+tzo, IpȔ|:X6J3Z5JXd]2 3%v*GvE@(S&SX7D0^{5t Z{ﮄsh- ]ɑqEV=^Ki9äBtI@&pEg*O<`F-}ǎ51H,<~qibQѓɳx#l$G9td1U+Sq%B[jOq+^ޏ7K >YY  $KK{*˝e"|$g"6v,,9.DaA,qэI~ܨ|kdv; hz2]x5{M5M~yלqTzUl9Mӏ.WVnkun !jzKO!v|& ;gۇ2BrI閵C tqHe[Zkގ=Q;OԶiᵞBcIU eN cOGz S__>.hNgG6).J$_Taѯ5^LqeB]O?A]H;ò{^0ٺuޚxB|:q'xu4"9Ο7k^eZ_fQOmzm̗{c3ٵKO|m*ek(8"yO(ٵ{LJb2Ǩkgg1_/qrDՆ[_l\ I~Bsc/x ),,̿@PFޞ>O)<<=5m=^x6}~6qoYGޣiY{uN+<,CǚwVxe~c!,5R4u/9In=G•^PF6ɼM򿶤$"\|78ؖYU cXFOKc4s-=6O<;.ϴ޶$q>e? qY}StirX?e/&R'ʑ[ѯMi{?8\g^>\!-VZCf.ȾzRWMh_{^H)mz}V%չM.EJUz7z>ZW6\BW~:W3!S_4~m ǚ! ;VeGKFڵ858Buj:ZZ(/H׭eav!$gpLV)țAJO~YBꤞ厅XJdjg{hR9~_f '5U+}W5%ZjzgTtozYD @%JK\qymeЪKIIp"xoz\B1$G)8Ԅ Jeyc".yyVBR-%BEA-k^Luj cYwԄ%X!e-4ZRḡlJvYsB԰˗0?RM\TlaߏVu4BmY!UyYylgd!m2$i=[hN,6)_~7͖CDF2zÕ{?l;Hܲk׋!/XAłrCXEI{]P[e! ?%Ktqܱ5! jַĞ*TvAG)fuxTҖV7~ 4=r! ob%jTwU$Bnqed䤿@0P&V]HJ)^YrޯĿbsY8=1! n}UD*7uƫi~!s[W{V9J;~Ӯ|[3s۷dڔIj?qJ'O,IkE]G(5\ۖ7)-g,ŶǗ=~e>k쐁%(g˦o[fxN_baGBm:܆VGЗ,G_D!/og,ҢVܤ_iS_~@ SkidSec Webshell

SkidSec WebShell

Server Address : 172.31.38.4

Web Server : Apache/2.4.58 (Ubuntu)

Uname : Linux ip-172-31-38-4 6.14.0-1017-aws #17~24.04.1-Ubuntu SMP Wed Nov 5 10:48:17 UTC 2025 x86_64

PHP Version : 7.4.33



Current Path : /lib/python3/dist-packages/twisted/python/test/



Current File : //lib/python3/dist-packages/twisted/python/test/test_deprecate.py
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Tests for Twisted's deprecation framework, L{twisted.python.deprecate}.
"""


import inspect
import sys
import types
import warnings
from os.path import normcase
from warnings import catch_warnings, simplefilter

try:
    from importlib import invalidate_caches
except ImportError:
    invalidate_caches = None  # type: ignore[assignment]

from incremental import Version

from twisted.python import deprecate
from twisted.python.deprecate import (
    DEPRECATION_WARNING_FORMAT,
    _appendToDocstring,
    _fullyQualifiedName as fullyQualifiedName,
    _getDeprecationDocstring,
    _getDeprecationWarningString,
    _mutuallyExclusiveArguments,
    _passedArgSpec,
    _passedSignature,
    deprecated,
    deprecatedKeywordParameter,
    deprecatedProperty,
    getDeprecationWarningString,
)
from twisted.python.filepath import FilePath
from twisted.python.runtime import platform
from twisted.python.test import deprecatedattributes
from twisted.python.test.modules_helpers import TwistedModulesMixin
from twisted.trial.unittest import SynchronousTestCase

# Note that various tests in this module require manual encoding of paths to
# utf-8. This can be fixed once FilePath supports Unicode; see #2366, #4736,
# #5203.


class _MockDeprecatedAttribute:
    """
    Mock of L{twisted.python.deprecate._DeprecatedAttribute}.

    @ivar value: The value of the attribute.
    """

    def __init__(self, value):
        self.value = value

    def get(self):
        """
        Get a known value.
        """
        return self.value


class ModuleProxyTests(SynchronousTestCase):
    """
    Tests for L{twisted.python.deprecate._ModuleProxy}, which proxies
    access to module-level attributes, intercepting access to deprecated
    attributes and passing through access to normal attributes.
    """

    def _makeProxy(self, **attrs):
        """
        Create a temporary module proxy object.

        @param **kw: Attributes to initialise on the temporary module object

        @rtype: L{twistd.python.deprecate._ModuleProxy}
        """
        mod = types.ModuleType("foo")
        for key, value in attrs.items():
            setattr(mod, key, value)
        return deprecate._ModuleProxy(mod)

    def test_getattrPassthrough(self):
        """
        Getting a normal attribute on a L{twisted.python.deprecate._ModuleProxy}
        retrieves the underlying attribute's value, and raises C{AttributeError}
        if a non-existent attribute is accessed.
        """
        proxy = self._makeProxy(SOME_ATTRIBUTE="hello")
        self.assertIs(proxy.SOME_ATTRIBUTE, "hello")
        self.assertRaises(AttributeError, getattr, proxy, "DOES_NOT_EXIST")

    def test_getattrIntercept(self):
        """
        Getting an attribute marked as being deprecated on
        L{twisted.python.deprecate._ModuleProxy} results in calling the
        deprecated wrapper's C{get} method.
        """
        proxy = self._makeProxy()
        _deprecatedAttributes = object.__getattribute__(proxy, "_deprecatedAttributes")
        _deprecatedAttributes["foo"] = _MockDeprecatedAttribute(42)
        self.assertEqual(proxy.foo, 42)

    def test_privateAttributes(self):
        """
        Private attributes of L{twisted.python.deprecate._ModuleProxy} are
        inaccessible when regular attribute access is used.
        """
        proxy = self._makeProxy()
        self.assertRaises(AttributeError, getattr, proxy, "_module")
        self.assertRaises(AttributeError, getattr, proxy, "_deprecatedAttributes")

    def test_setattr(self):
        """
        Setting attributes on L{twisted.python.deprecate._ModuleProxy} proxies
        them through to the wrapped module.
        """
        proxy = self._makeProxy()
        proxy._module = 1
        self.assertNotEqual(object.__getattribute__(proxy, "_module"), 1)
        self.assertEqual(proxy._module, 1)

    def test_repr(self):
        """
        L{twisted.python.deprecated._ModuleProxy.__repr__} produces a string
        containing the proxy type and a representation of the wrapped module
        object.
        """
        proxy = self._makeProxy()
        realModule = object.__getattribute__(proxy, "_module")
        self.assertEqual(repr(proxy), f"<{type(proxy).__name__} module={realModule!r}>")


class DeprecatedAttributeTests(SynchronousTestCase):
    """
    Tests for L{twisted.python.deprecate._DeprecatedAttribute} and
    L{twisted.python.deprecate.deprecatedModuleAttribute}, which issue
    warnings for deprecated module-level attributes.
    """

    def setUp(self):
        self.version = deprecatedattributes.version
        self.message = deprecatedattributes.message
        self._testModuleName = __name__ + ".foo"

    def _getWarningString(self, attr):
        """
        Create the warning string used by deprecated attributes.
        """
        return _getDeprecationWarningString(
            deprecatedattributes.__name__ + "." + attr,
            deprecatedattributes.version,
            DEPRECATION_WARNING_FORMAT + ": " + deprecatedattributes.message,
        )

    def test_deprecatedAttributeHelper(self):
        """
        L{twisted.python.deprecate._DeprecatedAttribute} correctly sets its
        __name__ to match that of the deprecated attribute and emits a warning
        when the original attribute value is accessed.
        """
        name = "ANOTHER_DEPRECATED_ATTRIBUTE"
        setattr(deprecatedattributes, name, 42)
        attr = deprecate._DeprecatedAttribute(
            deprecatedattributes, name, self.version, self.message
        )

        self.assertEqual(attr.__name__, name)

        # Since we're accessing the value getter directly, as opposed to via
        # the module proxy, we need to match the warning's stack level.
        def addStackLevel():
            attr.get()

        # Access the deprecated attribute.
        addStackLevel()
        warningsShown = self.flushWarnings([self.test_deprecatedAttributeHelper])
        self.assertIs(warningsShown[0]["category"], DeprecationWarning)
        self.assertEqual(warningsShown[0]["message"], self._getWarningString(name))
        self.assertEqual(len(warningsShown), 1)

    def test_deprecatedAttribute(self):
        """
        L{twisted.python.deprecate.deprecatedModuleAttribute} wraps a
        module-level attribute in an object that emits a deprecation warning
        when it is accessed the first time only, while leaving other unrelated
        attributes alone.
        """
        # Accessing non-deprecated attributes does not issue a warning.
        deprecatedattributes.ANOTHER_ATTRIBUTE
        warningsShown = self.flushWarnings([self.test_deprecatedAttribute])
        self.assertEqual(len(warningsShown), 0)

        name = "DEPRECATED_ATTRIBUTE"

        # Access the deprecated attribute. This uses getattr to avoid repeating
        # the attribute name.
        getattr(deprecatedattributes, name)

        warningsShown = self.flushWarnings([self.test_deprecatedAttribute])
        self.assertEqual(len(warningsShown), 1)
        self.assertIs(warningsShown[0]["category"], DeprecationWarning)
        self.assertEqual(warningsShown[0]["message"], self._getWarningString(name))

    def test_wrappedModule(self):
        """
        Deprecating an attribute in a module replaces and wraps that module
        instance, in C{sys.modules}, with a
        L{twisted.python.deprecate._ModuleProxy} instance but only if it hasn't
        already been wrapped.
        """
        sys.modules[self._testModuleName] = mod = types.ModuleType("foo")
        self.addCleanup(sys.modules.pop, self._testModuleName)

        setattr(mod, "first", 1)
        setattr(mod, "second", 2)

        deprecate.deprecatedModuleAttribute(
            Version("Twisted", 8, 0, 0), "message", self._testModuleName, "first"
        )

        proxy = sys.modules[self._testModuleName]
        self.assertNotEqual(proxy, mod)

        deprecate.deprecatedModuleAttribute(
            Version("Twisted", 8, 0, 0), "message", self._testModuleName, "second"
        )

        self.assertIs(proxy, sys.modules[self._testModuleName])


class ImportedModuleAttributeTests(TwistedModulesMixin, SynchronousTestCase):
    """
    Tests for L{deprecatedModuleAttribute} which involve loading a module via
    'import'.
    """

    _packageInit = """\
from twisted.python.deprecate import deprecatedModuleAttribute
from incremental import Version

deprecatedModuleAttribute(
    Version('Package', 1, 2, 3), 'message', __name__, 'module')
"""

    def pathEntryTree(self, tree):
        """
        Create some files in a hierarchy, based on a dictionary describing those
        files.  The resulting hierarchy will be placed onto sys.path for the
        duration of the test.

        @param tree: A dictionary representing a directory structure.  Keys are
            strings, representing filenames, dictionary values represent
            directories, string values represent file contents.

        @return: another dictionary similar to the input, with file content
            strings replaced with L{FilePath} objects pointing at where those
            contents are now stored.
        """

        def makeSomeFiles(pathobj, dirdict):
            pathdict = {}
            for key, value in dirdict.items():
                child = pathobj.child(key)
                if isinstance(value, bytes):
                    pathdict[key] = child
                    child.setContent(value)
                elif isinstance(value, dict):
                    child.createDirectory()
                    pathdict[key] = makeSomeFiles(child, value)
                else:
                    raise ValueError("only strings and dicts allowed as values")
            return pathdict

        base = FilePath(self.mktemp().encode("utf-8"))
        base.makedirs()

        result = makeSomeFiles(base, tree)
        # On Python 3, sys.path cannot include byte paths:
        self.replaceSysPath([base.path.decode("utf-8")] + sys.path)
        self.replaceSysModules(sys.modules.copy())
        return result

    def simpleModuleEntry(self):
        """
        Add a sample module and package to the path, returning a L{FilePath}
        pointing at the module which will be loadable as C{package.module}.
        """
        paths = self.pathEntryTree(
            {
                b"package": {
                    b"__init__.py": self._packageInit.encode("utf-8"),
                    b"module.py": b"",
                }
            }
        )
        return paths[b"package"][b"module.py"]

    def checkOneWarning(self, modulePath):
        """
        Verification logic for L{test_deprecatedModule}.
        """
        from package import module  # type: ignore[import-not-found]

        self.assertEqual(FilePath(module.__file__.encode("utf-8")), modulePath)
        emitted = self.flushWarnings([self.checkOneWarning])
        self.assertEqual(len(emitted), 1)
        self.assertEqual(
            emitted[0]["message"],
            "package.module was deprecated in Package 1.2.3: " "message",
        )
        self.assertEqual(emitted[0]["category"], DeprecationWarning)

    def test_deprecatedModule(self):
        """
        If L{deprecatedModuleAttribute} is used to deprecate a module attribute
        of a package, only one deprecation warning is emitted when the
        deprecated module is imported.
        """
        self.checkOneWarning(self.simpleModuleEntry())

    def test_deprecatedModuleMultipleTimes(self):
        """
        If L{deprecatedModuleAttribute} is used to deprecate a module attribute
        of a package, only one deprecation warning is emitted when the
        deprecated module is subsequently imported.
        """
        mp = self.simpleModuleEntry()
        # The first time, the code needs to be loaded.
        self.checkOneWarning(mp)
        # The second time, things are slightly different; the object's already
        # in the namespace.
        self.checkOneWarning(mp)
        # The third and fourth times, things things should all be exactly the
        # same, but this is a sanity check to make sure the implementation isn't
        # special casing the second time.  Also, putting these cases into a loop
        # means that the stack will be identical, to make sure that the
        # implementation doesn't rely too much on stack-crawling.
        for x in range(2):
            self.checkOneWarning(mp)


class WarnAboutFunctionTests(SynchronousTestCase):
    """
    Tests for L{twisted.python.deprecate.warnAboutFunction} which allows the
    callers of a function to issue a C{DeprecationWarning} about that function.
    """

    def setUp(self):
        """
        Create a file that will have known line numbers when emitting warnings.
        """
        self.package = FilePath(self.mktemp()).child("twisted_private_helper")
        self.package.makedirs()
        self.package.child("__init__.py").setContent(b"")
        self.package.child("module.py").setContent(
            b"""
"A module string"

from twisted.python import deprecate

def testFunction():
    "A doc string"
    a = 1 + 2
    return a

def callTestFunction():
    b = testFunction()
    if b == 3:
        deprecate.warnAboutFunction(testFunction, "A Warning String")
"""
        )
        self.package.child("pep626.py").setContent(
            b"""
"A module string"

from twisted.python import deprecate

def noop():
    pass

def testFunction(a=1, b=1):
    "A doc string"
    if a:
        if b:
            noop()
        else:
            pass

def callTestFunction():
    b = testFunction()
    if b is None:
        deprecate.warnAboutFunction(testFunction, "A Warning String")
"""
        )
        # Python 3 doesn't accept bytes in sys.path:
        packagePath = self.package.parent().path
        sys.path.insert(0, packagePath)
        self.addCleanup(sys.path.remove, packagePath)

        modules = sys.modules.copy()
        self.addCleanup(lambda: (sys.modules.clear(), sys.modules.update(modules)))

        # On Windows, most FilePath interactions produce
        # DeprecationWarnings, so flush them here so that they don't interfere
        # with the tests.
        if platform.isWindows():
            self.flushWarnings()

    def test_warning(self):
        """
        L{deprecate.warnAboutFunction} emits a warning the file and line number
        of which point to the beginning of the implementation of the function
        passed to it.
        """

        def aFunc():
            pass

        deprecate.warnAboutFunction(aFunc, "A Warning Message")
        warningsShown = self.flushWarnings()
        filename = __file__
        if filename.lower().endswith(".pyc"):
            filename = filename[:-1]
        self.assertSamePath(FilePath(warningsShown[0]["filename"]), FilePath(filename))
        self.assertEqual(warningsShown[0]["message"], "A Warning Message")

    def test_warningLineNumber(self):
        """
        L{deprecate.warnAboutFunction} emits a C{DeprecationWarning} with the
        number of a line within the implementation of the function passed to it.
        """
        from twisted_private_helper import module  # type: ignore[import-not-found]

        module.callTestFunction()
        warningsShown = self.flushWarnings()
        self.assertSamePath(
            FilePath(warningsShown[0]["filename"].encode("utf-8")),
            self.package.sibling(b"twisted_private_helper").child(b"module.py"),
        )
        # Line number 9 is the last line in the testFunction in the helper
        # module.
        self.assertEqual(warningsShown[0]["lineno"], 9)
        self.assertEqual(warningsShown[0]["message"], "A Warning String")
        self.assertEqual(len(warningsShown), 1)

    def test_warningLineNumberDisFindlinestarts(self):
        """
        L{deprecate.warnAboutFunction} emits a C{DeprecationWarning} with the
        number of a line within the implementation handling the case in which
        dis.findlinestarts returns the lines in random order.
        """
        from twisted_private_helper import pep626

        pep626.callTestFunction()
        warningsShown = self.flushWarnings()
        self.assertSamePath(
            FilePath(warningsShown[0]["filename"].encode("utf-8")),
            self.package.sibling(b"twisted_private_helper").child(b"pep626.py"),
        )
        # Line number 15 is the last line in the testFunction in the helper
        # module.
        self.assertEqual(warningsShown[0]["lineno"], 15)
        self.assertEqual(warningsShown[0]["message"], "A Warning String")
        self.assertEqual(len(warningsShown), 1)

    def assertSamePath(self, first, second):
        """
        Assert that the two paths are the same, considering case normalization
        appropriate for the current platform.

        @type first: L{FilePath}
        @type second: L{FilePath}

        @raise C{self.failureType}: If the paths are not the same.
        """
        self.assertTrue(
            normcase(first.path) == normcase(second.path),
            f"{first!r} != {second!r}",
        )

    def test_renamedFile(self):
        """
        Even if the implementation of a deprecated function is moved around on
        the filesystem, the line number in the warning emitted by
        L{deprecate.warnAboutFunction} points to a line in the implementation of
        the deprecated function.
        """
        from twisted_private_helper import module

        # Clean up the state resulting from that import; we're not going to use
        # this module, so it should go away.
        del sys.modules["twisted_private_helper"]
        del sys.modules[module.__name__]

        # Rename the source directory
        self.package.moveTo(self.package.sibling(b"twisted_renamed_helper"))

        # Make sure importlib notices we've changed importable packages:
        if invalidate_caches:  # type: ignore[truthy-function]
            invalidate_caches()

        # Import the newly renamed version
        from twisted_renamed_helper import module  # type: ignore[import-not-found]

        self.addCleanup(sys.modules.pop, "twisted_renamed_helper")
        self.addCleanup(sys.modules.pop, module.__name__)

        module.callTestFunction()
        warningsShown = self.flushWarnings([module.testFunction])
        warnedPath = FilePath(warningsShown[0]["filename"].encode("utf-8"))
        expectedPath = self.package.sibling(b"twisted_renamed_helper").child(
            b"module.py"
        )
        self.assertSamePath(warnedPath, expectedPath)
        self.assertEqual(warningsShown[0]["lineno"], 9)
        self.assertEqual(warningsShown[0]["message"], "A Warning String")
        self.assertEqual(len(warningsShown), 1)

    def test_filteredWarning(self):
        """
        L{deprecate.warnAboutFunction} emits a warning that will be filtered if
        L{warnings.filterwarning} is called with the module name of the
        deprecated function.
        """
        # Clean up anything *else* that might spuriously filter out the warning,
        # such as the "always" simplefilter set up by unittest._collectWarnings.
        # We'll also rely on trial to restore the original filters afterwards.
        del warnings.filters[:]

        warnings.filterwarnings(action="ignore", module="twisted_private_helper")

        from twisted_private_helper import module

        module.callTestFunction()

        warningsShown = self.flushWarnings()
        self.assertEqual(len(warningsShown), 0)

    def test_filteredOnceWarning(self):
        """
        L{deprecate.warnAboutFunction} emits a warning that will be filtered
        once if L{warnings.filterwarning} is called with the module name of the
        deprecated function and an action of once.
        """
        # Clean up anything *else* that might spuriously filter out the warning,
        # such as the "always" simplefilter set up by unittest._collectWarnings.
        # We'll also rely on trial to restore the original filters afterwards.
        del warnings.filters[:]

        warnings.filterwarnings(action="module", module="twisted_private_helper")

        from twisted_private_helper import module

        module.callTestFunction()
        module.callTestFunction()

        warningsShown = self.flushWarnings()
        self.assertEqual(len(warningsShown), 1)
        message = warningsShown[0]["message"]
        category = warningsShown[0]["category"]
        filename = warningsShown[0]["filename"]
        lineno = warningsShown[0]["lineno"]
        msg = warnings.formatwarning(message, category, filename, lineno)
        self.assertTrue(
            msg.endswith(
                "module.py:9: DeprecationWarning: A Warning String\n" "  return a\n"
            ),
            f"Unexpected warning string: {msg!r}",
        )


def dummyCallable():
    """
    Do nothing.

    This is used to test the deprecation decorators.
    """


def dummyReplacementMethod():
    """
    Do nothing.

    This is used to test the replacement parameter to L{deprecated}.
    """


class DeprecationWarningsTests(SynchronousTestCase):
    def test_getDeprecationWarningString(self):
        """
        L{getDeprecationWarningString} returns a string that tells us that a
        callable was deprecated at a certain released version of Twisted.
        """
        version = Version("Twisted", 8, 0, 0)
        self.assertEqual(
            getDeprecationWarningString(self.test_getDeprecationWarningString, version),
            "%s.DeprecationWarningsTests.test_getDeprecationWarningString "
            "was deprecated in Twisted 8.0.0" % (__name__,),
        )

    def test_getDeprecationWarningStringWithFormat(self):
        """
        L{getDeprecationWarningString} returns a string that tells us that a
        callable was deprecated at a certain released version of Twisted, with
        a message containing additional information about the deprecation.
        """
        version = Version("Twisted", 8, 0, 0)
        format = DEPRECATION_WARNING_FORMAT + ": This is a message"
        self.assertEqual(
            getDeprecationWarningString(
                self.test_getDeprecationWarningString, version, format
            ),
            "%s.DeprecationWarningsTests.test_getDeprecationWarningString was "
            "deprecated in Twisted 8.0.0: This is a message" % (__name__,),
        )

    def test_deprecateEmitsWarning(self):
        """
        Decorating a callable with L{deprecated} emits a warning.
        """
        version = Version("Twisted", 8, 0, 0)
        dummy = deprecated(version)(dummyCallable)

        def addStackLevel():
            dummy()

        with catch_warnings(record=True) as caught:
            simplefilter("always")
            addStackLevel()
            self.assertEqual(caught[0].category, DeprecationWarning)
            self.assertEqual(
                str(caught[0].message),
                getDeprecationWarningString(dummyCallable, version),
            )
            # rstrip in case .pyc/.pyo
            self.assertEqual(caught[0].filename.rstrip("co"), __file__.rstrip("co"))

    def test_deprecatedPreservesName(self):
        """
        The decorated function has the same name as the original.
        """
        version = Version("Twisted", 8, 0, 0)
        dummy = deprecated(version)(dummyCallable)
        self.assertEqual(dummyCallable.__name__, dummy.__name__)
        self.assertEqual(fullyQualifiedName(dummyCallable), fullyQualifiedName(dummy))

    def test_getDeprecationDocstring(self):
        """
        L{_getDeprecationDocstring} returns a note about the deprecation to go
        into a docstring.
        """
        version = Version("Twisted", 8, 0, 0)
        self.assertEqual(
            "Deprecated in Twisted 8.0.0.", _getDeprecationDocstring(version, "")
        )

    def test_deprecatedUpdatesDocstring(self):
        """
        The docstring of the deprecated function is appended with information
        about the deprecation.
        """

        def localDummyCallable():
            """
            Do nothing.

            This is used to test the deprecation decorators.
            """

        version = Version("Twisted", 8, 0, 0)
        dummy = deprecated(version)(localDummyCallable)

        _appendToDocstring(localDummyCallable, _getDeprecationDocstring(version, ""))

        self.assertEqual(localDummyCallable.__doc__, dummy.__doc__)

    def test_versionMetadata(self):
        """
        Deprecating a function adds version information to the decorated
        version of that function.
        """
        version = Version("Twisted", 8, 0, 0)
        dummy = deprecated(version)(dummyCallable)
        self.assertEqual(version, dummy.deprecatedVersion)

    def test_getDeprecationWarningStringReplacement(self):
        """
        L{getDeprecationWarningString} takes an additional replacement parameter
        that can be used to add information to the deprecation.  If the
        replacement parameter is a string, it will be interpolated directly into
        the result.
        """
        version = Version("Twisted", 8, 0, 0)
        warningString = getDeprecationWarningString(
            self.test_getDeprecationWarningString,
            version,
            replacement="something.foobar",
        )
        self.assertEqual(
            warningString,
            "%s was deprecated in Twisted 8.0.0; please use something.foobar "
            "instead" % (fullyQualifiedName(self.test_getDeprecationWarningString),),
        )

    def test_getDeprecationWarningStringReplacementWithCallable(self):
        """
        L{getDeprecationWarningString} takes an additional replacement parameter
        that can be used to add information to the deprecation. If the
        replacement parameter is a callable, its fully qualified name will be
        interpolated into the result.
        """
        version = Version("Twisted", 8, 0, 0)
        warningString = getDeprecationWarningString(
            self.test_getDeprecationWarningString,
            version,
            replacement=dummyReplacementMethod,
        )
        self.assertEqual(
            warningString,
            "%s was deprecated in Twisted 8.0.0; please use "
            "%s.dummyReplacementMethod instead"
            % (fullyQualifiedName(self.test_getDeprecationWarningString), __name__),
        )


@deprecated(Version("Twisted", 1, 2, 3))
class DeprecatedClass:
    """
    Class which is entirely deprecated without having a replacement.
    """


class ClassWithDeprecatedProperty:
    """
    Class with a single deprecated property.
    """

    _someProtectedValue = None

    @deprecatedProperty(Version("Twisted", 1, 2, 3))
    def someProperty(self):
        """
        Getter docstring.

        @return: The property.
        """
        return self._someProtectedValue

    @someProperty.setter  # type: ignore[no-redef]
    def someProperty(self, value):
        """
        Setter docstring.
        """
        self._someProtectedValue = value


@deprecatedKeywordParameter(Version("Twisted", 19, 2, 0), "foo")
def functionWithDeprecatedParameter(a, b, c=1, foo=2, bar=3):
    """
    Function with a deprecated keyword parameter.
    """


class DeprecatedDecoratorTests(SynchronousTestCase):
    """
    Tests for deprecated decorators.
    """

    def assertDocstring(self, target, expected):
        """
        Check that C{target} object has the C{expected} docstring lines.

        @param target: Object which is checked.
        @type target: C{anything}

        @param expected: List of lines, ignoring empty lines or leading or
            trailing spaces.
        @type expected: L{list} or L{str}
        """
        self.assertEqual(
            expected, [x.strip() for x in target.__doc__.splitlines() if x.strip()]
        )

    def test_propertyGetter(self):
        """
        When L{deprecatedProperty} is used on a C{property}, accesses raise a
        L{DeprecationWarning} and getter docstring is updated to inform the
        version in which it was deprecated. C{deprecatedVersion} attribute is
        also set to inform the deprecation version.
        """
        obj = ClassWithDeprecatedProperty()

        obj.someProperty

        self.assertDocstring(
            ClassWithDeprecatedProperty.someProperty,
            [
                "Getter docstring.",
                "@return: The property.",
                "Deprecated in Twisted 1.2.3.",
            ],
        )
        ClassWithDeprecatedProperty.someProperty.deprecatedVersion = Version(
            "Twisted", 1, 2, 3
        )

        message = (
            "twisted.python.test.test_deprecate.ClassWithDeprecatedProperty."
            "someProperty was deprecated in Twisted 1.2.3"
        )
        warnings = self.flushWarnings([self.test_propertyGetter])
        self.assertEqual(1, len(warnings))
        self.assertEqual(DeprecationWarning, warnings[0]["category"])
        self.assertEqual(message, warnings[0]["message"])

    def test_propertySetter(self):
        """
        When L{deprecatedProperty} is used on a C{property}, setter accesses
        raise a L{DeprecationWarning}.
        """
        newValue = object()
        obj = ClassWithDeprecatedProperty()

        obj.someProperty = newValue

        self.assertIs(newValue, obj._someProtectedValue)
        message = (
            "twisted.python.test.test_deprecate.ClassWithDeprecatedProperty."
            "someProperty was deprecated in Twisted 1.2.3"
        )
        warnings = self.flushWarnings([self.test_propertySetter])
        self.assertEqual(1, len(warnings))
        self.assertEqual(DeprecationWarning, warnings[0]["category"])
        self.assertEqual(message, warnings[0]["message"])

    def test_class(self):
        """
        When L{deprecated} is used on a class, instantiations raise a
        L{DeprecationWarning} and class's docstring is updated to inform the
        version in which it was deprecated. C{deprecatedVersion} attribute is
        also set to inform the deprecation version.
        """
        DeprecatedClass()

        self.assertDocstring(
            DeprecatedClass,
            [
                ("Class which is entirely deprecated without having a " "replacement."),
                "Deprecated in Twisted 1.2.3.",
            ],
        )
        DeprecatedClass.deprecatedVersion = Version("Twisted", 1, 2, 3)

        message = (
            "twisted.python.test.test_deprecate.DeprecatedClass "
            "was deprecated in Twisted 1.2.3"
        )
        warnings = self.flushWarnings([self.test_class])
        self.assertEqual(1, len(warnings))
        self.assertEqual(DeprecationWarning, warnings[0]["category"])
        self.assertEqual(message, warnings[0]["message"])

    def test_deprecatedReplacement(self):
        """
        L{deprecated} takes an additional replacement parameter that can be used
        to indicate the new, non-deprecated method developers should use.  If
        the replacement parameter is a string, it will be interpolated directly
        into the warning message.
        """
        version = Version("Twisted", 8, 0, 0)
        dummy = deprecated(version, "something.foobar")(dummyCallable)
        self.assertEqual(
            dummy.__doc__,
            "\n"
            "    Do nothing.\n\n"
            "    This is used to test the deprecation decorators.\n\n"
            "    Deprecated in Twisted 8.0.0; please use "
            "something.foobar"
            " instead.\n"
            "    ",
        )

    def test_deprecatedReplacementWithCallable(self):
        """
        L{deprecated} takes an additional replacement parameter that can be used
        to indicate the new, non-deprecated method developers should use.  If
        the replacement parameter is a callable, its fully qualified name will
        be interpolated into the warning message.
        """
        version = Version("Twisted", 8, 0, 0)
        decorator = deprecated(version, replacement=dummyReplacementMethod)
        dummy = decorator(dummyCallable)
        self.assertEqual(
            dummy.__doc__,
            "\n"
            "    Do nothing.\n\n"
            "    This is used to test the deprecation decorators.\n\n"
            "    Deprecated in Twisted 8.0.0; please use "
            "%s.dummyReplacementMethod instead.\n"
            "    " % (__name__,),
        )

    def test_deprecatedKeywordParameter(self):
        message = (
            "The 'foo' parameter to "
            "twisted.python.test.test_deprecate."
            "functionWithDeprecatedParameter "
            "was deprecated in Twisted 19.2.0"
        )

        with catch_warnings(record=True) as ws:
            simplefilter("always")

            functionWithDeprecatedParameter(10, 20)
            self.assertEqual(ws, [])

            functionWithDeprecatedParameter(10, 20, 30)
            self.assertEqual(ws, [])

            functionWithDeprecatedParameter(10, 20, foo=40)
            self.assertEqual(len(ws), 1)
            self.assertEqual(ws[0].category, DeprecationWarning)
            self.assertEqual(str(ws[0].message), message)

            ws.clear()
            functionWithDeprecatedParameter(10, 20, bar=50)
            self.assertEqual(ws, [])

            functionWithDeprecatedParameter(10, 20, 30, 40)
            self.assertEqual(len(ws), 1)
            self.assertEqual(ws[0].category, DeprecationWarning)
            self.assertEqual(str(ws[0].message), message)


class AppendToDocstringTests(SynchronousTestCase):
    """
    Test the _appendToDocstring function.

    _appendToDocstring is used to add text to a docstring.
    """

    def test_appendToEmptyDocstring(self):
        """
        Appending to an empty docstring simply replaces the docstring.
        """

        def noDocstring():
            pass

        _appendToDocstring(noDocstring, "Appended text.")
        self.assertEqual("Appended text.", noDocstring.__doc__)

    def test_appendToSingleLineDocstring(self):
        """
        Appending to a single line docstring places the message on a new line,
        with a blank line separating it from the rest of the docstring.

        The docstring ends with a newline, conforming to Twisted and PEP 8
        standards. Unfortunately, the indentation is incorrect, since the
        existing docstring doesn't have enough info to help us indent
        properly.
        """

        def singleLineDocstring():
            """This doesn't comply with standards, but is here for a test."""

        _appendToDocstring(singleLineDocstring, "Appended text.")
        self.assertEqual(
            [
                "This doesn't comply with standards, but is here for a test.",
                "",
                "Appended text.",
            ],
            singleLineDocstring.__doc__.splitlines(),
        )
        self.assertTrue(singleLineDocstring.__doc__.endswith("\n"))

    def test_appendToMultilineDocstring(self):
        """
        Appending to a multi-line docstring places the messade on a new line,
        with a blank line separating it from the rest of the docstring.

        Because we have multiple lines, we have enough information to do
        indentation.
        """

        def multiLineDocstring():
            """
            This is a multi-line docstring.
            """

        def expectedDocstring():
            """
            This is a multi-line docstring.

            Appended text.
            """

        _appendToDocstring(multiLineDocstring, "Appended text.")
        self.assertEqual(expectedDocstring.__doc__, multiLineDocstring.__doc__)


class MutualArgumentExclusionTests(SynchronousTestCase):
    """
    Tests for L{mutuallyExclusiveArguments}.
    """

    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 will be inspected.
        @type func: A callable.

        @param args: The arguments which could be passed to C{func}.

        @param kw: The keyword arguments which could be passed to C{func}.

        @return: L{_passedSignature} or L{_passedArgSpec}'s return value
        @rtype: L{dict}
        """
        if getattr(inspect, "signature", None):
            # Python 3
            return _passedSignature(inspect.signature(func), args, kw)
        else:
            # Python 2
            return _passedArgSpec(inspect.getargspec(func), args, kw)

    def test_passed_simplePositional(self):
        """
        L{passed} identifies the arguments passed by a simple
        positional test.
        """

        def func(a, b):
            pass

        self.assertEqual(self.checkPassed(func, 1, 2), dict(a=1, b=2))

    def test_passed_tooManyArgs(self):
        """
        L{passed} raises a L{TypeError} if too many arguments are
        passed.
        """

        def func(a, b):
            pass

        self.assertRaises(TypeError, self.checkPassed, func, 1, 2, 3)

    def test_passed_doublePassKeyword(self):
        """
        L{passed} raises a L{TypeError} if a argument is passed both
        positionally and by keyword.
        """

        def func(a):
            pass

        self.assertRaises(TypeError, self.checkPassed, func, 1, a=2)

    def test_passed_unspecifiedKeyword(self):
        """
        L{passed} raises a L{TypeError} if a keyword argument not
        present in the function's declaration is passed.
        """

        def func(a):
            pass

        self.assertRaises(TypeError, self.checkPassed, func, 1, z=2)

    def test_passed_star(self):
        """
        L{passed} places additional positional arguments into a tuple
        under the name of the star argument.
        """

        def func(a, *b):
            pass

        self.assertEqual(self.checkPassed(func, 1, 2, 3), dict(a=1, b=(2, 3)))

    def test_passed_starStar(self):
        """
        Additional keyword arguments are passed as a dict to the star star
        keyword argument.
        """

        def func(a, **b):
            pass

        self.assertEqual(
            self.checkPassed(func, 1, x=2, y=3, z=4), dict(a=1, b=dict(x=2, y=3, z=4))
        )

    def test_passed_noDefaultValues(self):
        """
        The results of L{passed} only include arguments explicitly
        passed, not default values.
        """

        def func(a, b, c=1, d=2, e=3):
            pass

        self.assertEqual(self.checkPassed(func, 1, 2, e=7), dict(a=1, b=2, e=7))

    def test_mutualExclusionPrimeDirective(self):
        """
        L{mutuallyExclusiveArguments} does not interfere in its
        decoratee's operation, either its receipt of arguments or its return
        value.
        """

        @_mutuallyExclusiveArguments([("a", "b")])
        def func(x, y, a=3, b=4):
            return x + y + a + b

        self.assertEqual(func(1, 2), 10)
        self.assertEqual(func(1, 2, 7), 14)
        self.assertEqual(func(1, 2, b=7), 13)

    def test_mutualExclusionExcludesByKeyword(self):
        """
        L{mutuallyExclusiveArguments} raises a L{TypeError}n if its
        decoratee is passed a pair of mutually exclusive arguments.
        """

        @_mutuallyExclusiveArguments([["a", "b"]])
        def func(a=3, b=4):
            return a + b

        self.assertRaises(TypeError, func, a=3, b=4)

    def test_invalidParameterType(self):
        """
        Create a fake signature with an invalid parameter
        type to test error handling.  The valid parameter
        types are specified in L{inspect.Parameter}.
        """

        class FakeSignature:
            def __init__(self, parameters):
                self.parameters = parameters

        class FakeParameter:
            def __init__(self, name, kind):
                self.name = name
                self.kind = kind

        def func(a, b):
            pass

        func(1, 2)
        parameters = inspect.signature(func).parameters
        dummyParameters = parameters.copy()
        dummyParameters["c"] = FakeParameter("fake", "fake")
        fakeSig = FakeSignature(dummyParameters)
        self.assertRaises(TypeError, _passedSignature, fakeSig, (1, 2), {})


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)