Merge remote-tracking branch 'upstream/features' into issue-1619-conftest-assert-rewrite
This commit is contained in:
		
						commit
						573866bfad
					
				
							
								
								
									
										1
									
								
								AUTHORS
								
								
								
								
							
							
						
						
									
										1
									
								
								AUTHORS
								
								
								
								
							|  | @ -72,6 +72,7 @@ Michael Birtwell | ||||||
| Michael Droettboom | Michael Droettboom | ||||||
| Mike Lundy | Mike Lundy | ||||||
| Nicolas Delaby | Nicolas Delaby | ||||||
|  | Oleg Pidsadnyi | ||||||
| Omar Kohl | Omar Kohl | ||||||
| Pieter Mulder | Pieter Mulder | ||||||
| Piotr Banaszkiewicz | Piotr Banaszkiewicz | ||||||
|  |  | ||||||
|  | @ -82,6 +82,12 @@ | ||||||
| 
 | 
 | ||||||
| * | * | ||||||
| 
 | 
 | ||||||
|  | * Fix `#1421`_: Exit tests if a collection error occurs and add | ||||||
|  |   ``--continue-on-collection-errors`` option to restore previous behaviour. | ||||||
|  |   Thanks `@olegpidsadnyi`_ and `@omarkohl`_ for the complete PR (`#1628`_). | ||||||
|  | 
 | ||||||
|  | * | ||||||
|  | 
 | ||||||
| * | * | ||||||
| 
 | 
 | ||||||
| .. _@milliams: https://github.com/milliams | .. _@milliams: https://github.com/milliams | ||||||
|  | @ -94,8 +100,13 @@ | ||||||
| .. _@palaviv: https://github.com/palaviv | .. _@palaviv: https://github.com/palaviv | ||||||
| .. _@omarkohl: https://github.com/omarkohl | .. _@omarkohl: https://github.com/omarkohl | ||||||
| .. _@mikofski: https://github.com/mikofski | .. _@mikofski: https://github.com/mikofski | ||||||
|  | <<<<<<< HEAD | ||||||
| .. _@sober7: https://github.com/sober7 | .. _@sober7: https://github.com/sober7 | ||||||
|  | ======= | ||||||
|  | .. _@olegpidsadnyi: https://github.com/olegpidsadnyi | ||||||
|  | >>>>>>> upstream/features | ||||||
| 
 | 
 | ||||||
|  | .. _#1421: https://github.com/pytest-dev/pytest/issues/1421 | ||||||
| .. _#1426: https://github.com/pytest-dev/pytest/issues/1426 | .. _#1426: https://github.com/pytest-dev/pytest/issues/1426 | ||||||
| .. _#1428: https://github.com/pytest-dev/pytest/pull/1428 | .. _#1428: https://github.com/pytest-dev/pytest/pull/1428 | ||||||
| .. _#1444: https://github.com/pytest-dev/pytest/pull/1444 | .. _#1444: https://github.com/pytest-dev/pytest/pull/1444 | ||||||
|  | @ -111,6 +122,7 @@ | ||||||
| .. _#372: https://github.com/pytest-dev/pytest/issues/372 | .. _#372: https://github.com/pytest-dev/pytest/issues/372 | ||||||
| .. _#1544: https://github.com/pytest-dev/pytest/issues/1544 | .. _#1544: https://github.com/pytest-dev/pytest/issues/1544 | ||||||
| .. _#1616: https://github.com/pytest-dev/pytest/pull/1616 | .. _#1616: https://github.com/pytest-dev/pytest/pull/1616 | ||||||
|  | .. _#1628: https://github.com/pytest-dev/pytest/pull/1628 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| **Bug Fixes** | **Bug Fixes** | ||||||
|  |  | ||||||
|  | @ -48,6 +48,9 @@ def pytest_addoption(parser): | ||||||
|                help="run pytest in strict mode, warnings become errors.") |                help="run pytest in strict mode, warnings become errors.") | ||||||
|     group._addoption("-c", metavar="file", type=str, dest="inifilename", |     group._addoption("-c", metavar="file", type=str, dest="inifilename", | ||||||
|                help="load configuration from `file` instead of trying to locate one of the implicit configuration files.") |                help="load configuration from `file` instead of trying to locate one of the implicit configuration files.") | ||||||
|  |     group._addoption("--continue-on-collection-errors", action="store_true", | ||||||
|  |                default=False, dest="continue_on_collection_errors", | ||||||
|  |                help="Force test execution even if collection errors occur.") | ||||||
| 
 | 
 | ||||||
