Skip to content

Commit

Permalink
Allow comparison of two report directories
Browse files Browse the repository at this point in the history
* It is possible to compare two report directory.
* Filter the results of local comparison.
* Test cases for local comparison.
  • Loading branch information
csordasmarton committed Jul 3, 2018
1 parent 1a36557 commit 9fd7d97
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 161 deletions.
17 changes: 13 additions & 4 deletions docs/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1400,13 +1400,20 @@ Results can be filtered by using separate filter options of `results`, `diff`,
~~~~~~~~~~~~~~~~~~~~~
filter arguments:
--review-status [REVIEW_STATUS [REVIEW_STATUS ...]]
Filter results by review statuses.
Filter results by review statuses. This can be used
only if basename or newname is a run name (on the
remote server). (default: ['unreviewed', 'confirmed'])
--detection-status [DETECTION_STATUS [DETECTION_STATUS ...]]
Filter results by detection statuses.
Filter results by detection statuses. This can be used
only if basename or newname is a run name (on the
remote server). (default: ['new', 'reopened',
'unresolved'])
--severity [SEVERITY [SEVERITY ...]]
Filter results by severities.
--tag [TAG [TAG ...]]
Filter results by version tag names.
Filter results by version tag names. This can be used
only if basename or newname is a run name (on the
remote server).
--file [FILE_PATH [FILE_PATH ...]]
Filter results by file path. The file path can contain
multiple * quantifiers which matches any number of
Expand All @@ -1422,7 +1429,9 @@ filter arguments:
can contain multiple * quantifiers which matches any
number of characters (zero or more).
--component [COMPONENT [COMPONENT ...]]
Filter results by source components.
Filter results by source components. This can be used
only if basename or newname is a run name (on the
remote server).
-s, --suppressed DEPRECATED. Use the '--filter' option to get false
positive (suppressed) results. Show only suppressed
results instead of only unsuppressed ones.
Expand Down
220 changes: 153 additions & 67 deletions libcodechecker/cmd/cmd_line_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,11 @@ def validate_filter_values(user_values, valid_values, value_type):
return True


def add_filter_conditions(client, report_filter, args):
def check_filter_values(args):
"""
This function fills some attributes of the given report filter based on
the arguments which is provided in the command line.
Check if filter values are valid values. Returns values which are checked
or exit from the interpreter.
"""

severities = checkers = file_path = dt_statuses = rw_statuses = None

filter_str = args.filter if 'filter' in args else None
Expand Down Expand Up @@ -154,48 +153,59 @@ def add_filter_conditions(client, report_filter, args):
values_to_check = [
(severities, ttypes.Severity._NAMES_TO_VALUES, 'severity'),
(dt_statuses, ttypes.DetectionStatus._NAMES_TO_VALUES,
'detection status'),
'detection status'),
(rw_statuses, ttypes.ReviewStatus._NAMES_TO_VALUES,
'review status')]
'review status')]

if not all(valid for valid in
[validate_filter_values(*x) for x in values_to_check]):
sys.exit(1)
return severities, checkers, file_path, dt_statuses, rw_statuses


def add_filter_conditions(client, report_filter, args):
"""
This function fills some attributes of the given report filter based on
the arguments which is provided in the command line.
"""

severities, checkers, file_path, dt_statuses, rw_statuses = \
check_filter_values(args)

if severities:
report_filter.severity = map(
lambda x: ttypes.Severity._NAMES_TO_VALUES[x.upper()],
severities)

if dt_statuses:
report_filter.detectionStatus = map(
lambda x: ttypes.DetectionStatus._NAMES_TO_VALUES[x.upper()],
dt_statuses)

if rw_statuses:
report_filter.reviewStatus = map(
lambda x: ttypes.ReviewStatus._NAMES_TO_VALUES[x.upper()],
rw_statuses)

if checkers:
report_filter.checkerName = checkers

if file_path:
report_filter.filepath = file_path

if 'checker_msg' in args:
report_filter.checkerMsg = args.checker_msg

if 'component' in args:
report_filter.componentNames = args.component

if file_path:
report_filter.filepath = file_path

if 'tag' in args:
run_history_filter = ttypes.RunHistoryFilter(tagNames=args.tag)
run_histories = client.getRunHistory(None, None, None,
run_history_filter)
if run_histories:
report_filter.runTag = [t.id for t in run_histories]

if dt_statuses:
report_filter.detectionStatus = map(
lambda x: ttypes.DetectionStatus._NAMES_TO_VALUES[x.upper()],
dt_statuses)

