Skip to content

Commit

Permalink
Merge pull request #3539 from csordasmarton/checker_doc_url_in_static…
Browse files Browse the repository at this point in the history
…_htmls

[cli] Add checker documentation URLs to static HTML files
  • Loading branch information
bruntib authored Dec 9, 2021
2 parents c526a25 + 87c3727 commit af517a5
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -684,3 +684,63 @@ def test_html_output_for_empty_plist(self):
os.path.join(output_path, 'notes.plist.html')))
self.assertFalse(os.path.exists(
os.path.join(output_path, f'{plist_file_name}.html')))

def test_html_checker_url(self):
""" Test whether checker documentation urls are generated properly. """
with tempfile.TemporaryDirectory() as tmp_dir:
notes_plist = os.path.join(
self.test_workspaces['NORMAL'], "test_files", "notes",
"notes.plist")
macros_plist = os.path.join(
self.test_workspaces['NORMAL'], "test_files", "macros",
"macros.plist")
shutil.copy(notes_plist, tmp_dir)
shutil.copy(macros_plist, tmp_dir)

macros_plist = os.path.join(tmp_dir, 'macros.plist')
with open(macros_plist, 'r+',
encoding="utf-8", errors="ignore") as f:
content = f.read()
new_content = content.replace(
"core.NullDereference", "UNKNOWN CHECKER NAME")
f.seek(0)
f.truncate()
f.write(new_content)

output_path = os.path.join(tmp_dir, 'html')
extract_cmd = [
'CodeChecker', 'parse', '-e', 'html', '-o', output_path,
tmp_dir]

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

# Test whether documentation urls are set properly for known
# checkers in the index.html file.
index_html = os.path.join(output_path, "index.html")
with open(index_html, 'r', encoding="utf-8", errors="ignore") as f:
content = f.read()

self.assertTrue(re.search(
'<a href=".*>alpha.clone.CloneChecker', content))
self.assertFalse(re.search(
'<a href=".*>UNKNOWN CHECKER NAME', content))
self.assertTrue(re.search('UNKNOWN CHECKER NAME', content))

# Test whether documentation urls are set properly for known
# checkers in the generated HTML report file.
report_html = os.path.join(output_path, "notes.plist.html")
with open(report_html, 'r',
encoding="utf-8", errors="ignore") as f:
content = f.read()
self.assertTrue(re.search('"url": ".+"', content))

# Test whether documentation urls are not set for unknown checkers
# in the generated HTML report file.
report_html = os.path.join(output_path, "macros.plist.html")
with open(report_html, 'r',
encoding="utf-8", errors="ignore") as f:
content = f.read()
self.assertTrue(re.search('"url": null', content))
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
#
# -------------------------------------------------------------------------

from typing import Any, Callable
from typing import Any, Callable, List, Optional, Union


class CheckerLabels:
severity: Callable[[Any, str], str]
label_of_checker: Callable[
[Any, str, str, Optional[str]], Union[str, List[str]]]
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,16 @@ class HTMLMacroExpansion(HTMLBugPathEvent):
HTMLMacroExpansions = List[HTMLMacroExpansion]


class Checker(TypedDict):
name: str
url: Optional[str]


class HTMLReport(TypedDict):
fileId: str
reportHash: Optional[str]
checkerName: str
checker: Checker
analyzerName: Optional[str]
line: int
column: int
message: str
Expand Down Expand Up @@ -168,6 +174,15 @@ def _add_source_file(self, file: File) -> FileSource:

return self.files[file.id]

def _get_doc_url(self, report: Report) -> Optional[str]:
""" Get documentation url for the given report if exists. """
if self._checker_labels:
doc_urls = self._checker_labels.label_of_checker(
report.checker_name, 'doc_url', report.analyzer_name)
return doc_urls[0] if doc_urls else None

return None

def _get_html_reports(
self,
reports: List[Report]
Expand Down Expand Up @@ -219,7 +234,11 @@ def to_macro_expansions(
html_reports.append({
'fileId': report.file.id,
'reportHash': report.report_hash,
'checkerName': report.checker_name,
'checker': {
'name': report.checker_name,
'url': self._get_doc_url(report)
},
'analyzerName': report.analyzer_name,
'line': report.line,
'column': report.column,
'message': report.message,
Expand Down Expand Up @@ -322,6 +341,14 @@ def create_index_html(self, output_dir: str):
rs = review_status.lower().replace(' ', '-')
file_path = self.files[report['fileId']]['filePath']

checker = report['checker']
doc_url = checker.get('url')
if doc_url:
checker_name_col_content = f'<a href="{doc_url}" '\
f'target="_blank">{checker["name"]}</a>'
else:
checker_name_col_content = checker["name"]

table_reports.write(f'''
<tr>
<td>{i + 1}</td>
Expand All @@ -334,7 +361,7 @@ def create_index_html(self, output_dir: str):
<i class="severity-{severity}"
title="{severity}"></i>
</td>
<td>{report['checkerName']}</td>
<td>{checker_name_col_content}</td>
<td>{message}</td>
<td class="bug-path-length">{bug_path_length}</td>
<td class="review-status review-status-{rs}">
Expand Down Expand Up @@ -375,7 +402,7 @@ def severity_order(severity: str) -> int:
checker_statistics: Dict[str, int] = defaultdict(int)
for html_file in self.generated_html_reports:
for report in self.generated_html_reports[html_file]:
checker = report['checkerName']
checker = report['checker']['name']
checker_statistics[checker] += 1

checker_rows: List[List[str]] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ var BugViewer = {
var events = report.events;
var lastBugEvent = events[events.length - 1];
this.setCurrentBugEvent(lastBugEvent, events.length - 1);
this.setCheckerName(report.checkerName);
this.setChecker(report.checker);
this.setReviewStatus(report.reviewStatus);

window.location.hash = '#reportHash=' + report.reportHash;
Expand All @@ -165,8 +165,14 @@ var BugViewer = {
});
},

setCheckerName : function (checkerName) {
this._checkerName.innerHTML = checkerName;
setChecker : function (checker) {
var content = checker.name;
if (checker.url) {
content = '<a href="' + checker.url + '" target="_blank">' +
checker.name + '</a>';
}

this._checkerName.innerHTML = content;
},

setReviewStatus : function (status) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def test_get_report_data_notes(self):
self.assertEqual(len(report['notes']), 1)
self.assertEqual(len(report['macros']), 0)
self.assertGreaterEqual(len(report['events']), 1)
self.assertEqual(report['checkerName'], 'alpha.clone.CloneChecker')
self.assertEqual(report['checker']['name'], 'alpha.clone.CloneChecker')

def test_get_report_data_macros(self):
""" Get report data for plist which contains macro expansion. """
Expand All @@ -128,7 +128,7 @@ def test_get_report_data_macros(self):
self.assertEqual(len(report['notes']), 0)
self.assertEqual(len(report['macros']), 1)
self.assertGreaterEqual(len(report['events']), 1)
self.assertEqual(report['checkerName'], 'core.NullDereference')
self.assertEqual(report['checker']['name'], 'core.NullDereference')

def test_get_report_data_simple(self):
""" Get report data for plist which contains simple reports. """
Expand All @@ -144,13 +144,13 @@ def test_get_report_data_simple(self):
self.assertEqual(len(html_reports), 2)

dead_stores = [r for r in html_reports if
r['checkerName'] == 'deadcode.DeadStores'][0]
r['checker']['name'] == 'deadcode.DeadStores'][0]
self.assertEqual(len(dead_stores['notes']), 0)
self.assertEqual(len(dead_stores['macros']), 0)
self.assertGreaterEqual(len(dead_stores['events']), 1)

divide_zero = [r for r in html_reports if
r['checkerName'] == 'core.DivideZero'][0]
r['checker']['name'] == 'core.DivideZero'][0]
self.assertEqual(len(divide_zero['notes']), 0)
self.assertEqual(len(divide_zero['macros']), 0)
self.assertGreaterEqual(len(divide_zero['events']), 1)
Expand Down

0 comments on commit af517a5

Please sign in to comment.