We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Pytest : TestReport' object has no attribute 'wasxfail
### Which version of dd-trace-py are you using?
0.52
### Which version of pip are you using?
21.1.3
_ddtrace requires pip>=18 to install one of our pre-built wheels_

### Which version of the libraries are you using?

pytest 6= 2.4
### How can we reproduce your problem?

```python
import pytest

pytestmark = pytest.mark.skip(reason="reason")

@pytest.mark.xfail(reason="xfaily")
def test_check():
    pass

@pytest.mark.xfail(condition=False)
def test_check1():
    pass

```
Causes 
```
*** AttributeError: 'TestReport' object has no attribute 'wasxfail'
```


We use this to skip whole modules of tests, and the end result is you have tests that are skipped, but also marked xfail. 

Conditional xfails also produce this error.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ddtrace/contrib/pytest/plugin.py`
Content:
```
1 import json
2 from typing import Dict
3 
4 import pytest
5 
6 import ddtrace
7 from ddtrace.constants import SPAN_KIND
8 from ddtrace.contrib.pytest.constants import FRAMEWORK
9 from ddtrace.contrib.pytest.constants import HELP_MSG
10 from ddtrace.contrib.pytest.constants import KIND
11 from ddtrace.contrib.trace_utils import int_service
12 from ddtrace.ext import SpanTypes
13 from ddtrace.ext import ci
14 from ddtrace.ext import test
15 from ddtrace.internal import compat
16 from ddtrace.internal.logger import get_logger
17 from ddtrace.pin import Pin
18 
19 
20 PATCH_ALL_HELP_MSG = "Call ddtrace.patch_all before running tests."
21 log = get_logger(__name__)
22 
23 
24 def is_enabled(config):
25     """Check if the ddtrace plugin is enabled."""
26     return config.getoption("ddtrace") or config.getini("ddtrace")
27 
28 
29 def _extract_span(item):
30     """Extract span from `pytest.Item` instance."""
31     return getattr(item, "_datadog_span", None)
32 
33 
34 def _store_span(item, span):
35     """Store span at `pytest.Item` instance."""
36     setattr(item, "_datadog_span", span)
37 
38 
39 def _extract_repository_name(repository_url):
40     # type: (str) -> str
41     """Extract repository name from repository url."""
42     try:
43         return compat.parse.urlparse(repository_url).path.rstrip(".git").rpartition("/")[-1]
44     except ValueError:
45         # In case of parsing error, default to repository url
46         log.warning("Repository name cannot be parsed from repository_url: %s", repository_url)
47         return repository_url
48 
49 
50 def pytest_addoption(parser):
51     """Add ddtrace options."""
52     group = parser.getgroup("ddtrace")
53 
54     group._addoption(
55         "--ddtrace",
56         action="store_true",
57         dest="ddtrace",
58         default=False,
59         help=HELP_MSG,
60     )
61 
62     group._addoption(
63         "--ddtrace-patch-all",
64         action="store_true",
65         dest="ddtrace-patch-all",
66         default=False,
67         help=PATCH_ALL_HELP_MSG,
68     )
69 
70     parser.addini("ddtrace", HELP_MSG, type="bool")
71     parser.addini("ddtrace-patch-all", PATCH_ALL_HELP_MSG, type="bool")
72 
73 
74 def pytest_configure(config):
75     config.addinivalue_line("markers", "dd_tags(**kwargs): add tags to current span")
76     if is_enabled(config):
77         ci_tags = ci.tags()
78         if ci_tags.get(ci.git.REPOSITORY_URL, None) and int_service(None, ddtrace.config.pytest) == "pytest":
79             repository_name = _extract_repository_name(ci_tags[ci.git.REPOSITORY_URL])
80             ddtrace.config.pytest["service"] = repository_name
81         Pin(tags=ci_tags, _config=ddtrace.config.pytest).onto(config)
82 
83 
84 def pytest_sessionfinish(session, exitstatus):
85     """Flush open tracer."""
86     pin = Pin.get_from(session.config)
87     if pin is not None:
88         pin.tracer.shutdown()
89 
90 
91 @pytest.fixture(scope="function")
92 def ddspan(request):
93     pin = Pin.get_from(request.config)
94     if pin:
95         return _extract_span(request.node)
96 
97 
98 @pytest.fixture(scope="session", autouse=True)
99 def patch_all(request):
100     if request.config.getoption("ddtrace-patch-all") or request.config.getini("ddtrace-patch-all"):
101         ddtrace.patch_all()
102 
103 
104 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
105 def pytest_runtest_protocol(item, nextitem):
106     pin = Pin.get_from(item.config)
107     if pin is None:
108         yield
109         return
110     with pin.tracer.trace(
111         ddtrace.config.pytest.operation_name,
112         service=int_service(pin, ddtrace.config.pytest),
113         resource=item.nodeid,
114         span_type=SpanTypes.TEST.value,
115     ) as span:
116         span.context.dd_origin = ci.CI_APP_TEST_ORIGIN
117         span.set_tags(pin.tags)
118         span.set_tag(SPAN_KIND, KIND)
119         span.set_tag(test.FRAMEWORK, FRAMEWORK)
120         span.set_tag(test.NAME, item.name)
121         span.set_tag(test.SUITE, item.module.__name__)
122         span.set_tag(test.TYPE, SpanTypes.TEST.value)
123 
124         # Parameterized test cases will have a `callspec` attribute attached to the pytest Item object.
125         # Pytest docs: https://docs.pytest.org/en/6.2.x/reference.html#pytest.Function
126         if getattr(item, "callspec", None):
127             parameters = {"arguments": {}, "metadata": {}}  # type: Dict[str, Dict[str, str]]
128             for param_name, param_val in item.callspec.params.items():
129                 try:
130                     parameters["arguments"][param_name] = repr(param_val)
131                 except Exception:
132                     parameters["arguments"][param_name] = "Could not encode"
133                     log.warning("Failed to encode %r", param_name, exc_info=True)
134             span.set_tag(test.PARAMETERS, json.dumps(parameters))
135 
136         markers = [marker.kwargs for marker in item.iter_markers(name="dd_tags")]
137         for tags in markers:
138             span.set_tags(tags)
139         _store_span(item, span)
140 
141         yield
142 
143 
144 def _extract_reason(call):
145     if call.excinfo is not None:
146         return call.excinfo.value
147 
148 
149 @pytest.hookimpl(hookwrapper=True)
150 def pytest_runtest_makereport(item, call):
151     """Store outcome for tracing."""
152     outcome = yield
153 
154     span = _extract_span(item)
155     if span is None:
156         return
157 
158     called_without_status = call.when == "call" and span.get_tag(test.STATUS) is None
159     failed_setup = call.when == "setup" and call.excinfo is not None
160     if not called_without_status and not failed_setup:
161         return
162 
163     try:
164         result = outcome.get_result()
165 
166         if hasattr(result, "wasxfail") or "xfail" in result.keywords:
167             if result.skipped:
168                 # XFail tests that fail are recorded skipped by pytest
169                 span.set_tag(test.RESULT, test.Status.XFAIL.value)
170                 span.set_tag(test.XFAIL_REASON, result.wasxfail)
171             else:
172                 span.set_tag(test.RESULT, test.Status.XPASS.value)
173                 if result.passed:
174                     # XPass (strict=False) are recorded passed by pytest
175                     span.set_tag(test.XFAIL_REASON, result.wasxfail)
176                 else:
177                     # XPass (strict=True) are recorded failed by pytest, longrepr contains reason
178                     span.set_tag(test.XFAIL_REASON, result.longrepr)
179 
180         if result.skipped:
181             if hasattr(result, "wasxfail"):
182                 # XFail tests that fail are recorded skipped by pytest, should be passed instead
183                 span.set_tag(test.STATUS, test.Status.PASS.value)
184             else:
185                 span.set_tag(test.STATUS, test.Status.SKIP.value)
186             reason = _extract_reason(call)
187             if reason is not None:
188                 span.set_tag(test.SKIP_REASON, reason)
189         elif result.passed:
190             span.set_tag(test.STATUS, test.Status.PASS.value)
191         else:
192             raise RuntimeWarning(result)
193     except Exception:
194         span.set_traceback()
195         span.set_tag(test.STATUS, test.Status.FAIL.value)
196
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ddtrace/contrib/pytest/plugin.py b/ddtrace/contrib/pytest/plugin.py
--- a/ddtrace/contrib/pytest/plugin.py
+++ b/ddtrace/contrib/pytest/plugin.py
@@ -162,24 +162,14 @@
 
     try:
         result = outcome.get_result()
