o
    i                  
   @  sd  d dl mZ d dlm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Zd dlZd dlZd dlmZ d dlZd dlZd dlmZ d dlmZ d d	lmZ d d
lmZ d dlmZ d dlmZ d dlmZ d dlmZ d dl m!Z! d dl"m#Z# d dl$m%Z% d dl$m&Z& d dl'm(Z( d dl)m*Z* d dl)m+Z+ d dl,m-Z- d dl.m/Z/ d dl0m1Z1 d dl2m3Z3 d dl4m5Z5 d dl6m7Z7 d dl8m9Z9 d dl8m:Z: d dl8m;Z; d dl8m<Z< d d l8m=Z= d d!l8m>Z> d d"l8m?Z? d d#l8m@Z@ d d$lAmBZB d d%lAmCZC d d&lAmDZD d d'lEmFZF d d(lEmGZG d d)lEmHZH d d*lEmIZI d d+lEmJZJ d dlKZLd d,lMmNZN d d-lMmOZO ejPr6d d.lQmRZR d/ZSd0ZTd1ZUzejVe5  ZWW n eXyP   d2ZWY nw d3ZYd4d5d6ZZe[e\Z]e^e_e`e_f Zae^e_e_ejbe_e^e_ece_edf f f f Zee^e_e`e_f Zfdd;d<Zgdd>d?ZhddAdBZiddEdFZjG dGdH dHZkejPrece_ejlf ZmnecZmG dIdJ dJZnG dKdL dLenZoG dMdN dNZpG dOdP dPZqddTdUZrddYdZZsdd\d]ZtddbdcZuddfdgZvejwdhdhdiddkdlZxddmdnZyddpdqZzddrdsZ{ddwdxZ|dd}d~Z}dddZ~dddZdddZdddZdddZejdddd Zejdddd ZdS )    )annotations)defaultdict)StringION)Path)runtestprotocol)clear_coverage_instance)stop_coverage)_is_pytest_cov_available)_is_pytest_cov_enabled)handle_coverage_report) get_source_lines_for_test_method)undecorated)CITag)
SetupError)get_workspace_path)catch_and_log_exceptions)setup_logging)BddTestOptPlugin)BenchmarkData)get_benchmark_tags_and_metrics)TestOptHooks)print_test_report_links)item_to_test_ref)RetryHandler)SessionManager)TelemetryAPI)Test)
TestModule)TestRef)TestRun)TestSession)
TestStatus)	TestSuite)TestTag)enable_all_ddtrace_integrations)install_global_trace_filter)trace_context)coverage_collection)get_coverage_percentage)install_coverage)install_coverage_percentage)uninstall_coverage_percentage)TestContext)asbool)TerminalReporterz!Flaky test is disabled by Datadogz*Skipped by Datadog Intelligent Test Runnerdatadog_itr_unskippablesession_manager_keypytestzno:rerunfailureszno:flaky)rerunfailuresflakyitempytest.Itemreturnr   c                 C  sR   zt | dd }|d ur| j jW S t| jj jW S  ty(   t  Y S w )Npath)	getattrr7   absoluteparentr   module__file__	Exceptioncwd)r4   	item_path r@   Z/home/ubuntu/.local/lib/python3.10/site-packages/ddtrace/testing/internal/pytest/plugin.py_get_module_path_from_item{   s   rB   workspace_pathc                 C  s,   t | }z||W S  ty   | Y S w )z=Get module path from pytest item, converted to relative path.)rB   relative_to
ValueError)r4   rC   abs_pathr@   r@   rA   #_get_relative_module_path_from_item   s   rG    tuple[t.Optional[str], int, int]c                 C  s   z&t t| dr
| jnt| dd }t||}t| |\}}|||fW S  tt	t
fyM   z|  \}}}||p<ddfW  Y S  t
yL   Y Y dS w w )aj  
    Extract test location information (file path, start line, end line) from a pytest item.

    Returns:
        Tuple of (relative_path, start_line, end_line)
        - relative_path: path relative to workspace, or None on failure
        - start_line: starting line number, or 0 if unavailable
        - end_line: ending line number, or 0 if unavailable
    r7   fspathunknownr   )Nr   r   )r   hasattrr7   r8   r9   strrD   _get_source_linesrE   OSErrorr=   
reportinfo)r4   rC   r?   relative_path
start_lineend_liner7   
_test_namer@   r@   rA   _get_test_location_info   s   
$rT   r?   tuple[int, int]c                 C  s   t | dsz|  d pddfW S  ty   Y dS w zt| j| j|}t|}|d p-d|d p2dfW S  tyT   z|  d pCddfW  Y S  tyS   Y Y dS w w )z
    Get start and end line numbers for a test item.

    Returns:
        Tuple of (start_line, end_line), with 0 indicating unavailable information
    _obj   r   )r   r   )rK   rO   r=   r   rV   namer   )r4   r?   test_method_objectsource_linesr@   r@   rA   rM      s"   