if rw_statuses:
report_filter.reviewStatus = map(
lambda x: ttypes.ReviewStatus._NAMES_TO_VALUES[x.upper()],
rw_statuses)

# ---------------------------------------------------------------------------
# Argument handlers for the 'CodeChecker cmd' subcommands.
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -287,6 +297,8 @@ def handle_diff_results(args):
init_logger(args.verbose if 'verbose' in args else None)
check_deprecated_arg_usage(args)

f_severities, f_checkers, f_file_path, _, _ = check_filter_values(args)

context = generic_package_context.get_context()

def get_diff_results(client, baseids, report_filter, cmp_data):
Expand Down Expand Up @@ -324,6 +336,40 @@ def get_diff_results(client, baseids, report_filter, cmp_data):
cmp_data)
return all_results

def skip_report_dir_result(report):
"""
Returns True if the report should be skipped from the results based on
the given filter set.
"""
if f_severities:
severity_name = context.severity_map.get(report.main['check_name'],
'UNSPECIFIED')
if severity_name.lower() not in map(str.lower, f_severities):
return True

if f_checkers:
checker_name = report.main['check_name']
if not any([re.match(r'^' + c.replace("*", ".*") + '$',
checker_name, re.IGNORECASE)
for c in f_checkers]):
return True

if f_file_path:
file_path = report.files[int(report.main['location']['file'])]
if not any([re.match(r'^' + f.replace("*", ".*") + '$',
file_path, re.IGNORECASE)
for f in f_file_path]):
return True

if 'checker_msg' in args:
checker_msg = report.main['description']
if not any([re.match(r'^' + c.replace("*", ".*") + '$',
checker_msg, re.IGNORECASE)
for c in args.checker_msg]):
return True

return False

def get_report_dir_results(reportdir):
all_reports = []
processed_path_hashes = set()
Expand All @@ -343,6 +389,9 @@ def get_report_dir_results(reportdir):
LOG.debug(report)
continue

if skip_report_dir_result(report):
continue

processed_path_hashes.add(path_hash)
report.main['location']['file_name'] = \
files[int(report.main['location']['file'])]
Expand Down Expand Up @@ -391,6 +440,33 @@ def get_diff_base_results(client, baseids, base_hashes, suppressed_hashes):
None)
return base_results

def get_diff_report_dirs(baseline_dir, newcheck_dir, cmp_data):
"""
Compares two report directories based on the cmp_data parameter and
returns the filtered results.
"""
filtered_reports = []
baseline_dir_results = get_report_dir_results(baseline_dir)
newcheck_dir_results = get_report_dir_results(newcheck_dir)

base_hashes = set([res.report_hash for res in baseline_dir_results])
new_hashes = set([res.report_hash for res in newcheck_dir_results])

if cmp_data.diffType == ttypes.DiffType.NEW:
for res in newcheck_dir_results:
if res.report_hash not in base_hashes:
filtered_reports.append(res)
if cmp_data.diffType == ttypes.DiffType.UNRESOLVED:
for res in newcheck_dir_results:
if res.report_hash in base_hashes:
filtered_reports.append(res)
elif cmp_data.diffType == ttypes.DiffType.RESOLVED:
for res in baseline_dir_results:
if res.report_hash not in new_hashes:
filtered_reports.append(res)

return filtered_reports

def get_diff_report_dir(client, baseids, report_dir, cmp_data):
filtered_reports = []
report_dir_results = get_report_dir_results(report_dir)
Expand Down Expand Up @@ -626,23 +702,23 @@ def print_reports(client, reports, output_format):
ttypes.LinesInFilesRequested(fileId=key,
lines=source_lines[key]))

source_line_contents = client.getLinesInSourceFileContents(
lines_in_files_requested, ttypes.Encoding.BASE64)

for report in reports:
if isinstance(report, Report):
# report is coming from a plist file.
bug_line = report.main['location']['line']
bug_col = report.main['location']['col']
sev = 'unknown'
checked_file = report.main['location']['file_name']\
+ ':' + str(bug_line) + ":" + str(bug_col)
check_name = report.main['check_name']
sev = context.severity_map.get(check_name, 'UNSPECIFIED')
check_msg = report.main['description']
source_line =\
get_line_from_file(report.main['location']['file_name'],
bug_line)
else:
source_line_contents = client.getLinesInSourceFileContents(
lines_in_files_requested, ttypes.Encoding.BASE64)

