Skip to content

Commit f2a5b17

Browse files
authored
Merge pull request #4796 from bruntib/checker-coverage-cli
[feat] Adding Checker coverage statistics to CLI
2 parents 93996c6 + 2c6d4b8 commit f2a5b17

File tree

4 files changed

+112
-62
lines changed

4 files changed

+112
-62
lines changed

web/client/codechecker_client/cmd_line_client.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,13 @@ def get_statistics(
15361536
def checker_count(checker_dict, key):
15371537
return checker_dict.get(key, 0)
15381538

1539+
def formatted_guidelines(guideline_rules: Iterable[dict]) -> Optional[str]:
1540+
return "; ".join(
1541+
f"{guideline_rule['guideline']}: "
1542+
f"{', '.join(guideline_rule['rules'])}"
1543+
for guideline_rule in guideline_rules
1544+
) if guideline_rules else None
1545+
15391546
client = setup_client(args.product_url)
15401547

15411548
if 'component' in args:
@@ -1587,6 +1594,44 @@ def checker_count(checker_dict, key):
15871594
})
15881595
severity_total += count
15891596

1597+
# Get checker coverage statistic
1598+
checker_details = client.getCheckerStatusVerificationDetails(
1599+
run_ids, all_checkers_report_filter)
1600+
1601+
checkers = [ttypes.Checker(stat.analyzerName, stat.checkerName)
1602+
for stat in checker_details.values()]
1603+
checker_labels = client.getCheckerLabels(checkers)
1604+
1605+
all_guideline_rules = []
1606+
for labels in checker_labels:
1607+
guideline_entries = []
1608+
guidelines = [label.split("guideline:")[1]
1609+
for label in labels if label.startswith("guideline")]
1610+
1611+
for guideline in guidelines:
1612+
guideline_entries.append({
1613+
"guideline": guideline,
1614+
"rules": [label.split(f"{guideline}:")[1]
1615+
for label in labels
1616+
if label.startswith(f"{guideline}:")]
1617+
})
1618+
all_guideline_rules.append(guideline_entries)
1619+
1620+
for checker_id, guideline_rules in zip(
1621+
checker_details, all_guideline_rules):
1622+
setattr(checker_details[checker_id], "guidelineRules", guideline_rules)
1623+
1624+
checker_coverage_stat = [{
1625+
"checker": stat.checkerName,
1626+
"severity": stat.severity,
1627+
"guidelineRules": stat.guidelineRules,
1628+
"enabledInAllRuns": len(stat.disabled) == 0,
1629+
"enabledRunLength": len(stat.enabled),
1630+
"disabledRunLength": len(stat.disabled),
1631+
"closed": stat.closed,
1632+
"outstanding": stat.outstanding,
1633+
} for stat in checker_details.values()]
1634+
15901635
all_results = []
15911636
total = defaultdict(int)
15921637
for key, checker_data in sorted(list(all_checkers_dict.items()),
@@ -1639,14 +1684,28 @@ def checker_count(checker_dict, key):
16391684

16401685
rows = []
16411686
for stat in severities:
1642-
rows.append((stat['severity'],
1643-
str(stat['reports'])))
1687+
rows.append((stat['severity'], str(stat['reports'])))
16441688

16451689
rows.append(('Total', str(severity_total)))
16461690

16471691
print(twodim.to_str(args.output_format, header, rows,
16481692
separate_footer=True))
16491693

1694+
# Print checker coverage stat
1695+
header = ["Checker Name", "guideline", "Severity",
1696+
"Enabled in all selected runs", "Closed Reports",
1697+
"Outstanding Reports",]
1698+
1699+
rows = [(stat["checker"],
1700+
formatted_guidelines(stat["guidelineRules"]),
1701+
ttypes.Severity._VALUES_TO_NAMES[stat["severity"]],
1702+
stat["enabledInAllRuns"],
1703+
str(stat["closed"]),
1704+
str(stat["outstanding"])) for stat in checker_coverage_stat]
1705+
1706+
print(twodim.to_str(args.output_format, header, rows,
1707+
separate_footer=False))
1708+
16501709

16511710
def handle_remove_run_results(args):
16521711

web/client/codechecker_client/helpers/results.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ def getCheckerCounts(self, base_run_ids, reportFilter, cmpData, limit,
145145
offset):
146146
pass
147147

148+
@thrift_client_call
149+
def getCheckerStatusVerificationDetails(self, runIds, reportFilter):
150+
pass
151+
152+
@thrift_client_call
153+
def getCheckerLabels(self, checkers):
154+
pass
155+
148156
@thrift_client_call
149157
def exportData(self, runId):
150158
pass

web/server/codechecker_server/api/report_server.py

Lines changed: 42 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
import sqlalchemy
2828
from sqlalchemy.sql.expression import or_, and_, not_, func, \
29-
asc, desc, union_all, select, bindparam, literal_column, case, cast, true
29+
asc, desc, union_all, select, bindparam, literal_column, cast, true
3030
from sqlalchemy.orm import contains_eager
3131
from sqlalchemy.types import ARRAY, String
3232

@@ -1384,48 +1384,6 @@ def get_run_id_expression(session, report_filter):
13841384
return Run.id.label("run_id")
13851385

13861386

1387-
def get_is_enabled_case(subquery):
1388-
"""
1389-
Creating a case statement to decide the report
1390-
is enabled or not based on the detection status
1391-
"""
1392-
detection_status_filters = subquery.c.detection_status.in_(list(
1393-
map(detection_status_str,
1394-
(DetectionStatus.OFF, DetectionStatus.UNAVAILABLE))
1395-
))
1396-
1397-
return case(
1398-
(detection_status_filters, False),
1399-
else_=True
1400-
)
1401-
1402-
1403-
def get_is_opened_case(subquery):
1404-
"""
1405-
Creating a case statement to decide the report is opened or not
1406-
based on the detection status and the review status
1407-
"""
1408-
detection_statuses = (
1409-
DetectionStatus.NEW,
1410-
DetectionStatus.UNRESOLVED,
1411-
DetectionStatus.REOPENED
1412-
)
1413-
review_statuses = (
1414-
API_ReviewStatus.UNREVIEWED,
1415-
API_ReviewStatus.CONFIRMED
1416-
)
1417-
detection_and_review_status_filters = [
1418-
subquery.c.detection_status.in_(list(map(
1419-
detection_status_str, detection_statuses))),
1420-
subquery.c.review_status.in_(list(map(
1421-
review_status_str, review_statuses)))
1422-
]
1423-
return case(
1424-
(and_(*detection_and_review_status_filters), True),
1425-
else_=False
1426-
)
1427-
1428-
14291387
def remove_reports(session: DBSession,
14301388
report_ids: Collection,
14311389
chunk_size: int = SQLITE_MAX_VARIABLE_NUMBER):
@@ -3345,18 +3303,15 @@ def getCheckerStatusVerificationDetails(self, run_ids, report_filter):
33453303

33463304
subquery = subquery.subquery()
33473305

3348-
is_enabled_case = get_is_enabled_case(subquery)
3349-
is_opened_case = get_is_opened_case(subquery)
3350-
33513306
query = (
33523307
session.query(
33533308
subquery.c.checker_id,
33543309
subquery.c.checker_name,
33553310
subquery.c.analyzer_name,
33563311
subquery.c.severity,
33573312
subquery.c.run_id,
3358-
is_enabled_case.label("isEnabled"),
3359-
is_opened_case.label("isOpened"),
3313+
subquery.c.detection_status,
3314+
subquery.c.review_status,
33603315
func.count(subquery.c.bug_id)
33613316
)
33623317
.group_by(
@@ -3365,8 +3320,8 @@ def getCheckerStatusVerificationDetails(self, run_ids, report_filter):
33653320
subquery.c.analyzer_name,
33663321
subquery.c.severity,
33673322
subquery.c.run_id,
3368-
is_enabled_case,
3369-
is_opened_case
3323+
subquery.c.detection_status,
3324+
subquery.c.review_status
33703325
)
33713326
)
33723327

@@ -3377,8 +3332,8 @@ def getCheckerStatusVerificationDetails(self, run_ids, report_filter):
33773332
analyzer_name, \
33783333
severity, \
33793334
run_id_list, \
3380-
is_enabled, \
3381-
is_opened, \
3335+
detection_status, \
3336+
review_status, \
33823337
cnt \
33833338
in query.all():
33843339

@@ -3394,6 +3349,22 @@ def getCheckerStatusVerificationDetails(self, run_ids, report_filter):
33943349
outstanding=0
33953350
))
33963351

