feat(prompts): implement Phase 6 - Incremental Execution (FR-7, FR-8)
Add change detection, structural diff-based impact analysis, configurable-depth incremental recomputation with circular suppression, and impact debt tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
162
tests/unit/prompts/test_impact_analyzer.py
Normal file
162
tests/unit/prompts/test_impact_analyzer.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
Unit tests for ImpactAnalyzer and metrics functions.
|
||||
|
||||
Tests diff ratios, magnitude scoring, and threshold decisions.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from markitect.prompts.incremental.metrics import (
|
||||
structural_diff_ratio,
|
||||
line_diff_ratio,
|
||||
calculate_change_magnitude,
|
||||
)
|
||||
from markitect.prompts.incremental.impact import ImpactAnalyzer
|
||||
from markitect.prompts.incremental.models import RecomputeConfig
|
||||
|
||||
|
||||
class TestStructuralDiffRatio:
|
||||
"""Tests for structural_diff_ratio."""
|
||||
|
||||
def test_identical_content(self):
|
||||
"""Test identical content returns 0.0."""
|
||||
assert structural_diff_ratio("hello", "hello") == 0.0
|
||||
|
||||
def test_completely_different(self):
|
||||
"""Test completely different content returns high ratio."""
|
||||
ratio = structural_diff_ratio("aaa", "zzz")
|
||||
assert ratio > 0.5
|
||||
|
||||
def test_empty_strings(self):
|
||||
"""Test both empty returns 0.0."""
|
||||
assert structural_diff_ratio("", "") == 0.0
|
||||
|
||||
def test_one_empty(self):
|
||||
"""Test one empty returns 1.0."""
|
||||
assert structural_diff_ratio("", "content") == 1.0
|
||||
assert structural_diff_ratio("content", "") == 1.0
|
||||
|
||||
def test_small_change(self):
|
||||
"""Test small change returns low ratio."""
|
||||
old = "The quick brown fox jumps over the lazy dog"
|
||||
new = "The quick brown fox leaps over the lazy dog"
|
||||
ratio = structural_diff_ratio(old, new)
|
||||
assert 0.0 < ratio < 0.5
|
||||
|
||||
def test_returns_float(self):
|
||||
"""Test return value is float between 0 and 1."""
|
||||
ratio = structural_diff_ratio("abc", "abd")
|
||||
assert isinstance(ratio, float)
|
||||
assert 0.0 <= ratio <= 1.0
|
||||
|
||||
|
||||
class TestLineDiffRatio:
|
||||
"""Tests for line_diff_ratio."""
|
||||
|
||||
def test_identical_lines(self):
|
||||
"""Test identical multi-line content returns 0.0."""
|
||||
content = "line1\nline2\nline3"
|
||||
assert line_diff_ratio(content, content) == 0.0
|
||||
|
||||
def test_one_line_changed(self):
|
||||
"""Test changing one line of several."""
|
||||
old = "line1\nline2\nline3"
|
||||
new = "line1\nmodified\nline3"
|
||||
ratio = line_diff_ratio(old, new)
|
||||
assert 0.0 < ratio < 1.0
|
||||
|
||||
def test_all_lines_changed(self):
|
||||
"""Test all lines changed returns high ratio."""
|
||||
old = "aaa\nbbb\nccc"
|
||||
new = "xxx\nyyy\nzzz"
|
||||
ratio = line_diff_ratio(old, new)
|
||||
assert ratio > 0.5
|
||||
|
||||
def test_empty_strings(self):
|
||||
"""Test both empty returns 0.0."""
|
||||
assert line_diff_ratio("", "") == 0.0
|
||||
|
||||
def test_one_empty(self):
|
||||
"""Test one empty returns 1.0."""
|
||||
assert line_diff_ratio("", "content") == 1.0
|
||||
assert line_diff_ratio("content", "") == 1.0
|
||||
|
||||
|
||||
class TestCalculateChangeMagnitude:
|
||||
"""Tests for calculate_change_magnitude."""
|
||||
|
||||
def test_none_old_content(self):
|
||||
"""Test None old_content (creation) returns 1.0."""
|
||||
assert calculate_change_magnitude(None, "new content") == 1.0
|
||||
|
||||
def test_none_new_content(self):
|
||||
"""Test None new_content (deletion) returns 1.0."""
|
||||
assert calculate_change_magnitude("old content", None) == 1.0
|
||||
|
||||
def test_both_none(self):
|
||||
"""Test both None returns 0.0."""
|
||||
assert calculate_change_magnitude(None, None) == 0.0
|
||||
|
||||
def test_structural_method(self):
|
||||
"""Test structural method (default)."""
|
||||
result = calculate_change_magnitude("abc", "abd", method="structural")
|
||||
assert 0.0 < result < 1.0
|
||||
|
||||
def test_line_method(self):
|
||||
"""Test line method."""
|
||||
result = calculate_change_magnitude("abc\ndef", "abc\nxyz", method="line")
|
||||
assert 0.0 < result < 1.0
|
||||
|
||||
def test_identical_content(self):
|
||||
"""Test identical content returns 0.0."""
|
||||
assert calculate_change_magnitude("same", "same") == 0.0
|
||||
|
||||
|
||||
class TestImpactAnalyzer:
|
||||
"""Tests for ImpactAnalyzer class."""
|
||||
|
||||
@pytest.fixture
|
||||
def analyzer(self):
|
||||
"""Create ImpactAnalyzer instance."""
|
||||
return ImpactAnalyzer()
|
||||
|
||||
def test_calculate_magnitude(self, analyzer):
|
||||
"""Test magnitude calculation delegates to metrics."""
|
||||
result = analyzer.calculate_magnitude("old", "new")
|
||||
assert isinstance(result, float)
|
||||
assert 0.0 <= result <= 1.0
|
||||
|
||||
def test_calculate_magnitude_creation(self, analyzer):
|
||||
"""Test magnitude for creation."""
|
||||
assert analyzer.calculate_magnitude(None, "new") == 1.0
|
||||
|
||||
def test_calculate_magnitude_identical(self, analyzer):
|
||||
"""Test magnitude for identical content."""
|
||||
assert analyzer.calculate_magnitude("same", "same") == 0.0
|
||||
|
||||
def test_should_recompute_above_threshold(self, analyzer):
|
||||
"""Test recompute when magnitude exceeds threshold."""
|
||||
config = RecomputeConfig(impact_threshold=0.3)
|
||||
assert analyzer.should_recompute(0.5, config) is True
|
||||
|
||||
def test_should_recompute_at_threshold(self, analyzer):
|
||||
"""Test recompute when magnitude equals threshold."""
|
||||
config = RecomputeConfig(impact_threshold=0.5)
|
||||
assert analyzer.should_recompute(0.5, config) is True
|
||||
|
||||
def test_should_not_recompute_below_threshold(self, analyzer):
|
||||
"""Test no recompute when magnitude below threshold."""
|
||||
config = RecomputeConfig(impact_threshold=0.5)
|
||||
assert analyzer.should_recompute(0.3, config) is False
|
||||
|
||||
def test_zero_threshold_always_recomputes(self, analyzer):
|
||||
"""Test zero threshold means any change triggers recompute."""
|
||||
config = RecomputeConfig(impact_threshold=0.0)
|
||||
assert analyzer.should_recompute(0.0, config) is True
|
||||
assert analyzer.should_recompute(0.01, config) is True
|
||||
|
||||
def test_high_threshold_only_major_changes(self, analyzer):
|
||||
"""Test high threshold only triggers on major changes."""
|
||||
config = RecomputeConfig(impact_threshold=0.9)
|
||||
assert analyzer.should_recompute(0.5, config) is False
|
||||
assert analyzer.should_recompute(0.95, config) is True
|
||||
Reference in New Issue
Block a user