|     group = parser.getgroup("collect", "collection") |     group = parser.getgroup("collect", "collection") | ||||||
|     group.addoption('--collectonly', '--collect-only', action="store_true", |     group.addoption('--collectonly', '--collect-only', action="store_true", | ||||||
|  | @ -133,6 +136,11 @@ def pytest_collection(session): | ||||||
|     return session.perform_collect() |     return session.perform_collect() | ||||||
| 
 | 
 | ||||||
| def pytest_runtestloop(session): | def pytest_runtestloop(session): | ||||||
|  |     if (session.testsfailed and | ||||||
|  |             not session.config.option.continue_on_collection_errors): | ||||||
|  |         raise session.Interrupted( | ||||||
|  |             "%d errors during collection" % session.testsfailed) | ||||||
|  | 
 | ||||||
|     if session.config.option.collectonly: |     if session.config.option.collectonly: | ||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -120,7 +120,7 @@ class TestGeneralUsage: | ||||||
|             "ImportError while importing test module*", |             "ImportError while importing test module*", | ||||||
|             "'No module named *does_not_work*", |             "'No module named *does_not_work*", | ||||||
|         ]) |         ]) | ||||||
|         assert result.ret == 1 |         assert result.ret == 2 | ||||||
| 
 | 
 | ||||||
|     def test_not_collectable_arguments(self, testdir): |     def test_not_collectable_arguments(self, testdir): | ||||||
|         p1 = testdir.makepyfile("") |         p1 = testdir.makepyfile("") | ||||||
|  | @ -665,11 +665,13 @@ class TestDurations: | ||||||
|         testdir.makepyfile(self.source) |         testdir.makepyfile(self.source) | ||||||
|         testdir.makepyfile(test_collecterror="""xyz""") |         testdir.makepyfile(test_collecterror="""xyz""") | ||||||
|         result = testdir.runpytest("--durations=2", "-k test_1") |         result = testdir.runpytest("--durations=2", "-k test_1") | ||||||
|         assert result.ret != 0 |         assert result.ret == 2 | ||||||
|         result.stdout.fnmatch_lines([ |         result.stdout.fnmatch_lines([ | ||||||
|             "*durations*", |             "*Interrupted: 1 errors during collection*", | ||||||
|             "*call*test_1*", |  | ||||||
|         ]) |         ]) | ||||||
|  |         # Collection errors abort test execution, therefore no duration is | ||||||
|  |         # output | ||||||
|  |         assert "duration" not in result.stdout.str() | ||||||
| 
 | 
 | ||||||
