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

Side by Side Diff: scripts/slave/recipe_modules/json/api.py

Issue 23889036: Refactor the way that TestApi works so that it is actually useful. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: License headers Created 7 years, 2 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 | Annotate | Revision Log
OLDNEW
1 # Copyright 2013 The Chromium Authors. All rights reserved. 1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import functools 5 import functools
6 import contextlib
6 import json 7 import json
7 import os
8 import tempfile
9 8
10 from cStringIO import StringIO 9 from cStringIO import StringIO
11 10
12 from slave import recipe_api 11 from slave import recipe_api
12 from slave import recipe_util
13
14 from .util import TestResults
13 15
14 class StringListIO(object): 16 class StringListIO(object):
15 def __init__(self): 17 def __init__(self):
16 self.lines = [StringIO()] 18 self.lines = [StringIO()]
17 19
18 def write(self, s): 20 def write(self, s):
19 while s: 21 while s:
20 i = s.find('\n') 22 i = s.find('\n')
21 if i == -1: 23 if i == -1:
22 self.lines[-1].write(s) 24 self.lines[-1].write(s)
23 break 25 break
24 self.lines[-1].write(s[:i]) 26 self.lines[-1].write(s[:i])
25 self.lines[-1] = self.lines[-1].getvalue() 27 self.lines[-1] = self.lines[-1].getvalue()
26 self.lines.append(StringIO()) 28 self.lines.append(StringIO())
27 s = s[i+1:] 29 s = s[i+1:]
28 30
29 def close(self): 31 def close(self):
30 if not isinstance(self.lines[-1], basestring): 32 if not isinstance(self.lines[-1], basestring):
31 self.lines[-1] = self.lines[-1].getvalue() 33 self.lines[-1] = self.lines[-1].getvalue()
32 34
33 35
34 def convert_trie_to_flat_paths(trie, prefix=None): 36 class JsonOutputPlaceholder(recipe_util.Placeholder):
35 # Cloned from webkitpy.layout_tests.layout_package.json_results_generator
36 # so that this code can stand alone.
37 result = {}
38 for name, data in trie.iteritems():
39 if prefix:
40 name = prefix + "/" + name
41
42 if len(data) and not "actual" in data and not "expected" in data:
43 result.update(convert_trie_to_flat_paths(data, name))
44 else:
45 result[name] = data
46
47 return result
48
49
50 class TestResults(object):
51 def __init__(self, jsonish):
52 self.raw = jsonish
53
54 self.tests = convert_trie_to_flat_paths(jsonish.get('tests', {}))
55 self.passes = {}
56 self.unexpected_passes = {}
57 self.failures = {}
58 self.unexpected_failures = {}
59 self.flakes = {}
60 self.unexpected_flakes = {}
61
62 for (test, result) in self.tests.iteritems():
63 key = 'unexpected_' if result.get('is_unexpected') else ''
64 actual_result = result['actual']
65 data = actual_result
66 if ' PASS' in actual_result:
67 key += 'flakes'
68 elif actual_result == 'PASS':
69 key += 'passes'
70 data = result
71 else:
72 key += 'failures'
73 getattr(self, key)[test] = data
74
75 def __getattr__(self, key):
76 if key in self.raw:
77 return self.raw[key]
78 raise AttributeError("'%s' object has no attribute '%s'" %
79 (self.__class__, key)) # pragma: no cover
80
81
82 class JsonOutputPlaceholder(recipe_api.Placeholder):
83 """JsonOutputPlaceholder is meant to be a placeholder object which, when added 37 """JsonOutputPlaceholder is meant to be a placeholder object which, when added
84 to a step's cmd list, will be replaced by annotated_run with the command 38 to a step's cmd list, will be replaced by annotated_run with the command
85 parameters --output-json /path/to/file during the evaluation of your recipe 39 parameters --output-json /path/to/file during the evaluation of your recipe
86 generator. 40 generator.
87 41
88 This placeholder can be optionally added when you use the Steps.step() 42 This placeholder can be optionally added when you use the Steps.step()
89 method in this module. 43 method in this module.
90 44
91 After the termination of the step, this file is expected to contain a valid 45 After the termination of the step, this file is expected to contain a valid
92 JSON document, which will be set as the json_output for that step in the 46 JSON document, which will be set as the json_output for that step in the
93 step_history OrderedDict passed to your recipe generator. 47 step_history OrderedDict passed to your recipe generator.
94 """ 48 """
95 # TODO(iannucci): The --output-json was a shortsighted bug. It should be 49 def __init__(self, api):
96 # --json-output to generalize to '--<module>-<method>' convention, which is 50 self.raw = api.m.raw_io.output('.json')
97 # used in multiple places in the recipe ecosystem.
98 def __init__(self, name='output', flag='--output-json'):
99 self.name = name
100 self.flag = flag
101 self.output_file = None
102 super(JsonOutputPlaceholder, self).__init__() 51 super(JsonOutputPlaceholder, self).__init__()
103 52
104 def render(self, test_data): 53 def render(self, test):
105 items = [self.flag] 54 return self.raw.render(test)
106 if test_data is not None:
107 items.append('/path/to/tmp/json')
108 else: # pragma: no cover
109 json_output_fd, self.output_file = tempfile.mkstemp()
110 os.close(json_output_fd)
111 items.append(self.output_file)
112 return items
113 55
114 def step_finished(self, presentation, result_data, test_data): 56 def result(self, presentation, test):
115 assert not hasattr(result_data, self.name) 57 raw_data = self.raw.result(presentation, test)
116 if test_data is not None:
117 raw_data = json.dumps(test_data.pop(self.name, None))
118 else: # pragma: no cover
119 assert self.output_file is not None
120 with open(self.output_file, 'r') as f:
121 raw_data = f.read()
122 os.unlink(self.output_file)
123 58
124 valid = False 59 valid = False
60 ret = None
125 try: 61 try:
126 setattr(result_data, self.name, json.loads(raw_data)) 62 ret = json.loads(raw_data)
127 valid = True 63 valid = True
128 except ValueError: # pragma: no cover 64 except ValueError: # pragma: no cover
129 pass 65 pass
130 66
131 key = 'json.' + self.name + ('' if valid else ' (invalid)') 67 key = self.name + ('' if valid else ' (invalid)')
132 listio = StringListIO() 68 with contextlib.closing(StringListIO()) as listio:
133 json.dump(getattr(result_data, self.name), listio, indent=2, sort_keys=True) 69 json.dump(ret, listio, indent=2, sort_keys=True)
134 listio.close()
135 presentation.logs[key] = listio.lines 70 presentation.logs[key] = listio.lines
136 71
72 return ret
73
137 74
138 class TestResultsOutputPlaceholder(JsonOutputPlaceholder): 75 class TestResultsOutputPlaceholder(JsonOutputPlaceholder):
139 def __init__(self): 76 def result(self, presentation, test):
140 super(TestResultsOutputPlaceholder, self).__init__( 77 ret = super(TestResultsOutputPlaceholder, self).result(presentation, test)
141 name='test_results', flag='--json-test-results') 78 return TestResults(ret)
142
143 def step_finished(self, presentation, result_data, test_data):
144 super(TestResultsOutputPlaceholder, self).step_finished(
145 presentation, result_data, test_data)
146 result_data.test_results = TestResults(result_data.test_results)
147 79
148 80
149 class JsonApi(recipe_api.RecipeApi): 81 class JsonApi(recipe_api.RecipeApi):
150 def __init__(self, **kwargs): 82 def __init__(self, **kwargs):
151 super(JsonApi, self).__init__(**kwargs) 83 super(JsonApi, self).__init__(**kwargs)
152 self.loads = json.loads 84 self.loads = json.loads
153 @functools.wraps(json.dumps) 85 @functools.wraps(json.dumps)
154 def dumps(*args, **kwargs): 86 def dumps(*args, **kwargs):
155 kwargs['sort_keys'] = True 87 kwargs['sort_keys'] = True
156 return json.dumps(*args, **kwargs) 88 return json.dumps(*args, **kwargs)
157 self.dumps = dumps 89 self.dumps = dumps
158 90
91 @recipe_util.returns_placeholder
159 def input(self, data): 92 def input(self, data):
160 """A placeholder which will expand to a file path containing <data>.""" 93 """A placeholder which will expand to a file path containing <data>."""
161 return recipe_api.InputDataPlaceholder(self.dumps(data), '.json') 94 return self.m.raw_io.input(self.dumps(data), '.json')
162 95
163 @staticmethod 96 @recipe_util.returns_placeholder
164 def output(): 97 def output(self):
165 """A placeholder which will expand to '--output-json /tmp/file'.""" 98 """A placeholder which will expand to '--output-json /tmp/file'."""
166 return JsonOutputPlaceholder() 99 return JsonOutputPlaceholder(self)
167 100
168 @staticmethod 101 @recipe_util.returns_placeholder
169 def test_results(): 102 def test_results(self):
170 """A placeholder which will expand to '--json-test-results /tmp/file'. 103 """A placeholder which will expand to '--json-test-results /tmp/file'.
171 104
172 The test_results will be an instance of the TestResults class. 105 The test_results will be an instance of the TestResults class.
173 """ 106 """
174 return TestResultsOutputPlaceholder() 107 return TestResultsOutputPlaceholder(self)
175 108
176 def property_args(self): 109 def property_args(self):
177 """Return --build-properties and --factory-properties arguments. LEGACY! 110 """Return --build-properties and --factory-properties arguments. LEGACY!
178 111
179 Since properties is the merge of build_properties and factory_properties, 112 Since properties is the merge of build_properties and factory_properties,
180 pass the merged dict as both arguments. 113 pass the merged dict as both arguments.
181 114
182 It's vastly preferable to have your recipe only pass the bare minimum 115 It's vastly preferable to have your recipe only pass the bare minimum
183 of arguments to steps. Passing property objects obscures the data that 116 of arguments to steps. Passing property objects obscures the data that
184 the script actually consumes from the property object. 117 the script actually consumes from the property object.
185 """ 118 """
186 prop_str = self.dumps(dict(self.m.properties)) 119 prop_str = self.dumps(dict(self.m.properties))
187 return [ 120 return [
188 '--factory-properties', prop_str, 121 '--factory-properties', prop_str,
189 '--build-properties', prop_str 122 '--build-properties', prop_str
190 ] 123 ]
191
OLDNEW
« no previous file with comments | « scripts/slave/recipe_modules/json/__init__.py ('k') | scripts/slave/recipe_modules/json/test_api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698