ϪfhdZddlmZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z ddlmZmZmZddlmZddlmZdd lmZdd lmZmZmZmZdd lmZed ZGd deZ GddeZ!GddejDZ#dZ$ddZ% ddZ&GddeZ'GddejDZ(GddejDZ)GddejDZ*y) z" Tests for Twisted plugin system. ) annotationsN)invalidate_caches) ModuleType)Callable TypedDictTypeVar) Interface)plugin)FilePath) EventDict addObserverremoveObservertextFromEventDict)unittest_TceZdZdZy) ITestPluginzS A plugin for use by the plugin system's unit tests. Do not use this. N__name__ __module__ __qualname____doc__:/usr/lib/python3/dist-packages/twisted/test/test_plugin.pyrrsrrceZdZdZy) ITestPlugin2z See L{ITestPlugin}. Nrrrrrr&srrceZdZdZddZddZ d ddZddZ ddZeddZ ddZ edd Z edd Z edd Z edd Zedd ZeddZddZy) PluginTestsz_ Tests which verify the behavior of the current, active Twisted plugins directory. ctjdd|_tjj |_t |j|_|jj|jjd|_ |jj|jjdjdt tjdj|jjdd|_tjj#d|jjddl}||_y) zV Save C{sys.path} and C{sys.modules}, and create a package for tests. N mypackage __init__.pyrzplugin_basic.pyz testplugin.py testpluginr)syspath originalPathmodulescopy savedModulesr mktemprootcreateDirectorychildpackage setContent__file__siblingcopyTooriginalPlugininsertr!module)selfr!s rsetUpzPluginTests.setUp2s HHQKKK,,.T[[]+  !!#yy{3  $$& =)44S9""#45<< LL   / + 499>>* rc|jtjddtjj tjj |j yzR Restore C{sys.path} and C{sys.modules} to their original values. Nr&r$r%r'clearupdater)r6s rtearDownzPluginTests.tearDownJ?''   4,,-rc|jJ|jjd}dj|dd}|d}t t j ||t j |j=ddg|xrdgxsgzD]%} tj|j|z'y#t$rY4wxYw)N.co) r0rsplitjoindelattrr$r'osremoveFileNotFoundError)r6r5 deleteSource modulePath packageName moduleNameexts r_unimportPythonModulez!PluginTests._unimportPythonModuleRs***__**3/ hhz#2/ ^  K(*5 KK (:!62$!<"= C  &//C/0 %  s"B44 C?CcV|jjdjy)z; Remove the plugins B{droping.cache} file. dropin.cacheN)r.r-rJr=s r _clearCachezPluginTests._clearCachebs >*113rcDtjdfd }|S)z This is a paranoid test wrapper, that calls C{meth} 2 times, clear the cache, and calls it 2 other times. It's supposed to ensure that the plugin system behaves correctly no matter what the state of the cache is. cf|||j||y)N)rT)r6meths rwrappedz+PluginTests._withCacheness..wrappedrs+ J J     J Jr)r6rreturnNone) functoolswraps)rWrXs` r_withCachenesszPluginTests._withCachenesshs'     rcPtj|j}||j}|j |j d|j|j d|j|jDcgc]}t|jvs|c}d}|j|j||j |jd|j |jjd|j |jttjg|j!}|j|t"j$d|jj&ddlm}|j||j&ycc}w)a  Check that the cache returned by L{plugin.getCache} hold the plugin B{testplugin}, and that this plugin has the properties we expect: provide L{TestPlugin}, has the good name and description, and can be loaded successfully. z mypackage.zI'm a test drop-in.r TestPluginz*A plugin used solely for testing purposes.N)r getCacher5r3 assertEqualrOassertIn descriptionpluginsrprovidedassertIsdropinnamestripIPluginloadr$r'r_mypackage.testpluginr#)r6cachergpp1 realPlugintps r test_cachezPluginTests.test_cache|sD ,t**+ **j9L9L8M,NO +V-?-?@  EA;!**+Da Ea H bii( ,/  NN "$P  {FNN&CDWWY   KK*T%8%8$9: ; F F * j"--0)Fs F#F#ctj|j|j}t d|j Dd}|j t|dy)zm L{CachedPlugin} has a helpful C{repr} which contains relevant information about it. c3@K|]}|jdk(s|yw)r_Nrh).0rns r z-PluginTests.test_cacheRepr..sV!qvv?UAVsrzTN)r r`r5r3listrdrarepr)r6 cachedDropin cachedPlugins rtest_cacheReprzPluginTests.test_cacheReprs\ t{{3D4G4GH V|';';VV      1 rcttjt|j}|j t |dddg}|D]-}|j|j|j/y)a L{plugin.getPlugins} should return the list of plugins matching the specified interface (here, L{ITestPlugin2}), and these plugins should be instances of classes with a C{test} method, to be sure L{plugin.getPlugins} load classes correctly. AnotherTestPluginThirdTestPluginN) rxr getPluginsrr5ralenrJrtest)r6rdnamesrns r test_pluginszPluginTests.test_pluginssfv((t{{CD Wq)$&78 A LL $ FFH rcttjdj|jj d |j dtj|jttjdddttjt|j}|j!t#|ddd g}|D]-}|j%|j&|j)/ |j+tjdd y #|j+tjdd wxYw) ze Check that L{plugin.getPlugins} is able to detect plugins added at runtime. plugin_extra1.pypluginextra.pymypackage.pluginextrar! pluginextraz&mypackage still has pluginextra moduler~r_FourthTestPluginTN)r r0r1r2r.r-failIfInr$r' assertFalsehasattrrxr rrr5rarrJrtest1rQr6plgsrrns rtest_detectNewFileszPluginTests.test_detectNewFiless ""#56== LL  / 0  S MM13;; ?    K0-@8  ))+t{{CDD   SY *!#56E  QZZ(    & &s{{3J'KT RD & &s{{3J'KT Rs C D88%Ecttjdj|jj d t tjt|j}|jt|dttjdj|jj d|jtjdt tjt|j}|jt|dgd}|D]-}|j!|j"|j%/ |jtjddy #|jtjddwxYw) z Check that if the content of a plugin change, L{plugin.getPlugins} is able to detect the new plugins added. rrr~zplugin_extra2.pyr)r_rFifthTestPluginTN)r r0r1r2r.r-rxr rrr5rarrQr$r'rJrrrs rtest_detectFilesChangedz#PluginTests.test_detectFilesChangedsL ""#56== LL  / 0  S))+t{{CDD   SY * X  & &'9 : A A ""#34   & &s{{3J'K L))+t{{CDD   SY *IE  QZZ(    & &s{{3J'KT RD & &s{{3J'KT Rs D.F%Gcttjdj|jj d t tjt|j|jtjddt tjt|j}|jdt|y#|jtjddwxYw)zs Check that when a dropin file is removed, L{plugin.getPlugins} doesn't return it anymore. rrrTN)r r0r1r2r.r-rxr rrr5rQr$r'rar)r6rs rtest_detectFilesRemovedz#PluginTests.test_detectFilesRemoveds ""#56== LL  / 0  S ""; < =  & &s{{3J'KT RF%%k4;;?@ CI&  & &s{{3J'KT Rs -C!!%Dc|j}|jtjj ||j j j| ttjt|j }|jt|d|j j j|y#|j j j|wxYw)zy Test that getCache skips over any entries in a plugin package's C{__path__} which do not exist. rN)r*rrIr%existsr5__path__appendrxr rrrarrJ)r6r%rs rtest_nonexistentPathEntryz%PluginTests.test_nonexistentPathEntrys {{} -. ##D) .))+t{{CDD   SY * KK ' ' -DKK ' ' -s %AC'C:cDt|j}|j|j|j |j dj }|jjj| ttjt|j}|jt|d|jjj!|y#|jjj!|wxYw)z Test that getCache skips over any entries in a plugin package's C{__path__} which refer to children of paths which are not directories. test_packagerN)r r*rrtouchr-r%r5rrrxr rrrarrJ)r6r%r-rs rtest_nonDirectoryChildEntryz'PluginTests.test_nonDirectoryChildEntry"s  & '  >*// ##E* /))+t{{CDD   SY * KK ' ' .DKK ' ' .s AC88'Dctj|j|jj d}t t jdj|jj dttj|jjdtj|jd|jtj|jjd|jtj|jdg}t|j|jt |jtj|j}|j#d||j#|j$|d|jt&j(fz}|D]}t+|}|J||vsy |j-d |y ) z The C{dropin.cache} file may not be writable: the cache should still be attainable, but an error should be logged to show that the cache couldn't be updated. rSrri@ir3Unable to write to plugin cache %s: error number %dN9Did not observe unwriteable cache warning in log events: )r r`r5r.r-r r0r1r2invalidateImportCachesrIchmodr% addCleanupr rrrbr3errnoEPERMrfail)r6 cachepatheventsrmexpectedevent maybeTexts rtest_deployedModezPluginTests.test_deployedMode3s  $LL&&~6  ""#56== LL  / 0    ""E* ' $,,"3"3U; )..%8#%FMM"  6 , mU+ d))51I NN KKL   E)%0I( ((9$  II &) rNrYrZ)F)r5rrLboolrYrZ)rWzCallable[[PluginTests], object]rYzCallable[[PluginTests], None])rrrrr7r>rQrTr]rrr|rrrrrrrrrrrr,s  0.8= 04  4 - &("1"1H   SS:SS@''" . .// /rrsj from twisted.plugin import pluginPackagePaths __path__.extend(pluginPackagePaths(__name__)) __all__ = [] cBdj|jdS)Nzfrom zope.interface import provider from twisted.plugin import IPlugin from twisted.test.test_plugin import ITestPlugin @provider(IPlugin, ITestPlugin) class {}: pass ascii)formatencoderus rpluginFileContentsrms     rc|j|jd}|j|r |jdjd|jd}|j|r$|jdjt|j|dzj||S)z' Create a plugindummy package. plugindummyr"rrd.py)r,r-r/pluginInitFile) entrypath pluginContentreal pluginModulepkgplugss r_createPluginDummyr}s  //- (C  - ++C0 IIi E   M"--n= KK u$%00? LrceZdZUded<y)_HasBoolLegacyKeyrlegacyN)rrr__annotations__rrrrrs Lrrc`eZdZdZd dZd dZd dZddZd dZd dZ d dZ d d Z d d Z d d Z y )DeveloperSetupTestsz These tests verify things about the plugin system without actually interacting with the deployed 'twisted.plugins' package, instead creating a temporary package. c:tjdd|_tjj |_t |j|_|jj|jjd|_ |jjd|_ |jjd|_ t|jtddd|_t|jtddd|_t|jtd d d |_tjj'|j|jfDcgc]}|jc}|j)|jjd jd |_|j*jd|_t/j.}t1j2|j*jdj|dz fdzt1j2|j,j|dz fdz|j5|j7ycc}w)a7 Create a complex environment with multiple entries on sys.path, akin to a developer's environment who has a development (trunk) checkout of Twisted, a system installed version of Twisted (for their operating system's tools) and a project which provides Twisted plugins. N system_pathdevelopment_pathapplication_pathsystemTplugindummy_builtindevappFplugindummy_apprrdrSzplugindummy_builtin.pyir~i)r$r% savedPathr'r(r)r r*fakeRootr,r- systemPathdevPathappPathrr systemPackage devPackage appPackageextend getAllPluginssysplugsyscachetimerIutime lockSystemresetEnvironment)r6xnows rr7zDeveloperSetupTests.setUps!KK,,. /  %%'----m<}}**+=> }}**+=> / OO/94AV - LL,U3T;P - LL,U3U: iik ##$<=BBS4ZMTUDUV ##cDj]Q%67  Js9Jctj|jjdtj|jjdy)zW Lock the system directories, as if they were unwritable by this user. imNrIrrr%rr=s rrzDeveloperSetupTests.lockSystem4 ""E* ##U+rctj|jjdtj|jjdy)zW Unlock the system directories, as if they were writable by this user. iNrr=s r unlockSystemz DeveloperSetupTests.unlockSystemrrcddl}ttjt|j }|Dcgc]}|j c}Scc}w)zl Get all the plugins loadable from our dummy package, and return their short names. rN)plugindummy.pluginsrxr rrrdr)r6rrplugs rrz!DeveloperSetupTests.getAllPluginss< # ""; 0C0CD E*+,$ ,,,sA c|jtjj|j|j |j fDcgc]}|jc}ycc}w)zc Change the environment to what it should be just as the test is starting. N)unsetEnvironmentr$r%rrrr)r6rs rrz$DeveloperSetupTests.resetEnvironmentsE  $,,)VWAWXWsA*cttjjtjj |j |j tjddy)zh Change the Python environment back to what it was before the test was started. N)rr$r'r;r<r)rr%r=s rrz$DeveloperSetupTests.unsetEnvironmentsC    4,,-nn rcD|j|jy)z Reset the Python environment to what it was before this test ran, and restore permissions on files which were marked read-only so that the directory may be cleanly cleaned up. N)rrr=s rr>zDeveloperSetupTests.tearDowns  rctdD]6}|j}|j|j|ddg8y)a Plugins added in the development path should be loadable, even when the (now non-importable) system path contains its own idea of the list of plugins for a package. Inversely, plugins added in the system path should not be available. rrrN)rangersortra)r6rrs r"test_developmentPluginAvailabilityz6DeveloperSetupTests.test_developmentPluginAvailabilitysBq 4A&&(E JJL   UUEN 3 4rcJ|jjd}|jtdt jdz }t j |j||f|jd}td}tj|jjfddi|t j |j||f|j|j|jd|j|j!d |j|j|jtd |j!d|j|jd |jy ) z Verify that if a stale .pyc file on the PYTHONPATH is replaced by a fresh .py file, the plugins in the new .py are picked up rather than the stale .pyc, even if the .pyc is still around. zstale.pyoneiz stale.pycT)rquietrtwoN)rr-r/rrrIrr%r1r compileall compile_dirrJrrbrr)r6mypathrpycextras rtest_freshPyReplacesStalePycz0DeveloperSetupTests.test_freshPyReplacesStalePyc s5 &&z2,U34 IIK$  q!f%nn[) ".t33F1FF Aq6"   eT//12 eT//12 ,U34 eT//12 eT//12rcp|j|jjdjt d|j t jj|jjg}t|j|jt|j|jd|jd|j jt"j$fz}|D]}t'|}|J||vsy|j)d|y)aF Verify that a failure to write the dropin.cache file on a read-only path will not affect the list of plugins returned. Note: this test should pass on both Linux and Windows, but may not provide useful coverage on Windows due to the different meaning of "read-only directory". z newstuff.pyrrNr)rrr-r/rrr$r%rJrr rrrrbrrrrrr)r6rrrrs rtest_newPluginsOnReadOnlyPathz1DeveloperSetupTests.test_newPluginsOnReadOnlyPath,s  =)445G5NO   ))*#%FMM"  6 eT//12I MM   KKL   E)%0I( ((9$  II &) rNr)rYz list[str])rrrrr7rrrrrr>rrrrrrrrs< ( T,, -Y%  43@&rrcHeZdZdZddZddZ d dZddZddZy) AdjacentPackageTestsz Tests for the behavior of the plugin system when there are multiple installed copies of the package containing the plugins being loaded. cztjdd|_tjj |_y)zS Save the elements of C{sys.path} and the items of C{sys.modules}. N)r$r%r&r'r(r)r=s rr7zAdjacentPackageTests.setUp[s) HHQKKK,,.rc|jtjddtjj tjj |j yr9r:r=s rr>zAdjacentPackageTests.tearDownbr?rc|j|}|jd}|j|jdjd|jd}|j|jdjt|j|dz}|jt ||S)a` Create a directory containing a Python package named I{dummy} with a I{plugins} subpackage. @type root: L{FilePath} @param root: The directory in which to create the hierarchy. @type name: C{str} @param name: The name of the directory to create which will contain the package. @type pluginName: C{str} @param pluginName: The name of a module to create in the I{dummy.plugins} package. @rtype: L{FilePath} @return: The directory which was created to contain the I{dummy} package. dummyr"rrdr)r-makedirsr/rr)r6r+rh pluginName directoryr.rdrs rcreateDummyPackagez'AdjacentPackageTests.createDummyPackagejs,JJt$ //'* m$//4-- * m$//?}}Z%%78  24 89rct|j}|j|j|dd}|j|dd}tj j |j tj j |j ddl}ttjt|j}|jdg|Dcgc]}|jc}ycc}w)z Only plugins from the first package in sys.path should be returned by getPlugins in the case where there are two Python packages by the same name installed, each with a plugin module by a single name. first somepluginsecondrNr r*rr r$r%r dummy.pluginsrxr rrrdrarr6r+firstDirectorysecondDirectoryrrdrns r.test_hiddenPackageSamePluginModuleNameObscuredzCAdjacentPackageTests.test_hiddenPackageSamePluginModuleNameObscured  & 00w M11$,O ++, ,,-v((emmDE '$AAQZZ$AB$A$C? ct|j}|j|j|dd}|j|dd}tj j |j tj j |j ddl}ttjt|j}|jdg|Dcgc]}|jc}ycc}w)z Plugins from the first package in sys.path should be returned by getPlugins in the case where there are two Python packages by the same name installed, each with a plugin module by a different name. r  thispluginr thatpluginrNrrs r3test_hiddenPackageDifferentPluginModuleNameObscuredzHAdjacentPackageTests.test_hiddenPackageDifferentPluginModuleNameObscuredrrNr)r+ FilePath[str]rhstrr rrYr) rrrrr7r>r rrrrrrrUsC /.!),:= BC(Crrc0eZdZdZddZddZddZddZy)PackagePathTestszg Tests for L{plugin.pluginPackagePaths} which constructs search paths for plugin packages. c4tjdd|_y)z3 Save the elements of C{sys.path}. N)r$r%r&r=s rr7zPackagePathTests.setUps HHQKrc>|jtjddy)z< Restore C{sys.path} to its original value. N)r&r$r%r=s rr>zPackagePathTests.tearDowns'' rc`td}td}|j|jgt_|jt j d|j dj dj|j dj djgy)z L{plugin.pluginPackagePaths} should return a list containing each directory in C{sys.path} with a suffix based on the supplied package name. foobar dummy.pluginsrrdN)r r%r$rar pluginPackagePathsr-)r6r#r$s rtest_pluginDirectoriesz'PackagePathTests.test_pluginDirectoriess uouoHHchh'   % %o 6 '"((388 '"((388  rc,t|j}|jdjdjd}|j|jdj d|jdj |jdj gt _|jtjd|jdjdjdj gy) a L{plugin.pluginPackagePaths} should exclude directories which are Python packages. The only allowed plugin package (the only one associated with a I{dummy} package which Python will allow to be imported) will already be known to the caller of L{plugin.pluginPackagePaths} and will most commonly already be in the C{__path__} they are about to mutate. r#rrdr"rr$r%N) r r*r-rr/r%r$rar r&)r6r+r#s rtest_pluginPackagesExcludedz,PackagePathTests.test_pluginPackagesExcludeds &jj%%g.44Y?  - ++C0JJu%**DJJu,=,B,BC   % %o 6 ZZ  $ $W - 3 3I > C C D rNr)rrrrr7r>r'r)rrrrrs ( (  " rr)rhrrYbytes) rrrr*rrrrrYr)+r __future__rrrr[rIr$r importlibrrtypesrtypingrrrzope.interfacer twistedr twisted.python.filepathr twisted.python.logr r rr twisted.trialrrrrTestCaserrrrrrrrrrrr5s #  A//$,XX" T])9 v(##vt   -2:>NQ& ~(++~B\C8,,\C~4 x((4 r