-
-        if hasattr(result, "wasxfail") or "xfail" in result.keywords:
-            if result.skipped:
-                # XFail tests that fail are recorded skipped by pytest
-                span.set_tag(test.RESULT, test.Status.XFAIL.value)
-                span.set_tag(test.XFAIL_REASON, result.wasxfail)
-            else:
-                span.set_tag(test.RESULT, test.Status.XPASS.value)
-                if result.passed:
-                    # XPass (strict=False) are recorded passed by pytest
-                    span.set_tag(test.XFAIL_REASON, result.wasxfail)
-                else:
-                    # XPass (strict=True) are recorded failed by pytest, longrepr contains reason
-                    span.set_tag(test.XFAIL_REASON, result.longrepr)
+        xfail = hasattr(result, "wasxfail") or "xfail" in result.keywords
+        has_skip_keyword = any(x in result.keywords for x in ["skip", "skipif", "skipped"])
 
         if result.skipped:
-            if hasattr(result, "wasxfail"):
+            if xfail and not has_skip_keyword:
                 # XFail tests that fail are recorded skipped by pytest, should be passed instead
+                span.set_tag(test.RESULT, test.Status.XFAIL.value)
+                span.set_tag(test.XFAIL_REASON, result.wasxfail)
                 span.set_tag(test.STATUS, test.Status.PASS.value)
             else:
                 span.set_tag(test.STATUS, test.Status.SKIP.value)