rM   c                   @  s   e Zd ZdZdZdZdZdS )	TestPhasesetupcallteardownFN)__name__
__module____qualname__SETUPCALLTEARDOWN__test__r@   r@   r@   rA   r[      s
    r[   c                   @  s$  e Zd ZdZdZdeddZdfddZdfddZdfddZdgddZ	dhddZ
ejdddddid"d#Zdjd'd(Zdkd)d*Zdld-d.Zdmd4d5Zdnd7d8Zdod:d;Zdpd<d=Zdqd@dAZdrdBdCZdsdGdHZdrdIdJZejddKdtdOdPZdudSdTZdvdWdXZdwdYdZZejddd[dxdbdcZddS )yTestOptPluginz.
    pytest plugin for test optimization.
    Fsession_managerr   r6   Nonec                 C  sb   d| _ ttjdrd| _ d| _tdd | _i | _i | _	i | _
d| _|| _| jj| _g | _d S )NT(_DD_CIVISIBILITY_USE_CI_CONTEXT_PROVIDERFc                   S  s   i S Nr@   r@   r@   r@   rA   <lambda>       z(TestOptPlugin.__init__.<locals>.<lambda>)enable_ddtrace_trace_filterr-   osenvirongetr$   r   reports_by_nodeidexcinfo_by_reportbenchmark_data_by_nodeidtests_by_nodeidis_xdist_workermanagersessionextra_failed_reports)selfrg   r@   r@   rA   __init__   s   

zTestOptPlugin.__init__rw   pytest.Sessionc                 C  s   t |jdd  }r|d }r| j| d| _|jdr"d| _| j  | j	  t
 jt| j	 | j	jtjd u d | jrJt| j	j | jrRt  d S d S )Nworkerinputdd_session_idTddtrace-patch-all)test_frameworkhas_codeownersis_unsupported_ci)r8   configrp   rw   set_session_idru   	getoptionr$   startrv   r   record_session_createdTEST_FRAMEWORKr   env_tagsr   PROVIDER_NAMErm   r%   writer)ry   rw   xdist_worker_input
session_idr@   r@   rA   pytest_sessionstart   s$   


z!TestOptPlugin.pytest_sessionstartc                   s    j |jtjjkrtjntj  j	r"t
|jdr" j j|jjd<  jjjr7d fd	d
}t|j|ttd tt|j}|d urO| j jtj< t  t   j   t jt j  jj t!j"d u  j # d  j	sx jj$% j   j  d S )Nworkeroutputtests_skipped_by_itrcoverage_report_bytesbytescoverage_formatrL   r6   boolc                   s    j j| |d dS )N)r   r   tags)rv   upload_coverage_report)r   r   ry   r@   rA   upload_func  s   z7TestOptPlugin.pytest_sessionfinish.<locals>.upload_func)r   r   is_pytest_cov_enabled_funcstop_coverage_func)r   r   r   efd_abort_reason)r   r   r   rL   r6   r   )&rw   
set_status
exitstatusr1   ExitCodeTESTS_FAILEDr!   FAILPASSru   rK   r   r   r   rv   settingscoverage_report_upload_enabledr   r
   r   r(   metricsr#   CODE_COVERAGE_LINES_PCTr+   r   finishr   rp   record_session_finishedr   r   r   r   r   &get_early_flake_detection_abort_reasonr   put_item)ry   rw   r   coverage_percentager@   r   rA   pytest_sessionfinish  s8   

z"TestOptPlugin.pytest_sessionfinishc                 C  s0   |j D ]}t|}| jj| q| j  dS )a&  
        Discover modules, suites, and tests that have been selected by pytest.

        NOTE: Using pytest_collection_finish instead of pytest_collection_modifyitems allows us to capture only the
        tests that pytest has selection for run (eg: with the use of -k as an argument).
        N)itemsr   rv   collected_testsaddfinish_collection)ry   rw   r4   test_refr@   r@   rA   pytest_collection_finish>  s   
z&TestOptPlugin.pytest_collection_finishr4   r5   r   r   "tuple[TestModule, TestSuite, Test]c                   s@   d fdd}dd	d
}d fdd}j j|||dS )zn
        Return the module, suite and test objects for a given test item, creating them if necessary.
        r;   r   r6   rh   c                   s   | j t jjd d S )N)module_path)set_locationrG   rv   rC   )r;   )r4   ry   r@   rA   _on_new_moduleP  s   z4TestOptPlugin._discover_test.<locals>._on_new_modulesuiter"   c                 S  s   d S rj   r@   )r   r@   r@   rA   _on_new_suiteS  s   z3TestOptPlugin._discover_test.<locals>._on_new_suitetestr   c                   s   t  jj\}}}| j|pd|d |rt|| jtj< t  }r(| 	| j
