Compare commits
2 Commits
8b08e5778a
...
2f237ac265
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f237ac265 | |||
| 3a8e86ca6b |
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "sessions-sublime"
|
||||
version = "0.7.32"
|
||||
version = "0.7.33"
|
||||
description = "Sublime-facing Python code for Sessions."
|
||||
requires-python = ">=3.8"
|
||||
license = {text = "MIT"}
|
||||
|
||||
12
rust/Cargo.lock
generated
12
rust/Cargo.lock
generated
@@ -221,7 +221,7 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "local_bridge"
|
||||
version = "0.7.32"
|
||||
version = "0.7.33"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"glob",
|
||||
@@ -432,7 +432,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "session_helper"
|
||||
version = "0.7.32"
|
||||
version = "0.7.33"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"notify",
|
||||
@@ -443,7 +443,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "session_protocol"
|
||||
version = "0.7.32"
|
||||
version = "0.7.33"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde",
|
||||
@@ -452,14 +452,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sessions_askpass"
|
||||
version = "0.7.32"
|
||||
version = "0.7.33"
|
||||
dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sessions_native"
|
||||
version = "0.7.32"
|
||||
version = "0.7.33"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"notify",
|
||||
@@ -773,7 +773,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "workspace_identity"
|
||||
version = "0.7.32"
|
||||
version = "0.7.33"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
|
||||
@@ -12,7 +12,7 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
version = "0.7.32"
|
||||
version = "0.7.33"
|
||||
authors = ["Myeongseon Choi <key262yek@gmail.com>"]
|
||||
repository = "https://git.teahaven.kr/sublime-rs/sessions"
|
||||
homepage = "https://git.teahaven.kr/sublime-rs/sessions"
|
||||
|
||||
151
sublime/tests/test_rust_local_watcher.py
Normal file
151
sublime/tests/test_rust_local_watcher.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""Tests for ``_rust_ffi.local_watcher`` wrapper contracts.
|
||||
|
||||
The Rust side of the watcher is exercised by
|
||||
``sessions_native::local_watcher::tests`` (6 tests covering the live
|
||||
``notify`` event loop, filtering, and stop idempotency). These
|
||||
Python-only tests pin the ctypes-wrapper layer contract:
|
||||
|
||||
* ``start`` returns the integer the Rust ABI returned (handle on
|
||||
success, 0 on failure).
|
||||
* ``drain`` decodes the ``\\x1F``-joined payload, retries on the
|
||||
buffer-too-small sentinel, returns ``()`` on negative rc or
|
||||
zero/negative handle.
|
||||
* ``stop`` returns ``True`` only when the Rust ABI returns ``1``.
|
||||
* All three raise ``SessionsNativeLibraryError`` when the symbol is
|
||||
missing from the cdylib.
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
|
||||
import pytest
|
||||
from sessions import _rust_ffi
|
||||
from sessions._rust_ffi import SessionsNativeLibraryError
|
||||
|
||||
|
||||
def _install(monkeypatch, **symbols) -> None:
|
||||
class _Lib:
|
||||
pass
|
||||
|
||||
lib = _Lib()
|
||||
for name, func in symbols.items():
|
||||
setattr(lib, name, func)
|
||||
monkeypatch.setattr(_rust_ffi._loader, "_native_lib", lambda: lib)
|
||||
|
||||
|
||||
class _FakeIntFunc:
|
||||
def __init__(self, rc: int) -> None:
|
||||
self._rc = rc
|
||||
self.argtypes = None
|
||||
self.restype = None
|
||||
|
||||
def __call__(self, *args: object) -> int:
|
||||
return self._rc
|
||||
|
||||
|
||||
class _FakeDrainFunc:
|
||||
"""Mimics ``sessions_local_watcher_drain``.
|
||||
|
||||
Returns ``rc`` and, when ``rc == 0``, writes ``payload`` (UTF-8 +
|
||||
NUL terminator) into the caller's ``out_buf``. When ``rc > out_cap``
|
||||
we expect the wrapper to retry with a bigger buffer.
|
||||
"""
|
||||
|
||||
def __init__(self, *, rc: int = 0, payload: str = "") -> None:
|
||||
self._rc = rc
|
||||
self._payload = payload
|
||||
self.argtypes = None
|
||||
self.restype = None
|
||||
self.calls: list[int] = []
|
||||
|
||||
def __call__(self, _handle: object, out_buf: object, out_cap: int) -> int:
|
||||
self.calls.append(out_cap)
|
||||
if self._rc != 0:
|
||||
return self._rc
|
||||
encoded = self._payload.encode("utf-8") + b"\x00"
|
||||
if out_cap < len(encoded):
|
||||
return len(encoded)
|
||||
ctypes.memmove(out_buf, encoded, len(encoded))
|
||||
return 0
|
||||
|
||||
|
||||
def test_start_returns_handle_from_rust(monkeypatch, tmp_path) -> None:
|
||||
_install(monkeypatch, sessions_local_watcher_start=_FakeIntFunc(rc=42))
|
||||
assert _rust_ffi.local_watcher.start(str(tmp_path)) == 42
|
||||
|
||||
|
||||
def test_start_returns_zero_on_failure(monkeypatch, tmp_path) -> None:
|
||||
_install(monkeypatch, sessions_local_watcher_start=_FakeIntFunc(rc=0))
|
||||
assert _rust_ffi.local_watcher.start(str(tmp_path)) == 0
|
||||
|
||||
|
||||
def test_start_raises_when_symbol_missing(monkeypatch, tmp_path) -> None:
|
||||
_install(monkeypatch) # no symbol bound
|
||||
with pytest.raises(SessionsNativeLibraryError):
|
||||
_rust_ffi.local_watcher.start(str(tmp_path))
|
||||
|
||||
|
||||
def test_drain_with_zero_handle_short_circuits(monkeypatch) -> None:
|
||||
# Should not even reach the Rust ABI; install a func that would
|
||||
# explode if called.
|
||||
_install(monkeypatch, sessions_local_watcher_drain=_FakeIntFunc(rc=-1))
|
||||
assert _rust_ffi.local_watcher.drain(0) == ()
|
||||
assert _rust_ffi.local_watcher.drain(-5) == ()
|
||||
|
||||
|
||||
def test_drain_returns_empty_tuple_on_empty_payload(monkeypatch) -> None:
|
||||
func = _FakeDrainFunc(rc=0, payload="")
|
||||
_install(monkeypatch, sessions_local_watcher_drain=func)
|
||||
assert _rust_ffi.local_watcher.drain(7) == ()
|
||||
|
||||
|
||||
def test_drain_splits_unit_separator(monkeypatch) -> None:
|
||||
func = _FakeDrainFunc(rc=0, payload="/a/b\x1f/c/d\x1f/e")
|
||||
_install(monkeypatch, sessions_local_watcher_drain=func)
|
||||
assert _rust_ffi.local_watcher.drain(1) == ("/a/b", "/c/d", "/e")
|
||||
|
||||
|
||||
def test_drain_returns_empty_on_unknown_handle(monkeypatch) -> None:
|
||||
# Rust returns -1 when ``handle`` is unknown ("watcher gone").
|
||||
_install(monkeypatch, sessions_local_watcher_drain=_FakeIntFunc(rc=-1))
|
||||
assert _rust_ffi.local_watcher.drain(99) == ()
|
||||
|
||||
|
||||
def test_drain_grows_buffer_on_buffer_too_small(monkeypatch) -> None:
|
||||
# First call returns the required size; second succeeds.
|
||||
payload = "/long/path/" + "x" * 16_000
|
||||
func = _FakeDrainFunc(rc=0, payload=payload)
|
||||
_install(monkeypatch, sessions_local_watcher_drain=func)
|
||||
out = _rust_ffi.local_watcher.drain(1)
|
||||
assert out == (payload,)
|
||||
# Two attempts: 8192 (initial), then >= encoded length.
|
||||
assert len(func.calls) >= 2
|
||||
assert func.calls[0] == 8192
|
||||
assert func.calls[-1] >= len(payload.encode("utf-8")) + 1
|
||||
|
||||
|
||||
def test_drain_raises_when_symbol_missing(monkeypatch) -> None:
|
||||
_install(monkeypatch)
|
||||
with pytest.raises(SessionsNativeLibraryError):
|
||||
_rust_ffi.local_watcher.drain(1)
|
||||
|
||||
|
||||
def test_stop_returns_true_when_rust_returned_one(monkeypatch) -> None:
|
||||
_install(monkeypatch, sessions_local_watcher_stop=_FakeIntFunc(rc=1))
|
||||
assert _rust_ffi.local_watcher.stop(1) is True
|
||||
|
||||
|
||||
def test_stop_returns_false_when_rust_returned_zero(monkeypatch) -> None:
|
||||
_install(monkeypatch, sessions_local_watcher_stop=_FakeIntFunc(rc=0))
|
||||
assert _rust_ffi.local_watcher.stop(1) is False
|
||||
|
||||
|
||||
def test_stop_with_zero_handle_short_circuits(monkeypatch) -> None:
|
||||
_install(monkeypatch, sessions_local_watcher_stop=_FakeIntFunc(rc=99))
|
||||
assert _rust_ffi.local_watcher.stop(0) is False
|
||||
assert _rust_ffi.local_watcher.stop(-3) is False
|
||||
|
||||
|
||||
def test_stop_raises_when_symbol_missing(monkeypatch) -> None:
|
||||
_install(monkeypatch)
|
||||
with pytest.raises(SessionsNativeLibraryError):
|
||||
_rust_ffi.local_watcher.stop(7)
|
||||
Reference in New Issue
Block a user