Skip to content
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
8 changes: 7 additions & 1 deletion tests/_vcr_redis_persister.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
VCR_VERBOSE_ENV = "LITELLM_VCR_VERBOSE"
MAX_EPISODES_PER_CASSETTE = 50

_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 os.path.abspath does not resolve symlinks, so _REPO_ROOT and a cassette path can represent the same physical location via different string prefixes (one through a symlink, one through the real path). In that case os.path.relpath(abs_path, start=_REPO_ROOT) produces a ../../… path which the subsequent lstrip("./") then silently mangles into a wrong key. Using os.path.realpath for both would make the prefix comparison reliable.

Suggested change
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))


_log = logging.getLogger(__name__)
_passed_by_cassette_key: dict[str, bool] = {}

Expand All @@ -22,7 +24,11 @@ def mark_test_outcome_for_cassette(cassette_path: str, passed: bool) -> None:


def redis_key_for(cassette_path: str) -> str:
rel = os.path.relpath(str(cassette_path))
abs_path = os.path.abspath(str(cassette_path))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Consistent with using realpath for _REPO_ROOT, abs_path should also be resolved through any symlinks before computing the relative path, otherwise the prefix comparison can still fail when the cassette path arrives via a symlinked directory.

Suggested change
abs_path = os.path.abspath(str(cassette_path))
abs_path = os.path.realpath(str(cassette_path))

try:
rel = os.path.relpath(abs_path, start=_REPO_ROOT)
except ValueError:
rel = os.path.basename(abs_path)
Comment on lines +30 to +31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The ValueError fallback (Windows cross-drive paths) uses only os.path.basename, which drops all directory context. Two cassettes in different subdirectories that share the same filename — e.g. tests/a/test_foo.yaml and tests/b/test_foo.yaml — would both resolve to litellm:vcr:cassette:test_foo and silently share the same Redis entry. Using a short hash of the full absolute path as the fallback would avoid this collision while remaining stable.

Suggested change
except ValueError:
rel = os.path.basename(abs_path)
except ValueError:
import hashlib
rel = hashlib.sha1(abs_path.encode()).hexdigest()[:16]

if rel.endswith(".yaml"):
rel = rel[: -len(".yaml")]
rel = rel.replace("/cassettes/", "/").lstrip("./")
Expand Down
25 changes: 25 additions & 0 deletions tests/llm_translation/test_vcr_redis_persister.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,31 @@ def test_redis_key_normalizes_path_passed_by_pytest_recording():
)


def test_redis_key_is_stable_across_working_directories(tmp_path, monkeypatch):
repo_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
abs_cassette = os.path.join(
repo_root,
"tests/llm_translation/cassettes/test_anthropic/test_streaming.yaml",
)

monkeypatch.chdir(repo_root)
key_from_root = redis_key_for(abs_cassette)

monkeypatch.chdir(os.path.join(repo_root, "tests", "llm_translation"))
key_from_subdir = redis_key_for(abs_cassette)

monkeypatch.chdir(tmp_path)
key_from_tmp = redis_key_for(abs_cassette)

assert key_from_root == key_from_subdir == key_from_tmp
assert (
key_from_root
== "litellm:vcr:cassette:tests/llm_translation/test_anthropic/test_streaming"
)


class _FlakyRedis:
def __init__(self, inner, fail_on: str):
self._inner = inner
Expand Down
Loading