Skip to content

Cache Module

The cache module implements incremental analysis, the third pillar of pytest-gremlins' speed strategy. Results are cached keyed by content hashes, allowing unchanged code/tests to be skipped on subsequent runs.

Overview

Traditional mutation testing runs all gremlins every time:

Text Only
Run 1: 1000 gremlins tested (5 minutes)
Run 2: 1000 gremlins tested (5 minutes)  # No changes
Run 3: 1000 gremlins tested (5 minutes)  # 1 file changed

Incremental analysis skips unchanged code:

Text Only
Run 1: 1000 gremlins tested (5 minutes)
Run 2: 0 gremlins tested (0 seconds)     # Cache hit
Run 3: 10 gremlins tested (30 seconds)   # Only changed file

Module Exports

Python
from pytest_gremlins.cache import (
    CachedGremlinResult,  # TypedDict for cached result shape
    ContentHasher,        # SHA-256 content hashing
    IncrementalCache,     # Cache coordinator
    ResultStore,          # SQLite-backed result cache
)

ContentHasher

Produces deterministic SHA-256 hashes for files and strings.

ContentHasher

Produces content hashes for files and strings.

Uses SHA-256 to produce deterministic hashes that uniquely identify content. These hashes form the cache keys for incremental analysis.

Example

hasher = ContentHasher() result = hasher.hash_string('def foo(): return 42') len(result) == 64 # SHA-256 produces 64 hex characters True

hash_string

Python
hash_string(content)

Hash a string and return its hex digest.

Parameters:

Name Type Description Default
content str

The string content to hash.

required

Returns:

Type Description
str

A 64-character hexadecimal string (SHA-256 digest).

Source code in src/pytest_gremlins/cache/hasher.py
Python
def hash_string(self, content: str) -> str:
    """Hash a string and return its hex digest.

    Args:
        content: The string content to hash.

    Returns:
        A 64-character hexadecimal string (SHA-256 digest).
    """
    return hashlib.sha256(content.encode('utf-8')).hexdigest()

hash_file

Python
hash_file(path)

Hash a file's content and return its hex digest.

Parameters:

Name Type Description Default
path Path

Path to the file to hash.

required

Returns:

Type Description
str

A 64-character hexadecimal string (SHA-256 digest).

Raises:

Type Description
FileNotFoundError

If the file does not exist.

Source code in src/pytest_gremlins/cache/hasher.py
Python
def hash_file(self, path: Path) -> str:
    """Hash a file's content and return its hex digest.

    Args:
        path: Path to the file to hash.

    Returns:
        A 64-character hexadecimal string (SHA-256 digest).

    Raises:
        FileNotFoundError: If the file does not exist.
    """
    content = path.read_text(encoding='utf-8')
    return self.hash_string(content)

hash_files

Python
hash_files(paths)

Hash multiple files and return a mapping of path to hash.

Parameters:

Name Type Description Default
paths list[Path]

List of file paths to hash.

required

Returns:

Type Description
dict[str, str]

Dictionary mapping string path to hex digest.

Source code in src/pytest_gremlins/cache/hasher.py
Python
def hash_files(self, paths: list[Path]) -> dict[str, str]:
    """Hash multiple files and return a mapping of path to hash.

    Args:
        paths: List of file paths to hash.

    Returns:
        Dictionary mapping string path to hex digest.
    """
    return {str(path): self.hash_file(path) for path in paths}

hash_combined

Python
hash_combined(hashes)

Combine multiple hashes into a single hash.

Useful for creating composite cache keys from multiple source files or a combination of source and test file hashes.

Parameters:

Name Type Description Default
hashes list[str]

List of hex digest strings to combine.

required

Returns:

Type Description
str

A single 64-character hexadecimal string.

Source code in src/pytest_gremlins/cache/hasher.py
Python
def hash_combined(self, hashes: list[str]) -> str:
    """Combine multiple hashes into a single hash.

    Useful for creating composite cache keys from multiple source
    files or a combination of source and test file hashes.

    Args:
        hashes: List of hex digest strings to combine.

    Returns:
        A single 64-character hexadecimal string.
    """
    combined = ''.join(hashes)
    return self.hash_string(combined)

ContentHasher Methods

Method Returns Description
hash_string(content) str Hash a string (64 hex chars)
hash_file(path) str Hash a file's content
hash_files(paths) dict[str, str] Hash multiple files
hash_combined(hashes) str Combine multiple hashes

