Ë í2¯WÎÖ…ãóê—dZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl m Z ddl m Z eje«Ze«ZdZej(fd„Zd„Zdefd „Zd „Zd „Zd „ZGd „de«ZGd„de«ZGd„de«ZGd„de«Z Gd„de«Z!Gd„de e!«Z"Gd„de e!«Z#Gd„de#«Z$Gd„de«Z%Gd„d e «Z&Gd!„d"e jN«Z'y)#a Utility classes and functions that make it easy to write :mod:`unittest` compatible test suites. Over the years I've developed the habit of writing test suites for Python projects using the :mod:`unittest` module. During those years I've come to know :pypi:`pytest` and in fact I use :pypi:`pytest` to run my test suites (due to its much better error reporting) but I've yet to publish a test suite that *requires* :pypi:`pytest`. I have several reasons for doing so: - It's nice to keep my test suites as simple and accessible as possible and not requiring a specific test runner is part of that attitude. - Whereas :mod:`unittest` is quite explicit, :pypi:`pytest` contains a lot of magic, which kind of contradicts the Python mantra "explicit is better than implicit" (IMHO). éN)ÚStringIO)Ú random_string)ÚCallableTimedOutÚ CaptureBufferÚ CaptureOutputÚContextManagerÚCustomSearchPathÚ MockedProgramÚPatchedAttributeÚ PatchedItemÚTemporaryDirectoryÚTestCaseÚconfigure_loggingÚ make_dirsÚretryÚrun_cliÚ skip_on_raiseÚtouchcó€— ddl}|j|¬«y#t$rtj|dd¬«YywxYw)aÔconfigure_logging(log_level=logging.DEBUG) Automatically configure logging to the terminal. :param log_level: The log verbosity (a number, defaults to :mod:`logging.DEBUG `). When :mod:`coloredlogs` is installed :func:`coloredlogs.install()` will be used to configure logging to the terminal. When this fails with an :exc:`~exceptions.ImportError` then :func:`logging.basicConfig()` is used as a fall back. rN)Úlevelz;%(asctime)s %(name)s[%(process)d] %(levelname)s %(message)sz%Y-%m-%d %H:%M:%S)rÚformatÚdatefmt)Ú coloredlogsÚinstallÚ ImportErrorÚloggingÚ basicConfig)Ú log_levelrs ú:/usr/lib/python3.12/site-packages/humanfriendly/testing.pyrrBsA€ð)ÛØ×Ñ )ÐÕ,øÜ ò)Ü×ÑØØPØ'÷ )ð)ús ‚™!=¼=cón—tjj|«stj|«yy)zc Create missing directories. :param pathname: The pathname of a directory (a string). N)ÚosÚpathÚisdirÚmakedirs)Úpathnames rrrXs%€ô �7‰7�=‰=˜Ô "Ü � ‰ �HÕð #óé<có—d}|tj«z } |«}|dur|S tj«|kDr t«‚tj|«|dkr|dz}ŒP#|$rtj«|kDr‚YŒ?wxYw)a±retry(func, timeout=60, exc_type=AssertionError) Retry a function until assertions no longer fail. :param func: A callable. When the callable returns :data:`False` it will also be retried. :param timeout: The number of seconds after which to abort (a number, defaults to 60). :param exc_type: The type of exceptions to retry (defaults to :exc:`~exceptions.AssertionError`). :returns: The value returned by `func`. :raises: Once the timeout has expired :func:`retry()` will raise the previously retried assertion error. When `func` keeps returning :data:`False` until `timeout` expires :exc:`CallableTimedOut` will be raised. This function sleeps between retries to avoid claiming CPU cycles we don't need. It starts by sleeping for 0.1 second but adjusts this to one second as the number of retries grows. gš™™™™™¹?Féé)ÚtimerÚsleep)ÚfuncÚtimeoutÚexc_typeÚpauseÚresults rrrbs›€ð( €EØ Œt�y‰y‹{Ñ€GØ ð )Ù“VˆFؘUÑ"Ø� ð#ô �y‰y‹{˜WÒ$Ü&Ó(Ð(Ü � ‰ �5ÔØ �1Š9Ø �Q‰JˆEð øð ò Ü�y‰y‹{˜WÒ$Øñ%ð úsœ A+Á+B  B cóF—t|«}|jd|jdtj««t j d|«d}d}d} ttd|«5|jdd«|d<tdi|¤Ž5} |«|jj«}|jj«}t« ddd«ddd«t j d «|j%dd«}d|fg} d|fd|fg} |r| n| } | D]5\} } | rt j d| | «Œ t j d| «Œ7||fS#|jj«}|jj«}t«wxYw#1swYŒÌxYw#1swYŒÐxYw#t$ra}t|t«r-t j d |j «|j }nt j#d d¬ «d }Yd}~�Œ#d}~wwxYw)aÙ Test a command line entry point. :param entry_point: The function that implements the command line interface (a callable). :param arguments: Any positional arguments (strings) become the command line arguments (:data:`sys.argv` items 1-N). :param options: The following keyword arguments are supported: **capture** Whether to use :class:`CaptureOutput`. Defaults to :data:`True` but can be disabled by passing :data:`False` instead. **input** Refer to :class:`CaptureOutput`. **merged** Refer to :class:`CaptureOutput`. **program_name** Used to set :data:`sys.argv` item 0. :returns: A tuple with two values: 1. The return code (an integer). 2. The captured output (a string). rÚ program_namez3Calling command line entry point with arguments: %sNÚargvÚcaptureTÚenabledz/Command line entry point returned successfully!z6Intercepting return code %s from SystemExit exception.z4Defaulting return code to 1 due to raised exception.©Úexc_infor)ÚmergedFzmerged streamsÚstdoutÚstderrzOutput on %s: %szNo output on %s.©)ÚlistÚinsertÚpopÚsysÚ executableÚloggerÚdebugr rr:Úgetvaluer;rÚ BaseExceptionÚ isinstanceÚ SystemExitÚcodeÚwarningÚget)Ú entry_pointÚ argumentsÚoptionsÚ returncoder:r;ÚcapturerÚeÚ is_mergedÚmerged_streamsÚseparate_streamsÚstreamsÚnameÚvalues rrrˆsã€ô4�Y“€IØ ×Ñ�Q˜Ÿ ™  N´C·N±NÓCÔDô ‡L�LÐFÈ ÔRð€JØ €FØ €FðHä œc 6¨9Õ 5à!(§¡¨Y¸Ó!=ˆG�IÑ ÜÑ) Ò)¨Xð (á”Mð&Ÿ_™_×5Ñ5Ó7�FØ%Ÿ_™_×5Ñ5Ó7�Fô&Õ'÷*÷6ô. � ‰ ÐFÔGð— ‘ ˜H eÓ,€IØ'¨Ð0Ð1€NØ! 6Ð*¨X°vÐ,>Ð?ÐÙ )‰nÐ/?€GÛ‰ ˆˆeÙ Ü �L‰LÐ,¨d°EÕ :ä �L‰LÐ+¨TÕ 2ð ð �vÐ Ðøð5&Ÿ_™_×5Ñ5Ó7�FØ%Ÿ_™_×5Ñ5Ó7�Fô&Õ'ú÷*Ð)ú÷6Ð 5ûô òÜ �aœÔ $Ü �L‰LÐQÐST×SYÑSYÔ ZØŸ™‰Jä �N‰NÐQÐ\`ˆNÔ a؈Jÿùð úsbÁF6Á*!F* F EÂ>FÃF*ÃF6ÅAFÆFÆF' Æ#F*Æ*F3Æ/F6Æ6 H Æ?AHÈH có‡—ˆfd„}|S)a; Decorate a test function to translation specific exception types to :exc:`unittest.SkipTest`. :param exc_types: One or more positional arguments give the exception types to be translated to :exc:`unittest.SkipTest`. :returns: A decorator function specialized to `exc_types`. cóF•‡—tj‰«ˆˆfd„«}|S)Ncó •— ‰|i|¤ŽS#‰$r=}tjdd¬«tjdt |«z«‚d}~wwxYw)Nz-Translating exception to unittest.SkipTest ..Tr7z#skipping test because %s was raised)rBrCÚunittestÚSkipTestÚtype)ÚargsÚkwrPÚ exc_typesÚfunctions €€rÚwrapperz1skip_on_raise..decorator..wrapperÞs[ø€ð YÙ Ð,¨Ñ,Ð,øØò YÜ— ‘ ÐLÐW[� Ô\Ü×'Ñ'Ð(MÔPTÐUVÓPWÑ(WÓXÐXûð Yúsƒ ‹A �8AÁA )Ú functoolsÚwraps)r`rar_s` €rÚ decoratorz skip_on_raise..decoratorÝs'ù€Ü �‰˜Ó "ô Yó #ð Yð ˆr&r<)r_rds` rrrÕsø€ôð Ðr&có—ttjj|««t |d«5tj |d«ddd«y#1swYyxYw)zä The equivalent of the UNIX :man:`touch` program in Python. :param filename: The pathname of the file to touch (a string). Note that missing directories are automatically created using :func:`make_dirs()`. ÚaN)rr!r"ÚdirnameÚopenÚutime)Úfilenames rrrés=€ôŒb�g‰g�o‰o˜hÓ'Ô(Ü ˆh˜Õ Ü �‰�˜4Ô ÷ × Ñ ús µAÁAcó—eZdZdZy)rz3Raised by :func:`retry()` when the timeout expires.N)Ú__name__Ú __module__Ú __qualname__Ú__doc__r<r&rrr÷s„â=r&rcó—eZdZdZd„Zdd„Zy)rz5Base class to enable composition of context managers.có—|S)úEnable use as context managers.r<©Úselfs rÚ __enter__zContextManager.__enter__s€àˆ r&Ncó—y)rrNr<)rtr/Ú exc_valueÚ tracebacks rÚ__exit__zContextManager.__exit__s�r&©NNN)rlrmrnroruryr<r&rrrüs„á?òô.r&rcó4‡—eZdZdZd„Zˆfd„Zdˆfd„ ZˆxZS)r zTContext manager that temporary replaces an object attribute using :func:`setattr()`.cóD—||_||_||_t|_y)zº Initialize a :class:`PatchedAttribute` object. :param obj: The object to patch. :param name: An attribute name. :param value: The value to set. N)Úobject_to_patchÚattribute_to_patchÚ patched_valueÚNOTHINGÚoriginal_value)rtÚobjrUrVs rÚ__init__zPatchedAttribute.__init__ s$€ð #ˆÔØ"&ˆÔØ"ˆÔÜ%ˆÕr&cóê•—tt|� «t|j|j t «|_t|j|j |j«|jS)zk Replace (patch) the attribute. :returns: The object whose attribute was patched. ) Úsuperr ruÚgetattrr}r~r€r�Úsetattrr©rtÚ __class__s €rruzPatchedAttribute.__enter__s[ø€ô Ô Ñ/Ô1ä% d×&:Ñ&:¸D×Cleanup the temporary directory using :func:`shutil.rmtree()`.N)r…r ryršÚshutilÚrmtreerŒs €rryzTemporaryDirectory.__exit__�sDø€ô Ô  $Ñ0°¸9ÀiÔPà × #Ñ #Ð /Ü �M‰M˜$×2Ñ2Ô 3Ø'+ˆDÕ $ð 0r&rzr�r�s@rr r ]sø„ñ ò(ô (÷,ñ,r&r có0‡—eZdZdZd„Zd„Zdˆfd„ ZˆxZS)ÚMockedHomeDirectoryzÔ Context manager to temporarily change ``$HOME`` (the current user's profile directory). This class is a composition of the :class:`PatchedItem` and :class:`TemporaryDirectory` context managers. có´—tj|tjdtjj d««t j|«y)z1Initialize a :class:`MockedHomeDirectory` object.ÚHOMEN)r rƒr!ÚenvironrJr rss rrƒzMockedHomeDirectory.__init__”s5€ä×јT¤2§:¡:¨v´r·z±z·~±~ÀfÓ7MÔNÜ×#Ñ# DÕ)r&cóh—tj|«}||_tj|«|S©z  Activate the custom ``$PATH``. :returns: The pathname of the directory that has been added to ``$PATH`` (a string). )r rurr ©rtÚ directorys rruzMockedHomeDirectory.__enter__™s2€ô'×0Ñ0°Ó6ˆ ð'ˆÔä×јdÔ#àÐr&có0•—tt|� |||«y)z Deactivate the custom ``$HOME``.N)r…r¢ryrŒs €rryzMockedHomeDirectory.__exit__ªsø€ä Ô! 4Ñ1°(¸IÀyÕQr&rzr�r�s@rr¢r¢‹sø„ñò*ò ÷"RñRr&r¢cóB‡—eZdZdZdd„Zd„Zdˆfd„ Zed„«ZˆxZ S)r zÍ Context manager to temporarily customize ``$PATH`` (the executable search path). This class is a composition of the :class:`PatchedItem` and :class:`TemporaryDirectory` context managers. cóœ—||_tj|tjd|j «t j|«y)a  Initialize a :class:`CustomSearchPath` object. :param isolated: :data:`True` to clear the original search path, :data:`False` to add the temporary directory to the start of the search path. ÚPATHN)Úisolated_search_pathr rƒr!r¥Úcurrent_search_pathr )rtÚisolateds rrƒzCustomSearchPath.__init__¸s8€ð%-ˆÔ!ä×јT¤2§:¡:¨v°t×7OÑ7OÔPÜ×#Ñ# DÕ)r&có—tj|«}|jr|nItjj |g|j jtj«z«|_tj|«|Sr§) r rur®r!ÚpathsepÚjoinr¯Úsplitrr r¨s rruzCustomSearchPath.__enter__Æsm€ô'×0Ñ0°Ó6ˆ ð×2Ò2‰IÜ—‘—‘ ) ¨t×/GÑ/G×/MÑ/MÌbÏjÉjÓ/YÑ!YÓZð Ôô ×јdÔ#ðÐr&có0•—tt|� |||«y)z Deactivate the custom ``$PATH``.N)r…r ryrŒs €rryzCustomSearchPath.__exit__Ûsø€ä Ô Ñ.¨x¸ÀIÕNr&có^—tjjdtj«S)z8The value of ``$PATH`` or :data:`os.defpath` (a string).r­)r!r¥rJÚdefpathrss rr¯z$CustomSearchPath.current_search_pathßs€ô�z‰z�~‰~˜f¤b§j¡jÓ1Ð1r&)Frz) rlrmrnrorƒruryÚpropertyr¯rŽr�s@rr r ¯s,ø„ñó *òõ*Oðñ2óô2r&r có8‡—eZdZdZdˆfd„ Zˆfd„Zˆfd„ZˆxZS)r z’ Context manager to mock the existence of a program (executable). This class extends the functionality of :class:`CustomSearchPath`. cób•—||_||_||_d|_tt |�«y)aÝ Initialize a :class:`MockedProgram` object. :param name: The name of the program (a string). :param returncode: The return code that the program should emit (a number, defaults to zero). :param script: Shell script code to include in the mocked program (a string or :data:`None`). This can be used to mock a program that is expected to generate specific output. N)r3Úprogram_returncodeÚprogram_scriptÚprogram_signal_filer…r rƒ)rtrUrNÚscriptr‰s €rrƒzMockedProgram.__init__ís2ø€ð!ˆÔØ",ˆÔØ$ˆÔØ#'ˆÔ ä Œm˜TÑ+Õ-r&c󀕗tt|� «}tjj |dt d«z«|_tjj ||j«}t|d«5}|jd«|jdtj|j«z«|jr,|jd|jj«z«|jd|jz«ddd«tj |d «|S#1swYŒ!xYw) zš Create the mock program. :returns: The pathname of the directory that has been added to ``$PATH`` (a string). zprogram-was-run-%sé Úwz #!/bin/sh z echo > %s z%s zexit %i Nií)r…r rur!r"r³rr½r3rhÚwriteÚpipesÚquoter¼Ústripr»Úchmod)rtr©r%Úhandler‰s €rruzMockedProgram.__enter__sîø€ôœ-¨Ñ8Ó:ˆ Ü#%§7¡7§<¡<° Ð;OÔR_Ð`bÓRcÑ;cÓ#dˆÔ Ü—7‘7—<‘<  ¨4×+<Ñ+<Ó=ˆÜ �(˜CÔ  FØ �L‰L˜Ô 'Ø �L‰L˜¬¯©°T×5MÑ5MÓ)NÑNÔ OØ×"Ò"Ø— ‘ ˜V d×&9Ñ&9×&?Ñ&?Ó&AÑAÔBØ �L‰L˜ t×'>Ñ'>Ñ>Ô ?÷ !ô �‰�˜5Ô!ØÐ÷!Ð ús Á;BD4Ä4D=cóö•— |jr)tjj|j«sJd|jz«‚ t t |�|i|¤ŽS#t t |�|i|¤ŽccYSxYw)z£ Ensure that the mock program was run. :raises: :exc:`~exceptions.AssertionError` when the mock program hasn't been run. zIt looks like %r was never run!)r½r!r"Úisfiler3r…r ry©rtr]r^r‰s €rryzMockedProgram.__exit__sxø€ð DØ×+Ò+´·±·±¸t×?WÑ?WÔ0Xð HØ2°T×5FÑ5FÑFó HÐXÐ0Xôœ¨Ñ6¸ÐCÀÑCÐ Cø”5œ¨Ñ6¸ÐCÀÑCÔ Cús ƒA A Á A8)rNr�r�s@rr r åsø„ñõ .ô&÷& Dð Dr&r cóH‡—eZdZdZdd„Zˆfd„Zd ˆfd„ Zd„Zd„Zd„Z ˆxZ S) ra Context manager that captures what's written to :data:`sys.stdout` and :data:`sys.stderr`. .. attribute:: stdin The :class:`~humanfriendly.compat.StringIO` object used to feed the standard input stream. .. attribute:: stdout The :class:`CaptureBuffer` object used to capture the standard output stream. .. attribute:: stderr The :class:`CaptureBuffer` object used to capture the standard error stream. cóÚ‡—t|«‰_t«‰_|r ‰jn t«‰_g‰_|r%‰j j ˆfd„dD««yy)aƒ Initialize a :class:`CaptureOutput` object. :param merged: :data:`True` to merge the streams, :data:`False` to capture them separately. :param input: The data that reads from :data:`sys.stdin` should return (a string). :param enabled: :data:`True` to enable capturing (the default), :data:`False` otherwise. This makes it easy to unconditionally use :class:`CaptureOutput` in a :keyword:`with` block while preserving the choice to opt out of capturing output. c 3óT•K—|]}tt|t‰|««–—Œ!y­w)N)r r@r†)Ú.0rUrts €rÚ z)CaptureOutput.__init__..Fs)øèø€ð+á9�Dô!¤ d¬G°D¸$Ó,?×@Ù9ùsƒ%()Ústdinr:r;N)rrÐrr:r;Úpatched_attributesÚextend)rtr9Úinputr6s` rrƒzCaptureOutput.__init__3s[ø€ô˜e“_ˆŒ Ü#“oˆŒ Ù%+�d—k’k´³ˆŒ Ø"$ˆÔÙ Ø × #Ñ #× *Ñ *ó+á9ó+õ ð r&cón•—tt|� «|jD]}|j«Œ|S)zLStart capturing what's written to :data:`sys.stdout` and :data:`sys.stderr`.)r…rrurÑ)rtÚcontextr‰s €rruzCaptureOutput.__enter__Ks1ø€ä Œm˜TÑ,Ô.Ø×.Ô.ˆGØ × Ñ Õ ð/àˆ r&cóx•—tt|� |||«|jD]}|j|||«Œy)zKStop capturing what's written to :data:`sys.stdout` and :data:`sys.stderr`.N)r…rryrÑ)rtr/rwrxrÕr‰s €rryzCaptureOutput.__exit__Rs8ø€ä Œm˜TÑ+¨H°iÀÔKØ×.Ô.ˆGØ × Ñ ˜X y°)Õ <ñ/r&có>—|j«j«S)z=Get the contents of :attr:`stdout` split into separate lines.©Úget_textÚ splitlinesrss rÚ get_lineszCaptureOutput.get_linesXó€à�}‰}‹×)Ñ)Ó+Ð+r&có6—|jj«S)z7Get the contents of :attr:`stdout` as a Unicode string.)r:rÙrss rrÙzCaptureOutput.get_text\ó€à�{‰{×#Ñ#Ó%Ð%r&có6—|jj«S)z+Get the text written to :data:`sys.stdout`.)r:rDrss rrDzCaptureOutput.getvalue`rÞr&)FÚTrz) rlrmrnrorƒruryrÛrÙrDrŽr�s@rrr!s&ø„ñó ô0õ=ò ,ò&ö&r&rcó—eZdZdZd„Zd„Zy)raV Helper for :class:`CaptureOutput` to provide an easy to use API. The two methods defined by this subclass were specifically chosen to match the names of the methods provided by my :pypi:`capturer` package which serves a similar role as :class:`CaptureOutput` but knows how to simulate an interactive terminal (tty). có>—|j«j«S)z9Get the contents of the buffer split into separate lines.rØrss rrÛzCaptureBuffer.get_linesprÜr&có"—|j«S)z3Get the contents of the buffer as a Unicode string.)rDrss rrÙzCaptureBuffer.get_textts€à�}‰}‹Ðr&N)rlrmrnrorÛrÙr<r&rrres„ñò,ór&rcó@‡—eZdZdZˆfd„Zej fd„ZˆxZS)rz_Subclass of :class:`unittest.TestCase` with automatic logging and other miscellaneous features.có,•—tt|� |i|¤Žy)z¥ Initialize a :class:`TestCase` object. Any positional and/or keyword arguments are passed on to the initializer of the superclass. N)r…rrƒrÊs €rrƒzTestCase.__init__}sø€ô Œh˜Ñ&¨Ð3°Ó3r&cóX—t|«tjjd«y)aÇsetUp(log_level=logging.DEBUG) Automatically configure logging to the terminal. :param log_level: Refer to :func:`configure_logging()`. The :func:`setUp()` method is automatically called by :class:`unittest.TestCase` before each test method starts. It does two things: - Logging to the terminal is configured using :func:`configure_logging()`. - Before the test method starts a newline is emitted, to separate the name of the test method (which will be printed to the terminal by :mod:`unittest` or :pypi:`pytest`) from the first line of logging output that the test method is likely going to generate. Ú N)rr@r;rÂ)rtrs rÚsetUpzTestCase.setUp†s €ô& ˜)Ô$ô � ‰ ×јÕr&) rlrmrnrorƒrÚDEBUGrèrŽr�s@rrrysø„áiô4ð&Ÿm™m÷r&r)(rorbrr!rÃrŸr@rœr+rZÚhumanfriendly.compatrÚhumanfriendly.textrÚ getLoggerrlrBÚobjectr€Ú__all__rérrÚAssertionErrorrrrrÚ Exceptionrrr r r r¢r r rrrr<r&rÚrñs&ðñó$ÛÛ Û Û Û ÛÛ Ûõ*Ý,ð ˆ× Ñ ˜8Ó $€ñ ‹(€ð €ð(!(§ ¡ ó)ò,ð ^ó#òLJòZò( !ô>�yô>ô .�Vô .ô&X�~ô&XôR)K�.ô)KôX+,˜ô+,ô\!R˜+Ð'9ô!RôH32�{Ð$6ô32ôl9DÐ$ô9DôxA&�NôA&ôH�Hôô($ˆx× Ñ õ$r&