OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 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 """Unit tests for buildbot_json.py.""" | |
7 | |
8 import json | |
9 import logging | |
10 import os | |
11 import cStringIO | |
12 import StringIO | |
13 import sys | |
14 import unittest | |
15 import urllib | |
16 | |
17 ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) | |
18 sys.path.insert(0, os.path.join(ROOT_DIR, '..')) | |
19 | |
20 import find_depot_tools # pylint: disable=W0611 | |
21 from testing_support import auto_stub | |
22 | |
23 # in tests/ | |
24 import reduce_test_data # pylint: disable=F0401 | |
25 | |
26 # In root | |
27 import buildbot_json | |
28 | |
29 | |
30 class BuildbotJsonTest(auto_stub.TestCase): | |
31 def setUp(self): | |
32 super(BuildbotJsonTest, self).setUp() | |
33 # Default mock. | |
34 self.old_urlopen = self.mock(urllib, 'urlopen', self.mockurlopen) | |
35 self.mock(sys, 'stderr', cStringIO.StringIO()) | |
36 self.mock(sys, 'stdout', cStringIO.StringIO()) | |
37 self.mock(buildbot_json.time, 'time', lambda: 1325394000.01) | |
38 self.url = 'http://build.chromium.org/p/tryserver.chromium' | |
39 self.datadir = os.path.join(ROOT_DIR, 'data') | |
40 if not os.path.isdir(self.datadir): | |
41 os.mkdir(self.datadir) | |
42 self.test_id = self.id().split('BuildbotJsonTest.', 1)[1] | |
43 self.filepath = os.path.join(self.datadir, self.test_id) + '.json' | |
44 self.queue = [] | |
45 self.training = False | |
46 if os.path.isfile(self.filepath): | |
47 self.queue = json.load(open(self.filepath)) | |
48 # Auto upgrade old data. | |
49 for i in xrange(len(self.queue)): | |
50 url = self.queue[i][0] | |
51 if not url.endswith('filter=1'): | |
52 if '?' in url: | |
53 url += '&filter=1' | |
54 else: | |
55 url += '?filter=1' | |
56 self.queue[i][0] = url | |
57 logging.warn('Auto-convert to training because missing filter=1.') | |
58 self.training = True | |
59 self.queue_index = 0 | |
60 self.reducer = reduce_test_data.Filterer() | |
61 | |
62 def tearDown(self): | |
63 try: | |
64 if not self.has_failed(): | |
65 if self.queue_index < len(self.queue): | |
66 self.queue = self.queue[:self.queue_index] | |
67 logging.warning('Auto-convert to training because of queue overflow') | |
68 self.training = True | |
69 if self.training: | |
70 json.dump(self.queue, open(self.filepath, 'w'), separators=(',',':')) | |
71 self.assertEqual(self.queue_index, len(self.queue)) | |
72 self.assertOut('stderr', '') | |
73 self.assertOut('stdout', '') | |
74 else: | |
75 if self.training: | |
76 logging.error('Not saving data even if in training mode.') | |
77 finally: | |
78 # Make sure the super class tearDown() function is called so stubs are | |
79 # removed. | |
80 super(BuildbotJsonTest, self).tearDown() | |
81 if self.training: | |
82 self.fail( | |
83 'Don\'t worry, it\'s just updating internal files. Please run ' | |
84 'again.\n%s' % '\n'.join(q[0] for q in self.queue)) | |
85 | |
86 def assertOut(self, out, expected): | |
87 """Check stderr/stdout and resets it.""" | |
88 self.assertEqual(str(expected), str(getattr(sys, out).getvalue())) | |
89 self.mock(sys, out, cStringIO.StringIO()) | |
90 | |
91 def mockurlopen(self, url): | |
92 self.assertTrue(self.queue_index <= len(self.queue)) | |
93 if self.queue_index != len(self.queue): | |
94 expected_url, data = self.queue[self.queue_index] | |
95 if url != expected_url: | |
96 logging.warn( | |
97 'Auto-convert to training because %s != %s.' % (url, expected_url)) | |
98 self.training = True | |
99 # Delete the remainder of the queue. | |
100 self.queue = self.queue[:self.queue_index] | |
101 | |
102 if self.queue_index == len(self.queue): | |
103 data = self.old_urlopen(url).read() | |
104 self.training = True | |
105 | |
106 # Re-filter it. | |
107 try: | |
108 data = json.loads(data) | |
109 except ValueError: | |
110 self.fail('Failed to decode %s' % url) | |
111 expected_url, new_data = self.reducer.filter_response(url, data) | |
112 assert new_data | |
113 new_data_json = json.dumps(new_data, separators=(',',':')) | |
114 | |
115 if self.queue_index == len(self.queue): | |
116 self.queue.append((url, new_data_json)) | |
117 elif new_data != data: | |
118 logging.warn( | |
119 'Auto-convert to training because url %s\n%s != %s.' % ( | |
120 url, data, new_data)) | |
121 self.queue[self.queue_index] = [url, new_data_json] | |
122 self.training = True | |
123 channel = StringIO.StringIO(new_data_json) | |
124 channel.headers = '<mocked headers>' | |
125 self.queue_index += 1 | |
126 return channel | |
127 | |
128 def testCommands(self): | |
129 # Assert no new command was added, otherwise a test needs to be written. | |
130 expected = [ | |
131 'busy', | |
132 'builds', | |
133 'count', | |
134 'current', | |
135 'disconnected', | |
136 'help', | |
137 'idle', | |
138 'interactive', | |
139 'last_failure', | |
140 'pending', | |
141 'run', | |
142 ] | |
143 actual = [i[3:] for i in dir(buildbot_json) if i.startswith('CMD')] | |
144 self.assertEqual(sorted(expected), sorted(actual)) | |
145 for i in actual: | |
146 self.assertTrue(hasattr(self, 'testCMD' + i)) | |
147 | |
148 def testCMDbusy(self): | |
149 parser = buildbot_json.gen_parser() | |
150 self.assertEqual( | |
151 0, | |
152 buildbot_json.CMDbusy(parser, [self.url, '-b', 'linux'])) | |
153 filepath = os.path.join(self.datadir, self.test_id) + '_expected.txt' | |
154 if self.training or not os.path.isfile(filepath): | |
155 # pylint: disable=E1101 | |
156 json.dump(sys.stdout.getvalue(), open(filepath, 'w')) | |
157 expected = json.load(open(filepath)) | |
158 self.assertOut('stdout', expected) | |
159 | |
160 def testCMDbuilds(self): | |
161 parser = buildbot_json.gen_parser() | |
162 self.assertEqual( | |
163 0, | |
164 buildbot_json.CMDbuilds( | |
165 parser, [self.url, '-b', 'linux', '-s', 'vm146-m4', '-q'])) | |
166 filepath = os.path.join(self.datadir, self.test_id) + '_expected.txt' | |
167 if self.training or not os.path.isfile(filepath): | |
168 # pylint: disable=E1101 | |
169 json.dump(sys.stdout.getvalue(), open(filepath, 'w')) | |
170 expected = json.load(open(filepath)) | |
171 self.assertOut('stdout', expected) | |
172 | |
173 def testCMDcount(self): | |
174 self.mock(buildbot_json.time, 'time', lambda: 1348166285.56) | |
175 parser = buildbot_json.gen_parser() | |
176 self.assertEqual( | |
177 0, | |
178 buildbot_json.CMDcount( | |
179 parser, [self.url, '-b', 'linux', '-o' '360'])) | |
180 filepath = os.path.join(self.datadir, self.test_id) + '_expected.txt' | |
181 if self.training or not os.path.isfile(filepath): | |
182 # pylint: disable=E1101 | |
183 json.dump(sys.stdout.getvalue(), open(filepath, 'w')) | |
184 expected = json.load(open(filepath)) | |
185 self.assertOut('stdout', expected) | |
186 | |
187 def testCMDdisconnected(self): | |
188 parser = buildbot_json.gen_parser() | |
189 self.assertEqual( | |
190 0, | |
191 buildbot_json.CMDdisconnected(parser, [self.url])) | |
192 self.assertOut( | |
193 'stdout', | |
194 'vm112-m4\nvm122-m4\nvm124-m4\nvm131-m4\nvm134-m4\nvm139-m4\nvm143-m4\n' | |
195 'vm146-m4\nvm157-m4\nvm162-m4\nvm165-m4\nvm60-m4\nvm62-m4\nvm64-m4\n') | |
196 | |
197 def testCMDhelp(self): | |
198 parser = buildbot_json.gen_parser() | |
199 self.assertEqual(0, buildbot_json.CMDhelp(parser, [])) | |
200 # No need to check exact output here. | |
201 # pylint: disable=E1101 | |
202 self.assertTrue( | |
203 'show program\'s version number and exit\n' in sys.stdout.getvalue()) | |
204 self.mock(sys, 'stdout', cStringIO.StringIO()) | |
205 | |
206 def testCMDidle(self): | |
207 parser = buildbot_json.gen_parser() | |
208 self.assertEqual( | |
209 0, | |
210 buildbot_json.CMDidle(parser, [self.url, '--builder', 'linux_clang'])) | |
211 self.assertOut( | |
212 'stdout', 'Builder linux_clang: vm104-m4, vm113-m4, vm165-m4\n') | |
213 | |
214 def testCMDinteractive(self): | |
215 self.mock(sys, 'stdin', cStringIO.StringIO('exit()')) | |
216 parser = buildbot_json.gen_parser() | |
217 try: | |
218 # TODO(maruel): Real testing. | |
219 buildbot_json.CMDinteractive(parser, [self.url]) | |
220 self.fail() | |
221 except SystemExit: | |
222 pass | |
223 self.assertOut( | |
224 'stderr', | |
225 'Buildbot interactive console for "http://build.chromium.org' | |
226 '/p/tryserver.chromium".\nHint: Start with typing: ' | |
227 '\'buildbot.printable_attributes\' or \'print str(buildbot)\' to ' | |
228 'explore.\n') | |
229 self.assertOut('stdout', '>>> ') | |
230 | |
231 def testCMDlast_failure(self): | |
232 parser = buildbot_json.gen_parser() | |
233 self.assertEqual( | |
234 0, | |
235 buildbot_json.CMDlast_failure( | |
236 parser, [self.url, '-b', 'linux', '--step', 'compile'])) | |
237 self.assertOut( | |
238 'stdout', | |
239 '27369 on vm136-m4: blame:jam@chromium.org\n' | |
240 '27367 on vm158-m4: blame:jam@chromium.org\n') | |
241 | |
242 def testCMDpending(self): | |
243 parser = buildbot_json.gen_parser() | |
244 self.assertEqual(0, buildbot_json.CMDpending(parser, [self.url])) | |
245 self.assertOut('stdout', | |
246 "Builder linux_touch: 2\n" | |
247 " revision: HEAD\n change:\n comment: u''\n" | |
248 " who: saintlou@google.com\n revision: HEAD\n change:\n" | |
249 " comment: u''\n who: saintlou@google.com\n") | |
250 | |
251 def testCMDcurrent(self): | |
252 parser = buildbot_json.gen_parser() | |
253 self.assertEqual(0, buildbot_json.CMDcurrent(parser, [self.url])) | |
254 filepath = os.path.join(self.datadir, self.test_id) + '_expected.txt' | |
255 if self.training or not os.path.isfile(filepath): | |
256 # pylint: disable=E1101 | |
257 json.dump(sys.stdout.getvalue(), open(filepath, 'w')) | |
258 expected = json.load(open(filepath)) | |
259 self.assertOut('stdout', expected) | |
260 | |
261 def testCMDrun(self): | |
262 parser = buildbot_json.gen_parser() | |
263 self.assertEqual( | |
264 0, | |
265 buildbot_json.CMDrun( | |
266 parser, [self.url, "print '\\n'.join(buildbot.builders.keys)"])) | |
267 self.assertOut('stdout', 'linux\nlinux_clang\nlinux_touch\n') | |
268 | |
269 def testCurrentBuilds(self): | |
270 b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') | |
271 actual = [] | |
272 for builder in b.builders: | |
273 self.assertEqual([], list(builder.current_builds.cached_children)) | |
274 i = 0 | |
275 last_build = None | |
276 for c in builder.current_builds: | |
277 self.assertEqual(builder, c.builder) | |
278 actual.append(str(c)) | |
279 i += 1 | |
280 last_build = c | |
281 if i: | |
282 self.assertEqual(last_build.number, builder.builds[-1].number) | |
283 self.assertEqual(i, len(list(builder.current_builds.cached_children))) | |
284 builder.current_builds.discard() | |
285 self.assertEqual([], list(builder.current_builds.cached_children)) | |
286 | |
287 filepath = os.path.join(self.datadir, self.test_id) + '_expected.json' | |
288 if self.training or not os.path.isfile(filepath): | |
289 json.dump(actual, open(filepath, 'w')) | |
290 expected = json.load(open(filepath)) | |
291 self.assertEqual(expected, actual) | |
292 | |
293 def test_builds_reverse(self): | |
294 # Check the 2 last builds from 'linux' using iterall() instead of | |
295 # __iter__(). The test also confirms that the build object itself is not | |
296 # loaded. | |
297 b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') | |
298 actual = [] | |
299 for b in b.builders['linux'].builds.iterall(): | |
300 actual.append(b.number) | |
301 # When using iterall() the Build data is delay loaded: | |
302 assert b._data is None # pylint: disable=W0212 | |
303 if len(actual) == 2: | |
304 break | |
305 | |
306 filepath = os.path.join(self.datadir, self.test_id) + '_expected.json' | |
307 if self.training or not os.path.isfile(filepath): | |
308 json.dump(actual, open(filepath, 'w')) | |
309 expected = json.load(open(filepath)) | |
310 self.assertEqual(expected, actual) | |
311 | |
312 def test_build_results(self): | |
313 b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') | |
314 # builds.data['results'] is not present. | |
315 self.assertEqual( | |
316 buildbot_json.SUCCESS, b.builders['linux_clang'].builds[1638].result) | |
317 self.assertEqual( | |
318 buildbot_json.SUCCESS, | |
319 b.builders['linux_clang'].builds[1638].steps[0].result) | |
320 | |
321 def test_build_steps_keys(self): | |
322 b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') | |
323 build = b.builders['linux_clang'].builds[1638] | |
324 #self.assertEqual([0, 1, 2, 3], build.steps.keys) | |
325 | |
326 # Grab cached version. There is none. | |
327 actual = [step for step in build.steps.cached_children] | |
328 self.assertEqual([], actual) | |
329 | |
330 # Force load. | |
331 actual = [step for step in build.steps] | |
332 self.assertEqual( | |
333 [buildbot_json.SUCCESS] * 4, [step.result for step in actual]) | |
334 self.assertEqual( | |
335 [True] * 4, [step.simplified_result for step in actual]) | |
336 self.assertEqual(4, len(actual)) | |
337 | |
338 # Grab cached version. | |
339 actual = [step for step in build.steps.cached_children] | |
340 self.assertEqual( | |
341 [buildbot_json.SUCCESS] * 4, [step.result for step in actual]) | |
342 self.assertEqual(4, len(actual)) | |
343 | |
344 def test_repr(self): | |
345 b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') | |
346 self.assertEqual('<Builder key=linux>', repr(b.builders['linux'])) | |
347 self.assertEqual("<Builders keys=['linux']>", repr(b.builders)) | |
348 | |
349 def test_refresh(self): | |
350 b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') | |
351 self.assertEqual(True, b.refresh()) | |
352 | |
353 def test_build_step_cached_data(self): | |
354 b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') | |
355 build = 30157 | |
356 self.assertEqual( | |
357 None, b.builders['linux'].current_builds[build].steps[0].cached_data) | |
358 b.builders['linux'].current_builds[build].steps[0].cache() | |
359 self.assertEqual( | |
360 'update_scripts', | |
361 b.builders['linux'].current_builds[build].steps[0].name) | |
362 self.assertEqual( | |
363 ['browser_tests', 'ui_tests'], | |
364 b.builders['linux'].current_builds[build].steps.failed) | |
365 self.assertEqual( | |
366 2, | |
367 b.builders['linux'].current_builds[build].steps[2 | |
368 ].cached_data['step_number']) | |
369 b.refresh() | |
370 # cache_keys() does the same thing as cache(). | |
371 b.builders['linux'].current_builds[build].steps.cache_keys() | |
372 | |
373 def test_contains(self): | |
374 b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') | |
375 self.assertTrue('linux' in b.builders) | |
376 self.assertEqual(3, len(list(b.builders.cached_children))) | |
377 try: | |
378 # The dereference of an invalid key when keys are cached will throw an | |
379 # exception. | |
380 # pylint: disable=W0104 | |
381 b.builders['non_existent'] | |
382 self.fail() | |
383 except KeyError: | |
384 pass | |
385 | |
386 def test_slaves(self): | |
387 b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') | |
388 self.assertEqual(11, len(b.slaves.names)) | |
389 self.assertEqual(False, b.slaves['mini34-m4'].connected) | |
390 | |
391 def test_build_revision(self): | |
392 class Root(object): | |
393 @staticmethod | |
394 def read(_): | |
395 return {'sourceStamp': {'revision': 321}} | |
396 build = buildbot_json.Build(Root(), '123', None) | |
397 self.assertEqual(321, build.revision) | |
398 | |
399 def test_build_revision_none(self): | |
400 class Root(object): | |
401 @staticmethod | |
402 def read(_): | |
403 return {} | |
404 build = buildbot_json.Build(Root(), '123', None) | |
405 self.assertEqual(None, build.revision) | |
406 | |
407 def test_build_duration(self): | |
408 class Root(object): | |
409 @staticmethod | |
410 def read(_): | |
411 return {'times': [3, 15]} | |
412 build = buildbot_json.Build(Root(), '123', None) | |
413 self.assertEqual(12, build.duration) | |
414 self.assertEqual(3, build.start_time) | |
415 self.assertEqual(15, build.end_time) | |
416 | |
417 def test_build_duration_none(self): | |
418 class Root(object): | |
419 @staticmethod | |
420 def read(_): | |
421 return {} | |
422 build = buildbot_json.Build(Root(), '123', None) | |
423 self.assertEqual(None, build.duration) | |
424 self.assertEqual(None, build.start_time) | |
425 self.assertEqual(None, build.end_time) | |
426 | |
427 def test_build_steps_names(self): | |
428 class Root(object): | |
429 @staticmethod | |
430 def read(url): # pylint: disable=E0213 | |
431 self.assertEqual('123', url) | |
432 return {'steps': [{'name': 'a'}, {'name': 'b'}]} | |
433 build = buildbot_json.Build(Root(), '123', None) | |
434 self.assertEqual(['a', 'b'], build.steps.keys) | |
435 | |
436 def test_build_step_duration(self): | |
437 class Root(object): | |
438 @staticmethod | |
439 def read(_): | |
440 return {'steps': [{'times': [3, 15], 'isStarted': True}]} | |
441 build = buildbot_json.Build(Root(), '123', None) | |
442 build_step = buildbot_json.BuildStep(buildbot_json.BuildSteps(build), 0) | |
443 self.assertEqual(12, build_step.duration) | |
444 self.assertEqual(True, build_step.is_running) | |
445 self.assertEqual(True, build_step.is_started) | |
446 self.assertEqual(False, build_step.is_finished) | |
447 | |
448 def test_build_step_duration_none(self): | |
449 class Root(object): | |
450 @staticmethod | |
451 def read(_): | |
452 return {'steps': [{}]} | |
453 build = buildbot_json.Build(Root(), '123', None) | |
454 build_step = buildbot_json.BuildStep(buildbot_json.BuildSteps(build), 0) | |
455 self.assertEqual(None, build_step.duration) | |
456 | |
457 | |
458 if __name__ == '__main__': | |
459 logging.basicConfig(level= | |
460 [logging.WARN, logging.INFO, logging.DEBUG][min(2, sys.argv.count('-v'))]) | |
461 unittest.main() | |
OLD | NEW |