Skip to content

Commit 9c98c01

Browse files
committed
1.1 release
Fix error message when source tree root directory not found. Add missing preprocessor support for #ifndef. Report negative items before neutral items. Ignore version control blobs. Fix condition for reporting #error to ignore non-architecture specific macros. Include the base name of the source tree root directory in the header of the HTML report. Fix missing icons in the HTML report. Change-Id: I040ec773579770f6412ef7dd6ed8c9ab6e70b6c1
1 parent d5214db commit 9c98c01

File tree

9 files changed

+198
-17
lines changed

9 files changed

+198
-17
lines changed

src/advisor/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"""
1818

1919
__project__ = 'porting-advisor'
20-
__version__ = '1.0'
20+
__version__ = '1.1'
2121
__summary__ = 'Produces an aarch64 porting readiness report.'
2222
__webpage__ = 'http://www.gitlab.com/arm-hpc/porting-advisor'
2323

src/advisor/html_report.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from jinja2 import Environment, PackageLoader
2020
from .report import Report
21+
import os
2122

2223

2324
class HtmlReport(Report):
@@ -30,5 +31,7 @@ def write_items(self, output_file, items):
3031
)
3132
template = env.get_template('advice.html')
3233
rendered = template.render(
33-
root_directory=self.root_directory, items=items)
34+
root_directory=self.root_directory,
35+
root_directory_basename=os.path.basename(self.root_directory),
36+
items=items)
3437
output_file.write(rendered)

src/advisor/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ def main():
4646
args = parser.parse_args()
4747

4848
if not os.path.exists(args.root):
49-
print(_('%s: directory not found.') % args.output, file=sys.stderr)
49+
print(_('%s: directory not found.') % args.root, file=sys.stderr)
5050
sys.exit(1)
5151
elif not os.path.isdir(args.root):
52-
print(_('%s: not a directory.') % args.output, file=sys.stderr)
52+
print(_('%s: not a directory.') % args.root, file=sys.stderr)
5353
sys.exit(1)
5454

5555
if not args.output:

src/advisor/naive_cpp.py

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,18 @@ class PreprocessorDirective:
2929

3030
def __init__(self, directive_type, if_line=None, is_compiler=None):
3131
self.directive_type = directive_type
32+
"""
33+
The type of directive:
34+
35+
TYPE_CONDITIONAL - a #if directive.
36+
TYPE_ERROR - a #error directive.
37+
TYPE_PRAGMA - a #pragma directive
38+
TYPE_OTHER - some other directive.
39+
"""
3240
self.if_line = if_line
41+
"""The line that opened the current preprocessor block."""
3342
self.is_compiler = is_compiler
43+
"""True if the current preprocessor block is compiler-speciifc, else False."""
3444

3545

3646
class NaiveCpp:
@@ -61,10 +71,31 @@ class NaiveCpp:
6171

6272
def __init__(self):
6373
self.in_aarch64 = []
74+
"""
75+
Stack of preprocessor block states. When a new preprocessor block is begun with #if or #ifdef a new state
76+
is pushed onto the stack. The state is True if the condition contains a macro defined on aarch64, False
77+
if the condition contains the negation of a macro defined on aarch64, and None (undefined) otherwise. When the
78+
preprocessor block is finished with #endif the state is popped.
79+
"""
6480
self.in_other_arch = []
81+
"""
82+
Stack of preprocessor block states. When a new preprocessor block is begun with #if or #ifdef a new state
83+
is pushed onto the stack. The state is True if the condition contains a macro defined on a non-aarch64
84+
architecture, False if the condition contains the negation of a macro defined on a non-aarch64 architecture, and
85+
None (undefined) otherwise. When the preprocessor block is finished with #endif the state is popped.
86+
"""
6587
self.in_compiler = []
88+
"""
89+
Stack of preprocessor block states. When a new preprocessor block is begun with #if or #ifdef a new state
90+
is pushed onto the stack. The state is True if the condition contains a compiler-specific macro, else False.
91+
When the preprocessor block is finished with #endif the state is popped.
92+
"""
6693
self.if_lines = []
67-
self.in_comment = False
94+
"""
95+
A stack of preprocessor block control statements. When a new preprocessor block is begun with #if or #ifdef
96+
the statement is pushed onto the stack. When the preprocessor block is finished with #endif the statement
97+
is popped.
98+
"""
6899

69100
def parse_line(self, line):
70101
"""Parse preprocessor directives in a source line.
@@ -125,6 +156,18 @@ def _parse_directive_line(self, line):
125156
self.if_lines.append(line)
126157
return PreprocessorDirective(directive_type=PreprocessorDirective.TYPE_CONDITIONAL, if_line=line,
127158
is_compiler=is_compiler)
159+
elif directive == 'ifndef':
160+
macro = parts[1]
161+
self.in_aarch64.append(
162+
False if NaiveCpp.AARCH64_MACROS_RE_PROG.match(macro) is not None else None)
163+
self.in_other_arch.append(
164+
False if NaiveCpp.NON_AARCH64_MACROS_RE_PROG.match(macro) is not None else None)
165+
is_compiler = False if NaiveCpp.COMPILER_MACROS_RE_PROG.match(
166+
macro) else None
167+
self.in_compiler.append(is_compiler)
168+
self.if_lines.append(line)
169+
return PreprocessorDirective(directive_type=PreprocessorDirective.TYPE_CONDITIONAL, if_line=line,
170+
is_compiler=is_compiler)
128171
elif directive == 'else':
129172
if self.in_aarch64:
130173
self.in_aarch64[-1] = \
@@ -193,29 +236,42 @@ def _is_expression_compiler(expression):
193236