r6t r6|   t  }rC| | dS dS )zAInitialize test with location, parameters, and custom attributes.rJ   )r7   rQ   N)rT   rv   rC   r   rL   r   r#   
SOURCE_END_get_test_parameters_jsonset_parametersis_skippable_test_is_test_unskippablemark_unskippable_get_test_custom_tagsset_tags)r   rP   rQ   rR   
parameterscustom_tagsr4   ry   r   r@   rA   _on_new_testV  s   
z2TestOptPlugin._discover_test.<locals>._on_new_test)on_new_moduleon_new_suiteon_new_testN)r;   r   r6   rh   )r   r"   r6   rh   )r   r   r6   rh   )rv   discover_test)ry   r4   r   r   r   r   r@   r   rA   _discover_testK  s   
zTestOptPlugin._discover_testr   'Test'c                 C  st   | j jjjsdS | r| s|tjj	t
d dS | s(| r6| r8|tjjdddd dS dS dS )u  Apply test management markers for the base plugin (used when an external rerun plugin drives execution).

        ATF retries are not supported in this mode — the external plugin controls the protocol and we cannot intercept
        individual test runs to capture real FAIL statuses. Disabled+ATF tests therefore get the same xfail treatment
        as quarantined tests: failures won't break the pipeline, but ATF retry semantics are silently unavailable.
        If ATF support is needed, disable the external rerun plugin (see the warning emitted at configure time).

        Overridden in TestOptPluginWithProtocol, which drives retries itself and needs real FAIL outcomes for ATF.
        NreasonFdd_quarantinedTstrictr   run)rv   r   test_managementenabledis_disabledis_attempt_to_fix
add_markerr1   markskip"DISABLED_BY_TEST_MANAGEMENT_REASONis_quarantinedxfailry   r4   r   r@   r@   rA   _apply_test_management_markersw  s   
z,TestOptPlugin._apply_test_management_markersTpytest_runtest_protocol)tryfirsthookwrapperspecnamenextitemt.Optional[pytest.Item]t.Generator[None, None, None]c              	   c  s   t |}|rt |nd }| ||\}}}| s&|  t jtd | s6|  t jtd |  || j	|j
< | ||| | || t| j0}t jtdd t }	d V  W d    n1 smw   Y  t jtdd W d    n1 sw   Y  |jstd|j
 | }
|
j|jd | |
|| |
  |
 }|
| || | jj|
 |  | jj |j!|	j"| jj#d |r|j$|j$kr|  | jj| t j%td |r|j$j&|j$j&kr|  | jj| t j'td d S d S )N)r   ddtrace)r   coverage_libraryzrTest Optimization pytest_runtest_protocol did not run for %s; perhaps some plugin or conftest.py has overridden it)start_ns)rD   )(r   r   
is_startedr   r   rp   record_module_createdr   record_suite_createdrt   nodeid_handle_itrr   r&   rm   record_coverage_startedr'   record_coverage_finished	test_runslogdebugmake_test_runr   _set_test_run_datar   
get_statusset_final_statusr   rv   r   r   coverage_writerput_coveragelast_test_runget_coverage_bitmapsrC   r   record_suite_finishedr;   record_module_finished)ry   r4   r   r   next_test_reftest_module
test_suiter   contextcoverage_datatest_runfinal_statusr@   r@   rA   pytest_runtest_protocol_wrapper  s^   

z-TestOptPlugin.pytest_runtest_protocol_wrapperr   r,   tuple[TestRun, _ReportGroup]c                 C  s|   | j |j }| }|  t jt|d tt	||dd}| 
||| t jt|| jjtj| jjd ||fS )N)r   r   F)r   r   )r   r   ci_provider_nameis_auto_injected)rt   r   r   r   r   rp   record_test_createdr   _make_reports_dictr   r   record_test_finishedrv   r   r   r   r  )ry   r4   r   r   r   r   reportsr@   r@   rA   _do_one_test_run  s   zTestOptPlugin._do_one_test_runc                 C  s   | j |j }| |}t| j}| |||\}}W d    n1 s$w   Y  | s>|r>||r>| ||||| nt	|drI| 
|| | || |  ||  |jrg|jd |  |jD ]	}| jj| qjd S )Ndd_disabled_attempt_to_fix)rt   r    _check_applicable_retry_handlersr&   rm   r	  is_skipped_by_itrshould_retry_do_retries_get_user_property,_mark_attempt_to_fix_report_group_as_skipped_log_test_reportsr   r   r   r   r   rv   r   r   )ry   r4   r   r   retry_handlerr   r   r  r@   r@   rA   _do_test_runs  s"   


zTestOptPlugin._do_test_runsr   r   c                 C  sj   |  |j\}}|| || || | j|jd  }r3||j ||j	 |
  d S d S rj   )_get_test_outcomer   r   r   set_contextrs   popr   set_metricsr   mark_benchmark)ry   r   r4   r   statusr   benchmark_datar@   r@   rA   r     s   


