1# -*- coding: utf-8 -*- 2from __future__ import absolute_import 3from __future__ import division 4from __future__ import print_function 5 6import textwrap 7 8import py 9 10import pytest 11from _pytest.config import PytestPluginManager 12from _pytest.main import EXIT_NOTESTSCOLLECTED 13from _pytest.main import EXIT_OK 14from _pytest.main import EXIT_USAGEERROR 15 16 17def ConftestWithSetinitial(path): 18 conftest = PytestPluginManager() 19 conftest_setinitial(conftest, [path]) 20 return conftest 21 22 23def conftest_setinitial(conftest, args, confcutdir=None): 24 class Namespace(object): 25 def __init__(self): 26 self.file_or_dir = args 27 self.confcutdir = str(confcutdir) 28 self.noconftest = False 29 self.pyargs = False 30 31 conftest._set_initial_conftests(Namespace()) 32 33 34@pytest.mark.usefixtures("_sys_snapshot") 35class TestConftestValueAccessGlobal(object): 36 @pytest.fixture(scope="module", params=["global", "inpackage"]) 37 def basedir(self, request, tmpdir_factory): 38 tmpdir = tmpdir_factory.mktemp("basedir", numbered=True) 39 tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3") 40 tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5") 41 if request.param == "inpackage": 42 tmpdir.ensure("adir/__init__.py") 43 tmpdir.ensure("adir/b/__init__.py") 44 45 yield tmpdir 46 47 def test_basic_init(self, basedir): 48 conftest = PytestPluginManager() 49 p = basedir.join("adir") 50 assert conftest._rget_with_confmod("a", p)[1] == 1 51 52 def test_immediate_initialiation_and_incremental_are_the_same(self, basedir): 53 conftest = PytestPluginManager() 54 assert not len(conftest._dirpath2confmods) 55 conftest._getconftestmodules(basedir) 56 snap1 = len(conftest._dirpath2confmods) 57 assert snap1 == 1 58 conftest._getconftestmodules(basedir.join("adir")) 59 assert len(conftest._dirpath2confmods) == snap1 + 1 60 conftest._getconftestmodules(basedir.join("b")) 61 assert len(conftest._dirpath2confmods) == snap1 + 2 62 63 def test_value_access_not_existing(self, basedir): 64 conftest = ConftestWithSetinitial(basedir) 65 with pytest.raises(KeyError): 66 conftest._rget_with_confmod("a", basedir) 67 68 def test_value_access_by_path(self, basedir): 69 conftest = ConftestWithSetinitial(basedir) 70 adir = basedir.join("adir") 71 assert conftest._rget_with_confmod("a", adir)[1] == 1 72 assert conftest._rget_with_confmod("a", adir.join("b"))[1] == 1.5 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) 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) 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")) 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) 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) 144 assert len(values) == 0 145 values = conftest._getconftestmodules(conf.dirpath()) 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) 150 values = conftest._getconftestmodules(conf.dirpath()) 151 assert values[0].__file__.startswith(str(conf)) 152 # and all sub paths get updated properly 153 values = conftest._getconftestmodules(p) 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()) 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 if name not in ("whatever", ".dotdir"): 174 assert subconftest in conftest._conftestpath2mod 175 assert len(conftest._conftestpath2mod) == 1 176 else: 177 assert subconftest not in conftest._conftestpath2mod 178 assert len(conftest._conftestpath2mod) == 0 179 180 181def test_conftest_confcutdir(testdir): 182 testdir.makeconftest("assert 0") 183 x = testdir.mkdir("x") 184 x.join("conftest.py").write( 185 textwrap.dedent( 186 """\ 187 def pytest_addoption(parser): 188 parser.addoption("--xyz", action="store_true") 189 """ 190 ) 191 ) 192 result = testdir.runpytest("-h", "--confcutdir=%s" % x, x) 193 result.stdout.fnmatch_lines(["*--xyz*"]) 194 assert "warning: could not load initial" not in result.stdout.str() 195 196 197@pytest.mark.skipif( 198 not hasattr(py.path.local, "mksymlinkto"), 199 reason="symlink not available on this platform", 200) 201def test_conftest_symlink(testdir): 202 """Ensure that conftest.py is used for resolved symlinks.""" 203 real = testdir.tmpdir.mkdir("real") 204 realtests = real.mkdir("app").mkdir("tests") 205 testdir.tmpdir.join("symlinktests").mksymlinkto(realtests) 206 testdir.tmpdir.join("symlink").mksymlinkto(real) 207 testdir.makepyfile( 208 **{ 209 "real/app/tests/test_foo.py": "def test1(fixture): pass", 210 "real/conftest.py": textwrap.dedent( 211 """ 212 import pytest 213 214 print("conftest_loaded") 215 216 @pytest.fixture 217 def fixture(): 218 print("fixture_used") 219 """ 220 ), 221 } 222 ) 223 result = testdir.runpytest("-vs", "symlinktests") 224 result.stdout.fnmatch_lines( 225 [ 226 "*conftest_loaded*", 227 "real/app/tests/test_foo.py::test1 fixture_used", 228 "PASSED", 229 ] 230 ) 231 assert result.ret == EXIT_OK 232 233 # Should not cause "ValueError: Plugin already registered" (#4174). 234 result = testdir.runpytest("-vs", "symlink") 235 assert result.ret == EXIT_OK 236 237 realtests.ensure("__init__.py") 238 result = testdir.runpytest("-vs", "symlinktests/test_foo.py::test1") 239 result.stdout.fnmatch_lines( 240 [ 241 "*conftest_loaded*", 242 "real/app/tests/test_foo.py::test1 fixture_used", 243 "PASSED", 244 ] 245 ) 246 assert result.ret == EXIT_OK 247 248 249@pytest.mark.skipif( 250 not hasattr(py.path.local, "mksymlinkto"), 251 reason="symlink not available on this platform", 252) 253def test_conftest_symlink_files(testdir): 254 """Check conftest.py loading when running in directory with symlinks.""" 255 real = testdir.tmpdir.mkdir("real") 256 source = { 257 "app/test_foo.py": "def test1(fixture): pass", 258 "app/__init__.py": "", 259 "app/conftest.py": textwrap.dedent( 260 """ 261 import pytest 262 263 print("conftest_loaded") 264 265 @pytest.fixture 266 def fixture(): 267 print("fixture_used") 268 """ 269 ), 270 } 271 testdir.makepyfile(**{"real/%s" % k: v for k, v in source.items()}) 272 273 # Create a build directory that contains symlinks to actual files 274 # but doesn't symlink actual directories. 275 build = testdir.tmpdir.mkdir("build") 276 build.mkdir("app") 277 for f in source: 278 build.join(f).mksymlinkto(real.join(f)) 279 build.chdir() 280 result = testdir.runpytest("-vs", "app/test_foo.py") 281 result.stdout.fnmatch_lines(["*conftest_loaded*", "PASSED"]) 282 assert result.ret == EXIT_OK 283 284 285def test_no_conftest(testdir): 286 testdir.makeconftest("assert 0") 287 result = testdir.runpytest("--noconftest") 288 assert result.ret == EXIT_NOTESTSCOLLECTED 289 290 result = testdir.runpytest() 291 assert result.ret == EXIT_USAGEERROR 292 293 294def test_conftest_existing_resultlog(testdir): 295 x = testdir.mkdir("tests") 296 x.join("conftest.py").write( 297 textwrap.dedent( 298 """\ 299 def pytest_addoption(parser): 300 parser.addoption("--xyz", action="store_true") 301 """ 302 ) 303 ) 304 testdir.makefile(ext=".log", result="") # Writes result.log 305 result = testdir.runpytest("-h", "--resultlog", "result.log") 306 result.stdout.fnmatch_lines(["*--xyz*"]) 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): 331 return p 332 333 conftest = PytestPluginManager() 334 conftest._confcutdir = testdir.tmpdir 335 monkeypatch.setattr(conftest, "_importconftest", impct) 336 assert conftest._getconftestmodules(sub) == [ct1, ct2] 337 338 339def test_fixture_dependency(testdir, monkeypatch): 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(object): 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 for x in testdir.tmpdir.visit(): 464 print(" " + x.relto(testdir.tmpdir)) 465 466 return {"runner": runner, "package": package, "swc": swc, "snc": snc} 467 468 # N.B.: "swc" stands for "subdir with conftest.py" 469 # "snc" stands for "subdir no [i.e. without] conftest.py" 470 @pytest.mark.parametrize( 471 "chdir,testarg,expect_ntests_passed", 472 [ 473 # Effective target: package/.. 474 ("runner", "..", 3), 475 ("package", "..", 3), 476 ("swc", "../..", 3), 477 ("snc", "../..", 3), 478 # Effective target: package 479 ("runner", "../package", 3), 480 ("package", ".", 3), 481 ("swc", "..", 3), 482 ("snc", "..", 3), 483 # Effective target: package/swc 484 ("runner", "../package/swc", 1), 485 ("package", "./swc", 1), 486 ("swc", ".", 1), 487 ("snc", "../swc", 1), 488 # Effective target: package/snc 489 ("runner", "../package/snc", 1), 490 ("package", "./snc", 1), 491 ("swc", "../snc", 1), 492 ("snc", ".", 1), 493 ], 494 ) 495 def test_parsefactories_relative_node_ids( 496 self, testdir, chdir, testarg, expect_ntests_passed 497 ): 498 """#616""" 499 dirs = self._setup_tree(testdir) 500 print("pytest run in cwd: %s" % (dirs[chdir].relto(testdir.tmpdir))) 501 print("pytestarg : %s" % (testarg)) 502 print("expected pass : %s" % (expect_ntests_passed)) 503 with dirs[chdir].as_cwd(): 504 reprec = testdir.inline_run(testarg, "-q", "--traceconfig") 505 reprec.assertoutcome(passed=expect_ntests_passed) 506 507 508@pytest.mark.parametrize( 509 "confcutdir,passed,error", [(".", 2, 0), ("src", 1, 1), (None, 1, 1)] 510) 511def test_search_conftest_up_to_inifile(testdir, confcutdir, passed, error): 512 """Test that conftest files are detected only up to an ini file, unless 513 an explicit --confcutdir option is given. 514 """ 515 root = testdir.tmpdir 516 src = root.join("src").ensure(dir=1) 517 src.join("pytest.ini").write("[pytest]") 518 src.join("conftest.py").write( 519 textwrap.dedent( 520 """\ 521 import pytest 522 @pytest.fixture 523 def fix1(): pass 524 """ 525 ) 526 ) 527 src.join("test_foo.py").write( 528 textwrap.dedent( 529 """\ 530 def test_1(fix1): 531 pass 532 def test_2(out_of_reach): 533 pass 534 """ 535 ) 536 ) 537 root.join("conftest.py").write( 538 textwrap.dedent( 539 """\ 540 import pytest 541 @pytest.fixture 542 def out_of_reach(): pass 543 """ 544 ) 545 ) 546 547 args = [str(src)] 548 if confcutdir: 549 args = ["--confcutdir=%s" % root.join(confcutdir)] 550 result = testdir.runpytest(*args) 551 match = "" 552 if passed: 553 match += "*%d passed*" % passed 554 if error: 555 match += "*%d error*" % error 556 result.stdout.fnmatch_lines(match) 557 558 559def test_issue1073_conftest_special_objects(testdir): 560 testdir.makeconftest( 561 """\ 562 class DontTouchMe(object): 563 def __getattr__(self, x): 564 raise Exception('cant touch me') 565 566 x = DontTouchMe() 567 """ 568 ) 569 testdir.makepyfile( 570 """\ 571 def test_some(): 572 pass 573 """ 574 ) 575 res = testdir.runpytest() 576 assert res.ret == 0 577 578 579def test_conftest_exception_handling(testdir): 580 testdir.makeconftest( 581 """\ 582 raise ValueError() 583 """ 584 ) 585 testdir.makepyfile( 586 """\ 587 def test_some(): 588 pass 589 """ 590 ) 591 res = testdir.runpytest() 592 assert res.ret == 4 593 assert "raise ValueError()" in [line.strip() for line in res.errlines] 594 595 596def test_hook_proxy(testdir): 597 """Session's gethookproxy() would cache conftests incorrectly (#2016). 598 It was decided to remove the cache altogether. 599 """ 600 testdir.makepyfile( 601 **{ 602 "root/demo-0/test_foo1.py": "def test1(): pass", 603 "root/demo-a/test_foo2.py": "def test1(): pass", 604 "root/demo-a/conftest.py": """\ 605 def pytest_ignore_collect(path, config): 606 return True 607 """, 608 "root/demo-b/test_foo3.py": "def test1(): pass", 609 "root/demo-c/test_foo4.py": "def test1(): pass", 610 } 611 ) 612 result = testdir.runpytest() 613 result.stdout.fnmatch_lines( 614 ["*test_foo1.py*", "*test_foo3.py*", "*test_foo4.py*", "*3 passed*"] 615 ) 616 617 618def test_required_option_help(testdir): 619 testdir.makeconftest("assert 0") 620 x = testdir.mkdir("x") 621 x.join("conftest.py").write( 622 textwrap.dedent( 623 """\ 624 def pytest_addoption(parser): 625 parser.addoption("--xyz", action="store_true", required=True) 626 """ 627 ) 628 ) 629 result = testdir.runpytest("-h", x) 630 assert "argument --xyz is required" not in result.stdout.str() 631 assert "general:" in result.stdout.str() 632