194237
@staticmethod
195238
def _in_x_code(x):
196-
for y in x:
197-
if y:
198-
return True
199-
return False
239+
return True in x
240+
241+
@staticmethod
242+
def _in_x_else_code(x):
243+
return False in x
200244

201245
def in_aarch64_specific_code(self):
202-
"""Are we in aarch64 specific code?
246+
"""
247+
Are we in aarch64 specific code?
203248
204249
Returns:
205250
bool: True if we are currently in an #ifdef __aarch64_ or similar block, else False.
206251
"""
207252
return NaiveCpp._in_x_code(self.in_aarch64)
208253

209254
def in_other_arch_specific_code(self):
210-
"""Are we in other architecture (non-aarch64) specific code?
255+
"""
256+
Are we in other architecture (non-aarch64) specific code?
211257
212258
Returns:
213259
bool: True if we are currently in an #ifdef OTHERARCH or similar block, else False.
214260
"""
215261
return NaiveCpp._in_x_code(self.in_other_arch)
216262

263+
def in_other_arch_else_code(self):
264+
"""
265+
Are we in the #else block of other architecture (non-aarch64) specific code?
266+
267+
Returns:
268+
bool: True if we are currently in the #else block of an #ifdef OTHERARCH or similar block, else False.
269+
"""
270+
return NaiveCpp._in_x_else_code(self.in_other_arch)
271+
217272
def in_compiler_specific_code(self):
218-
"""Are we in compiler specific code?
273+
"""
274+
Are we in compiler specific code?
219275
220276
Returns:
221277
bool: True if we are currently in an #ifdef COMPILER or similar block, else False.

src/advisor/report_item.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ReportItem:
2626
NEGATIVE = 'negative'
2727
ERROR = 'error'
2828
TYPES = [SUMMARY, POSITIVE,
29-
NEUTRAL, NEGATIVE,
29+
NEGATIVE, NEUTRAL,
3030
ERROR]
3131

3232
def __init__(self, description, filename=None, lineno=None, item_type=NEUTRAL):

src/advisor/scanner.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323

2424

