Last active
January 11, 2024 07:23
-
-
Save qstokkink/3da0625628a66337eece8a82fb85f5fa to your computer and use it in GitHub Desktop.
How pytest fixtures + redefined-outer-name (W0621) can be dangerous
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
In this example we have the `important_protection` fixture, | |
that SHOULD be called. It triggers "redefined-outer-name (W0621)" and | |
"Redefining name '...' from outer scope". | |
This example shows that two types of errors could have been prevented | |
by following the suggested Pylint definition, instead of ignoring the | |
warning. | |
## The suggested definition | |
Ignoring redefined-outer-name (W0621), we define the fixture as: | |
@pytest.fixture | |
def important_protection() -> Generator[int, None, None]: | |
The suggested definition according to https://pylint.readthedocs.io/en/ | |
latest/user_guide/messages/warning/redefined-outer-name.html is: | |
@pytest.fixture(name="important_protection") | |
def fixture_important_protection() -> Generator[int, None, None]: | |
## How this example fails to detect errors | |
When running the tests without the suggested definition, | |
the degraded tests (case 2a/2b) pass unexpectedly (wrong). | |
When running the tests with the suggested definition, | |
the degraded tests (case 2a/2b) fail as expected (correct). | |
""" | |
import os | |
from typing import Generator | |
import pytest | |
FAILURE_CODES = [1, 139] | |
@pytest.fixture | |
def important_protection() -> Generator[int, None, None]: | |
""" | |
Overwrite private key info and return an exit code to signal success. | |
""" | |
original_key = os.environ["PRIVATE_KEY"] | |
try: | |
os.environ["PRIVATE_KEY"] = "" # Remove the private key! | |
error_code = 0 | |
except Exception: | |
error_code = 1 | |
yield error_code | |
os.environ["PRIVATE_KEY"] = original_key | |
@pytest.fixture | |
def environment() -> None: | |
""" | |
Set the private key in our environment. Don't leak this! | |
""" | |
os.environ["PRIVATE_KEY"] = "pwnd" | |
def test_case_1(environment, important_protection) -> None: | |
""" | |
Case 1: In this first test everything is fine and works as expected. | |
""" | |
fixture_exit_code = important_protection | |
assert fixture_exit_code not in FAILURE_CODES | |
print(os.environ["PRIVATE_KEY"]) # Cleared, as expected | |
# Some time may pass until the above case 1 degrades into, or spawns, | |
# a test of type case 2. For example, after sloppy refactoring. | |
def test_case_2(environment) -> None: | |
""" | |
Case 2a: The programmer copy-pasted the safety check from case 1 | |
BUT forgot to add important_protection as a parameter. | |
Case 2b: Yet another programmer accidentally removed a parameter. | |
""" | |
fixture_exit_code = important_protection | |
assert fixture_exit_code not in FAILURE_CODES | |
print(os.environ["PRIVATE_KEY"]) # Uh, oh! Pwnd! | |
# The above is an example of an unnecessary and easily preventable | |
# risk to your code base. It could have been avoided by simply | |
# following the suggested definition of fixtures using a name. | |
# However, the consequences of may not always be as severe as leaking | |
# private keys. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment