Skip to content

Commit

Permalink
fix: unused snapshot detection for targeting single parameterized tes…
Browse files Browse the repository at this point in the history
…t case (#394)

* test: match structure to source

* test: refactor test distribution

* test: refactor use rematch lines

* fix: do not include empty extension in filename

* refactor: extract class name from node logic

* test: syrupy location

* test: add failing test case

* fix: use pytest expression to include snapshots

* refactor: names and method ordering

* test: pyargs test selection

* chore: replace protected _pytest import

* cr: move import to top of file

* cr: add docstring to methods

* chore: simple keyword matching support in older pytest version

* chore: revert incompatible change

* chore: revert incompatible change
  • Loading branch information
iamogbz authored Oct 30, 2020
1 parent 0937036 commit e008935
Show file tree
Hide file tree
Showing 45 changed files with 1,182 additions and 898 deletions.
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:
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

0 comments on commit e008935

Please sign in to comment.