Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions tests/gen-ut-summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Generate ut-summary.json from gtest XML reports and lcov coverage data.

Usage:
Called from shell test scripts. Reads environment variables:
- projectdir: project root directory
- builddir: build directory name (relative to projectdir)
- reportdir: report output directory name (relative to projectdir)

Optional overrides (take precedence over defaults):
- GTEST_XML_DIR: directory containing gtest XML reports
- COVERAGE_INFO: path to lcov .info file for coverage parsing

Output: {projectdir}/{reportdir}/ut-summary.json
"""

import json
import xml.etree.ElementTree as ET
import glob
import os
import subprocess
import re
import sys


def parse_gtest_xml(xml_dir):
"""Parse Google Test / JUnit XML output and return (total, passed, failed)."""
total = passed = failed = 0
for xml_file in sorted(glob.glob(os.path.join(xml_dir, "*.xml"))):
try:
root = ET.parse(xml_file).getroot()
# JUnit format uses <testsuites> as root with aggregated counts
# gtest XML uses <testsuites> or <testsuite> as root
t = int(root.get("tests", 0))
f_count = int(root.get("failures", 0))
err_count = int(root.get("errors", 0))
total += t
failed += f_count + err_count
passed += t - f_count - err_count
except Exception as e:
print(f"Warning: failed to parse {xml_file}: {e}", file=sys.stderr)
return total, passed, failed


def parse_lcov_summary(coverage_info):
"""Parse lcov --summary output and return coverage dict."""
result = {}
if not os.path.exists(coverage_info):
print(f"Warning: coverage info file not found: {coverage_info}", file=sys.stderr)
return result

lcov_out = subprocess.run(
["lcov", "--summary", coverage_info, "--rc", "lcov_branch_coverage=1"],
capture_output=True, text=True
)
summary_text = lcov_out.stdout + lcov_out.stderr

# Parse lines: "lines......: XX.X% (NNN of MMMM lines)"
m_lines = re.search(r'lines.*?:\s*([\d.]+)%\s*\((\d+)\s+of\s+(\d+)\s+\w+\)', summary_text)
if m_lines:
pct, hit, total = m_lines.groups()
result["line_coverage"] = {
"total": int(total),
"passed": int(hit),
"failed": int(total) - int(hit),
"coverage": f"{float(pct):.2f}%"
}

# Parse functions: "functions..: XX.X% (NNN of MMMM functions)"
m_func = re.search(r'functions.*?:\s*([\d.]+)%\s*\((\d+)\s+of\s+(\d+)\s+\w+\)', summary_text)
if m_func:
pct, hit, total = m_func.groups()
result["function_coverage"] = {
"total": int(total),
"passed": int(hit),
"failed": int(total) - int(hit),
"coverage": f"{float(pct):.2f}%"
}

return result


def main():
projectdir = os.environ.get("projectdir")
builddir = os.environ.get("builddir")
reportdir = os.environ.get("reportdir")

if not all([projectdir, builddir, reportdir]):
print("Error: environment variables projectdir, builddir, reportdir are required", file=sys.stderr)
sys.exit(1)

# Resolve paths (env overrides take precedence)
gtest_dir = os.environ.get("GTEST_XML_DIR",
os.path.join(projectdir, builddir, "report"))
coverage_info = os.environ.get("COVERAGE_INFO",
os.path.join(projectdir, builddir, "coverage.info"))

# --- Test case counts from gtest XML ---
total, passed, failed = parse_gtest_xml(gtest_dir)

result = {
"test_cases": {
"total": total,
"passed": passed,
"failed": failed
}
}

# --- Coverage from lcov --summary ---
coverage_data = parse_lcov_summary(coverage_info)
result.update(coverage_data)

# --- Write output ---
output_path = os.path.join(projectdir, reportdir, "ut-summary.json")
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, "w") as f:
json.dump(result, f, indent=2)

print(json.dumps(result, indent=2))


if __name__ == "__main__":
main()
30 changes: 19 additions & 11 deletions tests/test-prj-running.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@

set -u

builddir=build
reportdir=build-ut
export builddir=build
export reportdir=build-ut
export scriptdir="$(cd "$(dirname "$0")" && pwd)"
export projectdir="$(cd "${scriptdir}/.." && pwd)"

# Resolve project root (parent of the tests/ directory that holds this script)
script_dir="$(cd "$(dirname "$0")" && pwd)"
project_root="$(cd "${script_dir}/.." && pwd)"

build_path="${project_root}/${builddir}"
report_path="${project_root}/${reportdir}"
build_path="${projectdir}/${builddir}"
report_path="${projectdir}/${reportdir}"

# Fresh build directory to ensure a clean coverage run
rm -rf "${build_path}"
Expand All @@ -32,7 +30,7 @@ cmake -DCMAKE_SAFETYTEST_ARG="CMAKE_SAFETYTEST_ARG_ON" \
-DBUILD_TESTS=ON \
-DUSE_PDFIUM_BUNDLE=ON \
-DCMAKE_BUILD_TYPE=Debug \
"${project_root}"
"${projectdir}"

# Compile tests target
make -j"$(nproc)" test-deepin-reader
Expand All @@ -41,7 +39,10 @@ make -j"$(nproc)" test-deepin-reader
mkdir -p "${build_path}/report"

# Run tests and produce XML report
set +e
./tests/test-deepin-reader --gtest_output=xml:"${build_path}/report/report_deepin-reader.xml"
test_exit_code=$?
set -e

# Directory that holds coverage artifacts (build tree of the project)
workdir="${build_path}"
Expand All @@ -50,7 +51,9 @@ workdir="${build_path}"
lcov --directory "${workdir}" --zerocounters || true

# Re-run tests so .gcda files reflect a clean run
set +e
./tests/test-deepin-reader --gtest_output=xml:"${build_path}/report/report_deepin-reader.xml"
set -e

# Collect coverage data
lcov -d "${workdir}" -c -o ./coverage.info
Expand All @@ -60,7 +63,7 @@ lcov --extract ./coverage.info '*/reader/*' -o ./coverage.info
lcov --remove ./coverage.info '*/tests/*' -o ./coverage.info

# Exclude compiler-generated and unreachable functions (D0Ev, Q_OBJECT tr, env-dependent lambdas)
python3 "${script_dir}/exclude_unreachable.py" ./coverage.info ./coverage.info
python3 "${scriptdir}/exclude_unreachable.py" ./coverage.info ./coverage.info

# Generate HTML report
genhtml -o ./html ./coverage.info
Expand All @@ -73,4 +76,9 @@ cp -r html "${report_path}/"
cp -r "${build_path}/report" "${report_path}/"
cp -r asan*.log* "${report_path}/asan_deepin-reader.log" 2>/dev/null || true

exit 0
# 生成摘要 JSON
echo "==> Generating summary JSON: ${report_path}/ut-summary.json"

python3 "${scriptdir}/gen-ut-summary.py"

exit $test_exit_code
Loading