Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: unused snapshot detection for targeting single parameterized test case #394

Merged
merged 18 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 3 additions & 22 deletions src/syrupy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import argparse
import glob
import sys
from gettext import gettext
from typing import (
Expand All @@ -15,7 +14,7 @@
from .constants import DISABLE_COLOR_ENV_VAR
from .exceptions import FailedToLoadModuleMember
from .extensions import DEFAULT_EXTENSION
from .location import TestLocation
from .location import PyTestLocation
from .session import SnapshotSession
from .terminal import (
received_style,
Expand Down Expand Up @@ -101,18 +100,6 @@ def snapshot_name(name: str) -> str:
return None


def __is_testpath(arg: str) -> bool:
return not arg.startswith("-") and bool(glob.glob(arg.split("::")[0]))


def __is_testnode(arg: str) -> bool:
return __is_testpath(arg) and "::" 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.
Expand All @@ -123,13 +110,7 @@ def pytest_sessionstart(session: Any) -> None:
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
),
invocation_args=config.invocation_params.args,
)
config._syrupy.start()

Expand Down Expand Up @@ -180,6 +161,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=PyTestLocation(request.node),
session=request.session.config._syrupy,
)
4 changes: 2 additions & 2 deletions src/syrupy/assertion.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

if TYPE_CHECKING:
from .extensions.base import AbstractSyrupyExtension
from .location import TestLocation
from .location import PyTestLocation
from .session import SnapshotSession
from .types import (
PropertyFilter,
Expand Down Expand Up @@ -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: "PyTestLocation" = attr.ib(kw_only=True)
_update_snapshots: bool = attr.ib(kw_only=True)
_exclude: Optional["PropertyFilter"] = attr.ib(
init=False, default=None, kw_only=True
Expand Down
2 changes: 2 additions & 0 deletions src/syrupy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@

DISABLE_COLOR_ENV_VAR = "ANSI_COLORS_DISABLED"
DISABLE_COLOR_ENV_VARS = {DISABLE_COLOR_ENV_VAR, "NO_COLOR"}

PYTEST_NODE_SEP = "::"
11 changes: 6 additions & 5 deletions src/syrupy/extensions/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from syrupy.utils import walk_snapshot_dir

if TYPE_CHECKING:
from syrupy.location import TestLocation
from syrupy.location import PyTestLocation
from syrupy.types import (
PropertyFilter,
PropertyMatcher,
Expand Down Expand Up @@ -70,7 +70,7 @@ def serialize(
class SnapshotFossilizer(ABC):
@property
@abstractmethod
def test_location(self) -> "TestLocation":
def test_location(self) -> "PyTestLocation":
raise NotImplementedError

def get_snapshot_name(self, *, index: int = 0) -> str:
Expand All @@ -81,7 +81,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"""
Expand Down Expand Up @@ -371,9 +372,9 @@ def __strip_ends(self, line: str) -> str:


class AbstractSyrupyExtension(SnapshotSerializer, SnapshotFossilizer, SnapshotReporter):
def __init__(self, test_location: "TestLocation"):
def __init__(self, test_location: "PyTestLocation"):
self._test_location = test_location

@property
def test_location(self) -> "TestLocation":
def test_location(self) -> "PyTestLocation":
return self._test_location
19 changes: 16 additions & 3 deletions src/syrupy/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
Optional,
)

from syrupy.constants import PYTEST_NODE_SEP

class TestLocation:

class PyTestLocation:
iamogbz marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, node: Any):
self._node = node
self.filepath = self._node.fspath
Expand All @@ -17,8 +19,11 @@ 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
"""
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
def filename(self) -> str:
Expand All @@ -31,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}"
Expand All @@ -40,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:
Expand Down
Loading