# report is of ReportData type coming from CodeChecker server.
bug_line = report.line
bug_col = report.column
Expand All @@ -654,11 +730,11 @@ def print_reports(client, reports, output_format):
check_name = report.checkerId
check_msg = report.checkerMsg
rows.append(
(checked_file, check_name, sev, check_msg, source_line))
(sev, checked_file, check_msg, check_name, source_line))
if output_format == 'plaintext':
for row in rows:
print("{0}: {1} [{2}]\n{3}\n".format(row[0],
row[3], row[1], row[4]))
print("[{0}] {1}: {2} [{3}]\n{4}\n".format(row[0],
row[1], row[2], row[3], row[4]))
else:
print(twodim_to_str(output_format, header, rows))

Expand All @@ -673,29 +749,41 @@ def get_run_tag(client, run_ids, tag_name):

return run_histories[0] if len(run_histories) else None

client = setup_client(args.product_url)
def process_run_arg(run_arg_with_tag):
"""
Process the argument and returns run ids a run tag ids.
The argument has the following format: <run_name>:<run_tag>
"""
run_with_tag = run_arg_with_tag.split(':')
run_name = run_with_tag[0]

report_filter = ttypes.ReportFilter()
runs = get_runs(client, [run_name])
run_ids = map(lambda run: run.runId, runs)

# Set base run tag if it is available.
run_tag_name = run_with_tag[1] if len(run_with_tag) > 1 else None
run_tags = None
if run_tag_name:
tag = get_run_tag(client, run_ids, run_tag_name)
run_tags = [tag.id] if tag else None

# Process base run names and tags.
run_with_tag = args.basename.split(':')
base_run_name = run_with_tag[0]
if not run_ids:
LOG.warning(
"No run names match the given pattern: " + run_arg_with_tag)
sys.exit(1)

base_runs = get_runs(client, [base_run_name])
base_ids = map(lambda run: run.runId, base_runs)
LOG.info("Matching runs: " +
', '.join(map(lambda run: run.name, runs)))

# Set base run tag if it is available.
run_tag_name = run_with_tag[1] if len(run_with_tag) > 1 else None
if run_tag_name:
tag = get_run_tag(client, base_ids, run_tag_name)
report_filter.runTag = [tag.id] if tag else None
return run_ids, run_tags

if len(base_ids) == 0:
LOG.warning("No run names match the given pattern: " + args.basename)
sys.exit(1)
client = None

LOG.info("Matching base runs: " +
', '.join(map(lambda run: run.name, base_runs)))
# We set up the client if we are not comparing two local report directory.
if not os.path.isdir(args.basename) or not os.path.isdir(args.newname):
client = setup_client(args.product_url)

report_filter = ttypes.ReportFilter()

cmp_data = ttypes.CompareData()
if 'new' in args:
Expand All @@ -705,35 +793,33 @@ def get_run_tag(client, run_ids, tag_name):
elif 'resolved' in args:
cmp_data.diffType = ttypes.DiffType.RESOLVED

base_ids, base_run_tags = None, None
if not os.path.isdir(args.basename):
base_ids, base_run_tags = process_run_arg(args.basename)
report_filter.runTag = base_run_tags

if not os.path.isdir(args.newname):
new_ids, new_run_tags = process_run_arg(args.newname)
cmp_data.runIds = new_ids
cmp_data.runTag = new_run_tags

results = []
if os.path.isdir(args.newname):
# If newname is a valid directory we assume that it is a report dir and
# we are in local compare mode.
if os.path.isdir(args.basename) and os.path.isdir(args.newname):
# Both parameters are directories.
results = get_diff_report_dirs(os.path.abspath(args.basename),
os.path.abspath(args.newname),
cmp_data)
elif os.path.isdir(args.newname):
# If newname is a valid directory we assume that it is a report
# dir and we are in local compare mode.
results = get_diff_report_dir(client, base_ids,
os.path.abspath(args.newname),
cmp_data)
else:
run_with_tag = args.newname.split(':')
new_run_name = run_with_tag[0]
run_tag_name = run_with_tag[1] if len(run_with_tag) > 1 else None

new_runs = get_runs(client, [new_run_name])
new_ids = map(lambda run: run.runId, new_runs)

if len(new_ids) == 0:
LOG.warning(
"No run names match the given pattern: " + args.newname)
sys.exit(1)

LOG.info("Matching new runs: " +
', '.join(map(lambda run: run.name, new_runs)))

cmp_data.runIds = new_ids
if run_tag_name:
tag = get_run_tag(client, new_ids, run_tag_name)
cmp_data.runTag = [tag.id] if tag else None

results = get_diff_results(client, base_ids, report_filter, cmp_data)
results = get_diff_results(client,
base_ids,
report_filter,
cmp_data)

if len(results) == 0:
LOG.info("No results.")
Expand Down
Loading

0 comments on commit 9fd7d97

Please sign in to comment.