Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[client] Fix storage of multiple report directory #3263

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions web/client/codechecker_client/cmd/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,17 @@ def assemble_zip(inputs, zip_file, client):
# Add the files to the zip which will be sent to the server.
for ftc in files_to_compress:
_, filename = os.path.split(ftc)

# It is possible that multiple reports directories are given during
# the store command which contain the same plist file name but with
# different content. For this reason for plist files we need to
# generate a more unique file name when putting it to the storage
# zip file.
if filename.endswith(".plist"):
filename = \
(f"{os.path.splitext(filename)[0]}_"
f"{hashlib.md5(ftc.encode('utf-8')).hexdigest()}.plist")

zip_target = os.path.join('reports', filename)
zipf.write(ftc, zip_target)

Expand Down
13 changes: 11 additions & 2 deletions web/tests/functional/store/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from libtest import codechecker
from libtest import env
from libtest import plist_test

# Test workspace initialized at setup for report storage tests.
TEST_WORKSPACE = None
Expand All @@ -30,8 +31,6 @@ def setup_package():

# Configuration options.
codechecker_cfg = {
'suppress_file': None,
'skip_list_file': None,
'check_env': env.test_env(TEST_WORKSPACE),
'workspace': TEST_WORKSPACE,
'checkers': [],
Expand All @@ -52,6 +51,16 @@ def setup_package():
# Export the test configuration to the workspace.
env.export_test_cfg(TEST_WORKSPACE, {'codechecker_cfg': codechecker_cfg})

# Copy test files to a temporary directory not to modify the
# files in the repository.
# Report files will be overwritten during the tests.
test_dir = os.path.dirname(os.path.realpath(__file__))
dst_dir = os.path.join(TEST_WORKSPACE, "test_proj")
shutil.copytree(os.path.join(test_dir, "test_proj"), dst_dir)

report_file = os.path.join(dst_dir, "divide_zero.plist")
plist_test.prefix_file_path(report_file, dst_dir)


def teardown_package():
"""Clean up after the test."""
Expand Down
2 changes: 1 addition & 1 deletion web/tests/functional/store/test_proj/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# for the store update tests.

build:
$(CXX) divide_zero.cpp -o divide_zero
$(CXX) -c divide_zero.cpp -o /dev/null

clean:
rm -rf divide_zero
Expand Down
5 changes: 5 additions & 0 deletions web/tests/functional/store/test_proj/project_info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "store",
"clean_cmd": "make clean",
"build_cmd": "make build"
}
92 changes: 65 additions & 27 deletions web/tests/functional/store/test_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@
import json
import os
import plistlib
import shutil
import subprocess
import unittest

from libtest import codechecker
from libtest import env
from libtest import plist_test

from codechecker_common import util

Expand All @@ -36,54 +35,39 @@ def setUp(self):
print("Running " + test_class + " tests in " + self._test_workspace)

self._test_cfg = env.import_test_cfg(self._test_workspace)
self.codechecker_cfg = self._test_cfg["codechecker_cfg"]
self.test_proj_dir = os.path.join(
self.codechecker_cfg["workspace"], "test_proj")

def test_tim_path_prefix_store(self):
def test_trim_path_prefix_store(self):
"""Trim the path prefix from the sored reports.

The source file paths are converted to absolute with the
temporary test directory, the test trims that temporary
test directory from the source file path during the storage.
"""

test_dir = os.path.dirname(os.path.realpath(__file__))

report_dir = os.path.join(test_dir, "test_proj")

codechecker_cfg = self._test_cfg["codechecker_cfg"]

# Copy report files to a temporary directory not to modify the
# files in the repository.
# Report files will be overwritten during the tests.
temp_workspace = os.path.join(
codechecker_cfg["workspace"], "test_proj"
)
shutil.copytree(report_dir, temp_workspace)

report_file = os.path.join(temp_workspace, "divide_zero.plist")

# Convert file paths to absolute in the report.
plist_test.prefix_file_path(report_file, temp_workspace)
report_file = os.path.join(self.test_proj_dir, "divide_zero.plist")

report_content = {}
with open(report_file, mode="rb") as rf:
report_content = plistlib.load(rf)

trimmed_paths = [
util.trim_path_prefixes(path, [temp_workspace])
util.trim_path_prefixes(path, [self.test_proj_dir])
for path in report_content["files"]
]

run_name = "store_test"
store_cmd = [
env.codechecker_cmd(),
"store",
temp_workspace,
self.test_proj_dir,
"--name",
run_name,
"--url",
env.parts_to_url(codechecker_cfg),
env.parts_to_url(self.codechecker_cfg),
"--trim-path-prefix",
temp_workspace,
self.test_proj_dir,
"--verbose",
"debug",
]
Expand All @@ -105,7 +89,7 @@ def test_tim_path_prefix_store(self):
run_name,
# Use the 'Default' product.
"--url",
env.parts_to_url(codechecker_cfg),
env.parts_to_url(self.codechecker_cfg),
"-o",
"json",
]
Expand All @@ -120,3 +104,57 @@ def test_tim_path_prefix_store(self):
self.assertEqual(len(reports), 4)
for report in reports:
self.assertIn(report["checkedFile"], trimmed_paths)

def test_store_multiple_report_dirs(self):
""" Test storing multiple report directories.

Analyze the same project to different report directories with different
checker configurations and store these report directories with one
store command to a run.
"""
cfg = dict(self.codechecker_cfg)
codechecker.log(cfg, self.test_proj_dir)

report_dir1 = os.path.join(self.test_proj_dir, 'report_dir1')
report_dir2 = os.path.join(self.test_proj_dir, 'report_dir2')

cfg['reportdir'] = report_dir1
cfg['checkers'] = [
'-d', 'core.DivideZero', '-e', 'deadcode.DeadStores']
codechecker.analyze(cfg, self.test_proj_dir)

cfg['reportdir'] = report_dir2
cfg['checkers'] = [
'-e', 'core.DivideZero', '-d', 'deadcode.DeadStores']
codechecker.analyze(cfg, self.test_proj_dir)

run_name = "multiple_report_dirs"
store_cmd = [
env.codechecker_cmd(), "store",
report_dir1, report_dir2,
"--name", run_name,
"--url", env.parts_to_url(self.codechecker_cfg)]

proc = subprocess.Popen(
store_cmd, encoding="utf-8", errors="ignore",
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
_, err = proc.communicate()

self.assertNotIn("UserWarning: Duplicate name", err)

# Check the reports.
query_cmd = [
env.codechecker_cmd(), "cmd", "results",
run_name,
"--url", env.parts_to_url(self.codechecker_cfg),
"-o", "json"]

out = subprocess.check_output(
query_cmd, encoding="utf-8", errors="ignore")
reports = json.loads(out)

self.assertTrue(
any(r['checkerId'] == 'core.DivideZero' for r in reports))

self.assertTrue(
any(r['checkerId'] == 'deadcode.DeadStores' for r in reports))