Skip to content

Commit 41eafd7

Browse files
committed
Add test for cache
1 parent 69b327f commit 41eafd7

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

.vscode/launch.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"version": "0.2.0",
23
"configurations": [
34
{
45
"type": "debugpy",

tests/helpers/test_cache.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING
3+
from typing import TYPE_CHECKING, Any
44

55
from dissect.target.helpers.cache import Cache
66
from dissect.target.plugins.os.windows.amcache import AmcachePlugin
77
from dissect.target.plugins.os.windows.ual import UalPlugin
88

99
if TYPE_CHECKING:
10+
from collections.abc import Callable, Iterator
11+
1012
from dissect.target.target import Target
1113

1214

@@ -56,3 +58,68 @@ def test_cache_filename(target_win: Target) -> None:
5658
assert (
5759
cache4.cache_path(target_win, ()).stem == "dissect.target.plugins.os.windows.ual.UalPlugin.client_access.KCk="
5860
)
61+
62+
63+
def test_cache_write_failure_behavior(target_bare: Target) -> None:
64+
"""
65+
Specifically tests the 'Write Path' (Cache Miss) which returns a CacheWriter.
66+
We verify that CacheWriter acts as an Iterator even when the underlying
67+
plugin returns None (stops immediately).
68+
"""
69+
target_bare._config.CACHE_DIR = "/tmp"
70+
71+
# 1. Mock Plugin with two modes
72+
class MockPlugin:
73+
def __init__(self, target: Target):
74+
self.target = target
75+
76+
def success(self) -> Iterator[str]:
77+
yield "success_data"
78+
79+
def failure(self) -> Iterator[str]:
80+
if True:
81+
return None
82+
yield "unreachable"
83+
84+
plugin = MockPlugin(target_bare)
85+
86+
# 2. Setup Cache wrapper
87+
# We force output="yield" to use LineReader/CacheWriter
88+
# (mimicking the behavior of RecordWriter logic in a simpler test)
89+
def create_wrapper(func: Callable[..., Iterator[str]]) -> Callable[..., Iterator[str]]:
90+
cache = Cache(func)
91+
92+
def wrapper(*args: Any, **kwargs: Any) -> Iterator[str]:
93+
return cache.call(*args, **kwargs)
94+
95+
wrapper.__output__ = "yield"
96+
cache.wrapper = wrapper
97+
return wrapper
98+
99+
wrap_success = create_wrapper(MockPlugin.success)
100+
wrap_failure = create_wrapper(MockPlugin.failure)
101+
102+
# --- SCENARIO A: Success Case (Write Path) ---
103+
# This creates a CacheWriter.
104+
# IF CacheWriter is not wrapped in iter(), next() crashes here.
105+
gen_success = wrap_success(plugin)
106+
107+
assert next(gen_success) == "success_data"
108+
# Exhaust it to ensure file write completes
109+
list(gen_success)
110+
111+
# --- SCENARIO B: Failure Case (Write Path) ---
112+
# It creates a CacheWriter that wraps an empty generator.
113+
gen_failure = wrap_failure(plugin)
114+
115+
# CRITICAL CHECK:
116+
# 1. It must be an iterator (iter(obj) is obj)
117+
# 2. calling next() should raise StopIteration, NOT TypeError
118+
assert iter(gen_failure) is gen_failure
119+
120+
try:
121+
next(gen_failure)
122+
except StopIteration:
123+
pass
124+
except TypeError as e:
125+
raise AssertionError(f"Crash detected! CacheWriter is not an iterator: {e}")

0 commit comments

Comments
 (0)