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

Local Compare mode HTML output files #1044

Merged
merged 1 commit into from
Nov 3, 2017
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
108 changes: 106 additions & 2 deletions libcodechecker/cmd/cmd_line_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
# -------------------------------------------------------------------------

import base64
from collections import defaultdict
from datetime import datetime
import json
import os
import re
import sys
import shutil

from plist_to_html import PlistToHtml

from codeCheckerDBAccess_v6 import constants, ttypes

from libcodechecker import generic_package_context
from libcodechecker import suppress_handler
from libcodechecker import suppress_file_handler
from libcodechecker.analyze import plist_parser
Expand Down Expand Up @@ -167,6 +172,7 @@ def handle_list_results(args):


def handle_diff_results(args):
context = generic_package_context.get_context()

def get_diff_results(client, baseids, cmp_data):

Expand Down Expand Up @@ -321,7 +327,81 @@ def get_diff_report_dir(client, baseids, report_dir, diff_type):
filtered_reports.append(result)
return filtered_reports

def print_reports(client, reports, output_format):
def cached_report_file_lookup(file_cache, file_id):
"""
Get source file data for the given file and caches it in a file cache
if file data is not found in the cache. Finally, it returns the source
file data from the cache.
"""
if file_id not in file_cache:
source = client.getSourceFileData(file_id, True,
ttypes.Encoding.BASE64)
file_content = base64.b64decode(source.fileContent)
file_cache[file_id] = {'id': file_id,
'path': source.filePath,
'content': file_content}

return file_cache[file_id]

def get_report_data(client, reports, file_cache):
"""
Returns necessary report files and report data events for the HTML
plist parser.
"""
file_sources = {}
report_data = []

for report in reports:
file_sources[report.fileId] = cached_report_file_lookup(
file_cache, report.fileId)

details = client.getReportDetails(report.reportId)
events = []
for index, event in enumerate(details.pathEvents):
file_sources[event.fileId] = cached_report_file_lookup(
file_cache, event.fileId)

events.append({'line': event.startLine,
'col': event.startCol,
'file': event.fileId,
'msg': event.msg,
'step': index + 1})
report_data.append(events)

return {'files': file_sources,
'reports': report_data}

def report_to_html(client, reports, output_dir):
"""
Generate HTML output files for the given reports in the given output
directory by using the Plist To HTML parser.
"""
html_builder = PlistToHtml.HtmlBuilder(context.path_plist_to_html_dist)

file_report_map = defaultdict(list)
for report in reports:
file_report_map[report.fileId].append(report)

file_cache = {}
for file_id, file_reports in file_report_map.items():
checked_file = file_reports[0].checkedFile
filename = os.path.basename(checked_file)

report_data = get_report_data(client, file_reports, file_cache)

output_path = os.path.join(output_dir,
filename + '_' + str(file_id) + '.html')
html_builder.create(output_path, report_data)
print('Html file was generated for file://{0}: file://{1}'.format(
checked_file, output_path))

def print_reports(client, reports, output_format, diff_type):
output_dir = args.export_dir if 'export_dir' in args else None
if 'clean' in args and os.path.isdir(output_dir):
print("Previous analysis results in '{0}' have been removed, "
"overwriting with current results.".format(output_dir))
shutil.rmtree(output_dir)

if output_format == 'json':
output = []
for report in reports:
Expand All @@ -332,6 +412,24 @@ def print_reports(client, reports, output_format):
print(CmdLineOutputEncoder().encode(output))
return

if output_format == 'html':
if len(reports) == 0:
print('No {0} reports was found!'.format(diff_type))
return

output_dir = args.export_dir
if not os.path.exists(output_dir):
os.makedirs(output_dir)

print("Generating HTML output files to file://{0} directory:\n"
.format(output_dir))

report_to_html(client, reports, output_dir)

print('\nTo view the results in a browser run:\n'
' $ firefox {0}'.format(args.export_dir))
return

header = ['File', 'Checker', 'Severity', 'Msg', 'Source']
rows = []
for report in reports:
Expand Down Expand Up @@ -392,6 +490,12 @@ def print_reports(client, reports, output_format):
LOG.info("Matching against runs: " +
', '.join(map(lambda run: run.name, base_runs)))

diff_type = 'new'
if 'unresolved' in args:
diff_type = 'unresolved'
elif 'resolved' in args:
diff_type = 'resolved'

results = []
if report_dir_mode:
diff_type = 'new'
Expand All @@ -416,7 +520,7 @@ def print_reports(client, reports, output_format):
if len(results) == 0:
LOG.info("No results.")
else:
print_reports(client, results, args.output_format)
print_reports(client, results, args.output_format, diff_type)


def handle_list_result_types(args):
Expand Down
4 changes: 4 additions & 0 deletions libcodechecker/libclient/thrift_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ def wrapper(self, *args, **kwargs):
def getRunData(self, run_name_filter):
pass

@ThriftClientCall
def getReportDetails(self, reportId):
Copy link
Contributor

Choose a reason for hiding this comment

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

Did we miss this from the helper?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we did.

Copy link
Contributor

Choose a reason for hiding this comment

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

It wasn't missed per-se. I think this method was never actually used in the command-line client ever before.

pass

@ThriftClientCall
def getSourceFileData(self, fileId, fileContent, encoding):
pass
Expand Down
34 changes: 32 additions & 2 deletions libcodechecker/libhandlers/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import sys

from libcodechecker import output_formatters
from libcodechecker import util
from libcodechecker.cmd import cmd_line_client
from libcodechecker.cmd import product_client
from libcodechecker.logger import add_verbose_arguments
Expand Down Expand Up @@ -105,11 +106,27 @@ def __add_common_arguments(parser,
required=False,
# TODO: 'plaintext' only kept for legacy.
default="plaintext",
choices=["plaintext"] +
choices=["plaintext", "html"] +
output_formatters.USER_FORMATS,
help="The output format to use in showing "
"the data.")

common_group.add_argument('-e', '--export-dir',
dest="export_dir",
default=argparse.SUPPRESS,
help="Store the output in the given folder.")

common_group.add_argument('-c', '--clean',
dest="clean",
required=False,
action='store_true',
default=argparse.SUPPRESS,
help="Delete output results stored in the "
"output directory. (By default, it "
"would keep output files and "
"overwrites only those that contain "
"any reports).")

add_verbose_arguments(common_group)


Expand Down Expand Up @@ -211,6 +228,20 @@ def __register_diff(parser):
help="Show results that appear in both the 'base' and "
"the 'new' run.")

def __handle(args):
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you rename this to something more descriptive?

Copy link
Contributor

Choose a reason for hiding this comment

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

This is the usual name for this method in server and various other commands which have extra rules on their invocation.
This method only lives in the local scope of the registering function and isn't used anywhere else.

"""Custom handler for 'diff' so custom error messages can be
printed without having to capture 'parser' in main."""

output_dir = ['-e', '--export-dir']
if args.output_format == 'html' and \
not any(util.arg_match(output_dir, sys.argv[1:])):
parser.error("argument --output html: not allowed without "
"argument --export-dir")

cmd_line_client.handle_diff_results(args)

parser.set_defaults(func=__handle)


def __register_sum(parser):
"""
Expand Down Expand Up @@ -595,7 +626,6 @@ def add_arguments_to_parser(parser):
"differ between the two.",
help="Compare two analysis runs and show the difference.")
__register_diff(diff)
diff.set_defaults(func=cmd_line_client.handle_diff_results)
__add_common_arguments(diff, has_matrix_output=True)

sum_p = subcommands.add_parser(
Expand Down