Usage Example

Python
from pathlib import Path
from pytest_gremlins.cache import ContentHasher

hasher = ContentHasher()

# Hash a string
code = 'def foo(): return 42'
hash1 = hasher.hash_string(code)
print(hash1)  # 64-character hex string

# Hash a file
file_hash = hasher.hash_file(Path('src/module.py'))

# Hash multiple files
hashes = hasher.hash_files([
    Path('src/module.py'),
    Path('tests/test_module.py'),
])
# {'src/module.py': 'abc...', 'tests/test_module.py': 'def...'}

# Combine hashes for composite keys
combined = hasher.hash_combined([hash1, file_hash])

Hash Properties

  • Deterministic: Same content always produces same hash
  • Collision-resistant: Different content produces different hashes
  • Fast: SHA-256 is hardware-accelerated on modern CPUs
  • Fixed-size: Always 64 hexadecimal characters

ResultStore

SQLite-backed cache for gremlin test results.

ResultStore

Python
ResultStore(db_path)

SQLite-backed cache for gremlin test results.

Stores results as JSON blobs indexed by content-based cache keys. Keys are typically composed of source file hash + test file hash + gremlin definition.

Example

from pathlib import Path store = ResultStore(Path('.gremlins_cache/results.db')) store.put('abc123', {'status': 'zapped', 'killing_test': 'test_foo'}) store.get('abc123') {'status': 'zapped', 'killing_test': 'test_foo'} store.close()

If the database file is corrupted, it will be deleted and a fresh database will be created. A warning will be logged in this case.

Parameters:

Name Type Description Default
db_path Path

Path to the SQLite database file. Parent directories will be created if they don't exist.

required
Source code in src/pytest_gremlins/cache/store.py
Python
def __init__(self, db_path: Path) -> None:
    """Initialize the result store.

    If the database file is corrupted, it will be deleted and a fresh
    database will be created. A warning will be logged in this case.

    Args:
        db_path: Path to the SQLite database file. Parent directories
                 will be created if they don't exist.
    """
    db_path.parent.mkdir(parents=True, exist_ok=True)
    self._db_path = db_path
    self._conn = self._open_or_recreate_db()
    self._pending_writes: list[tuple[str, str]] = []

get

Python
get(cache_key)

Retrieve a cached result by key.

Parameters:

Name Type Description Default
cache_key str

The content-based cache key.

required

Returns:

Type Description
CachedGremlinResult | None

The cached result dictionary, or None if not found.

Source code in src/pytest_gremlins/cache/store.py
Python
def get(self, cache_key: str) -> CachedGremlinResult | None:
    """Retrieve a cached result by key.

    Args:
        cache_key: The content-based cache key.

    Returns:
        The cached result dictionary, or None if not found.
    """
    cursor = self._conn.execute(
        'SELECT result_json FROM results WHERE cache_key = ?',
        (cache_key,),
    )
    row = cursor.fetchone()
    if row is None:
        return None
    result: CachedGremlinResult = json.loads(row[0])
    return result

put

Python
put(cache_key, result)

Store a result in the cache.

Parameters:

Name Type Description Default
cache_key str

The content-based cache key.

required
result CachedGremlinResult

The result dictionary to cache.

required
Source code in src/pytest_gremlins/cache/store.py
Python
def put(self, cache_key: str, result: CachedGremlinResult) -> None:
    """Store a result in the cache.

    Args:
        cache_key: The content-based cache key.
        result: The result dictionary to cache.
    """
    result_json = json.dumps(result)
    self._conn.execute(
        'INSERT OR REPLACE INTO results (cache_key, result_json) VALUES (?, ?)',
        (cache_key, result_json),
    )
    self._conn.commit()

put_deferred

Python
put_deferred(cache_key, result)

Store a result without committing immediately.

Results are batched and committed on flush() or close(). This is faster for bulk inserts as it reduces commit overhead.

Parameters:

Name Type Description Default
cache_key str

The content-based cache key.

required
result CachedGremlinResult

The result dictionary to cache.

required
Source code in src/pytest_gremlins/cache/store.py
Python
def put_deferred(self, cache_key: str, result: CachedGremlinResult) -> None:
    """Store a result without committing immediately.

    Results are batched and committed on flush() or close(). This is
    faster for bulk inserts as it reduces commit overhead.

    Args:
        cache_key: The content-based cache key.
        result: The result dictionary to cache.
    """
    result_json = json.dumps(result)
    self._pending_writes.append((cache_key, result_json))

