1# Copyright (C) 2010 Google Inc. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7#     * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#     * Redistributions in binary form must reproduce the above
10# copyright notice, this list of conditions and the following disclaimer
11# in the documentation and/or other materials provided with the
12# distribution.
13#     * Neither the name of Google Inc. nor the names of its
14# contributors may be used to endorse or promote products derived from
15# this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29import json
30import logging
31
32_log = logging.getLogger(__name__)
33
34_JSON_PREFIX = "ADD_RESULTS("
35_JSON_SUFFIX = ");"
36
37
38def has_json_wrapper(string):
39    return string.startswith(_JSON_PREFIX) and string.endswith(_JSON_SUFFIX)
40
41
42def strip_json_wrapper(json_content):
43    # FIXME: Kill this code once the server returns json instead of jsonp.
44    if has_json_wrapper(json_content):
45        return json_content[len(_JSON_PREFIX):len(json_content) - len(_JSON_SUFFIX)]
46    return json_content
47
48
49def load_json(filesystem, file_path):
50    content = filesystem.read_text_file(file_path)
51    content = strip_json_wrapper(content)
52    return json.loads(content)
53
54
55def write_json(filesystem, json_object, file_path, callback=None):
56    # Specify separators in order to get compact encoding.
57    json_string = json.dumps(json_object, separators=(',', ':'))
58    if callback:
59        json_string = callback + "(" + json_string + ");"
60    filesystem.write_text_file(file_path, json_string)
61
62
63def convert_trie_to_flat_paths(trie, prefix=None):
64    """Converts the directory structure in the given trie to flat paths, prepending a prefix to each."""
65    result = {}
66    for name, data in trie.iteritems():
67        if prefix:
68            name = prefix + "/" + name
69
70        if len(data) and not "results" in data:
71            result.update(convert_trie_to_flat_paths(data, name))
72        else:
73            result[name] = data
74
75    return result
76
77
78def add_path_to_trie(path, value, trie):
79    """Inserts a single flat directory path and associated value into a directory trie structure."""
80    if not "/" in path:
81        trie[path] = value
82        return
83
84    directory, slash, rest = path.partition("/")
85    if not directory in trie:
86        trie[directory] = {}
87    add_path_to_trie(rest, value, trie[directory])
88
89
90def test_timings_trie(individual_test_timings):
91    """Breaks a test name into chunks by directory and puts the test time as a value in the lowest part, e.g.
92    foo/bar/baz.html: 1ms
93    foo/bar/baz1.html: 3ms
94
95    becomes
96    foo: {
97        bar: {
98            baz.html: 1,
99            baz1.html: 3
100        }
101    }
102    """
103    trie = {}
104    for test_result in individual_test_timings:
105        test = test_result.test_name
106
107        add_path_to_trie(test, int(1000 * test_result.test_run_time), trie)
108
109    return trie
110
111
112# FIXME: We already have a TestResult class in test_results.py
113class TestResult(object):
114    """A simple class that represents a single test result."""
115
116    # Test modifier constants.
117    (NONE, FAILS, FLAKY, DISABLED) = range(4)
118
119    def __init__(self, test, failed=False, elapsed_time=0):
120        self.test_name = test
121        self.failed = failed
122        self.test_run_time = elapsed_time
123
124        test_name = test
125        try:
126            test_name = test.split('.')[1]
127        except IndexError:
128            _log.warn("Invalid test name: %s.", test)
129            pass
130
131        if test_name.startswith('FAILS_'):
132            self.modifier = self.FAILS
133        elif test_name.startswith('FLAKY_'):
134            self.modifier = self.FLAKY
135        elif test_name.startswith('DISABLED_'):
136            self.modifier = self.DISABLED
137        else:
138            self.modifier = self.NONE
139
140    def fixable(self):
141        return self.failed or self.modifier == self.DISABLED
142