Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve values #56

Merged
merged 17 commits into from
Apr 9, 2024
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
155 changes: 150 additions & 5 deletions docs/eq_snapshot.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,172 @@
## General

A snapshot can be compared against any value with `==`.
The value gets recorded if the snapshot is undefined (`snapshot()`)
A snapshot can be compared with any value using `==`.
The value can be recorded with `--inline-snapshot=create` if the snapshot is empty.
The value can later be changed with `--inline-snapshot=fix` if the value the snapshot is compared with has changed.

Example:

=== "original code"
<!-- inline-snapshot: outcome-passed=1 outcome-errors=1 -->
```python
def test_something():
assert 2 + 2 == snapshot()
assert 2 + 4 == snapshot()
```

=== "--inline-snapshot=create"
<!-- inline-snapshot: create -->
```python
def test_something():
assert 2 + 2 == snapshot(4)
assert 2 + 4 == snapshot(6)
```

=== "value changed"
<!-- inline-snapshot: outcome-failed=1 -->
```python
def test_something():
assert 2 + 40 == snapshot(4)
```

=== "--inline-snapshot=fix"
<!-- inline-snapshot: fix -->
```python
def test_something():
assert 2 + 40 == snapshot(42)
```


## dirty-equals

It might be, that larger snapshots with many lists and dictionaries contain some values which change frequently and are not relevant for the test.
They might be part of larger data structures and be difficult to normalize.

Example:

=== "original code"
<!-- inline-snapshot: outcome-passed=1 outcome-errors=1 -->
```python
from inline_snapshot import snapshot
import datetime


def get_data():
return {
"date": datetime.datetime.utcnow(),
"payload": "some data",
}


def test_function():
assert get_data() == snapshot()
```

=== "--inline-snapshot=create"
<!-- inline-snapshot: create -->
```python
from inline_snapshot import snapshot
import datetime


def get_data():
return {
"date": datetime.datetime.utcnow(),
"payload": "some data",
}


def test_function():
assert get_data() == snapshot(
{"date": datetime.datetime(2024, 3, 14, 0, 0), "payload": "some data"}
)
```

inline-snapshot tries to change only the values that it needs to change in order to pass the equality comparison.
This allows to replace parts of the snapshot with [dirty-equals](https://dirty-equals.helpmanual.io/latest/) expressions.
This expressions are preserved as long as the `==` comparison with them is `True`.

Example:

=== "using IsDatetime()"
<!-- inline-snapshot: outcome-passed=1 -->
```python
from inline_snapshot import snapshot
from dirty_equals import IsDatetime
import datetime


def get_data():
return {
"date": datetime.datetime.utcnow(),
"payload": "some data",
}


def test_function():
assert get_data() == snapshot(
{
"date": IsDatetime(),
"payload": "some data",
}
)
```

=== "changed payload"
<!-- inline-snapshot: outcome-failed=1 -->
```python
from inline_snapshot import snapshot
from dirty_equals import IsDatetime
import datetime


def get_data():
return {
"date": datetime.datetime.utcnow(),
"payload": "data changed for some good reason",
}


def test_function():
assert get_data() == snapshot(
{
"date": IsDatetime(),
"payload": "some data",
}
)
```


=== "--inline-snapshot=fix"
<!-- inline-snapshot: fix -->
```python
from inline_snapshot import snapshot
from dirty_equals import IsDatetime
import datetime


def get_data():
return {
"date": datetime.datetime.utcnow(),
"payload": "data changed for some good reason",
}


def test_function():
assert get_data() == snapshot(
{
"date": IsDatetime(),
"payload": "data changed for some good reason",
}
)
```

!!! note
The current implementation looks only into lists, dictionaries and tuples and not into the representation of other data structures.

## pytest options

It interacts with the following `--inline-snapshot` flags:

- `create` create a new value if the snapshot value is undefined.
- `fix` record the new value and store it in the source code if it is different from the current one.
- `fix` record the value parts and store them in the source code if it is different from the current one.
- `update` update parts of the value if their representation has changed.
Parts which are replaced with dirty-equals expressions are not updated.
64 changes: 64 additions & 0 deletions inline_snapshot/_align.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from itertools import groupby


def align(seq_a, seq_b) -> str:

matrix: list = [[(0, "e")] + [(0, "i")] * len(seq_b)]

for a in seq_a:
last = matrix[-1]

new_line = [(0, "d")]
for bi, b in enumerate(seq_b, 1):
la, lc, lb = new_line[-1], last[bi - 1], last[bi]
values = [(la[0], "i"), (lb[0], "d")]
if a == b:
values.append((lc[0] + 1, "m"))

new_line.append(max(values))
matrix.append(new_line)

# backtrack

ai = len(seq_a)
bi = len(seq_b)
d = ""
track = ""

while d != "e":
_, d = matrix[ai][bi]
if d == "m":
ai -= 1
bi -= 1
elif d == "i":
bi -= 1
elif d == "d":
ai -= 1
if d != "e":
track += d

return track[::-1]


def add_x(track):
"""Replaces an `id` with the same number of insertions and deletions with
x."""
groups = [(c, len(list(v))) for c, v in groupby(track)]
i = 0
result = ""
while i < len(groups):
g = groups[i]
if i == len(groups) - 1:
result += g[0] * g[1]
break

ng = groups[i + 1]
if g[0] == "d" and ng[0] == "i" and g[1] == ng[1]:
result += "x" * g[1]
i += 1
else:
result += g[0] * g[1]

i += 1

return result
Loading
Loading