z TestOptPlugin._set_test_run_datar   r  r   r  _ReportGroupc                 C  sp  t  }| || |||tj |||tj |j}|| |  d}|rmt	| j
}	| |||	\}}W d    n1 sBw   Y  ||}|| | || |||tjsg|||tj |  |s)||}
||
 ||||
}|||
 }r| j| t|dr| || |jj|d |tj}t|dr| || |jj|d d S )NTr
  report)RetryReports_mark_test_reports_as_retrylog_test_reportr[   rb   rc   r   set_tags_for_test_runr   r&   rm   r	  r  get_final_statusr   make_final_reportget_extra_failed_reportrx   appendr  &_mark_attempt_to_fix_report_as_skippedihookpytest_runtest_logreportrp   rd   )ry   r4   r   r   r  r  retry_reportsr   r  r   r   final_reportextra_failed_reportteardown_reportr@   r@   rA   r    s>   






zTestOptPlugin._do_retriest.Optional[RetryHandler]c                 C  s$   | j jD ]}||r|  S qd S rj   )rv   retry_handlersshould_apply)ry   r   handlerr@   r@   rA   r  =  s
   
z.TestOptPlugin._check_applicable_retry_handlerstuple[t.Any, t.Any]c                 C  sD   t jt jt jfD ]}|| }r|jr|jt|ddf  S qdS )a<  
        Extract the most relevant report `longrepr` for a report group.

        Errors that happened during the call phase have more useful information, so we try to use that if available.

        Also return the corresponding `wasxfail` attribute if present, for correct indication of xfail/xpass tests.
        wasxfailN)NN)r[   rc   rb   rd   rp   longreprr8   )ry   r  whenr  r@   r@   rA   _extract_longreprD  s   zTestOptPlugin._extract_longreprc                 C  s(   |  ||tjs|  ||tj d S d S rj   )_mark_test_report_as_retryr[   rc   rb   )ry   r  r  r@   r@   rA   r   S  s   z)TestOptPlugin._mark_test_reports_as_retryr  t.Optional[pytest.TestReport]c                 C  sP   |du rdS |j tjkrd|_dS |jd pd}t|j|df}||_d|_dS )a?  
        Modify a test report for an attempt-to-fix test to make it look like it was skipped.

        This is called *after* ``_get_test_outcome`` has already captured the real status (FAIL/PASS), so the
        backend sees the true result while the terminal/junitxml shows the test as skipped (quarantined).
        NpassedrW   r   zQuarantined (Attempt to Fix)skipped)r5  r[   rd   outcomelocationrL   r7   r4  )ry   r4   r  line_numberr4  r@   r@   rA   r'  W  s   

