Repository: smarie/python-pytest-harvest

Issue:
pytest-harvest fails when some doctests exist

Comments:

Patch:
diff --git a/.gitignore b/.gitignore
index 0fbba57..4980c87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,7 @@ coverage.xml
 *.cover
 .hypothesis/
 .pytest_cache/
+pytest_harvest/_version.py
 
 # Translations
 *.mo
diff --git a/setup.cfg b/setup.cfg
index ab42292..4ef910e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -18,5 +18,5 @@ description-file = README.md
 test=pytest
 
 [tool:pytest]
-addopts = --verbose
+addopts = --verbose --doctest-modules
 testpaths = pytest_harvest/tests


Test patch:
diff --git a/pytest_harvest/plugin.py b/pytest_harvest/plugin.py
index 855d4e0..5e7f62a 100644
--- a/pytest_harvest/plugin.py
+++ b/pytest_harvest/plugin.py
@@ -48,7 +48,7 @@ def pytest_runtest_makereport(item, call):
 
 # ------------- To collect benchmark results ------------
 FIXTURE_STORE = OrderedDict()
-"""The default fixture store, that is also available through the `fixture_store` fixture. It is recommended to access 
+"""The default fixture store, that is also available through the `fixture_store` fixture. It is recommended to access
 it through `get_fixture_store(session)` so as to be xdist-compliant"""
 
 
@@ -90,23 +90,23 @@ results_bag = create_results_bag_fixture('fixture_store', name='results_bag')
 A "results bag" fixture: a dictionary where you can store anything (results, context, etc.) during your tests execution.
 It offers a "much"-like api: you can access all entries using the object protocol such as in `results_bag.a = 1`.
 
-This fixture has function-scope so a new, empty instance is injected in each test node. 
+This fixture has function-scope so a new, empty instance is injected in each test node.
 
-There are several ways to gather all results after they have been stored. 
+There are several ways to gather all results after they have been stored.
 
- * To get the raw stored results, use the `fixture_store` fixture: `fixture_store['results_bag']` will contain all 
+ * To get the raw stored results, use the `fixture_store` fixture: `fixture_store['results_bag']` will contain all
    result bags for all tests.
-   
- * If you are interested in both the stored results AND some stored fixture values (through `@saved_fixture`), you 
+
+ * If you are interested in both the stored results AND some stored fixture values (through `@saved_fixture`), you
    might rather wish to leverage the following helpers:
 
-     - use one of the `session_results_dct`, `module_results_dct`, `session_results_df` or `module_results_df` 
+     - use one of the `session_results_dct`, `module_results_dct`, `session_results_df` or `module_results_df`
        fixtures. They contain all available information, in a nicely summarized way.
-       
-     - use the `get_session_synthesis_dct(session)` helper method to create a similar synthesis than the above with 
+
+     - use the `get_session_synthesis_dct(session)` helper method to create a similar synthesis than the above with
        more customization capabilities.
 
-If you wish to create custom results bags similar to this one (for example to create several with different names), 
+If you wish to create custom results bags similar to this one (for example to create several with different names),
 use `create_results_bag_fixture`.
 """
 
diff --git a/pytest_harvest/results_session.py b/pytest_harvest/results_session.py
index ca199ed..200ec99 100644
--- a/pytest_harvest/results_session.py
+++ b/pytest_harvest/results_session.py
@@ -9,6 +9,7 @@ except ImportError:
     pass
 
 from pytest_harvest.common import HARVEST_PREFIX
+from _pytest.doctest import DoctestItem
 
 
 PYTEST_OBJ_NAME = 'pytest_obj'
@@ -322,6 +323,9 @@ def _pytest_item_matches_filter(item, filterset):
     if item_obj in filterset:
         return True
     # support class methods: the item object can be a bound method while the filter is maybe not
+    elif item_obj is None:
+        # This can happen with DoctestItem
+        return False
     elif _is_unbound_present(item_obj, filterset):
         return True
     elif any(item_obj.__module__ == f for f in filterset):
@@ -443,6 +447,9 @@ def get_pytest_params(item):
     if isinstance(item, _MinimalItem):
         # Our special _MinimalItem object - when xdist is used and worker states have been saved + restored
         return item.get_pytest_params()
+    elif isinstance(item, DoctestItem):
+        # No fixtures or parameters
+        return OrderedDict()
     else:
         param_dct = OrderedDict()
         for param_name in item.fixturenames:  # note: item.funcargnames gives the exact same list
diff --git a/pytest_harvest/tests_raw/test_get_session_results.py b/pytest_harvest/tests_raw/test_get_session_results.py
index 732a055..4b2be66 100644
--- a/pytest_harvest/tests_raw/test_get_session_results.py
+++ b/pytest_harvest/tests_raw/test_get_session_results.py
@@ -1,5 +1,5 @@
 # META
-# {'passed': 16, 'skipped': 1, 'failed': 1}
+# {'passed': 17, 'skipped': 1, 'failed': 1}
 # END META
 import os
 from itertools import product
@@ -210,6 +210,26 @@ def test_synthesis_contains_everything(request):
     assert len(missing) == 0
 
 
+def doctestable():
+    """Do nothing, but have a doctest.
+
+    Examples
+    --------
+    >>> 1 + 1
+    2
+    """
+    return
+
+
+# For some reason, adding a monkeypatch will cause an extra failure for
+# DoctestItem, possibly because it's a setup/teardown
+def test_deal_with_doctest(dummy, request):
+    """ Tests that setup/teardown harvesting with DoctestItem works """
+    synth_dct = get_session_synthesis_dct(request, filter_incomplete=False)
+    assert 'pytest_harvest/tests_raw/test_get_session_results.py::pytest_harvest.tests_raw.test_get_session_results.doctestable' \
+        in synth_dct
+
+
 @yield_fixture(scope='session', autouse=True)
 def make_synthesis(request):
     """This checks that the session-scoped fixture teardown hook works as well"""