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

add --session-timeout #165

Merged
merged 15 commits into from
Mar 7, 2024
Merged

add --session-timeout #165

merged 15 commits into from
Mar 7, 2024

Conversation

okken
Copy link
Contributor

@okken okken commented Jan 23, 2024

Addresses request #163

@okken
Copy link
Contributor Author

okken commented Jan 24, 2024

Adding WIP because docs need to be updated.

pytest_timeout.py Outdated Show resolved Hide resolved
pytest_timeout.py Outdated Show resolved Hide resolved
pytest_timeout.py Outdated Show resolved Hide resolved
test_pytest_timeout.py Outdated Show resolved Hide resolved
@okken
Copy link
Contributor Author

okken commented Jan 25, 2024

This is awesome feedback.
thanks so much for taking the time to add detailed notes. I’m grateful for your perspective.
I will play with this a bit and try some changes.

@okken
Copy link
Contributor Author

okken commented Jan 26, 2024

@flub I've made, I think, all suggested changes except the timeout location.
I went with pytest_runtest_makereport because that was the last point in pytest_runtest_protocol list of hooks that I could get hold of item, and thus session.
If I did the work right in pytest_runtest_protocol, I would end up with an extra test being run after the timeout.

Again, thanks so much for your feedback.
I've learned even more about pytest with this effort, and I appreciate it.

Copy link
Member

@flub flub left a comment

Choose a reason for hiding this comment

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

This should probably be added to the pytest_report_header as well. Or maybe only if it is set to keep the noise down

@@ -18,6 +19,7 @@


__all__ = ("is_debugging", "Settings")
suite_timeout_key = pytest.StashKey[float]()
Copy link
Member

Choose a reason for hiding this comment

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

TIL about pytest's Stash. As this is a module-level constant though could it be upper case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Uppercase is fine with me. I was going by convention of uses of StashKey in pytest-dev: https://github.com/search?q=org%3Apytest-dev%20pytest.StashKey&type=code
However, I like it uppercase. good call.

pytest_timeout.py Outdated Show resolved Hide resolved
pytest_timeout.py Outdated Show resolved Hide resolved
@@ -507,3 +534,12 @@ def dump_stacks(terminal):
thread_name = "<unknown>"
terminal.sep("~", title="Stack of %s (%s)" % (thread_name, thread_ident))
terminal.write("".join(traceback.format_stack(frame)))


def pytest_runtest_makereport(item, call):
Copy link
Member

Choose a reason for hiding this comment

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

this hook is apparently called many times for a single test, see CallInfo. Furthermore this hook is documented to "stop at first non-None result". are you sure this hook will always correctly interact with the hook that writes to the terminal or whatever output?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add a test for if call.when == "teardown" so it's only called once per test.
I can also add a hookimpl(tryfirst=True) to try to ensure that other hooks that might return non-None run after this one.
But really, I don't think there are guarantees

@flub
Copy link
Member

flub commented Jan 29, 2024

If I did the work right in pytest_runtest_protocol, I would end up with an extra test being run after the timeout.

I'm not sure I follow this yet. The docs I can find say:

If at any point session.shouldfail or session.shouldstop are set, the loop is terminated after the runtest protocol for the current item is finished.

Looking at pytest_runtestloop() in main.py seems to support what those docs are saying. It calls raise session.Failed(session.shouldfail) after the call to pytest_runtest_protocol()` thus cleanly terminating in between two items, without starting the next one or starting to call any reporting hooks about the next item.

What am I missing?
(I'm just reading code, not running so am probably missing something...)

@okken
Copy link
Contributor Author

okken commented Jan 31, 2024

This should probably be added to the pytest_report_header as well. Or maybe only if it is set to keep the noise down

good idea. I usually run with --no-header so it didn't occur to me. :)

@okken
Copy link
Contributor Author

okken commented Jan 31, 2024

If I did the work right in pytest_runtest_protocol, I would end up with an extra test being run after the timeout.

I'm not sure I follow this yet. The docs I can find say:

If at any point session.shouldfail or session.shouldstop are set, the loop is terminated after the runtest protocol for the current item is finished.

Looking at pytest_runtestloop() in main.py seems to support what those docs are saying. It calls raise session.Failed(session.shouldfail) after the call to pytest_runtest_protocol()` thus cleanly terminating in between two items, without starting the next one or starting to call any reporting hooks about the next item.

