test: add platform user story tests (43/43, 4 categories)

This commit is contained in:
hangshuo652
2026-06-18 17:10:40 +08:00
parent 2662c6c0ac
commit ecc5599b48
2 changed files with 470 additions and 0 deletions
+5
View File
@@ -9,6 +9,11 @@ class ReportGenerator:
"timestamp": run.timestamp, "duration_s": run.duration_s,
"fields_matched": run.fields_matched, "fields_mismatched": run.fields_mismatched,
"runner": run.runner, "branch_rate": run.branch_rate, "llm_cost": run.llm_cost,
"paragraph_rate": run.paragraph_rate, "decision_rate": run.decision_rate,
"quality_score": run.quality_score, "quality_warn": run.quality_warn,
"hina_type": run.hina_type, "hina_confidence": run.hina_confidence,
"heal_retry": run.heal_retry, "simple_retry": run.simple_retry,
"total_retry": run.total_retry,
"field_results": [{"field_name": fr.field_name, "status": fr.status,
"cobol_value": fr.cobol_value, "java_value": fr.java_value,
"suggestion": fr.suggestion} for fr in run.field_results]}
+465
View File
@@ -0,0 +1,465 @@
"""
cobol-java-v3 平台用户故事测试
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
测试对象: cobol-java-v3 平台自身(不是COBOL程序)
测试范围: 正常 / 异常 / 边界 / 缺陷 4类用户故事
执行: python -X utf8 test-data/test_platform_user_stories.py
"""
import sys, os, json, time, tempfile, shutil, traceback
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from data.diff_result import VerificationRun, FieldResult
from data.test_case import TestCase, TestSuite, SparkConfig
from data.field_tree import FieldTree
PASS = 0
FAIL = 0
ERRORS = []
def section(title):
print(f"\n{''*70}")
print(f" {title}")
print(f"{''*70}")
def test(name, category):
def decorator(fn):
global PASS, FAIL
try:
fn()
PASS += 1
print(f" [{category}] {name} → ✅ PASS")
except Exception as e:
FAIL += 1
tb = traceback.format_exc()[-300:]
ERRORS.append(f"{name}: {e}")
print(f" [{category}] {name} → ❌ FAIL: {e}")
print(f" {tb.split(chr(10))[-3]}")
return fn
return decorator
# ════════════════════════════════════════════
# 正常系 — Normal
# ════════════════════════════════════════════
section("N: 正常系ユーザーストーリー")
@test("VerificationRun 作成と全フィールド設定", "NORMAL")
def _():
vr = VerificationRun(program="TESTPGM", runner="native")
assert vr.program == "TESTPGM"
assert vr.runner == "native"
assert vr.timestamp != ""
vr.branch_rate = 0.95
vr.paragraph_rate = 1.0
vr.hina_type = "マッチング"
vr.quality_score = 0.85
vr.heal_retry = 1
assert vr.branch_rate == 0.95
assert vr.hina_type == "マッチング"
@test("TestCase 作成とフィールド設定", "NORMAL")
def _():
tc = TestCase(id="TC-001", fields={"BR-AMT": 1500, "BR-STATUS": "A"})
assert tc.id == "TC-001"
assert tc.fields["BR-AMT"] == 1500
assert tc.fields["BR-STATUS"] == "A"
assert tc.coverage_targets == []
@test("FieldResult 作成とステータス", "NORMAL")
def _():
fr = FieldResult(field_name="BR-AMT", status="PASS", cobol_value="1500", java_value="1500.00")
assert fr.field_name == "BR-AMT"
assert fr.status == "PASS"
fr.status = "MISMATCH"
assert fr.status == "MISMATCH"
@test("Config デフォルト値", "NORMAL")
def _():
from config import Config
c = Config()
assert c.quality_gate_mode == "warn"
assert c.runner_mode == "native"
assert c.dialect == "ibm"
assert c.gcov_enabled == False
assert c.max_quality_retries == 4
@test("Config from_toml 正常", "NORMAL")
def _():
from config import Config
c = Config.from_toml(path=Path(__file__).parent.parent / "aurak.toml")
assert c.project_name != "" or c.runner_mode != ""
@test("VerificationRun total_fields 計算", "NORMAL")
def _():
vr = VerificationRun(fields_matched=10, fields_mismatched=2)
assert vr.total_fields == 12
@test("HINA classifier L1: DB操作", "NORMAL")
def _():
from hina.classifier import detect_keyword
r = detect_keyword("EXEC SQL SELECT * FROM TABLE END-EXEC")
assert any("DB操作" in x[0] for x in r)
assert any(x[1] >= 0.95 for x in r)
@test("HINA classifier L1: CALL", "NORMAL")
def _():
from hina.classifier import detect_keyword
r = detect_keyword("CALL 'SUBPGM' USING A.\nLINKAGE SECTION.")
assert any("子程序调用" in x[0] for x in r)
@test("HINA strategy マッチングテンプレート", "NORMAL")
def _():
from hina.strategy import get_strategy
s = get_strategy("マッチング")
assert len(s["required"]) == 9
@test("Quality gate: 合格", "NORMAL")
def _():
from hina.gate import check
r = check([{"a": 1}], {}, {"branch_rate": 0.95, "paragraph_rate": 1.0, "uncovered_decision_ids": []})
assert r["passed"] == True
@test("RetryHandler: 即PASS", "NORMAL")
def _():
from hina.retry import RetryHandler
h = RetryHandler()
vr = h.run(lambda: VerificationRun(status="PASS"))
assert vr.status == "PASS"
assert vr.heal_retry == 0
@test("ReportGenerator: HTML生成", "NORMAL")
def _():
from report.generator import ReportGenerator
vr = VerificationRun(program="TEST", runner="native")
rd = Path(tempfile.mkdtemp())
try:
g = ReportGenerator()
p = g.generate_html(vr, rd / "test.html")
assert p.exists()
html = p.read_text(encoding="utf-8")
assert "TEST" in html
finally:
shutil.rmtree(rd)
@test("ReportGenerator: HTML カバレッジ表示", "NORMAL")
def _():
from report.generator import ReportGenerator
vr = VerificationRun(program="T1", paragraph_rate=0.9, branch_rate=0.85)
rd = Path(tempfile.mkdtemp())
try:
p = ReportGenerator().generate_html(vr, rd / "t.html")
html = p.read_text(encoding="utf-8")
assert "段落覆盖率" in html
assert "分支覆盖率" in html
finally:
shutil.rmtree(rd)
@test("ReportGenerator: HTML HINA表示", "NORMAL")
def _():
from report.generator import ReportGenerator
vr = VerificationRun(program="T2", hina_type="マッチング", hina_confidence=0.95)
rd = Path(tempfile.mkdtemp())
try:
p = ReportGenerator().generate_html(vr, rd / "t.html")
assert "HINA" in p.read_text(encoding="utf-8")
finally:
shutil.rmtree(rd)
@test("ReportGenerator: JSON 新フィールド", "NORMAL")
def _():
from report.generator import ReportGenerator
vr = VerificationRun(program="T3", branch_rate=0.9, quality_score=0.85)
rd = Path(tempfile.mkdtemp())
try:
p = ReportGenerator().generate_json(vr, rd / "t.json")
d = json.loads(p.read_text())
assert d["branch_rate"] == 0.9
assert d["quality_score"] == 0.85
finally:
shutil.rmtree(rd)
@test("cobol_testgen extract_structure: IF", "NORMAL")
def _():
from cobol_testgen import extract_structure
s = extract_structure("PROCEDURE DIVISION.\nIF A>B MOVE 1 TO C ELSE MOVE 2 TO C.\nGOBACK.")
assert "paragraphs" in s
assert "decision_points" in s
# ════════════════════════════════════════════
# 異常系 — Abnormal
# ════════════════════════════════════════════
section("A: 異常系ユーザーストーリー")
@test("空COBOLソース→extract_structure", "ABNORMAL")
def _():
from cobol_testgen import extract_structure
s = extract_structure("")
assert s is not None
assert s.get("total_branches", 0) == 0
@test("PROCEDURE DIVISIONなし→extract_structure", "ABNORMAL")
def _():
from cobol_testgen import extract_structure
s = extract_structure("IDENTIFICATION DIVISION.\nPROGRAM-ID. X.\nDATA DIVISION.\nWORKING-STORAGE SECTION.\n01 A PIC X(10).")
assert s is not None
assert "paragraphs" in s
@test("Quality gate: 空データ", "ABNORMAL")
def _():
from hina.gate import check
r = check([], {}, {"branch_rate": 0.0, "paragraph_rate": 0.0, "uncovered_decision_ids": []})
assert r["passed"] == False
assert "no_data" in r.get("issues", {})
@test("Quality gate: 分岐不足", "ABNORMAL")
def _():
from hina.gate import check
r = check([{"x": 1}], {}, {"branch_rate": 0.5, "paragraph_rate": 1.0, "uncovered_decision_ids": [1, 2]})
assert r["passed"] == False
assert "decision_gaps" in r.get("issues", {})
@test("RetryHandler: 全FAIL→FATAL", "ABNORMAL")
def _():
from hina.retry import RetryHandler
from data.diff_result import VerificationRun
h = RetryHandler(max_heal=1, max_simple=1)
vr = h.run(lambda: VerificationRun(status="ERROR", exit_code=3))
assert vr.status == "FATAL"
assert vr.exit_code == 4
@test("Config: 必須fieldなし", "ABNORMAL")
def _():
from config import Config
c = Config.from_toml(path="nonexistent.toml")
assert c.runner_mode == "native"
assert c.quality_gate_mode == "warn"
@test("extract_structure: 不正COBOL構文", "ABNORMAL")
def _():
from cobol_testgen import extract_structure
s = extract_structure("THIS IS NOT VALID COBOL @@@ @@@")
assert s is not None
@test("generate_data: 分岐なしプログラム", "ABNORMAL")
def _():
from cobol_testgen import generate_data
s = "PROCEDURE DIVISION.\nGOBACK."
r = generate_data(s)
assert isinstance(r, list)
assert len(r) == 0
@test("incremental_supplement: 存在しないID", "ABNORMAL")
def _():
from cobol_testgen import incremental_supplement
r = incremental_supplement(None, [-1])
assert isinstance(r, list)
@test("VerificationRun: 空フィールド", "ABNORMAL")
def _():
vr = VerificationRun()
assert vr.total_fields == 0
assert vr.status == "PASS"
@test("HINA classifier: キーワードなし", "ABNORMAL")
def _():
from hina.classifier import compute_confidence
r = compute_confidence("PROCEDURE DIVISION.\nDISPLAY 'HELLO'.")
assert r["category"] == "unknown"
assert r["confidence"] == 0.0
@test("HINA strategy: 未知のタイプ", "ABNORMAL")
def _():
from hina.strategy import get_strategy
s = get_strategy("UNKNOWN_TYPE_XXX")
assert s["required"] == []
@test("gcov_collector: ファイルなし", "ABNORMAL")
def _():
from hina.gcov_collector import collect_gcov
r = collect_gcov(Path("nonexistent.cbl"), Path("/dev/null"))
assert r["available"] == False
assert "reason" in r
# ════════════════════════════════════════════
# 境界系 — Boundary
# ════════════════════════════════════════════
section("B: 境界系ユーザーストーリー")
@test("超巨大プログラム: 1000個IF", "BOUNDARY")
def _():
from cobol_testgen import extract_structure
lines = ["PROCEDURE DIVISION."]
for i in range(1000):
lines.append(f"IF A > {i} THEN MOVE {i} TO X ELSE MOVE {i} TO Y END-IF.")
lines.append("GOBACK.")
src = "\n".join(lines)
t0 = time.time()
s = extract_structure(src)
elapsed = time.time() - t0
print(f" → 1000 IF: {elapsed:.1f}s, 安定")
assert s is not None
assert elapsed < 10 # 10秒以内に完了
@test("超長フィールド名: 1000文字", "BOUNDARY")
def _():
from cobol_testgen import extract_structure
long = "A" * 1000
src = f"""IDENTIFICATION DIVISION.
PROGRAM-ID. X.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 {long} PIC X(10).
PROCEDURE DIVISION.
GOBACK."""
s = extract_structure(src)
assert s is not None
@test("TestSuite 0件", "BOUNDARY")
def _():
ts = TestSuite()
assert ts.has_spark == False
assert len(ts.test_cases) == 0
@test("SparkConfig 大量レコード", "BOUNDARY")
def _():
from data.test_case import SparkConfig
sc = SparkConfig(num_records=100000)
assert sc.num_records == 100000
@test("VerificationRun 全フィールド最大値", "BOUNDARY")
def _():
vr = VerificationRun(fields_matched=9999, fields_mismatched=9999)
assert vr.total_fields == 19998
vr.branch_rate = 1.0
vr.quality_score = 1.0
assert vr.branch_rate == 1.0
@test("100並列TestCases作成", "BOUNDARY")
def _():
cases = [TestCase(id=f"TC-{i:04d}", fields={"X": i}) for i in range(100)]
assert len(cases) == 100
assert cases[0].id == "TC-0000"
assert cases[99].id == "TC-0099"
# ════════════════════════════════════════════
# 欠陥系 — Defect (過去修正したバグの回帰)
# ════════════════════════════════════════════
section("D: 欠陥系ユーザーストーリー (回帰テスト)")
@test("DEFECT-001:complete_tests→DataWriter", "DEFECT")
def _():
"""P1修复: complete_tests 必须传递给 DataWriter"""
from data.test_case import TestCase
tc = TestCase(id="CTG-0001", fields={"TX-AMT": 100})
assert tc.id == "CTG-0001"
assert tc.fields["TX-AMT"] == 100
# DataWriter 接受 TestCase[]
from data.test_case import TestSuite
ts = TestSuite(test_cases=[tc])
assert len(ts.test_cases) == 1
@test("DEFECT-002:质量门禁循环中同步更新", "DEFECT")
def _():
"""P2修复: 增量补充后complete_tests需要更新"""
from data.test_case import TestCase
base = [TestCase(id=f"B{i}", fields={"v": i}) for i in range(3)]
delta = [TestCase(id=f"D{i}", fields={"v": i+10}) for i in range(2)]
combined = base + delta
assert len(combined) == 5
assert combined[3].id == "D0"
@test("DEFECT-003:分层重试 heal恢复", "DEFECT")
def _():
"""分层重试: heal修复后应成功"""
from hina.retry import RetryHandler
from data.diff_result import VerificationRun
called = [0]
def fn():
called[0] += 1
if called[0] <= 2:
return VerificationRun(status="BLOCKED", exit_code=2,
debug={"cobol_build": {"log": "not found"}})
return VerificationRun(status="PASS")
h = RetryHandler(max_heal=3, max_simple=1)
vr = h.run(fn)
assert vr.status == "PASS"
assert vr.heal_retry > 0
@test("DEFECT-004:COPYBOOKファイル名不一致", "DEFECT")
def _():
"""修复: COPY BBBBBFC (5B+FC) の解決"""
from cobol_testgen.read import resolve_copybooks
src = " COPY BBBBBFC REPLACING ==(A)== BY ==R01==."
# copybookファイルがなくてもクラッシュしない
result = resolve_copybooks(src, "/nonexistent")
assert result is not None
@test("DEFECT-005:Lark VALUE句解析", "DEFECT")
def _():
"""修复: VALUE '文字' のLark解析"""
from cobol_testgen import extract_structure
src = "IDENTIFICATION DIVISION.\nPROGRAM-ID. X.\nDATA DIVISION.\nWORKING-STORAGE SECTION.\n01 A PIC X(10) VALUE 'TEST'.\nPROCEDURE DIVISION.\nGOBACK."
s = extract_structure(src)
assert s is not None
@test("DEFECT-006:OPEN方向OUTPUT誤認識", "DEFECT")
def _():
"""修复: OPEN方向キーワードがファイル名に含まれない"""
from cobol_testgen.read import scan_open_statements
src = "OPEN INPUT TRANS-FILE.\nOPEN OUTPUT OUTPUT-FILE."
dirs = scan_open_statements(src)
# 'OUTPUT'は方向キーワードとして除外され、ファイル名にはならない
assert 'OUTPUT' not in dirs # キーワードはフィルタされる
assert 'OUTPUT-FILE' in dirs
assert dirs['OUTPUT-FILE'] == 'OUTPUT'
@test("DEFECT-007:Enum値一致判定", "DEFECT")
def _():
"""HINA分類のmethodキー存在確認"""
from hina.classifier import compute_confidence
r = compute_confidence("EXEC SQL SELECT\nEND-EXEC.")
assert "method" in r
assert r["method"] == "keyword"
r2 = compute_confidence("DISPLAY 'X'.")
assert r2["method"] == "none"
@test("DEFECT-008:machine_json全フィールド", "DEFECT")
def _():
"""P5修复: machine_jsonに全フィールド含む"""
from report.generator import ReportGenerator
vr = VerificationRun(program="TEST", branch_rate=0.9, paragraph_rate=0.8,
quality_score=0.85, hina_type="M", hina_confidence=0.95)
rd = Path(tempfile.mkdtemp())
try:
p = ReportGenerator().generate_machine_json(vr, rd / "m.json")
d = json.loads(p.read_text())
assert "branch_rate" in d
assert "paragraph_rate" in d
assert "quality_score" in d
assert "hina_type" in d
finally:
shutil.rmtree(rd)
# ════════════════════════════════════════════
# 集計
# ════════════════════════════════════════════
section("テスト結果集計")
total = PASS + FAIL
print(f"\n 総テスト数: {total}")
print(f" 合格: {PASS}")
print(f" 不合格: {FAIL}")
print(f" 合格率: {PASS/max(total,1)*100:.1f}%")
print(f"\n RESULT: {'ALL PASSED' if FAIL==0 else 'SOME FAILED'}")
if ERRORS:
print(f"\n 失敗詳細:")
for e in ERRORS:
print(f"{e}")
sys.exit(0 if FAIL == 0 else 1)