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

#11616 disttrial worker test improvements #11617

Merged
merged 38 commits into from Sep 7, 2022

Conversation

exarkun
Copy link
Member

@exarkun exarkun commented Aug 25, 2022

Fixes #11616

These are handy for writing hamcrest-based tests for a lot of trial code.
The previous implementation used unnecessary fakes and encoded unnecessary
implementation details as well as unnecessarily duplicating in-memory
ITransport implementation available in other testing libraries in Twisted.

This is an almost-no-behavior-change commit that just makes the tests simpler
and exercises the code under test in a more realistic way.
Copy link
Member

@adiroiban adiroiban left a comment

Choose a reason for hiding this comment

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

Thanks for the refactoring.

This is not my style of writing tests... and I am not happy with matches_result..and this method is already in trunk.

Overall, there are a few nice cleanups for the tests and I think is best to have it merged.

src/twisted/trial/_dist/test/test_worker.py Outdated Show resolved Hide resolved
),
),
),
),
Copy link
Member

Choose a reason for hiding this comment

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

I am not a big fan of these generic assertions.

we can leave this as it is, since matched_result is already in trunk.
I find the name a bit too generic.

Something to my taste.

assert_than(result, is_testrun_failure(case, type=AssertionError, value=WorkerException("pyunit failure")

and all the extra isTuple, equal_to and the general structure is handled by the dedicated matcher.

From my experience, with these specialiez markers it was easier to refactor the internal structure of a code as you might only need to update the specialized marker, without updating each test.

Copy link
Member Author

Choose a reason for hiding this comment

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

This overlaps a bit with the comment above and I left a reply there.

frames=["file.py", "invalid code", "3"],
case = pyunitcases.PyUnitTest("test_error")
result = self.workerRunTest(case)
assert_that(
Copy link
Member

Choose a reason for hiding this comment

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

at least for me, this embedded structure is not easier to read ...
but maybe is just my bias for old-school xunit type assertions.

- self.assertEqual(failure.type, ValueError)
- self.assertEqual(failure.value, WorkerException("error"))
+ self.assertTestRunFailure(self.result, ValueError, WorkerException("error"))

For this type of assertion, you only focus on the end/high-level result, and ignore the internal structure of a test case.

It might be an advantage or disadvantage.

I usually write a test in which the internal structure is validated, and then all the other tests only check the high-level values, ignore the internal structure.

With the generic matches_result you need to remember the full structure of the test case result, each time you write the test.

Copy link
Member Author

Choose a reason for hiding this comment

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

at least for me, this embedded structure is not easier to read ...

I don't object to that. I think a lot of it is about familiarity and experience but it is also true that matcher-style tends to have fewer larger expressions rather than more smaller expressions.

Larger expressions in this style probably often take more practice to become comfortable reading. The benefit that you get is when multiple parts of the value are wrong, you get to learn about all of them after one test run instead of only the one related to the very first assertion.

testtools allows the best of both worlds with an expectThat function which can add a test failure and let the test keep running. But Twisted decided to go with pyhamcrest instead...

Anyhow, I'm open to improving the readability of these tests before merging.

Copy link
Member Author

Choose a reason for hiding this comment

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

  • self.assertTestRunFailure(self.result, ValueError, WorkerException("error"))

Looking at this idea specifically in more detail, I don't see a good way to make it work. The trouble is that assertTestRunFailure isn't reusable. test_runError, test_runFailure, test_runSkip, etc all look similar but they differ in slight ways.

If we rename the suggested function to assertTestRunError (since this case is about test suite "errors" rather than "failures") and we implemented it however (doesn't matter if we use hamcrest or not) then it is usable by exactly one test, test_runError. So we need another one for test_runFailure, and another for test_runSkip, etc.

In this case I don't think there's any benefit to abstracting the details away from the test. The purpose of the test method is exactly to encapsulate these details.

I can flatten the hamcrest code so it does repeated calls to assert_that instead of a single call with a complex matcher. I think that change makes it easier for folks without a lot of practice writing and reading these and it doesn't hurt runtime properties that much probably.

I don't think it makes sense to take the details of those assertions out of the test methods though. Or at least, I don't see a way that doesn't just lead to more work at development time and more work for future readers of the code.

Copy link
Member Author

Choose a reason for hiding this comment

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

I flattened the matcher in 6deb9f1 - I'm curious if you find that to be any better

Copy link
Member

Choose a reason for hiding this comment

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

thanks for the info and the updated.
Agree.
The flattened version looks easier to read, at a first read.

I am sure that after reading and looking at the code a few more times, the nested structures will look much easier to read.
I just wanted to leave a "raw" feedback.

I guess that other people might want to help contribute the Twisted, but then they found some code that they don't understand and then they give up.

I am searching for the formula for creating an open source project that is easy to contribute to, while still keeping high code quality standards.

Copy link
Member

@adiroiban adiroiban left a comment

Choose a reason for hiding this comment

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

I now see that this PR was not ready for review.


On a general note, I am -1 for using hypothesis in twisted, especially for general code.
It adds another thing that you must learn and be familiar with, before contributing to the project.

And for people not very familiar with the library, might be a reason to write invalid tests.

@exarkun exarkun changed the title 11616 test worker improvements #11616 disttrial worker test improvements Aug 26, 2022
@exarkun
Copy link
Member Author

exarkun commented Aug 26, 2022

needs-review

Copy link
Member

@adiroiban adiroiban left a comment

Choose a reason for hiding this comment

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

I think that this can be merged.


I am not a big fan of hamcrest , but I am not -1 on it.

Jean-Paul, if you think that testtool.assertThat provides a better dev experience in terms of writing matches and diffs, I think we can still consider a switch.


Using simple hypothesis strategies can make the code easier to use and encourage developers to write tests with explicit types.

I saw many bugs (in python 2.7) that were not discovered by a test, because the test was using something like received = 'binary-data' ... but that binary-data was a valid text that survived an accidental UTF-8 decode on it.
And in prod you have real binary data and then UTF-8 decoding fails.


I only did a quick review.

I don't know if anyone will have time to review it.
I think is best to have it merge, than keep this PR in the review queue for many weeks

frames=["file.py", "invalid code", "3"],
case = pyunitcases.PyUnitTest("test_error")
result = self.workerRunTest(case)
assert_that(
Copy link
Member

Choose a reason for hiding this comment

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

thanks for the info and the updated.
Agree.
The flattened version looks easier to read, at a first read.

I am sure that after reading and looking at the code a few more times, the nested structures will look much easier to read.
I just wanted to leave a "raw" feedback.

I guess that other people might want to help contribute the Twisted, but then they found some code that they don't understand and then they give up.

I am searching for the formula for creating an open source project that is easy to contribute to, while still keeping high code quality standards.

@@ -185,7 +185,7 @@ def addExpectedFailure(
"""
Add an expected failure to the reporter.
"""
_todo = Todo(todo)
_todo = Todo("<unknown>" if todo is None else todo)
Copy link
Member

Choose a reason for hiding this comment

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

I guess that this change was done to make it easier to remove Todo.__init__ while defining reason: str and not Optional[str]

I am happy to always have a string as the reason.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep. I also took a hint from the existing docstring for Todo which claimed reason was a string and said nothing about None.

This also removes the need for isTuple
They disagree with the inherited type which makes TestResult a very poorly
behaved subclass.  But, so it is.
@exarkun
Copy link
Member Author

exarkun commented Aug 29, 2022

thanks for the info and the updated.
Agree.
The flattened version looks easier to read, at a first read.

I am sure that after reading and looking at the code a few more times, the nested structures will look much easier to read.
I just wanted to leave a "raw" feedback.

I guess that other people might want to help contribute the Twisted, but then they found some code that they don't understand and then they give up.

I am searching for the formula for creating an open source project that is easy to contribute to, while still keeping high code quality standards.

Thanks for the reviews @adiroiban . I went ahead and flattened the hamcrest usage in the other test_worker.py tests to match the example I pushed earlier. I don't mind trying to keep these expressions more on the simple side. I think you make a good point about the consequences of such complex expressions on new contributors (or even existing contributors not familiar with this testing style).

I think having a richer matching library can also help us out and right now we're just starting to build one up for trial so we have some more work to do in that department. Also, I think "it's hard to describe what result I want" is a bit of a code smell for "what you want is not great" so as we find it is hard to write good matchers we might decide we want to introduce better data structures into the implementation to make it easier.

@exarkun
Copy link
Member Author

exarkun commented Aug 29, 2022

It looks like the conch manhole tests are somewhat reliably broken on Windows now. :/

exarkun and others added 20 commits September 1, 2022 08:16
the broken test doesn't try to write to sys.stdout so it doesn't matter (I hope!!)
the failure went away on CI.  will it still be gone in the next run?  who can say
@exarkun exarkun merged commit 2cb047b into twisted:trunk Sep 7, 2022
@exarkun exarkun deleted the 11616-test_worker-improvements branch September 7, 2022 01:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The test suite for worker result-reporting behavior involved in trial -j ... is unnecessarily complicated
3 participants