z4TestOptPlugin._mark_attempt_to_fix_report_as_skippedc                 C  s`   | tj }r| || d|tj _d|tj _dS | tj}| || d|tj _dS )zi
        Modify the test reports for an attempt-to-fix test to make it look like it was skipped.
        r9  N)rp   r[   rc   r'  rb   r;  rd   )ry   r4   r  call_reportsetup_reportr@   r@   rA   r  i  s   z:TestOptPlugin._mark_attempt_to_fix_report_group_as_skippedr5  rL   r   c                 C  s<   | | }r| jd|jfd| fg7  _d|_dS dS )Ndd_retry_outcomedd_retry_reasonrerunTF)rp   user_propertiesr;  get_pretty_name)ry   r  r  r5  r>  r@   r@   rA   r7  v  s   
z(TestOptPlugin._mark_test_report_as_retryc                 C  s6   t jt jt jfD ]}|| }r|jj|d qd S )Nr  )r[   rb   rc   rd   rp   r(  r)  )ry   r4   r  r5  r  r@   r@   rA   r    s
   zTestOptPlugin._log_test_reports)r   r]   pytest.CallInfo[t.Any]-t.Generator[None, pluggy.Result[t.Any], None]c                 c  s^    dV }|  }|| j|j |j< |j| j|< |jtjkr+t| }r-|| j	|j< dS dS dS )zF
        Save report and exception information for later use.
        N)

get_resultrq   r   r5  excinforr   r[   rd   r   rs   )ry   r4   r]   r;  r  r  r@   r@   rA   pytest_runtest_makereport  s   z'TestOptPlugin.pytest_runtest_makereportpytest.TestReportt.Optional[_ReportTestStatus]c                 C  s   t |d }rt |d}ddd|  d| dfS t|dd d	kr+d
ddddiffS t |drA|jtjkr?d
ddddiffS dS t |drOdddddiffS d S )Nr@  rA  rB  RzRETRY z ()r3  r   quarantinedQQUARANTINEDblueTr
  ) rR  rR  dd_flakyr3   KFLAKYyellow)r  upperr8   r5  r[   rd   )ry   r  retry_outcomeretry_reasonr@   r@   rA   pytest_report_teststatus  s   


z&TestOptPlugin.pytest_report_teststatusr   !tuple[TestStatus, dict[str, str]]c           
      C  s   t j}i }| j|i }tjtjtjfD ]Y}||}|sqt	|dd }r;|dkr;t
||tj< |jr6dnd|tj< | j|d}|jrTt j}|t|  ||fS |jrmt j}|rat
|jnd}	|	|tj<  ||fS q||fS )z
        Return test status and tags with exception/skip information for a given executed test.

        This methods consumes the test reports and exception information for the specified test, and removes them from
        the dictionaries.
        r3  Nr   xpassr   zUnknown skip reason)r!   r   rq   r  r[   rb   rc   rd   rp   r8   rL   r#   XFAIL_REASONr9  TEST_RESULTrr   failedr   update_get_exception_tagsr:  SKIPvalueSKIP_REASON)
ry   r   r  r   reports_dictphaser  r3  rH  r   r@   r@   rA   r    s0   

zTestOptPlugin._get_test_outcomec                 C  sP   | j |sd S | r|  d S | rd S |tjjt	d |
  d S )Nr   )rv   r   is_unskippablemark_forced_runr   r   r1   r   r   SKIPPED_BY_ITR_REASONmark_skipped_by_itr)ry   r4   r   r   r@   r@   rA   r     s   zTestOptPlugin._handle_itrr   r   terminalreporterr.   r   intr   pytest.Configc                 c  s    |j dd |j dg }| jD ]}d|_dd |jD |_q|| j |j d< dV  ||j d< |j d s:|j d= t|| j dS )a  
        Modify terminal summary before letting pytest emit it.

        During the test session, all retry attempt reports are logged with a 'rerun' category. We remove this
        category here so it doesn't show up in the final stat counts.

        To make the extra failed reports collected during retries (see `get_extra_failed_report` for details) show up
        with the rest of the failure exception reports, we modify them to look like normal failures, and append them to
        the failed reports. After they have been shown by pytest, we undo the change so that the final count of failed
        tests is not affected.
        rB  Nr_  c                 S  s    g | ]\}}|d kr||fqS )r@  r@   ).0kvr@   r@   rA   
<listcomp>  s     z9TestOptPlugin.pytest_terminal_summary.<locals>.<listcomp>)statsr  rp   rx   r;  rC  r   rv   )ry   rl  r   r   original_failed_reportsr  r@   r@   rA   pytest_terminal_summary  s   


z%TestOptPlugin.pytest_terminal_summaryN)rg   r   r6   rh   )rw   r{   r6   rh   )r4   r5   r   r   r6   r   r4   r5   r   r   r6   rh   )r4   r5   r   r   r6   r   )r4   r5   r   r   r   r,   r6   r  )r4   r5   r   r   r6   rh   )r   r   r4   r5   r   r,   r6   rh   )r4   r5   r   r   r   r   r  r   r  r  r6   rh   )r   r   r6   r.  )r  r  r6   r2  )r  r  r  r   r6   rh   )r4   r5   r  r8  r6   rh   )r4   r5   r  r  r6   rh   )r  r  r  r   r5  rL   r6   r   )r4   r5   r]   rE  r6   rF  )r  rJ  r6   rK  )r   rL   r6   r[  )r4   r5   r   r   r   r   r6   rh   )rl  r.   r   rm  r   rn  r6   r   )r_   r`   ra   __doc__re   rz   r   r   r   r   r   r1   hookimplr  r	  r  r   r  r  r6  r   r'  r  r7  r  rI  rZ  r  r   ru  r@   r@   r@   rA   rf      s:    



2

,
@



<









$rf   c                   @  s*   e Zd ZdZddd	Ze dddZdS )TestOptPluginWithProtocolax  
    TestOptPlugin subclass that registers pytest_runtest_protocol, taking ownership of test execution and retries.
    Used when no external rerun plugin (e.g. pytest-rerunfailures, flaky) is present. When such a plugin IS present,
    the base class is registered instead, so the external plugin drives retries while the wrapper hook still handles
    span bookkeeping.
    r4   r5   r   r   r6   rh   c                 C  s   | j jjjsdS | r| s|tjj	t
d dS | s(| rD| rF| r6| jdg7  _dS |tjjdddd dS dS dS )u  Apply test management markers for the plugin that drives retries itself.

        ATF tests must NOT use xfail here: xfail converts failures into SKIP outcomes, which prevents
        AttemptToFixHandler.get_final_status from seeing real FAIL counts and computing correct tags
        (HAS_FAILED_ALL_RETRIES, ATTEMPT_TO_FIX_PASSED). Instead, ATF tests set a user property so
        _get_test_outcome captures the true status, and reports are mangled to "skipped" afterwards so
        failures still don't break the pipeline.

        The outer condition mirrors the original logic: only quarantined tests and disabled+ATF tests
        are treated as "run but don't fail the pipeline" — plain ATF (neither disabled nor quarantined)
        runs normally without any report suppression.
        Nr   )r
  TFr   Tr   )rv   r   r   r   r   r   r   r1   r   r   r   r   rC  r   r   r@   r@   rA   r   	  s   z8TestOptPluginWithProtocol._apply_test_management_markersr   r   r   c                 C  s8   |j j|j|jd | || |j j|j|jd dS )N)r   r<  T)r(  pytest_runtest_logstartr   r<  r  pytest_runtest_logfinish)ry   r4   r   r@   r@   rA   r   "  s   z1TestOptPluginWithProtocol.pytest_runtest_protocolNrv  )r4   r5   r   r   r6   r   )r_   r`   ra   rw  r   r   r   r@   r@   r@   rA   ry    s
    
