- 修复 orchestrator_db.py: Java Runner 未传递 command_line 参数导致 ABEND - 新增 DB-Java 文件式运行 + DB 表比对功能 - 优化输出目录结构: output/<PROGRAM_ID>/cobol/ - 新增测试文件: test_java_comparison.py, test_java_e2e.py - 更新 AI 使用日志
376 lines
14 KiB
Python
376 lines
14 KiB
Python
import pytest
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
import subprocess
|
|
import json
|
|
|
|
|
|
class TestNativeJavaRunner:
|
|
"""NativeJavaRunner 编译和执行测试"""
|
|
|
|
def test_compile_success(self):
|
|
"""测试Java编译成功"""
|
|
from runners.native_java_runner import NativeJavaRunner
|
|
|
|
runner = NativeJavaRunner()
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.return_value = Mock(returncode=0, stdout="", stderr="")
|
|
result = runner.compile("tests/fixtures/java")
|
|
|
|
assert result.success is True
|
|
assert result.artifact_path != ""
|
|
mock_run.assert_called_once()
|
|
|
|
def test_compile_failure(self):
|
|
"""测试Java编译失败"""
|
|
from runners.native_java_runner import NativeJavaRunner
|
|
|
|
runner = NativeJavaRunner()
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.return_value = Mock(returncode=1, stdout="", stderr="Compilation error")
|
|
result = runner.compile("tests/fixtures/java")
|
|
|
|
assert result.success is False
|
|
assert "Compilation error" in result.log
|
|
|
|
def test_compile_source_not_found(self):
|
|
"""测试编译时源代码目录不存在"""
|
|
from runners.native_java_runner import NativeJavaRunner
|
|
|
|
runner = NativeJavaRunner()
|
|
result = runner.compile("nonexistent_dir")
|
|
|
|
assert result.success is False
|
|
assert "Source directory not found" in result.log
|
|
|
|
def test_compile_pom_not_found(self):
|
|
"""测试编译时pom.xml不存在"""
|
|
from runners.native_java_runner import NativeJavaRunner
|
|
|
|
runner = NativeJavaRunner()
|
|
result = runner.compile(".")
|
|
|
|
assert result.success is False
|
|
assert "pom.xml not found" in result.log
|
|
|
|
def test_run_success(self):
|
|
"""测试Java执行成功"""
|
|
from runners.native_java_runner import NativeJavaRunner
|
|
|
|
runner = NativeJavaRunner()
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.return_value = Mock(
|
|
returncode=0,
|
|
stdout='{"field1": "value1"}\n{"field2": "value2"}',
|
|
stderr=""
|
|
)
|
|
# 创建mock文件
|
|
mock_artifact = Path("test.jar")
|
|
mock_input = Path("input.json")
|
|
mock_artifact.touch()
|
|
mock_input.touch()
|
|
|
|
try:
|
|
result = runner.run("test.jar", "input.json", "output")
|
|
|
|
assert result.success is True
|
|
assert len(result.records) == 2
|
|
assert result.records[0]["field1"] == "value1"
|
|
finally:
|
|
mock_artifact.unlink(missing_ok=True)
|
|
mock_input.unlink(missing_ok=True)
|
|
|
|
def test_run_artifact_not_found(self):
|
|
"""测试执行时JAR文件不存在"""
|
|
from runners.native_java_runner import NativeJavaRunner
|
|
|
|
runner = NativeJavaRunner()
|
|
result = runner.run("nonexistent.jar", "input.json", "output")
|
|
|
|
assert result.success is False
|
|
assert "Artifact not found" in result.log
|
|
|
|
def test_run_input_not_found(self):
|
|
"""测试执行时输入文件不存在"""
|
|
from runners.native_java_runner import NativeJavaRunner
|
|
|
|
runner = NativeJavaRunner()
|
|
# 先创建artifact文件
|
|
mock_artifact = Path("test.jar")
|
|
mock_artifact.touch()
|
|
|
|
try:
|
|
result = runner.run("test.jar", "nonexistent.json", "output")
|
|
|
|
assert result.success is False
|
|
assert "Input file not found" in result.log
|
|
finally:
|
|
mock_artifact.unlink(missing_ok=True)
|
|
|
|
def test_run_timeout(self):
|
|
"""测试Java执行超时"""
|
|
from runners.native_java_runner import NativeJavaRunner
|
|
|
|
runner = NativeJavaRunner()
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.side_effect = subprocess.TimeoutExpired(cmd="java", timeout=60)
|
|
|
|
mock_artifact = Path("test.jar")
|
|
mock_input = Path("input.json")
|
|
mock_artifact.touch()
|
|
mock_input.touch()
|
|
|
|
try:
|
|
result = runner.run("test.jar", "input.json", "output")
|
|
|
|
assert result.success is False
|
|
assert "timed out" in result.log.lower()
|
|
finally:
|
|
mock_artifact.unlink(missing_ok=True)
|
|
mock_input.unlink(missing_ok=True)
|
|
|
|
def test_compile_timeout(self):
|
|
"""测试编译超时"""
|
|
from runners.native_java_runner import NativeJavaRunner
|
|
|
|
runner = NativeJavaRunner()
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.side_effect = subprocess.TimeoutExpired(cmd="mvn", timeout=120)
|
|
result = runner.compile("tests/fixtures/java")
|
|
|
|
assert result.success is False
|
|
assert "timed out" in result.log.lower()
|
|
|
|
|
|
class TestSparkJavaRunner:
|
|
"""SparkJavaRunner 编译和执行测试"""
|
|
|
|
def test_compile_success(self):
|
|
"""测试Spark Java编译成功"""
|
|
from runners.spark_java_runner import SparkJavaRunner
|
|
|
|
runner = SparkJavaRunner()
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.return_value = Mock(returncode=0, stdout="", stderr="")
|
|
result = runner.compile("tests/fixtures/java")
|
|
|
|
assert result.success is True
|
|
assert result.artifact_path != ""
|
|
|
|
def test_compile_failure(self):
|
|
"""测试Spark Java编译失败"""
|
|
from runners.spark_java_runner import SparkJavaRunner
|
|
|
|
runner = SparkJavaRunner()
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.return_value = Mock(returncode=1, stdout="", stderr="Compilation error")
|
|
result = runner.compile("tests/fixtures/java")
|
|
|
|
assert result.success is False
|
|
assert "Compilation error" in result.log
|
|
|
|
def test_run_success(self):
|
|
"""测试Spark Java执行成功"""
|
|
from runners.spark_java_runner import SparkJavaRunner
|
|
|
|
runner = SparkJavaRunner()
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.return_value = Mock(
|
|
returncode=0,
|
|
stdout='{"field1": "value1"}',
|
|
stderr=""
|
|
)
|
|
|
|
mock_artifact = Path("test.jar")
|
|
mock_input = Path("input.json")
|
|
mock_artifact.touch()
|
|
mock_input.touch()
|
|
|
|
try:
|
|
result = runner.run("test.jar", "input.json", "output")
|
|
|
|
assert result.success is True
|
|
finally:
|
|
mock_artifact.unlink(missing_ok=True)
|
|
mock_input.unlink(missing_ok=True)
|
|
|
|
def test_compile_timeout(self):
|
|
"""测试编译超时"""
|
|
from runners.spark_java_runner import SparkJavaRunner
|
|
|
|
runner = SparkJavaRunner()
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.side_effect = subprocess.TimeoutExpired(cmd="mvn", timeout=120)
|
|
result = runner.compile("tests/fixtures/java")
|
|
|
|
assert result.success is False
|
|
assert "timed out" in result.log.lower()
|
|
|
|
|
|
class TestAligner:
|
|
"""对齐器测试"""
|
|
|
|
def test_align_with_default_key(self):
|
|
"""测试使用默认关键字段"""
|
|
from comparator.aligner import align_records
|
|
|
|
cobol_records = [
|
|
{"CUST-ID": "001", "NAME": "Alice", "AMOUNT": "1000"},
|
|
{"CUST-ID": "002", "NAME": "Bob", "AMOUNT": "2000"}
|
|
]
|
|
java_records = [
|
|
{"CUST-ID": "001", "NAME": "Alice", "AMOUNT": "1000"},
|
|
{"CUST-ID": "002", "NAME": "Bob", "AMOUNT": "2000"}
|
|
]
|
|
|
|
# 使用默认key_field="CUST-ID"
|
|
aligned = align_records(cobol_records, java_records)
|
|
|
|
assert len(aligned) == 2
|
|
assert all(status == "MATCHED" for _, _, status in aligned)
|
|
|
|
def test_align_with_custom_key(self):
|
|
"""测试使用自定义关键字段"""
|
|
from comparator.aligner import align_records
|
|
|
|
cobol_records = [
|
|
{"ID": "001", "NAME": "Alice", "AMOUNT": "1000"},
|
|
{"ID": "002", "NAME": "Bob", "AMOUNT": "2000"}
|
|
]
|
|
java_records = [
|
|
{"ID": "001", "NAME": "Alice", "AMOUNT": "1000"},
|
|
{"ID": "002", "NAME": "Bob", "AMOUNT": "2000"}
|
|
]
|
|
|
|
# 使用自定义key_field="ID"
|
|
aligned = align_records(cobol_records, java_records, key_field="ID")
|
|
|
|
assert len(aligned) == 2
|
|
assert all(status == "MATCHED" for _, _, status in aligned)
|
|
|
|
def test_align_with_auto_detect(self):
|
|
"""测试自动检测关键字段"""
|
|
from comparator.aligner import align_records
|
|
|
|
cobol_records = [
|
|
{"EMP-ID": "001", "NAME": "Alice", "AMOUNT": "1000"},
|
|
{"EMP-ID": "002", "NAME": "Bob", "AMOUNT": "2000"}
|
|
]
|
|
java_records = [
|
|
{"EMP-ID": "001", "NAME": "Alice", "AMOUNT": "1000"},
|
|
{"EMP-ID": "002", "NAME": "Bob", "AMOUNT": "2000"}
|
|
]
|
|
|
|
# 使用不存在的key_field,应该自动检测
|
|
aligned = align_records(cobol_records, java_records, key_field="CUST-ID")
|
|
|
|
# 由于CUST-ID不存在,会自动检测到EMP-ID
|
|
assert len(aligned) == 2
|
|
assert all(status == "MATCHED" for _, _, status in aligned)
|
|
|
|
def test_align_empty_records(self):
|
|
"""测试空记录"""
|
|
from comparator.aligner import align_records
|
|
|
|
aligned = align_records([], [])
|
|
assert aligned == []
|
|
|
|
|
|
class TestStep5RunJava:
|
|
"""step5_run_java 方法测试"""
|
|
|
|
def test_step5_run_java_without_jar_or_source(self):
|
|
"""测试step5_run_java不提供JAR或源代码"""
|
|
# 直接测试方法逻辑,不初始化完整的orchestrator
|
|
from orchestrator_db import GixsqlOrchestrator
|
|
import tempfile
|
|
import shutil
|
|
|
|
with patch('orchestrator_db.load_schema') as mock_load:
|
|
mock_load.return_value = Mock()
|
|
|
|
# 创建临时目录
|
|
temp_dir = tempfile.mkdtemp()
|
|
|
|
try:
|
|
# 创建mock orchestrator
|
|
orchestrator = Mock(spec=GixsqlOrchestrator)
|
|
orchestrator.program_id = "TEST"
|
|
orchestrator.java_input_path = Path(temp_dir) / "test_input.json"
|
|
orchestrator.java_input_path.write_text("{}")
|
|
orchestrator.work_dir = Path(temp_dir)
|
|
orchestrator.runtime_dir = Path(temp_dir) / "output" / "TEST" / "cobol"
|
|
orchestrator.runtime_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# 调用真实方法
|
|
result = GixsqlOrchestrator.step5_run_java(orchestrator)
|
|
|
|
# 没有提供JAR或源代码,应该返回失败
|
|
assert result.success is False
|
|
assert "No Java JAR or source" in result.message
|
|
finally:
|
|
# 清理
|
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
|
|
def test_step5_run_java_timeout(self):
|
|
"""测试step5_run_java超时"""
|
|
from orchestrator_db import GixsqlOrchestrator
|
|
import tempfile
|
|
import shutil
|
|
|
|
with patch('orchestrator_db.load_schema') as mock_load, \
|
|
patch('orchestrator_db.subprocess.run') as mock_run:
|
|
mock_load.return_value = Mock()
|
|
mock_run.side_effect = subprocess.TimeoutExpired(cmd="java", timeout=60)
|
|
|
|
# 创建临时目录
|
|
temp_dir = tempfile.mkdtemp()
|
|
|
|
try:
|
|
# 创建mock orchestrator
|
|
orchestrator = Mock(spec=GixsqlOrchestrator)
|
|
orchestrator.program_id = "TEST"
|
|
orchestrator.java_input_path = Path(temp_dir) / "test_input.json"
|
|
orchestrator.java_input_path.write_text("{}")
|
|
orchestrator.work_dir = Path(temp_dir)
|
|
orchestrator.runtime_dir = Path(temp_dir) / "output" / "TEST" / "cobol"
|
|
orchestrator.runtime_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# 调用真实方法
|
|
result = GixsqlOrchestrator.step5_run_java(orchestrator, java_jar="test.jar")
|
|
|
|
assert result.success is False
|
|
assert "timeout" in result.message.lower()
|
|
finally:
|
|
# 清理
|
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
|
|
|
|
class TestStep6Verify:
|
|
"""step6_verify 方法测试"""
|
|
|
|
def test_step6_verify_no_records(self):
|
|
"""测试step6_verify没有COBOL和Java记录"""
|
|
from orchestrator_db import GixsqlOrchestrator
|
|
from data.diff_result import VerificationRun
|
|
|
|
with patch('orchestrator_db.load_schema') as mock_load:
|
|
mock_load.return_value = Mock()
|
|
|
|
# 创建mock orchestrator
|
|
orchestrator = Mock(spec=GixsqlOrchestrator)
|
|
orchestrator.program_id = "TEST"
|
|
orchestrator._current_db_path = None
|
|
orchestrator.db_path = None
|
|
orchestrator.java_output_path = None
|
|
orchestrator.runner = Mock()
|
|
orchestrator.schema = Mock()
|
|
orchestrator.schema.db_tables = []
|
|
|
|
# 调用真实方法
|
|
vr = GixsqlOrchestrator.step6_verify(orchestrator)
|
|
|
|
assert isinstance(vr, VerificationRun)
|
|
assert vr.status == "PASS"
|
|
assert vr.exit_code == 0
|