Skip to content

Commit

Permalink
[feat] Suppression rules apply to local report dir in remote diff
Browse files Browse the repository at this point in the history
Setting a suppression rule for a report hash affects the local results
in a local report directory when diffing them to a remote run. For
example when storing one report to a run and suppressing it in GUI, the
result of "CodeChecker cmd diff -b remote_run -n report_dir --new" is
an empty list even if "CodeChecker parse report_dir" shows a report and
"CodeChecker cmd results remote_run" doesn't.
  • Loading branch information
bruntib committed Apr 19, 2022
1 parent a92fffb commit 926bc30
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 18 deletions.
40 changes: 37 additions & 3 deletions web/client/codechecker_client/cmd_line_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -879,7 +879,25 @@ def get_diff_local_dir_remote_run(
run_ids, run_names, tag_ids = \
process_run_args(client, remote_run_names)
local_report_hashes = set([r.report_hash for r in report_dir_results])
local_report_hashes.update(baseline.get_report_hashes(baseline_files))

review_status_filter = ttypes.ReviewStatusRuleFilter()
review_status_filter.reportHashes = local_report_hashes
review_status_filter.reviewStatuses = [
ttypes.ReviewStatus.FALSE_POSITIVE,
ttypes.ReviewStatus.INTENTIONAL]
closed_hashes = set(
rule.reportHash for rule in
client.getReviewStatusRules(
review_status_filter, None, None, None))

report_dir_results = [
r for r in report_dir_results if
r.review_status not in ['false positive', 'intentional'] and
(r.report_hash not in closed_hashes or
r.review_status == 'confirmed')]
local_report_hashes = set(r.report_hash for r in report_dir_results)
local_report_hashes.update(
baseline.get_report_hashes(baseline_files) - closed_hashes)

if diff_type == ttypes.DiffType.NEW:
# Get report hashes which can be found only in the remote runs.
Expand Down Expand Up @@ -952,8 +970,24 @@ def get_diff_remote_run_local_dir(
process_run_args(client, remote_run_names)
local_report_hashes = set([r.report_hash for r in report_dir_results])

local_report_hashes = local_report_hashes.union(
baseline.get_report_hashes(baseline_files))
review_status_filter = ttypes.ReviewStatusRuleFilter()
review_status_filter.reportHashes = local_report_hashes
review_status_filter.reviewStatuses = [
ttypes.ReviewStatus.FALSE_POSITIVE,
ttypes.ReviewStatus.INTENTIONAL]
closed_hashes = set(
rule.reportHash for rule in
client.getReviewStatusRules(
review_status_filter, None, None, None))

report_dir_results = [
r for r in report_dir_results if
r.review_status not in ['false positive', 'intentional'] and
(r.report_hash not in closed_hashes or
r.review_status == 'confirmed')]
local_report_hashes = set(r.report_hash for r in report_dir_results)
local_report_hashes.update(
baseline.get_report_hashes(baseline_files) - closed_hashes)

remote_hashes = client.getDiffResultsHash(
run_ids, local_report_hashes, diff_type, None, tag_ids)
Expand Down
43 changes: 28 additions & 15 deletions web/tests/functional/diff_local_remote_suppress/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@
from libtest import project


# Test workspace should be initialized in this module.
TEST_WORKSPACE = None


def setup_package():
def init_projects():
"""
Setup the environment for testing diff_local_remote_suppress.
Expand Down Expand Up @@ -66,10 +62,7 @@ def setup_package():
------------------------------------------------------
"""

global TEST_WORKSPACE
TEST_WORKSPACE = env.get_workspace('diff_local_remote_suppress')

os.environ['TEST_WORKSPACE'] = TEST_WORKSPACE
TEST_WORKSPACE = os.environ['TEST_WORKSPACE']

test_project = 'cpp'

Expand Down Expand Up @@ -97,15 +90,22 @@ def setup_package():
print("This test uses a CodeChecker server... connecting...")
server_access = codechecker.start_or_get_server()
server_access['viewer_product'] = 'diff_local_remote_suppress'
codechecker.add_test_package_product(server_access, TEST_WORKSPACE)
codechecker_cfg.update(server_access)

env.export_test_cfg(TEST_WORKSPACE, test_config)
cc_client = env.setup_viewer_client(TEST_WORKSPACE)
for run_data in cc_client.getRunData(None, None, 0, None):
cc_client.removeRun(run_data.runId, None)

# Copy "cpp" test project 3 times to different directories.

test_proj_path_orig = os.path.join(TEST_WORKSPACE, "test_proj_orig")
test_proj_path_1 = os.path.join(TEST_WORKSPACE, "test_proj_1")
test_proj_path_2 = os.path.join(TEST_WORKSPACE, "test_proj_2")

shutil.rmtree(test_proj_path_orig, ignore_errors=True)
shutil.rmtree(test_proj_path_1, ignore_errors=True)
shutil.rmtree(test_proj_path_2, ignore_errors=True)
shutil.copytree(project.path(test_project), test_proj_path_orig)
shutil.copytree(project.path(test_project), test_proj_path_1)
shutil.copytree(project.path(test_project), test_proj_path_2)
Expand Down Expand Up @@ -180,12 +180,25 @@ def setup_package():
env.export_test_cfg(TEST_WORKSPACE, test_config)


def teardown_package():
"""Clean up after the test."""
def setup_package():
"""
The test files in this diff_local_remote_suppress test share the analyzed
projects. These tests are checking report suppression where the order of
suppressions matters. Since we can't rely on the ordering of tests, the
projects are set up by these tests individually. The setup happens in
function init_projects() and that is imported and executed in each test.
"""
os.environ['TEST_WORKSPACE'] = \
env.get_workspace('diff_local_remote_suppress')

# TODO: If environment variable is set keep the workspace
# and print out the path.
global TEST_WORKSPACE
server_access = codechecker.start_or_get_server()
server_access['viewer_product'] = 'diff_local_remote_suppress'
codechecker.add_test_package_product(
server_access, os.environ['TEST_WORKSPACE'])


def teardown_package():
TEST_WORKSPACE = os.environ['TEST_WORKSPACE']

check_env = env.import_test_cfg(TEST_WORKSPACE)[
'codechecker_cfg']['check_env']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@
from libtest import env
from libtest.codechecker import get_diff_results

from .__init__ import init_projects


class DiffLocalRemoteSuppress(unittest.TestCase):

@classmethod
def setUpClass(cls):
init_projects()

def setUp(self):
# TEST_WORKSPACE is automatically set by test package __init__.py .
test_workspace = os.environ['TEST_WORKSPACE']
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
#
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------

"""diff_local_remote function test.
Tests for the diff feature when comparing a local report directory
with a remote run in the database. These tests are for differences
based on bug suppressions especially.
"""


import os
import unittest

from codechecker_api.codeCheckerDBAccess_v6.ttypes import ReportFilter, \
RunFilter, ReviewStatus

from libtest import env
from libtest.codechecker import get_diff_results

from .__init__ import init_projects


class DiffLocalRemoteSuppressRule(unittest.TestCase):

@classmethod
def setUpClass(cls):
init_projects()

def setUp(self):
# TEST_WORKSPACE is automatically set by test package __init__.py .
test_workspace = os.environ['TEST_WORKSPACE']

test_class = self.__class__.__name__
print('Running ' + test_class + ' tests in ' + test_workspace)

# Get the test configuration from the prepared int the test workspace.
self._test_cfg = env.import_test_cfg(test_workspace)

# Get the test project configuration from the prepared test workspace.
self._testproject_data = env.setup_test_proj_cfg(test_workspace)
self.assertIsNotNone(self._testproject_data)

# Setup a viewer client to test viewer API calls.
self._cc_client = env.setup_viewer_client(test_workspace)
self.assertIsNotNone(self._cc_client)

# Get the CodeChecker cmd if needed for the tests.
self._codechecker_cmd = env.codechecker_cmd()

# Get the run names which belong to this test.
self._run_names = env.get_run_names(test_workspace)

self._project_path_orig = \
self._test_cfg['test_project']['project_path_orig']
self._project_path_1 = \
self._test_cfg['test_project']['project_path_1']
self._project_path_2 = \
self._test_cfg['test_project']['project_path_2']

self._report_dir_orig = \
os.path.join(self._project_path_orig, 'reports')
self._report_dir_1 = os.path.join(self._project_path_1, 'reports')
self._report_dir_2 = os.path.join(self._project_path_2, 'reports')

self._url = env.parts_to_url(self._test_cfg['codechecker_cfg'])

self._env = self._test_cfg['codechecker_cfg']['check_env']

self.guiSuppressAllHashes('core.CallAndMessage')
self.guiSuppressAllHashes('core.StackAddressEscape')

def guiSuppressAllHashes(self, checker_name):
project_orig_run_name = \
self._test_cfg['codechecker_cfg']['run_names']['test_project_orig']
run_filter = RunFilter(names=[project_orig_run_name])
project_orig_run = \
self._cc_client.getRunData(run_filter, None, None, None)[0]

report_filter = ReportFilter(checkerName=[checker_name])
reports = self._cc_client.getRunResults(
[project_orig_run.runId], 500, 0, None, report_filter, None, False)

for report in reports:
self._cc_client.changeReviewStatus(
report.reportId,
ReviewStatus.FALSE_POSITIVE,
"not a bug")

@staticmethod
def filter_by_checker(results, checker_name):
return list(filter(
lambda report: report['checker_name'] == checker_name,
results))

def test_local_remote_new(self):
"""
Suppressed reports are considered as closed in the new check in a
local-remote diff when querying new reports.
"""
results = get_diff_results(
[self._report_dir_orig], [self._run_names['test_project_2']],
'--new', 'json',
['--url', self._url])[0]

self.assertEqual(len(results), 0)

def test_local_remote_resolved(self):
"""
Suppressed reports are considered as closed in the new check in a
local-remote diff when querying resolved reports.
"""
results = get_diff_results(
[self._report_dir_orig], [self._run_names['test_project_2']],
'--resolved', 'json',
['--url', self._url])[0]

self.assertEqual(
len(results), 2)
self.assertEqual(
len(self.filter_by_checker(results, 'core.CallAndMessage')), 0)
self.assertEqual(
len(self.filter_by_checker(results, 'core.DivideZero')), 1)
self.assertEqual(
len(self.filter_by_checker(results, 'deadcode.DeadStores')), 1)

def test_local_remote_unresolved(self):
"""
Suppressed reports are considered as closed in the baseline and in new
check in a local-remote diff when querying unresolved reports.
"""
results = get_diff_results(
[self._report_dir_orig], [self._run_names['test_project_2']],
'--unresolved', 'json',
['--url', self._url])[0]

self.assertEqual(
len(results), 24)
self.assertEqual(
len(self.filter_by_checker(results, 'core.DivideZero')), 9)
self.assertEqual(
len(self.filter_by_checker(results, 'deadcode.DeadStores')), 5)
self.assertEqual(
len(self.filter_by_checker(results, 'cplusplus.NewDelete')), 5)
self.assertEqual(
len(self.filter_by_checker(results, 'unix.Malloc')), 1)
# core.StachAddressEscape reports are not considered unresolved because
# a "false positive" review status rule is set for them, so they are
# like not even in the system.
self.assertEqual(
len(self.filter_by_checker(results, 'core.StackAddressEscape')), 0)

def test_remote_local_new(self):
"""
Suppressed reports are considered as closed in the baseline in a
remote-local diff when querying new reports.
"""
results = get_diff_results(
[self._run_names['test_project_2']], [self._report_dir_orig],
'--new', 'json',
['--url', self._url])[0]

self.assertEqual(
len(results), 2)
self.assertEqual(
len(self.filter_by_checker(results, 'core.CallAndMessage')), 0)
self.assertEqual(
len(self.filter_by_checker(results, 'core.DivideZero')), 1)
self.assertEqual(
len(self.filter_by_checker(results, 'deadcode.DeadStores')), 1)

def test_remote_local_resolved(self):
"""
Suppressed reports are considered as closed in the baseline in a
remote-local diff when querying resolved reports.
"""
results = get_diff_results(
[self._run_names['test_project_2']], [self._report_dir_orig],
'--resolved', 'json',
['--url', self._url])[0]

self.assertEqual(len(results), 0)

def test_remote_local_unresolved(self):
"""
Suppressed reports are considered as closed in the baseline and in new
check in a remote-local diff when querying unresolved reports.
"""
results = get_diff_results(
[self._run_names['test_project_2']], [self._report_dir_orig],
'--unresolved', 'json',
['--url', self._url])[0]

self.assertEqual(
len(results), 24)
self.assertEqual(
len(self.filter_by_checker(results, 'core.DivideZero')), 9)
self.assertEqual(
len(self.filter_by_checker(results, 'deadcode.DeadStores')), 5)
self.assertEqual(
len(self.filter_by_checker(results, 'cplusplus.NewDelete')), 5)
self.assertEqual(
len(self.filter_by_checker(results, 'unix.Malloc')), 1)
# core.StachAddressEscape reports are not considered unresolved because
# a "false positive" review status rule is set for them, so they are
# like not even in the system.
self.assertEqual(
len(self.filter_by_checker(results, 'core.StackAddressEscape')), 0)

0 comments on commit 926bc30

Please sign in to comment.