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

chore: Stop running mypy on tests #5586

Closed
wants to merge 1 commit into from

Conversation

TheRealFalcon
Copy link
Member

This should be ready to review but in draft state as it may be controversial.

Proposed Commit Message

chore: Stop running mypy on tests

Often times we need to test error conditions or invalid input. That
necessitates doing things that mypy considers unsound. While mypy
can also provide some benefit to testing, the cost of silencing errors
isn't worth the benefit.

Additional Context

Test Steps

Merge type

  • Squash merge using "Proposed Commit Message"
  • Rebase and merge unique commits. Requires commit messages per-commit each referencing the pull request number (#<PR_NUM>)

Often times we need to test error conditions or invalid input. That
necessitates doing things that mypy considers unsound. While mypy
can also provide some benefit to testing, the cost of silencing errors
isn't worth the benefit.
@a-dubs
Copy link
Collaborator

a-dubs commented Aug 6, 2024

after having to do weird things for the ibm datasources tests, +1

Copy link
Collaborator

@blackboxsw blackboxsw left a comment

Choose a reason for hiding this comment

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

It does sometimes feel a bit like busy-work to be updating typing on unittests tests.

I see a bit more value in still running mypy on the integration tests which can be exposed to using pycloudlib and other library dependencies where we may more likely misuse some dependencies.

What do you think about the middle ground of dropping mypy from tests/unittests but still including tests/integration_tests subdir?

Copy link
Member

@holmanb holmanb left a comment

Choose a reason for hiding this comment

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

I commiserate with the complaints about mypy and Python typing that others have made.

While mypy can also provide some benefit to testing, the cost of silencing errors isn't worth the benefit. (@TheRealFalcon)

after having to do weird things for the ibm datasources tests, +1 (@a-dubs)

It does sometimes feel a bit like busy-work to be updating typing on unittests tests. (@blackboxsw)

It can feel tedious and slow, and certainly provides more benefit to runtime code than to tests. That said, I'd like to highlight the value that I see from tests.

When when working out Python types on pre-existing tests, I sometimes find that we are testing code paths which cannot run at runtime. These discoveries seem to fall into two categories:

  1. the original authors were testing additional code paths out of fear that certain types were possible that mypy can now tell us isn't going to happen
  2. the test writers incorrectly assumed the types of the runtime code

Mypy assists us by increasing our confidence that the code that we are testing is receiving inputs that they will receive at runtime - so I think that dropping mypy will actually decrease the quality of our tests over time. Due to the nature of dynamic typing, even if we had 100% coverage, the actual coverage of types that cloud-init sees at runtime could be much lower than 100%.

@TheRealFalcon
Copy link
Member Author

@holmanb , you make some good points, but contrast that with this sort of thing:

def parse_json(my_json) -> Optional[Dict]:
    ...

def read_file(filename, decode) -> Union[str, bytes]:
    ...

def test_json():
    x = parse_json('{"a": 1}')
    assert x['a'] == 1

def test_file():
    x = read_file('file.txt', True)
    assert x == 'content'

We either need to sprinkle # type: ignore everywhere, or we'll wind up doing stuff like:

def test_json():
    x = parse_json('{"a": 1}')
    if x is None:
        pytest.fail("oh noes")
    assert x['a'] == 1

I suspect we'll see more of the latter as we'll want to "properly" silence errors rather than just ignoring them. To me this is worse than silencing errors because we're needlessly bloating tests testing for conditions that are implicitly part of the assertion that already exists. Given how much of the cloud-init code base returns Optionals and Unions, I think this will be too common of a problem.

@holmanb
Copy link
Member

holmanb commented Aug 6, 2024

@holmanb , you make some good points, but contrast that with this sort of thing:

...

I suspect we'll see more of the latter as we'll want to "properly" silence errors rather than just ignoring them. To me this is worse than silencing errors because we're needlessly bloating tests testing for conditions that are implicitly part of the assertion that already exists. Given how much of the cloud-init code base returns Optionals and Unions, I think this will be too common of a problem.

For optionals it usually suffices to toss in an or <blah> in the assignment or start the assertion with a check for truthiness.

def parse_json(my_json) -> Optional[Dict]:
    ...

def test_json1():
    x = parse_json('{"a": 1}')
    assert x and x['a'] == 1

def test_json2():
    x = parse_json('{"a": 1}') or {}
    assert x['a'] == 1

Neither of these feels particularly burdensome to me.

The unions are more annoying because of mypy's strict union checking. Some projects deal with this by avoiding Union types altogether.

The workaround that typeshed uses is to use object or Any instead of Union. While this is less beneficial for type-based editor-completion, it avoids the isinstance() requirement and allows mypy to still do dynamic type checking. I'm not a fan of this approach because it sacrifices both the quality of type checking and completion - though it has merit especially for a library that has widely consumed types.

I try to avoid Union return types when writing new code because Unions make reasoning about usage harder - in addition to the mypy-driven boilderplate that results from it. We've made some changes to commonly used utilities in the cloud-init codebase for the same reason. Personally I like the little reminder that mypy provides about Union types because it feels like encouragement to write (or refactor) code to be easy to understand. I recognize, however, that this is probably not a commonly held perspective.

@holmanb
Copy link
Member

holmanb commented Aug 6, 2024

@aciba90 You've done a fair bit of typing work in cloud-init. I'd be curious to know your thoughts on the topic.

@a-dubs
Copy link
Collaborator

a-dubs commented Aug 7, 2024

@holmanb when he's right

⣀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⠀⠀⢀⠀⠀⠀⠀⠀⠐⠀⢢⠆⠐⠁⠀⠀⠀⠀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣶⠀⠀⡀⢀⣤⠀⠀⠐⢂⠀⠀⢈⣀⠀⠀⠀⢀⣠⠖⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⣿⣿⣿⣿⡇⢳⠠⠀⢄⡐⢂⣤⡄⠀⣂⠜⣿⣧⠚⠒⠒⣀⠀⠤⠐⡚⠀⠍⠀⠂⠉⢠⡶⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣀⣠⣤⣤⣀⣀⣀⣀⣀⣀
⣿⣿⣿⣿⣿⣇⠸⡇⠷⠼⠄⠐⠉⢁⣉⡠⠤⠼⣗⠂⠉⠁⠀⠀⢀⣀⣠⣤⠤⠤⢤⡶⠿⠶⣤⣤⣤⣄⣀⣀⣀⣀⣀⣀⣠⣤⣶⡾⠿⠟⠛⠉⠉⠉⠉⢉⠉⠉⠉⢭⣉⡛⠻⢿⣿
⣿⣿⣿⣿⣿⣿⣀⡧⠤⠔⢒⣊⠉⢤⠂⠀⠀⠉⢢⠀⠀⠀⠀⣿⣿⡟⠉⠀⠀⢠⡞⠀⣴⣶⣿⣶⣭⡻⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⢀⣀⣁⣀⠀⠀⠀⠙⠈⠀⠙
⣿⣿⣿⣿⣿⣿⡟⠒⠒⠉⣁⣀⣔⡇⠀⠀⠀⠀⠘⡣⡀⠀⠀⠙⣿⠀⠀⠀⢀⡟⠀⣸⢿⣿⣿⣿⣿⣿⣿⣿⡟⠛⠛⢿⣿⠀⠀⠀⠀⣀⣐⢛⣲⣴⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀
⣿⣿⣿⣿⣿⣿⣇⠀⢠⠀⢖⣦⣼⠁⠀⠀⠀⠀⠀⡿⠒⠂⠠⢤⣿⡄⠀⠠⣾⡇⠀⣿⡘⢿⣧⣯⣿⢿⣿⠏⠆⠀⠀⠈⣿⣇⠀⠀⠀⣿⣿⣿⣿⠏⠘⣿⣿⣿⣿⡆⠀⠀⠀⠀⢠
⣿⣿⣿⣿⣿⣿⣿⡟⣻⣧⣸⣿⣿⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠛⣧⠀⣴⣿⡇⠀⠻⠿⢿⣿⡿⠁⣺⡟⠀⠀⠀⠀⠀⢘⣿⣆⢀⡄⣿⠘⠻⣿⣷⣤⣙⣿⣿⣿⡿⠄⣀⢂⣠⣾
⣿⣿⣿⣿⣿⣿⣿⣧⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⡙⣷⣿⡟⠁⠀⠀⠀⠀⠋⣠⡾⠋⠀⠀⠀⠀⠀⠀⠀⠈⢿⣮⡝⣿⣿⡇⠈⠛⠛⠛⠛⠛⠋⠐⠀⣸⣶⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣻⣿⡿⡟⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⡼⠀⠙⠛⠲⠶⠦⠶⢶⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢝⡿⣿⣿⣿⣧⡄⠀⠀⠀⠀⠀⠀⣀⣠⣾⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠷⣦⣿⠀⠀⠀⠀⠀⠀⠘⠀⠀⠀⠀⣼⠃⠀⠀⠀⠀⠀⠀⢰⠏⠀⢀⣠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣿⠛⠻⣿⣛⠿⠟⠛⠛⠛⠋⠉⠀⠁
⣿⠟⠋⠉⠙⠻⡟⠁⠀⠀⠀⠀⠙⡆⠀⠀⠀⠀⠀⣸⣤⣤⣤⠤⣇⠀⠀⠀⠀⠀⠀⣰⠃⢀⣴⣿⣿⣧⣤⣤⣶⣦⣀⠀⠳⣄⠀⠀⠀⠀⠀⢹⡆⠀⠈⢻⣿⡄⠀⠀⠀⠀⠀⠀⠀
⡇⠀⠀⠀⠀⠀⠸⠀⠀⠀⠀⠀⠀⠂⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠈⣀⠀⠀⠀⢀⡼⠁⠀⣾⣿⣿⣿⠛⠿⢿⣿⣿⣿⣷⣤⣼⣄⠀⠀⠀⠀⠈⢿⣆⠀⠀⠹⣧⠀⠀⠀⠀⠀⠀⠀
⢁⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⡰⠊⠁⠀⢠⠏⠀⠀⠰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠈⢿⣷⡀⠀⠘⠀⠀⠀⠀⠀⠀⠀
⢸⠀⠀⠀⠀⠀⠀⢇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⠀⠀⠀⢀⠏⠀⠀⠀⠎⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⠀⠀⠈⢿⣿⡦⠀⠀⠀⠀⠀⠀⠀⠀
⢸⡀⢀⠀⠐⠒⠒⠚⠁⠒⠀⠉⠈⠛⠢⢀⠀⠀⠀⢸⠀⠀⠀⣿⠀⠀⠀⠰⠀⠀⠀⠀⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀
⠊⠱⣾⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⠦⢬⡆⠀⠀⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⣆⠀⠹⠛⠿⣿⣿⣿⠿⠛⣇⠀⠀⠀⠀⠀⠀⠀⢀⠞⠀⣼⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠙⢷⣄⠀⠀⠀⠀⠀⠁⠐⠀⠀⠀⠀⠀⠀⠀⠀⠙⡀⠀⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡄⠐⢠⠤⠿⠛⠳⢧⣌⡹⡀⠀⠀⠀⠀⠀⠀⠈⢲⣤⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠉⠓⠶⠦⢤⣬⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⡅⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⣤⣾⠀⠀⠀⠀⠀⠀⠀⣻⣀⣠⠀⠀⠀⠀⣠⣶⣿⣿⣿⡃⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠚⢿⡳⠀⠀⠀⠀⠀⠀⠀⠀⡏⠀⠘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣿⣿⣆⠀⠀⣨⠀⠀⢠⣿⣿⠟⠛⠓⢶⣾⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⢰⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⠿⢿⡿⠿⠿⠛⠁⠀⠀⠀⠘⠿⠟⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⡺⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

@TheRealFalcon
Copy link
Member Author

Good points @holmanb . I'll close this.

@TheRealFalcon TheRealFalcon deleted the mypy-is-a-bully branch August 7, 2024 15:53
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.

4 participants