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

Minor improvements to feature descriptions and docstrings for clarity.
No behavioral changes - all scenarios produce the same assertions.
This commit is contained in:
2026-05-07 21:36:06 +00:00
parent b426e9dd95
commit 13659e04cb
2 changed files with 63 additions and 67 deletions
@@ -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