@@ -188,7 +178,15 @@
                 span.set_tag(test.SKIP_REASON, reason)
         elif result.passed:
             span.set_tag(test.STATUS, test.Status.PASS.value)
+            if xfail and not has_skip_keyword:
+                # XPass (strict=False) are recorded passed by pytest
+                span.set_tag(test.XFAIL_REASON, getattr(result, "wasxfail", "XFail"))
+                span.set_tag(test.RESULT, test.Status.XPASS.value)
         else:
+            if xfail and not has_skip_keyword:
+                # XPass (strict=True) are recorded failed by pytest, longrepr contains reason
+                span.set_tag(test.XFAIL_REASON, result.longrepr)
+                span.set_tag(test.RESULT, test.Status.XPASS.value)
             raise RuntimeWarning(result)
     except Exception:
         span.set_traceback()

{"golden_diff": "diff --git a/ddtrace/contrib/pytest/plugin.py b/ddtrace/contrib/pytest/plugin.py\n--- a/ddtrace/contrib/pytest/plugin.py\n+++ b/ddtrace/contrib/pytest/plugin.py\n@@ -162,24 +162,14 @@\n \n     try:\n         result = outcome.get_result()\n-\n-        if hasattr(result, \"wasxfail\") or \"xfail\" in result.keywords:\n-            if result.skipped:\n-                # XFail tests that fail are recorded skipped by pytest\n-                span.set_tag(test.RESULT, test.Status.XFAIL.value)\n-                span.set_tag(test.XFAIL_REASON, result.wasxfail)\n-            else:\n-                span.set_tag(test.RESULT, test.Status.XPASS.value)\n-                if result.passed:\n-                    # XPass (strict=False) are recorded passed by pytest\n-                    span.set_tag(test.XFAIL_REASON, result.wasxfail)\n-                else:\n-                    # XPass (strict=True) are recorded failed by pytest, longrepr contains reason\n-                    span.set_tag(test.XFAIL_REASON, result.longrepr)\n+        xfail = hasattr(result, \"wasxfail\") or \"xfail\" in result.keywords\n+        has_skip_keyword = any(x in result.keywords for x in [\"skip\", \"skipif\", \"skipped\"])\n \n         if result.skipped:\n-            if hasattr(result, \"wasxfail\"):\n+            if xfail and not has_skip_keyword:\n                 # XFail tests that fail are recorded skipped by pytest, should be passed instead\n+                span.set_tag(test.RESULT, test.Status.XFAIL.value)\n+                span.set_tag(test.XFAIL_REASON, result.wasxfail)\n                 span.set_tag(test.STATUS, test.Status.PASS.value)\n             else:\n                 span.set_tag(test.STATUS, test.Status.SKIP.value)\n@@ -188,7 +178,15 @@\n                 span.set_tag(test.SKIP_REASON, reason)\n         elif result.passed:\n             span.set_tag(test.STATUS, test.Status.PASS.value)\n+            if xfail and not has_skip_keyword:\n+                # XPass (strict=False) are recorded passed by pytest\n+                span.set_tag(test.XFAIL_REASON, getattr(result, \"wasxfail\", \"XFail\"))\n+                span.set_tag(test.RESULT, test.Status.XPASS.value)\n         else:\n+            if xfail and not has_skip_keyword:\n+                # XPass (strict=True) are recorded failed by pytest, longrepr contains reason\n+                span.set_tag(test.XFAIL_REASON, result.longrepr)\n+                span.set_tag(test.RESULT, test.Status.XPASS.value)\n             raise RuntimeWarning(result)\n     except Exception:\n         span.set_traceback()\n", "issue": "Pytest : TestReport' object has no attribute 'wasxfail\n### Which version of dd-trace-py are you using?\r\n0.52\r\n### Which version of pip are you using?\r\n21.1.3\r\n_ddtrace requires pip>=18 to install one of our pre-built wheels_\r\n\r\n### Which version of the libraries are you using?\r\n\r\npytest 6= 2.4\r\n### How can we reproduce your problem?\r\n\r\n```python\r\nimport pytest\r\n\r\npytestmark = pytest.mark.skip(reason=\"reason\")\r\n\r\n@pytest.mark.xfail(reason=\"xfaily\")\r\ndef test_check():\r\n    pass\r\n\r\n@pytest.mark.xfail(condition=False)\r\ndef test_check1():\r\n    pass\r\n\r\n```\r\nCauses \r\n```\r\n*** AttributeError: 'TestReport' object has no attribute 'wasxfail'\r\n```\r\n\r\n\r\nWe use this to skip whole modules of tests, and the end result is you have tests that are skipped, but also marked xfail. \r\n\r\nConditional xfails also produce this error.\n", "before_files": [{"content": "import json\nfrom typing import Dict\n\nimport pytest\n\nimport ddtrace\nfrom ddtrace.constants import SPAN_KIND\nfrom ddtrace.contrib.pytest.constants import FRAMEWORK\nfrom ddtrace.contrib.pytest.constants import HELP_MSG\nfrom ddtrace.contrib.pytest.constants import KIND\nfrom ddtrace.contrib.trace_utils import int_service\nfrom ddtrace.ext import SpanTypes\nfrom ddtrace.ext import ci\nfrom ddtrace.ext import test\nfrom ddtrace.internal import compat\nfrom ddtrace.internal.logger import get_logger\nfrom ddtrace.pin import Pin\n\n\nPATCH_ALL_HELP_MSG = \"Call ddtrace.patch_all before running tests.\"\nlog = get_logger(__name__)\n\n\ndef is_enabled(config):\n    \"\"\"Check if the ddtrace plugin is enabled.\"\"\"\n    return config.getoption(\"ddtrace\") or config.getini(\"ddtrace\")\n\n\ndef _extract_span(item):\n    \"\"\"Extract span from `pytest.Item` instance.\"\"\"\n    return getattr(item, \"_datadog_span\", None)\n\n\ndef _store_span(item, span):\n    \"\"\"Store span at `pytest.Item` instance.\"\"\"\n    setattr(item, \"_datadog_span\", span)\n\n\ndef _extract_repository_name(repository_url):\n    # type: (str) -> str\n    \"\"\"Extract repository name from repository url.\"\"\"\n    try:\n        return compat.parse.urlparse(repository_url).path.rstrip(\".git\").rpartition(\"/\")[-1]\n    except ValueError:\n        # In case of parsing error, default to repository url\n        log.warning(\"Repository name cannot be parsed from repository_url: %s\", repository_url)\n        return repository_url\n\n\ndef pytest_addoption(parser):\n    \"\"\"Add ddtrace options.\"\"\"\n    group = parser.getgroup(\"ddtrace\")\n\n    group._addoption(\n        \"--ddtrace\",\n        action=\"store_true\",\n        dest=\"ddtrace\",\n        default=False,\n        help=HELP_MSG,\n    )\n\n    group._addoption(\n        \"--ddtrace-patch-all\",\n        action=\"store_true\",\n        dest=\"ddtrace-patch-all\",\n        default=False,\n        help=PATCH_ALL_HELP_MSG,\n    )\n\n    parser.addini(\"ddtrace\", HELP_MSG, type=\"bool\")\n    parser.addini(\"ddtrace-patch-all\", PATCH_ALL_HELP_MSG, type=\"bool\")\n\n\ndef pytest_configure(config):\n    config.addinivalue_line(\"markers\", \"dd_tags(**kwargs): add tags to current span\")\n    if is_enabled(config):\n        ci_tags = ci.tags()\n        if ci_tags.get(ci.git.REPOSITORY_URL, None) and int_service(None, ddtrace.config.pytest) == \"pytest\":\n            repository_name = _extract_repository_name(ci_tags[ci.git.REPOSITORY_URL])\n            ddtrace.config.pytest[\"service\"] = repository_name\n        Pin(tags=ci_tags, _config=ddtrace.config.pytest).onto(config)\n\n\ndef pytest_sessionfinish(session, exitstatus):\n    \"\"\"Flush open tracer.\"\"\"\n    pin = Pin.get_from(session.config)\n    if pin is not None:\n        pin.tracer.shutdown()\n\n\n@pytest.fixture(scope=\"function\")\ndef ddspan(request):\n    pin = Pin.get_from(request.config)\n    if pin:\n        return _extract_span(request.node)\n\n\n@pytest.fixture(scope=\"session\", autouse=True)\ndef patch_all(request):\n    if request.config.getoption(\"ddtrace-patch-all\") or request.config.getini(\"ddtrace-patch-all\"):\n        ddtrace.patch_all()\n\n\n@pytest.hookimpl(tryfirst=True, hookwrapper=True)\ndef pytest_runtest_protocol(item, nextitem):\n    pin = Pin.get_from(item.config)\n    if pin is None:\n        yield\n        return\n    with pin.tracer.trace(\n        ddtrace.config.pytest.operation_name,\n        service=int_service(pin, ddtrace.config.pytest),\n        resource=item.nodeid,\n        span_type=SpanTypes.TEST.value,\n    ) as span:\n        span.context.dd_origin = ci.CI_APP_TEST_ORIGIN\n        span.set_tags(pin.tags)\n        span.set_tag(SPAN_KIND, KIND)\n        span.set_tag(test.FRAMEWORK, FRAMEWORK)\n        span.set_tag(test.NAME, item.name)\n        span.set_tag(test.SUITE, item.module.__name__)\n        span.set_tag(test.TYPE, SpanTypes.TEST.value)\n\n        # Parameterized test cases will have a `callspec` attribute attached to the pytest Item object.\n        # Pytest docs: https://docs.pytest.org/en/6.2.x/reference.html#pytest.Function\n        if getattr(item, \"callspec\", None):\n            parameters = {\"arguments\": {}, \"metadata\": {}}  # type: Dict[str, Dict[str, str]]\n            for param_name, param_val in item.callspec.params.items():\n                try:\n                    parameters[\"arguments\"][param_name] = repr(param_val)\n                except Exception:\n                    parameters[\"arguments\"][param_name] = \"Could not encode\"\n                    log.warning(\"Failed to encode %r\", param_name, exc_info=True)\n            span.set_tag(test.PARAMETERS, json.dumps(parameters))\n\n        markers = [marker.kwargs for marker in item.iter_markers(name=\"dd_tags\")]\n        for tags in markers:\n            span.set_tags(tags)\n        _store_span(item, span)\n\n        yield\n\n\ndef _extract_reason(call):\n    if call.excinfo is not None:\n        return call.excinfo.value\n\n\n@pytest.hookimpl(hookwrapper=True)\ndef pytest_runtest_makereport(item, call):\n    \"\"\"Store outcome for tracing.\"\"\"\n    outcome = yield\n\n    span = _extract_span(item)\n    if span is None:\n        return\n\n    called_without_status = call.when == \"call\" and span.get_tag(test.STATUS) is None\n    failed_setup = call.when == \"setup\" and call.excinfo is not None\n    if not called_without_status and not failed_setup:\n        return\n\n    try:\n        result = outcome.get_result()\n\n        if hasattr(result, \"wasxfail\") or \"xfail\" in result.keywords:\n            if result.skipped:\n                # XFail tests that fail are recorded skipped by pytest\n                span.set_tag(test.RESULT, test.Status.XFAIL.value)\n                span.set_tag(test.XFAIL_REASON, result.wasxfail)\n            else:\n                span.set_tag(test.RESULT, test.Status.XPASS.value)\n                if result.passed:\n                    # XPass (strict=False) are recorded passed by pytest\n                    span.set_tag(test.XFAIL_REASON, result.wasxfail)\n                else:\n                    # XPass (strict=True) are recorded failed by pytest, longrepr contains reason\n                    span.set_tag(test.XFAIL_REASON, result.longrepr)\n\n        if result.skipped:\n            if hasattr(result, \"wasxfail\"):\n                # XFail tests that fail are recorded skipped by pytest, should be passed instead\n                span.set_tag(test.STATUS, test.Status.PASS.value)\n            else:\n                span.set_tag(test.STATUS, test.Status.SKIP.value)\n            reason = _extract_reason(call)\n            if reason is not None:\n                span.set_tag(test.SKIP_REASON, reason)\n        elif result.passed:\n            span.set_tag(test.STATUS, test.Status.PASS.value)\n        else:\n            raise RuntimeWarning(result)\n    except Exception:\n        span.set_traceback()\n        span.set_tag(test.STATUS, test.Status.FAIL.value)\n", "path": "ddtrace/contrib/pytest/plugin.py"}], "after_files": [{"content": "import json\nfrom typing import Dict\n\nimport pytest\n\nimport ddtrace\nfrom ddtrace.constants import SPAN_KIND\nfrom ddtrace.contrib.pytest.constants import FRAMEWORK\nfrom ddtrace.contrib.pytest.constants import HELP_MSG\nfrom ddtrace.contrib.pytest.constants import KIND\nfrom ddtrace.contrib.trace_utils import int_service\nfrom ddtrace.ext import SpanTypes\nfrom ddtrace.ext import ci\nfrom ddtrace.ext import test\nfrom ddtrace.internal import compat\nfrom ddtrace.internal.logger import get_logger\nfrom ddtrace.pin import Pin\n\n\nPATCH_ALL_HELP_MSG = \"Call ddtrace.patch_all before running tests.\"\nlog = get_logger(__name__)\n\n\ndef is_enabled(config):\n    \"\"\"Check if the ddtrace plugin is enabled.\"\"\"\n    return config.getoption(\"ddtrace\") or config.getini(\"ddtrace\")\n\n\ndef _extract_span(item):\n    \"\"\"Extract span from `pytest.Item` instance.\"\"\"\n    return getattr(item, \"_datadog_span\", None)\n\n\ndef _store_span(item, span):\n    \"\"\"Store span at `pytest.Item` instance.\"\"\"\n    setattr(item, \"_datadog_span\", span)\n\n\ndef _extract_repository_name(repository_url):\n    # type: (str) -> str\n    \"\"\"Extract repository name from repository url.\"\"\"\n    try:\n        return compat.parse.urlparse(repository_url).path.rstrip(\".git\").rpartition(\"/\")[-1]\n    except ValueError:\n        # In case of parsing error, default to repository url\n        log.warning(\"Repository name cannot be parsed from repository_url: %s\", repository_url)\n        return repository_url\n\n\ndef pytest_addoption(parser):\n    \"\"\"Add ddtrace options.\"\"\"\n    group = parser.getgroup(\"ddtrace\")\n\n    group._addoption(\n        \"--ddtrace\",\n        action=\"store_true\",\n        dest=\"ddtrace\",\n        default=False,\n        help=HELP_MSG,\n    )\n\n    group._addoption(\n        \"--ddtrace-patch-all\",\n        action=\"store_true\",\n        dest=\"ddtrace-patch-all\",\n        default=False,\n        help=PATCH_ALL_HELP_MSG,\n    )\n\n    parser.addini(\"ddtrace\", HELP_MSG, type=\"bool\")\n    parser.addini(\"ddtrace-patch-all\", PATCH_ALL_HELP_MSG, type=\"bool\")\n\n\ndef pytest_configure(config):\n    config.addinivalue_line(\"markers\", \"dd_tags(**kwargs): add tags to current span\")\n    if is_enabled(config):\n        ci_tags = ci.tags()\n        if ci_tags.get(ci.git.REPOSITORY_URL, None) and int_service(None, ddtrace.config.pytest) == \"pytest\":\n            repository_name = _extract_repository_name(ci_tags[ci.git.REPOSITORY_URL])\n            ddtrace.config.pytest[\"service\"] = repository_name\n        Pin(tags=ci_tags, _config=ddtrace.config.pytest).onto(config)\n\n\ndef pytest_sessionfinish(session, exitstatus):\n    \"\"\"Flush open tracer.\"\"\"\n    pin = Pin.get_from(session.config)\n    if pin is not None:\n        pin.tracer.shutdown()\n\n\n@pytest.fixture(scope=\"function\")\ndef ddspan(request):\n    pin = Pin.get_from(request.config)\n    if pin:\n        return _extract_span(request.node)\n\n\n@pytest.fixture(scope=\"session\", autouse=True)\ndef patch_all(request):\n    if request.config.getoption(\"ddtrace-patch-all\") or request.config.getini(\"ddtrace-patch-all\"):\n        ddtrace.patch_all()\n\n\n@pytest.hookimpl(tryfirst=True, hookwrapper=True)\ndef pytest_runtest_protocol(item, nextitem):\n    pin = Pin.get_from(item.config)\n    if pin is None:\n        yield\n        return\n    with pin.tracer.trace(\n        ddtrace.config.pytest.operation_name,\n        service=int_service(pin, ddtrace.config.pytest),\n        resource=item.nodeid,\n        span_type=SpanTypes.TEST.value,\n    ) as span:\n        span.context.dd_origin = ci.CI_APP_TEST_ORIGIN\n        span.set_tags(pin.tags)\n        span.set_tag(SPAN_KIND, KIND)\n        span.set_tag(test.FRAMEWORK, FRAMEWORK)\n        span.set_tag(test.NAME, item.name)\n        span.set_tag(test.SUITE, item.module.__name__)\n        span.set_tag(test.TYPE, SpanTypes.TEST.value)\n\n        # Parameterized test cases will have a `callspec` attribute attached to the pytest Item object.\n        # Pytest docs: https://docs.pytest.org/en/6.2.x/reference.html#pytest.Function\n        if getattr(item, \"callspec\", None):\n            parameters = {\"arguments\": {}, \"metadata\": {}}  # type: Dict[str, Dict[str, str]]\n            for param_name, param_val in item.callspec.params.items():\n                try:\n                    parameters[\"arguments\"][param_name] = repr(param_val)\n                except Exception:\n                    parameters[\"arguments\"][param_name] = \"Could not encode\"\n                    log.warning(\"Failed to encode %r\", param_name, exc_info=True)\n            span.set_tag(test.PARAMETERS, json.dumps(parameters))\n\n        markers = [marker.kwargs for marker in item.iter_markers(name=\"dd_tags\")]\n        for tags in markers:\n            span.set_tags(tags)\n        _store_span(item, span)\n\n        yield\n\n\ndef _extract_reason(call):\n    if call.excinfo is not None:\n        return call.excinfo.value\n\n\n@pytest.hookimpl(hookwrapper=True)\ndef pytest_runtest_makereport(item, call):\n    \"\"\"Store outcome for tracing.\"\"\"\n    outcome = yield\n\n    span = _extract_span(item)\n    if span is None:\n        return\n\n    called_without_status = call.when == \"call\" and span.get_tag(test.STATUS) is None\n    failed_setup = call.when == \"setup\" and call.excinfo is not None\n    if not called_without_status and not failed_setup:\n        return\n\n    try:\n        result = outcome.get_result()\n        xfail = hasattr(result, \"wasxfail\") or \"xfail\" in result.keywords\n        has_skip_keyword = any(x in result.keywords for x in [\"skip\", \"skipif\", \"skipped\"])\n\n        if result.skipped:\n            if xfail and not has_skip_keyword:\n                # XFail tests that fail are recorded skipped by pytest, should be passed instead\n                span.set_tag(test.RESULT, test.Status.XFAIL.value)\n                span.set_tag(test.XFAIL_REASON, result.wasxfail)\n                span.set_tag(test.STATUS, test.Status.PASS.value)\n            else:\n                span.set_tag(test.STATUS, test.Status.SKIP.value)\n            reason = _extract_reason(call)\n            if reason is not None:\n                span.set_tag(test.SKIP_REASON, reason)\n        elif result.passed:\n            span.set_tag(test.STATUS, test.Status.PASS.value)\n            if xfail and not has_skip_keyword:\n                # XPass (strict=False) are recorded passed by pytest\n                span.set_tag(test.XFAIL_REASON, getattr(result, \"wasxfail\", \"XFail\"))\n                span.set_tag(test.RESULT, test.Status.XPASS.value)\n        else:\n            if xfail and not has_skip_keyword:\n                # XPass (strict=True) are recorded failed by pytest, longrepr contains reason\n                span.set_tag(test.XFAIL_REASON, result.longrepr)\n                span.set_tag(test.RESULT, test.Status.XPASS.value)\n            raise RuntimeWarning(result)\n    except Exception:\n        span.set_traceback()\n        span.set_tag(test.STATUS, test.Status.FAIL.value)\n", "path": "ddtrace/contrib/pytest/plugin.py"}]}