o
    eR]GP                     @   s  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
ZddlZddlZddlZejjdg g g g dZi Zi ZejjdddZedd	Zej rdG d
d deZne	jjZd,ddZd,ddZ dd Z!dd Z"G dd de#Z$G dd dedg dZ%G dd de	j&Z'dd Z(dd Z)dd  Z*d!d" Z+d-d$d%Z,d&d' Z-d(d) Z.d*d+ Z/e,Z0e-Z1e+Z2dS ).aD  
Helper functions for testing.

Our **stylistic_issues**, **pyflakes_issues**, and **type_check_issues**
respect a 'exclude_paths' in our test config, excluding any absolute paths
matching those regexes. Issue strings can start or end with an asterisk
to match just against the prefix or suffix. For instance...

::

  exclude_paths .*/stem/test/data/.*

.. versionadded:: 1.2.0

::

  TimedTestRunner - test runner that tracks test runtimes
  test_runtimes - provides runtime of tests excuted through TimedTestRunners
  clean_orphaned_pyc - delete *.pyc files without corresponding *.py

  is_pyflakes_available - checks if pyflakes is available
  is_pycodestyle_available - checks if pycodestyle is available

  pyflakes_issues - static checks for problems via pyflakes
  stylistic_issues - checks for PEP8 and other stylistic issues
    Ntest)pep8.ignorepycodestyle.ignorepyflakes.ignoreexclude_pathsPENDINGRUNNINGFINISHEDAsyncResultztype msgc                   @      e Zd ZdZdS )SkipTestz Notes that the test was skipped.N__name__
__module____qualname____doc__ r   r   6/usr/lib/python3/dist-packages/stem/util/test_tools.pyr   ?       r   c                 C   s*   | |krt |du rd| |f |dS )z
  Function form of a TestCase's assertEqual.

  .. versionadded:: 1.6.0

  :param object expected: expected value
  :param object actual: actual value
  :param str msg: message if assertion fails

  :raises: **AssertionError** if values aren't equal
  NzExpected '%s' but was '%s'AssertionErrorexpectedactualmsgr   r   r   assert_equalE      r   c                 C   s*   | |vrt |du rd| |f |dS )a  
  Asserts that a given value is within this content.

  .. versionadded:: 1.6.0

  :param object expected: expected value
  :param object actual: actual value
  :param str msg: message if assertion fails

  :raises: **AssertionError** if the expected value isn't in the actual
  NzExpected '%s' to be within '%s'r   r   r   r   r   	assert_inV   r   r   c                 C   s   t | )z
  Function form of a TestCase's skipTest.

  .. versionadded:: 1.6.0

  :param str msg: reason test is being skipped

  :raises: **unittest.case.SkipTest** for this reason
  )r   )r   r   r   r   skipg   s   r   c                 C   s   t jj| }|t|j< |jS N)stemutilZ
test_tools	AsyncTestASYNC_TESTSnamemethod)funcr   r   r   r   asynchronousu   s   
r'   c                   @   s:   e Zd ZdZdddZdd Zdd	 Zd
d Zdd ZdS )r"   a  
  Test that's run asychronously. These are functions (no self reference)
  performed like the following...

  ::

    class MyTest(unittest.TestCase):
      @staticmethod
      def run_tests():
        MyTest.test_addition = stem.util.test_tools.AsyncTest(MyTest.test_addition).method

      @staticmethod
      def test_addition():
        if 1 + 1 != 2:
          raise AssertionError('tisk, tisk')

    MyTest.run()

  .. versionadded:: 1.6.0
  NFc                    sZ   d|j |jf  _| _| _| _ fdd _d  _d  _t	
  _d  _tj _d S )Nz%s.%sc                    s
     | S r   result)r   selfr   r   <lambda>   s   
 z$AsyncTest.__init__.<locals>.<lambda>)r   r   r$   _runner_runner_args	_threadedr%   _process_process_pipe	threadingRLock_process_lock_resultAsyncStatusr   _status)r+   runnerargsthreadedr   r*   r   __init__   s   