flush

Python
flush()

Commit all pending deferred writes.

This commits any results added via put_deferred() in a single transaction, which is much faster than individual commits.

Source code in src/pytest_gremlins/cache/store.py
Python
def flush(self) -> None:
    """Commit all pending deferred writes.

    This commits any results added via put_deferred() in a single
    transaction, which is much faster than individual commits.
    """
    if not self._pending_writes:
        return

    self._conn.executemany(
        'INSERT OR REPLACE INTO results (cache_key, result_json) VALUES (?, ?)',
        self._pending_writes,
    )
    self._conn.commit()
    self._pending_writes.clear()

delete

Python
delete(cache_key)

Remove a result from the cache.

Parameters:

Name Type Description Default
cache_key str

The cache key to remove.

required
Source code in src/pytest_gremlins/cache/store.py
Python
def delete(self, cache_key: str) -> None:
    """Remove a result from the cache.

    Args:
        cache_key: The cache key to remove.
    """
    self._conn.execute('DELETE FROM results WHERE cache_key = ?', (cache_key,))
    self._conn.commit()

delete_by_prefix

Python
delete_by_prefix(prefix)

Remove all results with keys matching a prefix.

Useful for invalidating all gremlins in a specific file when that file's content changes.

Parameters:

Name Type Description Default
prefix str

The key prefix to match. All keys starting with this prefix will be deleted.

required
Source code in src/pytest_gremlins/cache/store.py
Python
def delete_by_prefix(self, prefix: str) -> None:
    """Remove all results with keys matching a prefix.

    Useful for invalidating all gremlins in a specific file when
    that file's content changes.

    Args:
        prefix: The key prefix to match. All keys starting with
                this prefix will be deleted.
    """
    # Escape LIKE metacharacters (%, _) in the prefix to treat them as literals.
    # Use backslash as the escape character (specified in ESCAPE clause).
    escaped_prefix = prefix.replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_')
    self._conn.execute(
        "DELETE FROM results WHERE cache_key LIKE ? ESCAPE '\\'",
        (escaped_prefix + '%',),
    )
    self._conn.commit()

clear

Python
clear()

Remove all entries from the cache.

Source code in src/pytest_gremlins/cache/store.py
Python
def clear(self) -> None:
    """Remove all entries from the cache."""
    self._conn.execute('DELETE FROM results')
    self._conn.commit()

has

Python
has(cache_key)

Check if a key exists in the cache.

Parameters:

Name Type Description Default
cache_key str

The cache key to check.

required

Returns:

Type Description
bool

True if the key exists, False otherwise.

Source code in src/pytest_gremlins/cache/store.py
Python
def has(self, cache_key: str) -> bool:
    """Check if a key exists in the cache.

    Args:
        cache_key: The cache key to check.

    Returns:
        True if the key exists, False otherwise.
    """
    cursor = self._conn.execute(
        'SELECT 1 FROM results WHERE cache_key = ?',
        (cache_key,),
    )
    return cursor.fetchone() is not None

keys

Python
keys()

Get all cache keys.

Returns:

Type Description
list[str]

List of all cache keys currently stored.

Source code in src/pytest_gremlins/cache/store.py
Python
def keys(self) -> list[str]:
    """Get all cache keys.

    Returns:
        List of all cache keys currently stored.
    """
    cursor = self._conn.execute('SELECT cache_key FROM results')
    return [row[0] for row in cursor.fetchall()]

count

Python
count()

Get the number of cached entries.

Returns:

Type Description
int

Total count of cached results.

Source code in src/pytest_gremlins/cache/store.py
Python
def count(self) -> int:
    """Get the number of cached entries.

    Returns:
        Total count of cached results.
    """
    cursor = self._conn.execute('SELECT COUNT(*) FROM results')
    count: int = cursor.fetchone()[0]
    return count

close

Python
close()

Close the database connection.

Flushes any pending deferred writes before closing.

Source code in src/pytest_gremlins/cache/store.py
Python
def close(self) -> None:
    """Close the database connection.

    Flushes any pending deferred writes before closing.
    """
    self.flush()
    self._conn.close()

ResultStore Methods

