Skip to content

Commit

Permalink
[cli] Allow to specify output file name for parse command
Browse files Browse the repository at this point in the history
  • Loading branch information
csordasmarton committed Aug 12, 2021
1 parent a076cb7 commit 31472dd
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 29 deletions.
57 changes: 44 additions & 13 deletions analyzer/codechecker_analyzer/cmd/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,9 @@ def add_arguments_to_parser(parser):
output_opts.add_argument('-o', '--output',
dest="output_path",
default=argparse.SUPPRESS,
help="Store the output in the given folder.")
help="Store the output in the given file/folder. "
"Note: baseline files must have extension "
"'.baseline'.")

parser.add_argument('--suppress',
type=str,
Expand Down Expand Up @@ -662,7 +664,7 @@ def _generate_json_output(
severity_map: Dict,
input_dirs: List[str],
output_type: str,
output_path: Optional[str],
output_file_path: Optional[str],
trim_path_prefixes: Optional[List[str]],
skip_handler: Callable[[str], bool]
) -> int:
Expand All @@ -682,7 +684,7 @@ def _generate_json_output(
result of analyzing.
output_type : str
Specifies the type of output. It can be gerrit, json, codeclimate.
output_path : Optional[str]
output_file_path : Optional[str]
Path of the output file. If it contains file name then generated output
will be written into.
trim_path_prefixes : Optional[List[str]]
Expand All @@ -699,8 +701,7 @@ def _generate_json_output(
skip_handler)
output_text = json.dumps(reports)

if output_path:
output_file_path = os.path.join(output_path, 'reports.json')
if output_file_path:
with open(output_file_path, mode='w', encoding='utf-8',
errors="ignore") as output_f:
output_f.write(output_text)
Expand Down Expand Up @@ -791,26 +792,56 @@ def main(args):
trim_path_prefixes = args.trim_path_prefix if \
'trim_path_prefix' in args else None

output_path = None
output_dir_path = None
output_file_path = None
if 'output_path' in args:
output_path = os.path.abspath(args.output_path)

if not os.path.exists(output_path):
os.makedirs(output_path)
if export == 'html':
output_dir_path = output_path
else:
if os.path.exists(output_path) and os.path.isdir(output_path):
# For backward compatibility reason we handle the use case
# when directory is provided to this command.
LOG.error("Please provide a file path instead of a directory "
"for '%s' export type!", export)
sys.exit(1)

if export == 'baseline' and not baseline.check(output_path):
LOG.error("Baseline files must have '.baseline' extensions.")
sys.exit(1)

output_file_path = output_path
output_dir_path = os.path.dirname(output_file_path)

if not os.path.exists(output_dir_path):
os.makedirs(output_dir_path)

def get_output_file_path(default_file_name) -> Optional[str]:
""" Return an output file path. """
if output_file_path:
return output_file_path

if not output_dir_path:
return

return os.path.join(output_dir_path, default_file_name)

if export:
if export == 'baseline':
report_hashes, number_of_reports = _parse_convert_reports(
args.input, export, context.severity_map, trim_path_prefixes,
skip_handler)

output_path = get_output_file_path("reports.baseline")
if output_path:
baseline.write(output_path, report_hashes)

sys.exit(2 if number_of_reports else 0)

# The HTML part will be handled separately below.
if export != 'html':
output_path = get_output_file_path("reports.json")
sys.exit(_generate_json_output(
context.severity_map, args.input, export, output_path,
trim_path_prefixes, skip_handler))
Expand Down Expand Up @@ -879,7 +910,7 @@ def skip_html_report_data_handler(report_hash, source_file, report_line,

LOG.info("Generating html output files:")
PlistToHtml.parse(input_path,
output_path,
output_dir_path,
context.path_plist_to_html_dist,
skip_html_report_data_handler,
html_builder,
Expand Down Expand Up @@ -942,14 +973,14 @@ def skip_html_report_data_handler(report_hash, source_file, report_line,

# Create index.html and statistics.html for the generated html files.
if html_builder:
html_builder.create_index_html(output_path)
html_builder.create_statistics_html(output_path)
html_builder.create_index_html(output_dir_path)
html_builder.create_statistics_html(output_dir_path)

print('\nTo view statistics in a browser run:\n> firefox {0}'.format(
os.path.join(output_path, 'statistics.html')))
os.path.join(output_dir_path, 'statistics.html')))

print('\nTo view the results in a browser run:\n> firefox {0}'.format(
os.path.join(args.output_path, 'index.html')))
os.path.join(output_dir_path, 'index.html')))
else:
print("\n----==== Summary ====----")
if file_stats:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,16 +414,16 @@ def test_codeclimate_export(self):
""" Test exporting codeclimate output. """
test_project_notes = os.path.join(self.test_workspaces['NORMAL'],
"test_files", "notes")
output_path = self.test_workspaces['OUTPUT']
output_file_path = os.path.join(
self.test_workspaces['OUTPUT'], 'reports.json')
extract_cmd = ['CodeChecker', 'parse', "--export", "codeclimate",
test_project_notes, "--output", output_path,
test_project_notes, "--output", output_file_path,
'--trim-path-prefix', test_project_notes]

out, _, result = call_command(extract_cmd, cwd=self.test_dir,
env=self.env)
self.assertEqual(result, 2, "Parsing not found any issue.")
result_from_stdout = json.loads(out)
output_file_path = os.path.join(output_path, "reports.json")
with open(output_file_path, 'r', encoding='utf-8', errors='ignore') \
as handle:
result_from_file = json.load(handle)
Expand Down Expand Up @@ -558,7 +558,7 @@ def test_baseline_output(self):

extract_cmd = ['CodeChecker', 'parse',
"-e", "baseline",
"-o", output_path,
"-o", out_file_path,
test_project_notes,
'--trim-path-prefix', test_project_notes]

Expand All @@ -577,16 +577,67 @@ def test_baseline_output(self):

extract_cmd = ['CodeChecker', 'parse',
"-e", "baseline",
"-o", output_path,
"-o", out_file_path,
test_project_macros,
'--trim-path-prefix', test_project_macros]

_, _, result = call_command(
extract_cmd, cwd=self.test_dir, env=self.env)
self.assertEqual(result, 2, "Parsing not found any issue.")

report_hashes = baseline.get_report_hashes([out_file_path])
self.assertSetEqual(report_hashes, {
'3d15184f38c5fa57e479b744fe3f5035',
'f8fbc46cc5afbb056d92bd3d3d702781'})

def test_invalid_baseline_file_extension(self):
""" Test invalid baseline file extension for parse. """
output_path = self.test_workspaces['OUTPUT']
out_file_path = os.path.join(output_path, "cc_reports.invalid")

# Analyze the first project.
test_project_notes = os.path.join(
self.test_workspaces['NORMAL'], "test_files", "notes")

# Try to create baseline file with invalid extension.
parse_cmd = [
"CodeChecker", "parse", "-e", "baseline", "-o", out_file_path,
test_project_notes]

out, _, result = call_command(
parse_cmd, cwd=self.test_dir, env=self.env)
self.assertEqual(result, 1)
self.assertIn("Baseline files must have '.baseline' extensions", out)

# Try to create baseline file in a directory which exists.
os.makedirs(output_path)
parse_cmd = [
"CodeChecker", "parse", "-e", "baseline", "-o", output_path,
test_project_notes]

out, _, result = call_command(
parse_cmd, cwd=self.test_dir, env=self.env)
self.assertEqual(result, 1)
self.assertIn("Please provide a file path instead of a directory", out)

def test_custom_baseline_file(self):
""" Test parse baseline custom output file. """
output_path = self.test_workspaces['OUTPUT']
out_file_path = os.path.join(output_path, "cc_reports.baseline")

# Analyze the first project.
test_project_notes = os.path.join(
self.test_workspaces['NORMAL'], "test_files", "notes")

extract_cmd = ['CodeChecker', 'parse',
"-e", "baseline",
"-o", out_file_path,
test_project_notes]

_, _, result = call_command(
extract_cmd, cwd=self.test_dir, env=self.env)
self.assertEqual(result, 2, "Parsing not found any issue.")

report_hashes = baseline.get_report_hashes([out_file_path])
self.assertEqual(
report_hashes, {
'3d15184f38c5fa57e479b744fe3f5035',
'f8fbc46cc5afbb056d92bd3d3d702781'})
report_hashes, {'3d15184f38c5fa57e479b744fe3f5035'})
11 changes: 7 additions & 4 deletions codechecker_common/output/baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
""" CodeChecker baseline output helpers. """