|     def test_with_not(self, testdir): |     def test_with_not(self, testdir): | ||||||
|         testdir.makepyfile(self.source) |         testdir.makepyfile(self.source) | ||||||
|  |  | ||||||
|  | @ -642,3 +642,114 @@ class TestNodekeywords: | ||||||
|         """) |         """) | ||||||
|         reprec = testdir.inline_run("-k repr") |         reprec = testdir.inline_run("-k repr") | ||||||
|         reprec.assertoutcome(passed=1, failed=0) |         reprec.assertoutcome(passed=1, failed=0) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | COLLECTION_ERROR_PY_FILES = dict( | ||||||
|  |     test_01_failure=""" | ||||||
|  |         def test_1(): | ||||||
|  |             assert False | ||||||
|  |         """, | ||||||
|  |     test_02_import_error=""" | ||||||
|  |         import asdfasdfasdf | ||||||
|  |         def test_2(): | ||||||
|  |             assert True | ||||||
|  |         """, | ||||||
|  |     test_03_import_error=""" | ||||||
|  |         import asdfasdfasdf | ||||||
|  |         def test_3(): | ||||||
|  |             assert True | ||||||
|  |     """, | ||||||
|  |     test_04_success=""" | ||||||
|  |         def test_4(): | ||||||
|  |             assert True | ||||||
|  |     """, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | def test_exit_on_collection_error(testdir): | ||||||
|  |     """Verify that all collection errors are collected and no tests executed""" | ||||||
|  |     testdir.makepyfile(**COLLECTION_ERROR_PY_FILES) | ||||||
|  | 
 | ||||||
|  |     res = testdir.runpytest() | ||||||
|  |     assert res.ret == 2 | ||||||
|  | 
 | ||||||
|  |     res.stdout.fnmatch_lines([ | ||||||
|  |         "collected 2 items / 2 errors", | ||||||
|  |         "*ERROR collecting test_02_import_error.py*", | ||||||
|  |         "*No module named *asdfa*", | ||||||
|  |         "*ERROR collecting test_03_import_error.py*", | ||||||
|  |         "*No module named *asdfa*", | ||||||
|  |     ]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_exit_on_collection_with_maxfail_smaller_than_n_errors(testdir): | ||||||
|  |     """ | ||||||
|  |     Verify collection is aborted once maxfail errors are encountered ignoring | ||||||
|  |     further modules which would cause more collection errors. | ||||||
|  |     """ | ||||||
|  |     testdir.makepyfile(**COLLECTION_ERROR_PY_FILES) | ||||||
|  | 
 | ||||||
|  |     res = testdir.runpytest("--maxfail=1") | ||||||
|  |     assert res.ret == 2 | ||||||
|  | 
 | ||||||
|  |     res.stdout.fnmatch_lines([ | ||||||
|  |         "*ERROR collecting test_02_import_error.py*", | ||||||
|  |         "*No module named *asdfa*", | ||||||
|  |         "*Interrupted: stopping after 1 failures*", | ||||||
|  |     ]) | ||||||
|  | 
 | ||||||
|  |     assert 'test_03' not in res.stdout.str() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_exit_on_collection_with_maxfail_bigger_than_n_errors(testdir): | ||||||
|  |     """ | ||||||
|  |     Verify the test run aborts due to collection errors even if maxfail count of | ||||||
|  |     errors was not reached. | ||||||
|  |     """ | ||||||
|  |     testdir.makepyfile(**COLLECTION_ERROR_PY_FILES) | ||||||
|  | 
 | ||||||
|  |     res = testdir.runpytest("--maxfail=4") | ||||||
|  |     assert res.ret == 2 | ||||||
|  | 
 | ||||||
|  |     res.stdout.fnmatch_lines([ | ||||||
|  |         "collected 2 items / 2 errors", | ||||||
|  |         "*ERROR collecting test_02_import_error.py*", | ||||||
|  |         "*No module named *asdfa*", | ||||||
|  |         "*ERROR collecting test_03_import_error.py*", | ||||||
|  |         "*No module named *asdfa*", | ||||||
|  |     ]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_continue_on_collection_errors(testdir): | ||||||
|  |     """ | ||||||
|  |     Verify tests are executed even when collection errors occur when the | ||||||
|  |     --continue-on-collection-errors flag is set | ||||||
|  |     """ | ||||||
|  |     testdir.makepyfile(**COLLECTION_ERROR_PY_FILES) | ||||||
|  | 
 | ||||||
|  |     res = testdir.runpytest("--continue-on-collection-errors") | ||||||
|  |     assert res.ret == 1 | ||||||
|  | 
 | ||||||
|  |     res.stdout.fnmatch_lines([ | ||||||
|  |         "collected 2 items / 2 errors", | ||||||
|  |         "*1 failed, 1 passed, 2 error*", | ||||||
|  |     ]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_continue_on_collection_errors_maxfail(testdir): | ||||||
|  |     """ | ||||||
|  |     Verify tests are executed even when collection errors occur and that maxfail | ||||||
|  |     is honoured (including the collection error count). | ||||||
|  |     4 tests: 2 collection errors + 1 failure + 1 success | ||||||
|  |     test_4 is never executed because the test run is with --maxfail=3 which | ||||||
|  |     means it is interrupted after the 2 collection errors + 1 failure. | ||||||
|  |     """ | ||||||
|  |     testdir.makepyfile(**COLLECTION_ERROR_PY_FILES) | ||||||
|  | 
 | ||||||
|  |     res = testdir.runpytest("--continue-on-collection-errors", "--maxfail=3") | ||||||
|  |     assert res.ret == 2 | ||||||
|  | 
 | ||||||
|  |     res.stdout.fnmatch_lines([ | ||||||
|  |         "collected 2 items / 2 errors", | ||||||
|  |         "*Interrupted: stopping after 3 failures*", | ||||||
|  |         "*1 failed, 2 error*", | ||||||
|  |     ]) | ||||||
|  |  | ||||||
|  | @ -199,8 +199,20 @@ class TestDoctests: | ||||||
|             "*1 failed*", |             "*1 failed*", | ||||||
|         ]) |         ]) | ||||||
| 
 | 
 | ||||||
