From c21567d7ecb56da5ee24d56a0dee7776818512dc Mon Sep 17 00:00:00 2001 From: Chris January Date: Thu, 29 Oct 2020 11:57:51 +0000 Subject: [PATCH] Fix and regression tests for JSON output format. Fixes https://github.com/arm-hpc/porting-advisor/issues/5. --- CHANGES.md | 4 + .../test_json_report_from_command_line.py | 103 ++++++++++++++++ src/advisor/__init__.py | 4 +- src/advisor/issue_type_config.py | 2 + src/advisor/json_report.py | 9 +- src/advisor/report_factory.py | 2 +- src/advisor/source_scanner.py | 2 +- unittest/test_json_report.py | 110 ++++++++++++++++++ unittest/test_source_scanner.py | 2 +- 9 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 integrationtests/test_json_report_from_command_line.py create mode 100644 unittest/test_json_report.py diff --git a/CHANGES.md b/CHANGES.md index 670fc60..f4b0aca 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +1.4.1 2020-10-29 + + * Fix for https://github.com/arm-hpc/porting-advisor/issues/5. + 1.4 2020-07-03 * Add `--output-format` command line argument to specify the output format. diff --git a/integrationtests/test_json_report_from_command_line.py b/integrationtests/test_json_report_from_command_line.py new file mode 100644 index 0000000..509e7cc --- /dev/null +++ b/integrationtests/test_json_report_from_command_line.py @@ -0,0 +1,103 @@ +""" +Copyright 2020 Arm Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +""" + +import advisor.main +from advisor.issue_type_config import IssueTypeConfig +import json +import os +import tempfile +import unittest + +class TestJsonReportFromCommandLine(unittest.TestCase): + def test_json_report_from_command_line(self): + with tempfile.NamedTemporaryFile() as output,\ + tempfile.TemporaryDirectory() as srcdir: + self._populate_source_directory(srcdir) + argv = ['--output', output.name, + '--output-format', 'json', + srcdir] + advisor.main.main(argv) + + with open(output.name) as jsonf: + json_top = json.load(jsonf) + + self.assertIn('errors', json_top) + self.assertEqual(len(json_top['errors']), 0) + self.assertIn('issues', json_top) + self.assertEqual(len(json_top['issues']), 2) + self.assertIn('remarks', json_top) + self.assertEqual(len(json_top['remarks']), 1) + self.assertIn('issue_types', json_top) + self.assertEqual(json_top['issue_types'], IssueTypeConfig.DEFAULT_FILTER) + self.assertIn('target_os', json_top) + self.assertIn(json_top['target_os'], ['linux', 'windows']) + self.assertIn('root_directory', json_top) + self.assertEqual(json_top['root_directory'], srcdir) + self.assertIn('source_dirs', json_top) + self.assertEqual(len(json_top['source_dirs']), 1) + self.assertEqual(json_top['source_dirs'][0], srcdir) + self.assertIn('source_files', json_top) + self.assertEqual(len(json_top['source_files']), 3) + seen_test_negative = False + seen_test_neutral = False + seen_config_guess = False + for fname in json_top['source_files']: + if 'test_negative.c' in fname: + seen_test_negative = True + elif 'test_neutral.c' in fname: + seen_test_neutral = True + elif 'config.guess' in fname: + seen_config_guess = True + else: + self.fail('Unexpected source file name in JSON output') + self.assertTrue(seen_test_negative) + self.assertTrue(seen_test_neutral) + self.assertTrue(seen_config_guess) + seen_issue1 = False + seen_issue2 = False + for issue in json_top['issues']: + if 'test_negative.c' in issue: + self.assertIn('InlineAsm', issue) + seen_issue1 = True + elif 'test_neutral.c' in issue: + self.assertIn('PragmaSimd', issue) + seen_issue2 = True + else: + self.fail('Unexpected issue in JSON output') + self.assertTrue(seen_issue1) + self.assertTrue(seen_issue2) + seen_config_guess = False + for remark in json_top['remarks']: + if 'config.guess' in remark: + seen_config_guess = True + self.assertTrue(seen_config_guess) + + def _populate_source_directory(self, srcdir): + def write_file_contents(srcdir, fname, contents): + with open(os.path.join(srcdir, fname), 'w') as f: + f.write(contents) + + write_file_contents(srcdir, 'test_negative.c', ''' +__asm__("mov r0, r1") +''') + write_file_contents(srcdir, 'test_neutral.c', ''' +#pragma simd foo +''') + write_file_contents(srcdir, 'config.guess', ''' +'aarch64:Linux' +''') diff --git a/src/advisor/__init__.py b/src/advisor/__init__.py index 7a4479e..295d588 100644 --- a/src/advisor/__init__.py +++ b/src/advisor/__init__.py @@ -1,5 +1,5 @@ """ -Copyright 2017-2019 Arm Ltd. +Copyright 2017-2020 Arm Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ """ __project__ = 'porting-advisor' -__version__ = '1.4' +__version__ = '1.4.1' __summary__ = 'Produces an aarch64 porting readiness report.' __webpage__ = 'http://www.gitlab.com/arm-hpc/porting-advisor' diff --git a/src/advisor/issue_type_config.py b/src/advisor/issue_type_config.py index a4c3f3d..42a3148 100644 --- a/src/advisor/issue_type_config.py +++ b/src/advisor/issue_type_config.py @@ -51,6 +51,8 @@ def __init__(self, config_string=None): config_string = IssueTypeConfig.DEFAULT_FILTER self._include_by_default = True + self.config_string = config_string + issue_types = config_string.split(',') self.klasses = [] for issue_type in issue_types: diff --git a/src/advisor/json_report.py b/src/advisor/json_report.py index 7af7031..923b80a 100644 --- a/src/advisor/json_report.py +++ b/src/advisor/json_report.py @@ -22,8 +22,15 @@ class JsonReport(Report): """Generates a JSON report.""" + def __init__(self, root_directory, target_os='linux', issue_type_config=None): + """Generates a JSON report. + + issue_type_config (IssueTypeConfig): issue type filter configuration. + """ + super().__init__(root_directory, target_os) + self.issue_types = issue_type_config.config_string if issue_type_config else None + def write_items(self, output_file, items): - self.issue_types = issue_types # munge 'self' fields so it can be serialized self.source_dirs = list(self.source_dirs) self.issues = [i.__class__.__name__ + ': ' + str(i) for i in self.issues] diff --git a/src/advisor/report_factory.py b/src/advisor/report_factory.py index 25463c9..595881e 100644 --- a/src/advisor/report_factory.py +++ b/src/advisor/report_factory.py @@ -61,7 +61,7 @@ def createReport(self, root_directory, target_os='linux', issue_type_config=None elif output_format == ReportOutputFormat.CSV_ISSUE_TYPE_COUNT_BY_FILE: report = CsvIssueTypeCountByFileReport(root_directory, target_os=target_os, issue_type_config=issue_type_config) elif output_format == ReportOutputFormat.JSON: - report = JsonReport(root_directory, target_os=target_os) + report = JsonReport(root_directory, target_os=target_os, issue_type_config=issue_type_config) else: raise ValueError(output_format) return report diff --git a/src/advisor/source_scanner.py b/src/advisor/source_scanner.py index fb59a03..89eeadb 100644 --- a/src/advisor/source_scanner.py +++ b/src/advisor/source_scanner.py @@ -208,7 +208,7 @@ def finalize_report(self, report): for fname in self.other_arch_intrinsic_inline_asm_files: port_file = find_port_file( fname, report.source_files, report.source_dirs) - if not self.filter_ported_code or (port_file and port_file not in self.aarch64_intrinsic_inline_asm_files): + if not self.filter_ported_code or (not port_file or port_file not in self.aarch64_intrinsic_inline_asm_files): report.add_issue(self.other_arch_intrinsic_inline_asm_files[fname]) else: report.ported_inline_asm += 1 diff --git a/unittest/test_json_report.py b/unittest/test_json_report.py new file mode 100644 index 0000000..f80090c --- /dev/null +++ b/unittest/test_json_report.py @@ -0,0 +1,110 @@ +""" +Copyright 2020 Arm Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +""" + +from advisor.config_guess_scanner import ConfigGuessScanner +from advisor.issue_type_config import IssueTypeConfig +from advisor.json_report import JsonReport +from advisor.report_item import ReportItem +from advisor.source_scanner import SourceScanner +import io +import json +import os +import tempfile +import unittest + + +class TestJsonReport(unittest.TestCase): + def test_output(self): + config_guess_scanner = ConfigGuessScanner() + source_scanner = SourceScanner() + + issue_type_config = IssueTypeConfig() + report = JsonReport('/root', issue_type_config=issue_type_config) + report.add_source_file('/root/src/test_negative.c') + io_object = io.StringIO('__asm__("mov r0, r1")') + source_scanner.scan_file_object( + 'test_negative.c', io_object, report) + report.add_source_file('/root/src/test_neutral.c') + io_object = io.StringIO('#pragma simd foo') + source_scanner.scan_file_object( + 'test_neutral.c', io_object, report) + report.add_source_file('/root/src/config.guess') + io_object = io.StringIO('aarch64:Linux') + config_guess_scanner.scan_file_object( + 'config.guess', io_object, report) + self.assertEqual(len(report.issues), 2) + self.assertEqual(len(report.remarks), 1) + + with tempfile.NamedTemporaryFile(mode='w', delete=False) as ofp: + report.write(ofp) + fname = ofp.name + ofp.close() + + with open(fname) as ifp: + json_top = json.load(ifp) + + self.assertIn('errors', json_top) + self.assertEqual(len(json_top['errors']), 0) + self.assertIn('issues', json_top) + self.assertEqual(len(json_top['issues']), 2) + self.assertIn('remarks', json_top) + self.assertEqual(len(json_top['remarks']), 1) + self.assertIn('issue_types', json_top) + self.assertEqual(json_top['issue_types'], IssueTypeConfig.DEFAULT_FILTER) + self.assertIn('target_os', json_top) + self.assertIn(json_top['target_os'], ['linux', 'windows']) + self.assertIn('root_directory', json_top) + self.assertEqual(json_top['root_directory'], '/root') + self.assertIn('source_dirs', json_top) + self.assertEqual(len(json_top['source_dirs']), 1) + self.assertEqual(json_top['source_dirs'][0], '/root/src') + self.assertIn('source_files', json_top) + self.assertEqual(len(json_top['source_files']), 3) + seen_test_negative = False + seen_test_neutral = False + seen_config_guess = False + for fname in json_top['source_files']: + if 'test_negative.c' in fname: + seen_test_negative = True + elif 'test_neutral.c' in fname: + seen_test_neutral = True + elif 'config.guess' in fname: + seen_config_guess = True + else: + self.fail('Unexpected source file name in JSON output') + self.assertTrue(seen_test_negative) + self.assertTrue(seen_test_neutral) + self.assertTrue(seen_config_guess) + seen_issue1 = False + seen_issue2 = False + for issue in json_top['issues']: + if 'test_negative.c' in issue: + self.assertIn('InlineAsm', issue) + seen_issue1 = True + elif 'test_neutral.c' in issue: + self.assertIn('PragmaSimd', issue) + seen_issue2 = True + else: + self.fail('Unexpected issue in JSON output') + self.assertTrue(seen_issue1) + self.assertTrue(seen_issue2) + seen_config_guess = False + for remark in json_top['remarks']: + if 'config.guess' in remark: + seen_config_guess = True + self.assertTrue(seen_config_guess) diff --git a/unittest/test_source_scanner.py b/unittest/test_source_scanner.py index 057ecbb..7bccd49 100644 --- a/unittest/test_source_scanner.py +++ b/unittest/test_source_scanner.py @@ -195,7 +195,7 @@ def test_no_equivalent_inline_asm_single_file(self): source_scanner.scan_file_object( 'test.c', io_object, report) source_scanner.finalize_report(report) - self.assertEqual(len(report.issues), 1) + self.assertEqual(len(report.issues), 2) def test_equivalent_inline_asm_function_outline(self): source_scanner = SourceScanner()