1# -*- coding: utf-8 -*- 2from __future__ import absolute_import, division, print_function 3from xml.dom import minidom 4import py 5import sys 6import os 7from _pytest.junitxml import LogXML 8import pytest 9 10 11def runandparse(testdir, *args): 12 resultpath = testdir.tmpdir.join("junit.xml") 13 result = testdir.runpytest("--junitxml=%s" % resultpath, *args) 14 xmldoc = minidom.parse(str(resultpath)) 15 return result, DomNode(xmldoc) 16 17 18def assert_attr(node, **kwargs): 19 __tracebackhide__ = True 20 21 def nodeval(node, name): 22 anode = node.getAttributeNode(name) 23 if anode is not None: 24 return anode.value 25 26 expected = {name: str(value) for name, value in kwargs.items()} 27 on_node = {name: nodeval(node, name) for name in expected} 28 assert on_node == expected 29 30 31class DomNode(object): 32 33 def __init__(self, dom): 34 self.__node = dom 35 36 def __repr__(self): 37 return self.__node.toxml() 38 39 def find_first_by_tag(self, tag): 40 return self.find_nth_by_tag(tag, 0) 41 42 def _by_tag(self, tag): 43 return self.__node.getElementsByTagName(tag) 44 45 def find_nth_by_tag(self, tag, n): 46 items = self._by_tag(tag) 47 try: 48 nth = items[n] 49 except IndexError: 50 pass 51 else: 52 return type(self)(nth) 53 54 def find_by_tag(self, tag): 55 t = type(self) 56 return [t(x) for x in self.__node.getElementsByTagName(tag)] 57 58 def __getitem__(self, key): 59 node = self.__node.getAttributeNode(key) 60 if node is not None: 61 return node.value 62 63 def assert_attr(self, **kwargs): 64 __tracebackhide__ = True 65 return assert_attr(self.__node, **kwargs) 66 67 def toxml(self): 68 return self.__node.toxml() 69 70 @property 71 def text(self): 72 return self.__node.childNodes[0].wholeText 73 74 @property 75 def tag(self): 76 return self.__node.tagName 77 78 @property 79 def next_siebling(self): 80 return type(self)(self.__node.nextSibling) 81 82 83class TestPython(object): 84 85 def test_summing_simple(self, testdir): 86 testdir.makepyfile( 87 """ 88 import pytest 89 def test_pass(): 90 pass 91 def test_fail(): 92 assert 0 93 def test_skip(): 94 pytest.skip("") 95 @pytest.mark.xfail 96 def test_xfail(): 97 assert 0 98 @pytest.mark.xfail 99 def test_xpass(): 100 assert 1 101 """ 102 ) 103 result, dom = runandparse(testdir) 104 assert result.ret 105 node = dom.find_first_by_tag("testsuite") 106 node.assert_attr(name="pytest", errors=0, failures=1, skips=2, tests=5) 107 108 def test_summing_simple_with_errors(self, testdir): 109 testdir.makepyfile( 110 """ 111 import pytest 112 @pytest.fixture 113 def fixture(): 114 raise Exception() 115 def test_pass(): 116 pass 117 def test_fail(): 118 assert 0 119 def test_error(fixture): 120 pass 121 @pytest.mark.xfail 122 def test_xfail(): 123 assert False 124 @pytest.mark.xfail(strict=True) 125 def test_xpass(): 126 assert True 127 """ 128 ) 129 result, dom = runandparse(testdir) 130 assert result.ret 131 node = dom.find_first_by_tag("testsuite") 132 node.assert_attr(name="pytest", errors=1, failures=2, skips=1, tests=5) 133 134 def test_timing_function(self, testdir): 135 testdir.makepyfile( 136 """ 137 import time, pytest 138 def setup_module(): 139 time.sleep(0.01) 140 def teardown_module(): 141 time.sleep(0.01) 142 def test_sleep(): 143 time.sleep(0.01) 144 """ 145 ) 146 result, dom = runandparse(testdir) 147 node = dom.find_first_by_tag("testsuite") 148 tnode = node.find_first_by_tag("testcase") 149 val = tnode["time"] 150 assert round(float(val), 2) >= 0.03 151 152 def test_setup_error(self, testdir): 153 testdir.makepyfile( 154 """ 155 import pytest 156 157 @pytest.fixture 158 def arg(request): 159 raise ValueError() 160 def test_function(arg): 161 pass 162 """ 163 ) 164 result, dom = runandparse(testdir) 165 assert result.ret 166 node = dom.find_first_by_tag("testsuite") 167 node.assert_attr(errors=1, tests=1) 168 tnode = node.find_first_by_tag("testcase") 169 tnode.assert_attr( 170 file="test_setup_error.py", 171 line="5", 172 classname="test_setup_error", 173 name="test_function", 174 ) 175 fnode = tnode.find_first_by_tag("error") 176 fnode.assert_attr(message="test setup failure") 177 assert "ValueError" in fnode.toxml() 178 179 def test_teardown_error(self, testdir): 180 testdir.makepyfile( 181 """ 182 import pytest 183 184 @pytest.fixture 185 def arg(): 186 yield 187 raise ValueError() 188 def test_function(arg): 189 pass 190 """ 191 ) 192 result, dom = runandparse(testdir) 193 assert result.ret 194 node = dom.find_first_by_tag("testsuite") 195 tnode = node.find_first_by_tag("testcase") 196 tnode.assert_attr( 197 file="test_teardown_error.py", 198 line="6", 199 classname="test_teardown_error", 200 name="test_function", 201 ) 202 fnode = tnode.find_first_by_tag("error") 203 fnode.assert_attr(message="test teardown failure") 204 assert "ValueError" in fnode.toxml() 205 206 def test_call_failure_teardown_error(self, testdir): 207 testdir.makepyfile( 208 """ 209 import pytest 210 211 @pytest.fixture 212 def arg(): 213 yield 214 raise Exception("Teardown Exception") 215 def test_function(arg): 216 raise Exception("Call Exception") 217 """ 218 ) 219 result, dom = runandparse(testdir) 220 assert result.ret 221 node = dom.find_first_by_tag("testsuite") 222 node.assert_attr(errors=1, failures=1, tests=1) 223 first, second = dom.find_by_tag("testcase") 224 if not first or not second or first == second: 225 assert 0 226 fnode = first.find_first_by_tag("failure") 227 fnode.assert_attr(message="Exception: Call Exception") 228 snode = second.find_first_by_tag("error") 229 snode.assert_attr(message="test teardown failure") 230 231 def test_skip_contains_name_reason(self, testdir): 232 testdir.makepyfile( 233 """ 234 import pytest 235 def test_skip(): 236 pytest.skip("hello23") 237 """ 238 ) 239 result, dom = runandparse(testdir) 240 assert result.ret == 0 241 node = dom.find_first_by_tag("testsuite") 242 node.assert_attr(skips=1) 243 tnode = node.find_first_by_tag("testcase") 244 tnode.assert_attr( 245 file="test_skip_contains_name_reason.py", 246 line="1", 247 classname="test_skip_contains_name_reason", 248 name="test_skip", 249 ) 250 snode = tnode.find_first_by_tag("skipped") 251 snode.assert_attr(type="pytest.skip", message="hello23") 252 253 def test_mark_skip_contains_name_reason(self, testdir): 254 testdir.makepyfile( 255 """ 256 import pytest 257 @pytest.mark.skip(reason="hello24") 258 def test_skip(): 259 assert True 260 """ 261 ) 262 result, dom = runandparse(testdir) 263 assert result.ret == 0 264 node = dom.find_first_by_tag("testsuite") 265 node.assert_attr(skips=1) 266 tnode = node.find_first_by_tag("testcase") 267 tnode.assert_attr( 268 file="test_mark_skip_contains_name_reason.py", 269 line="1", 270 classname="test_mark_skip_contains_name_reason", 271 name="test_skip", 272 ) 273 snode = tnode.find_first_by_tag("skipped") 274 snode.assert_attr(type="pytest.skip", message="hello24") 275 276 def test_mark_skipif_contains_name_reason(self, testdir): 277 testdir.makepyfile( 278 """ 279 import pytest 280 GLOBAL_CONDITION = True 281 @pytest.mark.skipif(GLOBAL_CONDITION, reason="hello25") 282 def test_skip(): 283 assert True 284 """ 285 ) 286 result, dom = runandparse(testdir) 287 assert result.ret == 0 288 node = dom.find_first_by_tag("testsuite") 289 node.assert_attr(skips=1) 290 tnode = node.find_first_by_tag("testcase") 291 tnode.assert_attr( 292 file="test_mark_skipif_contains_name_reason.py", 293 line="2", 294 classname="test_mark_skipif_contains_name_reason", 295 name="test_skip", 296 ) 297 snode = tnode.find_first_by_tag("skipped") 298 snode.assert_attr(type="pytest.skip", message="hello25") 299 300 def test_mark_skip_doesnt_capture_output(self, testdir): 301 testdir.makepyfile( 302 """ 303 import pytest 304 @pytest.mark.skip(reason="foo") 305 def test_skip(): 306 print("bar!") 307 """ 308 ) 309 result, dom = runandparse(testdir) 310 assert result.ret == 0 311 node_xml = dom.find_first_by_tag("testsuite").toxml() 312 assert "bar!" not in node_xml 313 314 def test_classname_instance(self, testdir): 315 testdir.makepyfile( 316 """ 317 class TestClass(object): 318 def test_method(self): 319 assert 0 320 """ 321 ) 322 result, dom = runandparse(testdir) 323 assert result.ret 324 node = dom.find_first_by_tag("testsuite") 325 node.assert_attr(failures=1) 326 tnode = node.find_first_by_tag("testcase") 327 tnode.assert_attr( 328 file="test_classname_instance.py", 329 line="1", 330 classname="test_classname_instance.TestClass", 331 name="test_method", 332 ) 333 334 def test_classname_nested_dir(self, testdir): 335 p = testdir.tmpdir.ensure("sub", "test_hello.py") 336 p.write("def test_func(): 0/0") 337 result, dom = runandparse(testdir) 338 assert result.ret 339 node = dom.find_first_by_tag("testsuite") 340 node.assert_attr(failures=1) 341 tnode = node.find_first_by_tag("testcase") 342 tnode.assert_attr( 343 file=os.path.join("sub", "test_hello.py"), 344 line="0", 345 classname="sub.test_hello", 346 name="test_func", 347 ) 348 349 def test_internal_error(self, testdir): 350 testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0") 351 testdir.makepyfile("def test_function(): pass") 352 result, dom = runandparse(testdir) 353 assert result.ret 354 node = dom.find_first_by_tag("testsuite") 355 node.assert_attr(errors=1, tests=1) 356 tnode = node.find_first_by_tag("testcase") 357 tnode.assert_attr(classname="pytest", name="internal") 358 fnode = tnode.find_first_by_tag("error") 359 fnode.assert_attr(message="internal error") 360 assert "Division" in fnode.toxml() 361 362 @pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"]) 363 def test_failure_function(self, testdir, junit_logging): 364 testdir.makepyfile( 365 """ 366 import logging 367 import sys 368 369 def test_fail(): 370 print ("hello-stdout") 371 sys.stderr.write("hello-stderr\\n") 372 logging.info('info msg') 373 logging.warning('warning msg') 374 raise ValueError(42) 375 """ 376 ) 377 378 result, dom = runandparse(testdir, "-o", "junit_logging=%s" % junit_logging) 379 assert result.ret 380 node = dom.find_first_by_tag("testsuite") 381 node.assert_attr(failures=1, tests=1) 382 tnode = node.find_first_by_tag("testcase") 383 tnode.assert_attr( 384 file="test_failure_function.py", 385 line="3", 386 classname="test_failure_function", 387 name="test_fail", 388 ) 389 fnode = tnode.find_first_by_tag("failure") 390 fnode.assert_attr(message="ValueError: 42") 391 assert "ValueError" in fnode.toxml() 392 systemout = fnode.next_siebling 393 assert systemout.tag == "system-out" 394 assert "hello-stdout" in systemout.toxml() 395 assert "info msg" not in systemout.toxml() 396 systemerr = systemout.next_siebling 397 assert systemerr.tag == "system-err" 398 assert "hello-stderr" in systemerr.toxml() 399 assert "info msg" not in systemerr.toxml() 400 401 if junit_logging == "system-out": 402 assert "warning msg" in systemout.toxml() 403 assert "warning msg" not in systemerr.toxml() 404 elif junit_logging == "system-err": 405 assert "warning msg" not in systemout.toxml() 406 assert "warning msg" in systemerr.toxml() 407 elif junit_logging == "no": 408 assert "warning msg" not in systemout.toxml() 409 assert "warning msg" not in systemerr.toxml() 410 411 def test_failure_verbose_message(self, testdir): 412 testdir.makepyfile( 413 """ 414 import sys 415 def test_fail(): 416 assert 0, "An error" 417 """ 418 ) 419 420 result, dom = runandparse(testdir) 421 node = dom.find_first_by_tag("testsuite") 422 tnode = node.find_first_by_tag("testcase") 423 fnode = tnode.find_first_by_tag("failure") 424 fnode.assert_attr(message="AssertionError: An error assert 0") 425 426 def test_failure_escape(self, testdir): 427 testdir.makepyfile( 428 """ 429 import pytest 430 @pytest.mark.parametrize('arg1', "<&'", ids="<&'") 431 def test_func(arg1): 432 print(arg1) 433 assert 0 434 """ 435 ) 436 result, dom = runandparse(testdir) 437 assert result.ret 438 node = dom.find_first_by_tag("testsuite") 439 node.assert_attr(failures=3, tests=3) 440 441 for index, char in enumerate("<&'"): 442 443 tnode = node.find_nth_by_tag("testcase", index) 444 tnode.assert_attr( 445 file="test_failure_escape.py", 446 line="1", 447 classname="test_failure_escape", 448 name="test_func[%s]" % char, 449 ) 450 sysout = tnode.find_first_by_tag("system-out") 451 text = sysout.text 452 assert text == "%s\n" % char 453 454 def test_junit_prefixing(self, testdir): 455 testdir.makepyfile( 456 """ 457 def test_func(): 458 assert 0 459 class TestHello(object): 460 def test_hello(self): 461 pass 462 """ 463 ) 464 result, dom = runandparse(testdir, "--junitprefix=xyz") 465 assert result.ret 466 node = dom.find_first_by_tag("testsuite") 467 node.assert_attr(failures=1, tests=2) 468 tnode = node.find_first_by_tag("testcase") 469 tnode.assert_attr( 470 file="test_junit_prefixing.py", 471 line="0", 472 classname="xyz.test_junit_prefixing", 473 name="test_func", 474 ) 475 tnode = node.find_nth_by_tag("testcase", 1) 476 tnode.assert_attr( 477 file="test_junit_prefixing.py", 478 line="3", 479 classname="xyz.test_junit_prefixing." "TestHello", 480 name="test_hello", 481 ) 482 483 def test_xfailure_function(self, testdir): 484 testdir.makepyfile( 485 """ 486 import pytest 487 def test_xfail(): 488 pytest.xfail("42") 489 """ 490 ) 491 result, dom = runandparse(testdir) 492 assert not result.ret 493 node = dom.find_first_by_tag("testsuite") 494 node.assert_attr(skips=1, tests=1) 495 tnode = node.find_first_by_tag("testcase") 496 tnode.assert_attr( 497 file="test_xfailure_function.py", 498 line="1", 499 classname="test_xfailure_function", 500 name="test_xfail", 501 ) 502 fnode = tnode.find_first_by_tag("skipped") 503 fnode.assert_attr(message="expected test failure") 504 # assert "ValueError" in fnode.toxml() 505 506 def test_xfail_captures_output_once(self, testdir): 507 testdir.makepyfile( 508 """ 509 import sys 510 import pytest 511 512 @pytest.mark.xfail() 513 def test_fail(): 514 sys.stdout.write('XFAIL This is stdout') 515 sys.stderr.write('XFAIL This is stderr') 516 assert 0 517 """ 518 ) 519 result, dom = runandparse(testdir) 520 node = dom.find_first_by_tag("testsuite") 521 tnode = node.find_first_by_tag("testcase") 522 assert len(tnode.find_by_tag("system-err")) == 1 523 assert len(tnode.find_by_tag("system-out")) == 1 524 525 def test_xfailure_xpass(self, testdir): 526 testdir.makepyfile( 527 """ 528 import pytest 529 @pytest.mark.xfail 530 def test_xpass(): 531 pass 532 """ 533 ) 534 result, dom = runandparse(testdir) 535 # assert result.ret 536 node = dom.find_first_by_tag("testsuite") 537 node.assert_attr(skips=0, tests=1) 538 tnode = node.find_first_by_tag("testcase") 539 tnode.assert_attr( 540 file="test_xfailure_xpass.py", 541 line="1", 542 classname="test_xfailure_xpass", 543 name="test_xpass", 544 ) 545 546 def test_xfailure_xpass_strict(self, testdir): 547 testdir.makepyfile( 548 """ 549 import pytest 550 @pytest.mark.xfail(strict=True, reason="This needs to fail!") 551 def test_xpass(): 552 pass 553 """ 554 ) 555 result, dom = runandparse(testdir) 556 # assert result.ret 557 node = dom.find_first_by_tag("testsuite") 558 node.assert_attr(skips=0, tests=1) 559 tnode = node.find_first_by_tag("testcase") 560 tnode.assert_attr( 561 file="test_xfailure_xpass_strict.py", 562 line="1", 563 classname="test_xfailure_xpass_strict", 564 name="test_xpass", 565 ) 566 fnode = tnode.find_first_by_tag("failure") 567 fnode.assert_attr(message="[XPASS(strict)] This needs to fail!") 568 569 def test_collect_error(self, testdir): 570 testdir.makepyfile("syntax error") 571 result, dom = runandparse(testdir) 572 assert result.ret 573 node = dom.find_first_by_tag("testsuite") 574 node.assert_attr(errors=1, tests=1) 575 tnode = node.find_first_by_tag("testcase") 576 tnode.assert_attr(file="test_collect_error.py", name="test_collect_error") 577 assert tnode["line"] is None 578 fnode = tnode.find_first_by_tag("error") 579 fnode.assert_attr(message="collection failure") 580 assert "SyntaxError" in fnode.toxml() 581 582 def test_unicode(self, testdir): 583 value = "hx\xc4\x85\xc4\x87\n" 584 testdir.makepyfile( 585 """ 586 # coding: latin1 587 def test_hello(): 588 print (%r) 589 assert 0 590 """ 591 % value 592 ) 593 result, dom = runandparse(testdir) 594 assert result.ret == 1 595 tnode = dom.find_first_by_tag("testcase") 596 fnode = tnode.find_first_by_tag("failure") 597 if not sys.platform.startswith("java"): 598 assert "hx" in fnode.toxml() 599 600 def test_assertion_binchars(self, testdir): 601 """this test did fail when the escaping wasnt strict""" 602 testdir.makepyfile( 603 """ 604 605 M1 = '\x01\x02\x03\x04' 606 M2 = '\x01\x02\x03\x05' 607 608 def test_str_compare(): 609 assert M1 == M2 610 """ 611 ) 612 result, dom = runandparse(testdir) 613 print(dom.toxml()) 614 615 def test_pass_captures_stdout(self, testdir): 616 testdir.makepyfile( 617 """ 618 def test_pass(): 619 print('hello-stdout') 620 """ 621 ) 622 result, dom = runandparse(testdir) 623 node = dom.find_first_by_tag("testsuite") 624 pnode = node.find_first_by_tag("testcase") 625 systemout = pnode.find_first_by_tag("system-out") 626 assert "hello-stdout" in systemout.toxml() 627 628 def test_pass_captures_stderr(self, testdir): 629 testdir.makepyfile( 630 """ 631 import sys 632 def test_pass(): 633 sys.stderr.write('hello-stderr') 634 """ 635 ) 636 result, dom = runandparse(testdir) 637 node = dom.find_first_by_tag("testsuite") 638 pnode = node.find_first_by_tag("testcase") 639 systemout = pnode.find_first_by_tag("system-err") 640 assert "hello-stderr" in systemout.toxml() 641 642 def test_setup_error_captures_stdout(self, testdir): 643 testdir.makepyfile( 644 """ 645 import pytest 646 647 @pytest.fixture 648 def arg(request): 649 print('hello-stdout') 650 raise ValueError() 651 def test_function(arg): 652 pass 653 """ 654 ) 655 result, dom = runandparse(testdir) 656 node = dom.find_first_by_tag("testsuite") 657 pnode = node.find_first_by_tag("testcase") 658 systemout = pnode.find_first_by_tag("system-out") 659 assert "hello-stdout" in systemout.toxml() 660 661 def test_setup_error_captures_stderr(self, testdir): 662 testdir.makepyfile( 663 """ 664 import sys 665 import pytest 666 667 @pytest.fixture 668 def arg(request): 669 sys.stderr.write('hello-stderr') 670 raise ValueError() 671 def test_function(arg): 672 pass 673 """ 674 ) 675 result, dom = runandparse(testdir) 676 node = dom.find_first_by_tag("testsuite") 677 pnode = node.find_first_by_tag("testcase") 678 systemout = pnode.find_first_by_tag("system-err") 679 assert "hello-stderr" in systemout.toxml() 680 681 def test_avoid_double_stdout(self, testdir): 682 testdir.makepyfile( 683 """ 684 import sys 685 import pytest 686 687 @pytest.fixture 688 def arg(request): 689 yield 690 sys.stdout.write('hello-stdout teardown') 691 raise ValueError() 692 def test_function(arg): 693 sys.stdout.write('hello-stdout call') 694 """ 695 ) 696 result, dom = runandparse(testdir) 697 node = dom.find_first_by_tag("testsuite") 698 pnode = node.find_first_by_tag("testcase") 699 systemout = pnode.find_first_by_tag("system-out") 700 assert "hello-stdout call" in systemout.toxml() 701 assert "hello-stdout teardown" in systemout.toxml() 702 703 704def test_mangle_test_address(): 705 from _pytest.junitxml import mangle_test_address 706 707 address = "::".join(["a/my.py.thing.py", "Class", "()", "method", "[a-1-::]"]) 708 newnames = mangle_test_address(address) 709 assert newnames == ["a.my.py.thing", "Class", "method", "[a-1-::]"] 710 711 712def test_dont_configure_on_slaves(tmpdir): 713 gotten = [] 714 715 class FakeConfig(object): 716 717 def __init__(self): 718 self.pluginmanager = self 719 self.option = self 720 721 def getini(self, name): 722 return "pytest" 723 724 junitprefix = None 725 # XXX: shouldnt need tmpdir ? 726 xmlpath = str(tmpdir.join("junix.xml")) 727 register = gotten.append 728 729 fake_config = FakeConfig() 730 from _pytest import junitxml 731 732 junitxml.pytest_configure(fake_config) 733 assert len(gotten) == 1 734 FakeConfig.slaveinput = None 735 junitxml.pytest_configure(fake_config) 736 assert len(gotten) == 1 737 738 739class TestNonPython(object): 740 741 def test_summing_simple(self, testdir): 742 testdir.makeconftest( 743 """ 744 import pytest 745 def pytest_collect_file(path, parent): 746 if path.ext == ".xyz": 747 return MyItem(path, parent) 748 class MyItem(pytest.Item): 749 def __init__(self, path, parent): 750 super(MyItem, self).__init__(path.basename, parent) 751 self.fspath = path 752 def runtest(self): 753 raise ValueError(42) 754 def repr_failure(self, excinfo): 755 return "custom item runtest failed" 756 """ 757 ) 758 testdir.tmpdir.join("myfile.xyz").write("hello") 759 result, dom = runandparse(testdir) 760 assert result.ret 761 node = dom.find_first_by_tag("testsuite") 762 node.assert_attr(errors=0, failures=1, skips=0, tests=1) 763 tnode = node.find_first_by_tag("testcase") 764 tnode.assert_attr(name="myfile.xyz") 765 fnode = tnode.find_first_by_tag("failure") 766 fnode.assert_attr(message="custom item runtest failed") 767 assert "custom item runtest failed" in fnode.toxml() 768 769 770def test_nullbyte(testdir): 771 # A null byte can not occur in XML (see section 2.2 of the spec) 772 testdir.makepyfile( 773 """ 774 import sys 775 def test_print_nullbyte(): 776 sys.stdout.write('Here the null -->' + chr(0) + '<--') 777 sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--') 778 assert False 779 """ 780 ) 781 xmlf = testdir.tmpdir.join("junit.xml") 782 testdir.runpytest("--junitxml=%s" % xmlf) 783 text = xmlf.read() 784 assert "\x00" not in text 785 assert "#x00" in text 786 787 788def test_nullbyte_replace(testdir): 789 # Check if the null byte gets replaced 790 testdir.makepyfile( 791 """ 792 import sys 793 def test_print_nullbyte(): 794 sys.stdout.write('Here the null -->' + chr(0) + '<--') 795 sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--') 796 assert False 797 """ 798 ) 799 xmlf = testdir.tmpdir.join("junit.xml") 800 testdir.runpytest("--junitxml=%s" % xmlf) 801 text = xmlf.read() 802 assert "#x0" in text 803 804 805def test_invalid_xml_escape(): 806 # Test some more invalid xml chars, the full range should be 807 # tested really but let's just thest the edges of the ranges 808 # intead. 809 # XXX This only tests low unicode character points for now as 810 # there are some issues with the testing infrastructure for 811 # the higher ones. 812 # XXX Testing 0xD (\r) is tricky as it overwrites the just written 813 # line in the output, so we skip it too. 814 global unichr 815 try: 816 unichr(65) 817 except NameError: 818 unichr = chr 819 invalid = ( 820 0x00, 821 0x1, 822 0xB, 823 0xC, 824 0xE, 825 0x19, 826 27, # issue #126 827 0xD800, 828 0xDFFF, 829 0xFFFE, 830 0x0FFFF, 831 ) # , 0x110000) 832 valid = (0x9, 0xA, 0x20) 833 # 0xD, 0xD7FF, 0xE000, 0xFFFD, 0x10000, 0x10FFFF) 834 835 from _pytest.junitxml import bin_xml_escape 836 837 for i in invalid: 838 got = bin_xml_escape(unichr(i)).uniobj 839 if i <= 0xFF: 840 expected = "#x%02X" % i 841 else: 842 expected = "#x%04X" % i 843 assert got == expected 844 for i in valid: 845 assert chr(i) == bin_xml_escape(unichr(i)).uniobj 846 847 848def test_logxml_path_expansion(tmpdir, monkeypatch): 849 home_tilde = py.path.local(os.path.expanduser("~")).join("test.xml") 850 851 xml_tilde = LogXML("~%stest.xml" % tmpdir.sep, None) 852 assert xml_tilde.logfile == home_tilde 853 854 # this is here for when $HOME is not set correct 855 monkeypatch.setenv("HOME", tmpdir) 856 home_var = os.path.normpath(os.path.expandvars("$HOME/test.xml")) 857 858 xml_var = LogXML("$HOME%stest.xml" % tmpdir.sep, None) 859 assert xml_var.logfile == home_var 860 861 862def test_logxml_changingdir(testdir): 863 testdir.makepyfile( 864 """ 865 def test_func(): 866 import os 867 os.chdir("a") 868 """ 869 ) 870 testdir.tmpdir.mkdir("a") 871 result = testdir.runpytest("--junitxml=a/x.xml") 872 assert result.ret == 0 873 assert testdir.tmpdir.join("a/x.xml").check() 874 875 876def test_logxml_makedir(testdir): 877 """--junitxml should automatically create directories for the xml file""" 878 testdir.makepyfile( 879 """ 880 def test_pass(): 881 pass 882 """ 883 ) 884 result = testdir.runpytest("--junitxml=path/to/results.xml") 885 assert result.ret == 0 886 assert testdir.tmpdir.join("path/to/results.xml").check() 887 888 889def test_logxml_check_isdir(testdir): 890 """Give an error if --junit-xml is a directory (#2089)""" 891 result = testdir.runpytest("--junit-xml=.") 892 result.stderr.fnmatch_lines(["*--junitxml must be a filename*"]) 893 894 895def test_escaped_parametrized_names_xml(testdir): 896 testdir.makepyfile( 897 """ 898 import pytest 899 @pytest.mark.parametrize('char', [u"\\x00"]) 900 def test_func(char): 901 assert char 902 """ 903 ) 904 result, dom = runandparse(testdir) 905 assert result.ret == 0 906 node = dom.find_first_by_tag("testcase") 907 node.assert_attr(name="test_func[\\x00]") 908 909 910def test_double_colon_split_function_issue469(testdir): 911 testdir.makepyfile( 912 """ 913 import pytest 914 @pytest.mark.parametrize('param', ["double::colon"]) 915 def test_func(param): 916 pass 917 """ 918 ) 919 result, dom = runandparse(testdir) 920 assert result.ret == 0 921 node = dom.find_first_by_tag("testcase") 922 node.assert_attr(classname="test_double_colon_split_function_issue469") 923 node.assert_attr(name="test_func[double::colon]") 924 925 926def test_double_colon_split_method_issue469(testdir): 927 testdir.makepyfile( 928 """ 929 import pytest 930 class TestClass(object): 931 @pytest.mark.parametrize('param', ["double::colon"]) 932 def test_func(self, param): 933 pass 934 """ 935 ) 936 result, dom = runandparse(testdir) 937 assert result.ret == 0 938 node = dom.find_first_by_tag("testcase") 939 node.assert_attr(classname="test_double_colon_split_method_issue469.TestClass") 940 node.assert_attr(name="test_func[double::colon]") 941 942 943def test_unicode_issue368(testdir): 944 path = testdir.tmpdir.join("test.xml") 945 log = LogXML(str(path), None) 946 ustr = py.builtin._totext("ВНИ!", "utf-8") 947 from _pytest.runner import BaseReport 948 949 class Report(BaseReport): 950 longrepr = ustr 951 sections = [] 952 nodeid = "something" 953 location = "tests/filename.py", 42, "TestClass.method" 954 955 test_report = Report() 956 957 # hopefully this is not too brittle ... 958 log.pytest_sessionstart() 959 node_reporter = log._opentestcase(test_report) 960 node_reporter.append_failure(test_report) 961 node_reporter.append_collect_error(test_report) 962 node_reporter.append_collect_skipped(test_report) 963 node_reporter.append_error(test_report) 964 test_report.longrepr = "filename", 1, ustr 965 node_reporter.append_skipped(test_report) 966 test_report.longrepr = "filename", 1, "Skipped: 卡嘣嘣" 967 node_reporter.append_skipped(test_report) 968 test_report.wasxfail = ustr 969 node_reporter.append_skipped(test_report) 970 log.pytest_sessionfinish() 971 972 973def test_record_property(testdir): 974 testdir.makepyfile( 975 """ 976 import pytest 977 978 @pytest.fixture 979 def other(record_property): 980 record_property("bar", 1) 981 def test_record(record_property, other): 982 record_property("foo", "<1"); 983 """ 984 ) 985 result, dom = runandparse(testdir, "-rwv") 986 node = dom.find_first_by_tag("testsuite") 987 tnode = node.find_first_by_tag("testcase") 988 psnode = tnode.find_first_by_tag("properties") 989 pnodes = psnode.find_by_tag("property") 990 pnodes[0].assert_attr(name="bar", value="1") 991 pnodes[1].assert_attr(name="foo", value="<1") 992 993 994def test_record_property_same_name(testdir): 995 testdir.makepyfile( 996 """ 997 def test_record_with_same_name(record_property): 998 record_property("foo", "bar") 999 record_property("foo", "baz") 1000 """ 1001 ) 1002 result, dom = runandparse(testdir, "-rw") 1003 node = dom.find_first_by_tag("testsuite") 1004 tnode = node.find_first_by_tag("testcase") 1005 psnode = tnode.find_first_by_tag("properties") 1006 pnodes = psnode.find_by_tag("property") 1007 pnodes[0].assert_attr(name="foo", value="bar") 1008 pnodes[1].assert_attr(name="foo", value="baz") 1009 1010 1011def test_record_attribute(testdir): 1012 testdir.makepyfile( 1013 """ 1014 import pytest 1015 1016 @pytest.fixture 1017 def other(record_xml_attribute): 1018 record_xml_attribute("bar", 1) 1019 def test_record(record_xml_attribute, other): 1020 record_xml_attribute("foo", "<1"); 1021 """ 1022 ) 1023 result, dom = runandparse(testdir, "-rw") 1024 node = dom.find_first_by_tag("testsuite") 1025 tnode = node.find_first_by_tag("testcase") 1026 tnode.assert_attr(bar="1") 1027 tnode.assert_attr(foo="<1") 1028 result.stdout.fnmatch_lines( 1029 ["test_record_attribute.py::test_record", "*record_xml_attribute*experimental*"] 1030 ) 1031 1032 1033def test_random_report_log_xdist(testdir): 1034 """xdist calls pytest_runtest_logreport as they are executed by the slaves, 1035 with nodes from several nodes overlapping, so junitxml must cope with that 1036 to produce correct reports. #1064 1037 """ 1038 pytest.importorskip("xdist") 1039 testdir.makepyfile( 1040 """ 1041 import pytest, time 1042 @pytest.mark.parametrize('i', list(range(30))) 1043 def test_x(i): 1044 assert i != 22 1045 """ 1046 ) 1047 _, dom = runandparse(testdir, "-n2") 1048 suite_node = dom.find_first_by_tag("testsuite") 1049 failed = [] 1050 for case_node in suite_node.find_by_tag("testcase"): 1051 if case_node.find_first_by_tag("failure"): 1052 failed.append(case_node["name"]) 1053 1054 assert failed == ["test_x[22]"] 1055 1056 1057def test_runs_twice(testdir): 1058 f = testdir.makepyfile( 1059 """ 1060 def test_pass(): 1061 pass 1062 """ 1063 ) 1064 1065 result, dom = runandparse(testdir, f, f) 1066 assert "INTERNALERROR" not in result.stdout.str() 1067 first, second = [x["classname"] for x in dom.find_by_tag("testcase")] 1068 assert first == second 1069 1070 1071@pytest.mark.xfail(reason="hangs", run=False) 1072def test_runs_twice_xdist(testdir): 1073 pytest.importorskip("xdist") 1074 f = testdir.makepyfile( 1075 """ 1076 def test_pass(): 1077 pass 1078 """ 1079 ) 1080 1081 result, dom = runandparse(testdir, f, "--dist", "each", "--tx", "2*popen") 1082 assert "INTERNALERROR" not in result.stdout.str() 1083 first, second = [x["classname"] for x in dom.find_by_tag("testcase")] 1084 assert first == second 1085 1086 1087def test_fancy_items_regression(testdir): 1088 # issue 1259 1089 testdir.makeconftest( 1090 """ 1091 import pytest 1092 class FunItem(pytest.Item): 1093 def runtest(self): 1094 pass 1095 class NoFunItem(pytest.Item): 1096 def runtest(self): 1097 pass 1098 1099 class FunCollector(pytest.File): 1100 def collect(self): 1101 return [ 1102 FunItem('a', self), 1103 NoFunItem('a', self), 1104 NoFunItem('b', self), 1105 ] 1106 1107 def pytest_collect_file(path, parent): 1108 if path.check(ext='.py'): 1109 return FunCollector(path, parent) 1110 """ 1111 ) 1112 1113 testdir.makepyfile( 1114 """ 1115 def test_pass(): 1116 pass 1117 """ 1118 ) 1119 1120 result, dom = runandparse(testdir) 1121 1122 assert "INTERNALERROR" not in result.stdout.str() 1123 1124 items = sorted( 1125 "%(classname)s %(name)s %(file)s" % x for x in dom.find_by_tag("testcase") 1126 ) 1127 import pprint 1128 1129 pprint.pprint(items) 1130 assert ( 1131 items 1132 == [ 1133 u"conftest a conftest.py", 1134 u"conftest a conftest.py", 1135 u"conftest b conftest.py", 1136 u"test_fancy_items_regression a test_fancy_items_regression.py", 1137 u"test_fancy_items_regression a test_fancy_items_regression.py", 1138 u"test_fancy_items_regression b test_fancy_items_regression.py", 1139 u"test_fancy_items_regression test_pass" u" test_fancy_items_regression.py", 1140 ] 1141 ) 1142 1143 1144def test_global_properties(testdir): 1145 path = testdir.tmpdir.join("test_global_properties.xml") 1146 log = LogXML(str(path), None) 1147 from _pytest.runner import BaseReport 1148 1149 class Report(BaseReport): 1150 sections = [] 1151 nodeid = "test_node_id" 1152 1153 log.pytest_sessionstart() 1154 log.add_global_property("foo", 1) 1155 log.add_global_property("bar", 2) 1156 log.pytest_sessionfinish() 1157 1158 dom = minidom.parse(str(path)) 1159 1160 properties = dom.getElementsByTagName("properties") 1161 1162 assert properties.length == 1, "There must be one <properties> node" 1163 1164 property_list = dom.getElementsByTagName("property") 1165 1166 assert property_list.length == 2, "There most be only 2 property nodes" 1167 1168 expected = {"foo": "1", "bar": "2"} 1169 actual = {} 1170 1171 for p in property_list: 1172 k = str(p.getAttribute("name")) 1173 v = str(p.getAttribute("value")) 1174 actual[k] = v 1175 1176 assert actual == expected 1177 1178 1179def test_url_property(testdir): 1180 test_url = "http://www.github.com/pytest-dev" 1181 path = testdir.tmpdir.join("test_url_property.xml") 1182 log = LogXML(str(path), None) 1183 from _pytest.runner import BaseReport 1184 1185 class Report(BaseReport): 1186 longrepr = "FooBarBaz" 1187 sections = [] 1188 nodeid = "something" 1189 location = "tests/filename.py", 42, "TestClass.method" 1190 url = test_url 1191 1192 test_report = Report() 1193 1194 log.pytest_sessionstart() 1195 node_reporter = log._opentestcase(test_report) 1196 node_reporter.append_failure(test_report) 1197 log.pytest_sessionfinish() 1198 1199 test_case = minidom.parse(str(path)).getElementsByTagName("testcase")[0] 1200 1201 assert ( 1202 test_case.getAttribute("url") == test_url 1203 ), "The URL did not get written to the xml" 1204 1205 1206@pytest.mark.parametrize("suite_name", ["my_suite", ""]) 1207def test_set_suite_name(testdir, suite_name): 1208 if suite_name: 1209 testdir.makeini( 1210 """ 1211 [pytest] 1212 junit_suite_name={} 1213 """.format( 1214 suite_name 1215 ) 1216 ) 1217 expected = suite_name 1218 else: 1219 expected = "pytest" 1220 testdir.makepyfile( 1221 """ 1222 import pytest 1223 1224 def test_func(): 1225 pass 1226 """ 1227 ) 1228 result, dom = runandparse(testdir) 1229 assert result.ret == 0 1230 node = dom.find_first_by_tag("testsuite") 1231 node.assert_attr(name=expected) 1232