bc1d56d1a4
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>
160 lines
5.7 KiB
Python
160 lines
5.7 KiB
Python
"""WR-01~07: Worker 进程测试"""
|
|
|
|
import sys, os, json, tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
|
|
from web.worker import main as worker_main
|
|
|
|
|
|
def _write_task(tasks_dir, task_id, status="queued", runner="native"):
|
|
data = {
|
|
"id": task_id, "status": status, "runner": runner,
|
|
"copybook": f"/tmp/{task_id}/copybook.cpy",
|
|
"cobol_src": f"/tmp/{task_id}/program.cbl",
|
|
"java_src": f"/tmp/{task_id}/java",
|
|
"mapping": f"/tmp/{task_id}/mapping.yaml",
|
|
}
|
|
(tasks_dir / f"{task_id}.json").write_text(json.dumps(data), encoding="utf-8")
|
|
|
|
|
|
# ── WR-01: No tasks ──
|
|
|
|
def test_worker_no_tasks():
|
|
"""WR-01: 空 tasks/ → 无操作"""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
with patch("web.worker.TASKS_DIR", Path(tmp)), \
|
|
patch("web.worker.time") as mock_time:
|
|
mock_time.sleep.side_effect = KeyboardInterrupt
|
|
try:
|
|
worker_main()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
assert True
|
|
|
|
|
|
# ── WR-02: Normal task ──
|
|
|
|
def test_worker_normal_task():
|
|
"""WR-02: queued 任务 → 处理"""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
tasks_dir = Path(tmp)
|
|
_write_task(tasks_dir, "t001")
|
|
mock_vr = MagicMock(
|
|
program="T", status="PASS", fields_matched=5, fields_mismatched=0,
|
|
duration_s=0.5, runner="native", field_results=[], debug={},
|
|
)
|
|
with patch("web.worker.TASKS_DIR", tasks_dir), \
|
|
patch("config.Config") as mock_cfg, \
|
|
patch("orchestrator.run_pipeline", return_value=mock_vr), \
|
|
patch("web.worker.time") as mock_time:
|
|
mock_time.sleep.side_effect = KeyboardInterrupt
|
|
mock_cfg.return_value = MagicMock()
|
|
try:
|
|
worker_main()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
assert (tasks_dir / "t001.json").exists()
|
|
|
|
|
|
# ── WR-03: null JSON / empty file ──
|
|
|
|
def test_worker_null_json():
|
|
"""WR-03: null JSON → error 状态写入"""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
tasks_dir = Path(tmp)
|
|
(tasks_dir / "n.json").write_text("null")
|
|
with patch("web.worker.TASKS_DIR", tasks_dir), \
|
|
patch("web.worker.time") as mock_time:
|
|
mock_time.sleep.side_effect = KeyboardInterrupt
|
|
try:
|
|
worker_main()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
data = json.loads((tasks_dir / "n.json").read_text(encoding="utf-8"))
|
|
assert data["status"] == "error"
|
|
|
|
|
|
def test_worker_empty_json():
|
|
"""WR-03b: 空文件 → error 状态写入"""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
tasks_dir = Path(tmp)
|
|
(tasks_dir / "e.json").write_text("")
|
|
with patch("web.worker.TASKS_DIR", tasks_dir), \
|
|
patch("web.worker.time") as mock_time:
|
|
mock_time.sleep.side_effect = KeyboardInterrupt
|
|
try:
|
|
worker_main()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
data = json.loads((tasks_dir / "e.json").read_text(encoding="utf-8"))
|
|
assert data["status"] == "error"
|
|
|
|
|
|
# ── WR-04: Spark without spark-submit ──
|
|
|
|
def test_worker_spark_no_submit():
|
|
"""WR-04: spark 无 spark-submit → worker 内部处理"""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
tasks_dir = Path(tmp)
|
|
_write_task(tasks_dir, "s001", runner="spark")
|
|
with patch("web.worker.TASKS_DIR", tasks_dir), \
|
|
patch("config.Config") as mock_cfg, \
|
|
patch("orchestrator.run_pipeline") as mock_run, \
|
|
patch("web.worker.time") as mock_time:
|
|
mock_time.sleep.side_effect = KeyboardInterrupt
|
|
mock_cfg.return_value = MagicMock()
|
|
mock_run.return_value = MagicMock(
|
|
program="S", status="PASS", fields_matched=3, fields_mismatched=0,
|
|
duration_s=0.2, runner="spark", field_results=[], debug={},
|
|
)
|
|
try:
|
|
worker_main()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
assert True
|
|
|
|
|
|
# ── WR-05: Multiple tasks ──
|
|
|
|
def test_worker_multiple_tasks():
|
|
"""WR-05: 2个 queued → 依次处理"""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
tasks_dir = Path(tmp)
|
|
_write_task(tasks_dir, "a1")
|
|
_write_task(tasks_dir, "a2")
|
|
mock_vr = MagicMock(
|
|
program="M", status="PASS", fields_matched=4, fields_mismatched=0,
|
|
duration_s=0.1, runner="native", field_results=[], debug={},
|
|
)
|
|
with patch("web.worker.TASKS_DIR", tasks_dir), \
|
|
patch("config.Config") as mock_cfg, \
|
|
patch("orchestrator.run_pipeline", return_value=mock_vr), \
|
|
patch("web.worker.time") as mock_time:
|
|
mock_time.sleep.side_effect = KeyboardInterrupt
|
|
mock_cfg.return_value = MagicMock()
|
|
try:
|
|
worker_main()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
assert True
|
|
|
|
|
|
# ── WR-07: Task state machine ──
|
|
|
|
def test_task_state_machine():
|
|
"""WR-07: 只处理 queued 任务"""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
tasks_dir = Path(tmp)
|
|
_write_task(tasks_dir, "rt1", status="running")
|
|
with patch("web.worker.TASKS_DIR", tasks_dir), \
|
|
patch("web.worker.time") as mock_time:
|
|
mock_time.sleep.side_effect = KeyboardInterrupt
|
|
try:
|
|
worker_main()
|
|
except KeyboardInterrupt:
|
|
pass
|
|
data = json.loads((tasks_dir / "rt1.json").read_text(encoding="utf-8"))
|
|
assert data["status"] == "running"
|