|  |     def test_doctest_unex_importerror_only_txt(self, testdir): | ||||||
|  |         testdir.maketxtfile(""" | ||||||
|  |             >>> import asdalsdkjaslkdjasd | ||||||
|  |             >>> | ||||||
|  |         """) | ||||||
|  |         result = testdir.runpytest() | ||||||
|  |         # doctest is never executed because of error during hello.py collection | ||||||
|  |         result.stdout.fnmatch_lines([ | ||||||
|  |             "*>>> import asdals*", | ||||||
|  |             "*UNEXPECTED*ImportError*", | ||||||
|  |             "ImportError: No module named *asdal*", | ||||||
|  |         ]) | ||||||
| 
 | 
 | ||||||
|     def test_doctest_unex_importerror(self, testdir): |     def test_doctest_unex_importerror_with_module(self, testdir): | ||||||
|         testdir.tmpdir.join("hello.py").write(_pytest._code.Source(""" |         testdir.tmpdir.join("hello.py").write(_pytest._code.Source(""" | ||||||
|             import asdalsdkjaslkdjasd |             import asdalsdkjaslkdjasd | ||||||
|         """)) |         """)) | ||||||
|  | @ -209,10 +221,11 @@ class TestDoctests: | ||||||
|             >>> |             >>> | ||||||
|         """) |         """) | ||||||
|         result = testdir.runpytest("--doctest-modules") |         result = testdir.runpytest("--doctest-modules") | ||||||
|  |         # doctest is never executed because of error during hello.py collection | ||||||
|         result.stdout.fnmatch_lines([ |         result.stdout.fnmatch_lines([ | ||||||
|             "*>>> import hello", |             "*ERROR collecting hello.py*", | ||||||
|             "*UNEXPECTED*ImportError*", |             "*ImportError: No module named *asdals*", | ||||||
|             "*import asdals*", |             "*Interrupted: 1 errors during collection*", | ||||||
|         ]) |         ]) | ||||||
| 
 | 
 | ||||||
|     def test_doctestmodule(self, testdir): |     def test_doctestmodule(self, testdir): | ||||||
|  |  | ||||||
|  | @ -231,6 +231,6 @@ def test_failure_issue380(testdir): | ||||||
|             pass |             pass | ||||||
|     """) |     """) | ||||||
|     result = testdir.runpytest("--resultlog=log") |     result = testdir.runpytest("--resultlog=log") | ||||||
|     assert result.ret == 1 |     assert result.ret == 2 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -273,7 +273,7 @@ class TestCollectonly: | ||||||
|     def test_collectonly_error(self, testdir): |     def test_collectonly_error(self, testdir): | ||||||
|         p = testdir.makepyfile("import Errlkjqweqwe") |         p = testdir.makepyfile("import Errlkjqweqwe") | ||||||
|         result = testdir.runpytest("--collect-only", p) |         result = testdir.runpytest("--collect-only", p) | ||||||
|         assert result.ret == 1 |         assert result.ret == 2 | ||||||
|         result.stdout.fnmatch_lines(_pytest._code.Source(""" |         result.stdout.fnmatch_lines(_pytest._code.Source(""" | ||||||
|             *ERROR* |             *ERROR* | ||||||
|             *ImportError* |             *ImportError* | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue