Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
- uses: actions/checkout@v6
- run: echo "${{ matrix.python-version }}" > .python-version
- uses: jdx/mise-action@v3
- run: mise install # retry for network issues
- run: ./build_and_test.sh
shell: bash
- name: Publish Test Report
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test_min.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
- uses: actions/checkout@v6
- run: echo "${{ matrix.python-version }}" > .python-version
- uses: jdx/mise-action@v3
- run: mise install # retry for network issues
- run: python ./generate_prod_min.py
- run: ./build_and_test.sh
shell: bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v3
- run: mise install # retry for network issues
Copy link

Choose a reason for hiding this comment

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

question: The retry step always runs rather than conditionally on failure.

As written, this will always run mise install twice—once via jdx/mise-action and once in this step—rather than only retrying on failure. If you want an actual retry, consider gating this on a previous step’s failure with if: ${{ failure() }} or using a dedicated retry wrapper, so you avoid extra work when the initial install succeeds.

- run: ./tidy_code.sh
- run: ./run_mdsnippets.sh
shell: bash
Expand Down
33 changes: 31 additions & 2 deletions approvaltests/namer/inline_python_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,36 @@ def escape_backslashes(text: str) -> str:
return text.replace("\\", "\\\\")


def escape_control_characters(text: str) -> str:
if not text:
return text

translation_table = str.maketrans(
{
"\x00": "\\x00", # Null
"\x08": "\\b", # Backspace
"\x0b": "\\v", # Vertical tab
"\x0c": "\\f", # Form feed
"\x1b": "\\x1b", # Escape
"\x7f": "\\x7f", # Delete
"\u00a0": "\\u00a0", # Non-breaking space
"\u200b": "\\u200b", # Zero-width space
"\u200c": "\\u200c", # Zero-width non-joiner
"\u200d": "\\u200d", # Zero-width joiner
"\u200e": "\\u200e", # Left-to-right mark
"\u200f": "\\u200f", # Right-to-left mark
"\u202c": "\\u202c", # Pop directional formatting
"\u202d": "\\u202d", # Left-to-right override
"\u202e": "\\u202e", # Right-to-left override
"\u2028": "\\u2028", # Line separator
"\u2029": "\\u2029", # Paragraph separator
"\ufeff": "\\ufeff", # Byte order mark
}
)

return text.translate(translation_table)


class InlinePythonReporter(Reporter):
def __init__(
self,
Expand All @@ -64,10 +94,9 @@ def create_received_file(self, received_path: str, test_source_file: str) -> str

original_received_text = Path(received_path).read_text(encoding="utf-8")[:-1]
received_text = original_received_text + self.footer
# Handle preceding whitespace consistently across all lines.
received_text = handle_preceeding_whitespace(received_text)
# Escape backslashes to avoid accidental escape sequences in the docstring.
received_text = escape_backslashes(received_text)
received_text = escape_control_characters(received_text)
Comment on lines 98 to +99
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Consider the ordering of backslash vs control-character escaping to keep behavior consistent.

Currently, escape_backslashes doubles only the original backslashes; the backslashes introduced later by escape_control_characters (e.g. in "\x00") remain single. This produces mixed single/double backslashes and may leave control-character sequences still interpretable as escapes in docstrings. To keep behavior consistent and avoid accidental escape sequences, consider running escape_control_characters before escape_backslashes, or doing a final escape_backslashes after both steps so all backslashes are handled uniformly.

method_name = StackFrameNamer.get_test_frame().function
if detect_trailing_whitespace(original_received_text):
trailing_comment = " # Warning: Editors may remove trailing spaces, causing this test to fail"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,31 @@ How inline approvals handles whitespace in docstrings and verified strings, incl
4. Escape characters
Backslashes are special in docstrings. So the reporter will escape them with another backslash.

5. Control characters
If a control character is in the string, we want to represent it explicitly. For example `\u200f`.

The control characters we care about are:
- `\u200f` (right-to-left marker)
- `\0` (null)
- Vertical tab (\v, 0x0B)
- Form feed (\f, 0x0C)
- Escape (\x1b, 0x1B)
- Right-to-Left Override (RLO, U+202E) - Already being tested
- Left-to-Right Override (LRO, U+202D)
- Right-to-Left Mark (RLM, U+200F)
- Left-to-Right Mark (LRM, U+200E)
- Pop Directional Formatting (PDF, U+202C)
- Zero-Width Space (ZWSP, U+200B)
- Zero-Width Non-Joiner (ZWNJ, U+200C)
- Zero-Width Joiner (ZWJ, U+200D)
- Non-breaking space (NBSP, U+00A0)
- Byte Order Mark (BOM, U+FEFF)
- Backspace (\b, 0x08)
- Delete (DEL, 0x7F)
- Line Separator (U+2028)
- Paragraph Separator (U+2029)


## Special markers

If the first line is `<<approvaltests:preserve-leading-whitespace>>`, then remove it before verifying. This is only true if it's the first line.
Expand Down
Loading