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

feat: implement binary selfie methods in Python #496

Merged
merged 30 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e056580
feat: implement BinarySelfie methods using Kotlin implementation as r…
devin-ai-integration[bot] Dec 12, 2024
ec22177
test: add binary selfie tests
devin-ai-integration[bot] Dec 12, 2024
649d9bd
fix: update binary selfie tests to handle readonly mode and initial s…
devin-ai-integration[bot] Dec 12, 2024
f8a385b
feat: implement binary selfie methods in Python
devin-ai-integration[bot] Dec 12, 2024
b3bac6f
fix: correct file path resolution and snapshot mismatch handling in B…
devin-ai-integration[bot] Dec 12, 2024
22d197d
fix: improve test setup and fix import sorting
devin-ai-integration[bot] Dec 12, 2024
6180d28
fix: apply ruff formatting to SelfieImplementations.py
devin-ai-integration[bot] Dec 12, 2024
ce8059d
test: add binary snapshot files for binary selfie tests
devin-ai-integration[bot] Dec 12, 2024
c750a5a
fix: implement platform-agnostic path handling in TypedPath
devin-ai-integration[bot] Dec 12, 2024
43a6330
fix: ensure proper trailing slash handling in TypedPath
devin-ai-integration[bot] Dec 12, 2024
b0d9b95
style: apply ruff formatting to TypedPath.py
devin-ai-integration[bot] Dec 12, 2024
1c7334d
Implement the rest of the binary stuff.
nedtwigg Dec 13, 2024
291a0b5
Fix `SnapshotFileLayout.rootFolder()`
nedtwigg Dec 13, 2024
a4fa024
Forgot one.
nedtwigg Dec 13, 2024
fadc54b
Remove unused stuff.
nedtwigg Dec 13, 2024
abece41
Remove LLM cruft from `TypedPath`.
nedtwigg Dec 13, 2024
43da71d
Add nice error message when objects are not equal but their repr is.
nedtwigg Dec 13, 2024
ec4c207
Cleanup the the binary_test stuff.
nedtwigg Dec 13, 2024
aeb4024
fix a typo
nedtwigg Dec 13, 2024
dd507e2
No need for a trailing newline.
nedtwigg Dec 13, 2024
2150adc
Remove the test `.bin` files.
nedtwigg Dec 13, 2024
597f157
I would expect this to pass.
nedtwigg Dec 13, 2024
b697dce
And now with correct formatting.
nedtwigg Dec 13, 2024
6c5178f
Fixed the weird thing that was nuking our tests.
nedtwigg Dec 13, 2024
470eff4
Formatting fix.
nedtwigg Dec 13, 2024
7916fda
Testing a snapshot testing library is so hard!
nedtwigg Dec 13, 2024
6fc6caa
fixup formatting.
nedtwigg Dec 13, 2024
ba088d8
Cleanup the uv lock files.
nedtwigg Dec 13, 2024
b603d25
Even a bit better.
nedtwigg Dec 13, 2024
24a8029
Another fix.
nedtwigg Dec 13, 2024
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
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
Loading