Skip to content

Commit ab6558b

Browse files
committed
fix(serializer): preserve trailing newlines in ambr serialization (#950)
BREAKING CHANGE: Trailing newlines are now preserved in amber serialization. This mostly affects serialization of custom repr implementations.
1 parent 79b44a4 commit ab6558b

File tree

6 files changed

+74
-3
lines changed

6 files changed

+74
-3
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ venv/
5050
ENV/
5151
env.bak/
5252
venv.bak/
53-
.vscode
5453
.idea
5554
.DS_Store
55+
56+
# IDE
57+
.vscode
58+
!.vscode/extensions.json
59+
!.vscode/settings.json

.vscode/extensions.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"recommendations": [
3+
"littlefoxteam.vscode-python-test-adapter",
4+
"ms-python.mypy-type-checker"
5+
]
6+
}

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"pythonTestExplorer.testFramework": "pytest"
3+
}

src/syrupy/extensions/amber/serializer.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import collections
22
import inspect
3-
import os
43
from collections import OrderedDict
54
from types import (
65
FunctionType,
@@ -124,6 +123,10 @@ def write_file(
124123
f.write(f"{cls._marker_prefix}{cls.Marker.Name}: {snapshot.name}\n")
125124
for data_line in snapshot_data.splitlines(keepends=True):
126125
f.write(cls.with_indent(data_line, 1))
126+
if data_line.endswith("\n"):
127+
# splitlines does not split on a terminal/trailing newline, so we must
128+
# account for that manually
129+
f.write(cls.with_indent("", 1))
127130
f.write(f"\n{cls._marker_prefix}{cls.Marker.Divider}\n")
128131

129132
@classmethod
@@ -168,7 +171,9 @@ def __read_file_with_markers(
168171
if test_name and snapshot_data:
169172
yield Snapshot(
170173
name=test_name,
171-
data=snapshot_data.rstrip(os.linesep),
174+
data=snapshot_data.removesuffix("\r\n")
175+
if snapshot_data.endswith("\r\n")
176+
else snapshot_data.removesuffix("\n"),
172177
tainted=tainted,
173178
)
174179
test_name = None
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# serializer version: 1
2+
# name: test_multiline_repr
3+
Line1
4+
Line2
5+
6+
Line3
7+
# ---
8+
# name: test_trailing_2_newlines_in_repr
9+
ReprWithNewline
10+
11+
12+
# ---
13+
# name: test_trailing_newline_in_repr
14+
ReprWithNewline
15+
16+
# ---
17+
# name: test_trailing_no_newline_in_repr
18+
ReprWithNewline
19+
# ---
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
class ReprWithNewline:
2+
def __init__(self, newlines: int = 1) -> None:
3+
self.newlines = newlines
4+
5+
def __repr__(self) -> str:
6+
newlines = "\n" * self.newlines
7+
return f"ReprWithNewline{newlines}"
8+
9+
10+
def test_trailing_no_newline_in_repr(snapshot):
11+
assert ReprWithNewline(0) == snapshot
12+
13+
14+
def test_trailing_newline_in_repr(snapshot):
15+
assert ReprWithNewline(1) == snapshot
16+
17+
18+
def test_trailing_2_newlines_in_repr(snapshot):
19+
assert ReprWithNewline(2) == snapshot
20+
21+
22+
class MultilineRepr:
23+
def __repr__(self) -> str:
24+
return "\n".join(
25+
[
26+
"Line1",
27+
"Line2\n", # extra newline
28+
"Line3 ", # with an extra space
29+
]
30+
)
31+
32+
33+
def test_multiline_repr(snapshot):
34+
assert MultilineRepr() == snapshot

0 commit comments

Comments
 (0)