Coverage for tests / test_common.py: 100%
98 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1"""Tests for shared Halstead computation logic."""
3import math
5from common import compute_halstead, compute_ldi
6from models import HalsteadMetrics
9def test_known_input():
10 """Verify computed metrics against hand-calculated values."""
11 # 3 distinct operators, 4 distinct operands
12 # 5 total operators, 7 total operands
13 operators = ["+", "+", "*", "*", "="]
14 operands = ["x", "y", "z", "x", "y", "z", "w"]
16 h = compute_halstead(operators, operands)
18 assert h.n1 == 3 # distinct operators: +, *, =
19 assert h.n2 == 4 # distinct operands: x, y, z, w
20 assert h.N1 == 5 # total operators
21 assert h.N2 == 7 # total operands
22 assert h.vocabulary == 7 # n = 3 + 4
23 assert h.length == 12 # N = 5 + 7
25 expected_volume = 12 * math.log2(7)
26 assert h.volume == round(expected_volume, 2)
28 expected_difficulty = (3 / 2) * (7 / 4)
29 assert h.difficulty == round(expected_difficulty, 2)
31 expected_effort = expected_difficulty * expected_volume
32 assert h.effort == round(expected_effort, 2)
34 expected_time = expected_effort / 18
35 assert h.time == round(expected_time, 2)
37 expected_bugs = expected_effort ** (2 / 3) / 3000
38 assert h.bugs == round(expected_bugs, 4)
41def test_empty_inputs():
42 """Empty operator/operand lists produce zeroed metrics."""
43 h = compute_halstead([], [])
44 assert h.n1 == 0
45 assert h.n2 == 0
46 assert h.N1 == 0
47 assert h.N2 == 0
48 assert h.vocabulary == 0
49 assert h.length == 0
50 assert h.volume == 0.0
51 assert h.difficulty == 0.0
52 assert h.effort == 0.0
53 assert h.time == 0.0
54 assert h.bugs == 0.0
57def test_zero_operands_guard():
58 """Operators only (no operands) should not crash -- guards n2=0 division."""
59 h = compute_halstead(["+", "-"], [])
60 assert h.n1 == 2
61 assert h.n2 == 0
62 assert h.N1 == 2
63 assert h.N2 == 0
64 assert h.vocabulary == 2
65 assert h.length == 2
66 # Derived measures should be zero since n2=0
67 assert h.volume == 0.0
68 assert h.difficulty == 0.0
69 assert h.effort == 0.0
72def test_zero_operators_guard():
73 """Operands only (no operators) should not crash."""
74 h = compute_halstead([], ["x", "y"])
75 assert h.n1 == 0
76 assert h.n2 == 2
77 assert h.N1 == 0
78 assert h.N2 == 2
79 assert h.vocabulary == 2
80 assert h.length == 2
81 # n=2 and n2=2 so derived measures can be computed, but n1=0 means D=0
82 assert h.volume == round(2 * math.log2(2), 2)
83 assert h.difficulty == 0.0 # (0/2) * (2/2) = 0
86def test_effort_equals_d_times_v():
87 """E = D * V relationship holds."""
88 operators = ["=", "+", "*"]
89 operands = ["a", "b", "c", "a"]
90 h = compute_halstead(operators, operands)
91 # Verify E = D * V within rounding tolerance
92 assert abs(h.effort - h.difficulty * h.volume) < 0.1
95def test_time_equals_effort_over_18():
96 """T = E / 18 relationship holds."""
97 operators = ["=", "+", "-", "="]
98 operands = ["x", "y", "z", "result"]
99 h = compute_halstead(operators, operands)
100 assert abs(h.time - h.effort / 18) < 0.1
103def test_dataclass_defaults():
104 """HalsteadMetrics defaults are all zero."""
105 h = HalsteadMetrics()
106 assert h.n1 == 0
107 assert h.volume == 0.0
108 assert h.bugs == 0.0
111def test_single_operator_single_operand():
112 """Minimal non-empty program."""
113 h = compute_halstead(["="], ["x"])
114 assert h.n1 == 1
115 assert h.n2 == 1
116 assert h.vocabulary == 2
117 assert h.length == 2
118 assert h.volume == round(2 * math.log2(2), 2)
119 assert h.difficulty == round(0.5 * 1.0, 2)
122def test_ldi_basic():
123 """Verify LDI computation with known values."""
124 # Test case: vocabulary=10, n2=5, volume=8, difficulty=2.5
125 # Expected: (10/100 * 40) + (2.5/60 * 40) + (5/8 * 200) = 4 + 1.67 + 125 = 130.67, capped at 100
126 ldi = compute_ldi(volume=8, vocabulary=10, n2=5, difficulty=2.5)
127 assert ldi == 100.0
130def test_ldi_within_range():
131 """Verify LDI stays within 0-100 range."""
132 # Lower vocabulary/difficulty should produce smaller LDI
133 h = compute_halstead(["=", "+", "*"], ["a", "b", "c", "a"])
134 ldi = compute_ldi(h.volume, h.vocabulary, h.n2, h.difficulty)
135 assert 0 <= ldi <= 100
138def test_ldi_zero_volume():
139 """Zero volume returns 0 LDI."""
140 ldi = compute_ldi(volume=0, vocabulary=10, n2=5, difficulty=2.5)
141 assert ldi == 0.0
144def test_ldi_formula_components():
145 """Verify individual components of LDI formula."""
146 ldi = compute_ldi(volume=50, vocabulary=20, n2=10, difficulty=30)
147 # (20/100 * 40) = 8, (30/60 * 40) = 20, (10/50 * 200) = 40 = 68
148 assert round(ldi, 2) == 68.0
151def test_ldi_known_input():
152 """Calculate LDI from known Halstead metrics."""
153 h = compute_halstead(["=", "+", "*"], ["a", "b", "c", "a"])
154 # LDI should be computed without error
155 ldi = compute_ldi(h.volume, h.vocabulary, h.n2, h.difficulty)
156 assert isinstance(ldi, float)
157 assert 0 <= ldi <= 100