From c8bcf8cf0f346b05bc1b4c03388bf999b9c222f7 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Tue, 27 Oct 2020 11:47:38 -0400 Subject: [PATCH 01/16] test: match structure to source --- tests/{ => integration}/test_integration_custom.py | 0 tests/{ => integration}/test_integration_default.py | 0 .../test_integration_pytest_extension.py | 0 .../test_integration_single_file.py | 0 tests/syrupy/__init__.py | 0 tests/syrupy/extensions/__init__.py | 0 .../__snapshots__/test_extension_base.ambr | 0 .../__snapshots__/test_extension_image.ambr | 0 .../test_extension_image/test_image.png | Bin .../test_extension_image/test_image_vector.svg | 0 .../test_multiple_snapshot_extensions.2.png | Bin .../test_multiple_snapshot_extensions.3.svg | 0 .../test_multiple_snapshot_extensions.svg | 0 .../TestClass.test_class_method_name.raw | 0 .../TestClass.test_class_method_parametrizedx.raw | 0 .../TestClass.test_class_method_parametrizedy.raw | 0 .../TestClass.test_class_method_parametrizedz.raw | 0 .../amber}/__snapshots__/test_extension_amber.ambr | 0 .../amber}/__snapshots__/test_filters.ambr | 0 .../amber}/__snapshots__/test_matchers.ambr | 0 .../extensions/amber}/test_extension_amber.py | 0 tests/{ => syrupy/extensions/amber}/test_filters.py | 0 .../{ => syrupy/extensions/amber}/test_matchers.py | 0 .../{ => syrupy/extensions}/test_extension_base.py | 0 .../{ => syrupy/extensions}/test_extension_image.py | 0 .../extensions}/test_extension_single_file.py | 0 tests/{ => syrupy}/test_utils.py | 0 27 files changed, 0 insertions(+), 0 deletions(-) rename tests/{ => integration}/test_integration_custom.py (100%) rename tests/{ => integration}/test_integration_default.py (100%) rename tests/{ => integration}/test_integration_pytest_extension.py (100%) rename tests/{ => integration}/test_integration_single_file.py (100%) create mode 100644 tests/syrupy/__init__.py create mode 100644 tests/syrupy/extensions/__init__.py rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_base.ambr (100%) rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_image.ambr (100%) rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_image/test_image.png (100%) rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_image/test_image_vector.svg (100%) rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.2.png (100%) rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.3.svg (100%) rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.svg (100%) rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_single_file/TestClass.test_class_method_name.raw (100%) rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedx.raw (100%) rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedy.raw (100%) rename tests/{ => syrupy/extensions}/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedz.raw (100%) rename tests/{ => syrupy/extensions/amber}/__snapshots__/test_extension_amber.ambr (100%) rename tests/{ => syrupy/extensions/amber}/__snapshots__/test_filters.ambr (100%) rename tests/{ => syrupy/extensions/amber}/__snapshots__/test_matchers.ambr (100%) rename tests/{ => syrupy/extensions/amber}/test_extension_amber.py (100%) rename tests/{ => syrupy/extensions/amber}/test_filters.py (100%) rename tests/{ => syrupy/extensions/amber}/test_matchers.py (100%) rename tests/{ => syrupy/extensions}/test_extension_base.py (100%) rename tests/{ => syrupy/extensions}/test_extension_image.py (100%) rename tests/{ => syrupy/extensions}/test_extension_single_file.py (100%) rename tests/{ => syrupy}/test_utils.py (100%) diff --git a/tests/test_integration_custom.py b/tests/integration/test_integration_custom.py similarity index 100% rename from tests/test_integration_custom.py rename to tests/integration/test_integration_custom.py diff --git a/tests/test_integration_default.py b/tests/integration/test_integration_default.py similarity index 100% rename from tests/test_integration_default.py rename to tests/integration/test_integration_default.py diff --git a/tests/test_integration_pytest_extension.py b/tests/integration/test_integration_pytest_extension.py similarity index 100% rename from tests/test_integration_pytest_extension.py rename to tests/integration/test_integration_pytest_extension.py diff --git a/tests/test_integration_single_file.py b/tests/integration/test_integration_single_file.py similarity index 100% rename from tests/test_integration_single_file.py rename to tests/integration/test_integration_single_file.py diff --git a/tests/syrupy/__init__.py b/tests/syrupy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/syrupy/extensions/__init__.py b/tests/syrupy/extensions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/__snapshots__/test_extension_base.ambr b/tests/syrupy/extensions/__snapshots__/test_extension_base.ambr similarity index 100% rename from tests/__snapshots__/test_extension_base.ambr rename to tests/syrupy/extensions/__snapshots__/test_extension_base.ambr diff --git a/tests/__snapshots__/test_extension_image.ambr b/tests/syrupy/extensions/__snapshots__/test_extension_image.ambr similarity index 100% rename from tests/__snapshots__/test_extension_image.ambr rename to tests/syrupy/extensions/__snapshots__/test_extension_image.ambr diff --git a/tests/__snapshots__/test_extension_image/test_image.png b/tests/syrupy/extensions/__snapshots__/test_extension_image/test_image.png similarity index 100% rename from tests/__snapshots__/test_extension_image/test_image.png rename to tests/syrupy/extensions/__snapshots__/test_extension_image/test_image.png diff --git a/tests/__snapshots__/test_extension_image/test_image_vector.svg b/tests/syrupy/extensions/__snapshots__/test_extension_image/test_image_vector.svg similarity index 100% rename from tests/__snapshots__/test_extension_image/test_image_vector.svg rename to tests/syrupy/extensions/__snapshots__/test_extension_image/test_image_vector.svg diff --git a/tests/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.2.png b/tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.2.png similarity index 100% rename from tests/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.2.png rename to tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.2.png diff --git a/tests/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.3.svg b/tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.3.svg similarity index 100% rename from tests/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.3.svg rename to tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.3.svg diff --git a/tests/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.svg b/tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.svg similarity index 100% rename from tests/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.svg rename to tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.svg diff --git a/tests/__snapshots__/test_extension_single_file/TestClass.test_class_method_name.raw b/tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_name.raw similarity index 100% rename from tests/__snapshots__/test_extension_single_file/TestClass.test_class_method_name.raw rename to tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_name.raw diff --git a/tests/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedx.raw b/tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedx.raw similarity index 100% rename from tests/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedx.raw rename to tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedx.raw diff --git a/tests/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedy.raw b/tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedy.raw similarity index 100% rename from tests/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedy.raw rename to tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedy.raw diff --git a/tests/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedz.raw b/tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedz.raw similarity index 100% rename from tests/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedz.raw rename to tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedz.raw diff --git a/tests/__snapshots__/test_extension_amber.ambr b/tests/syrupy/extensions/amber/__snapshots__/test_extension_amber.ambr similarity index 100% rename from tests/__snapshots__/test_extension_amber.ambr rename to tests/syrupy/extensions/amber/__snapshots__/test_extension_amber.ambr diff --git a/tests/__snapshots__/test_filters.ambr b/tests/syrupy/extensions/amber/__snapshots__/test_filters.ambr similarity index 100% rename from tests/__snapshots__/test_filters.ambr rename to tests/syrupy/extensions/amber/__snapshots__/test_filters.ambr diff --git a/tests/__snapshots__/test_matchers.ambr b/tests/syrupy/extensions/amber/__snapshots__/test_matchers.ambr similarity index 100% rename from tests/__snapshots__/test_matchers.ambr rename to tests/syrupy/extensions/amber/__snapshots__/test_matchers.ambr diff --git a/tests/test_extension_amber.py b/tests/syrupy/extensions/amber/test_extension_amber.py similarity index 100% rename from tests/test_extension_amber.py rename to tests/syrupy/extensions/amber/test_extension_amber.py diff --git a/tests/test_filters.py b/tests/syrupy/extensions/amber/test_filters.py similarity index 100% rename from tests/test_filters.py rename to tests/syrupy/extensions/amber/test_filters.py diff --git a/tests/test_matchers.py b/tests/syrupy/extensions/amber/test_matchers.py similarity index 100% rename from tests/test_matchers.py rename to tests/syrupy/extensions/amber/test_matchers.py diff --git a/tests/test_extension_base.py b/tests/syrupy/extensions/test_extension_base.py similarity index 100% rename from tests/test_extension_base.py rename to tests/syrupy/extensions/test_extension_base.py diff --git a/tests/test_extension_image.py b/tests/syrupy/extensions/test_extension_image.py similarity index 100% rename from tests/test_extension_image.py rename to tests/syrupy/extensions/test_extension_image.py diff --git a/tests/test_extension_single_file.py b/tests/syrupy/extensions/test_extension_single_file.py similarity index 100% rename from tests/test_extension_single_file.py rename to tests/syrupy/extensions/test_extension_single_file.py diff --git a/tests/test_utils.py b/tests/syrupy/test_utils.py similarity index 100% rename from tests/test_utils.py rename to tests/syrupy/test_utils.py From 5f1b55d247b7631ba562d09b4bd4c47ebd8838b4 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Wed, 28 Oct 2020 17:33:22 -0400 Subject: [PATCH 02/16] test: refactor test distribution --- tests/integration/test_integration_custom.py | 64 -- tests/integration/test_integration_default.py | 570 ------------------ ..._extension.py => test_pytest_extension.py} | 0 .../test_snapshot_option_defaults.py | 25 + .../test_snapshot_option_extension.py | 47 ++ .../test_snapshot_option_update.py | 414 +++++++++++++ .../test_snapshot_option_warn_unused.py | 70 +++ ...file.py => test_snapshot_use_extension.py} | 83 ++- ...est_extension_base.ambr => test_base.ambr} | 0 ...t_filters.ambr => test_amber_filters.ambr} | 0 .../__snapshots__/test_amber_matchers.ambr | 45 ++ ..._amber.ambr => test_amber_serializer.ambr} | 26 - .../amber/__snapshots__/test_matchers.ambr | 19 - ...{test_filters.py => test_amber_filters.py} | 0 ...est_matchers.py => test_amber_matchers.py} | 26 + ...sion_amber.py => test_amber_serializer.py} | 29 - tests/syrupy/extensions/file/__init__.py | 0 .../file/__snapshots__/test_image_png.ambr | 3 + .../test_image_png}/test_image.png | Bin .../test_multiple_snapshot_extensions.2.png | Bin .../test_multiple_snapshot_extensions.png | Bin 0 -> 182 bytes .../__snapshots__/test_image_svg.ambr} | 0 .../test_image_svg/test_image.svg} | 0 .../test_multiple_snapshot_extensions.2.svg} | 0 .../test_multiple_snapshot_extensions.svg | 0 .../TestClass.test_class_method_name.raw | 0 ...tClass.test_class_method_parametrizedx.raw | 0 ...tClass.test_class_method_parametrizedy.raw | 0 ...tClass.test_class_method_parametrizedz.raw | 0 .../syrupy/extensions/file/test_image_png.py | 33 + .../test_image_svg.py} | 30 +- .../test_single_file.py} | 0 .../{test_extension_base.py => test_base.py} | 0 33 files changed, 728 insertions(+), 756 deletions(-) delete mode 100644 tests/integration/test_integration_custom.py delete mode 100644 tests/integration/test_integration_default.py rename tests/integration/{test_integration_pytest_extension.py => test_pytest_extension.py} (100%) create mode 100644 tests/integration/test_snapshot_option_defaults.py create mode 100644 tests/integration/test_snapshot_option_extension.py create mode 100644 tests/integration/test_snapshot_option_update.py create mode 100644 tests/integration/test_snapshot_option_warn_unused.py rename tests/integration/{test_integration_single_file.py => test_snapshot_use_extension.py} (50%) rename tests/syrupy/extensions/__snapshots__/{test_extension_base.ambr => test_base.ambr} (100%) rename tests/syrupy/extensions/amber/__snapshots__/{test_filters.ambr => test_amber_filters.ambr} (100%) create mode 100644 tests/syrupy/extensions/amber/__snapshots__/test_amber_matchers.ambr rename tests/syrupy/extensions/amber/__snapshots__/{test_extension_amber.ambr => test_amber_serializer.ambr} (90%) delete mode 100644 tests/syrupy/extensions/amber/__snapshots__/test_matchers.ambr rename tests/syrupy/extensions/amber/{test_filters.py => test_amber_filters.py} (100%) rename tests/syrupy/extensions/amber/{test_matchers.py => test_amber_matchers.py} (57%) rename tests/syrupy/extensions/amber/{test_extension_amber.py => test_amber_serializer.py} (83%) create mode 100644 tests/syrupy/extensions/file/__init__.py create mode 100644 tests/syrupy/extensions/file/__snapshots__/test_image_png.ambr rename tests/syrupy/extensions/{__snapshots__/test_extension_image => file/__snapshots__/test_image_png}/test_image.png (100%) rename tests/syrupy/extensions/{__snapshots__/test_extension_image => file/__snapshots__/test_image_png}/test_multiple_snapshot_extensions.2.png (100%) create mode 100644 tests/syrupy/extensions/file/__snapshots__/test_image_png/test_multiple_snapshot_extensions.png rename tests/syrupy/extensions/{__snapshots__/test_extension_image.ambr => file/__snapshots__/test_image_svg.ambr} (100%) rename tests/syrupy/extensions/{__snapshots__/test_extension_image/test_image_vector.svg => file/__snapshots__/test_image_svg/test_image.svg} (100%) rename tests/syrupy/extensions/{__snapshots__/test_extension_image/test_multiple_snapshot_extensions.3.svg => file/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.2.svg} (100%) rename tests/syrupy/extensions/{__snapshots__/test_extension_image => file/__snapshots__/test_image_svg}/test_multiple_snapshot_extensions.svg (100%) rename tests/syrupy/extensions/{__snapshots__/test_extension_single_file => file/__snapshots__/test_single_file}/TestClass.test_class_method_name.raw (100%) rename tests/syrupy/extensions/{__snapshots__/test_extension_single_file => file/__snapshots__/test_single_file}/TestClass.test_class_method_parametrizedx.raw (100%) rename tests/syrupy/extensions/{__snapshots__/test_extension_single_file => file/__snapshots__/test_single_file}/TestClass.test_class_method_parametrizedy.raw (100%) rename tests/syrupy/extensions/{__snapshots__/test_extension_single_file => file/__snapshots__/test_single_file}/TestClass.test_class_method_parametrizedz.raw (100%) create mode 100644 tests/syrupy/extensions/file/test_image_png.py rename tests/syrupy/extensions/{test_extension_image.py => file/test_image_svg.py} (54%) rename tests/syrupy/extensions/{test_extension_single_file.py => file/test_single_file.py} (100%) rename tests/syrupy/extensions/{test_extension_base.py => test_base.py} (100%) diff --git a/tests/integration/test_integration_custom.py b/tests/integration/test_integration_custom.py deleted file mode 100644 index 1837f6bf..00000000 --- a/tests/integration/test_integration_custom.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest - - -@pytest.fixture -def testcases(testdir): - testdir.makeconftest( - """ - import pytest - - from syrupy.extensions.base import AbstractSyrupyExtension - - class CustomSnapshotExtension(AbstractSyrupyExtension): - def _file_extension(self): - return "" - - def serialize(self, data, **kwargs): - return str(data) - - def get_snapshot_name(self, *, index = 0): - testname = self._test_location.testname[::-1] - return f"{testname}.{index}" - - def _read_snapshot_fossil(self, **kwargs): - pass - - def _read_snapshot_data_from_location(self, **kwargs): - pass - - def _write_snapshot_fossil(self, **kwargs): - pass - - def delete_snapshots(self, **kwargs): - pass - - def _get_file_basename(self, *, index = 0): - return self.test_location.filename[::-1] - - - @pytest.fixture - def snapshot_custom(snapshot): - return snapshot.use_extension(CustomSnapshotExtension) - """ - ) - return { - "passed": ( - """ - def test_passed_custom(snapshot_custom): - assert snapshot_custom == 'passed1' - assert snapshot_custom == 'passed2' - """ - ) - } - - -def test_warns_on_snapshot_name(testdir, testcases): - testdir.makepyfile(test_file=testcases["passed"]) - result = testdir.runpytest("-v", "--snapshot-update", "--snapshot-no-colors") - result_stdout = result.stdout.str() - assert "2 snapshots generated" in result_stdout - assert "Warning:" in result_stdout - assert "Can not relate snapshot name" in result_stdout - assert "Can not relate snapshot location" in result_stdout - assert "test_passed_custom" in result_stdout - assert result.ret == 0 diff --git a/tests/integration/test_integration_default.py b/tests/integration/test_integration_default.py deleted file mode 100644 index 941be0e1..00000000 --- a/tests/integration/test_integration_default.py +++ /dev/null @@ -1,570 +0,0 @@ -from pathlib import Path - -import pytest - - -@pytest.fixture -def collection(testdir): - tests = { - "test_collected": ( - """ - import pytest - - @pytest.mark.parametrize("actual", [1, 2, 3]) - def test_collected(snapshot, actual): - assert snapshot == actual - """ - ), - "test_not_collected": ( - """ - def test_collected1(snapshot): - assert snapshot == "hello" - """ - ), - } - testdir.makepyfile(**tests) - result = testdir.runpytest("-v", "--snapshot-update") - result.stdout.re_match_lines((r"4 snapshots generated.")) - testdir.makefile(".ambr", **{str(Path("__snapshots__", "other_snapfile")): ""}) - return testdir - - -def test_unused_snapshots_ignored_if_not_targeted_using_dash_m(collection): - updated_tests = { - "test_collected": ( - """ - import pytest - - @pytest.mark.parametrize("actual", [1, "2"]) - def test_collected(snapshot, actual): - assert snapshot == actual - """ - ), - } - collection.makepyfile(**updated_tests) - result = collection.runpytest("-v", "--snapshot-update", "-m", "parametrize") - result.stdout.re_match_lines( - ( - r"1 snapshot passed\. 1 snapshot updated\. 1 unused snapshot deleted\.", - r"Deleted test_collected\[3\] \(__snapshots__[\\/]test_collected.ambr\)", - ) - ) - snapshot_path = [collection.tmpdir, "__snapshots__"] - assert Path(*snapshot_path, "test_not_collected.ambr").exists() - assert Path(*snapshot_path, "other_snapfile.ambr").exists() - - -def test_unused_snapshots_ignored_if_not_targeted_using_dash_k(collection): - updated_tests = { - "test_collected": ( - """ - import pytest - - @pytest.mark.parametrize("actual", [1, "2"]) - def test_collected(snapshot, actual): - assert snapshot == actual - """ - ), - } - collection.makepyfile(**updated_tests) - result = collection.runpytest("-v", "--snapshot-update", "-k", "test_collected[") - result.stdout.re_match_lines( - ( - r"1 snapshot passed\. 1 snapshot updated\. 1 unused snapshot deleted\.", - r"Deleted test_collected\[3\] \(__snapshots__[\\/]test_collected.ambr\)", - ) - ) - snapshot_path = [collection.tmpdir, "__snapshots__"] - assert Path(*snapshot_path, "test_not_collected.ambr").exists() - assert Path(*snapshot_path, "other_snapfile.ambr").exists() - - -def test_unused_parameterized_ignored_if_not_targeted_using_dash_k(collection): - updated_tests = { - "test_collected": ( - """ - import pytest - - @pytest.mark.parametrize("actual", [1, 2]) - def test_collected(snapshot, actual): - assert snapshot == actual - """ - ), - } - collection.makepyfile(**updated_tests) - result = collection.runpytest("-v", "--snapshot-update", "-k", "test_collected[") - result.stdout.re_match_lines( - ( - r"2 snapshots passed\. 1 unused snapshot deleted\.", - r"Deleted test_collected\[3\] \(__snapshots__[\\/]test_collected\.ambr\)", - ) - ) - snapshot_path = [collection.tmpdir, "__snapshots__"] - assert Path(*snapshot_path, "test_not_collected.ambr").exists() - assert Path(*snapshot_path, "other_snapfile.ambr").exists() - - -def test_only_updates_targeted_snapshot_in_class_for_single_file(testdir): - test_content = """ - import pytest - - class TestClass: - def test_case_1(self, snapshot): - assert snapshot == 1 - - def test_case_2(self, snapshot): - assert snapshot == 2 - """ - - testdir.makepyfile(test_content=test_content) - testdir.runpytest("-v", "--snapshot-update") - - snapshot_path = Path(testdir.tmpdir, "__snapshots__") - assert snapshot_path.joinpath("test_content.ambr").exists() - - test_filepath = Path(testdir.tmpdir, "test_content.py") - result = testdir.runpytest(str(test_filepath), "-v", "-k test_case_2") - result_stdout = result.stdout.str() - assert "1 snapshot passed" in result_stdout - assert "snapshot unused" not in result_stdout - - -def test_only_updates_targeted_snapshot_for_single_file(testdir): - test_content = """ - import pytest - - def test_case_1(snapshot): - assert snapshot == 1 - - def test_case_2(snapshot): - assert snapshot == 2 - """ - - testdir.makepyfile(test_content=test_content) - testdir.runpytest("-v", "--snapshot-update") - - snapshot_path = Path(testdir.tmpdir, "__snapshots__") - assert snapshot_path.joinpath("test_content.ambr").exists() - - test_filepath = Path(testdir.tmpdir, "test_content.py") - result = testdir.runpytest(str(test_filepath), "-v", "-k test_case_2") - result_stdout = result.stdout.str() - assert "1 snapshot passed" in result_stdout - assert "snapshot unused" not in result_stdout - - -def test_multiple_snapshots(testdir): - test_content = """ - import pytest - - def test_case_1(snapshot): - assert snapshot == 1 - assert snapshot == 2 - """ - - testdir.makepyfile(test_content=test_content) - result = testdir.runpytest("-v", "--snapshot-update") - - result_stdout = result.stdout.str() - assert "Can not relate snapshot name" not in result_stdout - - -@pytest.fixture -def testcases(): - return { - "inject": ( - """ - def test_injection(snapshot): - assert snapshot is not None - """ - ), - "used": ( - """ - def test_used(snapshot): - assert snapshot == 'used' - """ - ), - "unused": ( - """ - def test_unused(snapshot): - assert snapshot == 'unused' - """ - ), - "updated_1": ( - """ - def test_updated_1(snapshot): - assert snapshot == ['this', 'will', 'be', 'updated'] - """ - ), - "updated_2": ( - """ - def test_updated_2(snapshot): - assert ['this', 'will', 'be', 'updated'] == snapshot - """ - ), - "updated_3": ( - """ - def test_updated_3(snapshot): - assert snapshot == ['this', 'will', 'be', 'updated'] - """ - ), - "updated_4": ( - """ - def test_updated_4(snapshot): - assert snapshot == "single line change" - """ - ), - "updated_5": ( - """ - def test_updated_5(snapshot): - assert snapshot == ''' - multiple line changes - with some lines staying the same - intermittent changes that have to be ignore by the differ output - because when there are a lot of changes you only want to see changes - you do not want to see this line - or this line - this line should not show up because new lines are normalised\\r\\n - \x1b[38;5;1mthis line should show up because it changes color\x1b[0m - ''' - """ - ), - } - - -@pytest.fixture -def testcases_updated(testcases): - updated_testcases = { - "updated_1": ( - """ - def test_updated_1(snapshot): - assert snapshot == ['this', 'will', 'not', 'match'] - """ - ), - "updated_2": ( - """ - def test_updated_2(snapshot): - assert ['this', 'will', 'fail'] == snapshot - """ - ), - "updated_3": ( - """ - def test_updated_3(snapshot): - assert snapshot == ['this', 'will', 'be', 'too', 'much'] - """ - ), - "updated_4": ( - """ - def test_updated_4(snapshot): - assert snapshot == "sing line changeling" - """ - ), - "updated_5": ( - """ - def test_updated_5(snapshot): - assert snapshot == ''' - multiple line changes - with some lines not staying the same - intermittent changes so unchanged lines have to be ignored by the differ - cause when there are a lot of changes you only want to see what changed - you do not want to see this line - or this line - this line should not show up because new lines are normalised\\n - \x1b[38;5;3mthis line should show up because it changes color\x1b[0m - and this line does not exist in the first one - ''' - """ - ), - } - return {**testcases, **updated_testcases} - - -def test_missing_snapshots(testdir, testcases): - testdir.makepyfile(test_file=testcases["used"]) - result = testdir.runpytest("-v") - result.stdout.re_match_lines((r"1 snapshot failed\.")) - assert result.ret == 1 - - -@pytest.fixture -def stubs(testdir, testcases): - pyfile_content = "\n\n".join(testcases.values()) - testdir.makepyfile(test_file=pyfile_content) - filepath = Path(testdir.tmpdir, "__snapshots__", "test_file.ambr") - return testdir.runpytest("-v", "--snapshot-update"), testdir, testcases, filepath - - -def test_injected_fixture(stubs): - result = stubs[0] - result.stdout.fnmatch_lines(["*::test_injection PASSED*"]) - assert result.ret == 0 - - -def test_generated_snapshots(stubs): - result = stubs[0] - result.stdout.re_match_lines((r"7 snapshots generated\.")) - assert result.ret == 0 - - -def test_failing_snapshots_diff(stubs, testcases_updated): - testdir = stubs[1] - testdir.makepyfile(test_file="\n\n".join(testcases_updated.values())) - result = testdir.runpytest("-vv", "--snapshot-no-colors") - result.stdout.re_match_lines( - ( - r".*assert snapshot == \['this', 'will', 'not', 'match'\]", - r".*AssertionError: assert \[- snapshot\] == \[\+ received\]", - r".* \[", - r".* ...", - r".* 'will',", - r".* - 'be',", - r".* - 'updated',", - r".* \+ 'not',", - r".* \+ 'match',", - r".* \]", - r".*assert \['this', 'will', 'fail'\] == snapshot", - r".*AssertionError: assert \[\+ received\] == \[- snapshot\]", - r".* \[", - r".* ...", - r".* 'will',", - r".* - 'be',", - r".* \+ 'fail',", - r".* - 'updated',", - r".* \]", - r".*assert snapshot == \['this', 'will', 'be', 'too', 'much'\]", - r".*AssertionError: assert \[- snapshot\] == \[\+ received\]", - r".* \[", - r".* ...", - r".* 'be',", - r".* - 'updated',", - r".* \+ 'too',", - r".* \+ 'much',", - r".* \]", - r".*assert snapshot == \"sing line changeling\"", - r".*AssertionError: assert \[- snapshot\] == \[\+ received\]", - r".* - 'single line change'", - r".* \+ 'sing line changeling'", - r".*AssertionError: assert \[- snapshot\] == \[\+ received\]", - r".* '", - r".* ...", - r".* multiple line changes", - r".* - with some lines staying the same", - r".* \+ with some lines not staying the same", - r".* - intermittent changes that have to be ignore by the differ out", - r".* \+ intermittent changes so unchanged lines have to be ignored b", - r".* - because when there are a lot of changes you only want to see ", - r".* \+ cause when there are a lot of changes you only want to see w", - r".* you do not want to see this line", - r".* ...", - r".* ", - r".* - \[38;5;1mthis line should show up because it changes color", - r".* \+ \[38;5;3mthis line should show up because it changes color", - r".* \+ and this line does not exist in the first one", - r".* ", - r".* '", - ) - ) - assert result.ret == 1 - - -def test_updated_snapshots(stubs, testcases_updated): - testdir = stubs[1] - testdir.makepyfile(test_file="\n\n".join(testcases_updated.values())) - result = testdir.runpytest("-v", "--snapshot-update") - result.stdout.re_match_lines((r"2 snapshots passed\. 5 snapshots updated\.")) - assert result.ret == 0 - - -def test_unused_snapshots(stubs): - _, testdir, tests, _ = stubs - testdir.makepyfile(test_file="\n\n".join(tests[k] for k in tests if k != "unused")) - result = testdir.runpytest("-v") - result.stdout.re_match_lines( - ( - r"6 snapshots passed\. 1 snapshot unused\.", - r"Re-run pytest with --snapshot-update to delete unused snapshots\.", - ) - ) - assert result.ret == 1 - - -def test_unused_snapshots_warning(stubs): - _, testdir, tests, _ = stubs - testdir.makepyfile(test_file="\n\n".join(tests[k] for k in tests if k != "unused")) - result = testdir.runpytest("-v", "--snapshot-warn-unused") - result.stdout.re_match_lines( - ( - r"6 snapshots passed\. 1 snapshot unused\.", - r"Re-run pytest with --snapshot-update to delete unused snapshots\.", - ) - ) - assert result.ret == 0 - - -def test_unused_snapshots_ignored_if_not_targeted_by_testnode_ids(testdir): - path_to_snap = Path("__snapshots__", "other_snapfile") - testdir.makefile(".ambr", **{str(path_to_snap): ""}) - testdir.makefile( - ".py", - test_life_uhh_finds_a_way=( - """ - def test_life_always_finds_a_way(snapshot): - assert snapshot == snapshot - - def test_clever_girl(snapshot): - assert snapshot == snapshot - """ - ), - ) - testfile = Path(testdir.tmpdir, "test_life_uhh_finds_a_way.py") - testdir.runpytest(str(testfile), "-v", "--snapshot-update") - result = testdir.runpytest( - f"{testfile}::test_life_always_finds_a_way", "-v", "--snapshot-update" - ) - result.stdout.re_match_lines((r"1 snapshot passed\.")) - assert result.ret == 0 - assert Path(f"{path_to_snap}.ambr").exists() - - -def test_unused_snapshots_ignored_if_not_targeted_by_module_testfiles(stubs): - _, testdir, tests, _ = stubs - path_to_snap = Path("__snapshots__", "other_snapfile") - testdir.makepyfile(test_file="\n\n".join(tests[k] for k in tests if k != "unused")) - testdir.makefile(".ambr", **{str(path_to_snap): ""}) - result = testdir.runpytest("-v", "--snapshot-update", "test_file.py") - result.stdout.re_match_lines( - ( - r"6 snapshots passed\. 1 unused snapshot deleted\.", - r"Deleted test_unused \(__snapshots__[\\/]test_file\.ambr\)", - ) - ) - assert result.ret == 0 - assert Path(f"{path_to_snap}.ambr").exists() - - -def test_unused_snapshots_cleaned_up_when_targeting_specific_testfiles(stubs): - _, testdir, _, _ = stubs - path_to_snap = Path("__snapshots__", "other_snapfile") - testdir.makepyfile( - test_file=( - """ - def test_used(snapshot): - assert True - """ - ), - ) - testdir.makefile(".ambr", **{str(path_to_snap): ""}) - result = testdir.runpytest("-v", "--snapshot-update", "test_file.py") - result.stdout.re_match_lines_random( - ( - r"7 unused snapshots deleted\.", - r"Deleted test_unused, test_updated_1, test_updated_2, test_updated_3", - r".*test_updated_3, test_updated_4, test_updated_5, test_used", - r".*test_used \(__snapshots__[\\/]test_file.ambr\)", - ) - ) - assert result.ret == 0 - assert Path(f"{path_to_snap}.ambr").exists() - - -def test_removed_snapshots(stubs): - _, testdir, tests, filepath = stubs - assert Path(filepath).exists() - testdir.makepyfile(test_file="\n\n".join(tests[k] for k in tests if k != "unused")) - result = testdir.runpytest("-v", "--snapshot-update") - result.stdout.re_match_lines( - ( - r"6 snapshots passed\. 1 unused snapshot deleted\.", - r"Deleted test_unused \(__snapshots__[\\/]test_file\.ambr\)", - ) - ) - assert result.ret == 0 - assert Path(filepath).exists() - - -def test_removed_snapshot_fossil(stubs): - _, testdir, tests, filepath = stubs - assert Path(filepath).exists() - testdir.makepyfile(test_file=tests["inject"]) - result = testdir.runpytest("-v", "--snapshot-update") - result.stdout.re_match_lines_random( - ( - r"7 unused snapshots deleted\.", - r"Deleted test_unused, test_updated_1, test_updated_2, test_updated_3", - r".*test_updated_3, test_updated_4, test_updated_5, test_used", - r".*test_used \(__snapshots__[\\/]test_file\.ambr\)", - ) - ) - assert result.ret == 0 - assert not Path(filepath).exists() - - -def test_removed_empty_snapshot_fossil_only(stubs): - _, testdir, _, filepath = stubs - path_to_snap = Path("__snapshots__", "test_empty") - testdir.makefile(".ambr", **{str(path_to_snap): ""}) - empty_filepath = Path(testdir.tmpdir, f"{path_to_snap}.ambr") - assert empty_filepath.exists() - result = testdir.runpytest("-v", "--snapshot-update") - result.stdout.re_match_lines( - ( - r"7 snapshots passed\. 1 unused snapshot deleted\.", - r"Deleted empty snapshot fossil \(__snapshots__[\\/]test_empty\.ambr\)", - ) - ) - assert result.ret == 0 - assert Path(filepath).exists() - assert not Path(empty_filepath).exists() - - -def test_removed_hanging_snapshot_fossil(stubs): - _, testdir, _, filepath = stubs - path_to_snap = Path("__snapshots__", "test_hanging") - testdir.makefile(".abc", **{str(path_to_snap): ""}) - hanging_filepath = Path(testdir.tmpdir, f"{path_to_snap}.abc") - assert hanging_filepath.exists() - result = testdir.runpytest("-v", "--snapshot-update") - result_stdout = result.stdout.str() - assert str(Path(filepath).relative_to(Path.cwd())) not in result_stdout - assert "1 unused snapshot deleted" in result_stdout - assert "unknown snapshot" in result_stdout - assert str(hanging_filepath.relative_to(Path.cwd())) in result_stdout - assert result.ret == 0 - assert Path(filepath).exists() - assert not Path(hanging_filepath).exists() - - -def test_snapshot_default_extension_option(testdir): - testdir.makepyfile( - test_file=( - """ - def test_default(snapshot): - assert b"default extension serializer" == snapshot - """ - ), - ) - result = testdir.runpytest( - "-v", - "--snapshot-update", - "--snapshot-default-extension", - "syrupy.extensions.single_file.SingleFileSnapshotExtension", - ) - result.stdout.re_match_lines((r"1 snapshot generated\.")) - assert Path( - testdir.tmpdir, "__snapshots__", "test_file", "test_default.raw" - ).exists() - assert result.ret == 0 - - -def test_snapshot_default_extension_option_failure(testdir, testcases): - testdir.makepyfile(test_file=testcases["used"]) - result = testdir.runpytest( - "-v", - "--snapshot-update", - "--snapshot-default-extension", - "syrupy.extensions.amber.DoesNotExistExtension", - ) - result_stderr = result.stderr.str() - assert "error: argument --snapshot-default-extension" in result_stderr - assert "Member 'DoesNotExistExtension' not found" in result_stderr - assert result.ret diff --git a/tests/integration/test_integration_pytest_extension.py b/tests/integration/test_pytest_extension.py similarity index 100% rename from tests/integration/test_integration_pytest_extension.py rename to tests/integration/test_pytest_extension.py diff --git a/tests/integration/test_snapshot_option_defaults.py b/tests/integration/test_snapshot_option_defaults.py new file mode 100644 index 00000000..357bbf38 --- /dev/null +++ b/tests/integration/test_snapshot_option_defaults.py @@ -0,0 +1,25 @@ +import pytest + + +@pytest.fixture +def testcases(): + return { + "inject": ( + """ + def test_injection(snapshot): + assert snapshot is not None + """ + ), + } + + +@pytest.fixture +def run_testcases(testdir, testcases): + pyfile_content = "\n\n".join(testcases.values()) + testdir.makepyfile(test_file=pyfile_content) + return testdir.runpytest("-v") + + +def test_injected_fixture(run_testcases): + run_testcases.stdout.fnmatch_lines(["*::test_injection PASSED*"]) + assert run_testcases.ret == 0 diff --git a/tests/integration/test_snapshot_option_extension.py b/tests/integration/test_snapshot_option_extension.py new file mode 100644 index 00000000..ca7bbd4a --- /dev/null +++ b/tests/integration/test_snapshot_option_extension.py @@ -0,0 +1,47 @@ +from pathlib import Path + +import pytest + + +@pytest.fixture +def testfile(testdir): + testdir.makepyfile( + test_file=( + """ + def test_default(snapshot): + assert b"default extension serializer" == snapshot + """ + ), + ) + return testdir + + +def test_snapshot_default_extension_option_success(testfile): + result = testfile.runpytest( + "-v", + "--snapshot-update", + "--snapshot-default-extension", + "syrupy.extensions.single_file.SingleFileSnapshotExtension", + ) + result_stdout = result.stdout.str() + assert "1 snapshot generated." in result_stdout + assert Path( + testfile.tmpdir, "__snapshots__", "test_file", "test_default.raw" + ).exists() + assert not result.ret + + +def test_snapshot_default_extension_option_failure(testfile): + result = testfile.runpytest( + "-v", + "--snapshot-update", + "--snapshot-default-extension", + "syrupy.extensions.amber.DoesNotExistExtension", + ) + result_stderr = result.stderr.str() + assert "error: argument --snapshot-default-extension" in result_stderr + assert "Member 'DoesNotExistExtension' not found" in result_stderr + assert not Path( + testfile.tmpdir, "__snapshots__", "test_file", "test_default.raw" + ).exists() + assert result.ret diff --git a/tests/integration/test_snapshot_option_update.py b/tests/integration/test_snapshot_option_update.py new file mode 100644 index 00000000..195587cf --- /dev/null +++ b/tests/integration/test_snapshot_option_update.py @@ -0,0 +1,414 @@ +from pathlib import Path + +import pytest + + +@pytest.fixture +def testcases_initial(): + return { + "test_used": ( + """ + import pytest + + @pytest.mark.parametrize("actual", [1, 2, 3]) + def test_used(snapshot, actual): + assert snapshot == actual + + def test_used1(snapshot): + assert snapshot == 'unused' + assert 'unused' == snapshot + """ + ), + "test_updated_1": ( + """ + def test_updated_1(snapshot): + assert snapshot == ['this', 'will', 'be', 'updated'] + """ + ), + "test_updated_2": ( + """ + def test_updated_2(snapshot): + assert ['this', 'will', 'be', 'updated'] == snapshot + """ + ), + "test_updated_3": ( + """ + def test_updated_3(snapshot): + assert snapshot == ['this', 'will', 'be', 'updated'] + """ + ), + "test_updated_4": ( + """ + def test_updated_4(snapshot): + assert snapshot == "single line change" + """ + ), + "test_updated_5": ( + """ + def test_updated_5(snapshot): + assert snapshot == ''' + multiple line changes + with some lines staying the same + intermittent changes that have to be ignore by the differ output + because when there are a lot of changes you only want to see changes + you do not want to see this line + or this line + this line should not show up because new lines are normalised\\r\\n + \x1b[38;5;1mthis line should show up because it changes color\x1b[0m + ''' + """ + ), + } + + +@pytest.fixture +def testcases_updated(testcases_initial): + updates = { + "test_updated_1": ( + """ + def test_updated_1(snapshot): + assert snapshot == ['this', 'will', 'not', 'match'] + """ + ), + "test_updated_2": ( + """ + def test_updated_2(snapshot): + assert ['this', 'will', 'fail'] == snapshot + """ + ), + "test_updated_3": ( + """ + def test_updated_3(snapshot): + assert snapshot == ['this', 'will', 'be', 'too', 'much'] + """ + ), + "test_updated_4": ( + """ + def test_updated_4(snapshot): + assert snapshot == "sing line changeling" + """ + ), + "test_updated_5": ( + """ + def test_updated_5(snapshot): + assert snapshot == ''' + multiple line changes + with some lines not staying the same + intermittent changes so unchanged lines have to be ignored by the differ + cause when there are a lot of changes you only want to see what changed + you do not want to see this line + or this line + this line should not show up because new lines are normalised\\n + \x1b[38;5;3mthis line should show up because it changes color\x1b[0m + and this line does not exist in the first one + ''' + """ + ), + } + return {**testcases_initial, **updates} + + +@pytest.fixture +def run_testcases(testdir, testcases_initial): + testdir.makepyfile(**testcases_initial) + result = testdir.runpytest("-v", "--snapshot-update") + result.stdout.re_match_lines((r"10 snapshots generated.")) + result.stdout.no_re_match_line(r".*Can not relate snapshot name.*") + + return result, testdir, testcases_initial + + +def test_update_failure_shows_snapshot_diff(run_testcases, testcases_updated): + testdir = run_testcases[1] + testdir.makepyfile(**testcases_updated) + result = testdir.runpytest("-vv") + result.stdout.re_match_lines( + ( + r".*assert snapshot == \['this', 'will', 'not', 'match'\]", + r".*AssertionError: assert \[- snapshot\] == \[\+ received\]", + r".* \[", + r".* ...", + r".* 'will',", + r".* - 'be',", + r".* - 'updated',", + r".* \+ 'not',", + r".* \+ 'match',", + r".* \]", + r".*assert \['this', 'will', 'fail'\] == snapshot", + r".*AssertionError: assert \[\+ received\] == \[- snapshot\]", + r".* \[", + r".* ...", + r".* 'will',", + r".* - 'be',", + r".* \+ 'fail',", + r".* - 'updated',", + r".* \]", + r".*assert snapshot == \['this', 'will', 'be', 'too', 'much'\]", + r".*AssertionError: assert \[- snapshot\] == \[\+ received\]", + r".* \[", + r".* ...", + r".* 'be',", + r".* - 'updated',", + r".* \+ 'too',", + r".* \+ 'much',", + r".* \]", + r".*assert snapshot == \"sing line changeling\"", + r".*AssertionError: assert \[- snapshot\] == \[\+ received\]", + r".* - 'single line change'", + r".* \+ 'sing line changeling'", + r".*AssertionError: assert \[- snapshot\] == \[\+ received\]", + r".* '", + r".* ...", + r".* multiple line changes", + r".* - with some lines staying the same", + r".* \+ with some lines not staying the same", + r".* - intermittent changes that have to be ignore by the differ out", + r".* \+ intermittent changes so unchanged lines have to be ignored b", + r".* - because when there are a lot of changes you only want to see ", + r".* \+ cause when there are a lot of changes you only want to see w", + r".* you do not want to see this line", + r".* ...", + r".* ", + r".* - \[38;5;1mthis line should show up because it changes color", + r".* \+ \[38;5;3mthis line should show up because it changes color", + r".* \+ and this line does not exist in the first one", + r".* ", + r".* '", + ) + ) + assert result.ret == 1 + + +def test_update_success_shows_snapshot_report(run_testcases, testcases_updated): + testdir = run_testcases[1] + testdir.makepyfile(**testcases_updated) + result = testdir.runpytest("-v", "--snapshot-update") + result.stdout.re_match_lines((r"5 snapshots passed\. 5 snapshots updated\.")) + assert result.ret == 0 + + +def test_update_targets_only_selected_parametrized_tests_for_update_dash_m( + run_testcases, +): + updated_tests = { + "test_used": ( + """ + import pytest + + @pytest.mark.parametrize("actual", [1, "2"]) + def test_used(snapshot, actual): + assert snapshot == actual + """ + ), + } + testdir = run_testcases[1] + testdir.makepyfile(**updated_tests) + result = testdir.runpytest("-v", "--snapshot-update", "-m", "parametrize") + result.stdout.re_match_lines( + ( + r"1 snapshot passed\. 1 snapshot updated\. 1 unused snapshot deleted\.", + r"Deleted test_used\[3\] \(__snapshots__[\\/]test_used.ambr\)", + ) + ) + snapshot_path = [testdir.tmpdir, "__snapshots__"] + assert Path(*snapshot_path, "test_used.ambr").exists() + assert Path(*snapshot_path, "test_updated_1.ambr").exists() + + +def test_update_targets_only_selected_parametrized_tests_for_update_dash_k( + run_testcases, +): + updated_tests = { + "test_used": ( + """ + import pytest + + @pytest.mark.parametrize("actual", [1, "2"]) + def test_used(snapshot, actual): + assert snapshot == actual + """ + ), + } + testdir = run_testcases[1] + testdir.makepyfile(**updated_tests) + result = testdir.runpytest("-v", "--snapshot-update", "-k", "test_used[") + result.stdout.re_match_lines( + ( + r"1 snapshot passed\. 1 snapshot updated\. 1 unused snapshot deleted\.", + r"Deleted test_used\[3\] \(__snapshots__[\\/]test_used.ambr\)", + ) + ) + snapshot_path = [testdir.tmpdir, "__snapshots__"] + assert Path(*snapshot_path, "test_used.ambr").exists() + assert Path(*snapshot_path, "test_updated_1.ambr").exists() + + +def test_update_targets_only_selected_parametrized_tests_for_removal_dash_k( + run_testcases, +): + updated_tests = { + "test_used": ( + """ + import pytest + + @pytest.mark.parametrize("actual", [1, 2]) + def test_used(snapshot, actual): + assert snapshot == actual + """ + ), + } + testdir = run_testcases[1] + testdir.makepyfile(**updated_tests) + result = testdir.runpytest("-v", "--snapshot-update", "-k", "test_used[") + result.stdout.re_match_lines( + ( + r"2 snapshots passed\. 1 unused snapshot deleted\.", + r"Deleted test_used\[3\] \(__snapshots__[\\/]test_used\.ambr\)", + ) + ) + snapshot_path = [testdir.tmpdir, "__snapshots__"] + assert Path(*snapshot_path, "test_used.ambr").exists() + assert Path(*snapshot_path, "test_updated_1.ambr").exists() + + +def test_update_targets_only_selected_class_tests_dash_k(testdir): + test_content = """ + import pytest + + class TestClass: + def test_case_1(self, snapshot): + assert snapshot == 1 + + def test_case_2(self, snapshot): + assert snapshot == 2 + """ + + testdir.makepyfile(test_content=test_content) + testdir.runpytest("-v", "--snapshot-update") + assert Path(testdir.tmpdir, "__snapshots__", "test_content.ambr").exists() + + result = testdir.runpytest("test_content.py", "-v", "-k test_case_2") + result_stdout = result.stdout.str() + assert "1 snapshot passed" in result_stdout + assert "snapshot unused" not in result_stdout + + +def test_update_targets_only_selected_module_tests_dash_k(testdir): + test_content = """ + import pytest + + def test_case_1(snapshot): + assert snapshot == 1 + + def test_case_2(snapshot): + assert snapshot == 2 + """ + + testdir.makepyfile(test_content=test_content) + testdir.runpytest("-v", "--snapshot-update") + assert Path(testdir.tmpdir, "__snapshots__", "test_content.ambr").exists() + + result = testdir.runpytest("test_content.py", "-v", "-k test_case_2") + result_stdout = result.stdout.str() + assert "1 snapshot passed" in result_stdout + assert "snapshot unused" not in result_stdout + + +def test_update_targets_only_selected_module_tests_nodes(run_testcases): + testdir = run_testcases[1] + snapfile_empty = Path("__snapshots__", "empty_snapfile.ambr") + testdir.makefile(".ambr", **{str(snapfile_empty): ""}) + testfile = Path(testdir.tmpdir, "test_used.py") + result = testdir.runpytest(f"{testfile}::test_used", "-v", "--snapshot-update") + result.stdout.re_match_lines((r"3 snapshots passed\.")) + result.stdout.no_re_match_line(r".*unused.*") + result.stdout.no_re_match_line(r".*updated.*") + result.stdout.no_re_match_line(r".*deleted.*") + assert result.ret == 0 + assert snapfile_empty.exists() + + +def test_update_targets_only_selected_module_tests_file_for_update(run_testcases): + testdir = run_testcases[1] + snapfile_empty = Path("__snapshots__", "empty_snapfile.ambr") + testdir.makefile(".ambr", **{str(snapfile_empty): ""}) + testdir.makepyfile( + test_used=( + """ + import pytest + + @pytest.mark.parametrize("actual", [1, 2, 3]) + def test_used(snapshot, actual): + assert snapshot == actual + """ + ) + ) + result = testdir.runpytest("-v", "--snapshot-update", "test_used.py") + result.stdout.re_match_lines( + ( + r"3 snapshots passed\. 2 unused snapshots deleted\.", + r"Deleted test_used1, test_used1\.1 \(__snapshots__[\\/]test_used\.ambr\)", + ) + ) + assert result.ret == 0 + assert snapfile_empty.exists() + assert Path("__snapshots__", "test_used.ambr").exists() + + +def test_update_targets_only_selected_module_test_file_for_removal(run_testcases): + testdir = run_testcases[1] + testdir.makepyfile( + test_used=( + """ + def test_used(snapshot): + assert True + """ + ), + ) + snapfile_empty = Path("__snapshots__", "empty_snapfile.ambr") + testdir.makefile(".ambr", **{str(snapfile_empty): ""}) + result = testdir.runpytest("-v", "--snapshot-update", "test_used.py") + result.stdout.re_match_lines( + ( + r"5 unused snapshots deleted\.", + r"Deleted test_used1, test_used1\.1, test_used\[1\], test_used\[2\]" + r", test_used\[3\] \(__snapshots__[\\/]test_used\.ambr\)", + ) + ) + assert result.ret == 0 + assert snapfile_empty.exists() + assert not Path("__snapshots__", "test_used.ambr").exists() + + +def test_update_removes_empty_snapshot_fossil_only(run_testcases): + testdir = run_testcases[1] + snapfile_empty = Path("__snapshots__", "empty_snapfile.ambr") + testdir.makefile(".ambr", **{str(snapfile_empty): ""}) + assert snapfile_empty.exists() + result = testdir.runpytest("-v", "--snapshot-update") + result.stdout.re_match_lines( + ( + r"10 snapshots passed\. 1 unused snapshot deleted\.", + r"Deleted empty snapshot fossil \(__snapshots__[\\/]empty_snapfile\.ambr\)", + ) + ) + assert result.ret == 0 + assert not snapfile_empty.exists() + assert Path("__snapshots__", "test_used.ambr").exists() + + +def test_update_removes_hanging_snapshot_fossil_file(run_testcases): + testdir = run_testcases[1] + snapfile_used = Path("__snapshots__", "test_used.ambr") + snapfile_hanging = Path("__snapshots__", "hanging_snapfile.abc") + testdir.makefile(".abc", **{str(snapfile_hanging): ""}) + assert snapfile_hanging.exists() + result = testdir.runpytest("-v", "--snapshot-update") + result_stdout = result.stdout.str() + assert str(snapfile_used) not in result_stdout + assert "1 unused snapshot deleted" in result_stdout + assert "unknown snapshot" in result_stdout + assert str(snapfile_hanging) in result_stdout + assert result.ret == 0 + assert snapfile_used.exists() + assert not snapfile_hanging.exists() diff --git a/tests/integration/test_snapshot_option_warn_unused.py b/tests/integration/test_snapshot_option_warn_unused.py new file mode 100644 index 00000000..f9a36134 --- /dev/null +++ b/tests/integration/test_snapshot_option_warn_unused.py @@ -0,0 +1,70 @@ +import pytest + + +@pytest.fixture +def testcases(): + return { + "used": ( + """ + def test_used(snapshot): + assert snapshot == 'used' + """ + ), + "unused": ( + """ + def test_unused(snapshot): + assert snapshot == 'unused' + """ + ), + } + + +@pytest.fixture +def run_testcases(testdir, testcases): + pyfile_content = "\n\n".join(testcases.values()) + testdir.makepyfile(test_file=pyfile_content) + result = testdir.runpytest("-v", "--snapshot-update") + result.stdout.re_match_lines((r"2 snapshots generated\.")) + return testdir, testcases + + +def test_unused_snapshots_failure(run_testcases): + testdir, testcases = run_testcases + testdir.makepyfile(test_file=testcases["used"]) + + result = testdir.runpytest("-v") + result.stdout.re_match_lines( + ( + r"1 snapshot passed\. 1 snapshot unused\.", + r"Re-run pytest with --snapshot-update to delete unused snapshots\.", + ) + ) + assert result.ret == 1 + + +def test_unused_snapshots_warning(run_testcases): + testdir, testcases = run_testcases + testdir.makepyfile(test_file=testcases["used"]) + + result = testdir.runpytest("-v", "--snapshot-warn-unused") + result.stdout.re_match_lines( + ( + r"1 snapshot passed\. 1 snapshot unused\.", + r"Re-run pytest with --snapshot-update to delete unused snapshots\.", + ) + ) + assert result.ret == 0 + + +def test_unused_snapshots_deletion(run_testcases): + testdir, testcases = run_testcases + testdir.makepyfile(test_file=testcases["used"]) + + result = testdir.runpytest("-v", "--snapshot-update") + result.stdout.re_match_lines( + ( + r"1 snapshot passed\. 1 unused snapshot deleted\.", + r"Deleted test_unused \(__snapshots__[\\/]test_file\.ambr\)", + ) + ) + assert result.ret == 0 diff --git a/tests/integration/test_integration_single_file.py b/tests/integration/test_snapshot_use_extension.py similarity index 50% rename from tests/integration/test_integration_single_file.py rename to tests/integration/test_snapshot_use_extension.py index b7bb0eb6..5c30ba35 100644 --- a/tests/integration/test_integration_single_file.py +++ b/tests/integration/test_snapshot_use_extension.py @@ -2,16 +2,37 @@ @pytest.fixture -def testcases(testdir): +def testcases_initial(testdir): testdir.makeconftest( """ import pytest - from syrupy.extensions.single_file import SingleFileSnapshotExtension + from syrupy.extensions.amber import AmberSnapshotExtension from syrupy.extensions.image import ( PNGImageSnapshotExtension, SVGImageSnapshotExtension, ) + from syrupy.extensions.single_file import SingleFileSnapshotExtension + + + class CustomSnapshotExtension(AmberSnapshotExtension): + @property + def _file_extension(self): + return "" + + def serialize(self, data, **kwargs): + return str(data) + + def get_snapshot_name(self, *, index = 0): + testname = self._test_location.testname[::-1] + return f"{testname}.{index}" + + def _get_file_basename(self, *, index = 0): + return self.test_location.filename[::-1] + + @pytest.fixture + def snapshot_custom(snapshot): + return snapshot.use_extension(CustomSnapshotExtension) @pytest.fixture @@ -32,6 +53,10 @@ def snapshot_svg(snapshot): return { "passed": ( """ + def test_passed_custom(snapshot_custom): + assert snapshot_custom == 'passed1' + assert snapshot_custom == 'passed2' + def test_passed_single(snapshot_single): assert snapshot_single == b'passed1' assert snapshot_single == b'passed2' @@ -50,7 +75,7 @@ def test_failed_image(snapshot_png): @pytest.fixture -def testcases_updated(testcases): +def testcases_updated(testcases_initial): updated_testcases = { "passed": ( """ @@ -59,11 +84,18 @@ def test_passed_single(snapshot_single): """ ) } - return {**testcases, **updated_testcases} + return {**testcases_initial, **updated_testcases} -def test_unsaved_snapshots(testdir, testcases): - testdir.makepyfile(test_file=testcases["passed"]) +@pytest.fixture +def generate_snapshots(testdir, testcases_initial): + testdir.makepyfile(test_file=testcases_initial["passed"]) + result = testdir.runpytest("-v", "--snapshot-update") + return result, testdir, testcases_initial + + +def test_unsaved_snapshots(testdir, testcases_initial): + testdir.makepyfile(test_file=testcases_initial["passed"]) result = testdir.runpytest("-v") output = result.stdout.str() assert "Snapshot 'test_passed_single' does not exist" in output @@ -71,42 +103,47 @@ def test_unsaved_snapshots(testdir, testcases): assert result.ret == 1 -def test_failed_snapshots(testdir, testcases): - testdir.makepyfile(test_file=testcases["failed"]) +def test_failed_snapshots(testdir, testcases_initial): + testdir.makepyfile(test_file=testcases_initial["failed"]) result = testdir.runpytest("-v", "--snapshot-update") assert "2 snapshots failed" in result.stdout.str() assert result.ret == 1 -@pytest.fixture -def stubs(testdir, testcases): - testdir.makepyfile(test_file=testcases["passed"]) - return testdir.runpytest("-v", "--snapshot-update"), testdir, testcases - - -def test_generated_snapshots(stubs): - result = stubs[0] +def test_generated_snapshots(generate_snapshots): + result = generate_snapshots[0] result_stdout = result.stdout.str() - assert "2 snapshots generated" in result_stdout + assert "4 snapshots generated" in result_stdout assert "snapshots unused" not in result_stdout assert result.ret == 0 -def test_unmatched_snapshots(stubs, testcases_updated): - testdir = stubs[1] +def test_unmatched_snapshots(generate_snapshots, testcases_updated): + testdir = generate_snapshots[1] testdir.makepyfile(test_file=testcases_updated["passed"]) result = testdir.runpytest("-v") result_stdout = result.stdout.str() assert "1 snapshot failed" in result_stdout - assert "1 snapshot unused" in result_stdout + assert "2 snapshots unused" in result_stdout assert result.ret == 1 -def test_updated_snapshots(stubs, testcases_updated): - testdir = stubs[1] +def test_updated_snapshots(generate_snapshots, testcases_updated): + testdir = generate_snapshots[1] testdir.makepyfile(test_file=testcases_updated["passed"]) result = testdir.runpytest("-v", "--snapshot-update") result_stdout = result.stdout.str() assert "1 snapshot updated" in result_stdout - assert "1 unused snapshot deleted" in result_stdout + assert "2 unused snapshots deleted" in result_stdout + assert result.ret == 0 + + +def test_warns_on_snapshot_name(generate_snapshots): + result = generate_snapshots[0] + result_stdout = result.stdout.str() + assert "4 snapshots generated" in result_stdout + assert "Warning:" in result_stdout + assert "Can not relate snapshot name" in result_stdout + assert "Can not relate snapshot location" in result_stdout + assert "test_passed_custom" in result_stdout assert result.ret == 0 diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_base.ambr b/tests/syrupy/extensions/__snapshots__/test_base.ambr similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_base.ambr rename to tests/syrupy/extensions/__snapshots__/test_base.ambr diff --git a/tests/syrupy/extensions/amber/__snapshots__/test_filters.ambr b/tests/syrupy/extensions/amber/__snapshots__/test_amber_filters.ambr similarity index 100% rename from tests/syrupy/extensions/amber/__snapshots__/test_filters.ambr rename to tests/syrupy/extensions/amber/__snapshots__/test_amber_filters.ambr diff --git a/tests/syrupy/extensions/amber/__snapshots__/test_amber_matchers.ambr b/tests/syrupy/extensions/amber/__snapshots__/test_amber_matchers.ambr new file mode 100644 index 00000000..063b5ff0 --- /dev/null +++ b/tests/syrupy/extensions/amber/__snapshots__/test_amber_matchers.ambr @@ -0,0 +1,45 @@ +# name: test_matches_expected_type + { + 'date_created': , + 'nested': { + 'id': , + }, + 'some_uuid': , + } +--- +# name: test_non_deterministic_snapshots + { + 'a': UUID(...), + 'b': { + 'b_1': 'This is deterministic', + 'b_2': datetime.datetime(...), + }, + 'c': [ + 'Your wish is my command', + 'Do not replace this one', + ], + } +--- +# name: test_non_deterministic_snapshots.1 + { + 'a': UUID('06335e84-2872-4914-8c5d-3ed07d2a2f16'), + 'b': { + 'b_1': 'This is deterministic', + 'b_2': datetime.datetime(2020, 5, 31, 0, 0), + }, + 'c': [ + 'Replace this one', + 'Do not replace this one', + ], + } +--- +# name: test_raises_unexpected_type + { + 'date_created': , + 'date_updated': datetime.date(2020, 6, 1), + 'nested': { + 'id': , + }, + 'some_uuid': , + } +--- diff --git a/tests/syrupy/extensions/amber/__snapshots__/test_extension_amber.ambr b/tests/syrupy/extensions/amber/__snapshots__/test_amber_serializer.ambr similarity index 90% rename from tests/syrupy/extensions/amber/__snapshots__/test_extension_amber.ambr rename to tests/syrupy/extensions/amber/__snapshots__/test_amber_serializer.ambr index ed4359e9..9dd204f2 100644 --- a/tests/syrupy/extensions/amber/__snapshots__/test_extension_amber.ambr +++ b/tests/syrupy/extensions/amber/__snapshots__/test_amber_serializer.ambr @@ -296,32 +296,6 @@ line 2 ' --- -# name: test_non_deterministic_snapshots - { - 'a': UUID(...), - 'b': { - 'b_1': 'This is deterministic', - 'b_2': datetime.datetime(...), - }, - 'c': [ - 'Your wish is my command', - 'Do not replace this one', - ], - } ---- -# name: test_non_deterministic_snapshots.1 - { - 'a': UUID('06335e84-2872-4914-8c5d-3ed07d2a2f16'), - 'b': { - 'b_1': 'This is deterministic', - 'b_2': datetime.datetime(2020, 5, 31, 0, 0), - }, - 'c': [ - 'Replace this one', - 'Do not replace this one', - ], - } ---- # name: test_numbers 3.5 --- diff --git a/tests/syrupy/extensions/amber/__snapshots__/test_matchers.ambr b/tests/syrupy/extensions/amber/__snapshots__/test_matchers.ambr deleted file mode 100644 index 0ef5a4f5..00000000 --- a/tests/syrupy/extensions/amber/__snapshots__/test_matchers.ambr +++ /dev/null @@ -1,19 +0,0 @@ -# name: test_matches_expected_type - { - 'date_created': , - 'nested': { - 'id': , - }, - 'some_uuid': , - } ---- -# name: test_raises_unexpected_type - { - 'date_created': , - 'date_updated': datetime.date(2020, 6, 1), - 'nested': { - 'id': , - }, - 'some_uuid': , - } ---- diff --git a/tests/syrupy/extensions/amber/test_filters.py b/tests/syrupy/extensions/amber/test_amber_filters.py similarity index 100% rename from tests/syrupy/extensions/amber/test_filters.py rename to tests/syrupy/extensions/amber/test_amber_filters.py diff --git a/tests/syrupy/extensions/amber/test_matchers.py b/tests/syrupy/extensions/amber/test_amber_matchers.py similarity index 57% rename from tests/syrupy/extensions/amber/test_matchers.py rename to tests/syrupy/extensions/amber/test_amber_matchers.py index 8ada2e6a..16962c38 100644 --- a/tests/syrupy/extensions/amber/test_matchers.py +++ b/tests/syrupy/extensions/amber/test_amber_matchers.py @@ -3,6 +3,7 @@ import pytest +from syrupy.extensions.amber.serializer import Repr from syrupy.matchers import ( PathTypeError, path_type, @@ -44,3 +45,28 @@ def test_raises_unexpected_type(snapshot): assert actual == snapshot(matcher=path_type(**kwargs, strict=False)) with pytest.raises(PathTypeError, match="does not match any of the expected"): assert actual == snapshot(matcher=path_type(**kwargs)) + + +def test_non_deterministic_snapshots(snapshot): + def matcher(data, path): + if isinstance(data, uuid.UUID): + return Repr("UUID(...)") + if isinstance(data, datetime.datetime): + return Repr("datetime.datetime(...)") + if tuple(p for p, _ in path[-2:]) == ("c", 0): + return "Your wish is my command" + return data + + assert { + "a": uuid.uuid4(), + "b": {"b_1": "This is deterministic", "b_2": datetime.datetime.now()}, + "c": ["Replace this one", "Do not replace this one"], + } == snapshot(matcher=matcher) + assert { + "a": uuid.UUID("06335e84-2872-4914-8c5d-3ed07d2a2f16"), + "b": { + "b_1": "This is deterministic", + "b_2": datetime.datetime(year=2020, month=5, day=31), + }, + "c": ["Replace this one", "Do not replace this one"], + } == snapshot diff --git a/tests/syrupy/extensions/amber/test_extension_amber.py b/tests/syrupy/extensions/amber/test_amber_serializer.py similarity index 83% rename from tests/syrupy/extensions/amber/test_extension_amber.py rename to tests/syrupy/extensions/amber/test_amber_serializer.py index 02a3c008..df464c74 100644 --- a/tests/syrupy/extensions/amber/test_extension_amber.py +++ b/tests/syrupy/extensions/amber/test_amber_serializer.py @@ -1,11 +1,7 @@ -import uuid from collections import namedtuple -from datetime import datetime import pytest -from syrupy.extensions.amber.serializer import Repr - def test_non_snapshots(snapshot): with pytest.raises(AssertionError): @@ -203,28 +199,3 @@ def test_parameter_with_dot(parameter_with_dot, snapshot): def test_doubly_parametrized(parameter_1, parameter_2, snapshot): assert parameter_1 == snapshot assert parameter_2 == snapshot - - -def test_non_deterministic_snapshots(snapshot): - def matcher(data, path): - if isinstance(data, uuid.UUID): - return Repr("UUID(...)") - if isinstance(data, datetime): - return Repr("datetime.datetime(...)") - if tuple(p for p, _ in path[-2:]) == ("c", 0): - return "Your wish is my command" - return data - - assert { - "a": uuid.uuid4(), - "b": {"b_1": "This is deterministic", "b_2": datetime.now()}, - "c": ["Replace this one", "Do not replace this one"], - } == snapshot(matcher=matcher) - assert { - "a": uuid.UUID("06335e84-2872-4914-8c5d-3ed07d2a2f16"), - "b": { - "b_1": "This is deterministic", - "b_2": datetime(year=2020, month=5, day=31), - }, - "c": ["Replace this one", "Do not replace this one"], - } == snapshot diff --git a/tests/syrupy/extensions/file/__init__.py b/tests/syrupy/extensions/file/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/syrupy/extensions/file/__snapshots__/test_image_png.ambr b/tests/syrupy/extensions/file/__snapshots__/test_image_png.ambr new file mode 100644 index 00000000..850ded2d --- /dev/null +++ b/tests/syrupy/extensions/file/__snapshots__/test_image_png.ambr @@ -0,0 +1,3 @@ +# name: test_multiple_snapshot_extensions.1 + b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x002\x00\x00\x002\x04\x03\x00\x00\x00\xec\x11\x95\x82\x00\x00\x00\x1bPLTE\xcc\xcc\xcc\x96\x96\x96\xaa\xaa\xaa\xb7\xb7\xb7\xb1\xb1\xb1\x9c\x9c\x9c\xbe\xbe\xbe\xa3\xa3\xa3\xc5\xc5\xc5\x05\xa4\xf2?\x00\x00\x00\tpHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x00AIDAT8\x8dc`\x18\x05\xa3\x80\xfe\x80I\xd9\xdc\x00F\xa2\x02\x16\x86\x88\x00\xa6\x16\x10\x89.\xc3\x1a" \xc0\x11\x01"\xd1e\xd8\x12#\x028"@$\x86=*\xe6\x06L- \x92zn\x1f\x05\xc3\x1b\x00\x00\xe5\xfb\x08g\r"af\x00\x00\x00\x00IEND\xaeB`\x82' +--- diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_image/test_image.png b/tests/syrupy/extensions/file/__snapshots__/test_image_png/test_image.png similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_image/test_image.png rename to tests/syrupy/extensions/file/__snapshots__/test_image_png/test_image.png diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.2.png b/tests/syrupy/extensions/file/__snapshots__/test_image_png/test_multiple_snapshot_extensions.2.png similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.2.png rename to tests/syrupy/extensions/file/__snapshots__/test_image_png/test_multiple_snapshot_extensions.2.png diff --git a/tests/syrupy/extensions/file/__snapshots__/test_image_png/test_multiple_snapshot_extensions.png b/tests/syrupy/extensions/file/__snapshots__/test_image_png/test_multiple_snapshot_extensions.png new file mode 100644 index 0000000000000000000000000000000000000000..7eb2b9ad8705a5c9b521a00ebfa3849dfecc8af8 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_>3?$zOPHh5G(g8jpu4m4inKo_Os#UADZ{NOg zNOuZqb%w(bDpwecm^^DC&S7B0Bo;>Se apb<}hbENYsC8hxlWAJqKb6Mw<&;$U~ggUnX literal 0 HcmV?d00001 diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_image.ambr b/tests/syrupy/extensions/file/__snapshots__/test_image_svg.ambr similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_image.ambr rename to tests/syrupy/extensions/file/__snapshots__/test_image_svg.ambr diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_image/test_image_vector.svg b/tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_image.svg similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_image/test_image_vector.svg rename to tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_image.svg diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.3.svg b/tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.2.svg similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.3.svg rename to tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.2.svg diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.svg b/tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.svg similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_image/test_multiple_snapshot_extensions.svg rename to tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.svg diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_name.raw b/tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_name.raw similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_name.raw rename to tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_name.raw diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedx.raw b/tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedx.raw similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedx.raw rename to tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedx.raw diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedy.raw b/tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedy.raw similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedy.raw rename to tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedy.raw diff --git a/tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedz.raw b/tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedz.raw similarity index 100% rename from tests/syrupy/extensions/__snapshots__/test_extension_single_file/TestClass.test_class_method_parametrizedz.raw rename to tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedz.raw diff --git a/tests/syrupy/extensions/file/test_image_png.py b/tests/syrupy/extensions/file/test_image_png.py new file mode 100644 index 00000000..c2b6a450 --- /dev/null +++ b/tests/syrupy/extensions/file/test_image_png.py @@ -0,0 +1,33 @@ +import base64 + +import pytest + +from syrupy.extensions.image import PNGImageSnapshotExtension + +actual_png = base64.b64decode( + b"iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAG1BMVEXMzMy" + b"Wlpaqqqq3t7exsbGcnJy+vr6jo6PFxcUFpPI/AAAACXBIWXMAAA7EAAAOxA" + b"GVKw4bAAAAQUlEQVQ4jWNgGAWjgP6ASdncAEaiAhaGiACmFhCJLsMaIiDAE" + b"QEi0WXYEiMCOCJAJIY9KuYGTC0gknpuHwXDGwAA5fsIZw0iYWYAAAAASUVO" + b"RK5CYII=" +) + + +@pytest.fixture +def snapshot_png(snapshot): + return snapshot.use_extension(PNGImageSnapshotExtension) + + +def test_image(snapshot_png): + assert actual_png == snapshot_png + + +def test_multiple_snapshot_extensions(snapshot): + """ + Example of switching extension classes on the fly. + These should be indexed in order of assertion. + """ + assert actual_png == snapshot(extension_class=PNGImageSnapshotExtension) + assert actual_png == snapshot() # uses initial extension class + assert snapshot._extension is not None + assert actual_png == snapshot(extension_class=PNGImageSnapshotExtension) diff --git a/tests/syrupy/extensions/test_extension_image.py b/tests/syrupy/extensions/file/test_image_svg.py similarity index 54% rename from tests/syrupy/extensions/test_extension_image.py rename to tests/syrupy/extensions/file/test_image_svg.py index d2b94a84..0d70e502 100644 --- a/tests/syrupy/extensions/test_extension_image.py +++ b/tests/syrupy/extensions/file/test_image_svg.py @@ -1,19 +1,7 @@ -import base64 - import pytest -from syrupy.extensions.image import ( - PNGImageSnapshotExtension, - SVGImageSnapshotExtension, -) +from syrupy.extensions.image import SVGImageSnapshotExtension -actual_png = base64.b64decode( - b"iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAG1BMVEXMzMy" - b"Wlpaqqqq3t7exsbGcnJy+vr6jo6PFxcUFpPI/AAAACXBIWXMAAA7EAAAOxA" - b"GVKw4bAAAAQUlEQVQ4jWNgGAWjgP6ASdncAEaiAhaGiACmFhCJLsMaIiDAE" - b"QEi0WXYEiMCOCJAJIY9KuYGTC0gknpuHwXDGwAA5fsIZw0iYWYAAAAASUVO" - b"RK5CYII=" -) actual_svg = ( '' '' @@ -28,19 +16,12 @@ @pytest.fixture -def snapshot_png(snapshot): - return snapshot.use_extension(PNGImageSnapshotExtension) - +def snapshot_svg(snapshot): + return snapshot.use_extension(SVGImageSnapshotExtension) -def test_image(snapshot_png): - assert actual_png == snapshot_png - -def test_image_vector(snapshot): - """ - Example of creating a previewable svg snapshot - """ - assert snapshot(extension_class=SVGImageSnapshotExtension) == actual_svg +def test_image(snapshot_svg): + assert actual_svg == snapshot_svg def test_multiple_snapshot_extensions(snapshot): @@ -51,5 +32,4 @@ def test_multiple_snapshot_extensions(snapshot): assert actual_svg == snapshot(extension_class=SVGImageSnapshotExtension) assert actual_svg == snapshot() # uses initial extension class assert snapshot._extension is not None - assert actual_png == snapshot(extension_class=PNGImageSnapshotExtension) assert actual_svg == snapshot(extension_class=SVGImageSnapshotExtension) diff --git a/tests/syrupy/extensions/test_extension_single_file.py b/tests/syrupy/extensions/file/test_single_file.py similarity index 100% rename from tests/syrupy/extensions/test_extension_single_file.py rename to tests/syrupy/extensions/file/test_single_file.py diff --git a/tests/syrupy/extensions/test_extension_base.py b/tests/syrupy/extensions/test_base.py similarity index 100% rename from tests/syrupy/extensions/test_extension_base.py rename to tests/syrupy/extensions/test_base.py From 20ebcfc25ba6e834d143fa2145c45dc98871a08f Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 02:15:15 -0400 Subject: [PATCH 03/16] test: refactor use rematch lines --- tests/integration/test_pytest_extension.py | 3 +- .../test_snapshot_option_extension.py | 12 ++++--- .../test_snapshot_option_update.py | 24 ++++++------- .../test_snapshot_use_extension.py | 35 +++++++++---------- 4 files changed, 36 insertions(+), 38 deletions(-) diff --git a/tests/integration/test_pytest_extension.py b/tests/integration/test_pytest_extension.py index 0d44b0de..8a327233 100644 --- a/tests/integration/test_pytest_extension.py +++ b/tests/integration/test_pytest_extension.py @@ -20,6 +20,5 @@ def test_example(snapshot): testdir.makepyfile(conftest=conftest) testdir.makepyfile(test_file=testcase) result = testdir.runpytest("test_file.py", "-v", "--snapshot-update") - result_stdout = result.stdout.str() + result.stdout.re_match_lines((r".*test_file.py::CUSTOM.*")) assert result.ret == 0 - assert "test_file.py::CUSTOM" in result_stdout diff --git a/tests/integration/test_snapshot_option_extension.py b/tests/integration/test_snapshot_option_extension.py index ca7bbd4a..546da3ff 100644 --- a/tests/integration/test_snapshot_option_extension.py +++ b/tests/integration/test_snapshot_option_extension.py @@ -23,8 +23,7 @@ def test_snapshot_default_extension_option_success(testfile): "--snapshot-default-extension", "syrupy.extensions.single_file.SingleFileSnapshotExtension", ) - result_stdout = result.stdout.str() - assert "1 snapshot generated." in result_stdout + result.stdout.re_match_lines((r"1 snapshot generated\.")) assert Path( testfile.tmpdir, "__snapshots__", "test_file", "test_default.raw" ).exists() @@ -38,9 +37,12 @@ def test_snapshot_default_extension_option_failure(testfile): "--snapshot-default-extension", "syrupy.extensions.amber.DoesNotExistExtension", ) - result_stderr = result.stderr.str() - assert "error: argument --snapshot-default-extension" in result_stderr - assert "Member 'DoesNotExistExtension' not found" in result_stderr + result.stderr.re_match_lines( + ( + r".*error: argument --snapshot-default-extension" + r": Member 'DoesNotExistExtension' not found.*", + ) + ) assert not Path( testfile.tmpdir, "__snapshots__", "test_file", "test_default.raw" ).exists() diff --git a/tests/integration/test_snapshot_option_update.py b/tests/integration/test_snapshot_option_update.py index 195587cf..9ac833ca 100644 --- a/tests/integration/test_snapshot_option_update.py +++ b/tests/integration/test_snapshot_option_update.py @@ -288,9 +288,8 @@ def test_case_2(self, snapshot): assert Path(testdir.tmpdir, "__snapshots__", "test_content.ambr").exists() result = testdir.runpytest("test_content.py", "-v", "-k test_case_2") - result_stdout = result.stdout.str() - assert "1 snapshot passed" in result_stdout - assert "snapshot unused" not in result_stdout + result.stdout.re_match_lines((r"1 snapshot passed\.")) + result.stdout.no_re_match_line(r".*snapshot unused.*") def test_update_targets_only_selected_module_tests_dash_k(testdir): @@ -309,9 +308,8 @@ def test_case_2(snapshot): assert Path(testdir.tmpdir, "__snapshots__", "test_content.ambr").exists() result = testdir.runpytest("test_content.py", "-v", "-k test_case_2") - result_stdout = result.stdout.str() - assert "1 snapshot passed" in result_stdout - assert "snapshot unused" not in result_stdout + result.stdout.re_match_lines((r"1 snapshot passed\.")) + result.stdout.no_re_match_line(r".*snapshot unused.*") def test_update_targets_only_selected_module_tests_nodes(run_testcases): @@ -389,7 +387,7 @@ def test_update_removes_empty_snapshot_fossil_only(run_testcases): result.stdout.re_match_lines( ( r"10 snapshots passed\. 1 unused snapshot deleted\.", - r"Deleted empty snapshot fossil \(__snapshots__[\\/]empty_snapfile\.ambr\)", + rf"Deleted empty snapshot fossil \({snapfile_empty}\)", ) ) assert result.ret == 0 @@ -404,11 +402,13 @@ def test_update_removes_hanging_snapshot_fossil_file(run_testcases): testdir.makefile(".abc", **{str(snapfile_hanging): ""}) assert snapfile_hanging.exists() result = testdir.runpytest("-v", "--snapshot-update") - result_stdout = result.stdout.str() - assert str(snapfile_used) not in result_stdout - assert "1 unused snapshot deleted" in result_stdout - assert "unknown snapshot" in result_stdout - assert str(snapfile_hanging) in result_stdout + result.stdout.re_match_lines( + ( + r"10 snapshots passed\. 1 unused snapshot deleted\.", + rf"Deleted unknown snapshot fossil \({snapfile_hanging}\)", + ) + ) + result.stdout.no_re_match_line(rf".*{snapfile_used}.*") assert result.ret == 0 assert snapfile_used.exists() assert not snapfile_hanging.exists() diff --git a/tests/integration/test_snapshot_use_extension.py b/tests/integration/test_snapshot_use_extension.py index 5c30ba35..26df61b8 100644 --- a/tests/integration/test_snapshot_use_extension.py +++ b/tests/integration/test_snapshot_use_extension.py @@ -97,24 +97,23 @@ def generate_snapshots(testdir, testcases_initial): def test_unsaved_snapshots(testdir, testcases_initial): testdir.makepyfile(test_file=testcases_initial["passed"]) result = testdir.runpytest("-v") - output = result.stdout.str() - assert "Snapshot 'test_passed_single' does not exist" in output - assert "+ b'passed1'" in output + result.stdout.re_match_lines( + (r".*Snapshot 'test_passed_single' does not exist!", r".*\+ b'passed1'") + ) assert result.ret == 1 def test_failed_snapshots(testdir, testcases_initial): testdir.makepyfile(test_file=testcases_initial["failed"]) result = testdir.runpytest("-v", "--snapshot-update") - assert "2 snapshots failed" in result.stdout.str() + result.stdout.re_match_lines((r"2 snapshots failed\.")) assert result.ret == 1 def test_generated_snapshots(generate_snapshots): result = generate_snapshots[0] - result_stdout = result.stdout.str() - assert "4 snapshots generated" in result_stdout - assert "snapshots unused" not in result_stdout + result.stdout.re_match_lines((r"4 snapshots generated\.")) + result.stdout.no_re_match_line(r".*snapshots unused.*") assert result.ret == 0 @@ -122,9 +121,7 @@ def test_unmatched_snapshots(generate_snapshots, testcases_updated): testdir = generate_snapshots[1] testdir.makepyfile(test_file=testcases_updated["passed"]) result = testdir.runpytest("-v") - result_stdout = result.stdout.str() - assert "1 snapshot failed" in result_stdout - assert "2 snapshots unused" in result_stdout + result.stdout.re_match_lines((r"1 snapshot failed\. 2 snapshots unused\.")) assert result.ret == 1 @@ -132,18 +129,18 @@ def test_updated_snapshots(generate_snapshots, testcases_updated): testdir = generate_snapshots[1] testdir.makepyfile(test_file=testcases_updated["passed"]) result = testdir.runpytest("-v", "--snapshot-update") - result_stdout = result.stdout.str() - assert "1 snapshot updated" in result_stdout - assert "2 unused snapshots deleted" in result_stdout + result.stdout.re_match_lines((r"1 snapshot updated\. 2 unused snapshots deleted\.")) assert result.ret == 0 def test_warns_on_snapshot_name(generate_snapshots): result = generate_snapshots[0] - result_stdout = result.stdout.str() - assert "4 snapshots generated" in result_stdout - assert "Warning:" in result_stdout - assert "Can not relate snapshot name" in result_stdout - assert "Can not relate snapshot location" in result_stdout - assert "test_passed_custom" in result_stdout + result.stdout.re_match_lines( + ( + r".*Warning:\s+", + r"\s+Can not relate snapshot location", + r"\s+Can not relate snapshot name", + r"4 snapshots generated\.", + ) + ) assert result.ret == 0 From 4c28d9f7cf1d34f4bd95b2027aa76bff84ff1a50 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 02:30:12 -0400 Subject: [PATCH 04/16] fix: do not include empty extension in filename --- src/syrupy/extensions/base.py | 3 ++- tests/integration/test_snapshot_option_update.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/syrupy/extensions/base.py b/src/syrupy/extensions/base.py index 7bd4dffb..17933798 100644 --- a/src/syrupy/extensions/base.py +++ b/src/syrupy/extensions/base.py @@ -83,7 +83,8 @@ def get_snapshot_name(self, *, index: int = 0) -> str: def get_location(self, *, index: int) -> str: """Returns full location where snapshot data is stored.""" basename = self._get_file_basename(index=index) - return str(Path(self._dirname).joinpath(f"{basename}.{self._file_extension}")) + fileext = f".{self._file_extension}" if self._file_extension else "" + return str(Path(self._dirname).joinpath(f"{basename}{fileext}")) def is_snapshot_location(self, *, location: str) -> bool: """Checks if supplied location is valid for this snapshot extension""" diff --git a/tests/integration/test_snapshot_option_update.py b/tests/integration/test_snapshot_option_update.py index 9ac833ca..815bb9a5 100644 --- a/tests/integration/test_snapshot_option_update.py +++ b/tests/integration/test_snapshot_option_update.py @@ -387,7 +387,7 @@ def test_update_removes_empty_snapshot_fossil_only(run_testcases): result.stdout.re_match_lines( ( r"10 snapshots passed\. 1 unused snapshot deleted\.", - rf"Deleted empty snapshot fossil \({snapfile_empty}\)", + r"Deleted empty snapshot fossil \(__snapshots__[\\/]empty_snapfile\.ambr\)", ) ) assert result.ret == 0 @@ -405,7 +405,8 @@ def test_update_removes_hanging_snapshot_fossil_file(run_testcases): result.stdout.re_match_lines( ( r"10 snapshots passed\. 1 unused snapshot deleted\.", - rf"Deleted unknown snapshot fossil \({snapfile_hanging}\)", + r"Deleted unknown snapshot fossil " + r"\(__snapshots__[\\/]hanging_snapfile\.abc\)", ) ) result.stdout.no_re_match_line(rf".*{snapfile_used}.*") From 1c97282073163071112e160fe0cc480743b2b3a6 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 03:54:54 -0400 Subject: [PATCH 05/16] refactor: extract class name from node logic --- src/syrupy/__init__.py | 9 ++++++--- src/syrupy/constants.py | 2 ++ src/syrupy/location.py | 5 +++-- .../TestClass.test_class_method_name.raw | 0 .../TestClass.test_class_method_parametrizedx.raw | 0 .../TestClass.test_class_method_parametrizedy.raw | 0 .../TestClass.test_class_method_parametrizedz.raw | 0 tests/syrupy/extensions/amber/test_amber_filters.py | 4 ++-- tests/syrupy/extensions/{file => image}/__init__.py | 0 .../__snapshots__/test_image_png.ambr | 0 .../__snapshots__/test_image_png/test_image.png | Bin .../test_multiple_snapshot_extensions.2.png | Bin .../test_multiple_snapshot_extensions.png | Bin .../__snapshots__/test_image_svg.ambr | 0 .../__snapshots__/test_image_svg/test_image.svg | 0 .../test_multiple_snapshot_extensions.2.svg | 0 .../test_multiple_snapshot_extensions.svg | 0 .../extensions/{file => image}/test_image_png.py | 0 .../extensions/{file => image}/test_image_svg.py | 0 .../extensions/{file => }/test_single_file.py | 0 20 files changed, 13 insertions(+), 7 deletions(-) rename tests/syrupy/extensions/{file => }/__snapshots__/test_single_file/TestClass.test_class_method_name.raw (100%) rename tests/syrupy/extensions/{file => }/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedx.raw (100%) rename tests/syrupy/extensions/{file => }/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedy.raw (100%) rename tests/syrupy/extensions/{file => }/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedz.raw (100%) rename tests/syrupy/extensions/{file => image}/__init__.py (100%) rename tests/syrupy/extensions/{file => image}/__snapshots__/test_image_png.ambr (100%) rename tests/syrupy/extensions/{file => image}/__snapshots__/test_image_png/test_image.png (100%) rename tests/syrupy/extensions/{file => image}/__snapshots__/test_image_png/test_multiple_snapshot_extensions.2.png (100%) rename tests/syrupy/extensions/{file => image}/__snapshots__/test_image_png/test_multiple_snapshot_extensions.png (100%) rename tests/syrupy/extensions/{file => image}/__snapshots__/test_image_svg.ambr (100%) rename tests/syrupy/extensions/{file => image}/__snapshots__/test_image_svg/test_image.svg (100%) rename tests/syrupy/extensions/{file => image}/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.2.svg (100%) rename tests/syrupy/extensions/{file => image}/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.svg (100%) rename tests/syrupy/extensions/{file => image}/test_image_png.py (100%) rename tests/syrupy/extensions/{file => image}/test_image_svg.py (100%) rename tests/syrupy/extensions/{file => }/test_single_file.py (100%) diff --git a/src/syrupy/__init__.py b/src/syrupy/__init__.py index 004e5d10..ae4f574f 100644 --- a/src/syrupy/__init__.py +++ b/src/syrupy/__init__.py @@ -12,7 +12,10 @@ import pytest from .assertion import SnapshotAssertion -from .constants import DISABLE_COLOR_ENV_VAR +from .constants import ( + DISABLE_COLOR_ENV_VAR, + PYTEST_NODE_SEP, +) from .exceptions import FailedToLoadModuleMember from .extensions import DEFAULT_EXTENSION from .location import TestLocation @@ -102,11 +105,11 @@ def snapshot_name(name: str) -> str: def __is_testpath(arg: str) -> bool: - return not arg.startswith("-") and bool(glob.glob(arg.split("::")[0])) + return not arg.startswith("-") and bool(glob.glob(arg.split(PYTEST_NODE_SEP)[0])) def __is_testnode(arg: str) -> bool: - return __is_testpath(arg) and "::" in arg + return __is_testpath(arg) and PYTEST_NODE_SEP in arg def __is_testmodule(arg: str) -> bool: diff --git a/src/syrupy/constants.py b/src/syrupy/constants.py index 5cbe9da4..0cb5fd03 100644 --- a/src/syrupy/constants.py +++ b/src/syrupy/constants.py @@ -10,3 +10,5 @@ DISABLE_COLOR_ENV_VAR = "ANSI_COLORS_DISABLED" DISABLE_COLOR_ENV_VARS = {DISABLE_COLOR_ENV_VAR, "NO_COLOR"} + +PYTEST_NODE_SEP = "::" diff --git a/src/syrupy/location.py b/src/syrupy/location.py index 4b2d9611..dfd66217 100644 --- a/src/syrupy/location.py +++ b/src/syrupy/location.py @@ -5,6 +5,8 @@ Optional, ) +from syrupy.constants import PYTEST_NODE_SEP + class TestLocation: def __init__(self, node: Any): @@ -17,8 +19,7 @@ def __init__(self, node: Any): @property def classname(self) -> Optional[str]: - _, __, qualname = self._node.location - return ".".join(list(self.__valid_ids(qualname))[:-1]) or None + return ".".join(self._node.nodeid.split(PYTEST_NODE_SEP)[1:-1]) or None @property def filename(self) -> str: diff --git a/tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_name.raw b/tests/syrupy/extensions/__snapshots__/test_single_file/TestClass.test_class_method_name.raw similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_name.raw rename to tests/syrupy/extensions/__snapshots__/test_single_file/TestClass.test_class_method_name.raw diff --git a/tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedx.raw b/tests/syrupy/extensions/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedx.raw similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedx.raw rename to tests/syrupy/extensions/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedx.raw diff --git a/tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedy.raw b/tests/syrupy/extensions/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedy.raw similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedy.raw rename to tests/syrupy/extensions/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedy.raw diff --git a/tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedz.raw b/tests/syrupy/extensions/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedz.raw similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedz.raw rename to tests/syrupy/extensions/__snapshots__/test_single_file/TestClass.test_class_method_parametrizedz.raw diff --git a/tests/syrupy/extensions/amber/test_amber_filters.py b/tests/syrupy/extensions/amber/test_amber_filters.py index d813ea95..726d95eb 100644 --- a/tests/syrupy/extensions/amber/test_amber_filters.py +++ b/tests/syrupy/extensions/amber/test_amber_filters.py @@ -8,7 +8,7 @@ ) -def test_filters_path_noop(snapshot): +def test_filters_path_noop(): with pytest.raises(TypeError, match="required positional argument"): paths() @@ -23,7 +23,7 @@ def test_filters_expected_paths(snapshot): assert actual == snapshot(exclude=paths("0", "date", "nested.id", "list.0")) -def test_filters_prop_noop(snapshot): +def test_filters_prop_noop(): with pytest.raises(TypeError, match="required positional argument"): props() diff --git a/tests/syrupy/extensions/file/__init__.py b/tests/syrupy/extensions/image/__init__.py similarity index 100% rename from tests/syrupy/extensions/file/__init__.py rename to tests/syrupy/extensions/image/__init__.py diff --git a/tests/syrupy/extensions/file/__snapshots__/test_image_png.ambr b/tests/syrupy/extensions/image/__snapshots__/test_image_png.ambr similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_image_png.ambr rename to tests/syrupy/extensions/image/__snapshots__/test_image_png.ambr diff --git a/tests/syrupy/extensions/file/__snapshots__/test_image_png/test_image.png b/tests/syrupy/extensions/image/__snapshots__/test_image_png/test_image.png similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_image_png/test_image.png rename to tests/syrupy/extensions/image/__snapshots__/test_image_png/test_image.png diff --git a/tests/syrupy/extensions/file/__snapshots__/test_image_png/test_multiple_snapshot_extensions.2.png b/tests/syrupy/extensions/image/__snapshots__/test_image_png/test_multiple_snapshot_extensions.2.png similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_image_png/test_multiple_snapshot_extensions.2.png rename to tests/syrupy/extensions/image/__snapshots__/test_image_png/test_multiple_snapshot_extensions.2.png diff --git a/tests/syrupy/extensions/file/__snapshots__/test_image_png/test_multiple_snapshot_extensions.png b/tests/syrupy/extensions/image/__snapshots__/test_image_png/test_multiple_snapshot_extensions.png similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_image_png/test_multiple_snapshot_extensions.png rename to tests/syrupy/extensions/image/__snapshots__/test_image_png/test_multiple_snapshot_extensions.png diff --git a/tests/syrupy/extensions/file/__snapshots__/test_image_svg.ambr b/tests/syrupy/extensions/image/__snapshots__/test_image_svg.ambr similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_image_svg.ambr rename to tests/syrupy/extensions/image/__snapshots__/test_image_svg.ambr diff --git a/tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_image.svg b/tests/syrupy/extensions/image/__snapshots__/test_image_svg/test_image.svg similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_image.svg rename to tests/syrupy/extensions/image/__snapshots__/test_image_svg/test_image.svg diff --git a/tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.2.svg b/tests/syrupy/extensions/image/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.2.svg similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.2.svg rename to tests/syrupy/extensions/image/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.2.svg diff --git a/tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.svg b/tests/syrupy/extensions/image/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.svg similarity index 100% rename from tests/syrupy/extensions/file/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.svg rename to tests/syrupy/extensions/image/__snapshots__/test_image_svg/test_multiple_snapshot_extensions.svg diff --git a/tests/syrupy/extensions/file/test_image_png.py b/tests/syrupy/extensions/image/test_image_png.py similarity index 100% rename from tests/syrupy/extensions/file/test_image_png.py rename to tests/syrupy/extensions/image/test_image_png.py diff --git a/tests/syrupy/extensions/file/test_image_svg.py b/tests/syrupy/extensions/image/test_image_svg.py similarity index 100% rename from tests/syrupy/extensions/file/test_image_svg.py rename to tests/syrupy/extensions/image/test_image_svg.py diff --git a/tests/syrupy/extensions/file/test_single_file.py b/tests/syrupy/extensions/test_single_file.py similarity index 100% rename from tests/syrupy/extensions/file/test_single_file.py rename to tests/syrupy/extensions/test_single_file.py From 0a91953b4ad33e217e0cbef505eab8a28a532402 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 06:08:31 -0400 Subject: [PATCH 06/16] test: syrupy location --- src/syrupy/__init__.py | 4 +- src/syrupy/assertion.py | 4 +- src/syrupy/extensions/base.py | 8 +-- src/syrupy/location.py | 2 +- src/syrupy/report.py | 6 +- tests/syrupy/test_location.py | 113 ++++++++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 tests/syrupy/test_location.py diff --git a/src/syrupy/__init__.py b/src/syrupy/__init__.py index ae4f574f..f94c6080 100644 --- a/src/syrupy/__init__.py +++ b/src/syrupy/__init__.py @@ -18,7 +18,7 @@ ) from .exceptions import FailedToLoadModuleMember from .extensions import DEFAULT_EXTENSION -from .location import TestLocation +from .location import NodeLocation from .session import SnapshotSession from .terminal import ( received_style, @@ -183,6 +183,6 @@ def snapshot(request: Any) -> "SnapshotAssertion": return SnapshotAssertion( update_snapshots=request.config.option.update_snapshots, extension_class=request.config.option.default_extension, - test_location=TestLocation(request.node), + test_location=NodeLocation(request.node), session=request.session.config._syrupy, ) diff --git a/src/syrupy/assertion.py b/src/syrupy/assertion.py index 57d69698..36efb2d0 100644 --- a/src/syrupy/assertion.py +++ b/src/syrupy/assertion.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from .extensions.base import AbstractSyrupyExtension - from .location import TestLocation + from .location import NodeLocation from .session import SnapshotSession from .types import ( PropertyFilter, @@ -47,7 +47,7 @@ class SnapshotAssertion: name: str = attr.ib(default="snapshot") _session: "SnapshotSession" = attr.ib(kw_only=True) _extension_class: Type["AbstractSyrupyExtension"] = attr.ib(kw_only=True) - _test_location: "TestLocation" = attr.ib(kw_only=True) + _test_location: "NodeLocation" = attr.ib(kw_only=True) _update_snapshots: bool = attr.ib(kw_only=True) _exclude: Optional["PropertyFilter"] = attr.ib(init=False, default=None) _extension: Optional["AbstractSyrupyExtension"] = attr.ib(init=False, default=None) diff --git a/src/syrupy/extensions/base.py b/src/syrupy/extensions/base.py index 17933798..b9412a09 100644 --- a/src/syrupy/extensions/base.py +++ b/src/syrupy/extensions/base.py @@ -44,7 +44,7 @@ from syrupy.utils import walk_snapshot_dir if TYPE_CHECKING: - from syrupy.location import TestLocation + from syrupy.location import NodeLocation from syrupy.types import ( PropertyFilter, PropertyMatcher, @@ -72,7 +72,7 @@ def serialize( class SnapshotFossilizer(ABC): @property @abstractmethod - def test_location(self) -> "TestLocation": + def test_location(self) -> "NodeLocation": raise NotImplementedError def get_snapshot_name(self, *, index: int = 0) -> str: @@ -374,9 +374,9 @@ def __strip_ends(self, line: str) -> str: class AbstractSyrupyExtension(SnapshotSerializer, SnapshotFossilizer, SnapshotReporter): - def __init__(self, test_location: "TestLocation"): + def __init__(self, test_location: "NodeLocation"): self._test_location = test_location @property - def test_location(self) -> "TestLocation": + def test_location(self) -> "NodeLocation": return self._test_location diff --git a/src/syrupy/location.py b/src/syrupy/location.py index dfd66217..b5d10c58 100644 --- a/src/syrupy/location.py +++ b/src/syrupy/location.py @@ -8,7 +8,7 @@ from syrupy.constants import PYTEST_NODE_SEP -class TestLocation: +class NodeLocation: def __init__(self, node: Any): self._node = node self.filepath = self._node.fspath diff --git a/src/syrupy/report.py b/src/syrupy/report.py index ee203a47..03359ccd 100644 --- a/src/syrupy/report.py +++ b/src/syrupy/report.py @@ -19,7 +19,7 @@ SnapshotFossils, SnapshotUnknownFossil, ) -from .location import TestLocation +from .location import NodeLocation from .terminal import ( bold, error_style, @@ -99,7 +99,7 @@ def unused(self) -> "SnapshotFossils": ): snapshot_location = unused_snapshot_fossil.location if self.is_providing_paths and not any( - TestLocation(node).matches_snapshot_location(snapshot_location) + NodeLocation(node).matches_snapshot_location(snapshot_location) for node in self.ran_items ): continue @@ -112,7 +112,7 @@ def unused(self) -> "SnapshotFossils": snapshot for snapshot in unused_snapshot_fossil if any( - TestLocation(node).matches_snapshot_name(snapshot.name) + NodeLocation(node).matches_snapshot_name(snapshot.name) for node in self.ran_items ) } diff --git a/tests/syrupy/test_location.py b/tests/syrupy/test_location.py new file mode 100644 index 00000000..38727415 --- /dev/null +++ b/tests/syrupy/test_location.py @@ -0,0 +1,113 @@ +from pathlib import Path +from unittest.mock import MagicMock + +import pytest +from _pytest.nodes import Item + +from syrupy.location import NodeLocation + + +def mock_pytest_item(node_id: str, method_name: str) -> Item: + mock_node = MagicMock(spec=Item) + mock_node.nodeid = node_id + [filepath, *_, nodename] = node_id.split("::") + mock_node.name = nodename + mock_node.fspath = filepath + mock_node.obj = MagicMock() + mock_node.obj.__module__ = Path(filepath).stem + mock_node.obj.__name__ = method_name + return mock_node + + +@pytest.mark.parametrize( + "node_id, method_name, expected_filename," + "expected_classname,expected_snapshotname", + ( + ( + "/tests/module/test_file.py::TestClass::method_name", + "method_name", + "test_file", + "TestClass", + "TestClass.method_name", + ), + ( + "/tests/module/test_file.py::TestClass::method_name[1]", + "method_name", + "test_file", + "TestClass", + "TestClass.method_name[1]", + ), + ( + "/tests/module/nest/test_file.py::TestClass::TestSubClass::method_name", + "method_name", + "test_file", + "TestClass.TestSubClass", + "TestClass.TestSubClass.method_name", + ), + ), +) +def test_location_properties( + node_id, + method_name, + expected_filename, + expected_classname, + expected_snapshotname, +): + location = NodeLocation(mock_pytest_item(node_id, method_name)) + assert location.classname == expected_classname + assert location.filename == expected_filename + assert location.snapshot_name == expected_snapshotname + + +@pytest.mark.parametrize( + "node_id, method_name," + "expected_location_matches, expected_location_misses," + "expected_snapshot_matches, expected_snapshot_misses", + ( + ( + "/tests/module/test_file.py::TestClass::method_name", + "method_name", + ("test_file.snap", "__snapshots__/test_file", "test_file/1.snap"), + ("test.snap", "__others__/test/file.snap"), + ( + "TestClass.method_name", + "TestClass.method_name[1]", + "TestClass.method_name.1", + ), + ("method_name", "TestClass.method_names"), + ), + ( + "/tests/module/test_file.py::TestClass::method_name[1]", + "method_name", + ("test_file.snap", "__snapshots__/test_file", "test_file/1.snap"), + ("test.snap", "__others__/test/file.snap"), + ( + "TestClass.method_name", + "TestClass.method_name[1]", + "TestClass.method_name.1", + ), + ("method_name", "TestClass.method_names"), + ), + ), +) +def test_location_matching( + node_id, + method_name, + expected_location_matches, + expected_location_misses, + expected_snapshot_matches, + expected_snapshot_misses, +): + location = NodeLocation(mock_pytest_item(node_id, method_name)) + + for location_match in expected_location_matches: + assert location.matches_snapshot_location(location_match) + + for location_miss in expected_location_misses: + assert not location.matches_snapshot_location(location_miss) + + for snapshot_match in expected_snapshot_matches: + assert location.matches_snapshot_name(snapshot_match) + + for snapshot_miss in expected_snapshot_misses: + assert not location.matches_snapshot_name(snapshot_miss) From a40aba93380f44708f3d71abc2b49a694d471894 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 18:27:32 -0400 Subject: [PATCH 07/16] test: add failing test case --- tests/integration/test_snapshot_option_update.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/integration/test_snapshot_option_update.py b/tests/integration/test_snapshot_option_update.py index 815bb9a5..84978ceb 100644 --- a/tests/integration/test_snapshot_option_update.py +++ b/tests/integration/test_snapshot_option_update.py @@ -223,7 +223,7 @@ def test_update_targets_only_selected_parametrized_tests_for_update_dash_k( """ import pytest - @pytest.mark.parametrize("actual", [1, "2"]) + @pytest.mark.parametrize("actual", [1, "2", 3]) def test_used(snapshot, actual): assert snapshot == actual """ @@ -231,13 +231,9 @@ def test_used(snapshot, actual): } testdir = run_testcases[1] testdir.makepyfile(**updated_tests) - result = testdir.runpytest("-v", "--snapshot-update", "-k", "test_used[") - result.stdout.re_match_lines( - ( - r"1 snapshot passed\. 1 snapshot updated\. 1 unused snapshot deleted\.", - r"Deleted test_used\[3\] \(__snapshots__[\\/]test_used.ambr\)", - ) - ) + result = testdir.runpytest("-v", "--snapshot-update", "-k", "test_used[2]") + result.stdout.re_match_lines((r"1 snapshot updated\.")) + result.stdout.no_fnmatch_line("*Deleted*") snapshot_path = [testdir.tmpdir, "__snapshots__"] assert Path(*snapshot_path, "test_used.ambr").exists() assert Path(*snapshot_path, "test_updated_1.ambr").exists() @@ -353,7 +349,7 @@ def test_used(snapshot, actual): assert Path("__snapshots__", "test_used.ambr").exists() -def test_update_targets_only_selected_module_test_file_for_removal(run_testcases): +def test_update_targets_only_selected_module_tests_file_for_removal(run_testcases): testdir = run_testcases[1] testdir.makepyfile( test_used=( From 950de5c85c289dd4aa1727e6a2382fd443ddba30 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 18:27:58 -0400 Subject: [PATCH 08/16] fix: use pytest expression to include snapshots --- src/syrupy/__init__.py | 42 ++++-------- src/syrupy/assertion.py | 4 +- src/syrupy/extensions/base.py | 8 +-- src/syrupy/location.py | 2 +- src/syrupy/report.py | 119 ++++++++++++++++++++++++++++++---- src/syrupy/session.py | 7 +- tests/syrupy/test_location.py | 6 +- 7 files changed, 131 insertions(+), 57 deletions(-) diff --git a/src/syrupy/__init__.py b/src/syrupy/__init__.py index f94c6080..a3154716 100644 --- a/src/syrupy/__init__.py +++ b/src/syrupy/__init__.py @@ -1,8 +1,8 @@ import argparse -import glob import sys from gettext import gettext from typing import ( + TYPE_CHECKING, Any, ContextManager, List, @@ -12,13 +12,10 @@ import pytest from .assertion import SnapshotAssertion -from .constants import ( - DISABLE_COLOR_ENV_VAR, - PYTEST_NODE_SEP, -) +from .constants import DISABLE_COLOR_ENV_VAR from .exceptions import FailedToLoadModuleMember from .extensions import DEFAULT_EXTENSION -from .location import NodeLocation +from .location import PyTestLocation from .session import SnapshotSession from .terminal import ( received_style, @@ -30,6 +27,9 @@ import_module_member, ) +if TYPE_CHECKING: + from _pytest.config import Config + def __default_extension_option(value: str) -> Any: try: @@ -104,37 +104,19 @@ def snapshot_name(name: str) -> str: return None -def __is_testpath(arg: str) -> bool: - return not arg.startswith("-") and bool(glob.glob(arg.split(PYTEST_NODE_SEP)[0])) - - -def __is_testnode(arg: str) -> bool: - return __is_testpath(arg) and PYTEST_NODE_SEP in arg - - -def __is_testmodule(arg: str) -> bool: - return arg == "--pyargs" - - def pytest_sessionstart(session: Any) -> None: """ Initialize snapshot session before tests are collected and ran. https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_sessionstart """ - config = session.config - config._syrupy = SnapshotSession( + config: "Config" = session.config + session.config._syrupy = SnapshotSession( warn_unused_snapshots=config.option.warn_unused_snapshots, update_snapshots=config.option.update_snapshots, - base_dir=config.rootdir, - is_providing_paths=any( - __is_testpath(arg) or __is_testmodule(arg) - for arg in config.invocation_params.args - ), - is_providing_nodes=any( - __is_testnode(arg) for arg in config.invocation_params.args - ), + base_dir=str(config.rootpath), + invocation_args=config.invocation_params.args, ) - config._syrupy.start() + session.config._syrupy.start() def pytest_collection_modifyitems( @@ -183,6 +165,6 @@ def snapshot(request: Any) -> "SnapshotAssertion": return SnapshotAssertion( update_snapshots=request.config.option.update_snapshots, extension_class=request.config.option.default_extension, - test_location=NodeLocation(request.node), + test_location=PyTestLocation(request.node), session=request.session.config._syrupy, ) diff --git a/src/syrupy/assertion.py b/src/syrupy/assertion.py index 36efb2d0..f2a9576d 100644 --- a/src/syrupy/assertion.py +++ b/src/syrupy/assertion.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from .extensions.base import AbstractSyrupyExtension - from .location import NodeLocation + from .location import PyTestLocation from .session import SnapshotSession from .types import ( PropertyFilter, @@ -47,7 +47,7 @@ class SnapshotAssertion: name: str = attr.ib(default="snapshot") _session: "SnapshotSession" = attr.ib(kw_only=True) _extension_class: Type["AbstractSyrupyExtension"] = attr.ib(kw_only=True) - _test_location: "NodeLocation" = attr.ib(kw_only=True) + _test_location: "PyTestLocation" = attr.ib(kw_only=True) _update_snapshots: bool = attr.ib(kw_only=True) _exclude: Optional["PropertyFilter"] = attr.ib(init=False, default=None) _extension: Optional["AbstractSyrupyExtension"] = attr.ib(init=False, default=None) diff --git a/src/syrupy/extensions/base.py b/src/syrupy/extensions/base.py index b9412a09..23e411a8 100644 --- a/src/syrupy/extensions/base.py +++ b/src/syrupy/extensions/base.py @@ -44,7 +44,7 @@ from syrupy.utils import walk_snapshot_dir if TYPE_CHECKING: - from syrupy.location import NodeLocation + from syrupy.location import PyTestLocation from syrupy.types import ( PropertyFilter, PropertyMatcher, @@ -72,7 +72,7 @@ def serialize( class SnapshotFossilizer(ABC): @property @abstractmethod - def test_location(self) -> "NodeLocation": + def test_location(self) -> "PyTestLocation": raise NotImplementedError def get_snapshot_name(self, *, index: int = 0) -> str: @@ -374,9 +374,9 @@ def __strip_ends(self, line: str) -> str: class AbstractSyrupyExtension(SnapshotSerializer, SnapshotFossilizer, SnapshotReporter): - def __init__(self, test_location: "NodeLocation"): + def __init__(self, test_location: "PyTestLocation"): self._test_location = test_location @property - def test_location(self) -> "NodeLocation": + def test_location(self) -> "PyTestLocation": return self._test_location diff --git a/src/syrupy/location.py b/src/syrupy/location.py index b5d10c58..fb433039 100644 --- a/src/syrupy/location.py +++ b/src/syrupy/location.py @@ -8,7 +8,7 @@ from syrupy.constants import PYTEST_NODE_SEP -class NodeLocation: +class PyTestLocation: def __init__(self, node: Any): self._node = node self.filepath = self._node.fspath diff --git a/src/syrupy/report.py b/src/syrupy/report.py index 03359ccd..eace4c18 100644 --- a/src/syrupy/report.py +++ b/src/syrupy/report.py @@ -8,18 +8,22 @@ Dict, Iterator, List, + Optional, + Set, + Tuple, ) import attr -import pytest +from _pytest.mark.expression import Expression +from .constants import PYTEST_NODE_SEP from .data import ( Snapshot, SnapshotFossil, SnapshotFossils, SnapshotUnknownFossil, ) -from .location import NodeLocation +from .location import PyTestLocation from .terminal import ( bold, error_style, @@ -29,6 +33,8 @@ ) if TYPE_CHECKING: + import pytest + from .assertion import SnapshotAssertion @@ -38,8 +44,6 @@ class SnapshotReport: all_items: Dict["pytest.Item", bool] = attr.ib() ran_items: Dict["pytest.Item", bool] = attr.ib() update_snapshots: bool = attr.ib() - is_providing_paths: bool = attr.ib() - is_providing_nodes: bool = attr.ib() warn_unused_snapshots: bool = attr.ib() assertions: List["SnapshotAssertion"] = attr.ib() discovered: "SnapshotFossils" = attr.ib(factory=SnapshotFossils) @@ -48,8 +52,12 @@ class SnapshotReport: matched: "SnapshotFossils" = attr.ib(factory=SnapshotFossils) updated: "SnapshotFossils" = attr.ib(factory=SnapshotFossils) used: "SnapshotFossils" = attr.ib(factory=SnapshotFossils) + _invocation_args: Tuple[str, ...] = attr.ib(factory=tuple) + _provided_test_paths: Dict[str, List[str]] = attr.ib(factory=dict) + _keyword_expressions: Set["Expression"] = attr.ib(factory=set) def __attrs_post_init__(self) -> None: + self.__parse_invocation_args() for assertion in self.assertions: self.discovered.merge(assertion.extension.discover_snapshots()) for result in assertion.executions.values(): @@ -67,6 +75,43 @@ def __attrs_post_init__(self) -> None: else: self.failed.update(snapshot_fossil) + def __parse_invocation_args(self) -> None: + arg_groups: List[Tuple[Optional[str], str]] = [] + path_as_package = False + maybe_opt_arg = None + for arg in self._invocation_args: + if arg.strip() == "--pyargs": + path_as_package = True + elif arg.startswith("-"): + if "=" in arg: + arg0, arg1 = arg.split("=") + arg_groups.append((arg0.strip(), arg1.strip())) + elif maybe_opt_arg is None: + maybe_opt_arg = arg + continue # do not reset maybe_opt_arg + else: + arg_groups.append((maybe_opt_arg, arg.strip())) + + maybe_opt_arg = None + + for maybe_opt_arg, arg_value in arg_groups: + if maybe_opt_arg == "-k": # or maybe_opt_arg == "-m": + self._keyword_expressions.add(Expression.compile(arg_value)) + elif maybe_opt_arg is None: + import importlib + + parts = arg_value.split(PYTEST_NODE_SEP) + package_or_filepath = parts[0].strip() + filepath = Path( + importlib.import_module(package_or_filepath).__file__ + if path_as_package + else package_or_filepath + ) + filepath_abs = str( + filepath if filepath.is_absolute() else filepath.absolute() + ) + self._provided_test_paths[filepath_abs] = parts[1:] + @property def num_created(self) -> int: return self._count_snapshots(self.created) @@ -89,7 +134,7 @@ def num_unused(self) -> int: @property def ran_all_collected_tests(self) -> bool: - return self.all_items == self.ran_items and not self.is_providing_nodes + return self.all_items == self.ran_items @property def unused(self) -> "SnapshotFossils": @@ -98,23 +143,25 @@ def unused(self) -> "SnapshotFossils": self.discovered, self.used ): snapshot_location = unused_snapshot_fossil.location - if self.is_providing_paths and not any( - NodeLocation(node).matches_snapshot_location(snapshot_location) - for node in self.ran_items + if self._provided_test_paths and not self._selected_items_match_location( + snapshot_location ): + # Paths/Packages were provided to pytest and the snapshot location + # does not match therefore ignore this unused snapshot fossil file continue - if self.ran_all_collected_tests: + provided_nodes = self._get_matching_path_nodes(snapshot_location) + if self.ran_all_collected_tests and not any(provided_nodes): + # All collected tests were run and files were not filtered by ::node + # therefore the snapshot fossil file at this location can be deleted unused_snapshots = {*unused_snapshot_fossil} mark_for_removal = snapshot_location not in self.used else: unused_snapshots = { snapshot for snapshot in unused_snapshot_fossil - if any( - NodeLocation(node).matches_snapshot_name(snapshot.name) - for node in self.ran_items - ) + if self._selected_items_match_name(snapshot.name) + or self._provided_nodes_match_name(snapshot.name, provided_nodes) } mark_for_removal = False @@ -217,3 +264,49 @@ def _diff_snapshot_fossils( def _count_snapshots(self, snapshot_fossils: "SnapshotFossils") -> int: return sum(len(snapshot_fossil) for snapshot_fossil in snapshot_fossils) + + def _is_matching_path(self, snapshot_location: str, provided_path: str) -> bool: + path = Path(provided_path) + return str(path if path.is_dir() else path.parent) in snapshot_location + + def _get_matching_paths(self, snapshot_location: str) -> Set[str]: + return { + path + for path in self._provided_test_paths + if self._is_matching_path(snapshot_location, path) + } + + def _get_matching_path_nodes(self, snapshot_location: str) -> List[List[str]]: + return [ + self._provided_test_paths[matching_path] + for matching_path in self._get_matching_paths(snapshot_location) + ] + + def _provided_nodes_match_name( + self, snapshot_name: str, provided_nodes: List[List[str]] + ) -> bool: + return any(snapshot_name in ".".join(node_path) for node_path in provided_nodes) + + def _selected_items_match_location(self, snapshot_location: str) -> bool: + return any( + PyTestLocation(item).matches_snapshot_location(snapshot_location) + for item in self.ran_items + ) + + def _selected_items_match_name(self, snapshot_name: str) -> bool: + if self._keyword_expressions: + return self._provided_keywords_match_name(snapshot_name) + return self._ran_items_match_name(snapshot_name) + + def _provided_keywords_match_name(self, snapshot_name: str) -> bool: + names = snapshot_name.split(".") + return any( + expr.evaluate(lambda subname: any(subname in name for name in names)) + for expr in self._keyword_expressions + ) + + def _ran_items_match_name(self, snapshot_name: str) -> bool: + return any( + PyTestLocation(item).matches_snapshot_name(snapshot_name) + for item in self.ran_items + ) diff --git a/src/syrupy/session.py b/src/syrupy/session.py index 92f839ff..4beb3637 100644 --- a/src/syrupy/session.py +++ b/src/syrupy/session.py @@ -5,6 +5,7 @@ Iterable, List, Optional, + Tuple, ) import attr @@ -24,8 +25,7 @@ class SnapshotSession: base_dir: str = attr.ib() update_snapshots: bool = attr.ib() warn_unused_snapshots: bool = attr.ib() - is_providing_paths: bool = attr.ib() - is_providing_nodes: bool = attr.ib() + _invocation_args: Tuple[str, ...] = attr.ib(factory=tuple) report: Optional["SnapshotReport"] = attr.ib(default=None) _all_items: Dict["pytest.Item", bool] = attr.ib(factory=dict) _ran_items: Dict["pytest.Item", bool] = attr.ib(factory=dict) @@ -48,8 +48,7 @@ def finish(self) -> int: assertions=self._assertions, update_snapshots=self.update_snapshots, warn_unused_snapshots=self.warn_unused_snapshots, - is_providing_paths=self.is_providing_paths, - is_providing_nodes=self.is_providing_nodes, + invocation_args=self._invocation_args, ) if self.report.num_unused: if self.update_snapshots: diff --git a/tests/syrupy/test_location.py b/tests/syrupy/test_location.py index 38727415..820cf920 100644 --- a/tests/syrupy/test_location.py +++ b/tests/syrupy/test_location.py @@ -4,7 +4,7 @@ import pytest from _pytest.nodes import Item -from syrupy.location import NodeLocation +from syrupy.location import PyTestLocation def mock_pytest_item(node_id: str, method_name: str) -> Item: @@ -53,7 +53,7 @@ def test_location_properties( expected_classname, expected_snapshotname, ): - location = NodeLocation(mock_pytest_item(node_id, method_name)) + location = PyTestLocation(mock_pytest_item(node_id, method_name)) assert location.classname == expected_classname assert location.filename == expected_filename assert location.snapshot_name == expected_snapshotname @@ -98,7 +98,7 @@ def test_location_matching( expected_snapshot_matches, expected_snapshot_misses, ): - location = NodeLocation(mock_pytest_item(node_id, method_name)) + location = PyTestLocation(mock_pytest_item(node_id, method_name)) for location_match in expected_location_matches: assert location.matches_snapshot_location(location_match) From 33c8372489c70c37c11e5a9f90c24ca743964bc3 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 18:43:23 -0400 Subject: [PATCH 09/16] refactor: names and method ordering --- src/syrupy/report.py | 34 ++++++++----------- .../__snapshots__/test_amber_matchers.ambr | 4 +-- .../extensions/amber/test_amber_matchers.py | 2 +- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/syrupy/report.py b/src/syrupy/report.py index eace4c18..6f1fcd41 100644 --- a/src/syrupy/report.py +++ b/src/syrupy/report.py @@ -269,17 +269,11 @@ def _is_matching_path(self, snapshot_location: str, provided_path: str) -> bool: path = Path(provided_path) return str(path if path.is_dir() else path.parent) in snapshot_location - def _get_matching_paths(self, snapshot_location: str) -> Set[str]: - return { - path - for path in self._provided_test_paths - if self._is_matching_path(snapshot_location, path) - } - def _get_matching_path_nodes(self, snapshot_location: str) -> List[List[str]]: return [ - self._provided_test_paths[matching_path] - for matching_path in self._get_matching_paths(snapshot_location) + self._provided_test_paths[path] + for path in self._provided_test_paths + if self._is_matching_path(snapshot_location, path) ] def _provided_nodes_match_name( @@ -287,17 +281,6 @@ def _provided_nodes_match_name( ) -> bool: return any(snapshot_name in ".".join(node_path) for node_path in provided_nodes) - def _selected_items_match_location(self, snapshot_location: str) -> bool: - return any( - PyTestLocation(item).matches_snapshot_location(snapshot_location) - for item in self.ran_items - ) - - def _selected_items_match_name(self, snapshot_name: str) -> bool: - if self._keyword_expressions: - return self._provided_keywords_match_name(snapshot_name) - return self._ran_items_match_name(snapshot_name) - def _provided_keywords_match_name(self, snapshot_name: str) -> bool: names = snapshot_name.split(".") return any( @@ -310,3 +293,14 @@ def _ran_items_match_name(self, snapshot_name: str) -> bool: PyTestLocation(item).matches_snapshot_name(snapshot_name) for item in self.ran_items ) + + def _selected_items_match_name(self, snapshot_name: str) -> bool: + if self._keyword_expressions: + return self._provided_keywords_match_name(snapshot_name) + return self._ran_items_match_name(snapshot_name) + + def _selected_items_match_location(self, snapshot_location: str) -> bool: + return any( + PyTestLocation(item).matches_snapshot_location(snapshot_location) + for item in self.ran_items + ) diff --git a/tests/syrupy/extensions/amber/__snapshots__/test_amber_matchers.ambr b/tests/syrupy/extensions/amber/__snapshots__/test_amber_matchers.ambr index 063b5ff0..a1683cb7 100644 --- a/tests/syrupy/extensions/amber/__snapshots__/test_amber_matchers.ambr +++ b/tests/syrupy/extensions/amber/__snapshots__/test_amber_matchers.ambr @@ -7,7 +7,7 @@ 'some_uuid': , } --- -# name: test_non_deterministic_snapshots +# name: test_matches_non_deterministic_snapshots { 'a': UUID(...), 'b': { @@ -20,7 +20,7 @@ ], } --- -# name: test_non_deterministic_snapshots.1 +# name: test_matches_non_deterministic_snapshots.1 { 'a': UUID('06335e84-2872-4914-8c5d-3ed07d2a2f16'), 'b': { diff --git a/tests/syrupy/extensions/amber/test_amber_matchers.py b/tests/syrupy/extensions/amber/test_amber_matchers.py index 16962c38..93fd2780 100644 --- a/tests/syrupy/extensions/amber/test_amber_matchers.py +++ b/tests/syrupy/extensions/amber/test_amber_matchers.py @@ -47,7 +47,7 @@ def test_raises_unexpected_type(snapshot): assert actual == snapshot(matcher=path_type(**kwargs)) -def test_non_deterministic_snapshots(snapshot): +def test_matches_non_deterministic_snapshots(snapshot): def matcher(data, path): if isinstance(data, uuid.UUID): return Repr("UUID(...)") From a0ae1c96c9f9cfab0d3142991f467047ffac7c35 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 19:33:34 -0400 Subject: [PATCH 10/16] test: pyargs test selection --- .../test_snapshot_option_update.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/integration/test_snapshot_option_update.py b/tests/integration/test_snapshot_option_update.py index 84978ceb..222e8093 100644 --- a/tests/integration/test_snapshot_option_update.py +++ b/tests/integration/test_snapshot_option_update.py @@ -1,3 +1,4 @@ +import sys from pathlib import Path import pytest @@ -110,6 +111,7 @@ def test_updated_5(snapshot): @pytest.fixture def run_testcases(testdir, testcases_initial): + sys.path.append(str(testdir.tmpdir)) testdir.makepyfile(**testcases_initial) result = testdir.runpytest("-v", "--snapshot-update") result.stdout.re_match_lines((r"10 snapshots generated.")) @@ -322,6 +324,24 @@ def test_update_targets_only_selected_module_tests_nodes(run_testcases): assert snapfile_empty.exists() +def test_update_targets_only_selected_module_tests_nodes_pyargs(run_testcases): + testdir = run_testcases[1] + snapfile_empty = Path("__snapshots__", "empty_snapfile.ambr") + testdir.makefile(".ambr", **{str(snapfile_empty): ""}) + result = testdir.runpytest( + "-v", + "--snapshot-update", + "--pyargs", + "test_used::test_used", + ) + result.stdout.re_match_lines((r"3 snapshots passed\.")) + result.stdout.no_re_match_line(r".*unused.*") + result.stdout.no_re_match_line(r".*updated.*") + result.stdout.no_re_match_line(r".*deleted.*") + assert result.ret == 0 + assert snapfile_empty.exists() + + def test_update_targets_only_selected_module_tests_file_for_update(run_testcases): testdir = run_testcases[1] snapfile_empty = Path("__snapshots__", "empty_snapfile.ambr") From 51dd223ff9300d2f3f0c86d429e0132c9e44deea Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 20:15:05 -0400 Subject: [PATCH 11/16] chore: replace protected _pytest import --- src/syrupy/__init__.py | 10 +++------- tests/syrupy/test_location.py | 5 ++--- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/syrupy/__init__.py b/src/syrupy/__init__.py index a3154716..e4ac52cc 100644 --- a/src/syrupy/__init__.py +++ b/src/syrupy/__init__.py @@ -2,7 +2,6 @@ import sys from gettext import gettext from typing import ( - TYPE_CHECKING, Any, ContextManager, List, @@ -27,9 +26,6 @@ import_module_member, ) -if TYPE_CHECKING: - from _pytest.config import Config - def __default_extension_option(value: str) -> Any: try: @@ -109,14 +105,14 @@ def pytest_sessionstart(session: Any) -> None: Initialize snapshot session before tests are collected and ran. https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_sessionstart """ - config: "Config" = session.config - session.config._syrupy = SnapshotSession( + config = session.config + config._syrupy = SnapshotSession( warn_unused_snapshots=config.option.warn_unused_snapshots, update_snapshots=config.option.update_snapshots, base_dir=str(config.rootpath), invocation_args=config.invocation_params.args, ) - session.config._syrupy.start() + config._syrupy.start() def pytest_collection_modifyitems( diff --git a/tests/syrupy/test_location.py b/tests/syrupy/test_location.py index 820cf920..71625597 100644 --- a/tests/syrupy/test_location.py +++ b/tests/syrupy/test_location.py @@ -2,13 +2,12 @@ from unittest.mock import MagicMock import pytest -from _pytest.nodes import Item from syrupy.location import PyTestLocation -def mock_pytest_item(node_id: str, method_name: str) -> Item: - mock_node = MagicMock(spec=Item) +def mock_pytest_item(node_id: str, method_name: str) -> "pytest.Item": + mock_node = MagicMock(spec=pytest.Item) mock_node.nodeid = node_id [filepath, *_, nodename] = node_id.split("::") mock_node.name = nodename From ac39bcb5d9600f8fe1e9a6cab6431af2f5167261 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 20:23:03 -0400 Subject: [PATCH 12/16] cr: move import to top of file --- src/syrupy/report.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/syrupy/report.py b/src/syrupy/report.py index 6f1fcd41..da425701 100644 --- a/src/syrupy/report.py +++ b/src/syrupy/report.py @@ -1,3 +1,4 @@ +import importlib from gettext import ( gettext, ngettext, @@ -98,8 +99,6 @@ def __parse_invocation_args(self) -> None: if maybe_opt_arg == "-k": # or maybe_opt_arg == "-m": self._keyword_expressions.add(Expression.compile(arg_value)) elif maybe_opt_arg is None: - import importlib - parts = arg_value.split(PYTEST_NODE_SEP) package_or_filepath = parts[0].strip() filepath = Path( From 61d87484dc7865ce42165562466110030f386391 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 21:04:53 -0400 Subject: [PATCH 13/16] cr: add docstring to methods --- src/syrupy/location.py | 12 +++++++ src/syrupy/report.py | 75 ++++++++++++++++++++++++++++++++++++++++++ src/syrupy/session.py | 4 +++ 3 files changed, 91 insertions(+) diff --git a/src/syrupy/location.py b/src/syrupy/location.py index fb433039..4102d841 100644 --- a/src/syrupy/location.py +++ b/src/syrupy/location.py @@ -19,6 +19,10 @@ def __init__(self, node: Any): @property def classname(self) -> Optional[str]: + """ + Pytest node names contain file path and module members delimited by `::` + Example tests/grouping/test_file.py::TestClass::TestSubClass::test_method + """ return ".".join(self._node.nodeid.split(PYTEST_NODE_SEP)[1:-1]) or None @property @@ -32,6 +36,10 @@ def snapshot_name(self) -> str: return str(self.testname) def __valid_id(self, name: str) -> str: + """ + Take characters from the name while the result would be a valid python + identified. Example: "test_2[A]" returns "test_2" while "1_a" would return "" + """ valid_id = "" for char in name: new_valid_id = f"{valid_id}{char}" @@ -41,6 +49,10 @@ def __valid_id(self, name: str) -> str: return valid_id def __valid_ids(self, name: str) -> Iterator[str]: + """ + Break a name path into valid name parts stopping at the first non valid name. + Example "TestClass.test_method_[1]" would yield ("TestClass", "test_method_") + """ for n in name.split("."): valid_id = self.__valid_id(n) if valid_id: diff --git a/src/syrupy/report.py b/src/syrupy/report.py index da425701..400d7f03 100644 --- a/src/syrupy/report.py +++ b/src/syrupy/report.py @@ -41,6 +41,12 @@ @attr.s class SnapshotReport: + """ + This class is responsible for determining the test summary and post execution + results. It will provide the lines of the report to be printed as well as the + information used for removal of unused or orphaned snapshots and fossils. + """ + base_dir: str = attr.ib() all_items: Dict["pytest.Item", bool] = attr.ib() ran_items: Dict["pytest.Item", bool] = attr.ib() @@ -77,6 +83,21 @@ def __attrs_post_init__(self) -> None: self.failed.update(snapshot_fossil) def __parse_invocation_args(self) -> None: + """ + Parse the invocation arguments to extract some information for test selection + This compiles and saves values from `-k`, `--pyargs` and test dir path + + https://docs.pytest.org/en/stable/reference.html#command-line-flags + https://docs.pytest.org/en/stable/reference.html#config + + Summary + -k: is evaluated and used to match against snapshot names when present + -m: is ignored for now as markers are not matched to snapshot names + --pyargs: arguments are imported to get their file locations + [args]: a path provided e.g. tests/test_file.py::TestClass::test_method + would result in `"tests/test_file.py"` being stored as the location in a + dictionary with `["TestClass", "test_method"]` being the test node path + """ arg_groups: List[Tuple[Optional[str], str]] = [] path_as_package = False maybe_opt_arg = None @@ -137,6 +158,14 @@ def ran_all_collected_tests(self) -> bool: @property def unused(self) -> "SnapshotFossils": + """ + Iterate over each snapshot that was discovered but never used and compute + if the snapshot was unused because the test attached to it was never run, + or if the snapshot is obsolete and therefore is a candidate for removal. + + Summary, if a snapshot was supposed to be run based on the invocation args + and it was not, then it should be marked as unused otherwise ignored. + """ unused_fossils = SnapshotFossils() for unused_snapshot_fossil in self._diff_snapshot_fossils( self.discovered, self.used @@ -177,6 +206,14 @@ def unused(self) -> "SnapshotFossils": @property def lines(self) -> Iterator[str]: + """ + These are the lines printed at the end of a test run. Example: + ``` + 2 snaphots passed. 5 snapshots generated. 1 unused snapshot deleted. + + Re-run pytest with --snapshot-update to delete unused snapshots. + ``` + """ summary_lines: List[str] = [] if self.num_failed: summary_lines.append( @@ -249,6 +286,13 @@ def lines(self) -> Iterator[str]: def _diff_snapshot_fossils( self, snapshot_fossils1: "SnapshotFossils", snapshot_fossils2: "SnapshotFossils" ) -> "SnapshotFossils": + """ + Find the difference between two collections of snapshot fossils. While + preserving the location site to all fossils in the first collections. That is + a collection with fossil sites {A{1,2}, B{3,4}, C{5,6}} with snapshot fossils + when diffed with another collection with snapshots {A{1,2}, B{3,4}, D{7,8}} + will result in a collection with the contents {A{}, B{}, C{5,6}}. + """ diffed_snapshot_fossils: "SnapshotFossils" = SnapshotFossils() for snapshot_fossil1 in snapshot_fossils1: snapshot_fossil2 = snapshot_fossils2.get( @@ -262,13 +306,25 @@ def _diff_snapshot_fossils( return diffed_snapshot_fossils def _count_snapshots(self, snapshot_fossils: "SnapshotFossils") -> int: + """ + Count all the snapshots at all the locations in the snapshot fossil collection + """ return sum(len(snapshot_fossil) for snapshot_fossil in snapshot_fossils) def _is_matching_path(self, snapshot_location: str, provided_path: str) -> bool: + """ + Check if a snapshot location matches the path provided by checking that the + provided path folder is in a parent position relative to the snapshot location + """ path = Path(provided_path) return str(path if path.is_dir() else path.parent) in snapshot_location def _get_matching_path_nodes(self, snapshot_location: str) -> List[List[str]]: + """ + For the snapshot location provided, get the nodes of the test paths provided to + pytest on invocation. If there were no paths provided then this list should be + empty. If there are paths without nodes provided then this is a list of empties + """ return [ self._provided_test_paths[path] for path in self._provided_test_paths @@ -278,9 +334,16 @@ def _get_matching_path_nodes(self, snapshot_location: str) -> List[List[str]]: def _provided_nodes_match_name( self, snapshot_name: str, provided_nodes: List[List[str]] ) -> bool: + """ + Check that a snapshot name matches the node paths provided + """ return any(snapshot_name in ".".join(node_path) for node_path in provided_nodes) def _provided_keywords_match_name(self, snapshot_name: str) -> bool: + """ + Check that a snapshot name would have been included by the keyword + expression parsed from the invocation arguments + """ names = snapshot_name.split(".") return any( expr.evaluate(lambda subname: any(subname in name for name in names)) @@ -288,17 +351,29 @@ def _provided_keywords_match_name(self, snapshot_name: str) -> bool: ) def _ran_items_match_name(self, snapshot_name: str) -> bool: + """ + Check that a snapshot name would match a test node using the Pytest location + """ return any( PyTestLocation(item).matches_snapshot_name(snapshot_name) for item in self.ran_items ) def _selected_items_match_name(self, snapshot_name: str) -> bool: + """ + Check that a snapshot name should be treated as selected by the current session + This being true means that if the snapshot was not used then it will be deleted + """ if self._keyword_expressions: return self._provided_keywords_match_name(snapshot_name) return self._ran_items_match_name(snapshot_name) def _selected_items_match_location(self, snapshot_location: str) -> bool: + """ + Check that a snapshot fossil location should is selected by the current session + This being true means that if no snapshot in the fossil was used then it should + be discarded as obsolete + """ return any( PyTestLocation(item).matches_snapshot_location(snapshot_location) for item in self.ran_items diff --git a/src/syrupy/session.py b/src/syrupy/session.py index 4beb3637..1c18f625 100644 --- a/src/syrupy/session.py +++ b/src/syrupy/session.py @@ -74,6 +74,10 @@ def remove_unused_snapshots( unused_snapshot_fossils: "SnapshotFossils", used_snapshot_fossils: "SnapshotFossils", ) -> None: + """ + Remove all unused snapshots using the registed extension for the fossil file + If there is not registered extension and the location is unused delete the file + """ for unused_snapshot_fossil in unused_snapshot_fossils: snapshot_location = unused_snapshot_fossil.location From 43282f29a82550d972b3a63b1c91cce0050e6357 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 22:02:00 -0400 Subject: [PATCH 14/16] chore: simple keyword matching support in older pytest version --- src/syrupy/report.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/syrupy/report.py b/src/syrupy/report.py index 400d7f03..1afed79d 100644 --- a/src/syrupy/report.py +++ b/src/syrupy/report.py @@ -6,7 +6,9 @@ from pathlib import Path from typing import ( TYPE_CHECKING, + Callable, Dict, + FrozenSet, Iterator, List, Optional, @@ -15,7 +17,6 @@ ) import attr -from _pytest.mark.expression import Expression from .constants import PYTEST_NODE_SEP from .data import ( @@ -118,7 +119,7 @@ def __parse_invocation_args(self) -> None: for maybe_opt_arg, arg_value in arg_groups: if maybe_opt_arg == "-k": # or maybe_opt_arg == "-m": - self._keyword_expressions.add(Expression.compile(arg_value)) + self._keyword_expressions.add(Expression.compose(arg_value)) elif maybe_opt_arg is None: parts = arg_value.split(PYTEST_NODE_SEP) package_or_filepath = parts[0].strip() @@ -378,3 +379,26 @@ def _selected_items_match_location(self, snapshot_location: str) -> bool: PyTestLocation(item).matches_snapshot_location(snapshot_location) for item in self.ran_items ) + + +@attr.s(frozen=True) +class Expression: + """ + Dumbed down version of _pytest.mark.expression.Expression not available in < 6.0 + https://github.com/pytest-dev/pytest/blob/6.0.x/src/_pytest/mark/expression.py + Added for pared down support on older pytest version and because the expression + module is not public. This only supports inclusion based on simple string matching. + """ + + code: FrozenSet[str] = attr.ib(factory=frozenset) + + def evaluate(self, matcher: Callable[[str], bool]) -> bool: + return any(map(matcher, self.code)) + + @staticmethod + def compose(value: str) -> "Expression": + delim = " " + replace_str = {" or ", " and ", " not ", "(", ")"} + for r in replace_str: + value = value.replace(f" {r} ", delim) + return Expression(code=frozenset(value.split(delim))) From baf8dab01622d2c512de0a7051cabf08844aa7e3 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 22:11:46 -0400 Subject: [PATCH 15/16] chore: revert incompatible change --- src/syrupy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syrupy/__init__.py b/src/syrupy/__init__.py index e4ac52cc..7314d3b8 100644 --- a/src/syrupy/__init__.py +++ b/src/syrupy/__init__.py @@ -109,7 +109,7 @@ def pytest_sessionstart(session: Any) -> None: config._syrupy = SnapshotSession( warn_unused_snapshots=config.option.warn_unused_snapshots, update_snapshots=config.option.update_snapshots, - base_dir=str(config.rootpath), + base_dir=config.rootdir, invocation_args=config.invocation_params.args, ) config._syrupy.start() From 7435bea2ab5674558c49ef4c5c780e13716c9b27 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi-Ugbe Date: Thu, 29 Oct 2020 22:21:36 -0400 Subject: [PATCH 16/16] chore: revert incompatible change --- .../test_snapshot_option_update.py | 22 +++++++++---------- .../test_snapshot_use_extension.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/integration/test_snapshot_option_update.py b/tests/integration/test_snapshot_option_update.py index 222e8093..2604ae93 100644 --- a/tests/integration/test_snapshot_option_update.py +++ b/tests/integration/test_snapshot_option_update.py @@ -115,7 +115,7 @@ def run_testcases(testdir, testcases_initial): testdir.makepyfile(**testcases_initial) result = testdir.runpytest("-v", "--snapshot-update") result.stdout.re_match_lines((r"10 snapshots generated.")) - result.stdout.no_re_match_line(r".*Can not relate snapshot name.*") + assert "Can not relate snapshot name" not in result.stdout.str() return result, testdir, testcases_initial @@ -235,7 +235,7 @@ def test_used(snapshot, actual): testdir.makepyfile(**updated_tests) result = testdir.runpytest("-v", "--snapshot-update", "-k", "test_used[2]") result.stdout.re_match_lines((r"1 snapshot updated\.")) - result.stdout.no_fnmatch_line("*Deleted*") + assert "Deleted" not in result.stdout.str() snapshot_path = [testdir.tmpdir, "__snapshots__"] assert Path(*snapshot_path, "test_used.ambr").exists() assert Path(*snapshot_path, "test_updated_1.ambr").exists() @@ -287,7 +287,7 @@ def test_case_2(self, snapshot): result = testdir.runpytest("test_content.py", "-v", "-k test_case_2") result.stdout.re_match_lines((r"1 snapshot passed\.")) - result.stdout.no_re_match_line(r".*snapshot unused.*") + assert "snaphot unused" not in result.stdout.str() def test_update_targets_only_selected_module_tests_dash_k(testdir): @@ -307,7 +307,7 @@ def test_case_2(snapshot): result = testdir.runpytest("test_content.py", "-v", "-k test_case_2") result.stdout.re_match_lines((r"1 snapshot passed\.")) - result.stdout.no_re_match_line(r".*snapshot unused.*") + assert "snaphot unused" not in result.stdout.str() def test_update_targets_only_selected_module_tests_nodes(run_testcases): @@ -317,9 +317,9 @@ def test_update_targets_only_selected_module_tests_nodes(run_testcases): testfile = Path(testdir.tmpdir, "test_used.py") result = testdir.runpytest(f"{testfile}::test_used", "-v", "--snapshot-update") result.stdout.re_match_lines((r"3 snapshots passed\.")) - result.stdout.no_re_match_line(r".*unused.*") - result.stdout.no_re_match_line(r".*updated.*") - result.stdout.no_re_match_line(r".*deleted.*") + assert "unused" not in result.stdout.str() + assert "updated" not in result.stdout.str() + assert "deleted" not in result.stdout.str() assert result.ret == 0 assert snapfile_empty.exists() @@ -335,9 +335,9 @@ def test_update_targets_only_selected_module_tests_nodes_pyargs(run_testcases): "test_used::test_used", ) result.stdout.re_match_lines((r"3 snapshots passed\.")) - result.stdout.no_re_match_line(r".*unused.*") - result.stdout.no_re_match_line(r".*updated.*") - result.stdout.no_re_match_line(r".*deleted.*") + assert "unused" not in result.stdout.str() + assert "updated" not in result.stdout.str() + assert "deleted" not in result.stdout.str() assert result.ret == 0 assert snapfile_empty.exists() @@ -425,7 +425,7 @@ def test_update_removes_hanging_snapshot_fossil_file(run_testcases): r"\(__snapshots__[\\/]hanging_snapfile\.abc\)", ) ) - result.stdout.no_re_match_line(rf".*{snapfile_used}.*") + assert f"{snapfile_used}" not in result.stdout.str() assert result.ret == 0 assert snapfile_used.exists() assert not snapfile_hanging.exists() diff --git a/tests/integration/test_snapshot_use_extension.py b/tests/integration/test_snapshot_use_extension.py index 26df61b8..a111f881 100644 --- a/tests/integration/test_snapshot_use_extension.py +++ b/tests/integration/test_snapshot_use_extension.py @@ -113,7 +113,7 @@ def test_failed_snapshots(testdir, testcases_initial): def test_generated_snapshots(generate_snapshots): result = generate_snapshots[0] result.stdout.re_match_lines((r"4 snapshots generated\.")) - result.stdout.no_re_match_line(r".*snapshots unused.*") + assert "snapshots unused" not in result.stdout.str() assert result.ret == 0