Skip to content
9 changes: 1 addition & 8 deletions web/client/codechecker_client/cli/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -1079,14 +1079,6 @@ def __register_runs(parser):
"run_2_c_name, run_3_d_name then \"run_2* "
"run_3_d_name\" shows the last three runs.")

group.add_argument('--details',
default=argparse.SUPPRESS,
action='store_true',
required=False,
help="Adds extra details to the run information in "
"JSON format, such as the list of files that are "
"failed to analyze.")

group.add_argument('--all-before-run',
type=str,
dest="all_before_run",
Expand Down Expand Up @@ -1298,6 +1290,7 @@ def add_arguments_to_parser(parser):
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="List the analysis runs available on the server.",
help="List the available analysis runs.")

__register_runs(runs)
runs.set_defaults(func=cmd_line_client.handle_list_runs)
__add_common_arguments(runs, output_formats=DEFAULT_OUTPUT_FORMATS)
Expand Down
28 changes: 20 additions & 8 deletions web/client/codechecker_client/cmd_line_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,9 +645,23 @@ def handle_list_runs(args):
# to a json format.
results = []
for run in runs:
if 'details' in args and args.details:
run.analyzerStatistics = \
client.getAnalysisStatistics(run.runId, None)
enabled_checkers: Dict[str, Set[str]] = {}
info_list: List[ttypes.AnalysisInfo] = client.getAnalysisInfo(
ttypes.AnalysisInfoFilter(runId=run.runId),
constants.MAX_QUERY_SIZE,
0)
for info in info_list:
for analyzer, checkers in (info.checkers or {}).items():
enabled_checkers.setdefault(analyzer, set())
for checker_name, checker_info in (checkers or {}).items():
if checker_info.enabled:
enabled_checkers[analyzer].add(checker_name)
run.analyzerStatistics = client.getAnalysisStatistics(
run.runId, None) or {}
for analyzer in run.analyzerStatistics:
stat = run.analyzerStatistics[analyzer]
stat.enabledCheckers = sorted(
list(enabled_checkers.get(analyzer, set())))
results.append({run.name: run})
print(CmdLineOutputEncoder().encode(results))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print(CmdLineOutputEncoder().encode(results))
print(json.dumps(results, cls=CmdLineOutputEncoder, indent=4))

This would print the output indented.


Expand Down Expand Up @@ -723,11 +737,9 @@ def handle_list_results(args):
query_report_details)

if args.output_format == 'json':
if 'details' in args:
run_id2name = {run.runId: run.name for run in run_data}
for report in all_results:
report.runName = run_id2name[report.runId]

run_id2name = {run.runId: run.name for run in run_data}
for report in all_results:
report.runName = run_id2name[report.runId]
print(CmdLineOutputEncoder().encode(all_results))
else:
header = ['File', 'Checker', 'Severity', 'Message', 'Bug path length',
Expand Down
4 changes: 4 additions & 0 deletions web/client/codechecker_client/helpers/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,7 @@ def getAnalysisStatistics(self, run_id, run_history_id):
@thrift_client_call
def storeAnalysisStatistics(self, run_name, zip_file):
pass

@thrift_client_call
def getAnalysisInfo(self, analysis_info_filter, limit, offset):
pass
85 changes: 85 additions & 0 deletions web/client/tests/unit/test_cmd_line_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import io
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unit test does not test the database query.

So please add a function test too in https://github.com/Ericsson/codechecker/blob/master/web/tests/functional/cmdline/test_cmdline.py

where the other function tests of CodeChecker cmd runs is tested.

import json
import unittest
from types import SimpleNamespace
from contextlib import redirect_stdout
from unittest.mock import Mock, patch

from codechecker_client import cmd_line_client


class Args(SimpleNamespace):
def __contains__(self, key):
return hasattr(self, key)


class DummyRun:
def __init__(self, runId, name="r"):
self.runId = runId
self.name = name
self.resultCount = 0
self.analyzerStatistics = {}
self.runDate = ""
self.versionTag = ""
self.duration = 0
self.description = ""
self.codeCheckerVersion = ""


class DummyCheckerCfg:
def __init__(self, enabled):
self.enabled = enabled


def make_analysis_info(checkers_dict):
checkers = {}
for analyzer, ckrs in checkers_dict.items():
checkers[analyzer] = {name: DummyCheckerCfg(en) for name, en in ckrs.items()}
return SimpleNamespace(checkers=checkers)


class ListRunsEnabledCheckersTest(unittest.TestCase):
def setUp(self):
cmd_line_client.LOG = SimpleNamespace(error=Mock(), warning=Mock(), info=Mock())

@patch("codechecker_client.cmd_line_client.init_logger")
@patch("codechecker_client.cmd_line_client.setup_client")
@patch("codechecker_client.cmd_line_client.get_run_data")
def test_enabled_checkers_json(self, get_run_data, setup_client, init_logger):
client = Mock()
setup_client.return_value = client
get_run_data.return_value = [DummyRun(1)]

client.getAnalysisInfo.return_value = [
make_analysis_info({"clangsa": {"a": True}})
]
client.getAnalysisStatistics.return_value = {
"clangsa": SimpleNamespace(
version="1",
failed=0,
successful=1,
failedFilePaths=[],
)
}

args = Args(
product_url="dummy",
sort_type="name",
sort_order="asc",
output_format="json"
)

buf = io.StringIO()
with redirect_stdout(buf):
cmd_line_client.handle_list_runs(args)

data = json.loads(buf.getvalue())
self.assertEqual(len(data), 1)
run_entry = data[0]
self.assertEqual(len(run_entry), 1)
_, run_data = next(iter(run_entry.items()))
self.assertIn("analyzerStatistics", run_data)
self.assertIn("clangsa", run_data["analyzerStatistics"])
stats = run_data["analyzerStatistics"]["clangsa"]
self.assertIn("enabledCheckers", stats)
self.assertEqual(stats["enabledCheckers"], ["a"])
17 changes: 4 additions & 13 deletions web/tests/functional/cmdline/test_cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,23 +230,15 @@ def test_runs_analysis_statistics(self):
'-o', 'json', '--url', str(self.server_url)]
ret, res, _ = run_cmd(res_cmd, environ=environ)

self.assertEqual(0, ret)
for run in json.loads(res):
for data in run.values():
self.assertIsNone(
data['analyzerStatistics']['clangsa']['failedFilePaths'])

res_cmd = [self._codechecker_cmd, 'cmd', 'runs',
'-o', 'json', '--url', str(self.server_url),
'--details']
ret, res, _ = run_cmd(res_cmd, environ=environ)

self.assertEqual(0, ret)
for run in json.loads(res):
for data in run.values():
self.assertEqual(
data['analyzerStatistics']['clangsa']['failedFilePaths'],
[])
self.assertNotEqual(
data['analyzerStatistics']['clangsa']['enabledCheckers'],
[])
Comment on lines +239 to +241
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check if the list contains a specific checker that is known to be enabled. The problem with this check is that this assertion is true if the value is None for some reason.


def test_proxy_settings(self):
""" Test proxy settings validation. """
Expand Down Expand Up @@ -352,8 +344,7 @@ def test_detailed_results_contain_run_names(self):
check_env = self._test_config['codechecker_cfg']['check_env']

res_cmd = [self._codechecker_cmd, 'cmd', 'results', 'test_files1*',
'test_files1*', '-o', 'json', '--url', str(self.server_url),
'--details']
'test_files1*', '-o', 'json', '--url', str(self.server_url)]

ret, out, _ = run_cmd(res_cmd, environ=check_env)
self.assertEqual(0, ret)
Expand Down