From dfd5910cd5ac9a93011d639303cdc060ef4c779a Mon Sep 17 00:00:00 2001 From: John Kurkowski Date: Tue, 25 Apr 2023 07:48:13 -0700 Subject: [PATCH] fix: defer snapshot default extension import (#734) * test: add coverage for bug #719 * fix: defer snapshot default extension import Fixes unable to use pytest's `pythonpath` option with this project's `--snapshot-default-extension` option. Does cause extension import errors to raise later than CLI argument parsing, and therefore emit on stdout, instead of stderr. --------- Co-authored-by: Noah Negin-Ulster --- src/syrupy/__init__.py | 6 -- src/syrupy/utils.py | 5 +- .../test_snapshot_option_extension.py | 7 +- ...st_snapshot_option_extension_pythonpath.py | 101 ++++++++++++++++++ 4 files changed, 106 insertions(+), 13 deletions(-) create mode 100644 tests/integration/test_snapshot_option_extension_pythonpath.py diff --git a/src/syrupy/__init__.py b/src/syrupy/__init__.py index 5f1a646c..560ddb0f 100644 --- a/src/syrupy/__init__.py +++ b/src/syrupy/__init__.py @@ -41,11 +41,6 @@ def __import_extension(value: Optional[str]) -> Any: raise argparse.ArgumentTypeError(e) -def __default_extension_option(value: Optional[str]) -> Any: - __import_extension(value) - return value - - def pytest_addoption(parser: Any) -> None: """ Exposes snapshot plugin configuration to pytest. @@ -78,7 +73,6 @@ def pytest_addoption(parser: Any) -> None: # all pytest options to be serializable. group.addoption( "--snapshot-default-extension", - type=__default_extension_option, default=None, dest="default_extension", help="Specify the default snapshot extension", diff --git a/src/syrupy/utils.py b/src/syrupy/utils.py index c0861734..4dd08cdb 100644 --- a/src/syrupy/utils.py +++ b/src/syrupy/utils.py @@ -48,11 +48,14 @@ def import_module_member(path: str) -> Any: ) ) try: - return getattr(import_module(module_name), module_member_name) + module = import_module(module_name) except ModuleNotFoundError: raise FailedToLoadModuleMember( gettext("Module '{}' does not exist.").format(module_name) ) + + try: + return getattr(module, module_member_name) except AttributeError: raise FailedToLoadModuleMember( gettext("Member '{}' not found in module '{}'.").format( diff --git a/tests/integration/test_snapshot_option_extension.py b/tests/integration/test_snapshot_option_extension.py index 546da3ff..884f92a6 100644 --- a/tests/integration/test_snapshot_option_extension.py +++ b/tests/integration/test_snapshot_option_extension.py @@ -37,12 +37,7 @@ def test_snapshot_default_extension_option_failure(testfile): "--snapshot-default-extension", "syrupy.extensions.amber.DoesNotExistExtension", ) - result.stderr.re_match_lines( - ( - r".*error: argument --snapshot-default-extension" - r": Member 'DoesNotExistExtension' not found.*", - ) - ) + result.stdout.re_match_lines((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_extension_pythonpath.py b/tests/integration/test_snapshot_option_extension_pythonpath.py new file mode 100644 index 00000000..35701540 --- /dev/null +++ b/tests/integration/test_snapshot_option_extension_pythonpath.py @@ -0,0 +1,101 @@ +import textwrap +from pathlib import Path + +import pytest + +import syrupy + +SUBDIR = "subdir_not_on_default_path" + + +@pytest.fixture(autouse=True) +def cache_clear(): + syrupy.__import_extension.cache_clear() + + +@pytest.fixture +def testfile(pytester): + subdir = pytester.mkpydir(SUBDIR) + + Path( + subdir, + "extension_file.py", + ).write_text( + data=textwrap.dedent( + """ + from syrupy.extensions.single_file import SingleFileSnapshotExtension + class MySingleFileExtension(SingleFileSnapshotExtension): + pass + """ + ), + encoding="utf-8", + ) + + pytester.makepyfile( + test_file=( + """ + def test_default(snapshot): + assert b"default extension serializer" == snapshot + """ + ) + ) + + return pytester + + +def test_snapshot_default_extension_option_success(testfile): + testfile.makeini( + f""" + [pytest] + pythonpath = + {Path(testfile.path, SUBDIR).as_posix()} + """ + ) + + result = testfile.runpytest( + "-v", + "--snapshot-update", + "--snapshot-default-extension", + "extension_file.MySingleFileExtension", + ) + result.stdout.re_match_lines((r"1 snapshot generated\.")) + assert Path( + testfile.path, "__snapshots__", "test_file", "test_default.raw" + ).exists() + assert not result.ret + + +def test_snapshot_default_extension_option_module_not_found(testfile): + result = testfile.runpytest( + "-v", + "--snapshot-update", + "--snapshot-default-extension", + "extension_file.MySingleFileExtension", + ) + result.stdout.re_match_lines((r".*: Module 'extension_file' does not exist.*",)) + assert not Path( + testfile.path, "__snapshots__", "test_file", "test_default.raw" + ).exists() + assert result.ret + + +def test_snapshot_default_extension_option_failure(testfile): + testfile.makeini( + f""" + [pytest] + pythonpath = + {Path(testfile.path, SUBDIR).as_posix()} + """ + ) + + result = testfile.runpytest( + "-v", + "--snapshot-update", + "--snapshot-default-extension", + "extension_file.DoesNotExistExtension", + ) + result.stdout.re_match_lines((r".*: Member 'DoesNotExistExtension' not found.*",)) + assert not Path( + testfile.path, "__snapshots__", "test_file", "test_default.raw" + ).exists() + assert result.ret