Repository: blake-r/pytest-durations (language: python)

Issue:
pytest-xdist is required
Hi, I start using pytest, and I installed pytest_durations plugins, but I got this error
```
platform win32 -- Python 3.7.11, pytest-7.0.1, pluggy-1.0.0
rootdir: ...\rootdir, configfile: pytest.ini
plugins: cov-3.0.0, custom-report-1.0.1, durations-1.0.1, excel-1.5.0
collected 1 item
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File "D:\...\lib\site-packages\_pytest\main.py", line 268, in wrap_session
INTERNALERROR>     session.exitstatus = doit(config, session) or 0
INTERNALERROR>   File "D:\...\lib\site-packages\_pytest\main.py", line 321, in _main
INTERNALERROR>     config.hook.pytest_collection(session=session)
INTERNALERROR>   File "D:\...\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
INTERNALERROR>     return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
INTERNALERROR>   File "D:\...\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>   File "D:\...\lib\site-packages\pluggy\_callers.py", line 60, in _multicall
INTERNALERROR>     return outcome.get_result()
INTERNALERROR>   File "D:\...\lib\site-packages\pluggy\_result.py", line 60, in get_result
INTERNALERROR>     raise ex[1].with_traceback(ex[2])
INTERNALERROR>   File "D:\...\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "D:\...\lib\site-packages\_pytest\main.py", line 332, in pytest_collection
INTERNALERROR>     session.perform_collect()
INTERNALERROR>   File "D:\...\lib\site-packages\_pytest\main.py", line 659, in perform_collect
INTERNALERROR>     self.config.pluginmanager.check_pending()
INTERNALERROR>   File "D:\...\lib\site-packages\pluggy\_manager.py", line 265, in check_pending
INTERNALERROR>     % (name, hookimpl.plugin),
INTERNALERROR> pluggy._manager.PluginValidationError: unknown hook 'pytest_testnodedown' in plugin <pytest_durations.plugin.Pyte
stDurationPlugin object at 0x000002740A822FC8>

==================================================== 9 warnings in 22.15s ===================================================== 
```

I found in other pytest plugin the same issue

