U
    /e?                     @  sH  U d Z ddlm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 ddlmZmZ ddlZddlZddlm
Z
 dd	 Zd
ddddZi Zded< G dd dZG dd deddZG dd deddZG dd deddZG dd deddZG d d! d!ZG d"d# d#ed$dZG d%d& d&ed'dZG d(d) d)ZdS )*aR  A pytest plugin to trace resource leaks.

Usage
-----
This plugin is enabled from the command line with -L / --leaks.
See `pytest --help` for further configuration settings.

You may mark individual tests as known to be leaking with the fixture

    @pytest.mark.leaking(check1, check2, ...)

Where the valid checks are 'fds', 'memory', 'threads', 'processes', 'tracemalloc'.

e.g.

    @pytest.mark.leaking("threads")

If you do, the specified checks won't report errors.

Known issues
------------
- Tests that contain imports will be flagged as leaking RAM (memory and tracemallock
  checks) if it's the first time in the test suite that the import happens; e.g.

      def test1():
          pytest.importorskip("numpy")

  Same issue happens with tests that populate global caches (e.g. linecache, re).
  A previous version of this plugin had an option to silently retry a test once after a
  failure; that version is no longer working as of the latest pytest. Reinstating the
  flag would solve this issue. See pytest_rerunfailures code for inspiration.

- The @gen_cluster fixture leaks 2 fds on the first test decorated with it within a test
  suite; This issue would also be fixed by rerunning failing tests.

- The @pytest.mark.flaky decorator (pytest_rerunfailures) completely disables this
  plugin for the decorated tests.