ry  c                   @  s6   e Zd ZdZdd ZdddZdddZdddZdS )r  zF
    Collect and manage reports for the retries of a single test.
    c                 C  s   t dd | _d S )Nc                   S  s   g S rj   r@   r@   r@   r@   rA   rk   0  rl   z'RetryReports.__init__.<locals>.<lambda>)r   reports_by_outcomer   r@   r@   rA   rz   /  s   zRetryReports.__init__r4   r5   r  r  r5  rL   r6   r   c                 C  sD   | | }r |jj|d t|dp|j}| j| | dS dS )z
        Collect and log the test report for a given test phase, if it exists.

        Returns True if the report exists, and False if not.
        Tests that fail or skip during setup do not have the call phase report.
        r  r@  TF)rp   r(  r)  r  r;  r|  r&  )ry   r4   r  r5  r  r;  r@   r@   rA   r!  2  s   zRetryReports.log_test_reportr   r   r   r!   rJ  c              	   C  s   t jdt jdt jdi}||t|}z| j| d }|j}t|dd}W n t	y8   t
d| d}d}Y nw g }	| rD|	dg7 }	tj|j|jd	d
 |jD tj|||j|	 d}
|durft|
d| |
S )a  
        Make the final report for the retries of a single test.

        The final status is provided by the retry handler. We assume that for a test to have a given outcome, at least
        one attempt must have had that outcome (e.g., for a test to have a 'failed' outcome, at least one retry must
        have failed).

        The attributes of the final report will be copied from the first retry report that had the same outcome. For
        instance, if the final report outcome is 'failed', it will have the same `longrepr` and `wasxfail` features as
        the first failed report.

        Additionally, if a test is marked as flaky (e.g., it both passed and failed during EFD), the final report will
        contain the `dd_flaky` user property. This allows us to identify the test as 'flaky' during logging, even though
        the final outcome is still one of 'passed', 'failed', or 'skipped'.
        r9  r_  r:  r   r3  NzUTest %s has final outcome %r, but no retry had this outcome; this should never happen)rS  Tc                 S  s   i | ]}|d qS )rW   r@   )ro  rp  r@   r@   rA   
<dictcomp>j  s    z2RetryReports.make_final_report.<locals>.<dictcomp>)r   r<  keywordsr5  r4  r;  rC  )r!   r   r   rb  rp   rL   r|  r4  r8   
IndexErrorr   warningis_flaky_runr1   
TestReportr   r<  r~  r[   rc   rC  setattr)ry   r   r4   r   outcomesr;  source_reportr4  r3  extra_user_propertiesr+  r@   r@   rA   r$  A  s:   
	zRetryReports.make_final_reportr8  c                 C  sH   |  s| o|  }|rdS | jd r"|tjkr"| jd d S dS )aW  
        Get an extra failed report to log at the end of the test session.

        If a test is retried and all retries failed, the final report will have a 'failed' status and contain the
        `longrepr` of one of the failures, and so the failure exception will be automatically logged at the end of the
        test session by pytest. In this case, this function returns None.

        But if some retries pass and others fail, the test will have a 'passed' final status, and no failure exception
        will be logged for the test. In this case, this function returns one of the failed reports, which is saved for
        logging at the end of the test session. This ensures we provide some failure log to the user.

        If the test is quarantined or disabled, and not attempt-to-fix, the failed report is not returned. If the test
        is attempt-to-fix, the failed report is returned, even if the test is quarantined or disabled: if the user is
        attempting to fix the test, and the attempt fails, we need to provide some feedback on the failure.

        Note that we only report _one_ failure per test (either the one embedded in the 'failed' final report, or the
        one returned by this function), even if the test failed multiple times. This is to avoid spamming the test
        output with multiple copies of the same error.
        Nr_  r   )r   r   r   r|  r!   r   )ry   r   r   suppress_errorsr@   r@   rA   r%  u  s   z$RetryReports.get_extra_failed_reportN)r4   r5   r  r  r5  rL   r6   r   )r   r   r4   r5   r   r!   r6   rJ  )r   r   r   r!   r6   r8  )r_   r`   ra   rw  rz   r!  r$  r%  r@   r@   r@   rA   r  *  s    

4r  c                   @  s6   e Zd ZdddZejdd	d
ZejdddZdS )XdistTestOptPluginmain_pluginrf   r6   rh   c                 C  s
   || _ d S rj   )r  )ry   r  r@   r@   rA   rz     s   
zXdistTestOptPlugin.__init__nodet.Anyc                 C  s   | j jj|jd< dS )zN
        Pass test session id from the main process to xdist workers.
        r}   N)r  rw   item_idr|   )ry   r  r@   r@   rA   pytest_configure_node  s   z(XdistTestOptPlugin.pytest_configure_nodeerrorc                 C  s8   t |dsdS |jd }r| jj j|7  _dS dS )zs
        Collect count of tests skipped by ITR from a worker node and add it to the main process' session.
        r   Nr   )rK   r   rp   r  rw   r   )ry   r  r  r   r@   r@   rA   pytest_testnodedown  s
   
z&XdistTestOptPlugin.pytest_testnodedownN)r  rf   r6   rh   )r  r  r6   rh   )r  r  r  r  r6   rh   )r_   r`   ra   rz   r1   rx  r  r  r@   r@   r@   rA   r    s    
r  r  list[pytest.TestReport]r  c                 C  s   dd | D S )Nc                 S  s   i | ]}|j |qS r@   )r5  )ro  r  r@   r@   rA   r}    s    z&_make_reports_dict.<locals>.<dictcomp>r@   )r  r@   r@   rA   r    s   r  parserpytest.Parserrh   c                 C  s   |  d}|jdddddd |jddddd	d |jd
ddddd | jdddd | jdddd | jdddd tjjjj|  dS )zAdd ddtrace options.r   z	--ddtrace
store_trueFz Enable Datadog Test Optimization)actiondestdefaulthelpz--no-ddtrace
no-ddtracez7Disable Datadog Test Optimization (overrides --ddtrace)z--ddtrace-patch-allr~   z$Enable all integrations with ddtracer   )typez7Disable Datadog Test Optimization (overrides 'ddtrace')N)	getgroup	addoptionaddinir   testinginternal
tracer_apipytest_hookspytest_addoption)r  groupr@   r@   rA   r    s4   
r  r   c                   C  s   t tjdd S )NDD_CIVISIBILITY_ENABLEDtrue)r-   rn   ro   rp   r@   r@   r@   rA   -_is_test_optimization_disabled_by_kill_switch  s   r  early_configrn  args	list[str]c                 C  s&   t  rdS td| |rdS td| |S )NFr  r   )r  _is_option_true)r  r  r@   r@   rA   _is_enabled_early  s
   r  optionrL   c                 C  s"   | | p|| pd|  |v S )Nz--)r   getini)r  r  r  r@   r@   rA   r    s   "r  Trk  r   c              
   c  s    t | |sd V  d S t  ttd}|jt| ttjd zt|d}W n t	yA } zt