- [pytest-xdist is required](https://github.com/pytest-dev/pytest-splinter/issues/13)

So its now executed well if install the xdist package:
`pip install pytest-xdist`

Shit happens sometimes, I see you upload a new version recent, typically environments failures.


Discussion / hints:

Fix patch:
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..94a6efd
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,18 @@
+[run]
+branch = true
+source = src
+
+[report]
+exclude_lines =
+    def __repr__
+    if self\.debug
+    raise AssertionError
+    raise NotImplementedError
+    if 0:
+    if __name__ == .__main__.:
+    if TYPE_CHECKING:
+omit =
+    */types.py
+show_missing = true
+skip_covered = true
+skip_empty = true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0cda8f8..ac4ffbb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,8 +30,7 @@ jobs:
       - name: Prepare for tests run
         run: |
           pip install poetry
-          poetry config virtualenvs.create false
           poetry install
       - name: Run tests
         run: |
-          pytest --showlocals -vv tests
+          pytest --showlocals -vv --cov
diff --git a/.gitignore b/.gitignore
index 3dea073..e9710bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 **/__pycache__
 **/.pytest_cache
 dist
+.coverage
diff --git a/poetry.lock b/poetry.lock
index dd4e887..2a34571 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -76,7 +76,7 @@ python-versions = ">=3.6.1"
 
 [[package]]
 name = "click"
-version = "8.0.3"
+version = "8.0.4"
 description = "Composable command line interface toolkit"
 category = "dev"
 optional = false
@@ -94,6 +94,20 @@ category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
+[[package]]
+name = "coverage"
+version = "6.2"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+tomli = {version = "*", optional = true, markers = "extra == \"toml\""}
+
+[package.extras]
+toml = ["tomli"]
+
 [[package]]
 name = "dataclasses"
 version = "0.8"
@@ -149,11 +163,11 @@ pyflakes = ">=2.4.0,<2.5.0"
 
 [[package]]
 name = "freezegun"
-version = "1.1.0"
+version = "1.2.0"
 description = "Let your Python tests travel through time"
 category = "dev"
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.6"
 
 [package.dependencies]
 python-dateutil = ">=2.7"
@@ -353,6 +367,21 @@ tomli = ">=1.0.0"
 [package.extras]
 testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
 
+[[package]]
+name = "pytest-cov"
+version = "3.0.0"
+description = "Pytest plugin for measuring coverage."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+coverage = {version = ">=5.2.1", extras = ["toml"]}
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
+
 [[package]]
 name = "pytest-forked"
 version = "1.4.0"
@@ -451,7 +480,7 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "virtualenv"
-version = "20.13.1"
+version = "20.13.3"
 description = "Virtual Python Environment builder"
 category = "dev"
 optional = false
@@ -484,7 +513,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
 [metadata]
 lock-version = "1.1"
 python-versions = ">=3.6.2"
-content-hash = "9b21b4416f8ede0771187df15f179b9ae72ed7e5a2729798f83d061733cde376"
+content-hash = "8822181d00acbdb317cb8b4b7c798f65124b337731d6a83224ea3e3f8dcc1044"
 
 [metadata.files]
 add-trailing-comma = [
@@ -532,13 +561,62 @@ cfgv = [
     {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
 ]
 click = [
-    {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
-    {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
+    {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
+    {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
 ]
 colorama = [
     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
     {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
 ]
+coverage = [
+    {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"},
+    {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"},
+    {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"},
+    {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"},
+    {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"},
+    {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"},
+    {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"},
+    {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"},
+    {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"},
+    {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"},
+    {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"},
+    {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"},
+    {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"},
+    {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"},
+    {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"},
+    {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"},
+    {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"},
+    {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"},
+    {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"},
+    {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"},
+    {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"},
+    {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"},
+    {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"},
+    {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"},
+    {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"},
+    {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"},
+    {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"},
+    {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"},
+    {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"},
+]
 dataclasses = [
     {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
     {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
@@ -560,8 +638,8 @@ flake8 = [
     {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
 ]
 freezegun = [
-    {file = "freezegun-1.1.0-py2.py3-none-any.whl", hash = "sha256:2ae695f7eb96c62529f03a038461afe3c692db3465e215355e1bb4b0ab408712"},
-    {file = "freezegun-1.1.0.tar.gz", hash = "sha256:177f9dd59861d871e27a484c3332f35a6e3f5d14626f2bf91be37891f18927f3"},
+    {file = "freezegun-1.2.0-py3-none-any.whl", hash = "sha256:93e90676da387a241a7c2ae7ae68af7b95bfe80e952d0bbf3e088342de90a38d"},
+    {file = "freezegun-1.2.0.tar.gz", hash = "sha256:e19563d0b05dcab6dc8ad21c05269004a96998ce2b6b1b8131f1297ae20dd038"},
 ]
 identify = [
     {file = "identify-2.4.4-py2.py3-none-any.whl", hash = "sha256:aa68609c7454dbcaae60a01ff6b8df1de9b39fe6e50b1f6107ec81dcda624aa6"},
@@ -631,6 +709,10 @@ pytest = [
     {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"},
     {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"},
 ]
+pytest-cov = [
+    {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
+    {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
+]
 pytest-forked = [
     {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"},
     {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"},
@@ -725,8 +807,8 @@ typing-extensions = [
     {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
 ]
 virtualenv = [
-    {file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"},
-    {file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"},
+    {file = "virtualenv-20.13.3-py2.py3-none-any.whl", hash = "sha256:dd448d1ded9f14d1a4bfa6bfc0c5b96ae3be3f2d6c6c159b23ddcfd701baa021"},
+    {file = "virtualenv-20.13.3.tar.gz", hash = "sha256:e9dd1a1359d70137559034c0f5433b34caf504af2dc756367be86a5a32967134"},
 ]
 zipp = [
     {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
diff --git a/poetry.toml b/poetry.toml
new file mode 100644
index 0000000..084377a
--- /dev/null
+++ b/poetry.toml
@@ -0,0 +1,2 @@
+[virtualenvs]
+create = false
diff --git a/pyproject.toml b/pyproject.toml
index 1e2db4c..c48b0e5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "pytest-durations"
-version = "1.0.1"
+version = "1.1.0"
 description = "Pytest plugin reporting fixtures and test functions execution time."
 authors = ["Oleg Blednov <oleg.codev@gmail.com>"]
 license = "MIT"
@@ -39,6 +39,7 @@ add-trailing-comma = "^2.2.1"
 autoflake = "^1.4"
 black = "^22.1.0"
 flake8 = "^4.0.1"
+pytest-cov = "^3.0.0"
 
 [build-system]
 requires = ["poetry-core>=1.0.0"]


Test patch:
diff --git a/src/pytest_durations/__init__.py b/src/pytest_durations/__init__.py
index f1d92a1..51fca06 100644
--- a/src/pytest_durations/__init__.py
+++ b/src/pytest_durations/__init__.py
@@ -1,3 +1,3 @@
 from pytest_durations.options import pytest_addoption, pytest_configure  # noqa: F401
 
-__version__ = "1.0.1"
+__version__ = "1.1.0"
diff --git a/src/pytest_durations/options.py b/src/pytest_durations/options.py
index f8bb28d..6323303 100644
--- a/src/pytest_durations/options.py
+++ b/src/pytest_durations/options.py
@@ -32,4 +32,11 @@ def pytest_configure(config: "Config") -> NoReturn:
 
     from pytest_durations.plugin import PytestDurationPlugin
 
-    config.pluginmanager.register(PytestDurationPlugin())
+    pluginmanager = config.pluginmanager
+
+    if pluginmanager.hasplugin("xdist"):
+        from pytest_durations.xdist import PytestDurationXdistMixin
+
+        PytestDurationPlugin = type("PytestDurationPlugin", (PytestDurationPlugin, PytestDurationXdistMixin), {})
+
+    pluginmanager.register(PytestDurationPlugin())
diff --git a/src/pytest_durations/plugin.py b/src/pytest_durations/plugin.py
index 8cb9949..9b034f6 100644
--- a/src/pytest_durations/plugin.py
+++ b/src/pytest_durations/plugin.py
@@ -1,5 +1,5 @@
 from contextlib import contextmanager
-from typing import Any, NoReturn, Optional, TYPE_CHECKING, Iterable, Tuple, Dict, List, Union
+from typing import Any, Iterable, NoReturn, Optional, TYPE_CHECKING, Tuple
 
 import pytest
 
@@ -11,14 +11,9 @@
 if TYPE_CHECKING:
     from _pytest.config import Config, ExitCode
     from _pytest.fixtures import FixtureDef, SubRequest
-    from _pytest.main import Session
     from _pytest.nodes import Item
     from _pytest.terminal import TerminalReporter
-    from xdist.workermanage import WorkerController
-
-MeasurementsT = Dict[str, Dict[str, List[float]]]
-
-_WORKEROUTPUT_KEY = "pytest_durations"
+    from pytest_durations.types import MeasurementsT
 
 
 class Category:
@@ -39,7 +34,7 @@ def report_items(cls) -> Iterable[Tuple[str, str]]:
 
 
 class PytestDurationPlugin:
-    measurements: MeasurementsT
+    measurements: "MeasurementsT"
     shared_fixture_duration: float
     last_fixture_teardown_start: float
 
@@ -102,22 +97,6 @@ def pytest_runtest_teardown(self, item: "Item") -> NoReturn:
             measurement.duration -= self.shared_fixture_duration
         self.shared_fixture_duration = 0.0
 
-    @pytest.hookimpl(tryfirst=True)
-    def pytest_sessionfinish(self, session: "Session", exitstatus: Union[int, "ExitCode"]) -> NoReturn:
-        """Send measurements to the master process if the current session runs under pytest-xdist."""
-        # for xdist, results should be added to worker output
-        workeroutput = getattr(session.config, "workeroutput", None)
-        if workeroutput is not None:
-            workeroutput[_WORKEROUTPUT_KEY] = self.measurements
-
-    def pytest_testnodedown(self, node: "WorkerController", error: Optional[Any]) -> NoReturn:
-        """Merge measurements from slave processes if the current sesions runs under pytest-xdist."""
-        # for xdist, results should be accumulated from workers
-        workeroutput = getattr(node, "workeroutput", None)
-        if workeroutput is not None:
-            node_measurements = node.workeroutput[_WORKEROUTPUT_KEY]
-            self._extend_measurements(node_measurements)
-
     def pytest_terminal_summary(
         self,
         terminalreporter: "TerminalReporter",
@@ -148,13 +127,3 @@ def _measure(self, category: str, key: str) -> Iterable["MeasureDuration"]:
             measurements[key].append(measurement.duration)
         except KeyError:
             measurements[key] = [measurement.duration]
-
-    def _extend_measurements(self, src: MeasurementsT) -> NoReturn:
-        """Merge measured durations by appending new value series to the end of existing ones."""
-        for category, src_series in src.items():
-            dst_series = self.measurements[category]
-            for key, values in src_series.items():
-                try:
-                    dst_series[key].extend(values)
-                except KeyError:
-                    dst_series[key] = values
diff --git a/src/pytest_durations/types.py b/src/pytest_durations/types.py
new file mode 100644
index 0000000..bb302ff
--- /dev/null
+++ b/src/pytest_durations/types.py
@@ -0,0 +1,3 @@
+from typing import Dict, List
+
+MeasurementsT = Dict[str, Dict[str, List[float]]]
diff --git a/src/pytest_durations/xdist.py b/src/pytest_durations/xdist.py
new file mode 100644
index 0000000..f38886c
--- /dev/null
+++ b/src/pytest_durations/xdist.py
@@ -0,0 +1,41 @@
+from typing import Union, NoReturn, Optional, Any, TYPE_CHECKING
+
+import pytest
+
+if TYPE_CHECKING:
+    from _pytest.config import ExitCode
+    from _pytest.main import Session
+    from xdist.workermanage import WorkerController
+    from pytest_durations.types import MeasurementsT
+
+_WORKEROUTPUT_KEY = "pytest_durations"
+
+
+class PytestDurationXdistMixin:
+    measurements: "MeasurementsT"
+
+    @pytest.hookimpl(tryfirst=True)
+    def pytest_sessionfinish(self, session: "Session", exitstatus: Union[int, "ExitCode"]) -> None:
+        """Send measurements to the master process if the current session runs under pytest-xdist."""
+        # for xdist, results should be added to worker output
+        workeroutput = getattr(session.config, "workeroutput", None)
+        if workeroutput is not None:
+            workeroutput[_WORKEROUTPUT_KEY] = self.measurements
+
+    def pytest_testnodedown(self, node: "WorkerController", error: Optional[Any]) -> NoReturn:
+        """Merge measurements from slave processes if the current sesions runs under pytest-xdist."""
+        # for xdist, results should be accumulated from workers
+        workeroutput = getattr(node, "workeroutput", None)
+        if workeroutput is not None:
+            node_measurements = node.workeroutput[_WORKEROUTPUT_KEY]
+            self._extend_measurements(node_measurements)
+
+    def _extend_measurements(self, src: "MeasurementsT") -> NoReturn:
+        """Merge measured durations by appending new value series to the end of existing ones."""
+        for category, src_series in src.items():
+            dst_series = self.measurements[category]
+            for key, values in src_series.items():
+                try:
+                    dst_series[key].extend(values)
+                except KeyError:
+                    dst_series[key] = values
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
new file mode 100644
index 0000000..8a01183
--- /dev/null
+++ b/tests/test_helpers.py
@@ -0,0 +1,24 @@
+from unittest.mock import create_autospec
+
+import pytest
+from _pytest.nodes import Item
+
+from pytest_durations.helpers import _get_test_key
+
+
+@pytest.mark.parametrize("param", (None, "param", "param1-param2"))
+@pytest.mark.parametrize(
+    ("nodeid", "expected"),
+    (
+        ("func", "func"),
+        ("filename.py::func", "func"),
+        ("filename.py::scope::func", "scope::func"),
+    ),
+)
+def test_get_test_key(nodeid, param, expected):
+    """Filename and parameters should be removed from test key."""
+    if param:
+        nodeid = f"{nodeid}[{param}]"
+    item = create_autospec(Item, instance=True, nodeid=nodeid)
+    result = _get_test_key(item)
+    assert result == expected
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index be4c13e..a7dc8f2 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -56,7 +56,6 @@ def test_function2(fixture_session, fixture_module, fixture_class, fixture_funct
         (),
         ("--pytest-durations", "1"),
         ("--pytest-durations-min", "0"),
-        ("--numprocesses", "2"),  # xdist
     ),
 )
 def test_plugin_with_options(pytester, sample_testfile, options):
@@ -71,3 +70,15 @@ def test_plugin_disable(pytester, sample_testfile):
     result = pytester.runpytest("--pytest-durations", "0")
     result.assert_outcomes(passed=2)
     result.stdout.no_fnmatch_line("*duration top*")
+
+
+def test_plugin_xdist_disabled(pytester, sample_testfile):
+    """Run when pytest-xdist is absent or disabled should be successful (#3)."""
+    result = pytester.runpytest("-p", "no:xdist")
+    result.assert_outcomes(passed=2)
+
+
+def test_plugin_xdist_enabled(pytester, sample_testfile):
+    """Run when pytest-xdist is absent or disabled should be successful (#3)."""
+    result = pytester.runpytest("--numprocesses", "2")
+    result.assert_outcomes(passed=2)
diff --git a/tests/test_ticker.py b/tests/test_ticker.py
index 87e9004..f394d7c 100644
--- a/tests/test_ticker.py
+++ b/tests/test_ticker.py
@@ -1,11 +1,20 @@
+import sys
 from datetime import datetime
+from importlib import import_module
 
 from _pytest.python_api import approx
 from freezegun import freeze_time
-
 from pytest_durations.ticker import get_current_ticks
 
 
+def test_freezegun_import_error():
+    """Import when freezegun is not installed should be successful."""
+    del sys.modules["pytest_durations.ticker"]  # remove alrady imported ticker
+    sys.modules["freezegun"] = None  # make freezegun unimportable
+    module = import_module("pytest_durations.ticker")
+    assert "freezegun" not in module.__dict__
+
+
 def test_get_current_ticks_frozen():
     """Time freezing should not affect tick counter."""
     ticks = get_current_ticks()
diff --git a/tests/test_version.py b/tests/test_version.py
new file mode 100644
index 0000000..77e6c9b
--- /dev/null
+++ b/tests/test_version.py
@@ -0,0 +1,12 @@
+from pathlib import Path
+
+from poetry.core.pyproject import PyProjectTOML
+
+from pytest_durations.__init__ import __version__
+
+
+def test_version():
+    """Package version should be in sync with project version."""
+    pyproject_toml = Path(__file__).parent.parent / "pyproject.toml"
+    project = PyProjectTOML(pyproject_toml).poetry_config
+    assert __version__ == project["version"]