2525
class Scanner:
26+
VCS_SUBDIRECTORIES=['.git', '.hg', '.svn', 'CVS']
27+
"""List of (hidden) subdirectories used by version control systems."""
28+
2629
def accepts_file(self, filename):
2730
"""Overriden by subclasses to decide whether or not to accept a
2831
file.
@@ -101,4 +104,6 @@ def scan_tree(self, root, report):
101104
for dirName, _, fileList in os.walk(root):
102105
for fname in fileList:
103106
path = os.path.join(dirName, fname)
107+
if any([('/%s/' % x) in path for x in Scanner.VCS_SUBDIRECTORIES]):
108+
continue
104109
self.scan_file(path, report)

src/advisor/source_scanner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def scan_file_object(self, filename, file_object, report):
8080
continue
8181
result = naive_cpp.parse_line(line)
8282
if result.directive_type == PreprocessorDirective.TYPE_ERROR \
83-
and not naive_cpp.in_other_arch_specific_code():
83+
and naive_cpp.in_other_arch_else_code():
8484
preprocessor_errors.append(PreprocessorErrorIssue(filename,
8585
lineno,
8686
line.strip()))

src/advisor/templates/advice.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
-->
1919
<html>
2020
<head>
21-
<title>Porting Readiness Report for aarch64</title>
21+
<title>aarch64 Porting Readiness Report for {{ root_directory_basename }}</title>
2222
<meta charset="UTF-8">
2323
<style>
2424
body {
@@ -69,15 +69,15 @@
6969
</svg>
7070
</div>
7171
<header>
72-
<span class="name">Porting Readiness Report for aarch64</span>
72+
<span class="name">aarch64 Porting Readiness Report for {{ root_directory_basename }}</span>
7373
</header>
7474
<section class="root">
7575
Source Root: <span class="root_directory">{{ root_directory }}</span>
7676
</section>
7777
<section class="advice">
7878
<table>
7979
{% for item in items %}
80-
<tr><td><svg class="icon"><use xlink:href="#{{ item.type }}"></use></svg></td><td>{{ item.filename if item.filename else '' }}{{ ':' if item.lineno else '' }}{{ item.lineno if item.lineno else '' }}{{ ': ' if item.filename else '' }}{{ item.description }}</td></tr>
80+
<tr><td><svg class="icon"><use xlink:href="#{{ item.item_type }}"></use></svg></td><td>{{ item.filename if item.filename else '' }}{{ ':' if item.lineno else '' }}{{ item.lineno if item.lineno else '' }}{{ ': ' if item.filename else '' }}{{ item.description }}</td></tr>
8181
{% endfor %}
8282
</table>
8383
</section>

unittest/test_html_report.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
Copyright 2017 Arm Ltd.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
"""
18+
19+
from advisor.config_guess_scanner import ConfigGuessScanner
20+
from advisor.html_report import HtmlReport
21+
from advisor.report_item import ReportItem
22+
from advisor.source_scanner import SourceScanner
23+
import io
24+
import tempfile
25+
import unittest
26+
27+
28+
class TestHtmlReport(unittest.TestCase):
29+
def test_item_icons(self):
30+
config_guess_scanner = ConfigGuessScanner()
31+
source_scanner = SourceScanner()
32+
33+
report = HtmlReport('/root')
34+
io_object = io.StringIO('__asm__("mov r0, r1")')
35+
source_scanner.scan_file_object(
36+
'test_negative.c', io_object, report)
37+
io_object = io.StringIO('#pragma simd foo')
38+
source_scanner.scan_file_object(
39+
'test_neutral.c', io_object, report)
40+
io_object = io.StringIO('aarch64:Linux')
41+
config_guess_scanner.scan_file_object(
42+
'config.guess', io_object, report)
43+
self.assertEquals(len(report.issues), 2)
44+
self.assertEquals(len(report.remarks), 1)
45+
46+
with tempfile.NamedTemporaryFile(mode='w', delete=False) as ofp:
47+
report.write(ofp)
48+
fname = ofp.name
49+
ofp.close()
50+
51+
with open(fname) as ifp:
52+
for line in ifp:
53+
if 'test_negative.c' in line:
54+
self.assertIn('#negative', line)
55+
elif 'test_neutral.c' in line:
56+
self.assertIn('#neutral', line)
57+
elif 'config.guess' in line:
58+
self.assertIn('#positive', line)
59+
60+
def test_item_order(self):
61+
config_guess_scanner = ConfigGuessScanner()
62+
source_scanner = SourceScanner()
63+
64+
report = HtmlReport('/root')
65+
io_object = io.StringIO('__asm__("mov r0, r1")')
66+
source_scanner.scan_file_object(
67+
'test_negative.c', io_object, report)
68+
io_object = io.StringIO('#pragma simd foo')
69+
source_scanner.scan_file_object(
70+
'test_neutral.c', io_object, report)
71+
io_object = io.StringIO('aarch64:Linux')
72+
config_guess_scanner.scan_file_object(
73+
'config.guess', io_object, report)
74+
self.assertEquals(len(report.issues), 2)
75+
self.assertEquals(len(report.remarks), 1)
76+
77+
with tempfile.NamedTemporaryFile(mode='w', delete=False) as ofp:
78+
report.write(ofp)
79+
fname = ofp.name
80+
ofp.close()
81+
82+
seenPositive=False
83+
seenNeutral=False
84+
seenNegative=False
85+
86+
with open(fname) as ifp:
87+
for line in ifp:
88+
if 'test_negative.c' in line:
89+
self.assertTrue(seenPositive)
90+
self.assertFalse(seenNeutral)
91+
seenNegative = True
92+
elif 'test_neutral.c' in line:
93+
self.assertTrue(seenPositive)
94+
self.assertTrue(seenNegative)
95+
seenNeutral = True
96+
elif 'config.guess' in line:
97+
self.assertFalse(seenNeutral)
98+
self.assertFalse(seenNegative)
99+
seenPositive = True
100+
101+
def test_heading(self):
102+
report = HtmlReport('/home/user/source/application-1.0')
103+
104+
with tempfile.NamedTemporaryFile(mode='w', delete=False) as ofp:
105+
report.write(ofp)
106+
fname = ofp.name
107+
ofp.close()
108+
109+
seenHeading=False
110+
111+
with open(fname) as ifp:
112+
for line in ifp:
113+
if 'Porting Readiness Report' in line:
114+
self.assertIn('application-1.0', line)
115+
seenHeading = True
116+
117+
self.assertTrue(seenHeading)

0 commit comments

Comments
 (0)