3
3
import json
4
4
import logging
5
5
import os
6
+ import re
6
7
7
8
from diff_cover .diff_cover_tool import main as diff_cover_main
8
9
9
10
from cover_agent .AICaller import AICaller
11
+ from cover_agent .CoverageProcessor import CoverageProcessor
10
12
from cover_agent .CustomLogger import CustomLogger
11
13
from cover_agent .FilePreprocessor import FilePreprocessor
12
14
from cover_agent .PromptBuilder import PromptBuilder
13
15
from cover_agent .Runner import Runner
14
16
from cover_agent .settings .config_loader import get_settings
15
17
from cover_agent .utils import load_yaml
16
- from cover_agent . coverage . processor import process_coverage , CoverageReport , CoverageData
18
+
17
19
18
20
class UnitTestValidator :
19
21
def __init__ (
@@ -106,6 +108,15 @@ def __init__(
106
108
with open (self .source_file_path , "r" ) as f :
107
109
self .source_code = f .read ()
108
110
111
+ # initialize the coverage processor
112
+ self .coverage_processor = CoverageProcessor (
113
+ file_path = self .code_coverage_report_path ,
114
+ src_file_path = self .source_file_path ,
115
+ coverage_type = self .coverage_type ,
116
+ use_report_coverage_feature_flag = self .use_report_coverage_feature_flag ,
117
+ diff_coverage_report_path = self .diff_cover_report_path ,
118
+ )
119
+
109
120
def get_coverage (self ):
110
121
"""
111
122
Run code coverage and build the prompt to be used for generating tests.
@@ -115,9 +126,6 @@ def get_coverage(self):
115
126
"""
116
127
# Run coverage and build the prompt
117
128
self .run_coverage ()
118
- # Run diff coverage if enabled
119
- if self .diff_coverage :
120
- self .generate_diff_coverage_report ()
121
129
return self .failed_test_runs , self .language , self .testing_framework , self .code_coverage_report
122
130
123
131
def get_code_language (self , source_file_path : str ) -> str :
@@ -288,9 +296,14 @@ def run_coverage(self):
288
296
), f'Fatal: Error running test command. Are you sure the command is correct? "{ self .test_command } "\n Exit code { exit_code } . \n Stdout: \n { stdout } \n Stderr: \n { stderr } '
289
297
290
298
try :
291
- self .current_coverage_report = self .post_process_coverage_report (time_of_test_command )
299
+ # Process the extracted coverage metrics
300
+ coverage , coverage_percentages = self .post_process_coverage_report (
301
+ time_of_test_command
302
+ )
303
+ self .current_coverage = coverage
304
+ self .last_coverage_percentages = coverage_percentages .copy ()
292
305
self .logger .info (
293
- f"Initial coverage: { round (self .current_coverage_report . total_coverage * 100 , 2 )} %"
306
+ f"Initial coverage: { round (self .current_coverage * 100 , 2 )} %"
294
307
)
295
308
296
309
except AssertionError as error :
@@ -449,6 +462,7 @@ def validate_test(self, generated_test: dict):
449
462
if exit_code != 0 :
450
463
break
451
464
465
+
452
466
# Step 3: Check for pass/fail from the Runner object
453
467
if exit_code != 0 :
454
468
# Test failed, roll back the test file to its original content
@@ -491,9 +505,11 @@ def validate_test(self, generated_test: dict):
491
505
492
506
# If test passed, check for coverage increase
493
507
try :
494
- new_coverage_report = self .post_process_coverage_report (time_of_test_command )
508
+ new_percentage_covered , new_coverage_percentages = self .post_process_coverage_report (
509
+ time_of_test_command
510
+ )
495
511
496
- if self . current_coverage_report is not None and new_coverage_report . total_coverage <= self .current_coverage_report . total_coverage :
512
+ if new_percentage_covered <= self .current_coverage :
497
513
# Coverage has not increased, rollback the test by removing it from the test file
498
514
with open (self .test_file_path , "w" ) as test_file :
499
515
test_file .write (original_content )
@@ -565,20 +581,20 @@ def validate_test(self, generated_test: dict):
565
581
additional_imports_lines
566
582
) # this is important, otherwise the next test will be inserted at the wrong line
567
583
568
- for key in new_coverage_report .file_coverage :
569
- new_v : CoverageData = new_coverage_report .file_coverage [key ]
570
- old_v : CoverageData = self .current_coverage_report .file_coverage [key ]
571
- if new_v .coverage > old_v .coverage and key == self .source_file_path .split ("/" )[- 1 ]:
584
+ for key in new_coverage_percentages :
585
+ if new_coverage_percentages [key ] > self .last_coverage_percentages [key ] and key == self .source_file_path .split ("/" )[- 1 ]:
572
586
self .logger .info (
573
- f"Coverage for provided source file: { key } increased to { round (new_v . coverage * 100 , 2 )} "
587
+ f"Coverage for provided source file: { key } increased from { round (self . last_coverage_percentages [ key ] * 100 , 2 ) } to { round ( new_coverage_percentages [ key ] * 100 , 2 )} "
574
588
)
575
- elif new_v . coverage > old_v . coverage :
589
+ elif new_coverage_percentages [ key ] > self . last_coverage_percentages [ key ] :
576
590
self .logger .info (
577
- f"Coverage for non-source file: { key } increased to { round (new_v . coverage * 100 , 2 )} "
591
+ f"Coverage for non-source file: { key } increased from { round (self . last_coverage_percentages [ key ] * 100 , 2 ) } to { round ( new_coverage_percentages [ key ] * 100 , 2 )} "
578
592
)
579
-
593
+ self .current_coverage = new_percentage_covered
594
+ self .last_coverage_percentages = new_coverage_percentages .copy ()
595
+
580
596
self .logger .info (
581
- f"Test passed and coverage increased. Current coverage: { round (new_coverage_report . total_coverage * 100 , 2 )} %"
597
+ f"Test passed and coverage increased. Current coverage: { round (new_percentage_covered * 100 , 2 )} %"
582
598
)
583
599
return {
584
600
"status" : "PASS" ,
@@ -675,20 +691,59 @@ def extract_error_message(self, fail_details):
675
691
logging .error (f"Error extracting error message: { e } " )
676
692
return ""
677
693
678
- def post_process_coverage_report (self , time_of_test_command : int ):
679
- report : CoverageReport = process_coverage (
680
- tool_type = self .coverage_type ,
681
- time_of_test_command = time_of_test_command ,
682
- report_path = self .code_coverage_report_path ,
683
- src_file_path = self .source_file_path ,
684
- is_global_coverage_enabled = self .use_report_coverage_feature_flag ,
685
- file_pattern = None ,
686
- diff_coverage_report_path = self .diff_cover_report_path ,
687
- )
688
- self .logger .info (
689
- f"coverage: Percentage { round (report .total_coverage * 100 , 2 )} %"
690
- )
691
- return report
694
+ def post_process_coverage_report (self , time_of_test_command ):
695
+ coverage_percentages = {}
696
+ if self .use_report_coverage_feature_flag :
697
+ self .logger .info (
698
+ "Using the report coverage feature flag to process the coverage report"
699
+ )
700
+ file_coverage_dict = self .coverage_processor .process_coverage_report (
701
+ time_of_test_command = time_of_test_command
702
+ )
703
+ total_lines_covered = 0
704
+ total_lines_missed = 0
705
+ total_lines = 0
706
+ for key in file_coverage_dict :
707
+ lines_covered , lines_missed , percentage_covered = (
708
+ file_coverage_dict [key ]
709
+ )
710
+ total_lines_covered += len (lines_covered )
711
+ total_lines_missed += len (lines_missed )
712
+ total_lines += len (lines_covered ) + len (lines_missed )
713
+ if key == self .source_file_path :
714
+ self .last_source_file_coverage = percentage_covered
715
+ if key not in coverage_percentages :
716
+ coverage_percentages [key ] = 0
717
+ coverage_percentages [key ] = percentage_covered
718
+ try :
719
+ percentage_covered = total_lines_covered / total_lines
720
+ except ZeroDivisionError :
721
+ self .logger .error (f"ZeroDivisionError: Attempting to perform total_lines_covered / total_lines: { total_lines_covered } / { total_lines } ." )
722
+ percentage_covered = 0
723
+
724
+ self .logger .info (
725
+ f"Total lines covered: { total_lines_covered } , Total lines missed: { total_lines_missed } , Total lines: { total_lines } "
726
+ )
727
+ self .logger .info (
728
+ f"coverage: Percentage { round (percentage_covered * 100 , 2 )} %"
729
+ )
730
+ elif self .diff_coverage :
731
+ self .generate_diff_coverage_report ()
732
+ lines_covered , lines_missed , percentage_covered = (
733
+ self .coverage_processor .process_coverage_report (
734
+ time_of_test_command = time_of_test_command
735
+ )
736
+ )
737
+ self .code_coverage_report = f"Lines covered: { lines_covered } \n Lines missed: { lines_missed } \n Percentage covered: { round (percentage_covered * 100 , 2 )} %"
738
+ else :
739
+ lines_covered , lines_missed , percentage_covered = (
740
+ self .coverage_processor .process_coverage_report (
741
+ time_of_test_command = time_of_test_command
742
+ )
743
+ )
744
+ self .code_coverage_report = f"Lines covered: { lines_covered } \n Lines missed: { lines_missed } \n Percentage covered: { round (percentage_covered * 100 , 2 )} %"
745
+ return percentage_covered , coverage_percentages
746
+
692
747
693
748
def generate_diff_coverage_report (self ):
694
749
"""
@@ -719,4 +774,4 @@ def generate_diff_coverage_report(self):
719
774
self .logger .error (f"Error running diff-cover: { e } " )
720
775
721
776
def get_current_coverage (self ):
722
- return self .current_coverage_report .total_coverage
777
+ return self .current_coverage_report .total_coverage
0 commit comments