Skip to content

Commit

Permalink
feat: implement binary selfie methods in Python (#496)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg authored Dec 13, 2024
2 parents 529992f + 24a8029 commit b6bdd16
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 529 deletions.
73 changes: 73 additions & 0 deletions python/example-pytest-selfie/tests/binary_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import pytest
from selfie_lib import expect_selfie


def test_empty_binary_base64():
"""Test base64 encoding of empty byte array"""
expect_selfie(bytes()).to_be_base64("")


def test_large_binary_base64():
"""Test base64 encoding of large byte array (256 bytes)"""
data = bytes(range(256))
expect_selfie(data).to_be_base64(
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
)


def test_binary_file():
"""Test writing binary data to a file"""
data = b"test binary data"
expect_selfie(data).to_be_file("tests/binary_test__test_binary_file.bin")


def test_binary_file_duplicate_equal():
"""Test writing same binary data to a file multiple times"""
expect_selfie(b"equal").to_be_file(
"tests/binary_test__test_binary_file_duplicate_equal.bin"
)
expect_selfie(b"equal").to_be_file(
"tests/binary_test__test_binary_file_duplicate_equal.bin"
)


def test_binary_file_duplicate_unequal():
"""Test writing same binary data to a file multiple times"""
with pytest.raises(Exception) as exc_info:
expect_selfie(b"a").to_be_file(
"tests/binary_test__test_binary_file_duplicate_unequal.bin"
)
expect_selfie(b"b").to_be_file(
"tests/binary_test__test_binary_file_duplicate_unequal.bin"
)
expect_selfie(safify(str(exc_info.value))).to_be(
"Snapshot mismatch, TODO: string comparison"
)


def test_binary_file_mismatch():
"""Test error handling for mismatched binary data"""
with pytest.raises(AssertionError):
expect_selfie(b"different").to_be_file(
"tests/binary_test__SHOULD_NOT_EXIST.bin"
)


def test_binary_file_not_found():
"""Test error handling for non-existent file"""
with pytest.raises(AssertionError) as exc_info:
expect_selfie(b"test").to_be_file("tests/binary_test__SHOULD_NOT_EXIST.bin")
assert "no such file" in str(exc_info.value)


def test_base64_mismatch():
"""Test error handling for mismatched base64 data"""
with pytest.raises(Exception) as exc_info:
expect_selfie(b"test data").to_be_base64("AAAA")
expect_selfie(safify(str(exc_info.value))).to_be(
"Snapshot mismatch, TODO: string comparison"
)


def safify(string: str) -> str:
return string.split("\n")[0]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test binary data
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
equal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a
24 changes: 0 additions & 24 deletions python/example-pytest-selfie/tests/to_be_file_test.py

This file was deleted.

233 changes: 0 additions & 233 deletions python/example-pytest-selfie/uv.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion python/pytest-selfie/pytest_selfie/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def __nullable_to_string(self, value, on_null: str) -> str:
def __comparison_assertion(
self, message: str, expected: str, actual: str
) -> Exception:
# this *should* through an exception that a good pytest runner will show nicely
# this *should* throw an exception that a good pytest runner will show nicely
assert expected == actual, message
# but in case it doesn't, we'll create our own here
return AssertionError(message)
Expand All @@ -69,6 +69,9 @@ def __init__(self, fs: FSImplementation, settings: SelfieSettingsAPI):
self.__root_folder = TypedPath.of_folder(os.path.abspath(settings.root_dir))
self.unix_newlines = self.__infer_default_line_ending_is_unix()

def root_folder(self) -> TypedPath:
return self.__root_folder

def snapshotfile_for_testfile(self, testfile: TypedPath) -> TypedPath:
if testfile.name.endswith(".py"):
return testfile.parent_folder().resolve_file(f"{testfile.name[:-3]}.ss")
Expand Down
79 changes: 0 additions & 79 deletions python/pytest-selfie/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 83 additions & 14 deletions python/selfie-lib/selfie_lib/SelfieImplementations.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def to_match_disk_TODO(self, sub: str = "") -> "DiskSelfie":
return self
else:
raise _selfieSystem().fs.assert_failed(
f"Can't call `toMatchDisk_TODO` in {Mode.readonly} mode!"
message=f"Can't call `toMatchDisk_TODO` in {Mode.readonly} mode!"
)

def facet(self, facet: str) -> "StringFacet":
Expand Down Expand Up @@ -186,17 +186,71 @@ def __init__(self, actual: Snapshot, disk: DiskStorage, only_facet: str):
f"The facet {only_facet} is a string, not a binary snapshot"
)