- You cannot expect the process memory to go down immediately and deterministically as
  soon as you garbage collect Python objects. This makes the 'memory' check very
  unreliable. On Linux, this can be improved by reducing the MALLOC_TRIM glibc setting
  (see distributed.yaml).
    )annotationsN)defaultdict)sleep)AnyClassVar)timec                 C  s`   |  d}dtdd tD }|jddd| dd	 |jd
tddd |jdddd d S )Nzresource leaksz, c                 s  s   | ]}d | V  qdS )z'%s'N ).0sr   r   D/tmp/pip-unpacked-wheel-g426oqom/distributed/pytest_resourceleaks.py	<genexpr>?   s     z#pytest_addoption.<locals>.<genexpr>z-Lz--leakszList of resources to monitor for leaks before and after each test. Can be 'all' or a comma-separated list of resource names (possible values: z).)helpz--leaks-timeoutg      ?zWWait at most these many seconds before marking a test as leaking (default: %(default)s))typedefaultr   z--leaks-fail
store_truezMark leaked tests as failed)actionr   )Zgetgroupjoinsortedall_checkersZ	addoptionfloat)parsergroupZknown_checkersr   r   r   pytest_addoption=   s$    

r   zpytest.ConfigNone)configreturnc                 C  s   |  d}|sd S |dkr.tdd tD }n
|d}tt|tt }|r^td|dd |D }t||  d	|  d
d}| j|d d S )Nleaksallc                 s  s   | ]}|d kr|V  qdS )demoNr   r	   cr   r   r   r   Z   s      z#pytest_configure.<locals>.<genexpr>,zunknown resources: c                 S  s   g | ]}t |  qS r   r   )r	   Zleakr   r   r   
<listcomp>a   s     z$pytest_configure.<locals>.<listcomp>Zleaks_timeoutZ
leaks_failcheckersgrace_delaymark_failedZleaks_checker)	getvaluer   r   splitset
ValueErrorLeakCheckerZpluginmanagerregister)r   r   unknownr%   checkerr   r   r   pytest_configureU   s     

r0   z dict[str, type[ResourceChecker]]r   c                   @  s   e Zd ZU ded< ddddZddd	d
ZddddZddddZddddZddddddZ	ddddddZ
dS )ResourceCheckerzClassVar[str]namestrr2   c                 C  s   |t kst|| _| t |< d S N)r   AssertionErrorr2   )clsr2   r   r   r   __init_subclass__p   s    z!ResourceChecker.__init_subclass__r   r   c                 C  s   d S r5   r   selfr   r   r   on_start_testu   s    zResourceChecker.on_start_testc                 C  s   d S r5   r   r:   r   r   r   on_stop_testx   s    zResourceChecker.on_stop_testc                 C  s   d S r5   r   r:   r   r   r   on_retry{   s    zResourceChecker.on_retryr   c                 C  s   t d S r5   NotImplementedErrorr:   r   r   r   measure~   s    zResourceChecker.measureboolbeforeafterr   c                 C  s   t d S r5   r?   r;   rD   rE   r   r   r   has_leak   s    zResourceChecker.has_leakc                 C  s   t d S r5   r?   rF   r   r   r   format   s    zResourceChecker.formatN)__name__
__module____qualname____annotations__r8   r<   r=   r>   rA   rG   rH   r   r   r   r   r1   m   s   
r1   c                   @  sT   e Zd ZU dZded< dd ZddddZddd	d
ddZdddd
ddZdS )DemoCheckerzKChecker that always leaks. Used to test the core LeakChecker functionality.intic                 C  s
   d| _ d S Nr   rO   r:   r   r   r   __init__   s    zDemoChecker.__init__r9   c                 C  s   |  j d7  _ | j S N   rQ   r:   r   r   r   rA      s    zDemoChecker.measurerB   rC   c                 C  s   ||kS r5   r   rF   r   r   r   rG      s    zDemoChecker.has_leakr3   c                 C  s   d| d| S )Nzcounter increased from z to r   rF   r   r   r   rH      s    zDemoChecker.formatN)	rI   rJ   rK   __doc__rL   rR   rA   rG   rH   r   r   r   r   rM      s   
rM   r   r4   c                   @  s>   e Zd ZddddZddddddZddd	dd
dZdS )	FDCheckerrN   r9   c                 C  s   t jdkrdS t  S d S )Nwin32r   )sysplatformpsutilProcessZnum_fdsr:   r   r   r   rA      s    
zFDChecker.measurerB   rC   c                 C  s   ||kS r5   r   rF   r   r   r   rG      s    zFDChecker.has_leakr3   c                 C  s   d||  d| d| dS )Nleaked z file descriptor(s) (z->)r   rF   r   r   r   rH      s    zFDChecker.formatNrI   rJ   rK   rA   rG   rH   r   r   r   r   rV      s   rV   Zfdsc                   @  sB   e Zd ZdZddddZdddddd	Zddd
dddZdS )RSSMemoryCheckeri   rN   r9   c                 C  s   t   jS r5   )rZ   r[   Zmemory_infoZrssr:   r   r   r   rA      s    zRSSMemoryChecker.measurerB   rC   c                 C  s   ||| j  kS r5   LEAK_THRESHOLDrF   r   r   r   rG      s    zRSSMemoryChecker.has_leakr3   c                 C  s   d|| d ddS )Nr\      .1fz MiB of RSS memoryr   rF   r   r   r   rH      s    zRSSMemoryChecker.formatN)rI   rJ   rK   ra   rA   rG   rH   r   r   r   r   r_      s   r_   Zmemoryc                   @  s>   e Zd ZddddZddddddZddd	dd
dZdS )ActiveThreadsCheckerzset[threading.Thread]r9   c                 C  s   t t S r5   )r*   	threading	enumerater:   r   r   r   rA      s    zActiveThreadsChecker.measurerB   rC   c                 C  s
   ||k S r5   r   rF   r   r   r   rG      s    zActiveThreadsChecker.has_leakr3   c                 C  s,   || }|st dt| dt|td S )Nr\   z Python thread(s): )key)r6   lenr   r3   r;   rD   rE   leakedr   r   r   rH      s    zActiveThreadsChecker.formatNr^   r   r   r   r   rd      s   rd   threadsc                   @  sn   e Zd ZU dZded< ded< ded< ded	< d
dddZddddZdddddZdddddZdS )ChildProcesszChild process info

    We use pid and creation time as keys to disambiguate between processes (and protect
    against pid reuse); other properties such as cmdline may change for a given process
    rN   pidr3   r2   z	list[str]cmdliner   create_timezpsutil.Process)pc                 C  s*   |j | _ | | _| | _| | _d S r5   )rm   r2   rn   ro   )r;   rp   r   r   r   rR      s    

zChildProcess.__init__r9   c                 C  s   | j S r5   )rm   r:   r   r   r   __hash__   s    zChildProcess.__hash__objectrB   )otherr   c                 C  s"   t |to | j|jko | j|jkS r5   )
isinstancerl   rm   ro   r;   rs   r   r   r   __eq__   s
    


zChildProcess.__eq__c                 C  s   t |tst|| j|jk S r5   )rt   rl   	TypeErrorrm   ru   r   r   r   __lt__   s    
zChildProcess.__lt__N)	rI   rJ   rK   rU   rL   rR   rq   rv   rx   r   r   r   r   rl      s   
rl   c                   @  s>   e Zd ZddddZddddddZddd	dd
dZdS )ChildProcessesCheckerzset[ChildProcess]r9   c              
   C  s   t  }t }|jddD ]}zl| Z | |jkrptj	|
 tjrptdd | D rpW 5 Q R  W q|t| W 5 Q R X W q tjk
r   Y qX q|S )NT)	recursivec                 s  s"   | ]}| d p| dV  qdS )z1from multiprocessing.resource_tracker import mainz+from multiprocessing.forkserver import mainN)
startswithr	   ar   r   r   r      s   
z0ChildProcessesChecker.measure.<locals>.<genexpr>)r*   rZ   r[   childrenZoneshotZppidrm   ospathsamefileZexerX   
executableanyrn   addrl   ZNoSuchProcess)r;   r~   rp   r    r   r   r   rA      s$    

zChildProcessesChecker.measurerB   rC   c                 C  s
   ||k S r5   r   rF   r   r   r   rG   	  s    zChildProcessesChecker.has_leakr3   c                 C  s8   t || }|stdt| dddd |D  S )Nr\   z processes:

c                 s  s*   | ]"}d |j  d|jd|jV  qdS )z  - pid=z, name=z
, cmdline=N)rm   r2   rn   )r	   rp   r   r   r   r     s    z/ChildProcessesChecker.format.<locals>.<genexpr>)r   r6   rh   r   ri   r   r   r   rH     s
    zChildProcessesChecker.formatNr^   r   r   r   r   ry      s   ry   Z	processesc                   @  sf   e Zd ZdZdZdZddddZdddd	Zd
dddZd
d
ddddZ	d
d
ddddZ
dS )TracemallocMemoryCheckerrb      i   r   r9   c                 C  s   t d d S rS   )tracemallocstartr:   r   r   r   r<     s    z&TracemallocMemoryChecker.on_start_testc                 C  s   t   d S r5   )r   stopr:   r   r   r   r=     s    z%TracemallocMemoryChecker.on_stop_testz tuple[int, tracemalloc.Snapshot]c                 C  s   t  \}}t  }||fS r5   )r   Zget_traced_memoryZtake_snapshot)r;   current_Zsnapr   r   r   rA   "  s    z TracemallocMemoryChecker.measurerB   rC   c                 C  s   |d |d | j  kS rP   r`   rF   r   r   r   rG   '  s    z!TracemallocMemoryChecker.has_leakr3   c                 C  s   |\}}|\}}| |d}d|| d ddg}|d | j D ]`}	|	jpP|	j}
|
| jk r` q|	jpj|	j}|d|
d dd| dg7 }|d	d
 |	j D 7 }qBd	|S )N	tracebackr\   rb   rc   z MiB of traced Python memoryz  - leaked z MiB in z
 calls at:c                 S  s   g | ]}d | qS )z    r   )r	   liner   r   r   r#   A  s     z3TracemallocMemoryChecker.format.<locals>.<listcomp>r   )
Z
compare_toNDIFF	size_diffsizeMIN_SIZE_DIFFZ
count_diffcountr   rH   r   )r;   rD   rE   Zbytes_beforeZsnap_beforeZbytes_afterZ
snap_afterZdifflinesstatr   r   r   r   r   rH   .  s    
zTracemallocMemoryChecker.formatN)rI   rJ   rK   ra   r   r   r<   r=   rA   rG   rH   r   r   r   r   r     s   r   r   c                   @  s  e Zd ZU ded< ded< ded< ded< d	ed
< ded< ded< ddddddZddddZdddddZdddddZdddddZddddd Z	e
jd!d"d#d$ Ze
jd!d"d%d& Ze
jd!d!d'd(d) Ze
jd!d!d'd*d+ Ze
jd,d- Zd.S )/r,   zlist[ResourceChecker]r%   r   r&   rB   r'   zdict[str, set[ResourceChecker]]skip_checkersz7dict[str, dict[ResourceChecker, list[tuple[Any, Any]]]]countersz1dict[str, list[tuple[ResourceChecker, Any, Any]]]r   zdefaultdict[str, set[str]]outcomesr$   c                 C  s2   || _ || _|| _i | _i | _i | _tt| _d S r5   )	r%   r&   r'   r   r   r   r   r*   r   )r;   r%   r&   r'   r   r   r   rR   T  s    zLeakChecker.__init__r   r9   c                 C  s   t   d S r5   )gcZcollectr:   r   r   r   cleanupc  s    zLeakChecker.cleanupr3   )nodeidr   c                   s    fddj D S )Nc                   s"   g | ]}|j  d kr|qS )r   )r   getr   r   r;   r   r   r#   g  s      z/LeakChecker.checks_for_item.<locals>.<listcomp>)r%   r;   r   r   r   r   checks_for_itemf  s    zLeakChecker.checks_for_itemz!list[tuple[ResourceChecker, Any]]c                 C  s   dd |  |D S )Nc                 S  s   g | ]}||  fqS r   )rA   r   r   r   r   r#   k  s     z'LeakChecker.measure.<locals>.<listcomp>)r   r   r   r   r   rA   i  s    zLeakChecker.measurec                 C  sT   |  |D ]}|  q
| |D ],\}}|d k	s6t| j| | |d f q"d S r5   )r   r<   rA   r6   r   append)r;   r   r/   rD   r   r   r   measure_before_testm  s
    
zLeakChecker.measure_before_testc           
        s   j   }d|krd S dd fdd}t }|j }| }|rj  |D ]\}}}|  qP| }|rt |k rtd   |D ]\}}}|  q| }qj|r|j < nj d   D ]}	|		  qd S )NZpassedz&list[tuple[ResourceChecker, Any, Any]]r9   c                    s^   g }   D ]J\}}j  | }|d \}}||f|d< |||r| |||f q| S )N)rA   r   rG   r   )r   r/   rE   r    rD   r   r   r   r   run_measurements|  s    z8LeakChecker.measure_after_test.<locals>.run_measurementsg?)
r   r   r&   r   r>   r   r   popr   r=   )
r;   r   r   r   t1deadliner   r    r   r/   r   r   r   measure_after_testt  s.    




zLeakChecker.measure_after_testT)hookwrapperc                 #  s   | j s
d S |j}|| jkstdd | j D | j|< |d}|rtt|jtt }|rtt	d| dt
t tdd |jD   fdd	| j D | j|< d V  d S )
Nc                 S  s   i | ]
}|g qS r   r   r   r   r   r   
<dictcomp>  s      z7LeakChecker.pytest_runtest_protocol.<locals>.<dictcomp>Zleakingz'pytest.mark.leaking: unknown resources z; must be one of c                 s  s   | ]}t | V  qd S r5   r"   r|   r   r   r   r     s     z6LeakChecker.pytest_runtest_protocol.<locals>.<genexpr>c                   s   h | ]}t | r|qS r   )rt   r   classesr   r   	<setcomp>  s    
 z6LeakChecker.pytest_runtest_protocol.<locals>.<setcomp>)r%   r   r   r6   Zget_closest_markerr   r*   argsr   r+   listtupler   )r;   itemnextitemr   Zleaking_markr.   r   r   r   pytest_runtest_protocol  s"    

z#LeakChecker.pytest_runtest_protocolc                 c  s   |  |j d V  d S r5   )r   r   )r;   r   r   r   r   pytest_runtest_setup  s    z LeakChecker.pytest_runtest_setup)r   Ztrylastc                 c  s6   d V  |  |j | j|j}|r2| jr2t  d S r5   )r   r   r   r   r'   pytestZfail)r;   r   r   r   r   r   pytest_runtest_teardown  s
    
z#LeakChecker.pytest_runtest_teardownc                 #  sz   |j  | j  |j d V }|jdkrv| j|j }|rv| jrl|d d|_d	 fdd|D |_
n
|d d S )NZteardown)failedLLEAKEDr   r   c              
     s0   g | ](\}}}  d |j  d||| qS )	 leaking : )r2   rH   )r	   r/   rD   rE   r   r   r   r#     s   z8LeakChecker.pytest_report_teststatus.<locals>.<listcomp>)rj   r   r   )r   r   r   outcomewhenr   r   r'   Zforce_resultr   Zlongrepr)r;   reportr   r   r   r   r   pytest_report_teststatus  s    


z$LeakChecker.pytest_report_teststatusc           
      C  sn   |}| d}|rj|dd |D ]F}|j}| j| D ]0\}}}	||j d|j d|||	  q6q"d S )Nrj   =zRESOURCE LEAKSr   r   )Z
getreportsZ	write_sepr   r   r   r2   rH   )
r;   ZterminalreporterZ
exitstatustrrj   repr   r/   rD   rE   r   r   r   pytest_terminal_summary  s    
z#LeakChecker.pytest_terminal_summaryN)rI   rJ   rK   rL   rR   r   r   rA   r   r   r   Zhookimplr   r   r   r   r   r   r   r   r   r,   F  s.   
5





r,   )rU   
__future__r   r   r   rX   re   r   collectionsr   r   r   typingr   r   rZ   r   Zdistributed.metricsr   r0   r   rL   r1   rM   rV   r_   rd   rl   ry   r   r,   r   r   r   r   <module>   s0   +")2