1import os, re, string, sys
2import frontend, reason_qualifier
3
4color_map = {
5        'header'        : '#e5e5c0', # greyish yellow
6        'blank'         : '#ffffff', # white
7        'plain_text'    : '#e5e5c0', # greyish yellow
8        'borders'       : '#bbbbbb', # grey
9        'white'         : '#ffffff', # white
10        'green'         : '#66ff66', # green
11        'yellow'        : '#fffc00', # yellow
12        'red'           : '#ff6666', # red
13
14        #### additional keys for shaded color of a box
15        #### depending on stats of GOOD/FAIL
16        '100pct'  : '#32CD32', # green, 94% to 100% of success
17        '95pct'   : '#c0ff80', # step twrds yellow, 88% to 94% of success
18        '90pct'   : '#ffff00', # yellow, 82% to 88%
19        '85pct'   : '#ffc040', # 76% to 82%
20        '75pct'   : '#ff4040', # red, 1% to 76%
21        '0pct'    : '#d080d0', # violet, <1% of success
22
23}
24
25_brief_mode = False
26
27
28def set_brief_mode():
29    global _brief_mode
30    _brief_mode = True
31
32
33def is_brief_mode():
34    return _brief_mode
35
36
37def color_keys_row():
38    """ Returns one row table with samples of 'NNpct' colors
39            defined in the color_map
40            and numbers of corresponding %%
41    """
42    ### This function does not require maintenance in case of
43    ### color_map augmenting - as long as
44    ### color keys for box shading have names that end with 'pct'
45    keys = filter(lambda key: key.endswith('pct'), color_map.keys())
46    def num_pct(key):
47        return int(key.replace('pct',''))
48    keys.sort(key=num_pct)
49    html = ''
50    for key in keys:
51        html+= "\t\t\t<td bgcolor =%s>&nbsp;&nbsp;&nbsp;</td>\n"\
52                        % color_map[key]
53        hint = key.replace('pct',' %')
54        if hint[0]<>'0': ## anything but 0 %
55            hint = 'to ' + hint
56        html+= "\t\t\t<td> %s </td>\n" % hint
57
58    html = """
59<table width = "500" border="0" cellpadding="2" cellspacing="2">\n
60    <tbody>\n
61            <tr>\n
62%s
63            </tr>\n
64    </tbody>
65</table><br>
66""" % html
67    return html
68
69
70def calculate_html(link, data, tooltip=None, row_label=None, column_label=None):
71    if not is_brief_mode():
72        hover_text = '%s:%s' % (row_label, column_label)
73        if data:  ## cell is not empty
74            hover_text += '<br>%s' % tooltip
75        else:
76            ## avoid "None" printed in empty cells
77            data = '&nbsp;'
78        html = ('<center><a class="info" href="%s">'
79                '%s<span>%s</span></a></center>' %
80                (link, data, hover_text))
81        return html
82    # no hover if embedded into AFE but links shall redirect to new window
83    if data: ## cell is non empty
84        html =  '<a href="%s" target="_blank">%s</a>' % (link, data)
85        return html
86    else: ## cell is empty
87        return '&nbsp;'
88
89
90class box:
91    def __init__(self, data, color_key = None, header = False, link = None,
92                 tooltip = None, row_label = None, column_label = None):
93
94        ## in brief mode we display grid table only and nothing more
95        ## - mouse hovering feature is stubbed in brief mode
96        ## - any link opens new window or tab
97
98        redirect = ""
99        if is_brief_mode():
100            ## we are acting under AFE
101            ## any link shall open new window
102            redirect = " target=NEW"
103
104        if data:
105            data = "<tt>%s</tt>" % data
106
107        if link and not tooltip:
108            ## FlipAxis corner, column and row headers
109            self.data = ('<a href="%s"%s>%s</a>' %
110                         (link, redirect, data))
111        else:
112            self.data = calculate_html(link, data, tooltip,
113                                       row_label, column_label)
114
115        if color_map.has_key(color_key):
116            self.color = color_map[color_key]
117        elif header:
118            self.color = color_map['header']
119        elif data:
120            self.color = color_map['plain_text']
121        else:
122            self.color = color_map['blank']
123        self.header = header
124
125
126    def html(self):
127        if self.data:
128            data = self.data
129        else:
130            data = '&nbsp'
131
132        if self.header:
133            box_html = 'th'
134        else:
135            box_html = 'td'
136
137        return "<%s bgcolor=%s>%s</%s>" % \
138                                (box_html, self.color, data, box_html)
139
140
141def grade_from_status(status_idx, status):
142    # % of goodness
143    # GOOD (6)  -> 1
144    # TEST_NA (8) is not counted
145    # ##  If the test doesn't PASS, it FAILS
146    # else -> 0
147
148    if status == status_idx['GOOD']:
149        return 1.0
150    else:
151        return 0.0
152
153
154def average_grade_from_status_count(status_idx, status_count):
155    average_grade = 0
156    total_count = 0
157    for key in status_count.keys():
158        if key not in (status_idx['TEST_NA'], status_idx['RUNNING']):
159            average_grade += (grade_from_status(status_idx, key)
160                                    * status_count[key])
161            total_count += status_count[key]
162    if total_count != 0:
163        average_grade = average_grade / total_count
164    else:
165        average_grade = 0.0
166    return average_grade
167
168
169def shade_from_status_count(status_idx, status_count):
170    if not status_count:
171        return None
172
173    ## average_grade defines a shade of the box
174    ## 0 -> violet
175    ## 0.76 -> red
176    ## 0.88-> yellow
177    ## 1.0 -> green
178    average_grade = average_grade_from_status_count(status_idx, status_count)
179
180    ## find appropiate keyword from color_map
181    if average_grade<0.01:
182        shade = '0pct'
183    elif average_grade<0.75:
184        shade = '75pct'
185    elif average_grade<0.85:
186        shade = '85pct'
187    elif average_grade<0.90:
188        shade = '90pct'
189    elif average_grade<0.95:
190        shade = '95pct'
191    else:
192        shade = '100pct'
193
194    return shade
195
196
197def status_html(db, box_data, shade):
198    """
199    status_count: dict mapping from status (integer key) to count
200    eg. { 'GOOD' : 4, 'FAIL' : 1 }
201    """
202    status_count_subset = box_data.status_count.copy()
203    test_na = db.status_idx['TEST_NA']
204    running = db.status_idx['RUNNING']
205    good = db.status_idx['GOOD']
206
207    status_count_subset[test_na] = 0  # Don't count TEST_NA
208    status_count_subset[running] = 0  # Don't count RUNNING
209    html = "%d&nbsp;/&nbsp;%d " % (status_count_subset.get(good, 0),
210                                   sum(status_count_subset.values()))
211    if test_na in box_data.status_count.keys():
212        html += ' (%d&nbsp;N/A)' % box_data.status_count[test_na]
213    if running in box_data.status_count.keys():
214        html += ' (%d&nbsp;running)' % box_data.status_count[running]
215
216    if box_data.reasons_list:
217        reasons_list = box_data.reasons_list
218        aggregated_reasons_list = \
219                reason_qualifier.aggregate_reason_fields(reasons_list)
220        for reason in aggregated_reasons_list:
221            ## a bit of more postprocessing
222            ## to look nicer in a cell
223            ## in future: to do subtable within the cell
224            reason = reason.replace('<br>','\n')
225            reason = reason.replace('<','[').replace('>',']')
226            reason = reason.replace('|','\n').replace('&',' AND ')
227            reason = reason.replace('\n','<br>')
228            html += '<br>' + reason
229
230    tooltip = ""
231    for status in sorted(box_data.status_count.keys(), reverse = True):
232        status_word = db.status_word[status]
233        tooltip += "%d %s " % (box_data.status_count[status], status_word)
234    return (html,tooltip)
235
236
237def status_count_box(db, tests, link = None):
238    """
239    Display a ratio of total number of GOOD tests
240    to total number of all tests in the group of tests.
241    More info (e.g. 10 GOOD, 2 WARN, 3 FAIL) is in tooltips
242    """
243    if not tests:
244        return box(None, None)
245
246    status_count = {}
247    for test in tests:
248        count = status_count.get(test.status_num, 0)
249        status_count[test.status_num] = count + 1
250    return status_precounted_box(db, status_count, link)
251
252
253def status_precounted_box(db, box_data, link = None,
254                                 x_label = None, y_label = None):
255    """
256    Display a ratio of total number of GOOD tests
257    to total number of all tests in the group of tests.
258    More info (e.g. 10 GOOD, 2 WARN, 3 FAIL) is in tooltips
259    """
260    status_count = box_data.status_count
261    if not status_count:
262        return box(None, None)
263
264    shade = shade_from_status_count(db.status_idx, status_count)
265    html,tooltip = status_html(db, box_data, shade)
266    precounted_box = box(html, shade, False, link, tooltip,
267                            x_label, y_label)
268    return precounted_box
269
270
271def print_table(matrix):
272    """
273    matrix: list of lists of boxes, giving a matrix of data
274    Each of the inner lists is a row, not a column.
275
276    Display the given matrix of data as a table.
277    """
278
279    print ('<table bgcolor="%s" cellspacing="1" cellpadding="5" '
280           'style="margin-right: 200px;">') % (
281           color_map['borders'])
282    for row in matrix:
283        print '<tr>'
284        for element in row:
285            print element.html()
286        print '</tr>'
287    print '</table>'
288
289
290def sort_tests(tests):
291    kernel_order = ['patch', 'config', 'build', 'mkinitrd', 'install']
292
293    results = []
294    for kernel_op in kernel_order:
295        test = 'kernel.' + kernel_op
296        if tests.count(test):
297            results.append(test)
298            tests.remove(test)
299    if tests.count('boot'):
300        results.append('boot')
301        tests.remove('boot')
302    return results + sorted(tests)
303
304
305def print_main_header():
306    hover_css="""\
307a.info{
308position:relative; /*this is the key*/
309z-index:1
310color:#000;
311text-decoration:none}
312
313a.info:hover{z-index:25;}
314
315a.info span{display: none}
316
317a.info:hover span{ /*the span will display just on :hover state*/
318display:block;
319position:absolute;
320top:1em; left:1em;
321min-width: 100px;
322overflow: visible;
323border:1px solid #036;
324background-color:#fff; color:#000;
325text-align: left
326}
327"""
328    print '<head><style type="text/css">'
329    print 'a { text-decoration: none }'
330    print hover_css
331    print '</style></head>'
332    print '<h2>'
333    print '<a href="compose_query.cgi">Functional</a>'
334    print '&nbsp&nbsp&nbsp'
335    print '<a href="machine_benchmark.cgi">Performance</a>'
336    print '&nbsp&nbsp&nbsp'
337    print '<a href="http://ossipedia.ipa.go.jp/crackerjack/compare_results.html">Crackerjack</a>'
338    print '&nbsp&nbsp&nbsp'
339    print '<a href="http://autotest.kernel.org">[About Page]</a>'
340    print '</h2><p>'
341
342
343def group_name(group):
344    name = re.sub('_', '<br>', group.name)
345    if re.search('/', name):
346        (owner, machine) = name.split('/', 1)
347        name = owner + '<br>' + machine
348    return name
349
350def print_add_test_form(available_params, attributes, cleared):
351    print '<form method="post">'
352    print '<input type="hidden" name="attributes" value="%s" />' % attributes
353    print '<input type="hidden" name="cleared" value="%s" />' % cleared
354    print '<select name="key">'
355    for text in available_params:
356        print '<option value="%s">%s</option>' % (text, text)
357    print '</select>'
358    print '<input type="submit" name="add" value="Add test" />'
359    print '<input type="submit" name="clear" value="Clear all tests" />'
360    print '<input type="submit" name="reset" value="Reset" />'
361    print '</form>'
362