Method Returns Description
get(cache_key) dict \| None Retrieve cached result
put(cache_key, result) None Store result (immediate commit)
put_deferred(cache_key, result) None Store result (batch commit)
flush() None Commit all deferred writes
has(cache_key) bool Check if key exists
delete(cache_key) None Remove single entry
delete_by_prefix(prefix) None Remove entries by prefix
clear() None Remove all entries
keys() list[str] Get all cache keys
count() int Get entry count
close() None Close database connection

Usage Example

Python
from pathlib import Path
from pytest_gremlins.cache import ResultStore

# Create store (creates parent directories if needed)
store = ResultStore(Path('.gremlins_cache/results.db'))

# Store a result (immediate commit)
store.put('g001:abc123:def456', {
    'status': 'zapped',
    'killing_test': 'test_boundary',
    'execution_time_ms': 150.5,
})

# Retrieve a result
result = store.get('g001:abc123:def456')
if result:
    print(f"Status: {result['status']}")

# Batch operations (faster for bulk inserts)
for i in range(100):
    store.put_deferred(f'key_{i}', {'value': i})
store.flush()  # Single commit for all 100

# Clean up
store.close()

Context Manager

Python
from pathlib import Path
from pytest_gremlins.cache import ResultStore

with ResultStore(Path('.cache/results.db')) as store:
    store.put('key', {'data': 'value'})
# Automatically closes on exit

Database Schema

SQL
CREATE TABLE results (
    cache_key TEXT PRIMARY KEY,
    result_json TEXT NOT NULL
);

Error Recovery

If the database is corrupted, ResultStore automatically:

  1. Detects the corruption on open
  2. Logs a warning
  3. Deletes the corrupted file
  4. Creates a fresh database

IncrementalCache

Coordinates content hashing and result storage for smart cache invalidation.

IncrementalCache

Python
IncrementalCache(cache_dir)

Coordinator for incremental analysis caching.

Combines content hashing with result storage to implement the incremental analysis invalidation rules:

  • Source file modified: cache miss (re-run gremlins in that file)
  • Test file modified: cache miss (re-run gremlins covered by those tests)
  • New test added: cache miss (re-run gremlins the new test covers)
  • Test deleted: cache miss (re-run gremlins that test was zapping)
  • Nothing changed: cache hit (return cached results instantly)

The cache key is composed of: - gremlin_id: Unique identifier for the mutation - source_hash: SHA-256 hash of the source file content - test_hashes: Combined hash of all test files covering this gremlin

Example

from pathlib import Path cache = IncrementalCache(Path('.gremlins_cache')) cache.cache_result('g001', 'src_hash', {'test_foo': 'hash'}, {'status': 'zapped'}) cache.get_cached_result('g001', 'src_hash', {'test_foo': 'hash'}) {'status': 'zapped'} cache.close()

Parameters:

Name Type Description Default
cache_dir Path

Directory to store cache files.

required
Source code in src/pytest_gremlins/cache/incremental.py
Python
def __init__(self, cache_dir: Path) -> None:
    """Initialize the incremental cache.

    Args:
        cache_dir: Directory to store cache files.
    """
    self._cache_dir = cache_dir
    self._hasher = ContentHasher()
    self._store = ResultStore(cache_dir / 'results.db')
    self._hits = 0
    self._misses = 0

get_cached_result

Python
get_cached_result(gremlin_id, source_hash, test_hashes)

Retrieve a cached result if available.

Returns None (cache miss) if: - No cached result exists for this gremlin - Source file content has changed - Any relevant test file content has changed - Tests have been added or removed

Parameters:

Name Type Description Default
gremlin_id str

Unique identifier for the gremlin.

required
source_hash str

Current SHA-256 hash of the source file.

required
test_hashes dict[str, str]

Current mapping of test name to content hash.

required

Returns:

Type Description
CachedGremlinResult | None

Cached result dictionary, or None if cache miss.

Source code in src/pytest_gremlins/cache/incremental.py
Python
def get_cached_result(
    self,
    gremlin_id: str,
    source_hash: str,
    test_hashes: dict[str, str],
) -> CachedGremlinResult | None:
    """Retrieve a cached result if available.

    Returns None (cache miss) if:
    - No cached result exists for this gremlin
    - Source file content has changed
    - Any relevant test file content has changed
    - Tests have been added or removed

    Args:
        gremlin_id: Unique identifier for the gremlin.
        source_hash: Current SHA-256 hash of the source file.
        test_hashes: Current mapping of test name to content hash.

    Returns:
        Cached result dictionary, or None if cache miss.
    """
    cache_key = self._build_cache_key(gremlin_id, source_hash, test_hashes)
    result = self._store.get(cache_key)

    if result is None:
        self._misses += 1
    else:
        self._hits += 1

    return result

