1import os 2import textwrap 3 4import py 5 6import pytest 7from _pytest.config import ExitCode 8from _pytest.config import PytestPluginManager 9from _pytest.pathlib import Path 10from _pytest.pathlib import symlink_or_skip 11 12 13def ConftestWithSetinitial(path): 14 conftest = PytestPluginManager() 15 conftest_setinitial(conftest, [path]) 16 return conftest 17 18 19def conftest_setinitial(conftest, args, confcutdir=None): 20 class Namespace: 21 def __init__(self): 22 self.file_or_dir = args 23 self.confcutdir = str(confcutdir) 24 self.noconftest = False 25 self.pyargs = False 26 self.importmode = "prepend" 27 28 conftest._set_initial_conftests(Namespace()) 29 30 31@pytest.mark.usefixtures("_sys_snapshot") 32class TestConftestValueAccessGlobal: 33 @pytest.fixture(scope="module", params=["global", "inpackage"]) 34 def basedir(self, request, tmpdir_factory): 35 tmpdir = tmpdir_factory.mktemp("basedir", numbered=True) 36 tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3") 37 tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5") 38 if request.param == "inpackage": 39 tmpdir.ensure("adir/__init__.py") 40 tmpdir.ensure("adir/b/__init__.py") 41 42 yield tmpdir 43 44 def test_basic_init(self, basedir): 45 conftest = PytestPluginManager() 46 p = basedir.join("adir") 47 assert conftest._rget_with_confmod("a", p, importmode="prepend")[1] == 1 48 49 def test_immediate_initialiation_and_incremental_are_the_same(self, basedir): 50 conftest = PytestPluginManager() 51 assert not len(conftest._dirpath2confmods) 52 conftest._getconftestmodules(basedir, importmode="prepend") 53 snap1 = len(conftest._dirpath2confmods) 54 assert snap1 == 1 55 conftest._getconftestmodules(basedir.join("adir"), importmode="prepend") 56 assert len(conftest._dirpath2confmods) == snap1 + 1 57 conftest._getconftestmodules(basedir.join("b"), importmode="prepend") 58 assert len(conftest._dirpath2confmods) == snap1 + 2 59 60 def test_value_access_not_existing(self, basedir): 61 conftest = ConftestWithSetinitial(basedir) 62 with pytest.raises(KeyError): 63 conftest._rget_with_confmod("a", basedir, importmode="prepend") 64 65 def test_value_access_by_path(self, basedir): 66 conftest = ConftestWithSetinitial(basedir) 67 adir = basedir.join("adir") 68 assert conftest._rget_with_confmod("a", adir, importmode="prepend")[1] == 1 69 assert ( 70 conftest._rget_with_confmod("a", adir.join("b"), importmode="prepend")[1] 71 == 1.5 72 ) 73 74 def test_value_access_with_confmod(self, basedir): 75 startdir = basedir.join("adir", "b") 76 startdir.ensure("xx", dir=True) 77 conftest = ConftestWithSetinitial(startdir) 78 mod, value = conftest._rget_with_confmod("a", startdir, importmode="prepend") 79 assert value == 1.5 80 path = py.path.local(mod.__file__) 81 assert path.dirpath() == basedir.join("adir", "b") 82 assert path.purebasename.startswith("conftest") 83 84 85def test_conftest_in_nonpkg_with_init(tmpdir, _sys_snapshot): 86 tmpdir.ensure("adir-1.0/conftest.py").write("a=1 ; Directory = 3") 87 tmpdir.ensure("adir-1.0/b/conftest.py").write("b=2 ; a = 1.5") 88 tmpdir.ensure("adir-1.0/b/__init__.py") 89 tmpdir.ensure("adir-1.0/__init__.py") 90 ConftestWithSetinitial(tmpdir.join("adir-1.0", "b")) 91 92 93def test_doubledash_considered(testdir): 94 conf = testdir.mkdir("--option") 95 conf.ensure("conftest.py") 96 conftest = PytestPluginManager() 97 conftest_setinitial(conftest, [conf.basename, conf.basename]) 98 values = conftest._getconftestmodules(conf, importmode="prepend") 99 assert len(values) == 1 100 101 102def test_issue151_load_all_conftests(testdir): 103 names = "code proj src".split() 104 for name in names: 105 p = testdir.mkdir(name) 106 p.ensure("conftest.py") 107 108 conftest = PytestPluginManager() 109 conftest_setinitial(conftest, names) 110 d = list(conftest._conftestpath2mod.values()) 111 assert len(d) == len(names) 112 113 114def test_conftest_global_import(testdir): 115 testdir.makeconftest("x=3") 116 p = testdir.makepyfile( 117 """ 118 import py, pytest 119 from _pytest.config import PytestPluginManager 120 conf = PytestPluginManager() 121 mod = conf._importconftest(py.path.local("conftest.py"), importmode="prepend") 122 assert mod.x == 3 123 import conftest 124 assert conftest is mod, (conftest, mod) 125 subconf = py.path.local().ensure("sub", "conftest.py") 126 subconf.write("y=4") 127 mod2 = conf._importconftest(subconf, importmode="prepend") 128 assert mod != mod2 129 assert mod2.y == 4 130 import conftest 131 assert conftest is mod2, (conftest, mod) 132 """ 133 ) 134 res = testdir.runpython(p) 135 assert res.ret == 0 136 137 138def test_conftestcutdir(testdir): 139 conf = testdir.makeconftest("") 140 p = testdir.mkdir("x") 141 conftest = PytestPluginManager() 142 conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p) 143 values = conftest._getconftestmodules(p, importmode="prepend") 144 assert len(values) == 0 145 values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend") 146 assert len(values) == 0 147 assert conf not in conftest._conftestpath2mod 148 # but we can still import a conftest directly 149 conftest._importconftest(conf, importmode="prepend") 150 values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend") 151 assert values[0].__file__.startswith(str(conf)) 152 # and all sub paths get updated properly 153 values = conftest._getconftestmodules(p, importmode="prepend") 154 assert len(values) == 1 155 assert values[0].__file__.startswith(str(conf)) 156 157 158def test_conftestcutdir_inplace_considered(testdir): 159 conf = testdir.makeconftest("") 160 conftest = PytestPluginManager() 161 conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath()) 162 values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend") 163 assert len(values) == 1 164 assert values[0].__file__.startswith(str(conf)) 165 166 167@pytest.mark.parametrize("name", "test tests whatever .dotdir".split()) 168def test_setinitial_conftest_subdirs(testdir, name): 169 sub = testdir.mkdir(name) 170 subconftest = sub.ensure("conftest.py") 171 conftest = PytestPluginManager() 172 conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir) 173 key = Path(str(subconftest)).resolve() 174 if name not in ("whatever", ".dotdir"): 175 assert key in conftest._conftestpath2mod 176 assert len(conftest._conftestpath2mod) == 1 177 else: 178 assert key not in conftest._conftestpath2mod 179 assert len(conftest._conftestpath2mod) == 0 180 181 182def test_conftest_confcutdir(testdir): 183 testdir.makeconftest("assert 0") 184 x = testdir.mkdir("x") 185 x.join("conftest.py").write( 186 textwrap.dedent( 187 """\ 188 def pytest_addoption(parser): 189 parser.addoption("--xyz", action="store_true") 190 """ 191 ) 192 ) 193 result = testdir.runpytest("-h", "--confcutdir=%s" % x, x) 194 result.stdout.fnmatch_lines(["*--xyz*"]) 195 result.stdout.no_fnmatch_line("*warning: could not load initial*") 196 197 198def test_conftest_symlink(testdir): 199 """`conftest.py` discovery follows normal path resolution and does not resolve symlinks.""" 200 # Structure: 201 # /real 202 # /real/conftest.py 203 # /real/app 204 # /real/app/tests 205 # /real/app/tests/test_foo.py 206 207 # Links: 208 # /symlinktests -> /real/app/tests (running at symlinktests should fail) 209 # /symlink -> /real (running at /symlink should work) 210 211 real = testdir.tmpdir.mkdir("real") 212 realtests = real.mkdir("app").mkdir("tests") 213 symlink_or_skip(realtests, testdir.tmpdir.join("symlinktests")) 214 symlink_or_skip(real, testdir.tmpdir.join("symlink")) 215 testdir.makepyfile( 216 **{ 217 "real/app/tests/test_foo.py": "def test1(fixture): pass", 218 "real/conftest.py": textwrap.dedent( 219 """ 220 import pytest 221 222 print("conftest_loaded") 223 224 @pytest.fixture 225 def fixture(): 226 print("fixture_used") 227 """ 228 ), 229 } 230 ) 231 232 # Should fail because conftest cannot be found from the link structure. 233 result = testdir.runpytest("-vs", "symlinktests") 234 result.stdout.fnmatch_lines(["*fixture 'fixture' not found*"]) 235 assert result.ret == ExitCode.TESTS_FAILED 236 237 # Should not cause "ValueError: Plugin already registered" (#4174). 238 result = testdir.runpytest("-vs", "symlink") 239 assert result.ret == ExitCode.OK 240 241 242def test_conftest_symlink_files(testdir): 243 """Symlinked conftest.py are found when pytest is executed in a directory with symlinked 244 files.""" 245 real = testdir.tmpdir.mkdir("real") 246 source = { 247 "app/test_foo.py": "def test1(fixture): pass", 248 "app/__init__.py": "", 249 "app/conftest.py": textwrap.dedent( 250 """ 251 import pytest 252 253 print("conftest_loaded") 254 255 @pytest.fixture 256 def fixture(): 257 print("fixture_used") 258 """ 259 ), 260 } 261 testdir.makepyfile(**{"real/%s" % k: v for k, v in source.items()}) 262 263 # Create a build directory that contains symlinks to actual files 264 # but doesn't symlink actual directories. 265 build = testdir.tmpdir.mkdir("build") 266 build.mkdir("app") 267 for f in source: 268 symlink_or_skip(real.join(f), build.join(f)) 269 build.chdir() 270 result = testdir.runpytest("-vs", "app/test_foo.py") 271 result.stdout.fnmatch_lines(["*conftest_loaded*", "PASSED"]) 272 assert result.ret == ExitCode.OK 273 274 275@pytest.mark.skipif( 276 os.path.normcase("x") != os.path.normcase("X"), 277 reason="only relevant for case insensitive file systems", 278) 279def test_conftest_badcase(testdir): 280 """Check conftest.py loading when directory casing is wrong (#5792).""" 281 testdir.tmpdir.mkdir("JenkinsRoot").mkdir("test") 282 source = {"setup.py": "", "test/__init__.py": "", "test/conftest.py": ""} 283 testdir.makepyfile(**{"JenkinsRoot/%s" % k: v for k, v in source.items()}) 284 285 testdir.tmpdir.join("jenkinsroot/test").chdir() 286 result = testdir.runpytest() 287 assert result.ret == ExitCode.NO_TESTS_COLLECTED 288 289 290def test_conftest_uppercase(testdir): 291 """Check conftest.py whose qualified name contains uppercase characters (#5819)""" 292 source = {"__init__.py": "", "Foo/conftest.py": "", "Foo/__init__.py": ""} 293 testdir.makepyfile(**source) 294 295 testdir.tmpdir.chdir() 296 result = testdir.runpytest() 297 assert result.ret == ExitCode.NO_TESTS_COLLECTED 298 299 300def test_no_conftest(testdir): 301 testdir.makeconftest("assert 0") 302 result = testdir.runpytest("--noconftest") 303 assert result.ret == ExitCode.NO_TESTS_COLLECTED 304 305 result = testdir.runpytest() 306 assert result.ret == ExitCode.USAGE_ERROR 307 308 309def test_conftest_existing_junitxml(testdir): 310 x = testdir.mkdir("tests") 311 x.join("conftest.py").write( 312 textwrap.dedent( 313 """\ 314 def pytest_addoption(parser): 315 parser.addoption("--xyz", action="store_true") 316 """ 317 ) 318 ) 319 testdir.makefile(ext=".xml", junit="") # Writes junit.xml 320 result = testdir.runpytest("-h", "--junitxml", "junit.xml") 321 result.stdout.fnmatch_lines(["*--xyz*"]) 322 323 324def test_conftest_import_order(testdir, monkeypatch): 325 ct1 = testdir.makeconftest("") 326 sub = testdir.mkdir("sub") 327 ct2 = sub.join("conftest.py") 328 ct2.write("") 329 330 def impct(p, importmode): 331 return p 332 333 conftest = PytestPluginManager() 334 conftest._confcutdir = testdir.tmpdir 335 monkeypatch.setattr(conftest, "_importconftest", impct) 336 assert conftest._getconftestmodules(sub, importmode="prepend") == [ct1, ct2] 337 338 339def test_fixture_dependency(testdir): 340 ct1 = testdir.makeconftest("") 341 ct1 = testdir.makepyfile("__init__.py") 342 ct1.write("") 343 sub = testdir.mkdir("sub") 344 sub.join("__init__.py").write("") 345 sub.join("conftest.py").write( 346 textwrap.dedent( 347 """\ 348 import pytest 349 350 @pytest.fixture 351 def not_needed(): 352 assert False, "Should not be called!" 353 354 @pytest.fixture 355 def foo(): 356 assert False, "Should not be called!" 357 358 @pytest.fixture 359 def bar(foo): 360 return 'bar' 361 """ 362 ) 363 ) 364 subsub = sub.mkdir("subsub") 365 subsub.join("__init__.py").write("") 366 subsub.join("test_bar.py").write( 367 textwrap.dedent( 368 """\ 369 import pytest 370 371 @pytest.fixture 372 def bar(): 373 return 'sub bar' 374 375 def test_event_fixture(bar): 376 assert bar == 'sub bar' 377 """ 378 ) 379 ) 380 result = testdir.runpytest("sub") 381 result.stdout.fnmatch_lines(["*1 passed*"]) 382 383 384def test_conftest_found_with_double_dash(testdir): 385 sub = testdir.mkdir("sub") 386 sub.join("conftest.py").write( 387 textwrap.dedent( 388 """\ 389 def pytest_addoption(parser): 390 parser.addoption("--hello-world", action="store_true") 391 """ 392 ) 393 ) 394 p = sub.join("test_hello.py") 395 p.write("def test_hello(): pass") 396 result = testdir.runpytest(str(p) + "::test_hello", "-h") 397 result.stdout.fnmatch_lines( 398 """ 399 *--hello-world* 400 """ 401 ) 402 403 404class TestConftestVisibility: 405 def _setup_tree(self, testdir): # for issue616 406 # example mostly taken from: 407 # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html 408 runner = testdir.mkdir("empty") 409 package = testdir.mkdir("package") 410 411 package.join("conftest.py").write( 412 textwrap.dedent( 413 """\ 414 import pytest 415 @pytest.fixture 416 def fxtr(): 417 return "from-package" 418 """ 419 ) 420 ) 421 package.join("test_pkgroot.py").write( 422 textwrap.dedent( 423 """\ 424 def test_pkgroot(fxtr): 425 assert fxtr == "from-package" 426 """ 427 ) 428 ) 429 430 swc = package.mkdir("swc") 431 swc.join("__init__.py").ensure() 432 swc.join("conftest.py").write( 433 textwrap.dedent( 434 """\ 435 import pytest 436 @pytest.fixture 437 def fxtr(): 438 return "from-swc" 439 """ 440 ) 441 ) 442 swc.join("test_with_conftest.py").write( 443 textwrap.dedent( 444 """\ 445 def test_with_conftest(fxtr): 446 assert fxtr == "from-swc" 447 """ 448 ) 449 ) 450 451 snc = package.mkdir("snc") 452 snc.join("__init__.py").ensure() 453 snc.join("test_no_conftest.py").write( 454 textwrap.dedent( 455 """\ 456 def test_no_conftest(fxtr): 457 assert fxtr == "from-package" # No local conftest.py, so should 458 # use value from parent dir's 459 """ 460 ) 461 ) 462 print("created directory structure:") 463 tmppath = Path(str(testdir.tmpdir)) 464 for x in tmppath.rglob(""): 465 print(" " + str(x.relative_to(tmppath))) 466 467 return {"runner": runner, "package": package, "swc": swc, "snc": snc} 468 469 # N.B.: "swc" stands for "subdir with conftest.py" 470 # "snc" stands for "subdir no [i.e. without] conftest.py" 471 @pytest.mark.parametrize( 472 "chdir,testarg,expect_ntests_passed", 473 [ 474 # Effective target: package/.. 475 ("runner", "..", 3), 476 ("package", "..", 3), 477 ("swc", "../..", 3), 478 ("snc", "../..", 3), 479 # Effective target: package 480 ("runner", "../package", 3), 481 ("package", ".", 3), 482 ("swc", "..", 3), 483 ("snc", "..", 3), 484 # Effective target: package/swc 485 ("runner", "../package/swc", 1), 486 ("package", "./swc", 1), 487 ("swc", ".", 1), 488 ("snc", "../swc", 1), 489 # Effective target: package/snc 490 ("runner", "../package/snc", 1), 491 ("package", "./snc", 1), 492 ("swc", "../snc", 1), 493 ("snc", ".", 1), 494 ], 495 ) 496 def test_parsefactories_relative_node_ids( 497 self, testdir, chdir, testarg, expect_ntests_passed 498 ): 499 """#616""" 500 dirs = self._setup_tree(testdir) 501 print("pytest run in cwd: %s" % (dirs[chdir].relto(testdir.tmpdir))) 502 print("pytestarg : %s" % (testarg)) 503 print("expected pass : %s" % (expect_ntests_passed)) 504 with dirs[chdir].as_cwd(): 505 reprec = testdir.inline_run(testarg, "-q", "--traceconfig") 506 reprec.assertoutcome(passed=expect_ntests_passed) 507 508 509@pytest.mark.parametrize( 510 "confcutdir,passed,error", [(".", 2, 0), ("src", 1, 1), (None, 1, 1)] 511) 512def test_search_conftest_up_to_inifile(testdir, confcutdir, passed, error): 513 """Test that conftest files are detected only up to an ini file, unless 514 an explicit --confcutdir option is given. 515 """ 516 root = testdir.tmpdir 517 src = root.join("src").ensure(dir=1) 518 src.join("pytest.ini").write("[pytest]") 519 src.join("conftest.py").write( 520 textwrap.dedent( 521 """\ 522 import pytest 523 @pytest.fixture 524 def fix1(): pass 525 """ 526 ) 527 ) 528 src.join("test_foo.py").write( 529 textwrap.dedent( 530 """\ 531 def test_1(fix1): 532 pass 533 def test_2(out_of_reach): 534 pass 535 """ 536 ) 537 ) 538 root.join("conftest.py").write( 539 textwrap.dedent( 540 """\ 541 import pytest 542 @pytest.fixture 543 def out_of_reach(): pass 544 """ 545 ) 546 ) 547 548 args = [str(src)] 549 if confcutdir: 550 args = ["--confcutdir=%s" % root.join(confcutdir)] 551 result = testdir.runpytest(*args) 552 match = "" 553 if passed: 554 match += "*%d passed*" % passed 555 if error: 556 match += "*%d error*" % error 557 result.stdout.fnmatch_lines(match) 558 559 560def test_issue1073_conftest_special_objects(testdir): 561 testdir.makeconftest( 562 """\ 563 class DontTouchMe(object): 564 def __getattr__(self, x): 565 raise Exception('cant touch me') 566 567 x = DontTouchMe() 568 """ 569 ) 570 testdir.makepyfile( 571 """\ 572 def test_some(): 573 pass 574 """ 575 ) 576 res = testdir.runpytest() 577 assert res.ret == 0 578 579 580def test_conftest_exception_handling(testdir): 581 testdir.makeconftest( 582 """\ 583 raise ValueError() 584 """ 585 ) 586 testdir.makepyfile( 587 """\ 588 def test_some(): 589 pass 590 """ 591 ) 592 res = testdir.runpytest() 593 assert res.ret == 4 594 assert "raise ValueError()" in [line.strip() for line in res.errlines] 595 596 597def test_hook_proxy(testdir): 598 """Session's gethookproxy() would cache conftests incorrectly (#2016). 599 It was decided to remove the cache altogether. 600 """ 601 testdir.makepyfile( 602 **{ 603 "root/demo-0/test_foo1.py": "def test1(): pass", 604 "root/demo-a/test_foo2.py": "def test1(): pass", 605 "root/demo-a/conftest.py": """\ 606 def pytest_ignore_collect(path, config): 607 return True 608 """, 609 "root/demo-b/test_foo3.py": "def test1(): pass", 610 "root/demo-c/test_foo4.py": "def test1(): pass", 611 } 612 ) 613 result = testdir.runpytest() 614 result.stdout.fnmatch_lines( 615 ["*test_foo1.py*", "*test_foo3.py*", "*test_foo4.py*", "*3 passed*"] 616 ) 617 618 619def test_required_option_help(testdir): 620 testdir.makeconftest("assert 0") 621 x = testdir.mkdir("x") 622 x.join("conftest.py").write( 623 textwrap.dedent( 624 """\ 625 def pytest_addoption(parser): 626 parser.addoption("--xyz", action="store_true", required=True) 627 """ 628 ) 629 ) 630 result = testdir.runpytest("-h", x) 631 result.stdout.no_fnmatch_line("*argument --xyz is required*") 632 assert "general:" in result.stdout.str() 633