d| d V  W Y d }~d S d }~ww || jt< |jjrR|jjsRt  d V  d S )NrX   )test_commandr   test_framework_version)rw   z%s)r  r   r    r   set_attributes_get_test_commandr1   __version__r   r   r   r  stashSESSION_MANAGER_STASH_KEYr   coverage_enabledr   setup_coverage_collection)r  r  r  rw   rg   er@   r@   rA   pytest_load_initial_conftests  s.   



r  c                  C  s   t  } t|  d S rj   )r   r)   )rC   r@   r@   rA   r  
  s   r  r   c           	   	     s    dd t rd S  jtd }|std d S  fddtD }|jj	j
p-|jjj
}|rA|rA|D ]	}td|| q4t}n|r[|jjj
rX|D ]}td|||t|  qJt}nt}z||d}W n tys   td	 Y d S w  j|  jt  jd
r jt|  jdr jt| tjjjj  |jjrt  sddl!m"} t# }|t$|gd td t% rt  s|jjrt&  d S d S d S )Nmarkersz+dd_tags(**kwargs): add tags to current spanz8Session manager not initialized (plugin was not enabled)c                   s   g | ]
} j |r|qS r@   )pluginmanager	hasplugin)ro  rX   r   r@   rA   rr    s    z$pytest_configure.<locals>.<listcomp>z%s is installed alongside Datadog Test Optimization. Datadog retries (ATR, EFD) take precedence over %s, which will be overridden for this test session.z%s is installed and Datadog Auto Test Retries and Early Flake Detection are disabled. Attempt to Fix retries will not work while %s drives test execution. To use Attempt to Fix, disable %s with: -p %s)rg   z)Error setting up Test Optimization pluginxdistz
pytest-bddr   )start_coverage)sourcezIStarted coverage.py collection for report upload (pytest-cov not enabled))'addinivalue_liner  r  rp   r  r   r   _EXTERNAL_RERUN_PLUGINSr   auto_test_retriesr   early_flake_detectionr  ry  r   rf   r=   	exceptionr  registeradd_hookspecsr   r  r  r   r   r  r  r  r  pytest_configurer   r
   'ddtrace.contrib.internal.coverage.patchr  r   rL   r	   r*   )	r   rg   detected_rerun_pluginsdd_retries_enabledplugin_nameplugin_classpluginr  rC   r@   r  rA   r    sj   

	