What am I missing? (I'm just reading code, not running so am probably missing something...)

What I'd really like is a hook right before pytest_runtest_protocol to be able to NOT run the next test if a timeout occurs.
But we don't have that.

Example:
10 tests that each take say 10 sec setup, 10 sec test, 10 sec teardown. So total of 30 sec per full test.
If we set suite timeout to 15 sec. we expect one test to complete, and not run a second test.

However, if we check for timeout in pytest_runtest_protocol we will see a timeout right when we start the second test, and we mark it to stop testing, then we will complete 2 tests, and run for 60 seconds.

If we check timeout in pytest_runtest_teardown, we still only check the timeout at the beginning of the teardown. It's not terrible there. But if someone has long teardowns, and the timeout is during the teardown, then we don't see the timeout until the next test is done.

So I picked the hook that can be checked after the teardown, and that really is only pytest_runtest_makereport


suite_timeout = config.getoption("--suite-timeout")
if suite_timeout:
timeout_header.append("suite timeout: %ss" % suite_timeout)
Copy link
Member

Choose a reason for hiding this comment

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

can you add some tests for this? I suspect you missed some newlines here.

@flub
Copy link
Member

flub commented Feb 6, 2024

What I'd really like is a hook right before pytest_runtest_protocol to be able to NOT run the next test if a timeout occurs. But we don't have that.

Example: 10 tests that each take say 10 sec setup, 10 sec test, 10 sec teardown. So total of 30 sec per full test. If we set suite timeout to 15 sec. we expect one test to complete, and not run a second test.

Right, let's make a test for exactly this scenario so we can play with this and make sure it behaves right. A test with two tests, and the suite-timeout is expected to trigger during the first test.

Because while I entirely agree with all your logic and desire to when to interrupt, I still don't understand what the thing is that stops the check from being added at the end of pytest_runtest_protocol. That way it'll always run the first test in a suite, but given the design you proposed that is what I would expect.

However, if we check for timeout in pytest_runtest_protocol we will see a timeout right when we start the second test, and we mark it to stop testing, then we will complete 2 tests, and run for 60 seconds.

If we check timeout in pytest_runtest_teardown, we still only check the timeout at the beginning of the teardown. It's not terrible there. But if someone has long teardowns, and the timeout is during the teardown, then we don't see the timeout until the next test is done.

I think when using the pytest_runtest_teardown hook you can also choose to run whatever code before or after the teardown? It is the hook that invokes the teardown, so it can do whatever is right? (not that I think we need to do something in this hook, but I'm trying to understand why you think this is an issue)

@okken
Copy link
Contributor Author

okken commented Feb 6, 2024

@flub Thanks for your patience with me and this PR.
I honestly didn't understand how @pytest.hookimpl(hookwrapper=True) and yield worked together to allow code to run at the end.

I've moved the suite timeout check to pytest_runtest_protocol. It was already being used by the plugin, so I tacked on the suite timeout check at the end of the existing implementation.

Question: Does it make sense to call this flag --session-timeout instead of --suite-timeout?

It just occurred to me that the rest of pytest generally uses the term "session" instead of "suite".
Both are used in the documentation, but I think the code only uses "session".

@okken okken changed the title add --suite-timeout add --session-timeout Feb 6, 2024
@okken
Copy link
Contributor Author

okken commented Feb 6, 2024

I'm going to assume you agree that --session-timeout makes more sense, so changing the PR.
Let me know if you like --suite-timeout better.

@flub
Copy link
Member

flub commented Feb 7, 2024

@flub Thanks for your patience with me and this PR. I honestly didn't understand how @pytest.hookimpl(hookwrapper=True) and yield worked together to allow code to run at the end.

Thanks for keeping at it! And bearing with my slow responses (I've been ill in between as well :( )

I've moved the suite timeout check to pytest_runtest_protocol. It was already being used by the plugin, so I tacked on the suite timeout check at the end of the existing implementation.

Glad to see this is working out! I think the changes are much simpler now.

Question: Does it make sense to call this flag --session-timeout instead of --suite-timeout?

It just occurred to me that the rest of pytest generally uses the term "session" instead of "suite". Both are used in the documentation, but I think the code only uses "session".

Yep, that's probably better.


I think there are a few more tests that would be really helpful to make sure this behaves right:

  • The test for the header output. We're concatenating strings now and we may want to really match the output to what we think it is.
  • I think the test described earlier that ensures a timeout occurs exactly in between two given tests would be good. This is what the feature promises so we should test it.

@okken
Copy link
Contributor Author

okken commented Feb 7, 2024

First, I added a test that would do the 2 test thing, with a slow fixture also, with this comment:

# 2 tests, each with 0.5 sec timeouts
# each using a fixture with 0.5 sec setup and 0.5 sec teardown
# So about 1.5 seconds per test, ~3 sec total,
# Therefore:
# A timeout of 1.25 sec should happen during the teardown of the first test
# and the second test should NOT be run

Then I realized that's also sufficient to test the timeout, so I just changed the one existing test to do that.

Second, I extended test_header to include session timeout, rather than add another header test just for session.

Copy link
Member

@flub flub left a comment

Choose a reason for hiding this comment

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

Looking good! Maybe time to start editing the README :)

# So about 1.5 seconds per test, ~3 sec total,
# Therefore:
# A timeout of 1.25 sec should happen during the teardown of the first test
# and the second test should NOT be run
Copy link
Member

Choose a reason for hiding this comment

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

i don't think this is asserted? not immediately sure how you would assert it, maybe with more verbose output and more output lines to match. or maybe there's something easier.

Copy link
Member

Choose a reason for hiding this comment

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

i don't think this is addressed yet?

# each using a fixture with 0.5 sec setup and 0.5 sec teardown
# So about 1.5 seconds per test, ~3 sec total,
# Therefore:
# A timeout of 1.25 sec should happen during the teardown of the first test
Copy link
Member

Choose a reason for hiding this comment

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

0.25 from an edge is too brittle. experience tells me you can't really work with sub-seconds times in this test suite, it gets flaky otherwise.

Make the total test duration 2s and the session timeout 1s?

Copy link
Member

Choose a reason for hiding this comment

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

All the numbers in this description are now wrong. maybe just describe that this is designed to timeout during the first test and ensure the first test still runs to completion but the 2nd test is not started?

@flub flub changed the base branch from master to main February 8, 2024 19:07
@okken
Copy link
Contributor Author

okken commented Feb 9, 2024

I'm really not sure where the right place to put the Session Timeout info in the docs, so I just:

  • added a section at bottom, right before the changelog
  • added a line to the 'unreleased' section of the changelog

When modifying the changelog I noticed the ini file setting for timeout, and figured session_timeout would also be good. So I added that to the code and added an inifile test for session_timeout

README.rst Outdated Show resolved Hide resolved
README.rst Outdated Show resolved Hide resolved
README.rst Outdated Show resolved Hide resolved
pytest_timeout.py Show resolved Hide resolved
# each using a fixture with 0.5 sec setup and 0.5 sec teardown
# So about 1.5 seconds per test, ~3 sec total,
# Therefore:
# A timeout of 1.25 sec should happen during the teardown of the first test
Copy link
Member

Choose a reason for hiding this comment

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

All the numbers in this description are now wrong. maybe just describe that this is designed to timeout during the first test and ensure the first test still runs to completion but the 2nd test is not started?

# So about 1.5 seconds per test, ~3 sec total,
# Therefore:
# A timeout of 1.25 sec should happen during the teardown of the first test
# and the second test should NOT be run
Copy link
Member

Choose a reason for hiding this comment

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

i don't think this is addressed yet?

test_pytest_timeout.py Show resolved Hide resolved
@flub
Copy link
Member

flub commented Feb 25, 2024

@okken ping, would be nice to see this merged. i was kind of waiting for this to publish a release. unless you think it'll take longer and i should release without waiting?

@okken
Copy link
Contributor Author

okken commented Feb 25, 2024

@flub thanks for the ping. I have so much going on and this slipped through the cracks.
I'll take a look right now.

@okken
Copy link
Contributor Author

okken commented Feb 25, 2024

I just pushed up some changes. All doc and/or comment changes

@okken
Copy link
Contributor Author

okken commented Feb 25, 2024

Feel free to modify any wording as you see fit to expedite the release

@okken
Copy link
Contributor Author

okken commented Feb 25, 2024

I'm pretty sure I addressed any concerns.

@flub flub merged commit 38c5f24 into pytest-dev:main Mar 7, 2024
14 checks passed
@flub
Copy link
Member

flub commented Mar 7, 2024

Thanks for the work and bearing with me!

romanz added a commit to trezor/trezor-firmware that referenced this pull request Feb 11, 2025
romanz added a commit to trezor/trezor-firmware that referenced this pull request Feb 11, 2025
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.

2 participants