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

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

Issue 1211243016: Added coverage script and tests. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixed nits. Created 5 years, 5 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/coverage_test.py » ('j') | build/android/coverage_test.py » ('J')
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 os
9 from lxml import html
jbudorick 2015/07/14 16:26:24 I'm somewhat concerned about this. What if we don'
estevenson1 2015/07/21 00:01:15 Was able to replace this with the standard library
10
11
12 class LineCoverage(object):
jbudorick 2015/07/14 16:26:24 This is basically a namedtuple with constants defi
estevenson1 2015/07/21 00:01:15 Done.
13 """Coverage information about a single line of code."""
14
15 NOT_EXECUTABLE = -1
16 NOT_COVERED = 0
17 COVERED = 1
18 PARTIALLY_COVERED = 2
19
20 def __init__(self, lineno, source, covered_status, fractional_line_coverage):
21 """Initializes LineCoverage.
22
23 Args:
24 lineno: Integer line number.
25 source: A string containing the original line of source code.
26 covered_status: The covered status of the line.
27 fractional_line_coverage: The fractional value representing the fraction
28 of instructions executed for a given line of code. Should be a floating
29 point number between (0.0, 1.0).
30 """
31 self.lineno = lineno
32 self.source = source
33 self.covered_status = covered_status
34 self.fractional_line_coverage = fractional_line_coverage
35
36
37 class _EmmaHtmlParser(object):
38 """Encapsulates HTML file parsing operations.
39
40 This class contains all operations related to parsing HTML files that were
41 produced using the EMMA code coverage tool. It uses the lxml module for
42 parsing.
43
44 Example HTML:
45
46 Package links:
47 <a href="_files/1.html">org.chromium.chrome</a>
48 This is returned by the selector |XPATH_SELECT_PACKAGE_ELEMENTS|.
49
50 Class links:
51 <a href="1e.html">DoActivity.java</a>
52 This is returned by the selector |XPATH_SELECT_CLASS_ELEMENTS|.
53
54 Line coverage data:
55 <tr class="p">
56 <td class="l" title="78% line coverage (7 out of 9)">108</td>
57 <td title="78% line coverage (7 out of 9 instructions)">
58 if (index < 0 || index = mSelectors.size()) index = 0;</td>
59 </tr>
60 <tr>
61 <td class="l">109</td>
62 <td> </td>
63 </tr>
64 <tr class="c">
65 <td class="l">110</td>
66 <td> if (mSelectors.get(index) != null) {</td>
67 </tr>
68 <tr class="z">
69 <td class="l">111</td>
70 <td> for (int i = 0; i < mSelectors.size(); i++) {</td>
71 </tr>
72 Each <tr> element is returned by the selector |XPATH_SELECT_LOC|.
73
74 We can parse this to get:
75 1. Line number
76 2. Line of source code
77 3. Coverage status (c, z, or p)
78 4. Fractional coverage value (% out of 100 if PARTIALLY_COVERED)
79 """
80 # Selector to match all <a> elements within the rows that are in the table
81 # that displays all of the different packages.
82 _XPATH_SELECT_PACKAGE_ELEMENTS = '/html/body/table[4]/tr[*]/td/a'
83
84 # Selector to match all <a> elements within the rows that are in the table
85 # that displays all of the different packages within a class.
86 _XPATH_SELECT_CLASS_ELEMENTS = '/html/body/table[3]/tr[*]/td/a'
87
88 # Selector to match all <tr> elements within the table containing Java source
89 # code in an EMMA HTML file.
90 _XPATH_SELECT_LOC = '/html/body/table[4]/tr'
91
92 # Children of HTML elements are represented as a list in lxml. These constants
93 # represent list indices corresponding to relevant child elements.
94
95 # Child 1 contains percentage covered for a line.
96 _ELEMENT_PERCENT_COVERED = 1
97
98 # Child 1 contains the original line of source code.
99 _ELEMENT_CONTAINING_SOURCE_CODE = 1
100
101 # Child 0 contains the line number.
102 _ELEMENT_CONTAINING_LINENO = 0
103
104 # Maps CSS class names to corresponding coverage constants.
105 _CSS_TO_STATUS = {
106 'c': LineCoverage.COVERED,
107 'p': LineCoverage.PARTIALLY_COVERED,
108 'z': LineCoverage.NOT_COVERED
109 }
110
111 # UTF-8 no break space.
112 _NO_BREAK_SPACE = '\xc2\xa0'
113
114 def __init__(self, emma_file_base_dir):
115 """Initializes _EmmaHtmlParser.
116
117 Args:
118 emma_file_base_dir: Path to the location where EMMA report files are
119 stored. Should be where index.html is stored.
120 """
121 self._base_dir = emma_file_base_dir
122 self._emma_files_path = os.path.join(self._base_dir, '_files')
123 self._index_path = os.path.join(self._base_dir, 'index.html')
124
125 def GetLineCoverage(self, emma_file_path):
126 """Returns a list of LineCoverage objects for the given EMMA HTML file.
127
128 Args:
129 emma_file_path: String representing the path to the EMMA HTML file.
130
131 Returns:
132 A list of LineCoverage objects.
133 """
134 def get_status(tr_element):
jbudorick 2015/07/14 16:26:24 I'm not sure that these need to be local functions
estevenson1 2015/07/21 00:01:15 Done.
135 """Returns coverage status for a <tr> element containing coverage info."""
136 if 'class' not in tr_element.attrib:
137 status = LineCoverage.NOT_EXECUTABLE
138 else:
139 status = self._CSS_TO_STATUS.get(
140 tr_element.attrib['class'], LineCoverage.NOT_EXECUTABLE)
141 return status
142
143 def get_fractional_line_coverage(tr_element, status):
144 """Returns coverage value for a <tr> element containing coverage info."""
145 # If line is partially covered, parse the <td> tag to get the
146 # coverage percent.
147 if status == LineCoverage.PARTIALLY_COVERED:
148 title_attribute = (
149 tr_element[self._ELEMENT_PERCENT_COVERED].attrib['title'])
150 # Parse string that contains percent covered: "83% line coverage ,,,".
mikecase (-- gone --) 2015/07/13 16:50:07 Is this supposed to be "83% line coverage ,,," or
estevenson1 2015/07/21 00:01:15 Done.
151 percent_covered = title_attribute.split('%')[0]
152 fractional_coverage_value = int(percent_covered) / 100.0
153 else:
154 fractional_coverage_value = 1.0
155 return fractional_coverage_value
156
157 def get_lineno(tr_element):
158 """Returns line number for a <tr> element containing coverage info."""
159 lineno_element = tr_element[self._ELEMENT_CONTAINING_LINENO]
160 # Handles oddly formatted HTML (where there is an extra <a> tag).
161 lineno = int(lineno_element.text or
162 lineno_element[self._ELEMENT_CONTAINING_LINENO].text)
163 return lineno
164
165 def get_source_code(tr_element):
166 """Returns Java source for a <tr> element containing coverage info."""
167 raw_source = tr_element[self._ELEMENT_CONTAINING_SOURCE_CODE].text
168 utf8_source = raw_source.encode('UTF-8')
169 readable_source = utf8_source.replace(self._NO_BREAK_SPACE, ' ')
170 return readable_source
171
172 line_tr_elements = self._FindElements(
173 emma_file_path, self._XPATH_SELECT_LOC)
174 line_coverage = []
175 for tr in line_tr_elements:
176 coverage_status = get_status(tr)
177 fractional_coverage = get_fractional_line_coverage(tr, coverage_status)
178 lineno = get_lineno(tr)
179 source = get_source_code(tr)
180 line = LineCoverage(lineno, source, coverage_status, fractional_coverage)
jbudorick 2015/07/14 16:26:24 I know I mentioned above about turning LineCoverag
estevenson1 2015/07/21 00:01:15 Chose to change LineCoverage into a namedtuple. I
181 line_coverage.append(line)
182
183 return line_coverage
184
185 def GetPackageNameToEmmaFileDict(self):
186 """Returns a dict mapping Java packages to EMMA HTML coverage files.
187
188 Parses the EMMA index.html file to get a list of packages, then parses each
189 package HTML file to get a list of classes for that package, and creates
190 a dict with this info.
191
192 Returns:
193 A dict mapping string representation of Java packages (with class
194 names appended) to the corresponding file paths of EMMA HTML files.
195 """
196 # These <a> elements contain each package name and the path of the file
197 # where all classes within said package are listed.
198 package_link_elements = self._FindElements(
199 self._index_path, self._XPATH_SELECT_PACKAGE_ELEMENTS)
200 # Maps file path of package directory (EMMA generated) to package name.
201 # Ex. emma_dir/f.html: org.chromium.chrome.
202 package_links = {
203 os.path.join(self._base_dir, link.attrib['href']): link.text
204 for link in package_link_elements if 'href' in link.attrib
205 }
206
207 package_to_emma = {}
208 for package_emma_file_path, package_name in package_links.iteritems():
209 # These <a> elements contain each class name in the current package and
210 # the path of the file where the coverage info is stored for each class.
211 coverage_file_link_elements = self._FindElements(
212 package_emma_file_path, self._XPATH_SELECT_CLASS_ELEMENTS)
213
214 for coverage_file_element in coverage_file_link_elements:
215 emma_coverage_file_path = os.path.join(
216 self._emma_files_path, coverage_file_element.attrib['href'])
217 full_package_name = package_name + '.' + coverage_file_element.text
jbudorick 2015/07/14 16:26:24 nit: '%s.%s' % (package_name, coverage_file_elemen
estevenson1 2015/07/21 00:01:15 Done.
218 package_to_emma[full_package_name] = emma_coverage_file_path
219
220 return package_to_emma
221
222 def _FindElements(self, file_path, xpath_selector):
223 """Reads a HTML file and performs an XPath match.
224
225 Args:
226 file_path: String representing the path to the HTML file.
227 xpath_selector: String representing xpath search pattern.
228
229 Returns:
230 A list of lxml.html.HtmlElements matching the given XPath selector.
231 Returns an empty list if there is no match.
232 """
233 with open(file_path) as f:
234 file_contents = f.read().decode('ISO-8859-1')
235 root = html.fromstring(file_contents)
236 return root.xpath(xpath_selector)
OLDNEW
« no previous file with comments | « no previous file | build/android/coverage_test.py » ('j') | build/android/coverage_test.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698