r  c                 C  sL   d}t | dd }r|dd|j7 }tjd }r$|d|7 }|S )z@Extract and re-create pytest session command from pytest config.r1   invocation_paramsNz {} PYTEST_ADDOPTS)r8   formatjoinr  rn   ro   rp   )r   commandr  addoptsr@   r@   rA   r  ^  s   r  rH  't.Optional[pytest.ExceptionInfo[t.Any]]dict[str, str]c                 C  sb   | d u ri S d}t  }tj| j| j| j| |d tj| tj	d| jj
| jjf tjt| jiS )N   )limitfilez%s.%s)r   	tracebackprint_exceptionr  rc  tbr#   ERROR_STACKgetvalue
ERROR_TYPEr`   r_   ERROR_MESSAGErL   )rH  max_entriesbufr@   r@   rA   ra  h  s   
ra  r  rJ  user_propertyt.Optional[t.Any]c                 C  s.   t | dg }|D ]\}}||kr|  S qd S )NrC  )r8   )r  r  rC  keyrc  r@   r@   rA   r  x  s   r  t.Optional[str]c              	   C  s   t | dd }|d u rd S i i d}| jj D ]%\}}z
t||d |< W q ty<   d|d |< tjd|dd Y qw ztj	|ddW S  t
yW   tjd	| dd Y d S w )
Ncallspec)	argumentsmetadatar  zCould not encodezFailed to encode %rT)exc_info)	sort_keysz*Failed to serialize parameters for test %s)r8   r  paramsr   _encode_test_parameterr=   r   r  jsondumps	TypeError)r4   r  r   
param_name	param_valr@   r@   rA   r     s"   
r   	parameterr  c                 C  s   t | }tdd|S )Nz at 0[xX][0-9a-fA-F]+rR  )reprresub)r  
param_reprr@   r@   rA   r    s   r  markerpytest.Markc                 C  s2   | j r
| j d }|S | jr| jd}|S d}|S )Nr   	conditionT)r  kwargsrp   )r  r  r@   r@   rA   _get_skipif_condition  s   
r  c                 C  s   t dd | jddD S )Nc                 s  s,    | ]}t |d u o|jdtkV  qdS )Fr   N)r  r  rp   ITR_UNSKIPPABLE_REASON)ro  r  r@   r@   rA   	<genexpr>  s
    
z'_is_test_unskippable.<locals>.<genexpr>skipifr  )anyiter_markers)r4   r@   r@   rA   r     s   
r   c                 C  s:   i }| j ddD ]}|j D ]
\}}t|||< qq|S )Ndd_tagsr  )r  r  r   rL   )r4   r   r  r  rc  r@   r@   rA   r     s   r   rw   )scopec                  C  s   ddl } | jS )z#Return the current tracer instance.r   N)r   tracer)r   r@   r@   rA   ddtracer  s   r  functionc                 C  s   | du rdS |   S )zReturn the current root span.N)current_root_span)r  r@   r@   rA   ddspan  s   r  )r4   r5   r6   r   )r4   r5   rC   r   r6   r   )r4   r5   rC   r   r6   rH   )r4   r5   r?   r   r6   rU   )r  r  r6   r  )r  r  r6   rh   )r6   r   )r  rn  r  r  r6   r   )r  rL   r  rn  r  r  r6   r   )r  rn  r  r  r  r  r6   r   )r6   rh   )r   rn  r6   rh   )r   rn  r6   rL   )rH  r  r6   r  )r  rJ  r  rL   r6   r  )r4   r5   r6   r  )r  r  r6   rL   )r  r  r6   r  )r4   r5   r6   r   )r4   r5   r6   r  )
__future__r   collectionsr   ior   r  loggingrn   pathlibr   r  r  typingt_pytest.runnerr   pluggyr1   r  r   r   'ddtrace.contrib.internal.coverage.utilsr	   r
   r   $ddtrace.internal.ci_visibility.utilsr   !ddtrace.internal.utils.inspectionr   ddtrace.testing.internal.cir   ddtrace.testing.internal.errorsr   ddtrace.testing.internal.gitr    ddtrace.testing.internal.loggingr   r   #ddtrace.testing.internal.pytest.bddr   )ddtrace.testing.internal.pytest.benchmarkr   r   )ddtrace.testing.internal.pytest.hookspecsr   ,ddtrace.testing.internal.pytest.report_linksr   %ddtrace.testing.internal.pytest.utilsr   'ddtrace.testing.internal.retry_handlersr   (ddtrace.testing.internal.session_managerr   "ddtrace.testing.internal.telemetryr   "ddtrace.testing.internal.test_datar   r   r   r   r    r!   r"   r#   +ddtrace.testing.internal.tracer_api.contextr$   r%   r&   ,ddtrace.testing.internal.tracer_api.coverager'   r(   r)   r*   r+   0ddtrace.testing.internal.tracer_api.pytest_hooksr   ddtrace.testing.internal.utilsr,   r-   TYPE_CHECKING_pytest.terminalr.   r   ri  r  StashKeyr  AttributeErrorr   r  	getLoggerr_   r   tuplerL   rm  	_LongreprUniondictr   _ReportTestStatus	_LocationrB   rG   rT   rM   r[   r  r  rf   ry  r  r  r  r  r  r  r  rx  r  r  r  r  ra  r  r   r  r  r   r   fixturer  r  r@   r@   r@   rA   <module>   s    






    /)i


#



&

O












