diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index edf71dad7..b3bdde78b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -158,19 +158,39 @@ As stated, the objective is to share maintenance and avoid "plugin-abandon". .. _`pull requests`: .. _pull-requests: -Preparing Pull Requests on GitHub ---------------------------------- +Preparing Pull Requests +----------------------- -.. note:: - What is a "pull request"? It informs project's core developers about the - changes you want to review and merge. Pull requests are stored on - `GitHub servers `_. - Once you send a pull request, we can discuss its potential modifications and - even add more commits to it later on. +Short version +~~~~~~~~~~~~~ -There's an excellent tutorial on how Pull Requests work in the -`GitHub Help Center `_, -but here is a simple overview: +#. Fork the repository; +#. Target ``master`` for bug-fix and doc changes; +#. Target ``features`` for new features or functionality changes. +#. Follow **PEP-8**. There's a ``tox`` command to help fixing it: ``tox -e fix-lint``. +#. Tests are run using ``tox``:: + + tox -e linting,py27,py36 + + The test environments above are usually enough to to cover most cases locally. + +#. Write a ``changelog`` entry: ``changelog/2574.bugfix``, use issue id number and one of + ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or ``trivial`` for the issue type. +#. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order. + + +Long version +~~~~~~~~~~~~ + + +What is a "pull request"? It informs project's core developers about the +changes you want to review and merge. Pull requests are stored on +`GitHub servers `_. +Once you send a pull request, we can discuss its potential modifications and +even add more commits to it later on. There's an excellent tutorial on how Pull Requests work in the +`GitHub Help Center `_. + +Here is a simple overview, with pytest-specific bits: #. Fork the `pytest GitHub repository `__. It's @@ -214,12 +234,18 @@ but here is a simple overview: This command will run tests via the "tox" tool against Python 2.7 and 3.6 and also perform "lint" coding-style checks. -#. You can now edit your local working copy. +#. You can now edit your local working copy. Please follow PEP-8. You can now make the changes you want and run the tests again as necessary. - To run tests on Python 2.7 and pass options to pytest (e.g. enter pdb on - failure) to pytest you can do:: + If you have too much linting errors, try running:: + + $ tox -e fix-lint + + To fix pep8 related errors. + + You can pass different options to ``tox``. For example, to run tests on Python 2.7 and pass options to pytest + (e.g. enter pdb on failure) to pytest you can do:: $ tox -e py27 -- --pdb @@ -232,9 +258,11 @@ but here is a simple overview: $ git commit -a -m "" $ git push -u - Make sure you add a message to ``CHANGELOG.rst`` and add yourself to - ``AUTHORS``. If you are unsure about either of these steps, submit your - pull request and we'll help you fix it up. +#. Create a new changelog entry in ``changelog``. The file should be named ``.``, + where *issueid* is the number of the issue related to the change and *type* is one of + ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or ``trivial``. + +#. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order. #. Finally, submit a pull request through the GitHub website using this data:: diff --git a/_pytest/capture.py b/_pytest/capture.py index 481bc2549..b627e5102 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -37,6 +37,7 @@ def pytest_load_initial_conftests(early_config, parser, args): ns = early_config.known_args_namespace if ns.capture == "fd": _py36_windowsconsoleio_workaround() + _colorama_workaround() _readline_workaround() pluginmanager = early_config.pluginmanager capman = CaptureManager(ns.capture) @@ -473,6 +474,24 @@ class DontReadFromInput: raise AttributeError('redirected stdin has no attribute buffer') +def _colorama_workaround(): + """ + Ensure colorama is imported so that it attaches to the correct stdio + handles on Windows. + + colorama uses the terminal on import time. So if something does the + first import of colorama while I/O capture is active, colorama will + fail in various ways. + """ + + if not sys.platform.startswith('win32'): + return + try: + import colorama # noqa + except ImportError: + pass + + def _readline_workaround(): """ Ensure readline is imported so that it attaches to the correct stdio diff --git a/_pytest/config.py b/_pytest/config.py index 4698ba790..d0ec62096 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -1079,7 +1079,6 @@ class Config(object): self.pluginmanager.load_setuptools_entrypoints('pytest11') self.pluginmanager.consider_env() self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy()) - confcutdir = self.known_args_namespace.confcutdir if self.known_args_namespace.confcutdir is None and self.inifile: confcutdir = py.path.local(self.inifile).dirname self.known_args_namespace.confcutdir = confcutdir diff --git a/_pytest/doctest.py b/_pytest/doctest.py index 88174cc72..cc505c8d0 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -140,7 +140,7 @@ class DoctestItem(pytest.Item): return super(DoctestItem, self).repr_failure(excinfo) def reportinfo(self): - return self.fspath, None, "[doctest] %s" % self.name + return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name def _get_flag_lookup(): diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 1b3c8387e..926b1f581 100644 --- a/_pytest/warnings.py +++ b/_pytest/warnings.py @@ -39,8 +39,9 @@ def pytest_addoption(parser): '-W', '--pythonwarnings', action='append', help="set which warnings to report, see -W option of python itself.") parser.addini("filterwarnings", type="linelist", - help="Each line specifies warning filter pattern which would be passed" - "to warnings.filterwarnings. Process after -W and --pythonwarnings.") + help="Each line specifies a pattern for " + "warnings.filterwarnings. " + "Processed after -W and --pythonwarnings.") @contextmanager diff --git a/changelog/2375.bugfix b/changelog/2375.bugfix new file mode 100644 index 000000000..3f4fd3c3d --- /dev/null +++ b/changelog/2375.bugfix @@ -0,0 +1 @@ +Add missing ``encoding`` attribute to ``sys.std*`` streams when using ``capsys`` capture mode. diff --git a/changelog/2375.trivial b/changelog/2375.trivial deleted file mode 100644 index a73ab6ccf..000000000 --- a/changelog/2375.trivial +++ /dev/null @@ -1 +0,0 @@ -Provides encoding attribute on CaptureIO. diff --git a/changelog/2510.bugfix b/changelog/2510.bugfix new file mode 100644 index 000000000..e6fcb7c74 --- /dev/null +++ b/changelog/2510.bugfix @@ -0,0 +1 @@ +Fix terminal color changing to black on Windows if ``colorama`` is imported in a ``conftest.py`` file. diff --git a/changelog/2533.trivial b/changelog/2533.trivial index cac4c3bdd..930fd4c0d 100644 --- a/changelog/2533.trivial +++ b/changelog/2533.trivial @@ -1 +1 @@ -Renamed the utility function `_pytest.compat._escape_strings` to `_ascii_escaped` to better communicate the function's purpose. +Renamed the utility function ``_pytest.compat._escape_strings`` to ``_ascii_escaped`` to better communicate the function's purpose. diff --git a/changelog/2562.trivial b/changelog/2562.trivial index 605c7cf74..33e34ff65 100644 --- a/changelog/2562.trivial +++ b/changelog/2562.trivial @@ -1 +1 @@ -Emit yield test warning only once per generator +Emit warning about ``yield`` tests being deprecated only once per generator. diff --git a/changelog/2574.bugfix b/changelog/2574.bugfix index 49a01342b..13396bc16 100644 --- a/changelog/2574.bugfix +++ b/changelog/2574.bugfix @@ -1 +1 @@ -The options --fixtures and --fixtures-per-test will now keep indentation within docstrings. +The options ```--fixtures`` and ```--fixtures-per-test`` will now keep indentation within docstrings. diff --git a/changelog/2581.trivial b/changelog/2581.trivial index ea6785c79..341ef337f 100644 --- a/changelog/2581.trivial +++ b/changelog/2581.trivial @@ -1 +1 @@ -Fixed all flake8 errors and warnings +Fixed all flake8 errors and warnings. diff --git a/changelog/2582.trivial b/changelog/2582.trivial new file mode 100644 index 000000000..a4e0793e4 --- /dev/null +++ b/changelog/2582.trivial @@ -0,0 +1 @@ +Added ``fix-lint`` tox environment to run automatic pep8 fixes on the code. diff --git a/changelog/2610.bugfix b/changelog/2610.bugfix new file mode 100644 index 000000000..3757723e0 --- /dev/null +++ b/changelog/2610.bugfix @@ -0,0 +1 @@ +doctests line numbers are now reported correctly, fixing `pytest-sugar#122 `_. diff --git a/doc/en/cache.rst b/doc/en/cache.rst index e4071a8f8..a6c85c3d4 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -1,4 +1,6 @@ .. _`cache_provider`: +.. _cache: + Cache: working with cross-testrun state ======================================= diff --git a/doc/en/contents.rst b/doc/en/contents.rst index 9f4a9a1be..4f5bf789f 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -31,9 +31,10 @@ Full pytest documentation plugins writing_plugins - example/index goodpractices + pythonpath customize + example/index bash-completion backwards-compatibility diff --git a/doc/en/customize.rst b/doc/en/customize.rst index 7be7ca2e5..db296b7ea 100644 --- a/doc/en/customize.rst +++ b/doc/en/customize.rst @@ -1,5 +1,5 @@ -Basic test configuration -=================================== +Configuration +============= Command line options and configuration file settings ----------------------------------------------------------------- @@ -15,17 +15,31 @@ which were registered by installed plugins. .. _rootdir: .. _inifiles: -initialization: determining rootdir and inifile +Initialization: determining rootdir and inifile ----------------------------------------------- .. versionadded:: 2.7 -pytest determines a "rootdir" for each test run which depends on +pytest determines a ``rootdir`` for each test run which depends on the command line arguments (specified test files, paths) and on -the existence of inifiles. The determined rootdir and ini-file are -printed as part of the pytest header. The rootdir is used for constructing -"nodeids" during collection and may also be used by plugins to store -project/testrun-specific information. +the existence of *ini-files*. The determined ``rootdir`` and *ini-file* are +printed as part of the pytest header during startup. + +Here's a summary what ``pytest`` uses ``rootdir`` for: + +* Construct *nodeids* during collection; each test is assigned + a unique *nodeid* which is rooted at the ``rootdir`` and takes in account full path, + class name, function name and parametrization (if any). + +* Is used by plugins as a stable location to store project/test run specific information; + for example, the internal :ref:`cache ` plugin creates a ``.cache`` subdirectory + in ``rootdir`` to store its cross-test run state. + +Important to emphasize that ``rootdir`` is **NOT** used to modify ``sys.path``/``PYTHONPATH`` or +influence how modules are imported. See :ref:`pythonpath` for more details. + +Finding the ``rootdir`` +~~~~~~~~~~~~~~~~~~~~~~~ Here is the algorithm which finds the rootdir from ``args``: diff --git a/doc/en/example/index.rst b/doc/en/example/index.rst index 363de5ab7..f63cb822a 100644 --- a/doc/en/example/index.rst +++ b/doc/en/example/index.rst @@ -1,8 +1,8 @@ .. _examples: -Usages and Examples -=========================================== +Examples and customization tricks +================================= Here is a (growing) list of examples. :ref:`Contact ` us if you need more examples or have questions. Also take a look at the diff --git a/doc/en/nose.rst b/doc/en/nose.rst index 5effd0d7b..10a10633a 100644 --- a/doc/en/nose.rst +++ b/doc/en/nose.rst @@ -26,7 +26,7 @@ Supported nose Idioms * setup and teardown at module/class/method level * SkipTest exceptions and markers * setup/teardown decorators -* ``yield``-based tests and their setup +* ``yield``-based tests and their setup (considered deprecated as of pytest 3.0) * ``__test__`` attribute on modules/classes/functions * general usage of nose utilities diff --git a/doc/en/pythonpath.rst b/doc/en/pythonpath.rst new file mode 100644 index 000000000..67de7f5d2 --- /dev/null +++ b/doc/en/pythonpath.rst @@ -0,0 +1,71 @@ +.. _pythonpath: + +pytest import mechanisms and ``sys.path``/``PYTHONPATH`` +======================================================== + +Here's a list of scenarios where pytest may need to change ``sys.path`` in order +to import test modules or ``conftest.py`` files. + +Test modules / ``conftest.py`` files inside packages +---------------------------------------------------- + +Consider this file and directory layout:: + + root/ + |- foo/ + |- __init__.py + |- conftest.py + |- bar/ + |- __init__.py + |- tests/ + |- __init__.py + |- test_foo.py + + +When executing:: + + pytest root/ + + + +pytest will find ``foo/bar/tests/test_foo.py`` and realize it is part of a package given that +there's an ``__init__.py`` file in the same folder. It will then search upwards until it can find the +last folder which still contains an ``__init__.py`` file in order to find the package *root* (in +this case ``foo/``). To load the module, it will insert ``root/`` to the front of +``sys.path`` (if not there already) in order to load +``test_foo.py`` as the *module* ``foo.bar.tests.test_foo``. + +The same logic applies to the ``conftest.py`` file: it will be imported as ``foo.conftest`` module. + +Preserving the full package name is important when tests live in a package to avoid problems +and allow test modules to have duplicated names. This is also discussed in details in +:ref:`test discovery`. + +Standalone test modules / ``conftest.py`` files +----------------------------------------------- + +Consider this file and directory layout:: + + root/ + |- foo/ + |- conftest.py + |- bar/ + |- tests/ + |- test_foo.py + + +When executing:: + + pytest root/ + +pytest will find ``foo/bar/tests/test_foo.py`` and realize it is NOT part of a package given that +there's no ``__init__.py`` file in the same folder. It will then add ``root/foo/bar/tests`` to +``sys.path`` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done +with the ``conftest.py`` file by adding ``root/foo`` to ``sys.path`` to import it as ``conftest``. + +For this reason this layout cannot have test modules with the same name, as they all will be +imported in the global import namespace. + +This is also discussed in details in :ref:`test discovery`. + + diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 64c072886..ec5504994 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -17,7 +17,7 @@ You can invoke testing through the Python interpreter from the command line:: python -m pytest [...] This is almost equivalent to invoking the command line script ``pytest [...]`` -directly, except that python will also add the current directory to ``sys.path``. +directly, except that Python will also add the current directory to ``sys.path``. Possible exit codes -------------------------------------------------------------- diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 861f2f48a..26e1a8a69 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -49,7 +49,7 @@ Plugin discovery order at tool startup Note that pytest does not find ``conftest.py`` files in deeper nested sub directories at tool startup. It is usually a good idea to keep - your conftest.py file in the top level test or project root directory. + your ``conftest.py`` file in the top level test or project root directory. * by recursively loading all plugins specified by the ``pytest_plugins`` variable in ``conftest.py`` files @@ -94,10 +94,12 @@ Here is how you might run it:: If you have ``conftest.py`` files which do not reside in a python package directory (i.e. one containing an ``__init__.py``) then "import conftest" can be ambiguous because there might be other - ``conftest.py`` files as well on your PYTHONPATH or ``sys.path``. + ``conftest.py`` files as well on your ``PYTHONPATH`` or ``sys.path``. It is thus good practice for projects to either put ``conftest.py`` under a package scope or to never import anything from a - conftest.py file. + ``conftest.py`` file. + + See also: :ref:`pythonpath`. Writing your own plugin diff --git a/testing/code/test_source.py b/testing/code/test_source.py index f8b6af3ee..aaa2b8c5f 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -335,21 +335,6 @@ def test_getstartingblock_singleline(): assert len(l) == 1 -def test_getstartingblock_multiline(): - class A(object): - def __init__(self, *args): - frame = sys._getframe(1) - self.source = _pytest._code.Frame(frame).statement - - x = A('x', - 'y' - , - 'z') - - l = [i for i in x.source.lines if i.strip()] - assert len(l) == 4 - - def test_getline_finally(): def c(): pass excinfo = pytest.raises(TypeError, """ diff --git a/testing/code/test_source_multiline_block.py b/testing/code/test_source_multiline_block.py new file mode 100644 index 000000000..4e8735d0c --- /dev/null +++ b/testing/code/test_source_multiline_block.py @@ -0,0 +1,26 @@ +# flake8: noqa +import sys + +import _pytest._code + + +def test_getstartingblock_multiline(): + """ + This test was originally found in test_source.py, but it depends on the weird + formatting of the ``x = A`` construct seen here and our autopep8 tool can only exclude entire + files (it does not support excluding lines/blocks using the traditional #noqa comment yet, + see hhatto/autopep8#307). It was considered better to just move this single test to its own + file and exclude it from autopep8 than try to complicate things. + """ + class A(object): + def __init__(self, *args): + frame = sys._getframe(1) + self.source = _pytest._code.Frame(frame).statement + + x = A('x', + 'y' + , + 'z') + + l = [i for i in x.source.lines if i.strip()] + assert len(l) == 4 diff --git a/testing/test_capture.py b/testing/test_capture.py index 7380ad654..313819a96 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -288,7 +288,7 @@ class TestLoggingInteraction(object): stream.close() # to free memory/release resources """) result = testdir.runpytest_subprocess(p) - result.stderr.str().find("atexit") == -1 + assert result.stderr.str().find("atexit") == -1 def test_logging_and_immediate_setupteardown(self, testdir): p = testdir.makepyfile(""" diff --git a/testing/test_doctest.py b/testing/test_doctest.py index dd444569c..8a81ea0ed 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -545,6 +545,22 @@ class TestDoctests(object): result = testdir.runpytest(p, '--doctest-modules') result.stdout.fnmatch_lines(['* 1 passed *']) + def test_reportinfo(self, testdir): + ''' + Test case to make sure that DoctestItem.reportinfo() returns lineno. + ''' + p = testdir.makepyfile(test_reportinfo=""" + def foo(x): + ''' + >>> foo('a') + 'b' + ''' + return 'c' + """) + items, reprec = testdir.inline_genitems(p, '--doctest-modules') + reportinfo = items[0].reportinfo() + assert reportinfo[1] == 1 + class TestLiterals(object): diff --git a/tox.ini b/tox.ini index 3dce17b34..61a9ece8d 100644 --- a/tox.ini +++ b/tox.ini @@ -156,6 +156,14 @@ commands = rm -rf /tmp/doc-exec* make regen +[testenv:fix-lint] +skipsdist = True +usedevelop = True +deps = + autopep8 +commands = + autopep8 --in-place -r --max-line-length=120 --exclude=vendored_packages,test_source_multiline_block.py _pytest testing + [testenv:jython] changedir = testing commands =