U
    c±³eH  ã                   @   sä   d Z ddlmZ ddlZddlZddlZddlmZ ddl	m
Z
mZmZ ddlmZ ddlmZmZmZmZ dd	lmZ d
dddgZedddge ƒZdd„ Zddd„ZG dd„ de
ƒZG dd„ deƒZG dd„ deƒZdd„ ZdS )ak
  
A Cython plugin for coverage.py

Requires the coverage package at least in version 4.0 (which added the plugin API).

This plugin requires the generated C sources to be available, next to the extension module.
It parses the C file and reads the original source files from it, which are stored in C comments.
It then reports a source file to coverage.py when it hits one of its lines during line tracing.

Basically, Cython can (on request) emit explicit trace calls into the C code that it generates,
and as a general human debugging helper, it always copies the current source code line
(and its surrounding context) into the C files before it generates code for that line, e.g.

::

      /* "line_trace.pyx":147
       * def cy_add_with_nogil(a,b):
       *     cdef int z, x=a, y=b         # 1
       *     with nogil:                  # 2             # <<<<<<<<<<<<<<
       *         z = 0                    # 3
       *         z += cy_add_nogil(x, y)  # 4
       */
       __Pyx_TraceLine(147,1,__PYX_ERR(0, 147, __pyx_L4_error))
      [C code generated for file line_trace.pyx, line 147, follows here]

The crux is that multiple source files can contribute code to a single C (or C++) file
(and thus, to a single extension module) besides the main module source file (.py/.pyx),
usually shared declaration files (.pxd) but also literally included files (.pxi).

Therefore, the coverage plugin doesn't actually try to look at the file that happened
to contribute the current source line for the trace call, but simply looks up the single
.c file from which the extension was compiled (which usually lies right next to it after
the build, having the same name), and parses the code copy comments from that .c file
to recover the original source files and their code as a line-to-file mapping.

That mapping is then used to report the ``__Pyx_TraceLine()`` calls to the coverage tool.
The plugin also reports the line of source code that it found in the C file to the coverage
tool to support annotated source representations.  For this, again, it does not look at the
actual source files but only reports the source code that it found in the C code comments.

