feat: Phase 2 complete — 13 Phases of COBOL type classification and test benchmark
P0.6: gcov infrastructure P1: extract_structure output expansion (11 new feature fields) P2: Confusion group rule engine (8 pairs + contradiction + backtrack) P3: 4-factor confidence calculation + quality gate update P4: 33+2 COBOL program type test samples (22 files, 7 categories) P5: parametrized/ test data generation engine P6: japanese_data.py lookup tables P7-10: Type-specific test suites (~159 parametrized tests) P11: Full classification pipeline (classify_program) + orchestrator integration P12: Documentation (module-interfaces, test-plan v3.0, coverage-matrix) Architecture decisions: - classification_pipeline/ merged to hina/pipeline/ - parametrized/ as independent module - japanese_data.py as root-level file - hina/__all__ only exports classify_program() Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
"""Phase 9: 横断系测试(轻量版 ~20 测试)。
|
||||
|
||||
覆盖四大领域:
|
||||
- VL: 可变长 / ODO 逻辑
|
||||
- LP: 循环 / PERFORM VARYING / UNTIL 逻辑
|
||||
- NP: 数值精度 / COMP-3 / ROUNDED 逻辑
|
||||
- D: 日期 / 闰年 / 月末 / 和历逻辑
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from datetime import date
|
||||
from typing import Any
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# VL: 可变长 / ODO 逻辑
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def _odo_offset(depending_on: int, base_size: int, item_size: int) -> int:
|
||||
"""模拟 COBOL OCCURS DEPENDING ON:
|
||||
总长 = 固定部 + 可变项数 * 每项大小
|
||||
"""
|
||||
if depending_on < 0:
|
||||
depending_on = 0
|
||||
if depending_on > 999:
|
||||
depending_on = 999
|
||||
return base_size + depending_on * item_size
|
||||
|
||||
|
||||
def _odo_read(table: list, start: int, count: int) -> list:
|
||||
"""模拟 ODO 读取指定数量的可变元素。"""
|
||||
return table[start:start + count]
|
||||
|
||||
|
||||
class TestODO:
|
||||
"""可变长 / ODO 逻辑 (5 tests)"""
|
||||
|
||||
def test_odo_basic_length(self):
|
||||
length = _odo_offset(5, 10, 4)
|
||||
assert length == 10 + 5 * 4
|
||||
|
||||
def test_odo_zero_items(self):
|
||||
assert _odo_offset(0, 10, 4) == 10
|
||||
|
||||
def test_odo_negative_depending(self):
|
||||
assert _odo_offset(-1, 10, 4) == 10
|
||||
|
||||
def test_odo_read_partial(self):
|
||||
table = [10, 20, 30, 40, 50]
|
||||
assert _odo_read(table, 1, 3) == [20, 30, 40]
|
||||
|
||||
def test_odo_read_beyond_end(self):
|
||||
table = [10, 20, 30]
|
||||
assert _odo_read(table, 1, 10) == [20, 30]
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# LP: 循环 / PERFORM VARYING / UNTIL 逻辑
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def _perform_varying(start: int, end: int, step: int = 1) -> list[int]:
|
||||
"""模拟 COBOL PERFORM VARYING: 返回每次循环的索引值。"""
|
||||
results: list[int] = []
|
||||
i = start
|
||||
if step > 0:
|
||||
while i <= end:
|
||||
results.append(i)
|
||||
i += step
|
||||
elif step < 0:
|
||||
while i >= end:
|
||||
results.append(i)
|
||||
i += step
|
||||
return results
|
||||
|
||||
|
||||
def _perform_until(initial: int, condition_func, body_func, max_iter: int = 1000) -> list:
|
||||
"""模拟 COBOL PERFORM UNTIL condition。"""
|
||||
results: list = []
|
||||
i = initial
|
||||
count = 0
|
||||
while not condition_func(i) and count < max_iter:
|
||||
val = body_func(i)
|
||||
results.append(val)
|
||||
i = val
|
||||
count += 1
|
||||
return results
|
||||
|
||||
|
||||
class TestPerformVarying:
|
||||
"""PERFORM VARYING 逻辑 (3 tests)"""
|
||||
|
||||
def test_varying_ascending(self):
|
||||
assert _perform_varying(1, 5) == [1, 2, 3, 4, 5]
|
||||
|
||||
def test_varying_step_2(self):
|
||||
assert _perform_varying(1, 10, 2) == [1, 3, 5, 7, 9]
|
||||
|
||||
def test_varying_descending(self):
|
||||
assert _perform_varying(5, 1, -1) == [5, 4, 3, 2, 1]
|
||||
|
||||
|
||||
class TestPerformUntil:
|
||||
"""PERFORM UNTIL 逻辑 (2 tests)"""
|
||||
|
||||
def test_until_reaches_target(self):
|
||||
result = _perform_until(1, lambda x: x >= 10, lambda x: x + 1)
|
||||
assert result == [2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
def test_until_condition_immediately_true(self):
|
||||
result = _perform_until(10, lambda x: x >= 10, lambda x: x + 1)
|
||||
assert result == []
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# NP: 数值精度 / COMP-3 / ROUNDED 逻辑
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def _comp3_to_value(bytes_data: bytes) -> int:
|
||||
"""模拟 COMP-3 (BCD) 到整数的转换。"""
|
||||
if not bytes_data:
|
||||
return 0
|
||||
last = bytes_data[-1]
|
||||
sign_nibble = last & 0x0F
|
||||
value_nibbles: list[int] = []
|
||||
for b in bytes_data[:-1]:
|
||||
value_nibbles.append((b >> 4) & 0x0F)
|
||||
value_nibbles.append(b & 0x0F)
|
||||
value_nibbles.append((last >> 4) & 0x0F)
|
||||
value = 0
|
||||
for nib in value_nibbles:
|
||||
value = value * 10 + nib
|
||||
if sign_nibble in (0x0D,):
|
||||
value = -value
|
||||
return value
|
||||
|
||||
|
||||
def _rounded(value: float, decimals: int) -> float:
|
||||
"""模拟 COBOL ROUNDED 子句。"""
|
||||
factor = 10 ** decimals
|
||||
return math.floor(value * factor + 0.5) / factor
|
||||
|
||||
|
||||
class TestComp3:
|
||||
"""COMP-3 数值精度 (3 tests)"""
|
||||
|
||||
def test_comp3_positive(self):
|
||||
# BCD: 0x12 0x3C -> 123
|
||||
assert _comp3_to_value(bytes([0x12, 0x3C])) == 123
|
||||
|
||||
def test_comp3_negative(self):
|
||||
# BCD: 0x45 0x6D -> -456
|
||||
assert _comp3_to_value(bytes([0x45, 0x6D])) == -456
|
||||
|
||||
def test_comp3_zero(self):
|
||||
assert _comp3_to_value(bytes([0x0C])) == 0
|
||||
|
||||
|
||||
class TestRounded:
|
||||
"""ROUNDED 子句 (2 tests)"""
|
||||
|
||||
def test_rounded_up(self):
|
||||
assert _rounded(1.235, 2) == 1.24
|
||||
|
||||
def test_rounded_down(self):
|
||||
assert _rounded(1.234, 2) == 1.23
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# D: 日期 / 闰年 / 月末 / 和历逻辑
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
def _is_leap_year(year: int) -> bool:
|
||||
return year % 400 == 0 or (year % 100 != 0 and year % 4 == 0)
|
||||
|
||||
|
||||
def _days_in_month(year: int, month: int) -> int:
|
||||
if month == 2:
|
||||
return 29 if _is_leap_year(year) else 28
|
||||
long_months = {1, 3, 5, 7, 8, 10, 12}
|
||||
return 31 if month in long_months else 30
|
||||
|
||||
|
||||
def _month_end_date(year: int, month: int) -> date:
|
||||
return date(year, month, _days_in_month(year, month))
|
||||
|
||||
|
||||
def _wareki_to_year(wareki_prefix: str, wareki_year: int) -> int:
|
||||
era_map = {
|
||||
"R": (2019, "令和"), "H": (1989, "平成"),
|
||||
"S": (1926, "昭和"), "T": (1912, "大正"),
|
||||
"M": (1868, "明治"),
|
||||
}
|
||||
if wareki_prefix not in era_map:
|
||||
raise ValueError(f"未知和历: {wareki_prefix!r}")
|
||||
return era_map[wareki_prefix][0] + wareki_year - 1
|
||||
|
||||
|
||||
class TestLeapYear:
|
||||
"""闰年判断 (2 tests)"""
|
||||
|
||||
def test_leap_year_divisible_by_400(self):
|
||||
assert _is_leap_year(2000) is True
|
||||
assert _is_leap_year(2400) is True
|
||||
|
||||
def test_leap_year_divisible_by_4_not_100(self):
|
||||
assert _is_leap_year(2024) is True
|
||||
assert _is_leap_year(2028) is True
|
||||
|
||||
|
||||
class TestMonthEnd:
|
||||
"""月末日期 (2 tests)"""
|
||||
|
||||
def test_february_leap_year(self):
|
||||
assert _days_in_month(2024, 2) == 29
|
||||
assert _month_end_date(2024, 2) == date(2024, 2, 29)
|
||||
|
||||
def test_february_non_leap(self):
|
||||
assert _days_in_month(2023, 2) == 28
|
||||
assert _month_end_date(2023, 2) == date(2023, 2, 28)
|
||||
|
||||
|
||||
class TestWareki:
|
||||
"""和历逻辑 (1 test)"""
|
||||
|
||||
def test_wareki_reiwa(self):
|
||||
assert _wareki_to_year("R", 5) == 2023
|
||||
|
||||
def test_wareki_invalid_prefix(self):
|
||||
try:
|
||||
_wareki_to_year("X", 1)
|
||||
assert False, "应抛出异常"
|
||||
except ValueError:
|
||||
pass
|
||||
Reference in New Issue
Block a user