266 lines
9.9 KiB
Python
266 lines
9.9 KiB
Python
"""
|
|
Coverage extraction module.
|
|
This module handles extracting coverage percentages from coverage report files.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
import shlex
|
|
import subprocess
|
|
import traceback
|
|
from .util import log, file_exists, get_file_size, list_directory, is_safe_command, run_command
|
|
|
|
# Global verbose flag
|
|
verbose = False
|
|
|
|
def set_verbose(value):
|
|
"""Set the global verbose flag."""
|
|
global verbose
|
|
verbose = value
|
|
|
|
def print_debug_output(content, coverage_type):
|
|
"""
|
|
Print debug information about the coverage output.
|
|
|
|
Args:
|
|
content: The content of the coverage file
|
|
coverage_type: Type of coverage report (extension or webview)
|
|
"""
|
|
if not verbose:
|
|
return
|
|
|
|
# Extract and print only the coverage summary section
|
|
if coverage_type == "extension":
|
|
# Look for the coverage summary section
|
|
summary_match = re.search(r'=============================== Coverage summary ===============================\n(.*?)\n=+', content, re.DOTALL)
|
|
if summary_match:
|
|
sys.stdout.write("\n##[group]EXTENSION COVERAGE SUMMARY\n")
|
|
sys.stdout.write("=============================== Coverage summary ===============================\n")
|
|
sys.stdout.write(summary_match.group(1) + "\n")
|
|
sys.stdout.write("================================================================================\n")
|
|
sys.stdout.write("##[endgroup]\n")
|
|
sys.stdout.flush()
|
|
else:
|
|
sys.stdout.write("\n##[warning]No coverage summary found in extension coverage file\n")
|
|
sys.stdout.flush()
|
|
else: # webview
|
|
# Look for the coverage table - specifically the "All files" row
|
|
table_match = re.search(r'% Coverage report from v8.*?-+\|.*?\n.*?\n(All files.*?)(?:\n[^\n]*\|)', content, re.DOTALL)
|
|
if table_match:
|
|
sys.stdout.write("\n##[group]WEBVIEW COVERAGE SUMMARY\n")
|
|
sys.stdout.write("% Coverage report from v8\n")
|
|
sys.stdout.write("-------------------|---------|----------|---------|---------|-------------------\n")
|
|
sys.stdout.write("File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s \n")
|
|
sys.stdout.write("-------------------|---------|----------|---------|---------|-------------------\n")
|
|
sys.stdout.write(table_match.group(1) + "\n")
|
|
sys.stdout.write("-------------------|---------|----------|---------|---------|-------------------\n")
|
|
sys.stdout.write("##[endgroup]\n")
|
|
sys.stdout.flush()
|
|
else:
|
|
sys.stdout.write("\n##[warning]No coverage table found in webview coverage file\n")
|
|
sys.stdout.flush()
|
|
|
|
def extract_coverage(file_path, coverage_type="extension"):
|
|
"""
|
|
Extract coverage percentage from a coverage report file.
|
|
|
|
Args:
|
|
file_path: Path to the coverage report file
|
|
coverage_type: Type of coverage report (extension or webview)
|
|
|
|
Returns:
|
|
Coverage percentage as a float
|
|
"""
|
|
|
|
# Always print file path for debugging
|
|
log(f"Checking coverage file: {file_path}")
|
|
|
|
# Check if file exists and get its size
|
|
if not file_exists(file_path):
|
|
sys.stdout.write(f"\n##[error]File {file_path} does not exist\n")
|
|
sys.stdout.flush()
|
|
log(f"Error: File {file_path} does not exist")
|
|
|
|
# Check if the directory exists
|
|
dir_path = os.path.dirname(file_path)
|
|
if not os.path.exists(dir_path):
|
|
sys.stdout.write(f"\n##[error]Directory {dir_path} does not exist\n")
|
|
sys.stdout.flush()
|
|
log(f"Error: Directory {dir_path} does not exist")
|
|
else:
|
|
# List directory contents for debugging
|
|
log(f"Directory {dir_path} exists, listing contents:")
|
|
try:
|
|
dir_contents = list_directory(dir_path)
|
|
for name, size in dir_contents:
|
|
log(f" {name} - {size}")
|
|
sys.stdout.write(f" {name} - {size}\n")
|
|
sys.stdout.flush()
|
|
except Exception as e:
|
|
log(f"Error listing directory: {e}")
|
|
|
|
return 0.0
|
|
|
|
file_size = get_file_size(file_path)
|
|
log(f"File size: {file_size} bytes")
|
|
sys.stdout.write(f"\n##[info]Coverage file {file_path} exists, size: {file_size} bytes\n")
|
|
sys.stdout.flush()
|
|
|
|
if file_size == 0:
|
|
sys.stdout.write(f"\n##[warning]File {file_path} is empty\n")
|
|
sys.stdout.flush()
|
|
log(f"Warning: File {file_path} is empty")
|
|
return 0.0
|
|
|
|
# List directory contents for debugging
|
|
dir_path = os.path.dirname(file_path)
|
|
log(f"Directory contents of {dir_path}:")
|
|
try:
|
|
dir_contents = list_directory(dir_path)
|
|
for name, size in dir_contents:
|
|
log(f" {name} - {size}")
|
|
except Exception as e:
|
|
log(f"Error listing directory: {e}")
|
|
|
|
with open(file_path, 'r') as f:
|
|
content = f.read()
|
|
|
|
# Print debug information if verbose
|
|
print_debug_output(content, coverage_type)
|
|
|
|
# Extract coverage percentage based on coverage type
|
|
if coverage_type == "extension":
|
|
# Extract the percentage from the "Lines" row in the coverage summary
|
|
# Pattern: Lines : xx.xx% ( xxxxxxx/xxxxxxx )
|
|
lines_match = re.search(r'Lines\s*:\s*(\d+\.\d+)%', content)
|
|
if lines_match:
|
|
coverage_pct = float(lines_match.group(1))
|
|
if verbose:
|
|
sys.stdout.write(f"Pattern matched (Lines percentage): {coverage_pct}\n")
|
|
sys.stdout.flush()
|
|
return coverage_pct
|
|
else:
|
|
# No coverage data found, log full content for debugging
|
|
log("No coverage data found. Full file content:")
|
|
log("=== Full file content ===")
|
|
log(content)
|
|
log("=== End file content ===")
|
|
else: # webview
|
|
# Extract the percentage from the "% Lines" column in the "All files" row
|
|
# Pattern: All files | xx.xx | xx.xx | xx.xx | xx.xx |
|
|
all_files_match = re.search(r'All files\s+\|\s+\d+\.\d+\s+\|\s+\d+\.\d+\s+\|\s+\d+\.\d+\s+\|\s+(\d+\.\d+)', content)
|
|
if all_files_match:
|
|
coverage_pct = float(all_files_match.group(1))
|
|
if verbose:
|
|
sys.stdout.write(f"Pattern matched (All files % Lines): {coverage_pct}\n")
|
|
sys.stdout.flush()
|
|
return coverage_pct
|
|
else:
|
|
# No coverage data found, log full content for debugging
|
|
log("No coverage data found. Full file content:")
|
|
log("=== Full file content ===")
|
|
log(content)
|
|
log("=== End file content ===")
|
|
|
|
# If no match found, return 0.0
|
|
return 0.0
|
|
|
|
def compare_coverage(base_cov, pr_cov):
|
|
"""
|
|
Compare coverage percentages between base and PR branches.
|
|
|
|
Args:
|
|
base_cov: Base branch coverage percentage
|
|
pr_cov: PR branch coverage percentage
|
|
|
|
Returns:
|
|
Tuple of (decreased, diff)
|
|
"""
|
|
try:
|
|
base_cov = float(base_cov)
|
|
pr_cov = float(pr_cov)
|
|
except ValueError:
|
|
sys.stdout.write(f"Error: Invalid coverage values - base: {base_cov}, PR: {pr_cov}\n")
|
|
sys.stdout.flush()
|
|
return False, 0
|
|
|
|
diff = pr_cov - base_cov
|
|
decreased = diff < 0
|
|
|
|
return decreased, abs(diff)
|
|
|
|
def run_coverage(command, output_file, coverage_type="extension"):
|
|
"""
|
|
Run a coverage command and extract the coverage percentage.
|
|
|
|
Args:
|
|
command: Command to run
|
|
output_file: File to save the output to
|
|
coverage_type: Type of coverage report (extension or webview)
|
|
|
|
Returns:
|
|
Coverage percentage as a float
|
|
|
|
Raises:
|
|
SystemExit: If the output file is not created or is empty
|
|
"""
|
|
|
|
try:
|
|
# Run the command and capture output
|
|
if not is_safe_command(command):
|
|
error_msg = f"ERROR: Unsafe command detected: {command}"
|
|
log(error_msg)
|
|
sys.stdout.write(f"\n##[error]{error_msg}\n")
|
|
sys.stdout.flush()
|
|
sys.exit(1)
|
|
|
|
# Run command using safe execution from util
|
|
returncode, stdout, stderr = run_command(command)
|
|
|
|
# Log command result
|
|
log(f"Command exit code: {returncode}")
|
|
log(f"Command stdout length: {len(stdout)} bytes")
|
|
log(f"Command stderr length: {len(stderr)} bytes")
|
|
|
|
# Save output to file
|
|
log(f"Saving command output to {output_file}")
|
|
with open(output_file, 'w') as f:
|
|
f.write(stdout)
|
|
if stderr:
|
|
f.write("\n\n=== STDERR ===\n")
|
|
f.write(stderr)
|
|
|
|
# Verify file was created and has content
|
|
if not file_exists(output_file):
|
|
error_msg = f"ERROR: Output file {output_file} was not created"
|
|
log(error_msg)
|
|
sys.stdout.write(f"\n##[error]{error_msg}\n")
|
|
sys.stdout.flush()
|
|
sys.exit(1) # Exit with error code to fail the workflow
|
|
|
|
file_size = get_file_size(output_file)
|
|
if file_size == 0:
|
|
error_msg = f"ERROR: Output file {output_file} is empty"
|
|
log(error_msg)
|
|
sys.stdout.write(f"\n##[error]{error_msg}\n")
|
|
sys.stdout.flush()
|
|
sys.exit(1) # Exit with error code to fail the workflow
|
|
|
|
log(f"Output file size: {file_size} bytes")
|
|
|
|
# Extract coverage percentage
|
|
coverage_pct = extract_coverage(output_file, coverage_type)
|
|
|
|
log(f"{coverage_type.capitalize()} coverage: {coverage_pct}%")
|
|
return coverage_pct
|
|
|
|
except Exception as e:
|
|
error_msg = f"Error running coverage command: {e}"
|
|
log(error_msg)
|
|
sys.stdout.write(f"\n##[error]{error_msg}\n")
|
|
sys.stdout.flush()
|
|
# Print stack trace for debugging
|
|
log(traceback.format_exc())
|
|
sys.exit(1) # Exit with error code to fail the workflow
|