zAsyncTest.__init__c                 O   s   t j rd S dd }| j] | jtjkrc|r|| _d|v r#|d | _t	
 \| _}| jrEtj||| j| jfd| j d| _| jd nt	j||| j| jfd| _| j  tj| _W d    d S W d    d S 1 snw   Y  d S )Nc              
   S   s   t d ztz|r|| n|  | tdd  W nK ty5 } z| tdt| W Y d }~n;d }~w tyP } z| tdt| W Y d }~n'd }~w   | tdt  Y W | 	  d S W | 	  d S W | 	  d S W | 	  d S | 	  w )N   Zsuccessfailureskippederror)
osnicesendr
   r   strr   	traceback
format_excclose)Zconnr8   r9   excr   r   r   _wrapper   s(   
  zAsyncTest.run.<locals>._wrapperr:   zBackground test of %s)targetr9   r$   T)rI   r9   )r    prereq_is_python_26r4   r7   r6   r   r.   r/   multiprocessingZPiper1   r2   ZThreadr-   r$   r0   Z	setDaemonZProcessstartr   )r+   Zrunner_argskwargsrH   Z
child_piper   r   r   run   s.   



"zAsyncTest.runc                 C   sB   | j  | jr| js| jjnd W  d    S 1 sw   Y  d S r   )r4   r0   r/   pidr*   r   r   r   rP      s   $zAsyncTest.pidc                 C   s   |  d  d S r   r(   r*   r   r   r   join   s   zAsyncTest.joinc                 C   s  t j rd S | jw | jtjkr|   | jtjkr*| j	
 | _| j  tj| _|r:| jjdkr:|| jj n'|rJ| jjdkrJ|| jj n'|ri| jjdkry|| jj W d    d S W d    d S W d    d S W d    d S W d    d S 1 sw   Y  d S )Nr=   r?   r>   )r    rJ   rK   r4   r7   r6   r   rO   r   r1   Zrecvr5   r0   rQ   r	   typefailr   skipTest)r+   r   r   r   r   r)      s0   


"zAsyncTest.result)NF)	r   r   r   r   r;   rO   rP   rQ   r)   r   r   r   r   r"   {   s    
+r"   c                   @   r   )Issuez
  Issue encountered by pyflakes or pycodestyle.

  :var int line_number: line number the issue occured on
  :var str message: description of the issue
  :var str line: content of the line the issue is about
  Nr   r   r   r   r   rU      r   rU   )line_numbermessagelinec                       s    e Zd ZdZ fddZ  ZS )TimedTestRunnerz
  Test runner that tracks the runtime of individual tests. When tests are run
  with this their runtimes are made available through
  :func:`stem.util.test_tools.test_runtimes`.

  .. versionadded:: 1.6.0
  c                    s>   |j D ]}t| G  fddd }||_qtt| |S )Nc                       sh   e Zd Zd fdd	Z fddZ fddZdd	 Z fd
