Skip to content

Commit ceae88a

Browse files
authored
test(backup): Create backup version snapshot tests (#58173)
The goal for the export.json feature is to support two minor versions (since we're using CalVer, roughly equivalent to two months) of releases when importing. That is to say, if the latest release is 23.10.0, and the previous two releases were 23.9.0 and 23.8.0, then a self-hosted release running code from [email protected] or self-hosted@latest (aka nightly) should be able to successfully import JSON backup data generated by [email protected] and [email protected], but not necessarily [email protected]. To support this, we add a new file `test_releases.py` that provides a snapshot test of an "exhaustive" (aka, has at least one of every exportable model) export. Every time we cut a new release, we will add a new commit directly after the release commit that renames the `test_at_head` snapshot to `test_at_<RELEASED_VERSION>`, and creates a test case for that version (see `test_at_23_9_1` in this PR for one such example). Test cases older than the 2 version support window will simultaneously be removed. In the future, we plan to automate this "release test rotation" process via GitHub actions, but for now the ospo team will take care to do this manually for every release. Some other notes of interest: - Exports cannot be compared for equality using simple string comparisons, since they have special comparators for cases like passwords, dates that may or may not have been updated, etc (see comparators.py for greater detail). To accommodate this, we extend the `insta_snapshot` fixture with an extra argument, `inequality_comparator`, that allows a custom comparison lambda to be passed in for such cases. - The `test_at_23_9_1` snapshot was generated by taking an early version of this PR, cherry-picking it onto the `23.9.1` release tag, generating a new snapshot there, then copying the result back into the main commit. Closes getsentry/team-ospo#197
1 parent 9b30768 commit ceae88a

File tree

5 files changed

+2768
-17
lines changed

5 files changed

+2768
-17
lines changed

src/sentry/testutils/pytest/fixtures.py

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
import sys
1212
from concurrent.futures import ThreadPoolExecutor
1313
from datetime import datetime
14-
from typing import Optional
14+
from string import Template
15+
from typing import Optional, Tuple
1516

1617
import pytest
1718
import requests
@@ -331,9 +332,24 @@ def ignore_aliases(self, data):
331332
return True
332333

333334

335+
def read_snapshot_file(reference_file: str) -> Tuple[str, str]:
336+
with open(reference_file, encoding="utf-8") as f:
337+
match = _yaml_snap_re.match(f.read())
338+
if match is None:
339+
raise OSError()
340+
341+
header, refval = match.groups()
342+
return (header, refval)
343+
344+
334345
@pytest.fixture
335346
def insta_snapshot(request, log):
336-
def inner(output, reference_file=None, subname=None):
347+
def inner(
348+
output,
349+
reference_file=None,
350+
subname=None,
351+
inequality_comparator=lambda refval, output: refval != output,
352+
):
337353
if reference_file is None:
338354
name = request.node.name
339355
for c in UNSAFE_PATH_CHARS:
@@ -362,18 +378,15 @@ def inner(output, reference_file=None, subname=None):
362378
)
363379

364380
try:
365-
with open(reference_file, encoding="utf-8") as f:
366-
match = _yaml_snap_re.match(f.read())
367-
if match is None:
368-
raise OSError()
369-
_header, refval = match.groups()
381+
_, refval = read_snapshot_file(reference_file)
370382
except OSError:
371383
refval = ""
372384

373385
refval = refval.rstrip()
374386
output = output.rstrip()
387+
is_unequal = inequality_comparator(refval, output)
375388

376-
if _snapshot_writeback is not None and refval != output:
389+
if _snapshot_writeback is not None and is_unequal:
377390
if not os.path.isdir(os.path.dirname(reference_file)):
378391
os.makedirs(os.path.dirname(reference_file))
379392
source = os.path.realpath(str(request.node.fspath))
@@ -397,27 +410,47 @@ def inner(output, reference_file=None, subname=None):
397410
output,
398411
)
399412
)
400-
elif refval != output:
413+
elif is_unequal:
401414
__tracebackhide__ = True
402-
_print_insta_diff(reference_file, refval, output)
415+
if isinstance(is_unequal, str):
416+
_print_custom_insta_diff(reference_file, is_unequal)
417+
else:
418+
_print_insta_diff(reference_file, refval, output)
403419

404420
yield inner
405421

406422

423+
INSTA_DIFF_TEMPLATE = Template(
424+
"""~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
425+
Snapshot $reference_file changed!
426+
427+
428+
Re-run pytest with SENTRY_SNAPSHOTS_WRITEBACK=new and then use 'make review-python-snapshots' to review.
429+
430+
Or: Use SENTRY_SNAPSHOTS_WRITEBACK=1 to update snapshots directly.
431+
432+
433+
$diff_text
434+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
435+
"""
436+
)
437+
438+
407439
def _print_insta_diff(reference_file, a, b):
408440
__tracebackhide__ = True
409441
pytest.fail(
410-
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
411-
"Snapshot {} changed!\n\n"
412-
"Re-run pytest with SENTRY_SNAPSHOTS_WRITEBACK=new and then use 'make review-python-snapshots' to review.\n"
413-
"Or: Use SENTRY_SNAPSHOTS_WRITEBACK=1 to update snapshots directly.\n\n"
414-
"{}\n"
415-
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n".format(
416-
reference_file, "\n".join(difflib.unified_diff(a.splitlines(), b.splitlines()))
442+
INSTA_DIFF_TEMPLATE.substitute(
443+
reference_file=reference_file,
444+
diff_text="\n".join(difflib.unified_diff(a.splitlines(), b.splitlines())),
417445
)
418446
)
419447

420448

449+
def _print_custom_insta_diff(reference_file, diff_text):
450+
__tracebackhide__ = True
451+
pytest.fail(INSTA_DIFF_TEMPLATE.substitute(reference_file=reference_file, diff_text=diff_text))
452+
453+
421454
@pytest.fixture
422455
def call_snuba(settings):
423456
def inner(endpoint):

0 commit comments

Comments
 (0)