cache_result

Python
cache_result(gremlin_id, source_hash, test_hashes, result)

Cache a gremlin test result.

The result is stored with a key that incorporates the gremlin ID and content hashes. Any change to source or test files will produce a different key, causing a cache miss.

Parameters:

Name Type Description Default
gremlin_id str

Unique identifier for the gremlin.

required
source_hash str

SHA-256 hash of the source file.

required
test_hashes dict[str, str]

Mapping of test name to content hash.

required
result CachedGremlinResult

The result dictionary to cache.

required
Source code in src/pytest_gremlins/cache/incremental.py
Python
def cache_result(
    self,
    gremlin_id: str,
    source_hash: str,
    test_hashes: dict[str, str],
    result: CachedGremlinResult,
) -> None:
    """Cache a gremlin test result.

    The result is stored with a key that incorporates the gremlin ID
    and content hashes. Any change to source or test files will
    produce a different key, causing a cache miss.

    Args:
        gremlin_id: Unique identifier for the gremlin.
        source_hash: SHA-256 hash of the source file.
        test_hashes: Mapping of test name to content hash.
        result: The result dictionary to cache.
    """
    cache_key = self._build_cache_key(gremlin_id, source_hash, test_hashes)
    self._store.put(cache_key, result)

cache_result_deferred

Python
cache_result_deferred(gremlin_id, source_hash, test_hashes, result)

Cache a gremlin test result without committing immediately.

Results are batched and committed on flush() or close(). This is faster for bulk inserts during mutation testing runs.

Parameters:

Name Type Description Default
gremlin_id str

Unique identifier for the gremlin.

required
source_hash str

SHA-256 hash of the source file.

required
test_hashes dict[str, str]

Mapping of test name to content hash.

required
result CachedGremlinResult

The result dictionary to cache.

required
Source code in src/pytest_gremlins/cache/incremental.py
Python
def cache_result_deferred(
    self,
    gremlin_id: str,
    source_hash: str,
    test_hashes: dict[str, str],
    result: CachedGremlinResult,
) -> None:
    """Cache a gremlin test result without committing immediately.

    Results are batched and committed on flush() or close(). This is
    faster for bulk inserts during mutation testing runs.

    Args:
        gremlin_id: Unique identifier for the gremlin.
        source_hash: SHA-256 hash of the source file.
        test_hashes: Mapping of test name to content hash.
        result: The result dictionary to cache.
    """
    cache_key = self._build_cache_key(gremlin_id, source_hash, test_hashes)
    self._store.put_deferred(cache_key, result)

flush

Python
flush()

Commit all pending deferred cache writes.

Source code in src/pytest_gremlins/cache/incremental.py
Python
def flush(self) -> None:
    """Commit all pending deferred cache writes."""
    self._store.flush()

invalidate_file

Python
invalidate_file(file_prefix)

Invalidate all cached results for gremlins in a file.

Removes all cache entries where the gremlin_id starts with the given prefix. Useful when a source file changes and all its gremlins need re-testing.

Parameters:

Name Type Description Default
file_prefix str

Prefix to match in gremlin IDs.

required
Source code in src/pytest_gremlins/cache/incremental.py
Python
def invalidate_file(self, file_prefix: str) -> None:
    """Invalidate all cached results for gremlins in a file.

    Removes all cache entries where the gremlin_id starts with
    the given prefix. Useful when a source file changes and all
    its gremlins need re-testing.

    Args:
        file_prefix: Prefix to match in gremlin IDs.
    """
    self._store.delete_by_prefix(f'{file_prefix}:')

clear

Python
clear()

Remove all cached results.

Source code in src/pytest_gremlins/cache/incremental.py
Python
def clear(self) -> None:
    """Remove all cached results."""
    self._store.clear()
    self._hits = 0
    self._misses = 0

get_stats

Python
get_stats()

Get cache statistics.

Returns:

Type Description
dict[str, int]

Dictionary with hits, misses, and total_entries counts.