dZfddZfddZ	  Z
S )z)TimedTestRunner.run.<locals>._TestWrapperNc                    s4   t   }tt| | |}t   | t|  < |S r   )timesuperrR   rO   TEST_RUNTIMESid)r+   r)   Z
start_time	__class__r   r   rO      s   z-TimedTestRunner.run.<locals>._TestWrapper.runc                    s   t j st| |S d S r   )r    rJ   rK   r[   rT   )r+   rW   r_   original_typer   r   rT   	  s   
z2TimedTestRunner.run.<locals>._TestWrapper.skipTestc                    s4   t j r| t|t| d S t| ||S r   )r    rJ   rK   ZassertEqualsetr[   assertItemsEqual)r+   r   r   r`   r   r   rc     s   
z:TimedTestRunner.run.<locals>._TestWrapper.assertItemsEqualc                 _   s&   | j |dt| |g|R i |S )a  
          Asserts the given invokation raises the expected excepiton. This is
          similar to unittest's assertRaises and assertRaisesRegexp, but checks
          for an exact match.

          This method is **not** being vended to external users and may be
          changed without notice. If you want this method to be part of our
          vended API then please let us know.
          z^%s$)assertRaisesRegexpreescape)r+   exc_typeexc_msgr&   r9   rN   r   r   r   assertRaisesWith  s   &z:TimedTestRunner.run.<locals>._TestWrapper.assertRaisesWithc              
      s   t j r7z||i | | d|  W d S  |y6 } z| t|t|tj W Y d }~d S d }~ww t	| j
|||g|R i |S )Nz*Expected a %s to be raised but nothing was)r    rJ   rK   rS   Z
assertTruere   searchrC   	MULTILINEr[   rd   )r+   rg   rh   r&   r9   rN   rG   r`   r   r   rd   "  s   
("z<TimedTestRunner.run.<locals>._TestWrapper.assertRaisesRegexpc                    s   d j  j| jf S )Nz%s.%s.%s)r   r   _testMethodNamer*   ra   r   r   r]   ,     z,TimedTestRunner.run.<locals>._TestWrapper.idc                    s   d| j  j jf S )Nz
%s (%s.%s))rl   r   r   r*   rm   r   r   __str__/  rn   z1TimedTestRunner.run.<locals>._TestWrapper.__str__r   )r   r   r   rO   rT   rc   ri   rd   r]   ro   __classcell__r   rm   r^   r   _TestWrapper   s    	
rq   )Z_testsrR   r_   r[   rY   rO   )r+   r   trq   r^   rm   r   rO      s
   
3zTimedTestRunner.run)r   r   r   r   rO   rp   r   r   r^   r   rY      s    rY   c                   C   s   t tS )z
  Provides the runtimes of tests executed through TimedTestRunners.

  :returns: **dict** of fully qualified test names to floats for the runtime in
    seconds

  .. versionadded:: 1.6.0
  )dictr\   r   r   r   r   test_runtimes7  s   
rt   c                 C   s   g }| D ]M}t jj|dD ]B}|dd }dtjjtjjf }||v r@||d\}}|ds2qtj	||dd d }tj
|sP|| t| qq|S )	as  
  Deletes any file with a \*.pyc extention without a corresponding \*.py. This
  helps to address a common gotcha when deleting python files...

  * You delete module 'foo.py' and run the tests to ensure that you haven't
    broken anything. They pass, however there *are* still some 'import foo'
    statements that still work because the bytecode (foo.pyc) is still around.

  * You push your change.

  * Another developer clones our repository and is confused because we have a
    bunch of ImportErrors.

  :param list paths: paths to search for orphaned pyc files

  :returns: list of absolute paths that were deleted
  z.pycNz%s__pycache__%s   .r   .py)r    r!   systemfiles_with_suffixr@   pathsepsplitendswithrQ   existsappendremove)pathsZorphaned_pycr{   Zpyc_pathZpy_pathpycacheZ	directorypycache_filenamer   r   r   clean_orphaned_pycD  s    


r   c                   C   s   t dot dS )zk
  Checks if pyflakes is availalbe.

  :returns: **True** if we can use pyflakes and **False** otherwise
  zpyflakes.apizpyflakes.reporter)_module_existsr   r   r   r   is_pyflakes_availableq  s   r   c                  C   s2   t dr	ddl} nt drddl} ndS t| dS )zq
  Checks if pycodestyle is availalbe.

  :returns: **True** if we can use pycodestyle and **False** otherwise
  pycodestyler   Npep8F