Apart from simplicity (read one file instead of finding and parsing many), part of the
reasoning here is that any line in the original sources for which there is no comment line
(and trace call) in the generated C code cannot count as executed, really, so the C code
comments are a very good source for coverage reporting.  They already filter out purely
declarative code lines that do not contribute executable code, and such (missing) lines
can then be marked as excluded from coverage analysis.
é    )Úabsolute_importN)Údefaultdict)ÚCoveragePluginÚ
FileTracerÚFileReporter)Úcanonical_filenameé   )Úfind_root_package_dirÚis_package_dirÚis_cython_generated_fileÚopen_source_file©Ú__version__z.cz.cppz.ccz.cxxú.pyz.pyxz.pxdc                 C   s.   t jj}tD ]}| | }||ƒr|  S qd S ©N)ÚosÚpathÚexistsÚC_FILE_EXTENSIONS)Ú	base_pathZfile_existsÚextÚ	file_name© r   ú.lib/python3.8/site-packages/Cython/Coverage.pyÚ_find_c_sourceE   s    
r   Fc                 C   sD  t j |¡}t j |¡sð| d¡s&|rðt j t j | ¡|¡}t j |¡rTt j |¡}t j |¡d }t j |¡\}}t j |¡}t j |¡}t	t
| t j¡ƒt
| t j¡ƒƒ}|D ]\}	}
|	|
kr² qðq²t j | ¡d | }t j |¡rðt|ƒS t j |¡s<tjD ]6}t j t j ||¡¡}t j |¡rt|ƒ  S qt|ƒS )Nú.pxir   )r   r   Úabspathr   ÚendswithÚjoinÚdirnameÚsplitextÚnormpathÚzipÚreversedÚsplitÚsepr   ÚsysÚrealpath)Z	main_fileZ	file_pathÚrelative_path_searchÚabs_pathÚrel_file_pathZ
abs_no_extZfile_no_extÚ	extensionZmatching_pathsZoneÚotherZmatching_abs_pathZsys_pathZ	test_pathr   r   r   Ú_find_dep_file_pathN   s0    ÿ"
r-   c                   @   s`   e Zd ZdZdZdZdZdZdd„ Zdd„ Z	dd„ Z
d	d
„ Zdd„ Zdd„ Zdd„ Zdd„ ZdS )ÚPluginNr   c                 C   s
   dt fgS )NzCython versionr   ©Úselfr   r   r   Úsys_info|   s    zPlugin.sys_infoc                 C   s   |  d¡| _d S )Nzreport:exclude_lines)Z
get_optionÚ_excluded_line_patterns)r0   Zconfigr   r   r   Ú	configure   s    zPlugin.configurec                 C   s¬   |  d¡s|  d¡rdS d }}ttj |¡ƒ}| jrN|| jkrN| j| d }|dkrˆ|  |¡\}}|sldS |  ||¡\}}|dkrˆdS | jdkr˜i | _t	|||| j| jƒS )zR
        Try to find a C source file for a file path found by the tracer.
        ú<zmemory:Nr   )
Ú
startswithr   r   r   r   Ú_c_files_mapÚ_find_source_filesÚ_read_source_linesÚ_file_path_mapÚCythonModuleTracer)r0   ÚfilenameÚc_fileÚpy_fileÚ_Úcoder   r   r   Úfile_tracer„   s     
zPlugin.file_tracerc              	   C   s€   t tj |¡ƒ}| jr2|| jkr2| j| \}}}n2|  |¡\}}|sHd S |  ||¡\}}|d krdd S t||||| j 	|t
ƒ ¡ƒS r   )r   r   r   r   r6   r7   r8   ÚCythonModuleReporterÚ_excluded_lines_mapÚgetÚ	frozenset)r0   r;   r<   r*   r?   r>   r   r   r   Úfile_reporter¢   s     ûzPlugin.file_reporterc           
      C   s  t j |¡\}}| ¡ }|tkr"nš|dkrPt d|tj¡}|r¼|d | ¡ … }nl|dkr~t d|tj¡}|r¼|d | ¡ … }n>|dkr¸|  	t j 
|¡|¡ || jkr¼| j| d d fS ndS |tkrÈ|nt|ƒ}|d kr.t |¡}t j ||¡ t jj¡}t|ƒdkr.t j t j 
|¡d	 |¡¡}t|ƒ}d }	|rˆt j |¡d d
 }	t j |	¡s^d }	t|ddsˆ|	r„t j |¡r„d }	d }||	fS )Nz.pydz[.]cp[0-9]+-win[_a-z0-9]*$z.soz&[.](?:cpython|pypy)-[0-9]+[-_a-z0-9]*$r   r   ©NNr   Ú.r   F)Zif_not_found)r   r   r    ÚlowerÚMODULE_FILE_EXTENSIONSÚreÚsearchÚIÚstartÚ_find_c_source_filesr   r6   r   r   r	   ZuncachedÚrelpathr$   r%   Úlenr   r   r   )
r0   r;   Úbasenamer   Zplatform_suffixr<   Zpackage_rootÚpackage_pathZtest_basepathZpy_source_filer   r   r   r7   »   sD    


zPlugin._find_source_filesc                 C   s†   t j |¡sdS t jj}t  |¡D ]B}||ƒd  ¡ }|tkr"|  t j ||¡|¡ || j	kr" dS q"t
|ƒr‚|  t j |¡|¡ dS )z¦
        Desperately parse all C files in the directory or its package parents
        (not re-descending) to find the (included) source file in one of them.
        Nr   )r   r   Úisdirr    ÚlistdirrH   r   r8   r   r6   r
   rN   r   )r0   Zdir_pathÚsource_filer    r;   r   r   r   r   rN   ì   s    
zPlugin._find_c_source_filesc                 C   sš   | j dkri | _ || j kr&| j | }n|  |¡}|| j |< | jdkrJi | _| ¡ D ]&\}}t||dd}|||f| j|< qR|| jkrˆdS | j| dd… S )zý
        Parse a Cython generated C/C++ source file and find the executable lines.
        Each executable line starts with a comment header that states source file
        and line number, as well as the surrounding range of source code lines.
        NT)r(   rF   r   )Ú_parsed_c_filesÚ_parse_cfile_linesr6   Úitemsr-   )r0   r<   Z
sourcefileÚ
code_linesr;   r?   r)   r   r   r   r8   þ   s     




ÿ
zPlugin._read_source_linesc              	   C   sÆ  t  d¡j}t  d¡j}t  d¡j}t  d¡j}t  d¡j}| jrbt  d dd„ | jD ƒ¡¡j}nd	d
„ }ttƒ}ttƒ}	d}
| j	dkr’ttƒ| _	t
|ƒæ}t|ƒ}|D ]Ò}||ƒ}|sîd|kr¨|
dk	r¨||ƒ}|r¨|	|
  t| d¡ƒ¡ q¨| ¡ \}}|}
t|ƒ}|D ]n}||ƒ}|rh| d¡ ¡ }||ƒr8 q¨||ƒrV| j	|  |¡  q¨||| |<  q¨n||ƒr
 q¨q
q¨W 5 Q R X | ¡ D ]2\}}t|ƒ |	 |d¡¡}|D ]}||= q°qŽ|S )zb
        Parse a C file and extract all source file lines that generated executable code.
        z */[*] +"(.*)":([0-9]+)$z *[*] (.*) # <<<<<<+$z *[*]/$z *__Pyx_TraceLine\(([0-9]+),zX\s*c(?:type)?def\s+(?:(?:public|external)\s+)?(?:struct|union|enum|class)(\s+[^:]+|)\s*:ú|c                 S   s   g | ]}d | ‘qS )z(?:%s)r   )Ú.0Zregexr   r   r   Ú
<listcomp>'  s     z-Plugin._parse_cfile_lines.<locals>.<listcomp>c                 S   s   dS )NFr   )Úliner   r   r   Ú<lambda>)  ó    z+Plugin._parse_cfile_lines.<locals>.<lambda>Nz__Pyx_TraceLine(r   r   )rJ   ÚcompileÚmatchr2   r   rK   r   ÚdictÚsetrB   ÚopenÚiterÚaddÚintÚgroupÚgroupsÚrstriprX   Ú
differencerC   )r0   r<   Zmatch_source_path_lineZmatch_current_code_lineZmatch_comment_endZmatch_trace_lineZnot_executableZline_is_excludedrY   Zexecutable_linesZcurrent_filenameÚlinesr]   ra   Z
trace_liner;   ÚlinenoZcomment_lineÚ	code_lineZ
dead_linesr   r   r   rW     s\    ÿ 





zPlugin._parse_cfile_lines)Ú__name__Ú
__module__Ú__qualname__r9   r6   rV   rB   r2   r1   r3   r@   rE   r7   rN   r8   rW   r   r   r   r   r.   p   s   1r.   c                       s0   e Zd ZdZ‡ fdd„Zdd„ Zdd„ Z‡  ZS )r:   zA
    Find the Python/Cython source file for a Cython module.
    c                    s0   t t| ƒ ¡  || _|| _|| _|| _|| _d S r   )Úsuperr:   Ú__init__Úmodule_filer=   r<   r6   r9   )r0   rt   r=   r<   Zc_files_mapZfile_path_map©Ú	__class__r   r   rs   Y  s    zCythonModuleTracer.__init__c                 C   s   dS )NTr   r/   r   r   r   Úhas_dynamic_source_filenamea  s    z.CythonModuleTracer.has_dynamic_source_filenamec                 C   s˜   |j j}z| j| W S  tk
r(   Y nX t||ƒ}| jr`|dd…  ¡ dkr`| j| j|< | jS | jdk	snt‚|| jkrŠ| j	|df| j|< || j|< |S )zR
        Determine source file path.  Called by the function call tracer.
        éýÿÿÿNr   )
Úf_codeÚco_filenamer9   ÚKeyErrorr-   r=   rH   r6   ÚAssertionErrorr<   )r0   r;   ÚframerU   r)   r   r   r   Údynamic_source_filenamed  s    


z*CythonModuleTracer.dynamic_source_filename)ro   rp   rq   Ú__doc__rs   rw   r~   Ú__classcell__r   r   ru   r   r:   U  s   r:   c                       sH   e Zd ZdZ‡ fdd„Zdd„ Zdd„ Zdd	„ Zd
d„ Zdd„ Z	‡  Z
S )rA   zP
    Provide detailed trace information for one source file to coverage.py.
    c                    s,   t t| ƒ |¡ || _|| _|| _|| _d S r   )rr   rA   rs   Únamer<   Ú_codeÚ_excluded_lines)r0   r<   rU   r*   r?   Úexcluded_linesru   r   r   rs     s
    zCythonModuleReporter.__init__c                 C   s
   t | jƒS )zJ
        Return set of line numbers that are possibly executable.
        )rc   r‚   r/   r   r   r   rl   †  s    zCythonModuleReporter.linesc                 C   s   | j S )zM
        Return set of line numbers that are excluded from coverage.
        )rƒ   r/   r   r   r   r„   Œ  s    z#CythonModuleReporter.excluded_linesc                 c   sL   d}t | j ¡ ƒD ]4\}}||kr2g V  |d7 }qd|fgV  |d7 }qd S )Nr   Útxt)Úsortedr‚   rX   )r0   Zcurrent_lineZline_norn   r   r   r   Ú_iter_source_tokens’  s    
z(CythonModuleReporter._iter_source_tokensc              
   C   sR   t j | j¡r6t| jƒ}| ¡ W  5 Q R £ S Q R X nd dd„ |  ¡ D ƒ¡S dS )zA
        Return the source code of the file as a string.
        Ú
c                 s   s"   | ]}|r|d  d ndV  qdS )r   r   Ú Nr   )r[   Útokensr   r   r   Ú	<genexpr>£  s   ÿz.CythonModuleReporter.source.<locals>.<genexpr>N)r   r   r   r;   r   Úreadr   r‡   )r0   Úfr   r   r   Úsource›  s    
þzCythonModuleReporter.sourcec              	   c   s`   t j | j¡rBt| jƒ"}|D ]}d| d¡fgV  qW 5 Q R X n|  ¡ D ]}d|fgV  qJdS )z6
        Iterate over the source code tokens.
        r…   rˆ   N)r   r   r   r;   r   rj   r‡   )r0   r   r]   r   r   r   Úsource_token_lines§  s     z'CythonModuleReporter.source_token_lines)ro   rp   rq   r   rs   rl   r„   r‡   rŽ   r   r€   r   r   ru   r   rA   {  s   	rA   c                 C   s   t ƒ }|  |¡ |  |¡ d S r   )r.   Zadd_configurerZadd_file_tracer)ZregZoptionsZpluginr   r   r   Úcoverage_init´  s    
r   )F)r   Z
__future__r   rJ   Úos.pathr   r&   Úcollectionsr   Zcoverage.pluginr   r   r   Zcoverage.filesr   ZUtilsr	   r
   r   r   r‰   r   r   rc   rI   r   r-   r.   r:   rA   r   r   r   r   r   Ú<module>   s$   0	
" f&9