a
    IDg'_                     @   sn  d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZ ddlmZ eeZe ZdZejfddZdd	 Zd
efddZdd Zdd Zdd ZG dd deZG dd deZG dd deZG dd deZ G dd deZ!G dd de e!Z"G dd  d e e!Z#G d!d" d"e#Z$G d#d$ d$eZ%G d%d& d&eZ&G d'd( d(e	j'Z'dS ))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                 C   s@   zddl }|j| d W n" ty:   tj| ddd Y n0 dS )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 <logging>`).

    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.
    r   N)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_levelr    r   f/mounts/lovelace/software/anaconda3/envs/paleomix/lib/python3.9/site-packages/humanfriendly/testing.pyr   B   s    r   c                 C   s   t j| st |  dS )zc
    Create missing directories.

    :param pathname: The pathname of a directory (a string).
    N)ospathisdirmakedirs)pathnamer   r   r   r   X   s    r   <   c                 C   s|   d}|t   7 }z|  }|dur&|W S W n  |yH   t   |krD Y n0 t   |kr\t t | |dk r|d9 }qdS )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      N)timer   sleep)functimeoutexc_typepauseresultr   r   r   r   b   s    

r   c              
   O   s  t |}|d|dtj td| d}d}d}zttd| |dd|d< tf i |R}z$|   W |j	
 }|j
 }t  n|j	
 }|j
 }t  0 W d   n1 s0    Y  W d   n1 s0    Y  W nZ ty@ } z@t|trtd	|j |j}ntjd
dd d}W Y d}~nd}~0 0 td |dd}d|fg}	d|fd|fg}
|r||	n|
}|D ],\}}|rtd|| ntd| q||fS )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enabledz6Intercepting return code %s from SystemExit exception.z4Defaulting return code to 1 due to raised exception.exc_infor%   z/Command line entry point returned successfully!mergedFzmerged streamsstdoutstderrzOutput on %s:
%szNo output on %s.)listinsertpopsys
executableloggerdebugr
   r   r5   getvaluer6   r   BaseException
isinstance
SystemExitcodewarningget)entry_point	argumentsoptions
returncoder5   r6   ZcapturereZ	is_mergedZmerged_streamsZseparate_streamsZstreamsnamevaluer   r   r   r      sD    



H

r   c                     s    fdd}|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                    s   t   fdd}|S )Nc               
      sZ   z| i |W S   yT } z,t jddd tdt| W Y d }~n
d }~0 0 d S )Nz-Translating exception to unittest.SkipTest ..Tr2   z#skipping test because %s was raised)r<   r=   unittestZSkipTesttype)argskwrI   )	exc_typesfunctionr   r   wrapper   s
    z1skip_on_raise.<locals>.decorator.<locals>.wrapper)	functoolswraps)rQ   rR   rP   )rQ   r   	decorator   s    z skip_on_raise.<locals>.decoratorr   )rP   rV   r   rU   r   r      s    	r   c                 C   sJ   t tj|  t| d t| d W d   n1 s<0    Y  dS )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)r   r   r    dirnameopenutime)filenamer   r   r   r      s    	r   c                   @   s   e Zd ZdZdS )r   z3Raised by :func:`retry()` when the timeout expires.N)__name__
__module____qualname____doc__r   r   r   r   r      s   r   c                   @   s"   e Zd ZdZdd ZdddZdS )r   z5Base class to enable composition of context managers.c                 C   s   | S )Enable use as context managers.r   selfr   r   r   	__enter__   s    zContextManager.__enter__Nc                 C   s   dS )r`   Nr   rb   r+   	exc_value	tracebackr   r   r   __exit__  s    zContextManager.__exit__)NNN)r\   r]   r^   r_   rc   rg   r   r   r   r   r      s   r   c                       s6   e Zd ZdZdd Z fddZd	 fdd	Z  ZS )
r
   zTContext manager that temporary replaces an object attribute using :func:`setattr()`.c                 C   s   || _ || _|| _t| _dS )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)rb   objrJ   rK   r   r   r   __init__  s    zPatchedAttribute.__init__c                    s8   t t|   t| j| jt| _t| j| j| j	 | jS )zk
        Replace (patch) the attribute.

        :returns: The object whose attribute was patched.
        )
superr
   rc   getattrrh   ri   rk   rl   setattrrj   ra   	__class__r   r   rc     s    zPatchedAttribute.__enter__Nc                    sD   t t| ||| | jtu r.t| j| j nt| j| j| j dS )z,Restore the attribute to its original value.N)	ro   r
   rg   rl   rk   delattrrh   ri   rq   rd   rr   r   r   rg   &  s    