def to_be_base64(self, expected: str) -> bytes:
raise NotImplementedError
def _actual_bytes(self) -> bytes:
return self.actual.subject_or_facet(self.only_facet).value_binary()

def to_match_disk(self, sub: str = "") -> "BinarySelfie":
super().to_match_disk(sub)
return self

def to_match_disk_TODO(self, sub: str = "") -> "BinarySelfie":
super().to_match_disk_TODO(sub)
return self

def to_be_base64_TODO(self, _: Any = None) -> bytes:
raise NotImplementedError
_toBeDidntMatch(None, self._actual_string(), LiteralString())
return self._actual_bytes()

def to_be_file(self, subpath: str) -> bytes:
raise NotImplementedError
def to_be_base64(self, expected: str) -> bytes:
expected_bytes = base64.b64decode(expected)
actual_bytes = self._actual_bytes()
if actual_bytes == expected_bytes:
return _checkSrc(actual_bytes)
else:
_toBeDidntMatch(expected, self._actual_string(), LiteralString())
return actual_bytes

def _actual_string(self) -> str:
return base64.b64encode(self._actual_bytes()).decode().replace("\r", "")

def _to_be_file_impl(self, subpath: str, is_todo: bool) -> bytes:
call = recordCall(False)
writable = _selfieSystem().mode.can_write(is_todo, call, _selfieSystem())
actual_bytes = self._actual_bytes()
path = _selfieSystem().layout.root_folder().resolve_file(subpath)

if writable:
if is_todo:
_selfieSystem().write_inline(TodoStub.to_be_file.create_literal(), call)
_selfieSystem().write_to_be_file(path, actual_bytes, call)
return actual_bytes
else:
if is_todo:
raise _selfieSystem().fs.assert_failed(
f"Can't call `to_be_file_TODO` in {Mode.readonly} mode!"
)
else:
if not _selfieSystem().fs.file_exists(path):
raise _selfieSystem().fs.assert_failed(
_selfieSystem().mode.msg_snapshot_not_found_no_such_file(path)
)
expected = _selfieSystem().fs.file_read_binary(path)
if expected == actual_bytes:
return actual_bytes
else:
raise _selfieSystem().fs.assert_failed(
message=_selfieSystem().mode.msg_snapshot_mismatch_binary(
expected, actual_bytes
),
expected=expected,
actual=actual_bytes,
)

def to_be_file_TODO(self, subpath: str) -> bytes:
raise NotImplementedError
return self._to_be_file_impl(subpath, True)

def to_be_file(self, subpath: str) -> bytes:
return self._to_be_file_impl(subpath, False)


def _checkSrc(value: T) -> T:
Expand All @@ -216,16 +270,27 @@ def _toBeDidntMatch(expected: Optional[T], actual: T, fmt: LiteralFormat[T]) ->
f"Can't call `toBe_TODO` in {Mode.readonly} mode!"
)
else:
raise _selfieSystem().fs.assert_failed(
_selfieSystem().mode.msg_snapshot_mismatch(), expected, actual
)
expectedStr = repr(expected)
actualStr = repr(actual)
if expectedStr == actualStr:
raise ValueError(
f"Value of type {type(actual)} is not `==` to the expected value, but they both have the same `repr` value:\n${expectedStr}"
)
else:
raise _selfieSystem().fs.assert_failed(
message=_selfieSystem().mode.msg_snapshot_mismatch(
expected=expectedStr, actual=actualStr
),
expected=expected,
actual=actual,
)


def _assertEqual(
expected: Optional[Snapshot], actual: Snapshot, storage: SnapshotSystem
):
if expected is None:
raise storage.fs.assert_failed(storage.mode.msg_snapshot_not_found())
raise storage.fs.assert_failed(message=storage.mode.msg_snapshot_not_found())
elif expected == actual:
return
else:
Expand All @@ -240,10 +305,14 @@ def _assertEqual(
),
)
)
expectedFacets = _serializeOnlyFacets(expected, mismatched_keys)
actualFacets = _serializeOnlyFacets(actual, mismatched_keys)
raise storage.fs.assert_failed(
storage.mode.msg_snapshot_mismatch(),
_serializeOnlyFacets(expected, mismatched_keys),
_serializeOnlyFacets(actual, mismatched_keys),
message=storage.mode.msg_snapshot_mismatch(
expected=expectedFacets, actual=actualFacets
),
expected=expectedFacets,
actual=actualFacets,
)


Expand Down
Loading

0 comments on commit b6bdd16

Please sign in to comment.