commit python-pytest-doctestplus for openSUSE:Factory
Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pytest-doctestplus for openSUSE:Factory checked in at 2024-01-03 12:27:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pytest-doctestplus (Old) and /work/SRC/openSUSE:Factory/.python-pytest-doctestplus.new.28375 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-pytest-doctestplus" Wed Jan 3 12:27:11 2024 rev:17 rq:1135878 version:1.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pytest-doctestplus/python-pytest-doctestplus.changes 2023-10-05 20:03:54.408346718 +0200 +++ /work/SRC/openSUSE:Factory/.python-pytest-doctestplus.new.28375/python-pytest-doctestplus.changes 2024-01-03 12:27:13.636384893 +0100 @@ -1,0 +2,11 @@ +Sun Dec 31 13:32:06 UTC 2023 - Dirk Müller <dmueller@suse.com> + +- update to 1.1.0: + * Added --doctest-plus-generate-diff to update documentation + based on actual output. + * Fix module level __doctest_requires__. + * Versions of Python <3.8 are no longer supported. + * Fix erroneous attempt to import __main__.py by skipping it. + * Respect pytest --import-mode. + +------------------------------------------------------------------- Old: ---- pytest-doctestplus-1.0.0.tar.gz New: ---- pytest-doctestplus-1.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pytest-doctestplus.spec ++++++ --- /var/tmp/diff_new_pack.j9b8JH/_old 2024-01-03 12:27:14.164404175 +0100 +++ /var/tmp/diff_new_pack.j9b8JH/_new 2024-01-03 12:27:14.164404175 +0100 @@ -27,16 +27,16 @@ %{?sle15_python_module_pythons} Name: python-pytest-doctestplus%{psuffix} -Version: 1.0.0 +Version: 1.1.0 Release: 0 Summary: Pytest plugin with advanced doctest features License: BSD-3-Clause URL: https://github.com/scientific-python/pytest-doctestplus Source: https://files.pythonhosted.org/packages/source/p/pytest-doctestplus/pytest-doctestplus-%{version}.tar.gz -BuildRequires: %{python_module base >= 3.7} -BuildRequires: %{python_module packaging >= 17.0} +BuildRequires: %{python_module base >= 3.8} +BuildRequires: %{python_module pip} BuildRequires: %{python_module setuptools_scm} -BuildRequires: %{python_module setuptools} +BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-packaging >= 17.0 @@ -49,6 +49,7 @@ BuildRequires: %{python_module pip >= 19.3.1} BuildRequires: %{python_module pytest-doctestplus = %{version}} BuildRequires: %{python_module pytest-remotedata >= 0.3.2} +BuildRequires: git-core %else BuildArch: noarch %endif @@ -63,11 +64,11 @@ %setup -q -n pytest-doctestplus-%{version} %build -%python_build +%pyproject_wheel %install %if !%{with test} -%python_install +%pyproject_install %python_expand %fdupes %{buildroot}%{$python_sitelib} %endif @@ -75,7 +76,7 @@ %check export LANG=en_US.UTF8 export PY_IGNORE_IMPORTMISMATCH=1 -%pytest tests/ --doctest-plus --doctest-rst -k "not test_remote_data_url" +%pytest tests/ --doctest-plus --doctest-rst -k "not test_remote_data_url and not test_import_mode" %endif %if !%{with test} @@ -83,6 +84,6 @@ %doc CHANGES.rst README.rst %license LICENSE.rst %{python_sitelib}/pytest_doctestplus -%{python_sitelib}/pytest_doctestplus-%{version}*-info +%{python_sitelib}/pytest_doctestplus-%{version}.dist-info %endif ++++++ pytest-doctestplus-1.0.0.tar.gz -> pytest-doctestplus-1.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/.github/workflows/publish.yml new/pytest-doctestplus-1.1.0/.github/workflows/publish.yml --- old/pytest-doctestplus-1.0.0/.github/workflows/publish.yml 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/.github/workflows/publish.yml 2023-12-13 19:12:44.000000000 +0100 @@ -13,7 +13,7 @@ if: ((github.event_name == 'push' && startsWith(github.ref, 'refs/tags')) || contains(github.event.pull_request.labels.*.name, 'Build wheels')) steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-python@v4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/.github/workflows/python-tests.yml new/pytest-doctestplus-1.1.0/.github/workflows/python-tests.yml --- old/pytest-doctestplus-1.0.0/.github/workflows/python-tests.yml 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/.github/workflows/python-tests.yml 2023-12-13 19:12:44.000000000 +0100 @@ -8,8 +8,8 @@ - '*' workflow_dispatch: schedule: - # Run every Sunday at 03:53 UTC - - cron: 53 3 * * 0 + # Run every Tuesday at 03:53 UTC + - cron: 53 3 * * 2 jobs: tests: @@ -19,17 +19,17 @@ matrix: include: - os: ubuntu-latest - python-version: 3.7 - toxenv: py37-test-pytest46 + python-version: 3.8 + toxenv: py38-test-pytestoldest - os: windows-latest - python-version: 3.7 - toxenv: py37-test-pytest50 + python-version: 3.8 + toxenv: py38-test-pytest50 - os: macos-latest - python-version: 3.7 - toxenv: py37-test-pytest51 + python-version: 3.8 + toxenv: py38-test-pytest51 - os: ubuntu-latest - python-version: 3.7 - toxenv: py37-test-pytest52 + python-version: 3.8 + toxenv: py38-test-pytest52 - os: windows-latest python-version: 3.8 toxenv: py38-test-pytest53 @@ -65,11 +65,11 @@ python-version: '3.11' toxenv: py311-test-pytestdev - os: ubuntu-latest - python-version: '3.12-dev' + python-version: '3.12' toxenv: py312-test-pytestdev steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} @@ -80,11 +80,3 @@ run: python -m pip install tox - name: Run Tox run: tox ${{ matrix.toxargs }} -v -e ${{ matrix.toxenv }} - - # - name: Slack Notification - # uses: 8398a7/action-slack@v3 - # with: - # status: ${{ job.status }} - # env: - # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} - # if: always() # TODO: cron diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/.mailmap new/pytest-doctestplus-1.1.0/.mailmap --- old/pytest-doctestplus-1.0.0/.mailmap 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/.mailmap 2023-12-13 19:12:44.000000000 +0100 @@ -12,7 +12,9 @@ Matteo Bachetti <matteo@matteobachetti.it> <matteo.bachetti@irap.omp.eu> Michael Seifert <michaelseifert04@yahoo.de> Pey Lian Lim <lim@stsci.edu> <2090236+pllim@users.noreply.github.com> +Philipp A. <flying-sheep@web.de> Pratik Patel <pratikpatel15133@gmail.com> +Sebastian Berg <sebastianb@nvidia.com> <sebastian@sipsolutions.net> Simon Conseil <contact@saimon.org> <s.conseil@ip2i.in2p3.fr> Simon Conseil <contact@saimon.org> <sconseil@gemini.edu> Tinuade Adeleke <summittinuade@gmail.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/CHANGES.rst new/pytest-doctestplus-1.1.0/CHANGES.rst --- old/pytest-doctestplus-1.0.0/CHANGES.rst 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/CHANGES.rst 2023-12-13 19:12:44.000000000 +0100 @@ -1,3 +1,18 @@ +1.1.0 (2023-12-13) +================== + +- Added ``--doctest-plus-generate-diff`` to update documentation based on + actual output. [#227] + +- Fix module level ``__doctest_requires__``. [#228] + +- Versions of Python <3.8 are no longer supported. [#217] + +- Fix erroneous attempt to import ``__main__.py`` by skipping it. [#232] + +- Respect pytest ``--import-mode``. [#233] + + 1.0.0 (2023-08-11) ================== @@ -69,7 +84,7 @@ 0.9.0 (2021-01-14) ================== -- Declare ``setuptools`` runtime dependency [#93] +- Declare ``setuptools`` runtime dependency. [#132] - Add ``SHOW_WARNINGS`` flag to show warnings. [#136] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/PKG-INFO new/pytest-doctestplus-1.1.0/PKG-INFO --- old/pytest-doctestplus-1.0.0/PKG-INFO 2023-08-11 18:22:14.703384200 +0200 +++ new/pytest-doctestplus-1.1.0/PKG-INFO 2023-12-13 19:12:59.113894500 +0100 @@ -1,12 +1,12 @@ Metadata-Version: 2.1 Name: pytest-doctestplus -Version: 1.0.0 +Version: 1.1.0 Summary: Pytest plugin with advanced doctest features. Home-page: https://github.com/scientific-python/pytest-doctestplus Author: Scientific Python Developers License: BSD Keywords: doctest,rst,pytest,py.test -Classifier: Development Status :: 3 - Alpha +Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Pytest Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License @@ -14,7 +14,6 @@ Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 @@ -23,10 +22,16 @@ Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Utilities -Requires-Python: >=3.7 +Requires-Python: >=3.8 Description-Content-Type: text/x-rst -Provides-Extra: test License-File: LICENSE.rst +Requires-Dist: pytest>=4.6 +Requires-Dist: setuptools>=30.3.0 +Requires-Dist: packaging>=17.0 +Provides-Extra: test +Requires-Dist: numpy; extra == "test" +Requires-Dist: pytest-remotedata>=0.3.2; extra == "test" +Requires-Dist: sphinx; extra == "test" ================== pytest-doctestplus @@ -71,6 +76,10 @@ * optional inclusion of ``*.rst`` files for doctests (see `Setup and Configuration`_) * optional inclusion of doctests in docstrings of Numpy ufuncs +Further, ``pytest-doctestplus`` supports editing files to fix incorrect docstrings +(See `Fixing Existing Docstrings`_). + +.. _pytest-remotedata: https://github.com/astropy/pytest-remotedata Installation ------------ @@ -132,6 +141,23 @@ doctest settings, see the `doctest documentation <https://docs.python.org/3/library/doctest.html#option-flags>`_. +Running Tests in Markdown Files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To run doctests in Markdown files, invoke pytest with the command line options +``--doctest-plus --doctest-glob '*.md'``. + +If you write doctests inside `GitHub-style triple backtick fenced code blocks +<https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#fenced-code-blocks>`_, +then in order for pytest-doctest to find and run them you need to include an +extra trailing newline inside your code blocks, like this:: + + ```pycon + >>> 1 + 2 + 2 + + ``` + Doctest Directives ~~~~~~~~~~~~~~~~~~ @@ -387,6 +413,54 @@ ``conf.py`` file. +Fixing Existing Docstrings +-------------------------- +The plugin has basic support to fix docstrings, this can be enabled by +running ``pytest`` with ``--doctest-plus-generate-diff``. +Without further options, this will print out a diff and a list of files that +would be modified. Using ``--doctest-plus-generate-diff=overwrite`` will +modify the files in-place, so it is recommended to run the check first to +verify the paths. +You may wish to review changes manually and only commit some patches e.g. using ``git commit --patch``. + +The current diff generation is still very basic, for example, it does not account for +existing ``...``. By default a diff is only generated for *failing* doctests. + +In general, a mass edit may wish to focus on a specific change and +possibly include passing tests. So you can opt-in into the behavior by +adding a hook to your ``conftest.py``:: + + @pytest.hookimpl + def pytest_doctestplus_diffhook(info): + info["use"] = True # Overwrite all results (even successes) + if info["fileno"] is None: + # E.g. NumPy has C docstrings that cannot be found, we can add + # custom logic here to try and find these: + info["filename"] = ... + info["lineno"] = ... + +Where ``info`` is a dictionary containing the following items: + +* ``use``: ``True`` or ``False`` signalling whether to apply the diff. This is + set to ``False`` if a doctest succeeded and ``True`` if the doctest failed. +* ``name``: The name of the test (e.g. the function being documented) +* ``filename``: The file that contains the test (this can be wrong in certain + situation and in that case ``test_lineno`` will be wrong as well). +* ``source``: The source code that was executed for this test +* ``test_lineno``: The line of code where the example block (or function) starts. + In some cases, the test file cannot be found and the lineno will be ``None``, + you can manually try to fix these. +* ``example_lineno``: The line number of the example snippet + (individual ``>>>``). +* ``want``: The current documentation. +* ``got``: The result of executing the example. + +You can modify the dictionary in-place to modify the behavior. + +Please note that we assume that this API will be used only occasionally and +reserve the right to change it at any time. + + Development Status ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/README.rst new/pytest-doctestplus-1.1.0/README.rst --- old/pytest-doctestplus-1.0.0/README.rst 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/README.rst 2023-12-13 19:12:44.000000000 +0100 @@ -41,6 +41,10 @@ * optional inclusion of ``*.rst`` files for doctests (see `Setup and Configuration`_) * optional inclusion of doctests in docstrings of Numpy ufuncs +Further, ``pytest-doctestplus`` supports editing files to fix incorrect docstrings +(See `Fixing Existing Docstrings`_). + +.. _pytest-remotedata: https://github.com/astropy/pytest-remotedata Installation ------------ @@ -102,6 +106,23 @@ doctest settings, see the `doctest documentation <https://docs.python.org/3/library/doctest.html#option-flags>`_. +Running Tests in Markdown Files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To run doctests in Markdown files, invoke pytest with the command line options +``--doctest-plus --doctest-glob '*.md'``. + +If you write doctests inside `GitHub-style triple backtick fenced code blocks +<https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#fenced-code-blocks>`_, +then in order for pytest-doctest to find and run them you need to include an +extra trailing newline inside your code blocks, like this:: + + ```pycon + >>> 1 + 2 + 2 + + ``` + Doctest Directives ~~~~~~~~~~~~~~~~~~ @@ -357,6 +378,54 @@ ``conf.py`` file. +Fixing Existing Docstrings +-------------------------- +The plugin has basic support to fix docstrings, this can be enabled by +running ``pytest`` with ``--doctest-plus-generate-diff``. +Without further options, this will print out a diff and a list of files that +would be modified. Using ``--doctest-plus-generate-diff=overwrite`` will +modify the files in-place, so it is recommended to run the check first to +verify the paths. +You may wish to review changes manually and only commit some patches e.g. using ``git commit --patch``. + +The current diff generation is still very basic, for example, it does not account for +existing ``...``. By default a diff is only generated for *failing* doctests. + +In general, a mass edit may wish to focus on a specific change and +possibly include passing tests. So you can opt-in into the behavior by +adding a hook to your ``conftest.py``:: + + @pytest.hookimpl + def pytest_doctestplus_diffhook(info): + info["use"] = True # Overwrite all results (even successes) + if info["fileno"] is None: + # E.g. NumPy has C docstrings that cannot be found, we can add + # custom logic here to try and find these: + info["filename"] = ... + info["lineno"] = ... + +Where ``info`` is a dictionary containing the following items: + +* ``use``: ``True`` or ``False`` signalling whether to apply the diff. This is + set to ``False`` if a doctest succeeded and ``True`` if the doctest failed. +* ``name``: The name of the test (e.g. the function being documented) +* ``filename``: The file that contains the test (this can be wrong in certain + situation and in that case ``test_lineno`` will be wrong as well). +* ``source``: The source code that was executed for this test +* ``test_lineno``: The line of code where the example block (or function) starts. + In some cases, the test file cannot be found and the lineno will be ``None``, + you can manually try to fix these. +* ``example_lineno``: The line number of the example snippet + (individual ``>>>``). +* ``want``: The current documentation. +* ``got``: The result of executing the example. + +You can modify the dictionary in-place to modify the behavior. + +Please note that we assume that this API will be used only occasionally and +reserve the right to change it at any time. + + Development Status ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/pytest_doctestplus/newhooks.py new/pytest-doctestplus-1.1.0/pytest_doctestplus/newhooks.py --- old/pytest-doctestplus-1.0.0/pytest_doctestplus/newhooks.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pytest-doctestplus-1.1.0/pytest_doctestplus/newhooks.py 2023-12-13 19:12:44.000000000 +0100 @@ -0,0 +1,5 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst + + +def pytest_doctestplus_diffhook(info): + """ called when a diff would be generated normally. """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/pytest_doctestplus/output_checker.py new/pytest-doctestplus-1.1.0/pytest_doctestplus/output_checker.py --- old/pytest-doctestplus-1.0.0/pytest_doctestplus/output_checker.py 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/pytest_doctestplus/output_checker.py 2023-12-13 19:12:44.000000000 +0100 @@ -43,8 +43,6 @@ rtol = 1e-05 atol = 1e-08 - _original_output_checker = doctest.OutputChecker - _str_literal_re = re.compile( r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) _byteorder_re = re.compile( @@ -55,9 +53,6 @@ r"([0-9]+)L", re.UNICODE) def __init__(self): - # NOTE OutputChecker is an old-style class with no __init__ method, - # so we can't call the base class version of __init__ here - exp = r'(?:e[+-]?\d+)' got_floats = (r'\s*([+-]?\d+\.\d*{0}?|' @@ -80,6 +75,10 @@ fmidend = r'(?<={}){}(?={}|$)'.format(front_sep, want_floats, back_sep) self.num_want_rgx = re.compile(r'({}|{})'.format(fbeg, fmidend)) + # As of 2023-09-26, Python base class has no init, but just in case + # it acquires one. + super().__init__() + def do_fixes(self, want, got): want = re.sub(self._str_literal_re, r'\1\2', want) want = re.sub(self._byteorder_re, r'\1\2\3', want) @@ -281,19 +280,13 @@ if flags & FLOAT_CMP: return self.normalize_floats(want, got, flags) - # Can't use super here because doctest.OutputChecker is not a - # new-style class. - return self._original_output_checker.check_output( - self, want, got, flags) + return super().check_output(want, got, flags) def output_difference(self, want, got, flags): if flags & FIX: want, got = self.do_fixes(want, got) - # Can't use super here because doctest.OutputChecker is not a - # new-style class. - return self._original_output_checker.output_difference( - self, want, got, flags) + return super().output_difference(want, got, flags) try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/pytest_doctestplus/plugin.py new/pytest-doctestplus-1.1.0/pytest_doctestplus/plugin.py --- old/pytest-doctestplus-1.0.0/pytest_doctestplus/plugin.py 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/pytest_doctestplus/plugin.py 2023-12-13 19:12:44.000000000 +0100 @@ -1,4 +1,5 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst + """ This plugin provides advanced doctest support and enables the testing of .rst files. @@ -8,8 +9,11 @@ import os import re import sys +import tempfile import warnings +from collections import defaultdict from pathlib import Path +import subprocess from textwrap import indent from unittest import SkipTest @@ -119,6 +123,20 @@ parser.addoption("--doctest-only", action="store_true", help="Test only doctests. Implies usage of doctest-plus.") + parser.addoption("--doctest-plus-generate-diff", + help=( + "Generate a diff where expected output and actual " + "output differ. " + "The diff is printed to stdout if not using " + "`--doctest-plus-generate-diff=overwrite` which " + "causes editing of the original files.\n" + "NOTE: Unless an in-pace build is picked up, python " + "file paths may point to unexpected places. " + "If 'overwrite' is not used, will create a temporary " + "folder and use `git diff -p` to generate a diff."), + choices=["diff", "overwrite"], + action="store", nargs="?", default=False, const="diff") + parser.addini("text_file_format", "Default format for docs. " "This is no longer recommended, use --doctest-glob instead.") @@ -160,6 +178,11 @@ default=[]) +def pytest_addhooks(pluginmanager): + from pytest_doctestplus import newhooks + pluginmanager.add_hookspecs(newhooks) + + def get_optionflags(parent): optionflags_str = parent.config.getini('doctest_optionflags') flag_int = 0 @@ -185,6 +208,8 @@ def pytest_configure(config): doctest_plugin = config.pluginmanager.getplugin('doctest') run_regular_doctest = config.option.doctestmodules and not config.option.doctest_plus + if config.option.doctest_plus_generate_diff: + config.option.doctest_only = True use_doctest_plus = config.getini( 'doctest_plus') or config.option.doctest_plus or config.option.doctest_only use_doctest_ufunc = config.getini( @@ -204,13 +229,17 @@ use_rst = config.getini('doctest_rst') or config.option.doctest_rst file_ext = config.option.text_file_format or config.getini('text_file_format') or 'rst' if use_rst: - config.option.doctestglob.append('*.{}'.format(file_ext)) + config.option.doctestglob.append(f'*.{file_ext}') # override default comment characters ext_comment_pairs = [pair.split('=') for pair in config.getini('text_file_comment_chars')] for ext, chars in ext_comment_pairs: comment_characters[ext] = chars + # Fetch the global hook function: + global doctestplus_diffhook + doctestplus_diffhook = config.hook.pytest_doctestplus_diffhook + class DocTestModulePlus(doctest_plugin.DoctestModule): # pytest 2.4.0 defines "collect". Prior to that, it defined # "runtest". The "collect" approach is better, because we can @@ -228,7 +257,7 @@ fspath = self.fspath filepath = self.fspath.basename - if filepath == "setup.py": + if filepath in ("setup.py", "__main__.py"): return elif filepath == "conftest.py": if PYTEST_GE_7_0: @@ -245,11 +274,12 @@ try: if PYTEST_GT_5: from _pytest.pathlib import import_path + mode = self.config.getoption("importmode") if PYTEST_GE_7_0: - module = import_path(fspath, root=self.config.rootpath) + module = import_path(fspath, mode=mode, root=self.config.rootpath) elif PYTEST_GT_5: - module = import_path(fspath) + module = import_path(fspath, mode=mode) else: module = fspath.pyimport() except ImportError: @@ -268,6 +298,7 @@ checker=OutputChecker(), # Helper disables continue-on-failure when debugging is enabled continue_on_failure=_get_continue_on_failure(config), + generate_diff=config.option.doctest_plus_generate_diff, ) for test in finder.find(module): @@ -332,6 +363,7 @@ runner = DebugRunnerPlus( verbose=False, optionflags=optionflags, checker=OutputChecker(), continue_on_failure=_get_continue_on_failure(self.config), + generate_diff=self.config.option.doctest_plus_generate_diff, ) parser = DocTestParserPlus() @@ -394,7 +426,7 @@ skip_next = False lines = entry.strip().splitlines() if any(re.match( - '{} doctest-skip-all'.format(comment_char), x.strip()) for x in lines): + f'{comment_char} doctest-skip-all', x.strip()) for x in lines): skip_all = True continue @@ -405,7 +437,7 @@ # special environment to be in between, e.g. \begin{python} last_lines = lines[-2:] matches = [re.match( - r'{}\s+doctest-skip\s*::(\s+.*)?'.format(comment_char), + fr'{comment_char}\s+doctest-skip\s*::(\s+.*)?', last_line) for last_line in last_lines] if len(matches) > 1: @@ -423,7 +455,7 @@ if config.getoption('remote_data', 'none') != 'any': matches = (re.match( - r'{}\s+doctest-remote-data\s*::'.format(comment_char), + fr'{comment_char}\s+doctest-remote-data\s*::', last_line) for last_line in last_lines) if any(matches): @@ -431,7 +463,7 @@ continue matches = [re.match( - r'{}\s+doctest-requires\s*::\s+(.*)'.format(comment_char), + fr'{comment_char}\s+doctest-requires\s*::\s+(.*)', last_line) for last_line in last_lines] if len(matches) > 1: @@ -487,7 +519,7 @@ config.pluginmanager.unregister(doctest_plugin) -class DoctestPlus(object): +class DoctestPlus: def __init__(self, doctest_module_item_cls, doctest_textfile_item_cls, file_globs): """ doctest_module_item_cls should be a class inheriting @@ -673,6 +705,7 @@ if mod in cls._import_cache: if not cls._import_cache[mod]: return False + continue if cls._module_checker.check(mod): cls._import_cache[mod] = True @@ -714,9 +747,17 @@ for pats, mods in reqs.items(): if not isinstance(pats, tuple): pats = (pats,) + for pat in pats: - if not fnmatch.fnmatch(test.name, '.'.join((name, pat))): - continue + if pat == '*': + pass + elif pat == '.' and test.name == name: + pass + elif fnmatch.fnmatch(test.name, '.'.join((name, pat))): + pass + else: + continue # The pattern does not apply + if not self.check_required_modules(mods): return False return True @@ -726,12 +767,143 @@ return tests +def write_modified_file(fname, new_fname, changes): + # Sort in reversed order to edit the lines: + bad_tests = [] + changes.sort(key=lambda x: (x["test_lineno"], x["example_lineno"]), + reverse=True) + + with open(fname, "r") as f: + text = f.readlines() + + for change in changes: + if change["test_lineno"] is None: + bad_tests.append(change["name"]) + continue + lineno = change["test_lineno"] + change["example_lineno"] + 1 + + indentation = " " * change["nindent"] + want = indent(change["want"], indentation, lambda x: True) + # Replace fully blank lines with the required `<BLANKLINE>` + # (May need to do this also if line contains only whitespace) + got = change["got"].replace("\n\n", "\n<BLANKLINE>\n") + got = indent(got, indentation, lambda x: True) + + text[lineno:lineno+want.count("\n")] = [got] + + with open(new_fname, "w") as f: + f.write("".join(text)) + + return bad_tests + + +def pytest_terminal_summary(terminalreporter, exitstatus, config): + changesets = DebugRunnerPlus._changesets + diff_mode = DebugRunnerPlus._generate_diff + DebugRunnerPlus._changesets = defaultdict(lambda: []) + DebugRunnerPlus._generate_diff = None + all_bad_tests = [] + if not diff_mode: + return # we do not report or apply diffs + + if diff_mode != "overwrite": + # In this mode, we write a corrected file to a temporary folder in + # order to compare them (rather than modifying the file). + terminalreporter.section("Reporting DoctestPlus Diffs") + if not changesets: + terminalreporter.write_line("No doc changes to show") + return + + # Strip away the common part of the path to make it a bit clearner... + common_path = os.path.commonpath(changesets.keys()) + if not os.path.isdir(common_path): + common_path = os.path.split(common_path)[0] + + with tempfile.TemporaryDirectory() as tmpdirname: + for fname, changes in changesets.items(): + # Create a new filename and ensure the path exists (in the + # temporary directory). + new_fname = fname.replace(common_path, tmpdirname) + os.makedirs(os.path.split(new_fname)[0], exist_ok=True) + + bad_tests = write_modified_file(fname, new_fname, changes) + all_bad_tests.extend(bad_tests) + + # git diff returns 1 to signal changes, so just ignore the + # exit status: + with subprocess.Popen( + ["git", "diff", "-p", "--no-index", fname, new_fname], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as p: + p.wait() + # Diff should be fine, but write error if not: + diff = p.stderr.read() + diff += p.stdout.read() + + # hide the temporary directory (cleaning up anyway): + if not os.path.isabs(common_path): + diff = diff.replace(tmpdirname, "/" + common_path) + else: + # diff seems to not include extra / + diff = diff.replace(tmpdirname, common_path) + terminalreporter.write(diff) + terminalreporter.write_line(f"{tmpdirname}, {common_path}") + + terminalreporter.section("Files with modifications", "-") + terminalreporter.write_line( + "The following files would be overwritten with " + "`--doctest-plus-generate-diff=overwrite`:") + for fname in changesets: + terminalreporter.write_line(f" {fname}") + terminalreporter.write_line( + "make sure these file paths are correct before calling it!") + else: + # We are in overwrite mode so will write the modified version directly + # back into the same file and only report which files were changed. + terminalreporter.section("DoctestPlus Fixing File Docs") + if not changesets: + terminalreporter.write_line("No doc changes to apply") + return + terminalreporter.write_line("Applied fix to the following files:") + for fname, changes in changesets.items(): + bad_tests = write_modified_file(fname, fname, changes) + all_bad_tests.extend(bad_tests) + terminalreporter.write_line(f" {fname}") + + if all_bad_tests: + terminalreporter.section("Broken Linenumbers", "-") + terminalreporter.write_line( + "Doctestplus was unable to fix the following tests " + "(their source is hidden or `__module__` overridden?)") + for bad_test in all_bad_tests: + terminalreporter.write_line(f" {bad_test}") + terminalreporter.write_line( + "You can implementing a hook function to fix this (see README).") + + class DebugRunnerPlus(doctest.DebugRunner): - def __init__(self, checker=None, verbose=None, optionflags=0, continue_on_failure=True): + _changesets = defaultdict(lambda: []) + _generate_diff = False + + def __init__(self, checker=None, verbose=None, optionflags=0, + continue_on_failure=True, generate_diff=False): + # generated_diff is False, "diff", or "overwrite" (only need truthiness) + DebugRunnerPlus._generate_diff = generate_diff + super().__init__(checker=checker, verbose=verbose, optionflags=optionflags) self.continue_on_failure = continue_on_failure + def report_success(self, out, test, example, got): + if self._generate_diff: + self.track_diff(False, out, test, example, got) + return + + return super().report_success(out, test, example, got) + def report_failure(self, out, test, example, got): + if self._generate_diff: + self.track_diff(True, out, test, example, got) + return + failure = doctest.DocTestFailure(test, example, got) if self.continue_on_failure: out.append(failure) @@ -747,3 +919,17 @@ out.append(failure) else: raise failure + + def track_diff(self, use, out, test, example, got): + if example.want == got: + return + + info = dict(use=use, name=test.name, filename=test.filename, + source=example.source, nindent=example.indent, + want=example.want, got=got, test_lineno=test.lineno, + example_lineno=example.lineno) + doctestplus_diffhook(info=info) + if not info["use"]: + return + + self._changesets[info["filename"]].append(info) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/pytest_doctestplus/version.py new/pytest-doctestplus-1.1.0/pytest_doctestplus/version.py --- old/pytest-doctestplus-1.0.0/pytest_doctestplus/version.py 2023-08-11 18:22:14.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/pytest_doctestplus/version.py 2023-12-13 19:12:59.000000000 +0100 @@ -1,4 +1,16 @@ # file generated by setuptools_scm # don't change, don't track in version control -__version__ = version = '1.0.0' -__version_tuple__ = version_tuple = (1, 0, 0) +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple, Union + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = '1.1.0' +__version_tuple__ = version_tuple = (1, 1, 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/pytest_doctestplus.egg-info/PKG-INFO new/pytest-doctestplus-1.1.0/pytest_doctestplus.egg-info/PKG-INFO --- old/pytest-doctestplus-1.0.0/pytest_doctestplus.egg-info/PKG-INFO 2023-08-11 18:22:14.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/pytest_doctestplus.egg-info/PKG-INFO 2023-12-13 19:12:59.000000000 +0100 @@ -1,12 +1,12 @@ Metadata-Version: 2.1 Name: pytest-doctestplus -Version: 1.0.0 +Version: 1.1.0 Summary: Pytest plugin with advanced doctest features. Home-page: https://github.com/scientific-python/pytest-doctestplus Author: Scientific Python Developers License: BSD Keywords: doctest,rst,pytest,py.test -Classifier: Development Status :: 3 - Alpha +Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Pytest Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License @@ -14,7 +14,6 @@ Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 @@ -23,10 +22,16 @@ Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Utilities -Requires-Python: >=3.7 +Requires-Python: >=3.8 Description-Content-Type: text/x-rst -Provides-Extra: test License-File: LICENSE.rst +Requires-Dist: pytest>=4.6 +Requires-Dist: setuptools>=30.3.0 +Requires-Dist: packaging>=17.0 +Provides-Extra: test +Requires-Dist: numpy; extra == "test" +Requires-Dist: pytest-remotedata>=0.3.2; extra == "test" +Requires-Dist: sphinx; extra == "test" ================== pytest-doctestplus @@ -71,6 +76,10 @@ * optional inclusion of ``*.rst`` files for doctests (see `Setup and Configuration`_) * optional inclusion of doctests in docstrings of Numpy ufuncs +Further, ``pytest-doctestplus`` supports editing files to fix incorrect docstrings +(See `Fixing Existing Docstrings`_). + +.. _pytest-remotedata: https://github.com/astropy/pytest-remotedata Installation ------------ @@ -132,6 +141,23 @@ doctest settings, see the `doctest documentation <https://docs.python.org/3/library/doctest.html#option-flags>`_. +Running Tests in Markdown Files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To run doctests in Markdown files, invoke pytest with the command line options +``--doctest-plus --doctest-glob '*.md'``. + +If you write doctests inside `GitHub-style triple backtick fenced code blocks +<https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#fenced-code-blocks>`_, +then in order for pytest-doctest to find and run them you need to include an +extra trailing newline inside your code blocks, like this:: + + ```pycon + >>> 1 + 2 + 2 + + ``` + Doctest Directives ~~~~~~~~~~~~~~~~~~ @@ -387,6 +413,54 @@ ``conf.py`` file. +Fixing Existing Docstrings +-------------------------- +The plugin has basic support to fix docstrings, this can be enabled by +running ``pytest`` with ``--doctest-plus-generate-diff``. +Without further options, this will print out a diff and a list of files that +would be modified. Using ``--doctest-plus-generate-diff=overwrite`` will +modify the files in-place, so it is recommended to run the check first to +verify the paths. +You may wish to review changes manually and only commit some patches e.g. using ``git commit --patch``. + +The current diff generation is still very basic, for example, it does not account for +existing ``...``. By default a diff is only generated for *failing* doctests. + +In general, a mass edit may wish to focus on a specific change and +possibly include passing tests. So you can opt-in into the behavior by +adding a hook to your ``conftest.py``:: + + @pytest.hookimpl + def pytest_doctestplus_diffhook(info): + info["use"] = True # Overwrite all results (even successes) + if info["fileno"] is None: + # E.g. NumPy has C docstrings that cannot be found, we can add + # custom logic here to try and find these: + info["filename"] = ... + info["lineno"] = ... + +Where ``info`` is a dictionary containing the following items: + +* ``use``: ``True`` or ``False`` signalling whether to apply the diff. This is + set to ``False`` if a doctest succeeded and ``True`` if the doctest failed. +* ``name``: The name of the test (e.g. the function being documented) +* ``filename``: The file that contains the test (this can be wrong in certain + situation and in that case ``test_lineno`` will be wrong as well). +* ``source``: The source code that was executed for this test +* ``test_lineno``: The line of code where the example block (or function) starts. + In some cases, the test file cannot be found and the lineno will be ``None``, + you can manually try to fix these. +* ``example_lineno``: The line number of the example snippet + (individual ``>>>``). +* ``want``: The current documentation. +* ``got``: The result of executing the example. + +You can modify the dictionary in-place to modify the behavior. + +Please note that we assume that this API will be used only occasionally and +reserve the right to change it at any time. + + Development Status ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/pytest_doctestplus.egg-info/SOURCES.txt new/pytest-doctestplus-1.1.0/pytest_doctestplus.egg-info/SOURCES.txt --- old/pytest-doctestplus-1.0.0/pytest_doctestplus.egg-info/SOURCES.txt 2023-08-11 18:22:14.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/pytest_doctestplus.egg-info/SOURCES.txt 2023-12-13 19:12:59.000000000 +0100 @@ -13,6 +13,7 @@ licenses/README.rst licenses/SYMPY_LICENSE.rst pytest_doctestplus/__init__.py +pytest_doctestplus/newhooks.py pytest_doctestplus/output_checker.py pytest_doctestplus/plugin.py pytest_doctestplus/utils.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/setup.cfg new/pytest-doctestplus-1.1.0/setup.cfg --- old/pytest-doctestplus-1.0.0/setup.cfg 2023-08-11 18:22:14.703384200 +0200 +++ new/pytest-doctestplus-1.1.0/setup.cfg 2023-12-13 19:12:59.113894500 +0100 @@ -3,7 +3,7 @@ url = https://github.com/scientific-python/pytest-doctestplus author = Scientific Python Developers classifiers = - Development Status :: 3 - Alpha + Development Status :: 5 - Production/Stable Framework :: Pytest Intended Audience :: Developers License :: OSI Approved :: BSD License @@ -11,7 +11,6 @@ Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -29,7 +28,7 @@ [options] zip_safe = False packages = find: -python_requires = >=3.7 +python_requires = >=3.8 setup_requires = setuptools_scm install_requires = diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/tests/conftest.py new/pytest-doctestplus-1.1.0/tests/conftest.py --- old/pytest-doctestplus-1.0.0/tests/conftest.py 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/tests/conftest.py 2023-12-13 19:12:44.000000000 +0100 @@ -8,7 +8,7 @@ # Keep this until we require numpy to be >=2.0 or there is a directive in doctestplus # to support multiple ways of repr -if Version(np.__version__) > Version("2.0.0.dev0+151"): +if Version(np.__version__) >= Version("2.0.dev"): np.set_printoptions(legacy="1.25") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/tests/python/doctests.py new/pytest-doctestplus-1.1.0/tests/python/doctests.py --- old/pytest-doctestplus-1.0.0/tests/python/doctests.py 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/tests/python/doctests.py 2023-12-13 19:12:44.000000000 +0100 @@ -1,5 +1,12 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst +""" +Also module level skips should be matched with `*` and `.`, test at least +the `.` version (the star would match all others too). + +>>> import foobar +""" + __doctest_skip__ = [ 'skip_this_test', 'ClassWithSomeBadDocTests.this_test_fails', @@ -7,6 +14,7 @@ ] __doctest_requires__ = { + '.': ['foobar'], 'depends_on_foobar': ['foobar'], 'depends_on_foobar_submodule': ['foobar.baz'], 'depends_on_two_modules': ['os', 'foobar'], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/tests/test_doctestplus.py new/pytest-doctestplus-1.1.0/tests/test_doctestplus.py --- old/pytest-doctestplus-1.0.0/tests/test_doctestplus.py 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/tests/test_doctestplus.py 2023-12-13 19:12:44.000000000 +0100 @@ -3,6 +3,8 @@ from textwrap import dedent import sys +from packaging.version import Version + import pytest import doctest @@ -12,6 +14,9 @@ pytest_plugins = ['pytester'] +PYTEST_LT_6 = Version(pytest.__version__) < Version('6.0.0') + + def test_ignored_whitespace(testdir): testdir.makeini( """ @@ -593,6 +598,32 @@ ).assertoutcome(passed=1) +@pytest.mark.xfail(reason='known issue, fenced code blocks require an extra trailing newline') +def test_markdown_fenced_code(testdir): + testdir.makefile('.md', foo="""\ +``` +>>> 1 + 1 +2 +``` +""") + testdir.inline_run( + '--doctest-plus', '--doctest-glob', '*.md' + ).assertoutcome(passed=1) + + +def test_markdown_fenced_code_with_extra_newline(testdir): + testdir.makefile('.md', foo="""\ +``` +>>> 1 + 1 +2 + +``` +""") + testdir.inline_run( + '--doctest-plus', '--doctest-glob', '*.md' + ).assertoutcome(passed=1) + + def test_text_file_comments(testdir): testdir.makefile( '.md', @@ -769,20 +800,38 @@ ) -def test_doctest_subpackage_requires(testdir, caplog): +# Note that each entry under doctest_subpackage_requires has different whitespace +# around the = to make sure that all cases work properly. +SUBPACKAGE_REQUIRES_INI = ( + "makeini", + """ + [pytest] + doctest_subpackage_requires = + test/a/* = pytest>1 + test/b/*= pytest>1;averyfakepackage>99999.9 + test/c/*=anotherfakepackage>=22000.1.2 + """ +) +SUBPACKAGE_REQUIRES_PYPROJECT = ( + "makepyprojecttoml", + """ + [tool.pytest.ini_options] + doctest_subpackage_requires = [ + "test/a/* = pytest>1", + "test/b/*= pytest>1;averyfakepackage>99999.9", + "test/c/*=anotherfakepackage>=22000.1.2", + ] + """ +) - # Note that each entry below has different whitespace around the = to - # make sure that all cases work properly. - testdir.makeini( - """ - [pytest] - doctest_subpackage_requires = - test/a/* = pytest>1 - test/b/*= pytest>1;averyfakepackage>99999.9 - test/c/*=anotherfakepackage>=22000.1.2 - """ - ) +@pytest.fixture() +def subpackage_requires_testdir(testdir, request): + if request.param[0] == 'makepyprojecttoml' and PYTEST_LT_6: + return None, None + + config_file = getattr(testdir, request.param[0])(request.param[1]) + test = testdir.mkdir('test') a = test.mkdir('a') b = test.mkdir('b') @@ -801,12 +850,46 @@ b.join('testcode.py').write(pyfile) c.join('testcode.py').write(pyfile) - reprec = testdir.inline_run(test, "--doctest-plus") + return config_file, testdir + + +@pytest.mark.parametrize('subpackage_requires_testdir', [SUBPACKAGE_REQUIRES_INI, SUBPACKAGE_REQUIRES_PYPROJECT], indirect=True) +def test_doctest_subpackage_requires(subpackage_requires_testdir, caplog): + config_file, testdir = subpackage_requires_testdir + if config_file is None: + pytest.skip("pyproject.toml not supported in pytest<6") + + reprec = testdir.inline_run(str(testdir), f"-c={config_file}", "--doctest-plus") reprec.assertoutcome(passed=1) assert reprec.listoutcomes()[0][0].location[0] == os.path.join('test', 'a', 'testcode.py') assert caplog.text == '' +@pytest.mark.parametrize(('import_mode', 'expected'), [ + pytest.param('importlib', dict(passed=2), marks=pytest.mark.skipif(PYTEST_LT_6, reason="importlib import mode not supported on Pytest <6"), id="importlib"), + pytest.param('append', dict(failed=1), id="append"), + pytest.param('prepend', dict(failed=1), id="prepend"), +]) +def test_import_mode(testdir, import_mode, expected): + """Test that two files with the same name but in different folders work with --import-mode=importlib.""" + a = testdir.mkdir('a') + b = testdir.mkdir('b') + + pyfile = dedent(""" + def f(): + ''' + >>> 1 + 1 + ''' + """) + + a.join('testcode.py').write(pyfile) + b.join('testcode.py').write(pyfile) + + reprec = testdir.inline_run(str(testdir), "--doctest-plus", f"--import-mode={import_mode}") + reprec.assertoutcome(**expected) + + def test_doctest_skip(testdir): testdir.makeini( """ @@ -1039,6 +1122,24 @@ assert ("something()\nUNEXPECTED EXCEPTION: NameError" in report.longreprtext) is cont_on_fail +def test_main(testdir): + pkg = testdir.mkdir('pkg') + code = dedent( + ''' + def f(): + raise RuntimeError("This is a CLI, do not execute module while doctesting") + + f() + ''' + ) + pkg.join('__init__.py').write_text("", "utf-8") + main_path = pkg.join('__main__.py') + main_path.write_text(code, "utf-8") + + testdir.inline_run(pkg).assertoutcome(passed=0) + testdir.inline_run(pkg, '--doctest-plus').assertoutcome(passed=0) + + def test_ufunc(testdir): pytest.importorskip('numpy') @@ -1144,6 +1245,88 @@ result.assertoutcome(passed=2, failed=0) +NORCURSEDIRS_INI = ( + "makeini", + """ + [pytest] + doctest_norecursedirs = + "bad_dir" + "*/bad_file.py" + """ +) +NORCURSEDIRS_PYPROJECT = ( + "makepyprojecttoml", + """ + [tool.pytest.ini_options] + doctest_norecursedirs = [ + "bad_dir", + "*/bad_file.py", + ] + """ +) + + +@pytest.fixture() +def norecursedirs_testdir(testdir, request): + if request.param[0] == 'makepyprojecttoml' and PYTEST_LT_6: + return None, None + + config_file = getattr(testdir, request.param[0])(request.param[1]) + + bad_text = dedent(""" + def f(): + ''' + This should fail doc testing + >>> 1 + 2 + ''' + pass + """) + + good_text = dedent(""" + def g(): + ''' + This should pass doc testing + >>> 1 + 1 + ''' + pass + """) + + # Create a bad file that should be by its folder + bad_subdir = testdir.mkdir("bad_dir") + bad_file = bad_subdir.join("test_foobar.py") + bad_file.write_text(bad_text, "utf-8") + + # Create a bad file that should be skipped by its name + okay_subdir1 = testdir.mkdir("okay_foo_dir") + bad_file = okay_subdir1.join("bad_file.py") + bad_file.write_text(bad_text, "utf-8") + # Create a good file in that directory that doctest won't skip + good_file1 = okay_subdir1.join("good_file1.py") + good_file1.write_text(good_text, "utf-8") + + # Create another bad file that should be skipped by its name + okay_subdir2 = testdir.mkdir("okay_bar_dir") + bad_file = okay_subdir2.join("bad_file.py") + bad_file.write_text(bad_text, "utf-8") + # Create a good file in that directory that doctest won't skip + good_file2 = okay_subdir2.join("good_file2.py") + good_file2.write_text(good_text, "utf-8") + + return config_file, testdir + + +@pytest.mark.parametrize('norecursedirs_testdir', [NORCURSEDIRS_INI, NORCURSEDIRS_PYPROJECT], indirect=True) +def test_doctest_norecursedirs(norecursedirs_testdir): + config_file, testdir = norecursedirs_testdir + if config_file is None: + pytest.skip("pyproject.toml not supported in pytest<6") + + reprec = testdir.inline_run(str(testdir), f"-c={config_file}", "--doctest-plus") + reprec.assertoutcome(passed=2) + + def test_norecursedirs(testdir): testdir.makeini( """ @@ -1165,3 +1348,39 @@ """, "utf-8") reprec = testdir.inline_run(str(testdir), "--doctest-plus") reprec.assertoutcome(failed=0, passed=0) + + +def test_generate_diff_basic(testdir, capsys): + p = testdir.makepyfile(""" + def f(): + ''' + >>> print(2) + 4 + >>> print(3) + 5 + ''' + pass + """) + with open(p) as f: + original = f.read() + + testdir.inline_run(p, "--doctest-plus-generate-diff") + diff = dedent(""" + >>> print(2) + - 4 + + 2 + >>> print(3) + - 5 + + 3 + """) + captured = capsys.readouterr() + assert diff in captured.out + + testdir.inline_run(p, "--doctest-plus-generate-diff=overwrite") + captured = capsys.readouterr() + assert "Applied fix to the following files" in captured.out + + with open(p) as f: + result = f.read() + + assert result == original.replace("4", "2").replace("5", "3") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-doctestplus-1.0.0/tox.ini new/pytest-doctestplus-1.1.0/tox.ini --- old/pytest-doctestplus-1.0.0/tox.ini 2023-08-11 18:21:57.000000000 +0200 +++ new/pytest-doctestplus-1.1.0/tox.ini 2023-12-13 19:12:44.000000000 +0100 @@ -1,6 +1,6 @@ [tox] envlist = - py{37,38,39,310,311,312}-test + py{38,39,310,311,312}-test codestyle requires = setuptools >= 30.3.0 @@ -14,7 +14,7 @@ py312: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/scientific-python-nightly-wheels/simple description = run tests deps = - pytest46: pytest==4.6.* + pytestoldest: pytest==4.6.0 pytest50: pytest==5.0.* pytest51: pytest==5.1.* pytest52: pytest==5.2.*
participants (1)
-
Source-Sync