zPatchedAttribute.__exit__)NNNr\   r]   r^   r_   rn   rc   rg   __classcell__r   r   rr   r   r
     s   r
   c                       s6   e Zd ZdZdd Z fddZd	 fdd	Z  ZS )
r   z[Context manager that temporary replaces an object item using :meth:`~object.__setitem__()`.c                 C   s   || _ || _|| _t| _dS )z
        Initialize a :class:`PatchedItem` object.

        :param obj: The object to patch.
        :param item: The item to patch.
        :param value: The value to set.
        N)rh   item_to_patchrj   rk   rl   )rb   rm   itemrK   r   r   r   rn   5  s    zPatchedItem.__init__c                    sN   t t|   z| j| j | _W n ty8   t| _Y n0 | j| j| j< | jS )za
        Replace (patch) the item.

        :returns: The object whose item was patched.
        )	ro   r   rc   rh   rw   rl   KeyErrorrk   rj   ra   rr   r   r   rc   B  s    zPatchedItem.__enter__Nc                    s<   t t| ||| | jtu r*| j| j= n| j| j| j< dS )z'Restore the item to its original value.N)ro   r   rg   rl   rk   rh   rw   rd   rr   r   r   rg   R  s    
zPatchedItem.__exit__)NNNru   r   r   rr   r   r   1  s   r   c                       s6   e Zd ZdZdd Z fddZd	 fdd	Z  ZS )
r   a#  
    Easy temporary directory creation & cleanup using the :keyword:`with` statement.

    Here's an example of how to use this:

    .. code-block:: python

       with TemporaryDirectory() as directory:
           # Do something useful here.
           assert os.path.isdir(directory)
    c                 K   s   || _ d| _dS )z
        Initialize a :class:`TemporaryDirectory` object.

        :param options: Any keyword arguments are passed on to
                        :func:`tempfile.mkdtemp()`.
        N)mkdtemp_optionstemporary_directory)rb   rG   r   r   r   rn   k  s    zTemporaryDirectory.__init__c                    s(   t t|   tjf i | j| _| jS )z
        Create the temporary directory using :func:`tempfile.mkdtemp()`.

        :returns: The pathname of the directory (a string).
        )ro   r   rc   tempfilemkdtemprz   r{   ra   rr   r   r   rc   u  s    zTemporaryDirectory.__enter__Nc                    s4   t t| ||| | jdur0t| j d| _dS )z>Cleanup the temporary directory using :func:`shutil.rmtree()`.N)ro   r   rg   r{   shutilrmtreerd   rr   r   r   rg     s    
zTemporaryDirectory.__exit__)NNNru   r   r   rr   r   r   ]  s   
r   c                       s2   e Zd ZdZdd Zdd Zd	 fdd	Z  ZS )
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                 C   s(   t | tjdtjd t|  dS )z1Initialize a :class:`MockedHomeDirectory` object.HOMEN)r   rn   r   environrD   r   ra   r   r   r   rn     s    zMockedHomeDirectory.__init__c                 C   s   t | }|| _t|  |S z
        Activate the custom ``$PATH``.

        :returns: The pathname of the directory that has
                  been added to ``$PATH`` (a string).
        )r   rc   rj   r   rb   	directoryr   r   r   rc     s    

zMockedHomeDirectory.__enter__Nc                    s   t t| ||| dS )z Deactivate the custom ``$HOME``.N)ro   r   rg   rd   rr   r   r   rg     s    zMockedHomeDirectory.__exit__)NNNru   r   r   rr   r   r     s   r   c                       s@   e Zd ZdZdddZdd Zd fdd		Zed
d Z  Z	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.
    Fc                 C   s(   || _ t| tjd| j t|  dS )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   rn   r   r   current_search_pathr   )rb   isolatedr   r   r   rn     s    	zCustomSearchPath.__init__c                 C   s@   t | }| jr|ntj|g| jtj | _t	|  |S r   )
r   rc   r   r   pathsepjoinr   splitrj   r   r   r   r   r   rc     s    


zCustomSearchPath.__enter__Nc                    s   t t| ||| dS )z Deactivate the custom ``$PATH``.N)ro   r   rg   rd   rr   r   r   rg     s    zCustomSearchPath.__exit__c                 C   s   t jdt jS )z8The value of ``$PATH`` or :data:`os.defpath` (a string).r   )r   r   rD   defpathra   r   r   r   r     s    z$CustomSearchPath.current_search_path)F)NNN)
r\   r]   r^   r_   rn   rc   rg   propertyr   rv   r   r   rr   r   r     s   
r   c                       s:   e Zd ZdZd
 fdd	Z fddZ fdd	Z  ZS )r	   z
    Context manager to mock the existence of a program (executable).

    This class extends the functionality of :class:`CustomSearchPath`.
    r   Nc                    s*   || _ || _|| _d| _tt|   dS )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)r.   program_returncodeprogram_scriptprogram_signal_filero   r	   rn   )rb   rJ   rH   scriptrr   r   r   rn     s
    zMockedProgram.__init__c                    s   t t|  }tj|dtd | _tj|| j}t	|dZ}|
