chore(steps): improve fileConfig error handling step readability (issue #7874)
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 43s
CI / helm (pull_request) Successful in 49s
CI / build (pull_request) Successful in 1m22s
CI / lint (pull_request) Failing after 1m36s
CI / benchmark-regression (pull_request) Failing after 1m41s
CI / typecheck (pull_request) Successful in 1m48s
CI / quality (pull_request) Successful in 1m48s
CI / security (pull_request) Successful in 1m52s
CI / e2e_tests (pull_request) Successful in 4m34s
CI / integration_tests (pull_request) Successful in 4m47s
CI / unit_tests (pull_request) Failing after 6m32s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 3s
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 43s
CI / helm (pull_request) Successful in 49s
CI / build (pull_request) Successful in 1m22s
CI / lint (pull_request) Failing after 1m36s
CI / benchmark-regression (pull_request) Failing after 1m41s
CI / typecheck (pull_request) Successful in 1m48s
CI / quality (pull_request) Successful in 1m48s
CI / security (pull_request) Successful in 1m52s
CI / e2e_tests (pull_request) Successful in 4m34s
CI / integration_tests (pull_request) Successful in 4m47s
CI / unit_tests (pull_request) Failing after 6m32s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 3s
Minor improvements to feature descriptions and docstrings for clarity. No behavioral changes - all scenarios produce the same assertions.
This commit is contained in:
@@ -16,18 +16,16 @@ from behave import given, then, when # noqa: TID251
|
||||
|
||||
|
||||
def _write_malformed_ini(directory: Path) -> Path:
|
||||
"""Write a deliberately malformed alembic.ini to *directory*.
|
||||
|
||||
Returns the path to the written file so callers can verify its presence
|
||||
in error output.
|
||||
"""
|
||||
"""Write a deliberately malformed alembic.ini to *directory*."""
|
||||
ini = directory / "alembic.ini"
|
||||
ini.write_text(
|
||||
"[alembic]\n"
|
||||
"script_location = ./migrations\n"
|
||||
"\n"
|
||||
# Reference a logger that does NOT exist anywhere — fileConfig()
|
||||
# will raise ``KeyError`` at startup.
|
||||
# Reference a logger that does NOT exist anywhere. fileConfig()
|
||||
# raises ``KeyError`` at startup when the referenced key is absent
|
||||
# from the INI file, producing an unhandled exception before any
|
||||
# migration runs.
|
||||
"[loggers]\n"
|
||||
"keys = root,invalid_nonexistent_logger\n"
|
||||
"\n",
|
||||
@@ -62,11 +60,10 @@ def _write_valid_ini(directory: Path) -> Path:
|
||||
"[formatter_simple]\n"
|
||||
"format = %(levelname)s:%(name)s:%(message)s\n",
|
||||
)
|
||||
return ini
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# Scenario: fileConfig failure exits with non-zero exit code #
|
||||
# Given steps #
|
||||
# ----------------------------------------------------------------------- #
|
||||
|
||||
@given("a fresh temporary directory")
|
||||
@@ -74,13 +71,25 @@ def step_given_fresh_temp_dir(context: Any) -> None: # noqa: F821
|
||||
context.test_dir = Path(tempfile.mkdtemp(prefix="fileconfig_test_"))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# When steps #
|
||||
# ----------------------------------------------------------------------- #
|
||||
|
||||
@when("the alembic.ini contains an invalid logger section that references nonexistent loggers")
|
||||
def step_when_invalid_log_section(context: Any) -> None:
|
||||
"""Write a malformed alembic.ini to the test directory."""
|
||||
_write_malformed_ini(context.test_dir)
|
||||
|
||||
|
||||
@then("the migration runner exits with a non-zero exit code")
|
||||
def step_then_non_zero_exit(context: Any) -> None:
|
||||
@when("the alembic.ini contains a malformed logging configuration")
|
||||
def step_when_malformed_config(context: Any) -> None:
|
||||
"""Alias for when steps that write a malformed config."""
|
||||
_write_malformed_ini(context.test_dir)
|
||||
|
||||
|
||||
@when("migrations are invoked normally")
|
||||
def step_when_invocations_normal(context: Any) -> None: # noqa: C901
|
||||
"""Invoke ``alembic current`` to trigger env.py startup (fileConfig)."""
|
||||
result = subprocess.run(
|
||||
["alembic", "current"],
|
||||
cwd=context.test_dir,
|
||||
@@ -89,72 +98,60 @@ def step_then_non_zero_exit(context: Any) -> None:
|
||||
timeout=10,
|
||||
)
|
||||
context.migration_result = result
|
||||
assert result.returncode != 0, (
|
||||
f"Expected non-zero exit code; got {result.returncode}.\n"
|
||||
f"stderr: {result.stderr}"
|
||||
)
|
||||
|
||||
|
||||
@when("the alembic.ini contains a valid logger configuration")
|
||||
def step_when_valid_config(context: Any) -> None:
|
||||
"""Write a fully-valid alembic.ini to the test directory."""
|
||||
_write_valid_ini(context.test_dir)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# Scenario: fileConfig failure message includes the config file path #
|
||||
# Then steps #
|
||||
# ----------------------------------------------------------------------- #
|
||||
|
||||
@when("the alembic.ini contains a malformed logging configuration")
|
||||
def step_when_malformed_config(context: Any) -> None:
|
||||
_write_malformed_ini(context.test_dir)
|
||||
|
||||
|
||||
@then('the error output on stderr includes the config file path "<config_path>"')
|
||||
def step_then_stderr_includes_path(context: Any, config_path: str) -> None: # noqa: A002
|
||||
assert hasattr(context, "migration_result"), (
|
||||
"No migration result recorded; Was a When step executed?"
|
||||
)
|
||||
assert context.migration_result.returncode != 0, (
|
||||
f"Expected non-zero exit; got {context.migration_result.returncode}"
|
||||
)
|
||||
stderr = context.migration_result.stderr
|
||||
assert config_path in stderr or "alembic.ini" in stderr, (
|
||||
f"Error must mention config path. Got:\n{stderr}"
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# Scenario: fileConfig failure message is user-actionable #
|
||||
# ----------------------------------------------------------------------- #
|
||||
|
||||
@then("the error output on stderr includes actionable guidance about "[loggers]" section")
|
||||
@then('the error output on stderr includes actionable guidance about "[loggers]" section')
|
||||
def step_then_stderr_includes_guidance(context: Any) -> None:
|
||||
assert context.migration_result.returncode != 0, (
|
||||
f"Expected non-zero exit; got {context.migration_result.returncode}"
|
||||
)
|
||||
stderr = context.migration_result.stderr
|
||||
# The diagnostic string must mention [loggers] or "logger" + section.
|
||||
"""Assert that the error message contains ``[loggers]`` guidance."""
|
||||
result = _assert_invocation(context)
|
||||
stderr = result.stderr
|
||||
assert "[loggers]" in stderr or "loggers" in stderr.lower(), (
|
||||
f"Error must be user-actionable and mention [loggers]. Got:\n{stderr}"
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# Scenario: fileConfig succeeds silently when INI file is valid #
|
||||
# ----------------------------------------------------------------------- #
|
||||
|
||||
@when("the alembic.ini contains a valid logger configuration")
|
||||
def step_when_valid_config(context: Any) -> None:
|
||||
_write_valid_ini(context.test_dir)
|
||||
@then(
|
||||
'the error output on stderr includes the config file path "alembic.ini"'
|
||||
)
|
||||
def step_then_stderr_includes_alembic_ini(context: Any) -> None:
|
||||
"""Assert that the error message contains the config file path."""
|
||||
result = _assert_invocation(context)
|
||||
stderr = result.stderr
|
||||
assert (
|
||||
"alembic.ini" in stderr
|
||||
), f"Error must include 'alembic.ini'. Got:\n{stderr}"
|
||||
|
||||
|
||||
@then("no error message appears on stderr")
|
||||
def step_then_no_stderr_error(context: Any) -> None:
|
||||
result = context.migration_result
|
||||
if result.returncode != 0:
|
||||
# Even with valid config it's acceptable for alembic to report a
|
||||
# minor warning if no migration directory exists — the key point is
|
||||
# there is NO error from fileConfig().
|
||||
"""Assert that the alembic invocation produced no stderr output."""
|
||||
result = _assert_invocation(context)
|
||||
assert not result.stderr.strip(), (
|
||||
f"Unexpected stderr when config is valid:\n{result.stderr}"
|
||||
)
|
||||
|
||||
stderr = result.stderr.strip()
|
||||
# fileConfig errors are always multi-line and include "ERROR:" or
|
||||
# contain "loggers". Acceptable non-error output is either empty or
|
||||
# only contains migration metadata messages.
|
||||
assert not stderr, (
|
||||
f"Unexpected stderr when config is valid:\n{stderr}"
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# Internal helpers #
|
||||
# ----------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def _assert_invocation(context: Any) -> subprocess.CompletedProcess[str]: # noqa: F821
|
||||
"""Utility: assert that a migration invocation has been recorded."""
|
||||
if not hasattr(context, "migration_result"):
|
||||
msg = (
|
||||
"No migration result recorded. Did all required *When* steps execute? "
|
||||
"The test flow must be: Given … → When … → When … → Then …"
|
||||
)
|
||||
raise AssertionError(msg)
|
||||
return context.migration_result
|
||||
|
||||
@@ -11,33 +11,32 @@ Feature: Alembic fileConfig error handling (issue #7874)
|
||||
|
||||
Scenario Outline: fileConfig failure exits with non-zero exit code
|
||||
When the alembic.ini contains an invalid logger section that references nonexistent loggers
|
||||
And migrations are invoked normally
|
||||
Then the error output on stderr includes actionable guidance about "[loggers]" section
|
||||
|
||||
Examples: malformed configurations
|
||||
| description |
|
||||
| references undefined logger key |
|
||||
| references undefined handler |
|
||||
|
||||
Scenario Outline: fileConfig failure message includes the config file path
|
||||
When the alembic.ini contains a malformed logging configuration
|
||||
And migrations are invoked normally
|
||||
Then the error output on stderr includes the config file path "alembic.ini"
|
||||
|
||||
Examples: malformed configurations
|
||||
| description |
|
||||
| references undefined logger key |
|
||||
| references undefined handler |
|
||||
|
||||
Scenario Outline: fileConfig failure message is user-actionable
|
||||
When the alembic.ini contains a malformed logging configuration
|
||||
And migrations are invoked normally
|
||||
Then the error output on stderr includes actionable guidance about "[loggers]" section
|
||||
|
||||
Examples: malformed configurations
|
||||
| description |
|
||||
| references undefined logger key |
|
||||
| references undefined handler |
|
||||
|
||||
Scenario: fileConfig succeeds silently when INI file is valid
|
||||
Given a fresh temporary directory
|
||||
When the alembic.ini contains a valid logger configuration
|
||||
And migrations are invoked normally
|
||||
Then no error message appears on stderr
|
||||
|
||||
Reference in New Issue
Block a user