Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(48)

Side by Side Diff: build/android/emma_coverage_stats.py

Issue 1211243016: Added coverage script and tests. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Renamed module to avoid any naming conflicts. Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | build/android/emma_coverage_stats_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright 2015 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Generates incremental code coverage reports for Java code in Chromium."""
7
8 import collections
9 import os
10 from xml.etree import ElementTree
11
12 NOT_EXECUTABLE = -1
13 NOT_COVERED = 0
14 COVERED = 1
15 PARTIALLY_COVERED = 2
16
17 # Coverage information about a single line of code.
18 LineCoverage = collections.namedtuple(
19 'LineCoverage',
20 ['lineno', 'source', 'covered_status', 'fractional_line_coverage'])
21
22
23 class _EmmaHtmlParser(object):
24 """Encapsulates HTML file parsing operations.
25
26 This class contains all operations related to parsing HTML files that were
27 produced using the EMMA code coverage tool. It uses the lxml module for
28 parsing.
29
30 Example HTML:
31
32 Package links:
33 <a href="_files/1.html">org.chromium.chrome</a>
34 This is returned by the selector |XPATH_SELECT_PACKAGE_ELEMENTS|.
35
36 Class links:
37 <a href="1e.html">DoActivity.java</a>
38 This is returned by the selector |XPATH_SELECT_CLASS_ELEMENTS|.
39
40 Line coverage data:
41 <tr class="p">
42 <td class="l" title="78% line coverage (7 out of 9)">108</td>
43 <td title="78% line coverage (7 out of 9 instructions)">
44 if (index < 0 || index = mSelectors.size()) index = 0;</td>
45 </tr>
46 <tr>
47 <td class="l">109</td>
48 <td> </td>
49 </tr>
50 <tr class="c">
51 <td class="l">110</td>
52 <td> if (mSelectors.get(index) != null) {</td>
53 </tr>
54 <tr class="z">
55 <td class="l">111</td>
56 <td> for (int i = 0; i < mSelectors.size(); i++) {</td>
57 </tr>
58 Each <tr> element is returned by the selector |XPATH_SELECT_LOC|.
59
60 We can parse this to get:
61 1. Line number
62 2. Line of source code
63 3. Coverage status (c, z, or p)
64 4. Fractional coverage value (% out of 100 if PARTIALLY_COVERED)
65 """
66 # Selector to match all <a> elements within the rows that are in the table
67 # that displays all of the different packages.
68 _XPATH_SELECT_PACKAGE_ELEMENTS = './/BODY/TABLE[4]/TR/TD/A'
69
70 # Selector to match all <a> elements within the rows that are in the table
71 # that displays all of the different packages within a class.
72 _XPATH_SELECT_CLASS_ELEMENTS = './/BODY/TABLE[3]/TR/TD/A'
73
74 # Selector to match all <tr> elements within the table containing Java source
75 # code in an EMMA HTML file.
76 _XPATH_SELECT_LOC = './/BODY/TABLE[4]/TR'
77
78 # Children of HTML elements are represented as a list in lxml. These constants
79 # represent list indices corresponding to relevant child elements.
80
81 # Child 1 contains percentage covered for a line.
82 _ELEMENT_PERCENT_COVERED = 1
83
84 # Child 1 contains the original line of source code.
85 _ELEMENT_CONTAINING_SOURCE_CODE = 1
86
87 # Child 0 contains the line number.
88 _ELEMENT_CONTAINING_LINENO = 0
89
90 # Maps CSS class names to corresponding coverage constants.
91 _CSS_TO_STATUS = {'c': COVERED, 'p': PARTIALLY_COVERED, 'z': NOT_COVERED}
92
93 # UTF-8 no break space.
94 _NO_BREAK_SPACE = '\xc2\xa0'
95
96 def __init__(self, emma_file_base_dir):
97 """Initializes _EmmaHtmlParser.
98
99 Args:
100 emma_file_base_dir: Path to the location where EMMA report files are
101 stored. Should be where index.html is stored.
102 """
103 self._base_dir = emma_file_base_dir
104 self._emma_files_path = os.path.join(self._base_dir, '_files')
105 self._index_path = os.path.join(self._base_dir, 'index.html')
106
107 def GetLineCoverage(self, emma_file_path):
108 """Returns a list of LineCoverage objects for the given EMMA HTML file.
109
110 Args:
111 emma_file_path: String representing the path to the EMMA HTML file.
112
113 Returns:
114 A list of LineCoverage objects.
115 """
116 line_tr_elements = self._FindElements(
117 emma_file_path, self._XPATH_SELECT_LOC)
118 line_coverage = []
119 for tr in line_tr_elements:
120 # Get the coverage status.
121 coverage_status = self._CSS_TO_STATUS.get(tr.get('CLASS'), NOT_EXECUTABLE)
122 # Get the fractional coverage value.
123 if coverage_status == PARTIALLY_COVERED:
124 title_attribute = (tr[self._ELEMENT_PERCENT_COVERED].get('TITLE'))
125 # Parse string that contains percent covered: "83% line coverage ...".
126 percent_covered = title_attribute.split('%')[0]
127 fractional_coverage = int(percent_covered) / 100.0
128 else:
129 fractional_coverage = 1.0
130
131 # Get the line number.
132 lineno_element = tr[self._ELEMENT_CONTAINING_LINENO]
133 # Handles oddly formatted HTML (where there is an extra <a> tag).
134 lineno = int(lineno_element.text or
135 lineno_element[self._ELEMENT_CONTAINING_LINENO].text)
136 # Get the original line of Java source code.
137 raw_source = tr[self._ELEMENT_CONTAINING_SOURCE_CODE].text
138 utf8_source = raw_source.encode('UTF-8')
139 source = utf8_source.replace(self._NO_BREAK_SPACE, ' ')
140
141 line = LineCoverage(lineno, source, coverage_status, fractional_coverage)
142 line_coverage.append(line)
143
144 return line_coverage
145
146 def GetPackageNameToEmmaFileDict(self):
147 """Returns a dict mapping Java packages to EMMA HTML coverage files.
148
149 Parses the EMMA index.html file to get a list of packages, then parses each
150 package HTML file to get a list of classes for that package, and creates
151 a dict with this info.
152
153 Returns:
154 A dict mapping string representation of Java packages (with class
155 names appended) to the corresponding file paths of EMMA HTML files.
156 """
157 # These <a> elements contain each package name and the path of the file
158 # where all classes within said package are listed.
159 package_link_elements = self._FindElements(
160 self._index_path, self._XPATH_SELECT_PACKAGE_ELEMENTS)
161 # Maps file path of package directory (EMMA generated) to package name.
162 # Ex. emma_dir/f.html: org.chromium.chrome.
163 package_links = {
164 os.path.join(self._base_dir, link.attrib['HREF']): link.text
165 for link in package_link_elements if 'HREF' in link.attrib
166 }
167
168 package_to_emma = {}
169 for package_emma_file_path, package_name in package_links.iteritems():
170 # These <a> elements contain each class name in the current package and
171 # the path of the file where the coverage info is stored for each class.
172 coverage_file_link_elements = self._FindElements(
173 package_emma_file_path, self._XPATH_SELECT_CLASS_ELEMENTS)
174
175 for coverage_file_element in coverage_file_link_elements:
176 emma_coverage_file_path = os.path.join(
177 self._emma_files_path, coverage_file_element.attrib['HREF'])
178 full_package_name = '%s.%s' % (package_name, coverage_file_element.text)
179 package_to_emma[full_package_name] = emma_coverage_file_path
180
181 return package_to_emma
182
183 def _FindElements(self, file_path, xpath_selector):
184 """Reads a HTML file and performs an XPath match.
185
186 Args:
187 file_path: String representing the path to the HTML file.
188 xpath_selector: String representing xpath search pattern.
189
190 Returns:
191 A list of lxml.html.HtmlElements matching the given XPath selector.
jbudorick 2015/07/28 02:45:48 nit: this is no longer lxml
estevenson1 2015/07/28 19:25:51 Done.
192 Returns an empty list if there is no match.
193 """
194 with open(file_path) as f:
195 file_contents = f.read().decode('ISO-8859-1').encode('UTF-8')
196 root = ElementTree.fromstring(file_contents)
197 return root.findall(xpath_selector)
OLDNEW
« no previous file with comments | « no previous file | build/android/emma_coverage_stats_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698