d |
dt| j  | jr||
d| j   |
d| j  W d   n1 s0    Y  t|d	 |S )
z
        Create the mock program.

        :returns: The pathname of the directory that has
                  been added to ``$PATH`` (a string).
        zprogram-was-run-%s
   wz
#!/bin/sh
z
echo > %s
z%s
zexit %i
Ni  )ro   r	   rc   r   r    r   r   r   r.   rY   writepipesquoter   stripr   chmod)rb   r   r#   handlerr   r   r   rc      s    
.zMockedProgram.__enter__c              
      sd   z:| j rtj| j s$J d| j W tt| j|i |S tt| j|i |     Y S 0 dS )z
        Ensure that the mock program was run.

        :raises: :exc:`~exceptions.AssertionError` when
                 the mock program hasn't been run.
        zIt looks like %r was never run!N)r   r   r    isfiler.   ro   r	   rg   rb   rN   rO   rr   r   r   rg     s
    zMockedProgram.__exit__)r   Nru   r   r   rr   r   r	     s   r	   c                       sP   e Zd ZdZdddZ fddZd fd
d	Zdd Zdd Zdd Z	  Z
S )r   a  
    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.
    F Tc                    sL   t | _t  _|r jnt  _g  _|rH j fdddD  dS )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   s    | ]}t t|t |V  qd S )N)r
   r:   rp   ).0rJ   ra   r   r   	<genexpr>F  s   z)CaptureOutput.__init__.<locals>.<genexpr>)stdinr5   r6   N)r   r   r   r5   r6   patched_attributesextend)rb   r4   inputr1   r   ra   r   rn   3  s    
zCaptureOutput.__init__c                    s&   t t|   | jD ]}|  q| S )zLStart capturing what's written to :data:`sys.stdout` and :data:`sys.stderr`.)ro   r   rc   r   )rb   contextrr   r   r   rc   K  s    

zCaptureOutput.__enter__Nc                    s2   t t| ||| | jD ]}|||| qdS )zKStop capturing what's written to :data:`sys.stdout` and :data:`sys.stderr`.N)ro   r   rg   r   )rb   r+   re   rf   r   rr   r   r   rg   R  s    
zCaptureOutput.__exit__c                 C   s   |    S )z=Get the contents of :attr:`stdout` split into separate lines.get_text
splitlinesra   r   r   r   	get_linesX  s    zCaptureOutput.get_linesc                 C   s
   | j  S )z7Get the contents of :attr:`stdout` as a Unicode string.)r5   r   ra   r   r   r   r   \  s    zCaptureOutput.get_textc                 C   s
   | j  S )z+Get the text written to :data:`sys.stdout`.)r5   r>   ra   r   r   r   r>   `  s    zCaptureOutput.getvalue)Fr   T)NNN)r\   r]   r^   r_   rn   rc   rg   r   r   r>   rv   r   r   rr   r   r   !  s   
r   c                   @   s    e Zd ZdZdd Zdd ZdS )r   aV  
    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                 C   s   |    S )z9Get the contents of the buffer split into separate lines.r   ra   r   r   r   r   p  s    zCaptureBuffer.get_linesc                 C   s   |   S )z3Get the contents of the buffer as a Unicode string.)r>   ra   r   r   r   r   t  s    zCaptureBuffer.get_textN)r\   r]   r^   r_   r   r   r   r   r   r   r   e  s   	r   c                       s.   e Zd ZdZ fddZejfddZ  ZS )r   z_Subclass of :class:`unittest.TestCase` with automatic logging and other miscellaneous features.c                    s   t t| j|i | dS )z
        Initialize a :class:`TestCase` object.

        Any positional and/or keyword arguments are passed on to the
        initializer of the superclass.
        N)ro   r   rn   r   rr   r   r   rn   }  s    zTestCase.__init__c                 C   s   t | tjd dS )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)r   r:   r6   r   )rb   r   r   r   r   setUp  s    zTestCase.setUp)	r\   r]   r^   r_   rn   r   DEBUGr   rv   r   r   rr   r   r   y  s   	r   )(r_   rS   r   r   r   r~   r:   r|   r'   rL   Zhumanfriendly.compatr   Zhumanfriendly.textr   	getLoggerr\   r<   objectrk   __all__r   r   r   AssertionErrorr   r   r   r   	Exceptionr   r   r
   r   r   r   r   r	   r   r   r   r   r   r   r   <module>   s>   

&M),.$6<D