final-vibevault-template/.autograde/run_tests.py
2025-12-02 15:55:32 +08:00

165 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""
通用测试运行器 - 根据语言配置运行测试并生成 JUnit XML
支持的语言:
- python: pytest
- java: maven (mvn test)
- r: testthat (通过 JUnit Reporter)
环境变量:
- LANGUAGE: 编程语言 (python/java/r)
- TEST_DIR: 测试目录路径
- SOURCE_DIR: 源代码目录路径
"""
import argparse
import subprocess
import sys
import os
from pathlib import Path
def run_python_tests(test_dir, output_xml, **kwargs):
"""运行 Python pytest 测试"""
cmd = [
"pytest", test_dir,
f"--junit-xml={output_xml}",
"-v", "--tb=short"
]
# 添加覆盖率选项(如果指定)
source_dir = kwargs.get('source_dir')
if source_dir:
cmd.extend([
f"--cov={source_dir}",
"--cov-report=term-missing",
"--cov-report=json:coverage.json"
])
print(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=False)
return result
def run_java_tests(test_dir, output_xml, **kwargs):
"""运行 Java Maven 测试"""
cmd = ["mvn", "test", "-B"]
print(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=False)
# Maven 自动生成 XML 在 target/surefire-reports/
# 需要复制到指定的输出位置
surefire_dir = Path("target/surefire-reports")
if surefire_dir.exists():
# 合并所有 TEST-*.xml 文件
import xml.etree.ElementTree as ET
xml_files = list(surefire_dir.glob("TEST-*.xml"))
if xml_files:
# 简单情况:只复制第一个(或合并)
import shutil
if len(xml_files) == 1:
shutil.copy(xml_files[0], output_xml)
else:
# 合并多个 XML 文件(简化版本)
root = ET.Element("testsuites")
for xml_file in xml_files:
tree = ET.parse(xml_file)
root.append(tree.getroot())
tree = ET.ElementTree(root)
tree.write(output_xml, encoding='utf-8', xml_declaration=True)
return result
def run_r_tests(test_dir, output_xml, **kwargs):
"""运行 R testthat 测试"""
# R 脚本:使用 testthat 的 JUnitReporter
# 注意:需要安装 testthat (>= 3.0.0)
r_script = f"""
library(testthat)
# 配置 JUnit reporter
reporter <- JunitReporter$new(file = '{output_xml}')
# 运行测试
test_dir(
path = '{test_dir}',
reporter = reporter,
stop_on_failure = FALSE
)
"""
# 将脚本写入临时文件
import tempfile
with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as f:
f.write(r_script)
script_path = f.name
try:
cmd = ["Rscript", script_path]
print(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=False)
return result
finally:
# 清理临时文件
if os.path.exists(script_path):
os.remove(script_path)
def main():
parser = argparse.ArgumentParser(
description="通用测试运行器 - 支持 Python/Java/R"
)
parser.add_argument(
"--language",
required=True,
choices=["python", "java", "r"],
help="编程语言"
)
parser.add_argument(
"--test-dir",
required=True,
help="测试目录路径"
)
parser.add_argument(
"--output-xml",
default="test-results.xml",
help="JUnit XML 输出文件路径"
)
parser.add_argument(
"--source-dir",
help="源代码目录(用于覆盖率)"
)
args = parser.parse_args()
# 语言对应的运行器
runners = {
"python": run_python_tests,
"java": run_java_tests,
"r": run_r_tests,
}
if args.language not in runners:
print(f"❌ Unsupported language: {args.language}", file=sys.stderr)
sys.exit(1)
# 运行测试
result = runners[args.language](
args.test_dir,
args.output_xml,
source_dir=args.source_dir
)
sys.exit(result.returncode)
if __name__ == "__main__":
main()