3352+
is_enabled = detection_status not in map(
3353+
detection_status_str,
3354+
(DetectionStatus.OFF, DetectionStatus.UNAVAILABLE))
3355+
3356+
is_opened = \
3357+
detection_status in map(
3358+
detection_status_str,
3359+
(DetectionStatus.NEW,
3360+
DetectionStatus.UNRESOLVED,
3361+
DetectionStatus.REOPENED)) \
3362+
and \
3363+
review_status in map(
3364+
review_status_str,
3365+
(API_ReviewStatus.UNREVIEWED,
3366+
API_ReviewStatus.CONFIRMED))
3367+
33973368
if is_enabled:
33983369
for r in (run_id_list.split(",")
33993370
if isinstance(run_id_list, str)
@@ -3602,10 +3573,24 @@ def getReportStatusCounts(self, run_ids, report_filter, cmp_data):
36023573
filter_expression, join_tables = process_report_filter(
36033574
session, run_ids, report_filter, cmp_data)
36043575

3576+
detection_and_review_status_filters = [
3577+
Report.detection_status.in_(list(map(
3578+
detection_status_str,
3579+
(DetectionStatus.NEW,
3580+
DetectionStatus.UNRESOLVED,
3581+
DetectionStatus.REOPENED)))),
3582+
Report.review_status.in_(list(map(
3583+
review_status_str,
3584+
(API_ReviewStatus.UNREVIEWED,
3585+
API_ReviewStatus.CONFIRMED))))
3586+
]
3587+
36053588
extended_table = session.query(
36063589
Report.review_status,
36073590
Report.detection_status,
3608-
Report.bug_id
3591+
Report.bug_id,
3592+
and_(*detection_and_review_status_filters)
3593+
.label("isOutstanding")
36093594
)
36103595

36113596
if report_filter.annotations is not None:
@@ -3620,19 +3605,16 @@ def getReportStatusCounts(self, run_ids, report_filter, cmp_data):
36203605

36213606
extended_table = extended_table.subquery()
36223607

3623-
is_outstanding_case = get_is_opened_case(extended_table)
3624-
case_label = "isOutstanding"
3625-
36263608
if report_filter.isUnique:
36273609
q = session.query(
3628-
is_outstanding_case.label(case_label),
3610+
extended_table.c.isOutstanding,
36293611
func.count(extended_table.c.bug_id.distinct())) \
3630-
.group_by(is_outstanding_case)
3612+
.group_by(extended_table.c.isOutstanding)
36313613
else:
36323614
q = session.query(
3633-
is_outstanding_case.label(case_label),
3615+
extended_table.c.isOutstanding,
36343616
func.count(extended_table.c.bug_id)) \
3635-
.group_by(is_outstanding_case)
3617+
.group_by(extended_table.c.isOutstanding)
36363618

36373619
results = {
36383620
report_status_enum(

web/tests/functional/cmdline/test_cmdline.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def run_cmd(cmd, environ=None):
3737

3838
out, err = proc.communicate()
3939
print(out)
40+
print(err)
4041
return proc.returncode, out, err
4142

4243

0 commit comments

Comments
 (0)