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