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

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: 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
10
11
12 class LineCoverage(object):
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].
mikecase (-- gone --) 2015/07/07 17:12:33 s/"[0.0 - 1.0]"/"(0.0, 1.0)"
estevenson1 2015/07/07 23:54:55 Done.
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 To get package links:
47 <a href="_files/1.html">org.chromium.chrome</a>
48 This is returned by the selector |XPATH_SELECT_PACKAGE_ELEMENTS|.
mikecase (-- gone --) 2015/07/07 17:12:32 nit: I would maybe just change .... "To get pack
estevenson1 2015/07/07 23:54:55 Done.
49
50 To get class links:
mikecase (-- gone --) 2015/07/07 17:12:32 As above, maybe... s/"To get class links:"/"Class
estevenson1 2015/07/07 23:54:55 Done, and fixed other occurrences.
51 <a href="1e.html">DoActivity.java</a>
52 This is returned by the selector |XPATH_SELECT_CLASS_ELEMENTS|.
53
54 To get coverage information:
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 # Technically both child 1 and 2 contain the percentage covered for a line.
mikecase (-- gone --) 2015/07/07 17:12:33 nit: clarify this comment. What does "technically
estevenson1 2015/07/07 23:54:55 Done.
96 _ELEMENT_PERCENT_COVERED = 1
97
98 # The second child contains the original line of source code.
mikecase (-- gone --) 2015/07/07 17:12:32 So this says "second child" (index 1) and above yo
estevenson1 2015/07/07 23:54:56 Done.
99 _ELEMENT_CONTAINING_SOURCE_CODE = 1
100
101 # The first child contains the line number.
102 _ELEMENT_CONTAINING_LINENO = 0
103
104 # Maps CSS class names to corresponding coverage constants.
105 _CSS_TO_STATUS = {'c': LineCoverage.COVERED,
mikecase (-- gone --) 2015/07/07 17:12:32 nit: Would change formatting to be _CSS_TO_STATUS
estevenson1 2015/07/07 23:54:55 Done.
106 'p': LineCoverage.PARTIALLY_COVERED,
107 'z': LineCoverage.NOT_COVERED}
108
109 # UTF-8 no break space.
110 _NO_BREAK_SPACE = '\xc2\xa0'
111
112 def __init__(self, emma_file_base_dir):
113 """Initializes _EmmaHtmlParser.
114
115 Args:
116 emma_file_base_dir: Path to the location where EMMA report files are
117 stored. Should be where index.html is stored.
118 """
119 self._base_dir = emma_file_base_dir
120 self._emma_files_path = os.path.join(self._base_dir, '_files')
121 self._index_path = os.path.join(self._base_dir, 'index.html')
122
123 def GetLineCoverage(self, emma_file_path):
124 """Returns a list of LineCoverage objects for the given EMMA HTML file.
125
126 Args:
127 emma_file_path: String representing the path to the EMMA HTML file.
128
129 Returns:
130 A list of LineCoverage objects.
131 """
132 def get_status(tr_element):
133 """Returns coverage status for a <tr> element containing coverage info."""
134 if 'class' not in tr_element.attrib:
135 status = LineCoverage.NOT_EXECUTABLE
136 else:
137 status = self._CSS_TO_STATUS.get(
138 tr_element.attrib['class'], LineCoverage.NOT_EXECUTABLE)
139 return status
140
141 def get_fractional_line_coverage(tr_element, status):
142 """Returns coverage value for a <tr> element containing coverage info."""
143 # If line is partially covered, parse the <td> tag to get the
144 # coverage percent.
145 if status == LineCoverage.PARTIALLY_COVERED:
146 title_attribute = (
147 tr_element[self._ELEMENT_PERCENT_COVERED].attrib['title'])
148 # Parse string that contains percent covered: "83% line coverage ,,,".
149 percent_covered = title_attribute.split('%')[0]
150 fractional_coverage_value = int(percent_covered) / 100.0
151 else:
152 fractional_coverage_value = 1.0
153 return fractional_coverage_value
154
155 def get_lineno(tr_element):
156 """Returns line number for a <tr> element containing coverage info."""
157 lineno_element = tr_element[self._ELEMENT_CONTAINING_LINENO]
158 # Handles oddly formatted HTML (where there is an extra <a> tag).
159 lineno = int(lineno_element.text or
160 lineno_element[self._ELEMENT_CONTAINING_LINENO].text)
161 return lineno
162
163 def get_source_code(tr_element):
164 """Returns Java source for a <tr> element containing coverage info."""
165 raw_source = tr_element[self._ELEMENT_CONTAINING_SOURCE_CODE].text
166 utf8_source = raw_source.encode('UTF-8')
167 readable_source = utf8_source.replace(self._NO_BREAK_SPACE, ' ')
168 return readable_source
169
170 line_tr_elements = self._FindElements(emma_file_path,
171 _path=self._XPATH_SELECT_LOC)
172 line_coverage = []
173 for tr in line_tr_elements:
174 coverage_status = get_status(tr)
175 fractional_coverage = get_fractional_line_coverage(tr, coverage_status)
176 lineno = get_lineno(tr)
177 source = get_source_code(tr)
178 line = LineCoverage(lineno, source, coverage_status, fractional_coverage)
179 line_coverage.append(line)
180
181 return line_coverage
182
183 def GetPackageNameToEmmaFileDict(self):
184 """Returns a dict mapping Java packages to EMMA HTML coverage files.
185
186 Parses the EMMA index.html file to get a list of packages, then parses each
187 package HTML file to get a list of classes for that package, and creates
188 a dict with this info.
189
190 Returns:
191 A dict mapping string representation of Java packages (with class
192 names appended) to the corresponding file paths of EMMA HTML files.
193 """
194 # The <a> elements that contain each package name and the path of the file
195 # where all classes within said package are listed.
196 package_a_elements = self._FindElements(
197 self._index_path, _path=self._XPATH_SELECT_PACKAGE_ELEMENTS)
198 # Maps file path of package directory (EMMA generated) to package name.
199 # Ex. emma_dir/f.html: org.chromium.chrome.
200 package_links = {os.path.join(self._base_dir, link.attrib['href']):
201 link.text
202 for link in package_a_elements if 'href' in link.attrib}
mikecase (-- gone --) 2015/07/07 17:12:33 nit: Would prefer format.... package_links = {
estevenson1 2015/07/07 23:54:55 Done.
203
204 package_to_emma = {}
205 for package_emma_file_path, package_name in package_links.iteritems():
206 # The <a> elements that contain each class name in the current package and
mikecase (-- gone --) 2015/07/07 17:12:33 super nit: Kind of confusingly worked comments. M
estevenson1 2015/07/07 23:54:55 Done.
207 # the path of the file where the coverage info is stored for each class.
mikecase (-- gone --) 2015/07/07 17:12:32 Nit: capitalize "the"
208 coverage_file_a_elements = self._FindElements(
209 package_emma_file_path,
210 _path=self._XPATH_SELECT_CLASS_ELEMENTS)
211
212 for coverage_file_element in coverage_file_a_elements:
213 emma_file_path = os.path.join(self._emma_files_path,
mikecase (-- gone --) 2015/07/07 17:12:32 what is the difference between package_emma_file_p
estevenson1 2015/07/07 23:54:55 package_emma_file_path is the path to the EMMA rep
214 coverage_file_element.attrib['href'])
215 full_package_name = package_name + '.' + coverage_file_element.text
216 package_to_emma[full_package_name] = emma_file_path
217
218 return package_to_emma
219
220 def _FindElements(self, file_path, **kwargs):
mikecase (-- gone --) 2015/07/07 17:12:33 Get rid of kwargs if you can. And just pass the xp
estevenson1 2015/07/07 23:54:55 Done.
221 """Reads a HTML file and performs an XPath match.
222
223 Args:
224 file_path: String representing the path to the HTML file.
225 **kwargs: Keyword arguments for XPath match.
226
227 Returns:
228 A list of lxml.html.HtmlElements matching the given XPath selector.
229 Returns an empty list if there is no match.
230 """
231 try:
232 with open(file_path) as f:
233 file_contents = f.read().decode('ISO-8859-1')
234 root = html.fromstring(file_contents)
235 return root.xpath(**kwargs)
236 except IOError:
237 return []
mikecase (-- gone --) 2015/07/07 17:12:33 You should maybe just fail here? If this is expect
estevenson1 2015/07/07 23:54:55 Done.
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