BaseReport)r   r   r   hasattr)r   r   r   r   is_pycodestyle_available{  s   


r   Fc                    s  i g }g g t d t d  D ]<}d|v rG|dd\}}d|v r9|dd\}}| | | f q| dkrF|  q|| qfddt rtd	r`d
dl}	nd
dl}	G  fddd|	j |	j	| d}
|

tt|  S )a  
  Checks for stylistic issues that are an issue according to the parts of PEP8
  we conform to. You can suppress pycodestyle issues by making a 'test'
  configuration that sets 'pycodestyle.ignore'.

  For example, with a 'test/settings.cfg' of...

  ::

    # pycodestyle compliance issues that we're ignoreing...
    #
    # * E111 and E121 four space indentations
    # * E501 line is over 79 characters

    pycodestyle.ignore E111
    pycodestyle.ignore E121
    pycodestyle.ignore E501

    pycodestyle.ignore run_tests.py => E402: import stem.util.enum

  ... you can then run tests with...

  ::

    import stem.util.conf

    test_config = stem.util.conf.get_config('test')
    test_config.load('test/settings.cfg')

    issues = stylistic_issues('my_project')

  .. versionchanged:: 1.3.0
     Renamed from get_stylistic_issues() to stylistic_issues(). The old name
     still works as an alias, but will be dropped in Stem version 2.0.0.

  .. versionchanged:: 1.4.0
     Changing tuples in return value to be namedtuple instances, and adding the
     line that had the issue.

  .. versionchanged:: 1.4.0
     Added the prefer_single_quotes option.

  .. versionchanged:: 1.6.0
     Changed 'pycodestyle.ignore' code snippets to only need to match against
     the prefix.

  :param list paths: paths to search for stylistic issues
  :param bool check_newlines: check that we have standard newlines (\n), not
    windows (\r\n) nor classic mac (\r)
  :param bool check_exception_keyword: checks that we're using 'as' for
    exceptions rather than a comma
  :param bool prefer_single_quotes: standardize on using single rather than
    double quotes for strings, when reasonable

  :returns: dict of paths list of :class:`stem.util.test_tools.Issue` instances
  r   r   =>rv   :*c                    sT   D ]\}}}|  |r||kr| |r dS q D ]
}|  |r' dS qdS )NTF)r~   strip
startswith)r{   rulecodeignored_pathZignored_ruleZignored_code)ignore_all_for_filesignore_for_filer   r   
is_ignored  s    
z$stylistic_issues.<locals>.is_ignoredr   r   Nc                       s:   e Zd Z fddZ fddZ  ZS )z%stylistic_issues.<locals>.StyleReportc           
         s*  t  | |||| sssd S d}D ]
}||r! d S qt|D ]l\}}|ddd  }	rId|v rI|g t|d d| |	sLq&d|	v rS| }rn|		drn|	d	rn|g t|d d
| r|sd|	v rd|	vrd|	vr|	ds|g t|d d| q&d S )NF#rv   r   zcontains a windows newlinez"""exceptz, exc:z(except clause should use 'as', not comma"'\z$use single rather than double quotes)
r[   	init_filer~   	enumerater}   r   
setdefaultr   rU   r   )
r+   filenamelinesr   Zline_offsetZis_block_commentr   indexrX   Zcontent)StyleReportr_   check_exception_keywordcheck_newlinesr   issuesprefer_single_quotesr   r   r     s.   
"z/stylistic_issues.<locals>.StyleReport.init_filec                    s^   t  | ||||}|r+t| j|}| j||s-| jg t||| d S d S d S r   )r[   r?   	linecachegetliner   r   r   rU   )r+   rV   offsettextcheckr   rX   )r   r_   r   r   r   r   r?     s    z+stylistic_issues.<locals>.StyleReport.error)r   r   r   r   r?   rp   r   )r   r   r   r   r   r   r   r^   r   r     s    0r   )ignorereporter)CONFIGr}   r   r   r   r   r   r   r   Z
StyleGuideZcheck_fileslist_python_files)r   r   r   r   Zignore_rulesr   r{   Z
rule_entryr   r   Zstyle_checkerr   )r   r   r   r   r   r   r   r   r   stylistic_issues  s.   :
":r   c                    sX   i  t  r*ddl}ddl}G  fddd|jj}| }t| D ]	}|j|| q  S )aP  
  Performs static checks via pyflakes. False positives can be ignored via
  'pyflakes.ignore' entries in our 'test' config. For instance...

  ::

    pyflakes.ignore stem/util/test_tools.py => 'pyflakes' imported but unused
    pyflakes.ignore stem/util/test_tools.py => 'pycodestyle' imported but unused

  .. versionchanged:: 1.3.0
     Renamed from get_pyflakes_issues() to pyflakes_issues(). The old name
     still works as an alias, but will be dropped in Stem version 2.0.0.

  .. versionchanged:: 1.4.0
     Changing tuples in return value to be namedtuple instances, and adding the
     line that had the issue.

  .. versionchanged:: 1.5.0
     Support matching against prefix or suffix issue strings.

  :param list paths: paths to search for problems

  :returns: dict of paths list of :class:`stem.util.test_tools.Issue` instances
  r   Nc                       s@   e Zd Zdd Zdd Zdd Zdd Zd	d
 Z fddZdS )z!pyflakes_issues.<locals>.Reporterc                 S   sB   i | _ td D ]}|d\}}| j | g |  qd S )Nr   r   )_ignored_issuesr   r}   r   r   r   )r+   rX   r{   issuer   r   r   r;   J  s
   z*pyflakes_issues.<locals>.Reporter.__init__c                 S   s   |  |d |d  d S r   _register_issue)r+   r   r   r   r   r   unexpectedErrorQ  rn   z1pyflakes_issues.<locals>.Reporter.unexpectedErrorc                 S   s   |  |||| d S r   r   )r+   r   r   linenor   r   r   r   r   syntaxErrorT  rn   z-pyflakes_issues.<locals>.Reporter.syntaxErrorc                 S   s    |  |j|j|j|j d  d S r   )r   r   r   rW   Zmessage_args)r+   r   r   r   r   flakeW  s    z'pyflakes_issues.<locals>.Reporter.flakec                 S   s|   | j  D ]6\}}||r;||v r dS dd |D D ]}||r'  dS qdd |D D ]}||r:  dS q/qdS )NTc                 S   "   g | ]}| d r|dd qS )r   Nrv   )r~   .0ir   r   r   
<listcomp>c     " zApyflakes_issues.<locals>.Reporter._is_ignored.<locals>.<listcomp>c                 S   r   )r   rv   N)r   r   r   r   r   r   g  r   F)r   itemsr~   r   )r+   r{   r   r   Zignored_issuesprefixsuffixr   r   r   _is_ignoredZ  s   


z-pyflakes_issues.<locals>.Reporter._is_ignoredc                    sJ   |  ||s#|r|r|st|| } |g t||| d S d S r   )r   r   r   r   r   r   rU   )r+   r{   rV   r   rX   r   r   r   r   m  s
   z1pyflakes_issues.<locals>.Reporter._register_issueN)	r   r   r   r;   r   r   r   r   r   r   r   r   r   ReporterI  s    r   )r   Zpyflakes.apiZpyflakes.reporterr   r   r   ZapiZ	checkPath)r   Zpyflakesr   r   r{   r   r   r   pyflakes_issues)  s   +r   c                 C   s$   zt |  W dS  ty   Y dS w )z
  Checks if a module exists.

  :param str module_name: module to check existance of

  :returns: **True** if module exists and **False** otherwise
  TF)
__import__ImportError)Zmodule_namer   r   r   r   |  s   	r   c                 c   sV    | D ]%}t jj|dD ]}d}td D ]}t||r!d} nq|s'|V  qqd S )Nrx   Fr   T)r    r!   ry   rz   r   re   match)r   r{   Z	file_pathr   Zexclude_pathr   r   r   r     s   r   r   )FFF)3r   collectionsr   rL   r@   re   r2   rZ   rD   ZunittestZstem.prereqr    Zstem.util.confZstem.util.enumZstem.util.systemr!   confZconfig_dictr   r\   r#   enumZUppercaseEnumr6   
namedtupler
   rJ   rK   	Exceptionr   caser   r   r   r'   objectr"   rU   ZTextTestRunnerrY   rt   r   r   r   r   r   r   r   Zget_stylistic_issuesZget_pyflakes_issuesZis_pep8_availabler   r   r   r   <module>   s\   



m
E-

 S