From 22f54784c23793831699b5c239d3a4b534a2aa4d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 20 Jul 2017 19:14:54 -0300 Subject: [PATCH 01/20] Add "fix-lint" tox environment to fix linting errors --- CONTRIBUTING.rst | 7 +++++- changelog/2582.trivial | 1 + testing/code/test_excinfo.py | 16 ++++++------ testing/code/test_source.py | 19 ++------------ testing/code/test_source_multiline_block.py | 26 +++++++++++++++++++ testing/test_capture.py | 28 ++++++++++----------- testing/test_conftest.py | 4 +-- testing/test_runner.py | 4 +-- tox.ini | 8 ++++++ 9 files changed, 69 insertions(+), 44 deletions(-) create mode 100644 changelog/2582.trivial create mode 100644 testing/code/test_source_multiline_block.py diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index edf71dad7..71d70e8b7 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -212,7 +212,12 @@ but here is a simple overview: $ tox -e linting,py27,py36 This command will run tests via the "tox" tool against Python 2.7 and 3.6 - and also perform "lint" coding-style checks. + and also perform "lint" coding-style checks. If you have too much linting errors, + try running:: + + $ tox -e fix-lint + + To fix pep8 related errors. #. You can now edit your local working copy. 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/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index a7dfe80a6..37ceeb423 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -144,10 +144,10 @@ class TestTraceback_f_g_h(object): xyz() """) try: - exec (source.compile()) + exec(source.compile()) except NameError: tb = _pytest._code.ExceptionInfo().traceback - print (tb[-1].getsource()) + print(tb[-1].getsource()) s = str(tb[-1].getsource()) assert s.startswith("def xyz():\n try:") assert s.strip().endswith("except somenoname:") @@ -341,7 +341,7 @@ def test_excinfo_errisinstance(): def test_excinfo_no_sourcecode(): try: - exec ("raise ValueError()") + exec("raise ValueError()") except ValueError: excinfo = _pytest._code.ExceptionInfo() s = str(excinfo.traceback[-1]) @@ -431,7 +431,7 @@ class TestFormattedExcinfo(object): def excinfo_from_exec(self, source): source = _pytest._code.Source(source).strip() try: - exec (source.compile()) + exec(source.compile()) except KeyboardInterrupt: raise except: @@ -471,7 +471,7 @@ class TestFormattedExcinfo(object): pr = FormattedExcinfo() co = compile("raise ValueError()", "", "exec") try: - exec (co) + exec(co) except ValueError: excinfo = _pytest._code.ExceptionInfo() repr = pr.repr_excinfo(excinfo) @@ -486,7 +486,7 @@ a = 1 raise ValueError() """, "", "exec") try: - exec (co) + exec(co) except ValueError: excinfo = _pytest._code.ExceptionInfo() repr = pr.repr_excinfo(excinfo) @@ -992,7 +992,7 @@ raise ValueError() tw = TWMock() r.toterminal(tw) for line in tw.lines: - print (line) + print(line) assert tw.lines[0] == "" assert tw.lines[1] == " def f():" assert tw.lines[2] == "> g()" @@ -1040,7 +1040,7 @@ raise ValueError() tw = TWMock() r.toterminal(tw) for line in tw.lines: - print (line) + print(line) assert tw.lines[0] == "" assert tw.lines[1] == " def f():" assert tw.lines[2] == " try:" diff --git a/testing/code/test_source.py b/testing/code/test_source.py index f7f272c7b..aaa2b8c5f 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -170,12 +170,12 @@ class TestSourceParsingAndCompiling(object): def test_compile(self): co = _pytest._code.compile("x=3") d = {} - exec (co, d) + exec(co, d) assert d['x'] == 3 def test_compile_and_getsource_simple(self): co = _pytest._code.compile("x=3") - exec (co) + exec(co) source = _pytest._code.Source(co) assert str(source) == "x=3" @@ -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 38a92ca0e..313819a96 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -83,14 +83,14 @@ class TestCaptureManager(object): assert outerr == ("", "") outerr = capman.suspendcapture() assert outerr == ("", "") - print ("hello") + print("hello") out, err = capman.suspendcapture() if method == "no": assert old == (sys.stdout, sys.stderr, sys.stdin) else: assert not out capman.resumecapture() - print ("hello") + print("hello") out, err = capman.suspendcapture() if method != "no": assert out == "hello\n" @@ -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(""" @@ -305,7 +305,7 @@ class TestLoggingInteraction(object): assert 0 """) for optargs in (('--capture=sys',), ('--capture=fd',)): - print (optargs) + print(optargs) result = testdir.runpytest_subprocess(p, *optargs) s = result.stdout.str() result.stdout.fnmatch_lines([ @@ -331,7 +331,7 @@ class TestLoggingInteraction(object): assert 0 """) for optargs in (('--capture=sys',), ('--capture=fd',)): - print (optargs) + print(optargs) result = testdir.runpytest_subprocess(p, *optargs) s = result.stdout.str() result.stdout.fnmatch_lines([ @@ -879,7 +879,7 @@ class TestStdCapture(object): def test_capturing_readouterr(self): with self.getcapture() as cap: - print ("hello world") + print("hello world") sys.stderr.write("hello error\n") out, err = cap.readouterr() assert out == "hello world\n" @@ -890,7 +890,7 @@ class TestStdCapture(object): def test_capturing_readouterr_unicode(self): with self.getcapture() as cap: - print ("hx\xc4\x85\xc4\x87") + print("hx\xc4\x85\xc4\x87") out, err = cap.readouterr() assert out == py.builtin._totext("hx\xc4\x85\xc4\x87\n", "utf8") @@ -905,7 +905,7 @@ class TestStdCapture(object): def test_reset_twice_error(self): with self.getcapture() as cap: - print ("hello") + print("hello") out, err = cap.readouterr() pytest.raises(ValueError, cap.stop_capturing) assert out == "hello\n" @@ -919,7 +919,7 @@ class TestStdCapture(object): sys.stderr.write("world") sys.stdout = capture.CaptureIO() sys.stderr = capture.CaptureIO() - print ("not seen") + print("not seen") sys.stderr.write("not seen\n") out, err = cap.readouterr() assert out == "hello" @@ -929,9 +929,9 @@ class TestStdCapture(object): def test_capturing_error_recursive(self): with self.getcapture() as cap1: - print ("cap1") + print("cap1") with self.getcapture() as cap2: - print ("cap2") + print("cap2") out2, err2 = cap2.readouterr() out1, err1 = cap1.readouterr() assert out1 == "cap1\n" @@ -961,9 +961,9 @@ class TestStdCapture(object): assert sys.stdin is old def test_stdin_nulled_by_default(self): - print ("XXX this test may well hang instead of crashing") - print ("XXX which indicates an error in the underlying capturing") - print ("XXX mechanisms") + print("XXX this test may well hang instead of crashing") + print("XXX which indicates an error in the underlying capturing") + print("XXX mechanisms") with self.getcapture(): pytest.raises(IOError, "sys.stdin.read()") diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 05453f766..39590f5f2 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -321,9 +321,9 @@ class TestConftestVisibility(object): # use value from parent dir's """)) - print ("created directory structure:") + print("created directory structure:") for x in testdir.tmpdir.visit(): - print (" " + x.relto(testdir.tmpdir)) + print(" " + x.relto(testdir.tmpdir)) return { "runner": runner, diff --git a/testing/test_runner.py b/testing/test_runner.py index 842810f1b..567b98eeb 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -226,7 +226,7 @@ class BaseFunctionalTests(object): raise ValueError(42) """) reps = rec.getreports("pytest_runtest_logreport") - print (reps) + print(reps) for i in range(2): assert reps[i].nodeid.endswith("test_method") assert reps[i].passed @@ -253,7 +253,7 @@ class BaseFunctionalTests(object): assert True """) reps = rec.getreports("pytest_runtest_logreport") - print (reps) + print(reps) assert len(reps) == 3 # assert reps[0].nodeid.endswith("test_method") diff --git a/tox.ini b/tox.ini index c6fa445af..cbb61f5f7 100644 --- a/tox.ini +++ b/tox.ini @@ -149,6 +149,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 = From 3c28a8ec1a51d2857e53371ee2c61809da9fa845 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 20 Jul 2017 19:39:07 -0300 Subject: [PATCH 02/20] Improve formatting/grammar of changelog entries --- changelog/2375.bugfix | 1 + changelog/2375.trivial | 1 - changelog/2533.trivial | 2 +- changelog/2562.trivial | 2 +- changelog/2574.bugfix | 2 +- changelog/2581.trivial | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 changelog/2375.bugfix delete mode 100644 changelog/2375.trivial 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/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. From 3d24485caef6631c43f4964d43a15dc05a67a74a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 19 Jul 2017 21:11:17 -0300 Subject: [PATCH 03/20] Clarify PYTHONPATH changes and ``rootdir`` roles - Also minor adjustments in the docs (wording, formatting, links, etc). Fix #2589 --- _pytest/config.py | 1 - doc/en/cache.rst | 2 ++ doc/en/contents.rst | 3 +- doc/en/customize.rst | 30 +++++++++++----- doc/en/example/index.rst | 4 +-- doc/en/nose.rst | 2 +- doc/en/pythonpath.rst | 71 ++++++++++++++++++++++++++++++++++++++ doc/en/usage.rst | 2 +- doc/en/writing_plugins.rst | 8 +++-- 9 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 doc/en/pythonpath.rst 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/doc/en/cache.rst b/doc/en/cache.rst index 688b6dd04..f3da3e6d6 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -1,3 +1,5 @@ +.. _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 ce0a36c11..215e6ddd0 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 763328f5a..775297882 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 From 97e5a3c889c16d6f6ade0626210c412296b28b8e Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 21 Jul 2017 14:18:45 +0200 Subject: [PATCH 04/20] Fix help for filterwarnings ini option --- _pytest/warnings.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/_pytest/warnings.py b/_pytest/warnings.py index 915862a9d..9e0307281 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 From 50764d9ebbea4cd15717e49ba4ef4f467254a2be Mon Sep 17 00:00:00 2001 From: Andras Tim Date: Fri, 21 Jul 2017 21:27:48 +0200 Subject: [PATCH 05/20] Avoid interactive pdb when pytest tests itself - fix #2023 The debugging.py calls post_mortem() on error and pdb will drops an interactive debugger when the stdin is a readable fd. --- _pytest/pytester.py | 7 +++++-- changelog/2023.bugfix | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog/2023.bugfix diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 1783c9c0c..674adca94 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -916,8 +916,11 @@ class Testdir: env['PYTHONPATH'] = os.pathsep.join(filter(None, [ str(os.getcwd()), env.get('PYTHONPATH', '')])) kw['env'] = env - return subprocess.Popen(cmdargs, - stdout=stdout, stderr=stderr, **kw) + + popen = subprocess.Popen(cmdargs, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, **kw) + popen.stdin.close() + + return popen def run(self, *cmdargs): """Run a command with arguments. diff --git a/changelog/2023.bugfix b/changelog/2023.bugfix new file mode 100644 index 000000000..acf4b405b --- /dev/null +++ b/changelog/2023.bugfix @@ -0,0 +1 @@ +Set ``stdin`` to a closed ``PIPE`` in ``pytester.py.Testdir.popen()`` for avoid unwanted interactive ``pdb`` From 61219da0e29a0e72907d3047fca401a751d5703c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 22 Jul 2017 15:58:12 -0300 Subject: [PATCH 06/20] Update PR guide and add a "short" version --- CONTRIBUTING.rst | 69 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 71d70e8b7..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 @@ -212,19 +232,20 @@ but here is a simple overview: $ tox -e linting,py27,py36 This command will run tests via the "tox" tool against Python 2.7 and 3.6 - and also perform "lint" coding-style checks. If you have too much linting errors, - try running:: + and also perform "lint" coding-style checks. - $ tox -e fix-lint - - To fix pep8 related errors. - -#. 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 @@ -237,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:: From af2c15332422421ac280f90ac9a7ab470c44ed7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20H=E1=BB=93ng=20Qu=C3=A2n?= Date: Mon, 24 Jul 2017 11:52:24 +0700 Subject: [PATCH 07/20] Report lineno from doctest This is to fix pytest-sugar#122 issue. --- _pytest/doctest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(): From 0aa2480e6a0659b4f714fccf0d4784717903bfba Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 22 Jul 2017 22:11:51 -0300 Subject: [PATCH 08/20] Fix travis build after change from "precise" to "trusty" Travis recently has changed its dist from "precise" to "trusty", so some Python versions are no longer installed by default --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0a71e7dc1..a90101e52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,12 +11,9 @@ env: - TOXENV=coveralls # note: please use "tox --listenvs" to populate the build matrix below - TOXENV=linting - - TOXENV=py26 - TOXENV=py27 - - TOXENV=py33 - TOXENV=py34 - TOXENV=py35 - - TOXENV=pypy - TOXENV=py27-pexpect - TOXENV=py27-xdist - TOXENV=py27-trial @@ -30,6 +27,12 @@ env: matrix: include: + - env: TOXENV=py26 + python: '2.6' + - env: TOXENV=py33 + python: '3.3' + - env: TOXENV=pypy + python: 'pypy-5.4' - env: TOXENV=py36 python: '3.6' - env: TOXENV=py37 From 43544a431ca958b411d6b84c7093f03e48461349 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Mon, 24 Jul 2017 15:17:39 +0300 Subject: [PATCH 09/20] Early import colorama so that it get's the correct terminal --- _pytest/capture.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/_pytest/capture.py b/_pytest/capture.py index 481bc2549..be93eef2b 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 From df12500661e1408d247ff95193ad5a6dc7aaedbb Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Mon, 24 Jul 2017 15:28:20 +0300 Subject: [PATCH 10/20] Create 2611.bugfix --- changelog/2611.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/2611.bugfix diff --git a/changelog/2611.bugfix b/changelog/2611.bugfix new file mode 100644 index 000000000..a491ce959 --- /dev/null +++ b/changelog/2611.bugfix @@ -0,0 +1 @@ +Early import colorama so that it get's the correct terminal. From d90bef44cc2c0a84b300b730e5fddb22b9287c1d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 24 Jul 2017 09:31:16 -0300 Subject: [PATCH 11/20] Update changelog file for #2510 --- changelog/2510.bugfix | 1 + changelog/2611.bugfix | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog/2510.bugfix delete mode 100644 changelog/2611.bugfix 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/2611.bugfix b/changelog/2611.bugfix deleted file mode 100644 index a491ce959..000000000 --- a/changelog/2611.bugfix +++ /dev/null @@ -1 +0,0 @@ -Early import colorama so that it get's the correct terminal. From d0ecfdf00f951714058b629b21719cac2ab53c75 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Mon, 24 Jul 2017 16:55:50 +0300 Subject: [PATCH 12/20] Delete trailing whitespace --- _pytest/capture.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/capture.py b/_pytest/capture.py index be93eef2b..b627e5102 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -478,7 +478,7 @@ 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. From dea671f8ba3a76283735e8414a11595bc755872d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20H=E1=BB=93ng=20Qu=C3=A2n?= Date: Mon, 24 Jul 2017 22:01:03 +0700 Subject: [PATCH 13/20] Add changelog for #2610 --- changelog/2610.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/2610.bugfix 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 `_. From d40d77432c71d8eaa6bbc1dfb908fbcd4fe2bbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20H=E1=BB=93ng=20Qu=C3=A2n?= Date: Mon, 24 Jul 2017 23:07:45 +0700 Subject: [PATCH 14/20] Add test case for DoctestItem.reportinfo() --- testing/test_doctest.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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): From 72531f30c0d8834230f2007fe5b563c9fe5af04b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 4 Jul 2017 12:16:42 +0200 Subject: [PATCH 15/20] Improve error message for CollectError with skip/skipif --- _pytest/python.py | 7 ++++--- changelog/2546.trivial | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 changelog/2546.trivial diff --git a/_pytest/python.py b/_pytest/python.py index d0f13a758..29e3182d4 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -442,9 +442,10 @@ class Module(main.File, PyCollector): if e.allow_module_level: raise raise self.CollectError( - "Using pytest.skip outside of a test is not allowed. If you are " - "trying to decorate a test function, use the @pytest.mark.skip " - "or @pytest.mark.skipif decorators instead." + "Using pytest.skip outside of a test is not allowed. " + "To decorate a test function, use the @pytest.mark.skip " + "or @pytest.mark.skipif decorators instead, and to skip a " + "module use `pytestmark = pytest.mark.{skip,skipif}." ) self.config.pluginmanager.consider_module(mod) return mod diff --git a/changelog/2546.trivial b/changelog/2546.trivial new file mode 100644 index 000000000..53e43bc17 --- /dev/null +++ b/changelog/2546.trivial @@ -0,0 +1 @@ +Improve error message for CollectError with skip/skipif. From 869eed9898df0d06c7d9be916d4224141c7dd3fe Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 4 Jul 2017 12:57:15 +0200 Subject: [PATCH 16/20] Fix lineno offset in show_skipped The line number is 0-based here, so add 1. --- _pytest/skipping.py | 2 +- changelog/2548.bugfix | 1 + testing/test_skipping.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog/2548.bugfix diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 619650092..b11aea801 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -382,4 +382,4 @@ def show_skipped(terminalreporter, lines): reason = reason[9:] lines.append( "SKIP [%d] %s:%d: %s" % - (num, fspath, lineno, reason)) + (num, fspath, lineno + 1, reason)) diff --git a/changelog/2548.bugfix b/changelog/2548.bugfix new file mode 100644 index 000000000..9a44dc334 --- /dev/null +++ b/changelog/2548.bugfix @@ -0,0 +1 @@ +Fix lineno offset in show_skipped. diff --git a/testing/test_skipping.py b/testing/test_skipping.py index b780e4dc8..6608ccadf 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -708,7 +708,7 @@ def test_skipped_reasons_functional(testdir): ) result = testdir.runpytest('-rs') result.stdout.fnmatch_lines([ - "*SKIP*2*conftest.py:3: test", + "*SKIP*2*conftest.py:4: test", ]) assert result.ret == 0 From 949a1406f08495e0365e5ce91ea14ab104a34866 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 25 Jul 2017 18:27:26 +0200 Subject: [PATCH 17/20] Revisit CONTRIBUTING.rst --- CONTRIBUTING.rst | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index b3bdde78b..fa317fd1b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -34,13 +34,13 @@ If you are reporting a bug, please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting, - specifically Python interpreter version, - installed libraries and pytest version. + specifically the Python interpreter version, installed libraries, and pytest + version. * Detailed steps to reproduce the bug. -If you can write a demonstration test that currently fails but should pass (xfail), -that is a very useful commit to make as well, even if you can't find how -to fix the bug yet. +If you can write a demonstration test that currently fails but should pass +(xfail), that is a very useful commit to make as well, even if you cannot +fix the bug itself. .. _fixbugs: @@ -165,29 +165,30 @@ Short version ~~~~~~~~~~~~~ #. Fork the repository; -#. Target ``master`` for bug-fix and doc changes; +#. Target ``master`` for bugfixes 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. + The test environments above are usually enough 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. +#. 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 +What is a "pull request"? It informs the 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 +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: From 1a42e2658648dd967dd76958e2da817fd30850d6 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 25 Jul 2017 13:37:06 -0300 Subject: [PATCH 18/20] Improve changelog wording to be more user-oriented --- changelog/2548.bugfix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/2548.bugfix b/changelog/2548.bugfix index 9a44dc334..8201594ed 100644 --- a/changelog/2548.bugfix +++ b/changelog/2548.bugfix @@ -1 +1 @@ -Fix lineno offset in show_skipped. +Fix line number when reporting summary of skipped tests. From 0603d1d500c14a67715e486568b50f15b2086616 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 5 Jul 2017 21:54:33 +0200 Subject: [PATCH 19/20] capture: ensure name of EncodedFile being a string Fixes https://github.com/pytest-dev/pytest/issues/2555. --- _pytest/capture.py | 5 +++++ changelog/2555.bugfix | 1 + testing/test_capture.py | 10 ++++++++++ 3 files changed, 16 insertions(+) create mode 100644 changelog/2555.bugfix diff --git a/_pytest/capture.py b/_pytest/capture.py index b627e5102..a4171f0fa 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -254,6 +254,11 @@ class EncodedFile(object): data = ''.join(linelist) self.write(data) + @property + def name(self): + """Ensure that file.name is a string.""" + return repr(self.buffer) + def __getattr__(self, name): return getattr(object.__getattribute__(self, "buffer"), name) diff --git a/changelog/2555.bugfix b/changelog/2555.bugfix new file mode 100644 index 000000000..8c20bbc67 --- /dev/null +++ b/changelog/2555.bugfix @@ -0,0 +1 @@ +capture: ensure that EncodedFile.name is a string. diff --git a/testing/test_capture.py b/testing/test_capture.py index 313819a96..4dd5d8e09 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -716,13 +716,21 @@ def test_dupfile(tmpfile): assert nf not in flist print(i, end="", file=nf) flist.append(nf) + + fname_open = flist[0].name + assert fname_open == repr(flist[0].buffer) + for i in range(5): f = flist[i] f.close() + fname_closed = flist[0].name + assert fname_closed == repr(flist[0].buffer) + assert fname_closed != fname_open tmpfile.seek(0) s = tmpfile.read() assert "01234" in repr(s) tmpfile.close() + assert fname_closed == repr(flist[0].buffer) def test_dupfile_on_bytesio(): @@ -730,6 +738,7 @@ def test_dupfile_on_bytesio(): f = capture.safe_text_dupfile(io, "wb") f.write("hello") assert io.getvalue() == b"hello" + assert 'BytesIO object' in f.name def test_dupfile_on_textio(): @@ -737,6 +746,7 @@ def test_dupfile_on_textio(): f = capture.safe_text_dupfile(io, "wb") f.write("hello") assert io.getvalue() == "hello" + assert not hasattr(f, 'name') @contextlib.contextmanager From 10ded399d8246ef8383d5cac27f58fe9b0c65fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mihai=20Capot=C4=83?= Date: Wed, 26 Jul 2017 09:55:42 -0700 Subject: [PATCH 20/20] Show multiple issue links in CHANGELOG entries Restores the functionality removed in PR #2488. --- AUTHORS | 1 + changelog/2620.trivial | 1 + changelog/_template.rst | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changelog/2620.trivial diff --git a/AUTHORS b/AUTHORS index d58cd3d2d..5259d0322 100644 --- a/AUTHORS +++ b/AUTHORS @@ -120,6 +120,7 @@ Michael Birtwell Michael Droettboom Michael Seifert Michal Wajszczuk +Mihai Capotă Mike Lundy Ned Batchelder Neven Mundar diff --git a/changelog/2620.trivial b/changelog/2620.trivial new file mode 100644 index 000000000..51c0bd160 --- /dev/null +++ b/changelog/2620.trivial @@ -0,0 +1 @@ +Show multiple issue links in CHANGELOG entries. diff --git a/changelog/_template.rst b/changelog/_template.rst index 66c850ffd..a898abc15 100644 --- a/changelog/_template.rst +++ b/changelog/_template.rst @@ -13,7 +13,8 @@ {% if definitions[category]['showcontent'] %} {% for text, values in sections[section][category]|dictsort(by='value') %} -- {{ text }}{% if category != 'vendor' %} (`{{ values[0] }} `_){% endif %} +{% set issue_joiner = joiner(', ') %} +- {{ text }}{% if category != 'vendor' %} ({% for value in values|sort %}{{ issue_joiner() }}`{{ value }} `_{% endfor %}){% endif %} {% endfor %}