2
0

extraction.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. """
  2. Coverage extraction module.
  3. This module handles extracting coverage percentages from coverage report files.
  4. """
  5. import os
  6. import re
  7. import sys
  8. import shlex
  9. import subprocess
  10. import traceback
  11. from .util import log, file_exists, get_file_size, list_directory, is_safe_command, run_command
  12. # Global verbose flag
  13. verbose = False
  14. def set_verbose(value):
  15. """Set the global verbose flag."""
  16. global verbose
  17. verbose = value
  18. def print_debug_output(content, coverage_type):
  19. """
  20. Print debug information about the coverage output.
  21. Args:
  22. content: The content of the coverage file
  23. coverage_type: Type of coverage report (extension or webview)
  24. """
  25. if not verbose:
  26. return
  27. # Extract and print only the coverage summary section
  28. if coverage_type == "extension":
  29. # Look for the coverage summary section
  30. summary_match = re.search(r'=============================== Coverage summary ===============================\n(.*?)\n=+', content, re.DOTALL)
  31. if summary_match:
  32. sys.stdout.write("\n##[group]EXTENSION COVERAGE SUMMARY\n")
  33. sys.stdout.write("=============================== Coverage summary ===============================\n")
  34. sys.stdout.write(summary_match.group(1) + "\n")
  35. sys.stdout.write("================================================================================\n")
  36. sys.stdout.write("##[endgroup]\n")
  37. sys.stdout.flush()
  38. else:
  39. sys.stdout.write("\n##[warning]No coverage summary found in extension coverage file\n")
  40. sys.stdout.flush()
  41. else: # webview
  42. # Look for the coverage table - specifically the "All files" row
  43. table_match = re.search(r'% Coverage report from v8.*?-+\|.*?\n.*?\n(All files.*?)(?:\n[^\n]*\|)', content, re.DOTALL)
  44. if table_match:
  45. sys.stdout.write("\n##[group]WEBVIEW COVERAGE SUMMARY\n")
  46. sys.stdout.write("% Coverage report from v8\n")
  47. sys.stdout.write("-------------------|---------|----------|---------|---------|-------------------\n")
  48. sys.stdout.write("File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s \n")
  49. sys.stdout.write("-------------------|---------|----------|---------|---------|-------------------\n")
  50. sys.stdout.write(table_match.group(1) + "\n")
  51. sys.stdout.write("-------------------|---------|----------|---------|---------|-------------------\n")
  52. sys.stdout.write("##[endgroup]\n")
  53. sys.stdout.flush()
  54. else:
  55. sys.stdout.write("\n##[warning]No coverage table found in webview coverage file\n")
  56. sys.stdout.flush()
  57. def extract_coverage(file_path, coverage_type="extension"):
  58. """
  59. Extract coverage percentage from a coverage report file.
  60. Args:
  61. file_path: Path to the coverage report file
  62. coverage_type: Type of coverage report (extension or webview)
  63. Returns:
  64. Coverage percentage as a float
  65. """
  66. # Always print file path for debugging
  67. log(f"Checking coverage file: {file_path}")
  68. # Check if file exists and get its size
  69. if not file_exists(file_path):
  70. sys.stdout.write(f"\n##[error]File {file_path} does not exist\n")
  71. sys.stdout.flush()
  72. log(f"Error: File {file_path} does not exist")
  73. # Check if the directory exists
  74. dir_path = os.path.dirname(file_path)
  75. if not os.path.exists(dir_path):
  76. sys.stdout.write(f"\n##[error]Directory {dir_path} does not exist\n")
  77. sys.stdout.flush()
  78. log(f"Error: Directory {dir_path} does not exist")
  79. else:
  80. # List directory contents for debugging
  81. log(f"Directory {dir_path} exists, listing contents:")
  82. try:
  83. dir_contents = list_directory(dir_path)
  84. for name, size in dir_contents:
  85. log(f" {name} - {size}")
  86. sys.stdout.write(f" {name} - {size}\n")
  87. sys.stdout.flush()
  88. except Exception as e:
  89. log(f"Error listing directory: {e}")
  90. return 0.0
  91. file_size = get_file_size(file_path)
  92. log(f"File size: {file_size} bytes")
  93. sys.stdout.write(f"\n##[info]Coverage file {file_path} exists, size: {file_size} bytes\n")
  94. sys.stdout.flush()
  95. if file_size == 0:
  96. sys.stdout.write(f"\n##[warning]File {file_path} is empty\n")
  97. sys.stdout.flush()
  98. log(f"Warning: File {file_path} is empty")
  99. return 0.0
  100. # List directory contents for debugging
  101. dir_path = os.path.dirname(file_path)
  102. log(f"Directory contents of {dir_path}:")
  103. try:
  104. dir_contents = list_directory(dir_path)
  105. for name, size in dir_contents:
  106. log(f" {name} - {size}")
  107. except Exception as e:
  108. log(f"Error listing directory: {e}")
  109. with open(file_path, 'r') as f:
  110. content = f.read()
  111. # Print debug information if verbose
  112. print_debug_output(content, coverage_type)
  113. # Extract coverage percentage based on coverage type
  114. if coverage_type == "extension":
  115. # Extract the percentage from the "Lines" row in the coverage summary
  116. # Pattern: Lines : xx.xx% ( xxxxxxx/xxxxxxx )
  117. lines_match = re.search(r'Lines\s*:\s*(\d+\.\d+)%', content)
  118. if lines_match:
  119. coverage_pct = float(lines_match.group(1))
  120. if verbose:
  121. sys.stdout.write(f"Pattern matched (Lines percentage): {coverage_pct}\n")
  122. sys.stdout.flush()
  123. return coverage_pct
  124. else:
  125. # No coverage data found, log full content for debugging
  126. log("No coverage data found. Full file content:")
  127. log("=== Full file content ===")
  128. log(content)
  129. log("=== End file content ===")
  130. else: # webview
  131. # Extract the percentage from the "% Lines" column in the "All files" row
  132. # Pattern: All files | xx.xx | xx.xx | xx.xx | xx.xx |
  133. 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)
  134. if all_files_match:
  135. coverage_pct = float(all_files_match.group(1))
  136. if verbose:
  137. sys.stdout.write(f"Pattern matched (All files % Lines): {coverage_pct}\n")
  138. sys.stdout.flush()
  139. return coverage_pct
  140. else:
  141. # No coverage data found, log full content for debugging
  142. log("No coverage data found. Full file content:")
  143. log("=== Full file content ===")
  144. log(content)
  145. log("=== End file content ===")
  146. # If no match found, return 0.0
  147. return 0.0
  148. def compare_coverage(base_cov, pr_cov):
  149. """
  150. Compare coverage percentages between base and PR branches.
  151. Args:
  152. base_cov: Base branch coverage percentage
  153. pr_cov: PR branch coverage percentage
  154. Returns:
  155. Tuple of (decreased, diff)
  156. """
  157. try:
  158. base_cov = float(base_cov)
  159. pr_cov = float(pr_cov)
  160. except ValueError:
  161. sys.stdout.write(f"Error: Invalid coverage values - base: {base_cov}, PR: {pr_cov}\n")
  162. sys.stdout.flush()
  163. return False, 0
  164. diff = pr_cov - base_cov
  165. decreased = diff < 0
  166. return decreased, abs(diff)
  167. def run_coverage(command, output_file, coverage_type="extension"):
  168. """
  169. Run a coverage command and extract the coverage percentage.
  170. Args:
  171. command: Command to run
  172. output_file: File to save the output to
  173. coverage_type: Type of coverage report (extension or webview)
  174. Returns:
  175. Coverage percentage as a float
  176. Raises:
  177. SystemExit: If the output file is not created or is empty
  178. """
  179. try:
  180. # Run the command and capture output
  181. if not is_safe_command(command):
  182. error_msg = f"ERROR: Unsafe command detected: {command}"
  183. log(error_msg)
  184. sys.stdout.write(f"\n##[error]{error_msg}\n")
  185. sys.stdout.flush()
  186. sys.exit(1)
  187. # Run command using safe execution from util
  188. returncode, stdout, stderr = run_command(command)
  189. # Log command result
  190. log(f"Command exit code: {returncode}")
  191. log(f"Command stdout length: {len(stdout)} bytes")
  192. log(f"Command stderr length: {len(stderr)} bytes")
  193. # Save output to file
  194. log(f"Saving command output to {output_file}")
  195. with open(output_file, 'w') as f:
  196. f.write(stdout)
  197. if stderr:
  198. f.write("\n\n=== STDERR ===\n")
  199. f.write(stderr)
  200. # Verify file was created and has content
  201. if not file_exists(output_file):
  202. error_msg = f"ERROR: Output file {output_file} was not created"
  203. log(error_msg)
  204. sys.stdout.write(f"\n##[error]{error_msg}\n")
  205. sys.stdout.flush()
  206. sys.exit(1) # Exit with error code to fail the workflow
  207. file_size = get_file_size(output_file)
  208. if file_size == 0:
  209. error_msg = f"ERROR: Output file {output_file} is empty"
  210. log(error_msg)
  211. sys.stdout.write(f"\n##[error]{error_msg}\n")
  212. sys.stdout.flush()
  213. sys.exit(1) # Exit with error code to fail the workflow
  214. log(f"Output file size: {file_size} bytes")
  215. # Extract coverage percentage
  216. coverage_pct = extract_coverage(output_file, coverage_type)
  217. log(f"{coverage_type.capitalize()} coverage: {coverage_pct}%")
  218. return coverage_pct
  219. except Exception as e:
  220. error_msg = f"Error running coverage command: {e}"
  221. log(error_msg)
  222. sys.stdout.write(f"\n##[error]{error_msg}\n")
  223. sys.stdout.flush()
  224. # Print stack trace for debugging
  225. log(traceback.format_exc())
  226. sys.exit(1) # Exit with error code to fail the workflow