| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- """
- 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
|