1import os 2import platform 3from datetime import datetime 4from typing import cast 5from typing import List 6from typing import Tuple 7from xml.dom import minidom 8 9import py 10import xmlschema 11 12import pytest 13from _pytest.compat import TYPE_CHECKING 14from _pytest.config import Config 15from _pytest.junitxml import bin_xml_escape 16from _pytest.junitxml import LogXML 17from _pytest.pathlib import Path 18from _pytest.reports import BaseReport 19from _pytest.reports import TestReport 20from _pytest.store import Store 21 22 23@pytest.fixture(scope="session") 24def schema(): 25 """Return an xmlschema.XMLSchema object for the junit-10.xsd file.""" 26 fn = Path(__file__).parent / "example_scripts/junit-10.xsd" 27 with fn.open() as f: 28 return xmlschema.XMLSchema(f) 29 30 31@pytest.fixture 32def run_and_parse(testdir, schema): 33 """Fixture that returns a function that can be used to execute pytest and 34 return the parsed ``DomNode`` of the root xml node. 35 36 The ``family`` parameter is used to configure the ``junit_family`` of the written report. 37 "xunit2" is also automatically validated against the schema. 38 """ 39 40 def run(*args, family="xunit1"): 41 if family: 42 args = ("-o", "junit_family=" + family) + args 43 xml_path = testdir.tmpdir.join("junit.xml") 44 result = testdir.runpytest("--junitxml=%s" % xml_path, *args) 45 if family == "xunit2": 46 with xml_path.open() as f: 47 schema.validate(f) 48 xmldoc = minidom.parse(str(xml_path)) 49 return result, DomNode(xmldoc) 50 51 return run 52 53 54def assert_attr(node, **kwargs): 55 __tracebackhide__ = True 56 57 def nodeval(node, name): 58 anode = node.getAttributeNode(name) 59 if anode is not None: 60 return anode.value 61 62 expected = {name: str(value) for name, value in kwargs.items()} 63 on_node = {name: nodeval(node, name) for name in expected} 64 assert on_node == expected 65 66 67class DomNode: 68 def __init__(self, dom): 69 self.__node = dom 70 71 def __repr__(self): 72 return self.__node.toxml() 73 74 def find_first_by_tag(self, tag): 75 return self.find_nth_by_tag(tag, 0) 76 77 def _by_tag(self, tag): 78 return self.__node.getElementsByTagName(tag) 79 80 @property 81 def children(self): 82 return [type(self)(x) for x in self.__node.childNodes] 83 84 @property 85 def get_unique_child(self): 86 children = self.children 87 assert len(children) == 1 88 return children[0] 89 90 def find_nth_by_tag(self, tag, n): 91 items = self._by_tag(tag) 92 try: 93 nth = items[n] 94 except IndexError: 95 pass 96 else: 97 return type(self)(nth) 98 99 def find_by_tag(self, tag): 100 t = type(self) 101 return [t(x) for x in self.__node.getElementsByTagName(tag)] 102 103 def __getitem__(self, key): 104 node = self.__node.getAttributeNode(key) 105 if node is not None: 106 return node.value 107 108 def assert_attr(self, **kwargs): 109 __tracebackhide__ = True 110 return assert_attr(self.__node, **kwargs) 111 112 def toxml(self): 113 return self.__node.toxml() 114 115 @property 116 def text(self): 117 return self.__node.childNodes[0].wholeText 118 119 @property 120 def tag(self): 121 return self.__node.tagName 122 123 @property 124 def next_sibling(self): 125 return type(self)(self.__node.nextSibling) 126 127 128parametrize_families = pytest.mark.parametrize("xunit_family", ["xunit1", "xunit2"]) 129 130 131class TestPython: 132 @parametrize_families 133 def test_summing_simple(self, testdir, run_and_parse, xunit_family): 134 testdir.makepyfile( 135 """ 136 import pytest 137 def test_pass(): 138 pass 139 def test_fail(): 140 assert 0 141 def test_skip(): 142 pytest.skip("") 143 @pytest.mark.xfail 144 def test_xfail(): 145 assert 0 146 @pytest.mark.xfail 147 def test_xpass(): 148 assert 1 149 """ 150 ) 151 result, dom = run_and_parse(family=xunit_family) 152 assert result.ret 153 node = dom.find_first_by_tag("testsuite") 154 node.assert_attr(name="pytest", errors=0, failures=1, skipped=2, tests=5) 155 156 @parametrize_families 157 def test_summing_simple_with_errors(self, testdir, run_and_parse, xunit_family): 158 testdir.makepyfile( 159 """ 160 import pytest 161 @pytest.fixture 162 def fixture(): 163 raise Exception() 164 def test_pass(): 165 pass 166 def test_fail(): 167 assert 0 168 def test_error(fixture): 169 pass 170 @pytest.mark.xfail 171 def test_xfail(): 172 assert False 173 @pytest.mark.xfail(strict=True) 174 def test_xpass(): 175 assert True 176 """ 177 ) 178 result, dom = run_and_parse(family=xunit_family) 179 assert result.ret 180 node = dom.find_first_by_tag("testsuite") 181 node.assert_attr(name="pytest", errors=1, failures=2, skipped=1, tests=5) 182 183 @parametrize_families 184 def test_hostname_in_xml(self, testdir, run_and_parse, xunit_family): 185 testdir.makepyfile( 186 """ 187 def test_pass(): 188 pass 189 """ 190 ) 191 result, dom = run_and_parse(family=xunit_family) 192 node = dom.find_first_by_tag("testsuite") 193 node.assert_attr(hostname=platform.node()) 194 195 @parametrize_families 196 def test_timestamp_in_xml(self, testdir, run_and_parse, xunit_family): 197 testdir.makepyfile( 198 """ 199 def test_pass(): 200 pass 201 """ 202 ) 203 start_time = datetime.now() 204 result, dom = run_and_parse(family=xunit_family) 205 node = dom.find_first_by_tag("testsuite") 206 timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f") 207 assert start_time <= timestamp < datetime.now() 208 209 def test_timing_function(self, testdir, run_and_parse, mock_timing): 210 testdir.makepyfile( 211 """ 212 from _pytest import timing 213 def setup_module(): 214 timing.sleep(1) 215 def teardown_module(): 216 timing.sleep(2) 217 def test_sleep(): 218 timing.sleep(4) 219 """ 220 ) 221 result, dom = run_and_parse() 222 node = dom.find_first_by_tag("testsuite") 223 tnode = node.find_first_by_tag("testcase") 224 val = tnode["time"] 225 assert float(val) == 7.0 226 227 @pytest.mark.parametrize("duration_report", ["call", "total"]) 228 def test_junit_duration_report( 229 self, testdir, monkeypatch, duration_report, run_and_parse 230 ): 231 232 # mock LogXML.node_reporter so it always sets a known duration to each test report object 233 original_node_reporter = LogXML.node_reporter 234 235 def node_reporter_wrapper(s, report): 236 report.duration = 1.0 237 reporter = original_node_reporter(s, report) 238 return reporter 239 240 monkeypatch.setattr(LogXML, "node_reporter", node_reporter_wrapper) 241 242 testdir.makepyfile( 243 """ 244 def test_foo(): 245 pass 246 """ 247 ) 248 result, dom = run_and_parse( 249 "-o", "junit_duration_report={}".format(duration_report) 250 ) 251 node = dom.find_first_by_tag("testsuite") 252 tnode = node.find_first_by_tag("testcase") 253 val = float(tnode["time"]) 254 if duration_report == "total": 255 assert val == 3.0 256 else: 257 assert duration_report == "call" 258 assert val == 1.0 259 260 @parametrize_families 261 def test_setup_error(self, testdir, run_and_parse, xunit_family): 262 testdir.makepyfile( 263 """ 264 import pytest 265 266 @pytest.fixture 267 def arg(request): 268 raise ValueError("Error reason") 269 def test_function(arg): 270 pass 271 """ 272 ) 273 result, dom = run_and_parse(family=xunit_family) 274 assert result.ret 275 node = dom.find_first_by_tag("testsuite") 276 node.assert_attr(errors=1, tests=1) 277 tnode = node.find_first_by_tag("testcase") 278 tnode.assert_attr(classname="test_setup_error", name="test_function") 279 fnode = tnode.find_first_by_tag("error") 280 fnode.assert_attr(message='failed on setup with "ValueError: Error reason"') 281 assert "ValueError" in fnode.toxml() 282 283 @parametrize_families 284 def test_teardown_error(self, testdir, run_and_parse, xunit_family): 285 testdir.makepyfile( 286 """ 287 import pytest 288 289 @pytest.fixture 290 def arg(): 291 yield 292 raise ValueError('Error reason') 293 def test_function(arg): 294 pass 295 """ 296 ) 297 result, dom = run_and_parse(family=xunit_family) 298 assert result.ret 299 node = dom.find_first_by_tag("testsuite") 300 tnode = node.find_first_by_tag("testcase") 301 tnode.assert_attr(classname="test_teardown_error", name="test_function") 302 fnode = tnode.find_first_by_tag("error") 303 fnode.assert_attr(message='failed on teardown with "ValueError: Error reason"') 304 assert "ValueError" in fnode.toxml() 305 306 @parametrize_families 307 def test_call_failure_teardown_error(self, testdir, run_and_parse, xunit_family): 308 testdir.makepyfile( 309 """ 310 import pytest 311 312 @pytest.fixture 313 def arg(): 314 yield 315 raise Exception("Teardown Exception") 316 def test_function(arg): 317 raise Exception("Call Exception") 318 """ 319 ) 320 result, dom = run_and_parse(family=xunit_family) 321 assert result.ret 322 node = dom.find_first_by_tag("testsuite") 323 node.assert_attr(errors=1, failures=1, tests=1) 324 first, second = dom.find_by_tag("testcase") 325 assert first 326 assert second 327 assert first != second 328 fnode = first.find_first_by_tag("failure") 329 fnode.assert_attr(message="Exception: Call Exception") 330 snode = second.find_first_by_tag("error") 331 snode.assert_attr( 332 message='failed on teardown with "Exception: Teardown Exception"' 333 ) 334 335 @parametrize_families 336 def test_skip_contains_name_reason(self, testdir, run_and_parse, xunit_family): 337 testdir.makepyfile( 338 """ 339 import pytest 340 def test_skip(): 341 pytest.skip("hello23") 342 """ 343 ) 344 result, dom = run_and_parse(family=xunit_family) 345 assert result.ret == 0 346 node = dom.find_first_by_tag("testsuite") 347 node.assert_attr(skipped=1) 348 tnode = node.find_first_by_tag("testcase") 349 tnode.assert_attr(classname="test_skip_contains_name_reason", name="test_skip") 350 snode = tnode.find_first_by_tag("skipped") 351 snode.assert_attr(type="pytest.skip", message="hello23") 352 353 @parametrize_families 354 def test_mark_skip_contains_name_reason(self, testdir, run_and_parse, xunit_family): 355 testdir.makepyfile( 356 """ 357 import pytest 358 @pytest.mark.skip(reason="hello24") 359 def test_skip(): 360 assert True 361 """ 362 ) 363 result, dom = run_and_parse(family=xunit_family) 364 assert result.ret == 0 365 node = dom.find_first_by_tag("testsuite") 366 node.assert_attr(skipped=1) 367 tnode = node.find_first_by_tag("testcase") 368 tnode.assert_attr( 369 classname="test_mark_skip_contains_name_reason", name="test_skip" 370 ) 371 snode = tnode.find_first_by_tag("skipped") 372 snode.assert_attr(type="pytest.skip", message="hello24") 373 374 @parametrize_families 375 def test_mark_skipif_contains_name_reason( 376 self, testdir, run_and_parse, xunit_family 377 ): 378 testdir.makepyfile( 379 """ 380 import pytest 381 GLOBAL_CONDITION = True 382 @pytest.mark.skipif(GLOBAL_CONDITION, reason="hello25") 383 def test_skip(): 384 assert True 385 """ 386 ) 387 result, dom = run_and_parse(family=xunit_family) 388 assert result.ret == 0 389 node = dom.find_first_by_tag("testsuite") 390 node.assert_attr(skipped=1) 391 tnode = node.find_first_by_tag("testcase") 392 tnode.assert_attr( 393 classname="test_mark_skipif_contains_name_reason", name="test_skip" 394 ) 395 snode = tnode.find_first_by_tag("skipped") 396 snode.assert_attr(type="pytest.skip", message="hello25") 397 398 @parametrize_families 399 def test_mark_skip_doesnt_capture_output( 400 self, testdir, run_and_parse, xunit_family 401 ): 402 testdir.makepyfile( 403 """ 404 import pytest 405 @pytest.mark.skip(reason="foo") 406 def test_skip(): 407 print("bar!") 408 """ 409 ) 410 result, dom = run_and_parse(family=xunit_family) 411 assert result.ret == 0 412 node_xml = dom.find_first_by_tag("testsuite").toxml() 413 assert "bar!" not in node_xml 414 415 @parametrize_families 416 def test_classname_instance(self, testdir, run_and_parse, xunit_family): 417 testdir.makepyfile( 418 """ 419 class TestClass(object): 420 def test_method(self): 421 assert 0 422 """ 423 ) 424 result, dom = run_and_parse(family=xunit_family) 425 assert result.ret 426 node = dom.find_first_by_tag("testsuite") 427 node.assert_attr(failures=1) 428 tnode = node.find_first_by_tag("testcase") 429 tnode.assert_attr( 430 classname="test_classname_instance.TestClass", name="test_method" 431 ) 432 433 @parametrize_families 434 def test_classname_nested_dir(self, testdir, run_and_parse, xunit_family): 435 p = testdir.tmpdir.ensure("sub", "test_hello.py") 436 p.write("def test_func(): 0/0") 437 result, dom = run_and_parse(family=xunit_family) 438 assert result.ret 439 node = dom.find_first_by_tag("testsuite") 440 node.assert_attr(failures=1) 441 tnode = node.find_first_by_tag("testcase") 442 tnode.assert_attr(classname="sub.test_hello", name="test_func") 443 444 @parametrize_families 445 def test_internal_error(self, testdir, run_and_parse, xunit_family): 446 testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0") 447 testdir.makepyfile("def test_function(): pass") 448 result, dom = run_and_parse(family=xunit_family) 449 assert result.ret 450 node = dom.find_first_by_tag("testsuite") 451 node.assert_attr(errors=1, tests=1) 452 tnode = node.find_first_by_tag("testcase") 453 tnode.assert_attr(classname="pytest", name="internal") 454 fnode = tnode.find_first_by_tag("error") 455 fnode.assert_attr(message="internal error") 456 assert "Division" in fnode.toxml() 457 458 @pytest.mark.parametrize( 459 "junit_logging", ["no", "log", "system-out", "system-err", "out-err", "all"] 460 ) 461 @parametrize_families 462 def test_failure_function( 463 self, testdir, junit_logging, run_and_parse, xunit_family 464 ): 465 testdir.makepyfile( 466 """ 467 import logging 468 import sys 469 470 def test_fail(): 471 print("hello-stdout") 472 sys.stderr.write("hello-stderr\\n") 473 logging.info('info msg') 474 logging.warning('warning msg') 475 raise ValueError(42) 476 """ 477 ) 478 479 result, dom = run_and_parse( 480 "-o", "junit_logging=%s" % junit_logging, family=xunit_family 481 ) 482 assert result.ret, "Expected ret > 0" 483 node = dom.find_first_by_tag("testsuite") 484 node.assert_attr(failures=1, tests=1) 485 tnode = node.find_first_by_tag("testcase") 486 tnode.assert_attr(classname="test_failure_function", name="test_fail") 487 fnode = tnode.find_first_by_tag("failure") 488 fnode.assert_attr(message="ValueError: 42") 489 assert "ValueError" in fnode.toxml(), "ValueError not included" 490 491 if junit_logging in ["log", "all"]: 492 logdata = tnode.find_first_by_tag("system-out") 493 log_xml = logdata.toxml() 494 assert logdata.tag == "system-out", "Expected tag: system-out" 495 assert "info msg" not in log_xml, "Unexpected INFO message" 496 assert "warning msg" in log_xml, "Missing WARN message" 497 if junit_logging in ["system-out", "out-err", "all"]: 498 systemout = tnode.find_first_by_tag("system-out") 499 systemout_xml = systemout.toxml() 500 assert systemout.tag == "system-out", "Expected tag: system-out" 501 assert "info msg" not in systemout_xml, "INFO message found in system-out" 502 assert ( 503 "hello-stdout" in systemout_xml 504 ), "Missing 'hello-stdout' in system-out" 505 if junit_logging in ["system-err", "out-err", "all"]: 506 systemerr = tnode.find_first_by_tag("system-err") 507 systemerr_xml = systemerr.toxml() 508 assert systemerr.tag == "system-err", "Expected tag: system-err" 509 assert "info msg" not in systemerr_xml, "INFO message found in system-err" 510 assert ( 511 "hello-stderr" in systemerr_xml 512 ), "Missing 'hello-stderr' in system-err" 513 assert ( 514 "warning msg" not in systemerr_xml 515 ), "WARN message found in system-err" 516 if junit_logging == "no": 517 assert not tnode.find_by_tag("log"), "Found unexpected content: log" 518 assert not tnode.find_by_tag( 519 "system-out" 520 ), "Found unexpected content: system-out" 521 assert not tnode.find_by_tag( 522 "system-err" 523 ), "Found unexpected content: system-err" 524 525 @parametrize_families 526 def test_failure_verbose_message(self, testdir, run_and_parse, xunit_family): 527 testdir.makepyfile( 528 """ 529 import sys 530 def test_fail(): 531 assert 0, "An error" 532 """ 533 ) 534 result, dom = run_and_parse(family=xunit_family) 535 node = dom.find_first_by_tag("testsuite") 536 tnode = node.find_first_by_tag("testcase") 537 fnode = tnode.find_first_by_tag("failure") 538 fnode.assert_attr(message="AssertionError: An error\nassert 0") 539 540 @parametrize_families 541 def test_failure_escape(self, testdir, run_and_parse, xunit_family): 542 testdir.makepyfile( 543 """ 544 import pytest 545 @pytest.mark.parametrize('arg1', "<&'", ids="<&'") 546 def test_func(arg1): 547 print(arg1) 548 assert 0 549 """ 550 ) 551 result, dom = run_and_parse( 552 "-o", "junit_logging=system-out", family=xunit_family 553 ) 554 assert result.ret 555 node = dom.find_first_by_tag("testsuite") 556 node.assert_attr(failures=3, tests=3) 557 558 for index, char in enumerate("<&'"): 559 560 tnode = node.find_nth_by_tag("testcase", index) 561 tnode.assert_attr( 562 classname="test_failure_escape", name="test_func[%s]" % char 563 ) 564 sysout = tnode.find_first_by_tag("system-out") 565 text = sysout.text 566 assert "%s\n" % char in text 567 568 @parametrize_families 569 def test_junit_prefixing(self, testdir, run_and_parse, xunit_family): 570 testdir.makepyfile( 571 """ 572 def test_func(): 573 assert 0 574 class TestHello(object): 575 def test_hello(self): 576 pass 577 """ 578 ) 579 result, dom = run_and_parse("--junitprefix=xyz", family=xunit_family) 580 assert result.ret 581 node = dom.find_first_by_tag("testsuite") 582 node.assert_attr(failures=1, tests=2) 583 tnode = node.find_first_by_tag("testcase") 584 tnode.assert_attr(classname="xyz.test_junit_prefixing", name="test_func") 585 tnode = node.find_nth_by_tag("testcase", 1) 586 tnode.assert_attr( 587 classname="xyz.test_junit_prefixing.TestHello", name="test_hello" 588 ) 589 590 @parametrize_families 591 def test_xfailure_function(self, testdir, run_and_parse, xunit_family): 592 testdir.makepyfile( 593 """ 594 import pytest 595 def test_xfail(): 596 pytest.xfail("42") 597 """ 598 ) 599 result, dom = run_and_parse(family=xunit_family) 600 assert not result.ret 601 node = dom.find_first_by_tag("testsuite") 602 node.assert_attr(skipped=1, tests=1) 603 tnode = node.find_first_by_tag("testcase") 604 tnode.assert_attr(classname="test_xfailure_function", name="test_xfail") 605 fnode = tnode.find_first_by_tag("skipped") 606 fnode.assert_attr(type="pytest.xfail", message="42") 607 608 @parametrize_families 609 def test_xfailure_marker(self, testdir, run_and_parse, xunit_family): 610 testdir.makepyfile( 611 """ 612 import pytest 613 @pytest.mark.xfail(reason="42") 614 def test_xfail(): 615 assert False 616 """ 617 ) 618 result, dom = run_and_parse(family=xunit_family) 619 assert not result.ret 620 node = dom.find_first_by_tag("testsuite") 621 node.assert_attr(skipped=1, tests=1) 622 tnode = node.find_first_by_tag("testcase") 623 tnode.assert_attr(classname="test_xfailure_marker", name="test_xfail") 624 fnode = tnode.find_first_by_tag("skipped") 625 fnode.assert_attr(type="pytest.xfail", message="42") 626 627 @pytest.mark.parametrize( 628 "junit_logging", ["no", "log", "system-out", "system-err", "out-err", "all"] 629 ) 630 def test_xfail_captures_output_once(self, testdir, junit_logging, run_and_parse): 631 testdir.makepyfile( 632 """ 633 import sys 634 import pytest 635 636 @pytest.mark.xfail() 637 def test_fail(): 638 sys.stdout.write('XFAIL This is stdout') 639 sys.stderr.write('XFAIL This is stderr') 640 assert 0 641 """ 642 ) 643 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) 644 node = dom.find_first_by_tag("testsuite") 645 tnode = node.find_first_by_tag("testcase") 646 if junit_logging in ["system-err", "out-err", "all"]: 647 assert len(tnode.find_by_tag("system-err")) == 1 648 else: 649 assert len(tnode.find_by_tag("system-err")) == 0 650 651 if junit_logging in ["log", "system-out", "out-err", "all"]: 652 assert len(tnode.find_by_tag("system-out")) == 1 653 else: 654 assert len(tnode.find_by_tag("system-out")) == 0 655 656 @parametrize_families 657 def test_xfailure_xpass(self, testdir, run_and_parse, xunit_family): 658 testdir.makepyfile( 659 """ 660 import pytest 661 @pytest.mark.xfail 662 def test_xpass(): 663 pass 664 """ 665 ) 666 result, dom = run_and_parse(family=xunit_family) 667 # assert result.ret 668 node = dom.find_first_by_tag("testsuite") 669 node.assert_attr(skipped=0, tests=1) 670 tnode = node.find_first_by_tag("testcase") 671 tnode.assert_attr(classname="test_xfailure_xpass", name="test_xpass") 672 673 @parametrize_families 674 def test_xfailure_xpass_strict(self, testdir, run_and_parse, xunit_family): 675 testdir.makepyfile( 676 """ 677 import pytest 678 @pytest.mark.xfail(strict=True, reason="This needs to fail!") 679 def test_xpass(): 680 pass 681 """ 682 ) 683 result, dom = run_and_parse(family=xunit_family) 684 # assert result.ret 685 node = dom.find_first_by_tag("testsuite") 686 node.assert_attr(skipped=0, tests=1) 687 tnode = node.find_first_by_tag("testcase") 688 tnode.assert_attr(classname="test_xfailure_xpass_strict", name="test_xpass") 689 fnode = tnode.find_first_by_tag("failure") 690 fnode.assert_attr(message="[XPASS(strict)] This needs to fail!") 691 692 @parametrize_families 693 def test_collect_error(self, testdir, run_and_parse, xunit_family): 694 testdir.makepyfile("syntax error") 695 result, dom = run_and_parse(family=xunit_family) 696 assert result.ret 697 node = dom.find_first_by_tag("testsuite") 698 node.assert_attr(errors=1, tests=1) 699 tnode = node.find_first_by_tag("testcase") 700 fnode = tnode.find_first_by_tag("error") 701 fnode.assert_attr(message="collection failure") 702 assert "SyntaxError" in fnode.toxml() 703 704 def test_unicode(self, testdir, run_and_parse): 705 value = "hx\xc4\x85\xc4\x87\n" 706 testdir.makepyfile( 707 """\ 708 # coding: latin1 709 def test_hello(): 710 print(%r) 711 assert 0 712 """ 713 % value 714 ) 715 result, dom = run_and_parse() 716 assert result.ret == 1 717 tnode = dom.find_first_by_tag("testcase") 718 fnode = tnode.find_first_by_tag("failure") 719 assert "hx" in fnode.toxml() 720 721 def test_assertion_binchars(self, testdir, run_and_parse): 722 """This test did fail when the escaping wasn't strict.""" 723 testdir.makepyfile( 724 """ 725 726 M1 = '\x01\x02\x03\x04' 727 M2 = '\x01\x02\x03\x05' 728 729 def test_str_compare(): 730 assert M1 == M2 731 """ 732 ) 733 result, dom = run_and_parse() 734 print(dom.toxml()) 735 736 @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) 737 def test_pass_captures_stdout(self, testdir, run_and_parse, junit_logging): 738 testdir.makepyfile( 739 """ 740 def test_pass(): 741 print('hello-stdout') 742 """ 743 ) 744 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) 745 node = dom.find_first_by_tag("testsuite") 746 pnode = node.find_first_by_tag("testcase") 747 if junit_logging == "no": 748 assert not node.find_by_tag( 749 "system-out" 750 ), "system-out should not be generated" 751 if junit_logging == "system-out": 752 systemout = pnode.find_first_by_tag("system-out") 753 assert ( 754 "hello-stdout" in systemout.toxml() 755 ), "'hello-stdout' should be in system-out" 756 757 @pytest.mark.parametrize("junit_logging", ["no", "system-err"]) 758 def test_pass_captures_stderr(self, testdir, run_and_parse, junit_logging): 759 testdir.makepyfile( 760 """ 761 import sys 762 def test_pass(): 763 sys.stderr.write('hello-stderr') 764 """ 765 ) 766 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) 767 node = dom.find_first_by_tag("testsuite") 768 pnode = node.find_first_by_tag("testcase") 769 if junit_logging == "no": 770 assert not node.find_by_tag( 771 "system-err" 772 ), "system-err should not be generated" 773 if junit_logging == "system-err": 774 systemerr = pnode.find_first_by_tag("system-err") 775 assert ( 776 "hello-stderr" in systemerr.toxml() 777 ), "'hello-stderr' should be in system-err" 778 779 @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) 780 def test_setup_error_captures_stdout(self, testdir, run_and_parse, junit_logging): 781 testdir.makepyfile( 782 """ 783 import pytest 784 785 @pytest.fixture 786 def arg(request): 787 print('hello-stdout') 788 raise ValueError() 789 def test_function(arg): 790 pass 791 """ 792 ) 793 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) 794 node = dom.find_first_by_tag("testsuite") 795 pnode = node.find_first_by_tag("testcase") 796 if junit_logging == "no": 797 assert not node.find_by_tag( 798 "system-out" 799 ), "system-out should not be generated" 800 if junit_logging == "system-out": 801 systemout = pnode.find_first_by_tag("system-out") 802 assert ( 803 "hello-stdout" in systemout.toxml() 804 ), "'hello-stdout' should be in system-out" 805 806 @pytest.mark.parametrize("junit_logging", ["no", "system-err"]) 807 def test_setup_error_captures_stderr(self, testdir, run_and_parse, junit_logging): 808 testdir.makepyfile( 809 """ 810 import sys 811 import pytest 812 813 @pytest.fixture 814 def arg(request): 815 sys.stderr.write('hello-stderr') 816 raise ValueError() 817 def test_function(arg): 818 pass 819 """ 820 ) 821 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) 822 node = dom.find_first_by_tag("testsuite") 823 pnode = node.find_first_by_tag("testcase") 824 if junit_logging == "no": 825 assert not node.find_by_tag( 826 "system-err" 827 ), "system-err should not be generated" 828 if junit_logging == "system-err": 829 systemerr = pnode.find_first_by_tag("system-err") 830 assert ( 831 "hello-stderr" in systemerr.toxml() 832 ), "'hello-stderr' should be in system-err" 833 834 @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) 835 def test_avoid_double_stdout(self, testdir, run_and_parse, junit_logging): 836 testdir.makepyfile( 837 """ 838 import sys 839 import pytest 840 841 @pytest.fixture 842 def arg(request): 843 yield 844 sys.stdout.write('hello-stdout teardown') 845 raise ValueError() 846 def test_function(arg): 847 sys.stdout.write('hello-stdout call') 848 """ 849 ) 850 result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) 851 node = dom.find_first_by_tag("testsuite") 852 pnode = node.find_first_by_tag("testcase") 853 if junit_logging == "no": 854 assert not node.find_by_tag( 855 "system-out" 856 ), "system-out should not be generated" 857 if junit_logging == "system-out": 858 systemout = pnode.find_first_by_tag("system-out") 859 assert "hello-stdout call" in systemout.toxml() 860 assert "hello-stdout teardown" in systemout.toxml() 861 862 863def test_mangle_test_address(): 864 from _pytest.junitxml import mangle_test_address 865 866 address = "::".join(["a/my.py.thing.py", "Class", "()", "method", "[a-1-::]"]) 867 newnames = mangle_test_address(address) 868 assert newnames == ["a.my.py.thing", "Class", "method", "[a-1-::]"] 869 870 871def test_dont_configure_on_workers(tmpdir) -> None: 872 gotten = [] # type: List[object] 873 874 class FakeConfig: 875 if TYPE_CHECKING: 876 workerinput = None 877 878 def __init__(self): 879 self.pluginmanager = self 880 self.option = self 881 self._store = Store() 882 883 def getini(self, name): 884 return "pytest" 885 886 junitprefix = None 887 # XXX: shouldn't need tmpdir ? 888 xmlpath = str(tmpdir.join("junix.xml")) 889 register = gotten.append 890 891 fake_config = cast(Config, FakeConfig()) 892 from _pytest import junitxml 893 894 junitxml.pytest_configure(fake_config) 895 assert len(gotten) == 1 896 FakeConfig.workerinput = None 897 junitxml.pytest_configure(fake_config) 898 assert len(gotten) == 1 899 900 901class TestNonPython: 902 @parametrize_families 903 def test_summing_simple(self, testdir, run_and_parse, xunit_family): 904 testdir.makeconftest( 905 """ 906 import pytest 907 def pytest_collect_file(path, parent): 908 if path.ext == ".xyz": 909 return MyItem.from_parent(name=path.basename, parent=parent) 910 class MyItem(pytest.Item): 911 def runtest(self): 912 raise ValueError(42) 913 def repr_failure(self, excinfo): 914 return "custom item runtest failed" 915 """ 916 ) 917 testdir.tmpdir.join("myfile.xyz").write("hello") 918 result, dom = run_and_parse(family=xunit_family) 919 assert result.ret 920 node = dom.find_first_by_tag("testsuite") 921 node.assert_attr(errors=0, failures=1, skipped=0, tests=1) 922 tnode = node.find_first_by_tag("testcase") 923 tnode.assert_attr(name="myfile.xyz") 924 fnode = tnode.find_first_by_tag("failure") 925 fnode.assert_attr(message="custom item runtest failed") 926 assert "custom item runtest failed" in fnode.toxml() 927 928 929@pytest.mark.parametrize("junit_logging", ["no", "system-out"]) 930def test_nullbyte(testdir, junit_logging): 931 # A null byte can not occur in XML (see section 2.2 of the spec) 932 testdir.makepyfile( 933 """ 934 import sys 935 def test_print_nullbyte(): 936 sys.stdout.write('Here the null -->' + chr(0) + '<--') 937 sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--') 938 assert False 939 """ 940 ) 941 xmlf = testdir.tmpdir.join("junit.xml") 942 testdir.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) 943 text = xmlf.read() 944 assert "\x00" not in text 945 if junit_logging == "system-out": 946 assert "#x00" in text 947 if junit_logging == "no": 948 assert "#x00" not in text 949 950 951@pytest.mark.parametrize("junit_logging", ["no", "system-out"]) 952def test_nullbyte_replace(testdir, junit_logging): 953 # Check if the null byte gets replaced 954 testdir.makepyfile( 955 """ 956 import sys 957 def test_print_nullbyte(): 958 sys.stdout.write('Here the null -->' + chr(0) + '<--') 959 sys.stdout.write('In repr form -->' + repr(chr(0)) + '<--') 960 assert False 961 """ 962 ) 963 xmlf = testdir.tmpdir.join("junit.xml") 964 testdir.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) 965 text = xmlf.read() 966 if junit_logging == "system-out": 967 assert "#x0" in text 968 if junit_logging == "no": 969 assert "#x0" not in text 970 971 972def test_invalid_xml_escape(): 973 # Test some more invalid xml chars, the full range should be 974 # tested really but let's just test the edges of the ranges 975 # instead. 976 # XXX This only tests low unicode character points for now as 977 # there are some issues with the testing infrastructure for 978 # the higher ones. 979 # XXX Testing 0xD (\r) is tricky as it overwrites the just written 980 # line in the output, so we skip it too. 981 invalid = ( 982 0x00, 983 0x1, 984 0xB, 985 0xC, 986 0xE, 987 0x19, 988 27, # issue #126 989 0xD800, 990 0xDFFF, 991 0xFFFE, 992 0x0FFFF, 993 ) # , 0x110000) 994 valid = (0x9, 0xA, 0x20) 995 # 0xD, 0xD7FF, 0xE000, 0xFFFD, 0x10000, 0x10FFFF) 996 997 for i in invalid: 998 got = bin_xml_escape(chr(i)) 999 if i <= 0xFF: 1000 expected = "#x%02X" % i 1001 else: 1002 expected = "#x%04X" % i 1003 assert got == expected 1004 for i in valid: 1005 assert chr(i) == bin_xml_escape(chr(i)) 1006 1007 1008def test_logxml_path_expansion(tmpdir, monkeypatch): 1009 home_tilde = py.path.local(os.path.expanduser("~")).join("test.xml") 1010 xml_tilde = LogXML("~%stest.xml" % tmpdir.sep, None) 1011 assert xml_tilde.logfile == home_tilde 1012 1013 monkeypatch.setenv("HOME", str(tmpdir)) 1014 home_var = os.path.normpath(os.path.expandvars("$HOME/test.xml")) 1015 xml_var = LogXML("$HOME%stest.xml" % tmpdir.sep, None) 1016 assert xml_var.logfile == home_var 1017 1018 1019def test_logxml_changingdir(testdir): 1020 testdir.makepyfile( 1021 """ 1022 def test_func(): 1023 import os 1024 os.chdir("a") 1025 """ 1026 ) 1027 testdir.tmpdir.mkdir("a") 1028 result = testdir.runpytest("--junitxml=a/x.xml") 1029 assert result.ret == 0 1030 assert testdir.tmpdir.join("a/x.xml").check() 1031 1032 1033def test_logxml_makedir(testdir): 1034 """--junitxml should automatically create directories for the xml file""" 1035 testdir.makepyfile( 1036 """ 1037 def test_pass(): 1038 pass 1039 """ 1040 ) 1041 result = testdir.runpytest("--junitxml=path/to/results.xml") 1042 assert result.ret == 0 1043 assert testdir.tmpdir.join("path/to/results.xml").check() 1044 1045 1046def test_logxml_check_isdir(testdir): 1047 """Give an error if --junit-xml is a directory (#2089)""" 1048 result = testdir.runpytest("--junit-xml=.") 1049 result.stderr.fnmatch_lines(["*--junitxml must be a filename*"]) 1050 1051 1052def test_escaped_parametrized_names_xml(testdir, run_and_parse): 1053 testdir.makepyfile( 1054 """\ 1055 import pytest 1056 @pytest.mark.parametrize('char', ["\\x00"]) 1057 def test_func(char): 1058 assert char 1059 """ 1060 ) 1061 result, dom = run_and_parse() 1062 assert result.ret == 0 1063 node = dom.find_first_by_tag("testcase") 1064 node.assert_attr(name="test_func[\\x00]") 1065 1066 1067def test_double_colon_split_function_issue469(testdir, run_and_parse): 1068 testdir.makepyfile( 1069 """ 1070 import pytest 1071 @pytest.mark.parametrize('param', ["double::colon"]) 1072 def test_func(param): 1073 pass 1074 """ 1075 ) 1076 result, dom = run_and_parse() 1077 assert result.ret == 0 1078 node = dom.find_first_by_tag("testcase") 1079 node.assert_attr(classname="test_double_colon_split_function_issue469") 1080 node.assert_attr(name="test_func[double::colon]") 1081 1082 1083def test_double_colon_split_method_issue469(testdir, run_and_parse): 1084 testdir.makepyfile( 1085 """ 1086 import pytest 1087 class TestClass(object): 1088 @pytest.mark.parametrize('param', ["double::colon"]) 1089 def test_func(self, param): 1090 pass 1091 """ 1092 ) 1093 result, dom = run_and_parse() 1094 assert result.ret == 0 1095 node = dom.find_first_by_tag("testcase") 1096 node.assert_attr(classname="test_double_colon_split_method_issue469.TestClass") 1097 node.assert_attr(name="test_func[double::colon]") 1098 1099 1100def test_unicode_issue368(testdir) -> None: 1101 path = testdir.tmpdir.join("test.xml") 1102 log = LogXML(str(path), None) 1103 ustr = "ВНИ!" 1104 1105 class Report(BaseReport): 1106 longrepr = ustr 1107 sections = [] # type: List[Tuple[str, str]] 1108 nodeid = "something" 1109 location = "tests/filename.py", 42, "TestClass.method" 1110 1111 test_report = cast(TestReport, Report()) 1112 1113 # hopefully this is not too brittle ... 1114 log.pytest_sessionstart() 1115 node_reporter = log._opentestcase(test_report) 1116 node_reporter.append_failure(test_report) 1117 node_reporter.append_collect_error(test_report) 1118 node_reporter.append_collect_skipped(test_report) 1119 node_reporter.append_error(test_report) 1120 test_report.longrepr = "filename", 1, ustr 1121 node_reporter.append_skipped(test_report) 1122 test_report.longrepr = "filename", 1, "Skipped: 卡嘣嘣" 1123 node_reporter.append_skipped(test_report) 1124 test_report.wasxfail = ustr # type: ignore[attr-defined] 1125 node_reporter.append_skipped(test_report) 1126 log.pytest_sessionfinish() 1127 1128 1129def test_record_property(testdir, run_and_parse): 1130 testdir.makepyfile( 1131 """ 1132 import pytest 1133 1134 @pytest.fixture 1135 def other(record_property): 1136 record_property("bar", 1) 1137 def test_record(record_property, other): 1138 record_property("foo", "<1"); 1139 """ 1140 ) 1141 result, dom = run_and_parse() 1142 node = dom.find_first_by_tag("testsuite") 1143 tnode = node.find_first_by_tag("testcase") 1144 psnode = tnode.find_first_by_tag("properties") 1145 pnodes = psnode.find_by_tag("property") 1146 pnodes[0].assert_attr(name="bar", value="1") 1147 pnodes[1].assert_attr(name="foo", value="<1") 1148 result.stdout.fnmatch_lines(["*= 1 passed in *"]) 1149 1150 1151def test_record_property_same_name(testdir, run_and_parse): 1152 testdir.makepyfile( 1153 """ 1154 def test_record_with_same_name(record_property): 1155 record_property("foo", "bar") 1156 record_property("foo", "baz") 1157 """ 1158 ) 1159 result, dom = run_and_parse() 1160 node = dom.find_first_by_tag("testsuite") 1161 tnode = node.find_first_by_tag("testcase") 1162 psnode = tnode.find_first_by_tag("properties") 1163 pnodes = psnode.find_by_tag("property") 1164 pnodes[0].assert_attr(name="foo", value="bar") 1165 pnodes[1].assert_attr(name="foo", value="baz") 1166 1167 1168@pytest.mark.parametrize("fixture_name", ["record_property", "record_xml_attribute"]) 1169def test_record_fixtures_without_junitxml(testdir, fixture_name): 1170 testdir.makepyfile( 1171 """ 1172 def test_record({fixture_name}): 1173 {fixture_name}("foo", "bar") 1174 """.format( 1175 fixture_name=fixture_name 1176 ) 1177 ) 1178 result = testdir.runpytest() 1179 assert result.ret == 0 1180 1181 1182@pytest.mark.filterwarnings("default") 1183def test_record_attribute(testdir, run_and_parse): 1184 testdir.makeini( 1185 """ 1186 [pytest] 1187 junit_family = xunit1 1188 """ 1189 ) 1190 testdir.makepyfile( 1191 """ 1192 import pytest 1193 1194 @pytest.fixture 1195 def other(record_xml_attribute): 1196 record_xml_attribute("bar", 1) 1197 def test_record(record_xml_attribute, other): 1198 record_xml_attribute("foo", "<1"); 1199 """ 1200 ) 1201 result, dom = run_and_parse() 1202 node = dom.find_first_by_tag("testsuite") 1203 tnode = node.find_first_by_tag("testcase") 1204 tnode.assert_attr(bar="1") 1205 tnode.assert_attr(foo="<1") 1206 result.stdout.fnmatch_lines( 1207 ["*test_record_attribute.py:6:*record_xml_attribute is an experimental feature"] 1208 ) 1209 1210 1211@pytest.mark.filterwarnings("default") 1212@pytest.mark.parametrize("fixture_name", ["record_xml_attribute", "record_property"]) 1213def test_record_fixtures_xunit2(testdir, fixture_name, run_and_parse): 1214 """Ensure record_xml_attribute and record_property drop values when outside of legacy family.""" 1215 testdir.makeini( 1216 """ 1217 [pytest] 1218 junit_family = xunit2 1219 """ 1220 ) 1221 testdir.makepyfile( 1222 """ 1223 import pytest 1224 1225 @pytest.fixture 1226 def other({fixture_name}): 1227 {fixture_name}("bar", 1) 1228 def test_record({fixture_name}, other): 1229 {fixture_name}("foo", "<1"); 1230 """.format( 1231 fixture_name=fixture_name 1232 ) 1233 ) 1234 1235 result, dom = run_and_parse(family=None) 1236 expected_lines = [] 1237 if fixture_name == "record_xml_attribute": 1238 expected_lines.append( 1239 "*test_record_fixtures_xunit2.py:6:*record_xml_attribute is an experimental feature" 1240 ) 1241 expected_lines = [ 1242 "*test_record_fixtures_xunit2.py:6:*{fixture_name} is incompatible " 1243 "with junit_family 'xunit2' (use 'legacy' or 'xunit1')".format( 1244 fixture_name=fixture_name 1245 ) 1246 ] 1247 result.stdout.fnmatch_lines(expected_lines) 1248 1249 1250def test_random_report_log_xdist(testdir, monkeypatch, run_and_parse): 1251 """`xdist` calls pytest_runtest_logreport as they are executed by the workers, 1252 with nodes from several nodes overlapping, so junitxml must cope with that 1253 to produce correct reports (#1064).""" 1254 pytest.importorskip("xdist") 1255 monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) 1256 testdir.makepyfile( 1257 """ 1258 import pytest, time 1259 @pytest.mark.parametrize('i', list(range(30))) 1260 def test_x(i): 1261 assert i != 22 1262 """ 1263 ) 1264 _, dom = run_and_parse("-n2") 1265 suite_node = dom.find_first_by_tag("testsuite") 1266 failed = [] 1267 for case_node in suite_node.find_by_tag("testcase"): 1268 if case_node.find_first_by_tag("failure"): 1269 failed.append(case_node["name"]) 1270 1271 assert failed == ["test_x[22]"] 1272 1273 1274@parametrize_families 1275def test_root_testsuites_tag(testdir, run_and_parse, xunit_family): 1276 testdir.makepyfile( 1277 """ 1278 def test_x(): 1279 pass 1280 """ 1281 ) 1282 _, dom = run_and_parse(family=xunit_family) 1283 root = dom.get_unique_child 1284 assert root.tag == "testsuites" 1285 suite_node = root.get_unique_child 1286 assert suite_node.tag == "testsuite" 1287 1288 1289def test_runs_twice(testdir, run_and_parse): 1290 f = testdir.makepyfile( 1291 """ 1292 def test_pass(): 1293 pass 1294 """ 1295 ) 1296 1297 result, dom = run_and_parse(f, f) 1298 result.stdout.no_fnmatch_line("*INTERNALERROR*") 1299 first, second = [x["classname"] for x in dom.find_by_tag("testcase")] 1300 assert first == second 1301 1302 1303def test_runs_twice_xdist(testdir, run_and_parse): 1304 pytest.importorskip("xdist") 1305 testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") 1306 f = testdir.makepyfile( 1307 """ 1308 def test_pass(): 1309 pass 1310 """ 1311 ) 1312 1313 result, dom = run_and_parse(f, "--dist", "each", "--tx", "2*popen") 1314 result.stdout.no_fnmatch_line("*INTERNALERROR*") 1315 first, second = [x["classname"] for x in dom.find_by_tag("testcase")] 1316 assert first == second 1317 1318 1319def test_fancy_items_regression(testdir, run_and_parse): 1320 # issue 1259 1321 testdir.makeconftest( 1322 """ 1323 import pytest 1324 class FunItem(pytest.Item): 1325 def runtest(self): 1326 pass 1327 class NoFunItem(pytest.Item): 1328 def runtest(self): 1329 pass 1330 1331 class FunCollector(pytest.File): 1332 def collect(self): 1333 return [ 1334 FunItem.from_parent(name='a', parent=self), 1335 NoFunItem.from_parent(name='a', parent=self), 1336 NoFunItem.from_parent(name='b', parent=self), 1337 ] 1338 1339 def pytest_collect_file(path, parent): 1340 if path.check(ext='.py'): 1341 return FunCollector.from_parent(fspath=path, parent=parent) 1342 """ 1343 ) 1344 1345 testdir.makepyfile( 1346 """ 1347 def test_pass(): 1348 pass 1349 """ 1350 ) 1351 1352 result, dom = run_and_parse() 1353 1354 result.stdout.no_fnmatch_line("*INTERNALERROR*") 1355 1356 items = sorted("%(classname)s %(name)s" % x for x in dom.find_by_tag("testcase")) 1357 import pprint 1358 1359 pprint.pprint(items) 1360 assert items == [ 1361 "conftest a", 1362 "conftest a", 1363 "conftest b", 1364 "test_fancy_items_regression a", 1365 "test_fancy_items_regression a", 1366 "test_fancy_items_regression b", 1367 "test_fancy_items_regression test_pass", 1368 ] 1369 1370 1371@parametrize_families 1372def test_global_properties(testdir, xunit_family) -> None: 1373 path = testdir.tmpdir.join("test_global_properties.xml") 1374 log = LogXML(str(path), None, family=xunit_family) 1375 1376 class Report(BaseReport): 1377 sections = [] # type: List[Tuple[str, str]] 1378 nodeid = "test_node_id" 1379 1380 log.pytest_sessionstart() 1381 log.add_global_property("foo", "1") 1382 log.add_global_property("bar", "2") 1383 log.pytest_sessionfinish() 1384 1385 dom = minidom.parse(str(path)) 1386 1387 properties = dom.getElementsByTagName("properties") 1388 1389 assert properties.length == 1, "There must be one <properties> node" 1390 1391 property_list = dom.getElementsByTagName("property") 1392 1393 assert property_list.length == 2, "There most be only 2 property nodes" 1394 1395 expected = {"foo": "1", "bar": "2"} 1396 actual = {} 1397 1398 for p in property_list: 1399 k = str(p.getAttribute("name")) 1400 v = str(p.getAttribute("value")) 1401 actual[k] = v 1402 1403 assert actual == expected 1404 1405 1406def test_url_property(testdir) -> None: 1407 test_url = "http://www.github.com/pytest-dev" 1408 path = testdir.tmpdir.join("test_url_property.xml") 1409 log = LogXML(str(path), None) 1410 1411 class Report(BaseReport): 1412 longrepr = "FooBarBaz" 1413 sections = [] # type: List[Tuple[str, str]] 1414 nodeid = "something" 1415 location = "tests/filename.py", 42, "TestClass.method" 1416 url = test_url 1417 1418 test_report = cast(TestReport, Report()) 1419 1420 log.pytest_sessionstart() 1421 node_reporter = log._opentestcase(test_report) 1422 node_reporter.append_failure(test_report) 1423 log.pytest_sessionfinish() 1424 1425 test_case = minidom.parse(str(path)).getElementsByTagName("testcase")[0] 1426 1427 assert ( 1428 test_case.getAttribute("url") == test_url 1429 ), "The URL did not get written to the xml" 1430 1431 1432@parametrize_families 1433def test_record_testsuite_property(testdir, run_and_parse, xunit_family): 1434 testdir.makepyfile( 1435 """ 1436 def test_func1(record_testsuite_property): 1437 record_testsuite_property("stats", "all good") 1438 1439 def test_func2(record_testsuite_property): 1440 record_testsuite_property("stats", 10) 1441 """ 1442 ) 1443 result, dom = run_and_parse(family=xunit_family) 1444 assert result.ret == 0 1445 node = dom.find_first_by_tag("testsuite") 1446 properties_node = node.find_first_by_tag("properties") 1447 p1_node = properties_node.find_nth_by_tag("property", 0) 1448 p2_node = properties_node.find_nth_by_tag("property", 1) 1449 p1_node.assert_attr(name="stats", value="all good") 1450 p2_node.assert_attr(name="stats", value="10") 1451 1452 1453def test_record_testsuite_property_junit_disabled(testdir): 1454 testdir.makepyfile( 1455 """ 1456 def test_func1(record_testsuite_property): 1457 record_testsuite_property("stats", "all good") 1458 """ 1459 ) 1460 result = testdir.runpytest() 1461 assert result.ret == 0 1462 1463 1464@pytest.mark.parametrize("junit", [True, False]) 1465def test_record_testsuite_property_type_checking(testdir, junit): 1466 testdir.makepyfile( 1467 """ 1468 def test_func1(record_testsuite_property): 1469 record_testsuite_property(1, 2) 1470 """ 1471 ) 1472 args = ("--junitxml=tests.xml",) if junit else () 1473 result = testdir.runpytest(*args) 1474 assert result.ret == 1 1475 result.stdout.fnmatch_lines( 1476 ["*TypeError: name parameter needs to be a string, but int given"] 1477 ) 1478 1479 1480@pytest.mark.parametrize("suite_name", ["my_suite", ""]) 1481@parametrize_families 1482def test_set_suite_name(testdir, suite_name, run_and_parse, xunit_family): 1483 if suite_name: 1484 testdir.makeini( 1485 """ 1486 [pytest] 1487 junit_suite_name={suite_name} 1488 junit_family={family} 1489 """.format( 1490 suite_name=suite_name, family=xunit_family 1491 ) 1492 ) 1493 expected = suite_name 1494 else: 1495 expected = "pytest" 1496 testdir.makepyfile( 1497 """ 1498 import pytest 1499 1500 def test_func(): 1501 pass 1502 """ 1503 ) 1504 result, dom = run_and_parse(family=xunit_family) 1505 assert result.ret == 0 1506 node = dom.find_first_by_tag("testsuite") 1507 node.assert_attr(name=expected) 1508 1509 1510def test_escaped_skipreason_issue3533(testdir, run_and_parse): 1511 testdir.makepyfile( 1512 """ 1513 import pytest 1514 @pytest.mark.skip(reason='1 <> 2') 1515 def test_skip(): 1516 pass 1517 """ 1518 ) 1519 _, dom = run_and_parse() 1520 node = dom.find_first_by_tag("testcase") 1521 snode = node.find_first_by_tag("skipped") 1522 assert "1 <> 2" in snode.text 1523 snode.assert_attr(message="1 <> 2") 1524 1525 1526@parametrize_families 1527def test_logging_passing_tests_disabled_does_not_log_test_output( 1528 testdir, run_and_parse, xunit_family 1529): 1530 testdir.makeini( 1531 """ 1532 [pytest] 1533 junit_log_passing_tests=False 1534 junit_logging=system-out 1535 junit_family={family} 1536 """.format( 1537 family=xunit_family 1538 ) 1539 ) 1540 testdir.makepyfile( 1541 """ 1542 import pytest 1543 import logging 1544 import sys 1545 1546 def test_func(): 1547 sys.stdout.write('This is stdout') 1548 sys.stderr.write('This is stderr') 1549 logging.warning('hello') 1550 """ 1551 ) 1552 result, dom = run_and_parse(family=xunit_family) 1553 assert result.ret == 0 1554 node = dom.find_first_by_tag("testcase") 1555 assert len(node.find_by_tag("system-err")) == 0 1556 assert len(node.find_by_tag("system-out")) == 0 1557 1558 1559@parametrize_families 1560@pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"]) 1561def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430( 1562 testdir, junit_logging, run_and_parse, xunit_family 1563): 1564 testdir.makeini( 1565 """ 1566 [pytest] 1567 junit_log_passing_tests=False 1568 junit_family={family} 1569 """.format( 1570 family=xunit_family 1571 ) 1572 ) 1573 testdir.makepyfile( 1574 """ 1575 import pytest 1576 import logging 1577 import sys 1578 1579 def test_func(): 1580 logging.warning('hello') 1581 assert 0 1582 """ 1583 ) 1584 result, dom = run_and_parse( 1585 "-o", "junit_logging=%s" % junit_logging, family=xunit_family 1586 ) 1587 assert result.ret == 1 1588 node = dom.find_first_by_tag("testcase") 1589 if junit_logging == "system-out": 1590 assert len(node.find_by_tag("system-err")) == 0 1591 assert len(node.find_by_tag("system-out")) == 1 1592 elif junit_logging == "system-err": 1593 assert len(node.find_by_tag("system-err")) == 1 1594 assert len(node.find_by_tag("system-out")) == 0 1595 else: 1596 assert junit_logging == "no" 1597 assert len(node.find_by_tag("system-err")) == 0 1598 assert len(node.find_by_tag("system-out")) == 0 1599