from io import TextIOWrapper
import os
from typing import Iterable, List, Set

from codechecker_common import logger
Expand All @@ -20,7 +19,12 @@

def __get_report_hashes(f: TextIOWrapper) -> List[str]:
""" Get report hashes from the given file. """
return [h for h in f.readlines() if h]
return [h.strip() for h in f.readlines() if h]


def check(file_path: str) -> bool:
""" True if the given file path is a baseline file. """
return file_path.endswith('.baseline')


def get_report_hashes(
Expand All @@ -43,12 +47,11 @@ def convert(reports: Iterable[Report]) -> List[str]:
return sorted(set(r.report_hash for r in reports))


def write(output_dir_path: str, report_hashes: Iterable[str]):
def write(file_path: str, report_hashes: Iterable[str]):
""" Create a new baseline file or extend an existing one with the given
report hashes in the given output directory. It will remove the duplicates
and also sort the report hashes before writing it to a file.
"""
file_path = os.path.join(output_dir_path, 'reports.baseline')
with open(file_path, mode='a+', encoding='utf-8', errors="ignore") as f:
f.seek(0)
old_report_hashes = __get_report_hashes(f)
Expand Down
3 changes: 2 additions & 1 deletion docs/analyzer/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1655,7 +1655,8 @@ export arguments:
server. For more information see our usage guide.
(default: None)
-o OUTPUT_PATH, --output OUTPUT_PATH
Store the output in the given folder.
Store the output in the given file/folder. Note:
baseline files must have extension '.baseline'.
Environment variables
------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion web/client/codechecker_client/cmd_line_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def filter_local_file_remote_run(
for r in run_args:
if os.path.isdir(r):
local_dirs.append(os.path.abspath(r))
elif os.path.isfile(r) and r.endswith(".baseline"):
elif os.path.isfile(r) and baseline.check(r):
baseline_files.append(os.path.abspath(r))
else:
run_names.append(r)
Expand Down
5 changes: 3 additions & 2 deletions web/tests/libtest/codechecker.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,17 @@ def get_diff_results(basenames, newnames, diff_type, format_type=None,

def create_baseline_file(report_dir: str, cc_env=None) -> str:
""" Create baseline file from the given report directory. """
baseline_file_path = os.path.join(report_dir, 'reports.baseline')
parse_cmd = [
env.codechecker_cmd(), 'parse', report_dir,
'-e', 'baseline', '-o', report_dir]
'-e', 'baseline', '-o', baseline_file_path]

proc = subprocess.Popen(
parse_cmd, encoding="utf-8", errors="ignore", env=cc_env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proc.communicate()

return os.path.join(report_dir, 'reports.baseline')
return baseline_file_path


def login(codechecker_cfg, test_project_path, username, password,
Expand Down

0 comments on commit 31472dd

Please sign in to comment.