From c31b999c4249ef96a88d823dd8155bcf57f715c1 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:07:35 +0000 Subject: [PATCH 1/2] fix: render standard-test assertion messages correctly Co-Authored-By: bot_apk --- .../test/standard_tests/connector_base.py | 4 +- .../test/standard_tests/source_base.py | 4 +- unit_tests/test/test_assertion_messages.py | 66 +++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 unit_tests/test/test_assertion_messages.py diff --git a/airbyte_cdk/test/standard_tests/connector_base.py b/airbyte_cdk/test/standard_tests/connector_base.py index dac656cf48..1c9bf24e10 100644 --- a/airbyte_cdk/test/standard_tests/connector_base.py +++ b/airbyte_cdk/test/standard_tests/connector_base.py @@ -114,6 +114,6 @@ def test_check( connector_root=self.get_connector_root_dir(), ) assert len(result.connection_status_messages) == 1, ( - f"Expected exactly one CONNECTION_STATUS message. " - "Got: {result.connection_status_messages!s}" + "Expected exactly one CONNECTION_STATUS message. " + f"Got: {result.connection_status_messages!s}" ) diff --git a/airbyte_cdk/test/standard_tests/source_base.py b/airbyte_cdk/test/standard_tests/source_base.py index faecb03c7b..ce9b861726 100644 --- a/airbyte_cdk/test/standard_tests/source_base.py +++ b/airbyte_cdk/test/standard_tests/source_base.py @@ -96,8 +96,8 @@ def test_spec(self) -> None: # If an error occurs, it will be raised above. assert len(result.spec_messages) == 1, ( - "Expected exactly 1 spec message but got {len(result.spec_messages)}", - result.errors, + f"Expected exactly 1 spec message but got {len(result.spec_messages)}. " + f"Errors: {result.errors!s}" ) def test_basic_read( diff --git a/unit_tests/test/test_assertion_messages.py b/unit_tests/test/test_assertion_messages.py new file mode 100644 index 0000000000..917e40579f --- /dev/null +++ b/unit_tests/test/test_assertion_messages.py @@ -0,0 +1,66 @@ +# Copyright (c) 2025 Airbyte, Inc., all rights reserved. +"""Guards against assertion messages that never render as intended. + +Two failure modes are covered: + +- A string message containing a `{placeholder}` but missing the `f` prefix, which + prints the literal braces instead of the interpolated value. +- A tuple message, which is always truthy and prints as a tuple repr. +""" + +from __future__ import annotations + +import ast +import re +from pathlib import Path + +import pytest + +import airbyte_cdk.test.standard_tests as standard_tests + +_PLACEHOLDER_PATTERN = re.compile(r"\{[A-Za-z_][A-Za-z0-9_.\[\]()!:'\"\s]*\}") +_STANDARD_TESTS_DIR = Path(standard_tests.__file__).parent + + +def _uninterpolated_placeholders(node: ast.expr) -> list[str]: + """Return placeholder-looking substrings that will print literally.""" + if isinstance(node, ast.Constant) and isinstance(node.value, str): + return _PLACEHOLDER_PATTERN.findall(node.value) + + if isinstance(node, ast.JoinedStr): + # Implicit concatenation of f-string and plain-string fragments collapses + # into a single JoinedStr; plain fragments survive as Constant values. + found: list[str] = [] + for value in node.values: + found.extend(_uninterpolated_placeholders(value)) + return found + + return [] + + +@pytest.mark.parametrize( + "source_file", + sorted(_STANDARD_TESTS_DIR.rglob("*.py")), + ids=lambda path: path.name, +) +def test_assert_messages_are_renderable(source_file: Path) -> None: + tree = ast.parse(source_file.read_text(), filename=str(source_file)) + problems: list[str] = [] + for node in ast.walk(tree): + if not isinstance(node, ast.Assert) or node.msg is None: + continue + + if isinstance(node.msg, ast.Tuple): + problems.append( + f"{source_file.name}:{node.msg.lineno}: assert message is a tuple, " + "which is always truthy and prints as a tuple repr" + ) + continue + + for placeholder in _uninterpolated_placeholders(node.msg): + problems.append( + f"{source_file.name}:{node.msg.lineno}: assert message contains " + f"{placeholder} but the fragment is missing the `f` prefix" + ) + + assert not problems, "\n".join(problems) From d21b65ece13b113a97b0e61b3fd912657d63c5c3 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:14:08 +0000 Subject: [PATCH 2/2] test: detect `+`-concatenated assert messages and report assert lineno Co-Authored-By: bot_apk --- unit_tests/test/test_assertion_messages.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/unit_tests/test/test_assertion_messages.py b/unit_tests/test/test_assertion_messages.py index 917e40579f..3c9e1b24c7 100644 --- a/unit_tests/test/test_assertion_messages.py +++ b/unit_tests/test/test_assertion_messages.py @@ -35,6 +35,10 @@ def _uninterpolated_placeholders(node: ast.expr) -> list[str]: found.extend(_uninterpolated_placeholders(value)) return found + if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add): + # Messages built with `+` concatenation. + return _uninterpolated_placeholders(node.left) + _uninterpolated_placeholders(node.right) + return [] @@ -52,14 +56,14 @@ def test_assert_messages_are_renderable(source_file: Path) -> None: if isinstance(node.msg, ast.Tuple): problems.append( - f"{source_file.name}:{node.msg.lineno}: assert message is a tuple, " + f"{source_file.name}:{node.lineno}: assert message is a tuple, " "which is always truthy and prints as a tuple repr" ) continue for placeholder in _uninterpolated_placeholders(node.msg): problems.append( - f"{source_file.name}:{node.msg.lineno}: assert message contains " + f"{source_file.name}:{node.lineno}: assert message contains " f"{placeholder} but the fragment is missing the `f` prefix" )