Source code in src/pytest_gremlins/cache/incremental.py
Python
def get_stats(self) -> dict[str, int]:
    """Get cache statistics.

    Returns:
        Dictionary with hits, misses, and total_entries counts.
    """
    return {
        'hits': self._hits,
        'misses': self._misses,
        'total_entries': self._store.count(),
    }

close

Python
close()

Close the cache and release resources.

Source code in src/pytest_gremlins/cache/incremental.py
Python
def close(self) -> None:
    """Close the cache and release resources."""
    self._store.close()

IncrementalCache Methods

Method Returns Description
get_cached_result(gremlin_id, source_hash, test_hashes) dict \| None Get cached result
cache_result(gremlin_id, source_hash, test_hashes, result) None Cache result (immediate)
cache_result_deferred(...) None Cache result (batch)
flush() None Commit deferred writes
invalidate_file(file_prefix) None Invalidate file's gremlins
clear() None Clear entire cache
get_stats() dict Get hit/miss statistics
close() None Close and release resources

Cache Key Structure

Cache keys incorporate three components:

Text Only
{gremlin_id}:{source_hash}:{combined_test_hash}
Component Changes When
gremlin_id Never (uniquely identifies mutation)
source_hash Source file content changes
combined_test_hash Any covering test file changes

Invalidation Rules

Change Cache Effect
Source file modified Miss for all gremlins in that file
Test file modified Miss for gremlins covered by that test
New test added Miss for gremlins the new test covers
Test deleted Miss for gremlins that test was zapping
Nothing changed Hit (return cached result)

Usage Example

Python
from pathlib import Path
from pytest_gremlins.cache import IncrementalCache

cache = IncrementalCache(Path('.gremlins_cache'))

# Define content hashes
source_hash = 'abc123...'  # Hash of source file
test_hashes = {
    'test_login': 'def456...',  # Hash of test file
    'test_logout': 'ghi789...',
}

# Try to get cached result
result = cache.get_cached_result(
    gremlin_id='g001',
    source_hash=source_hash,
    test_hashes=test_hashes,
)

if result is not None:
    print(f"Cache hit: {result['status']}")
else:
    # Run the test
    actual_result = run_mutation_test('g001')

    # Cache the result
    cache.cache_result(
        gremlin_id='g001',
        source_hash=source_hash,
        test_hashes=test_hashes,
        result={
            'status': actual_result.status.value,
            'killing_test': actual_result.killing_test,
            'execution_time_ms': actual_result.execution_time_ms,
        },
    )

# Check statistics
stats = cache.get_stats()
print(f"Hits: {stats['hits']}, Misses: {stats['misses']}")

cache.close()

Batch Caching

For better performance during mutation testing runs:

Python
cache = IncrementalCache(Path('.gremlins_cache'))

# Use deferred writes (batched commits)
for gremlin_id, result in results:
    cache.cache_result_deferred(
        gremlin_id=gremlin_id,
        source_hash=source_hashes[gremlin_id],
        test_hashes=test_hashes[gremlin_id],
        result=result,
    )

# Single commit for all results
cache.flush()
cache.close()

Context Manager

Python
from pathlib import Path
from pytest_gremlins.cache import IncrementalCache

with IncrementalCache(Path('.gremlins_cache')) as cache:
    result = cache.get_cached_result('g001', 'abc', {'test': 'def'})
# Automatically closes

CLI Integration

Enable caching via command line:

Bash
# Enable incremental caching
pytest --gremlins --gremlin-cache

# Clear cache and start fresh
pytest --gremlins --gremlin-cache --gremlin-clear-cache

Cache Location

By default, cache is stored in .gremlins_cache/ in the project root:

Text Only
.gremlins_cache/
└── results.db    # SQLite database

Performance Impact

Example Scenario

Text Only
Initial run (cold cache):
  1000 gremlins x 50ms average = 50 seconds

Second run (no changes):
  1000 cache hits x 0.1ms lookup = 0.1 seconds

After changing one 50-gremlin file:
  50 cache misses x 50ms = 2.5 seconds
  950 cache hits x 0.1ms = 0.1 seconds
  Total: 2.6 seconds (vs 50 seconds without cache)

Best Practices

  1. Enable caching in development - Faster feedback during TDD
  2. Disable in CI - Start fresh for authoritative results
  3. Clear cache after refactoring - Major changes may confuse the cache
  4. Monitor hit rates - Low hit rates may indicate frequent test changes