Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issue 1074 and clean up junitxml #1091

Merged
merged 20 commits into from
Dec 7, 2015

Conversation

RonnyPfannschmidt
Copy link
Member

i started by running yapf on junitxml to get my ide warning-free
did the same on its tests and will simplify the test api a bit (i want the xml api out of the picture)

Fix #1074
Fix #641

@RonnyPfannschmidt RonnyPfannschmidt changed the title [wip] fix issue 1074 and clean up junitxml fix issue 1074 and clean up junitxml Oct 10, 2015
@RonnyPfannschmidt
Copy link
Member Author

@alex can you perhaps take a look if that branch has positive effect on the memory usage of the junit reporting of the large testsuite?

@nicoddemus
Copy link
Member

Sorry for taking so long to review this @RonnyPfannschmidt! 😅

Overall I really liked the changes you made. As a minor nitpick, I found some PEP-8 errors (mostly too much white-space between functions), but as I said those are minor.

It seems this PR would interest @icemac as well.

names = mangle_testnames(report.nodeid.split("::"))
def make_properties_node(self):
"""Return a Junit node containing custom properties set for
the current test, if any, and reset the current custom properties.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docstring no longer reflects reality (it doesn't clean properties).

@nicoddemus
Copy link
Member

Aside from my minor comments, LGTM. 👍 Feel free to merge if you think my naming suggestions are not worthwhile to make right now. 😄

@icemac
Copy link

icemac commented Oct 14, 2015

I will have a look into it, after this PR is merged and released. Thanks for looking into it.

@RonnyPfannschmidt
Copy link
Member Author

@icemac please take a look at it beforehand, i don't have a good benchmark atm, and i'd like to be sure i'm delivering the right thing

@icemac
Copy link

icemac commented Oct 15, 2015

@RonnyPfannschmidt I'll test today.

@icemac
Copy link

icemac commented Oct 15, 2015

@RonnyPfannschmidt I get an exception after some tests are run (summing up the number of successes etc. in the summary line is less than the number of collected items), see the transcript:

bin/py.test -p no:sugar --junitxml=parts/tests.xml --cov-report html --cov-report xml --cov-report term --cov src
============================= test session starts ==============================
platform linux2 -- Python 2.7.9, pytest-2.8.2.dev1, py-1.4.30, pluggy-0.3.1
rootdir: .../instance, inifile: pytest.ini
plugins: gocept.pytestlayer-2.0, cov-1.8.1, remove-stale-bytecode-1.0
collected 4449 items
src/pytest/doc/en/example/assertion/test_failures.py .
src/pytest/doc/en/example/assertion/test_setup_flow_example.py ..
src/pytest/doc/en/example/assertion/global_testmodule_config/test_hello.py .
src/pytest/doc/en/example/costlysetup/sub1/test_quick.py .
src/pytest/doc/en/example/costlysetup/sub2/test_two.py ..
src/pytest/doc/en/example/py2py3/test_py2.py .
src/pytest/testing/test_argcomplete.py ..
src/pytest/testing/test_assertinterpret.py ...........................s
src/pytest/testing/test_assertion.py ..........................................F..F...
src/pytest/testing/test_assertrewrite.py ..........................FF..s........F.....
src/pytest/testing/test_cache.py .................
src/pytest/testing/test_capture.py ...........x.....FFFFF.......F.FF....F.......................................F..F...FFF...
src/pytest/testing/test_collection.py ............x....F.................
src/pytest/testing/test_config.py ........x......................................
src/pytest/testing/test_conftest.py .............F.................................
src/pytest/testing/test_doctest.py ..................................
src/pytest/testing/test_genscript.py s...ss.
src/pytest/testing/test_helpconfig.py .....FF
src/pytest/testing/test_junitxml.py .....F.........................s
src/pytest/testing/test_mark.py ....................................x.................x.
src/pytest/testing/test_monkeypatch.py ..............................
src/pytest/testing/test_nose.py ...................
src/pytest/testing/test_parseopt.py .........................s
src/pytest/testing/test_pastebin.py ...
src/pytest/testing/test_pdb.py ....ssssssssssssFs
src/pytest/testing/test_pluginmanager.py ..F......F................
src/pytest/testing/test_pytester.py x......
src/pytest/testing/test_recwarn.py ...................
src/pytest/testing/test_resultlog.py ...........
src/pytest/testing/test_runner.py ..................sssssss..ssss.......x.........F....
src/pytest/testing/test_runner_xunit.py .............
src/pytest/testing/test_session.py ................
src/pytest/testing/test_skipping.py .................................................
src/pytest/testing/test_terminal.py ..........s.........................s.......................................................
src/pytest/testing/test_tmpdir.py ...........s
src/pytest/testing/test_unittest.py ...................ssssssss...........
src/pytest/testing/cx_freeze/tests/test_doctest.txt .
src/pytest/testing/cx_freeze/tests/test_trivial.py ..
[… many tests of the project under test ran here without any failure …]
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File ".../src/pytest/_pytest/main.py", line 90, in wrap_session
INTERNALERROR>     session.exitstatus = doit(config, session) or 0
INTERNALERROR>   File ".../src/pytest/_pytest/main.py", line 121, in _main
INTERNALERROR>     config.hook.pytest_runtestloop(session=session)
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 724, in __call__
INTERNALERROR>     return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 338, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 333, in <lambda>
INTERNALERROR>     _MultiCall(methods, kwargs, hook.spec_opts).execute()
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 596, in execute
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File ".../src/pytest/_pytest/main.py", line 146, in pytest_runtestloop
INTERNALERROR>     item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 724, in __call__
INTERNALERROR>     return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 338, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 333, in <lambda>
INTERNALERROR>     _MultiCall(methods, kwargs, hook.spec_opts).execute()
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 595, in execute
INTERNALERROR>     return _wrapped_call(hook_impl.function(*args), self.execute)
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 253, in _wrapped_call
INTERNALERROR>     return call_outcome.get_result()
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 279, in get_result
INTERNALERROR>     _reraise(*ex)  # noqa
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 264, in __init__
INTERNALERROR>     self.result = func()
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 596, in execute
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File ".../src/pytest/_pytest/runner.py", line 65, in pytest_runtest_protocol
INTERNALERROR>     runtestprotocol(item, nextitem=nextitem)
INTERNALERROR>   File ".../src/pytest/_pytest/runner.py", line 72, in runtestprotocol
INTERNALERROR>     rep = call_and_report(item, "setup", log)
INTERNALERROR>   File ".../src/pytest/_pytest/runner.py", line 123, in call_and_report
INTERNALERROR>     hook.pytest_runtest_logreport(report=report)
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 724, in __call__
INTERNALERROR>     return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 338, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 333, in <lambda>
INTERNALERROR>     _MultiCall(methods, kwargs, hook.spec_opts).execute()
INTERNALERROR>   File ".../src/pytest/_pytest/vendored_packages/pluggy.py", line 596, in execute
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File ".../src/pytest/_pytest/junitxml.py", line 320, in pytest_runtest_logreport
INTERNALERROR>     self.update_testcase_duration(report)
INTERNALERROR>   File ".../src/pytest/_pytest/junitxml.py", line 329, in update_testcase_duration
INTERNALERROR>     reporter.duration += getattr(report, 'duration', 0.0)
INTERNALERROR> AttributeError: '_NodeReporter' object has no attribute 'duration'
28 failed, 2178 passed, 73 skipped, 10 xfailed, 9 pytest-warnings in 400.96 seconds 

@RonnyPfannschmidt
Copy link
Member Author

i see, good find, i'll investigate

@RonnyPfannschmidt
Copy link
Member Author

after investigation i'm certain the issue cannot be replicated when running the correct version of pytest

@nicoddemus @hpk42 this one seems ready for merge

'failure',
'skipped',
], 0)
self.nodere_porters = {} # nodeid -> _NodeReporter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be node_reporters?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good find

@nicoddemus
Copy link
Member

Could be that the problem reported by @icemac be due to _NodeReporter.finalize() being called twice? That would explain the AttributeError.

@RonnyPfannschmidt
Copy link
Member Author

i think its related to the issue about duplicate tests

@nicoddemus
Copy link
Member

Good idea. But then it should be simple to verify it, just pass the same file name to pytest twice and you should see the same error. If that's the case we should fix this branch, as we just decided that running the same set of tests more than once is a valid use case.

@RonnyPfannschmidt
Copy link
Member Author

i just tried, and it will err out

@nicoddemus
Copy link
Member

The current master will repeat a new testcase tag when you execute the same test twice:

<?xml version="1.0" encoding="utf-8"?>
<testsuite errors="0" failures="0" name="pytest" skips="0" tests="2" time="0.013">
  <testcase classname="tmp.test_foo1" file="tmp\test_foo1.py" line="0" name="test_1" time="0.0010001659393310547"/>
  <testcase classname="tmp.test_foo1" file="tmp\test_foo1.py" line="0" name="test_1" time="0.0010001659393310547"/>
</testsuite>

I was thinking, to support the same case here, perhaps we could:

  • instead of a node_reporters_ordered list, just keep a list of xml strings which have been finished so far (_NodeReporter.to_xml()); lets call that xml_test_cases (just for exemplification, feel free to use a more appropriate name);
  • instead of calling _NodeReporter.finalize() when you see a "teardown" event in pytest_runtest_logreport, just call to_xml(), store it in xml_test_cases and discard the _NodeReporter instance for that nodeid. We won't even need a finalize method anymore.

This will change the order of the testsuite tags in the report, where previously they would appear in the order they were "setup" first, but now they will appear in the order they where "teardown" first, but I don't think this is an issue at all.

What do you think?

@RonnyPfannschmidt
Copy link
Member Author

Will break on xdist

@nicoddemus
Copy link
Member

Will break on xdist

You mean in the normal usage, or for duplicated test ids?

@RonnyPfannschmidt
Copy link
Member Author

For duplicate tests, they can interleave wrt reports

@nicoddemus
Copy link
Member

Thought about that too, but that happens today already IIUC (both master and this PR)... my proposal would at least avoid the internal error.

@RonnyPfannschmidt
Copy link
Member Author

I plan to fix both by extending the key with the slave ID and implemening your proposal

@nicoddemus
Copy link
Member

Sounds good, thanks!

But how will you know which item was executed in which slave? We don't have how to get that information now, I think.

@nicoddemus
Copy link
Member

Oooh didn't know about this line:

    def slave_testreport(self, node, rep):
        if rep.when == "call" or (rep.when == "setup" and not rep.passed):
            self.sched.remove_item(node, rep.item_index, rep.duration)
        # self.report_line("testreport %s: %s" %(rep.id, rep.status))
        rep.node = node   # <----
        self.config.hook.pytest_runtest_logreport(report=rep)
        self._handlefailures(rep)

So the report object knows where each test executed, nice. Your suggestion seems perfect then. 😎

@RonnyPfannschmidt
Copy link
Member Author

that little gem came directly from @hpk42 :)

@nicoddemus
Copy link
Member

I think this change warrants a CHANGELOG entry

@nicoddemus
Copy link
Member

I think after you add a CHANGELOG entry you can merge this @RonnyPfannschmidt

@RonnyPfannschmidt
Copy link
Member Author

im on it

@RonnyPfannschmidt
Copy link
Member Author

rebased on to of master and added the entry, i'll wait for travis then push the button

@nicoddemus
Copy link
Member

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants