13
13
from typing import TYPE_CHECKING , Any , Dict , Generator , Literal , TypedDict
14
14
15
15
import pytest
16
+ from packaging .version import Version
16
17
17
18
if TYPE_CHECKING :
18
19
from pluggy import Result
@@ -61,6 +62,7 @@ def __init__(self, message):
61
62
collected_tests_so_far = []
62
63
TEST_RUN_PIPE = os .getenv ("TEST_RUN_PIPE" )
63
64
SYMLINK_PATH = None
65
+ INCLUDE_BRANCHES = False
64
66
65
67
66
68
def pytest_load_initial_conftests (early_config , parser , args ): # noqa: ARG001
@@ -70,6 +72,9 @@ def pytest_load_initial_conftests(early_config, parser, args): # noqa: ARG001
70
72
raise VSCodePytestError (
71
73
"\n \n ERROR: pytest-cov is not installed, please install this before running pytest with coverage as pytest-cov is required. \n "
72
74
)
75
+ if "--cov-branch" in args :
76
+ global INCLUDE_BRANCHES
77
+ INCLUDE_BRANCHES = True
73
78
74
79
global TEST_RUN_PIPE
75
80
TEST_RUN_PIPE = os .getenv ("TEST_RUN_PIPE" )
@@ -363,6 +368,8 @@ def check_skipped_condition(item):
363
368
class FileCoverageInfo (TypedDict ):
364
369
lines_covered : list [int ]
365
370
lines_missed : list [int ]
371
+ executed_branches : int
372
+ total_branches : int
366
373
367
374
368
375
def pytest_sessionfinish (session , exitstatus ):
@@ -436,6 +443,15 @@ def pytest_sessionfinish(session, exitstatus):
436
443
# load the report and build the json result to return
437
444
import coverage
438
445
446
+ coverage_version = Version (coverage .__version__ )
447
+ global INCLUDE_BRANCHES
448
+ # only include branches if coverage version is 7.7.0 or greater (as this was when the api saves)
449
+ if coverage_version < Version ("7.7.0" ) and INCLUDE_BRANCHES :
450
+ print (
451
+ "Plugin warning[vscode-pytest]: Branch coverage not supported in this coverage versions < 7.7.0. Please upgrade coverage package if you would like to see branch coverage."
452
+ )
453
+ INCLUDE_BRANCHES = False
454
+
439
455
try :
440
456
from coverage .exceptions import NoSource
441
457
except ImportError :
@@ -448,9 +464,8 @@ def pytest_sessionfinish(session, exitstatus):
448
464
file_coverage_map : dict [str , FileCoverageInfo ] = {}
449
465
450
466
# remove files omitted per coverage report config if any
451
- omit_files = cov .config .report_omit
452
- if omit_files :
453
- print ("Plugin info[vscode-pytest]: Omit files/rules: " , omit_files )
467
+ omit_files : list [str ] | None = cov .config .report_omit
468
+ if omit_files is not None :
454
469
for pattern in omit_files :
455
470
for file in list (file_set ):
456
471
if pathlib .Path (file ).match (pattern ):
@@ -459,6 +474,18 @@ def pytest_sessionfinish(session, exitstatus):
459
474
for file in file_set :
460
475
try :
461
476
analysis = cov .analysis2 (file )
477
+ taken_file_branches = 0
478
+ total_file_branches = - 1
479
+
480
+ if INCLUDE_BRANCHES :
481
+ branch_stats : dict [int , tuple [int , int ]] = cov .branch_stats (file )
482
+ total_file_branches = sum (
483
+ [total_exits for total_exits , _ in branch_stats .values ()]
484
+ )
485
+ taken_file_branches = sum (
486
+ [taken_exits for _ , taken_exits in branch_stats .values ()]
487
+ )
488
+
462
489
except NoSource :
463
490
# as per issue 24308 this best way to handle this edge case
464
491
continue
@@ -473,6 +500,8 @@ def pytest_sessionfinish(session, exitstatus):
473
500
file_info : FileCoverageInfo = {
474
501
"lines_covered" : list (lines_covered ), # list of int
475
502
"lines_missed" : list (lines_missed ), # list of int
503
+ "executed_branches" : taken_file_branches ,
504
+ "total_branches" : total_file_branches ,
476
505
}
477
506
# convert relative path to absolute path
478
507
if not pathlib .Path (file ).is_absolute ():
0 commit comments