| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Unit tests for buildbot_json.py.""" |
| |
| import json |
| import logging |
| import os |
| import cStringIO |
| import StringIO |
| import sys |
| import unittest |
| import urllib |
| |
| ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| sys.path.insert(0, os.path.join(ROOT_DIR, '..')) |
| |
| import find_depot_tools # pylint: disable=W0611 |
| from testing_support import auto_stub |
| |
| # in tests/ |
| import reduce_test_data # pylint: disable=F0401 |
| |
| # In root |
| import buildbot_json |
| |
| |
| class BuildbotJsonTest(auto_stub.TestCase): |
| def setUp(self): |
| super(BuildbotJsonTest, self).setUp() |
| # Default mock. |
| self.old_urlopen = self.mock(urllib, 'urlopen', self.mockurlopen) |
| self.mock(sys, 'stderr', cStringIO.StringIO()) |
| self.mock(sys, 'stdout', cStringIO.StringIO()) |
| self.mock(buildbot_json.time, 'time', lambda: 1325394000.01) |
| self.url = 'http://build.chromium.org/p/tryserver.chromium' |
| self.datadir = os.path.join(ROOT_DIR, 'data') |
| if not os.path.isdir(self.datadir): |
| os.mkdir(self.datadir) |
| self.test_id = self.id().split('BuildbotJsonTest.', 1)[1] |
| self.filepath = os.path.join(self.datadir, self.test_id) + '.json' |
| self.queue = [] |
| self.training = False |
| if os.path.isfile(self.filepath): |
| self.queue = json.load(open(self.filepath)) |
| # Auto upgrade old data. |
| for i in xrange(len(self.queue)): |
| url = self.queue[i][0] |
| if not url.endswith('filter=1'): |
| if '?' in url: |
| url += '&filter=1' |
| else: |
| url += '?filter=1' |
| self.queue[i][0] = url |
| logging.warn('Auto-convert to training because missing filter=1.') |
| self.training = True |
| self.queue_index = 0 |
| self.reducer = reduce_test_data.Filterer() |
| |
| def tearDown(self): |
| try: |
| if not self.has_failed(): |
| if self.queue_index < len(self.queue): |
| self.queue = self.queue[:self.queue_index] |
| logging.warning('Auto-convert to training because of queue overflow') |
| self.training = True |
| if self.training: |
| json.dump(self.queue, open(self.filepath, 'w'), separators=(',',':')) |
| self.assertEqual(self.queue_index, len(self.queue)) |
| self.assertOut('stderr', '') |
| self.assertOut('stdout', '') |
| else: |
| if self.training: |
| logging.error('Not saving data even if in training mode.') |
| finally: |
| # Make sure the super class tearDown() function is called so stubs are |
| # removed. |
| super(BuildbotJsonTest, self).tearDown() |
| if self.training: |
| self.fail( |
| 'Don\'t worry, it\'s just updating internal files. Please run ' |
| 'again.\n%s' % '\n'.join(q[0] for q in self.queue)) |
| |
| def assertOut(self, out, expected): |
| """Check stderr/stdout and resets it.""" |
| self.assertEqual(str(expected), str(getattr(sys, out).getvalue())) |
| self.mock(sys, out, cStringIO.StringIO()) |
| |
| def mockurlopen(self, url): |
| self.assertTrue(self.queue_index <= len(self.queue)) |
| if self.queue_index != len(self.queue): |
| expected_url, data = self.queue[self.queue_index] |
| if url != expected_url: |
| logging.warn( |
| 'Auto-convert to training because %s != %s.' % (url, expected_url)) |
| self.training = True |
| # Delete the remainder of the queue. |
| self.queue = self.queue[:self.queue_index] |
| |
| if self.queue_index == len(self.queue): |
| data = self.old_urlopen(url).read() |
| self.training = True |
| |
| # Re-filter it. |
| try: |
| data = json.loads(data) |
| except ValueError: |
| self.fail('Failed to decode %s' % url) |
| expected_url, new_data = self.reducer.filter_response(url, data) |
| assert new_data |
| new_data_json = json.dumps(new_data, separators=(',',':')) |
| |
| if self.queue_index == len(self.queue): |
| self.queue.append((url, new_data_json)) |
| elif new_data != data: |
| logging.warn( |
| 'Auto-convert to training because url %s\n%s != %s.' % ( |
| url, data, new_data)) |
| self.queue[self.queue_index] = [url, new_data_json] |
| self.training = True |
| channel = StringIO.StringIO(new_data_json) |
| channel.headers = '<mocked headers>' |
| self.queue_index += 1 |
| return channel |
| |
| def testCommands(self): |
| # Assert no new command was added, otherwise a test needs to be written. |
| expected = [ |
| 'busy', |
| 'builds', |
| 'count', |
| 'current', |
| 'disconnected', |
| 'help', |
| 'idle', |
| 'interactive', |
| 'last_failure', |
| 'pending', |
| 'run', |
| ] |
| actual = [i[3:] for i in dir(buildbot_json) if i.startswith('CMD')] |
| self.assertEqual(sorted(expected), sorted(actual)) |
| for i in actual: |
| self.assertTrue(hasattr(self, 'testCMD' + i)) |
| |
| def testCMDbusy(self): |
| parser = buildbot_json.gen_parser() |
| self.assertEqual( |
| 0, |
| buildbot_json.CMDbusy(parser, [self.url, '-b', 'linux'])) |
| filepath = os.path.join(self.datadir, self.test_id) + '_expected.txt' |
| if self.training or not os.path.isfile(filepath): |
| # pylint: disable=E1101 |
| json.dump(sys.stdout.getvalue(), open(filepath, 'w')) |
| expected = json.load(open(filepath)) |
| self.assertOut('stdout', expected) |
| |
| def testCMDbuilds(self): |
| parser = buildbot_json.gen_parser() |
| self.assertEqual( |
| 0, |
| buildbot_json.CMDbuilds( |
| parser, [self.url, '-b', 'linux', '-s', 'vm146-m4', '-q'])) |
| filepath = os.path.join(self.datadir, self.test_id) + '_expected.txt' |
| if self.training or not os.path.isfile(filepath): |
| # pylint: disable=E1101 |
| json.dump(sys.stdout.getvalue(), open(filepath, 'w')) |
| expected = json.load(open(filepath)) |
| self.assertOut('stdout', expected) |
| |
| def testCMDcount(self): |
| self.mock(buildbot_json.time, 'time', lambda: 1348166285.56) |
| parser = buildbot_json.gen_parser() |
| self.assertEqual( |
| 0, |
| buildbot_json.CMDcount( |
| parser, [self.url, '-b', 'linux', '-o' '360'])) |
| filepath = os.path.join(self.datadir, self.test_id) + '_expected.txt' |
| if self.training or not os.path.isfile(filepath): |
| # pylint: disable=E1101 |
| json.dump(sys.stdout.getvalue(), open(filepath, 'w')) |
| expected = json.load(open(filepath)) |
| self.assertOut('stdout', expected) |
| |
| def testCMDdisconnected(self): |
| parser = buildbot_json.gen_parser() |
| self.assertEqual( |
| 0, |
| buildbot_json.CMDdisconnected(parser, [self.url])) |
| self.assertOut( |
| 'stdout', |
| 'vm112-m4\nvm122-m4\nvm124-m4\nvm131-m4\nvm134-m4\nvm139-m4\nvm143-m4\n' |
| 'vm146-m4\nvm157-m4\nvm162-m4\nvm165-m4\nvm60-m4\nvm62-m4\nvm64-m4\n') |
| |
| def testCMDhelp(self): |
| parser = buildbot_json.gen_parser() |
| self.assertEqual(0, buildbot_json.CMDhelp(parser, [])) |
| # No need to check exact output here. |
| # pylint: disable=E1101 |
| self.assertTrue( |
| 'show program\'s version number and exit\n' in sys.stdout.getvalue()) |
| self.mock(sys, 'stdout', cStringIO.StringIO()) |
| |
| def testCMDidle(self): |
| parser = buildbot_json.gen_parser() |
| self.assertEqual( |
| 0, |
| buildbot_json.CMDidle(parser, [self.url, '--builder', 'linux_clang'])) |
| self.assertOut( |
| 'stdout', 'Builder linux_clang: vm104-m4, vm113-m4, vm165-m4\n') |
| |
| def testCMDinteractive(self): |
| self.mock(sys, 'stdin', cStringIO.StringIO('exit()')) |
| parser = buildbot_json.gen_parser() |
| try: |
| # TODO(maruel): Real testing. |
| buildbot_json.CMDinteractive(parser, [self.url]) |
| self.fail() |
| except SystemExit: |
| pass |
| self.assertOut( |
| 'stderr', |
| 'Buildbot interactive console for "http://build.chromium.org' |
| '/p/tryserver.chromium".\nHint: Start with typing: ' |
| '\'buildbot.printable_attributes\' or \'print str(buildbot)\' to ' |
| 'explore.\n') |
| self.assertOut('stdout', '>>> ') |
| |
| def testCMDlast_failure(self): |
| parser = buildbot_json.gen_parser() |
| self.assertEqual( |
| 0, |
| buildbot_json.CMDlast_failure( |
| parser, [self.url, '-b', 'linux', '--step', 'compile'])) |
| self.assertOut( |
| 'stdout', |
| '27369 on vm136-m4: blame:jam@chromium.org\n' |
| '27367 on vm158-m4: blame:jam@chromium.org\n') |
| |
| def testCMDpending(self): |
| parser = buildbot_json.gen_parser() |
| self.assertEqual(0, buildbot_json.CMDpending(parser, [self.url])) |
| self.assertOut('stdout', |
| "Builder linux_touch: 2\n" |
| " revision: HEAD\n change:\n comment: u''\n" |
| " who: saintlou@google.com\n revision: HEAD\n change:\n" |
| " comment: u''\n who: saintlou@google.com\n") |
| |
| def testCMDcurrent(self): |
| parser = buildbot_json.gen_parser() |
| self.assertEqual(0, buildbot_json.CMDcurrent(parser, [self.url])) |
| filepath = os.path.join(self.datadir, self.test_id) + '_expected.txt' |
| if self.training or not os.path.isfile(filepath): |
| # pylint: disable=E1101 |
| json.dump(sys.stdout.getvalue(), open(filepath, 'w')) |
| expected = json.load(open(filepath)) |
| self.assertOut('stdout', expected) |
| |
| def testCMDrun(self): |
| parser = buildbot_json.gen_parser() |
| self.assertEqual( |
| 0, |
| buildbot_json.CMDrun( |
| parser, [self.url, "print '\\n'.join(buildbot.builders.keys)"])) |
| self.assertOut('stdout', 'linux\nlinux_clang\nlinux_touch\n') |
| |
| def testCurrentBuilds(self): |
| b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') |
| actual = [] |
| for builder in b.builders: |
| self.assertEqual([], list(builder.current_builds.cached_children)) |
| i = 0 |
| last_build = None |
| for c in builder.current_builds: |
| self.assertEqual(builder, c.builder) |
| actual.append(str(c)) |
| i += 1 |
| last_build = c |
| if i: |
| self.assertEqual(last_build.number, builder.builds[-1].number) |
| self.assertEqual(i, len(list(builder.current_builds.cached_children))) |
| builder.current_builds.discard() |
| self.assertEqual([], list(builder.current_builds.cached_children)) |
| |
| filepath = os.path.join(self.datadir, self.test_id) + '_expected.json' |
| if self.training or not os.path.isfile(filepath): |
| json.dump(actual, open(filepath, 'w')) |
| expected = json.load(open(filepath)) |
| self.assertEqual(expected, actual) |
| |
| def test_builds_reverse(self): |
| # Check the 2 last builds from 'linux' using iterall() instead of |
| # __iter__(). The test also confirms that the build object itself is not |
| # loaded. |
| b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') |
| actual = [] |
| for b in b.builders['linux'].builds.iterall(): |
| actual.append(b.number) |
| # When using iterall() the Build data is delay loaded: |
| assert b._data is None # pylint: disable=W0212 |
| if len(actual) == 2: |
| break |
| |
| filepath = os.path.join(self.datadir, self.test_id) + '_expected.json' |
| if self.training or not os.path.isfile(filepath): |
| json.dump(actual, open(filepath, 'w')) |
| expected = json.load(open(filepath)) |
| self.assertEqual(expected, actual) |
| |
| def test_build_results(self): |
| b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') |
| # builds.data['results'] is not present. |
| self.assertEqual( |
| buildbot_json.SUCCESS, b.builders['linux_clang'].builds[1638].result) |
| self.assertEqual( |
| buildbot_json.SUCCESS, |
| b.builders['linux_clang'].builds[1638].steps[0].result) |
| |
| def test_build_steps_keys(self): |
| b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') |
| build = b.builders['linux_clang'].builds[1638] |
| #self.assertEqual([0, 1, 2, 3], build.steps.keys) |
| |
| # Grab cached version. There is none. |
| actual = [step for step in build.steps.cached_children] |
| self.assertEqual([], actual) |
| |
| # Force load. |
| actual = [step for step in build.steps] |
| self.assertEqual( |
| [buildbot_json.SUCCESS] * 4, [step.result for step in actual]) |
| self.assertEqual( |
| [True] * 4, [step.simplified_result for step in actual]) |
| self.assertEqual(4, len(actual)) |
| |
| # Grab cached version. |
| actual = [step for step in build.steps.cached_children] |
| self.assertEqual( |
| [buildbot_json.SUCCESS] * 4, [step.result for step in actual]) |
| self.assertEqual(4, len(actual)) |
| |
| def test_repr(self): |
| b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') |
| self.assertEqual('<Builder key=linux>', repr(b.builders['linux'])) |
| self.assertEqual("<Builders keys=['linux']>", repr(b.builders)) |
| |
| def test_refresh(self): |
| b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') |
| self.assertEqual(True, b.refresh()) |
| |
| def test_build_step_cached_data(self): |
| b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') |
| build = 30157 |
| self.assertEqual( |
| None, b.builders['linux'].current_builds[build].steps[0].cached_data) |
| b.builders['linux'].current_builds[build].steps[0].cache() |
| self.assertEqual( |
| 'update_scripts', |
| b.builders['linux'].current_builds[build].steps[0].name) |
| self.assertEqual( |
| ['browser_tests', 'ui_tests'], |
| b.builders['linux'].current_builds[build].steps.failed) |
| self.assertEqual( |
| 2, |
| b.builders['linux'].current_builds[build].steps[2 |
| ].cached_data['step_number']) |
| b.refresh() |
| # cache_keys() does the same thing as cache(). |
| b.builders['linux'].current_builds[build].steps.cache_keys() |
| |
| def test_contains(self): |
| b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') |
| self.assertTrue('linux' in b.builders) |
| self.assertEqual(3, len(list(b.builders.cached_children))) |
| try: |
| # The dereference of an invalid key when keys are cached will throw an |
| # exception. |
| # pylint: disable=W0104 |
| b.builders['non_existent'] |
| self.fail() |
| except KeyError: |
| pass |
| |
| def test_slaves(self): |
| b = buildbot_json.Buildbot('http://build.chromium.org/p/tryserver.chromium') |
| self.assertEqual(11, len(b.slaves.names)) |
| self.assertEqual(False, b.slaves['mini34-m4'].connected) |
| |
| def test_build_revision(self): |
| class Root(object): |
| @staticmethod |
| def read(_): |
| return {'sourceStamp': {'revision': 321}} |
| build = buildbot_json.Build(Root(), '123', None) |
| self.assertEqual(321, build.revision) |
| |
| def test_build_revision_none(self): |
| class Root(object): |
| @staticmethod |
| def read(_): |
| return {} |
| build = buildbot_json.Build(Root(), '123', None) |
| self.assertEqual(None, build.revision) |
| |
| def test_build_duration(self): |
| class Root(object): |
| @staticmethod |
| def read(_): |
| return {'times': [3, 15]} |
| build = buildbot_json.Build(Root(), '123', None) |
| self.assertEqual(12, build.duration) |
| self.assertEqual(3, build.start_time) |
| self.assertEqual(15, build.end_time) |
| |
| def test_build_duration_none(self): |
| class Root(object): |
| @staticmethod |
| def read(_): |
| return {} |
| build = buildbot_json.Build(Root(), '123', None) |
| self.assertEqual(None, build.duration) |
| self.assertEqual(None, build.start_time) |
| self.assertEqual(None, build.end_time) |
| |
| def test_build_steps_names(self): |
| class Root(object): |
| @staticmethod |
| def read(url): # pylint: disable=E0213 |
| self.assertEqual('123', url) |
| return {'steps': [{'name': 'a'}, {'name': 'b'}]} |
| build = buildbot_json.Build(Root(), '123', None) |
| self.assertEqual(['a', 'b'], build.steps.keys) |
| |
| def test_build_step_duration(self): |
| class Root(object): |
| @staticmethod |
| def read(_): |
| return {'steps': [{'times': [3, 15], 'isStarted': True}]} |
| build = buildbot_json.Build(Root(), '123', None) |
| build_step = buildbot_json.BuildStep(buildbot_json.BuildSteps(build), 0) |
| self.assertEqual(12, build_step.duration) |
| self.assertEqual(True, build_step.is_running) |
| self.assertEqual(True, build_step.is_started) |
| self.assertEqual(False, build_step.is_finished) |
| |
| def test_build_step_duration_none(self): |
| class Root(object): |
| @staticmethod |
| def read(_): |
| return {'steps': [{}]} |
| build = buildbot_json.Build(Root(), '123', None) |
| build_step = buildbot_json.BuildStep(buildbot_json.BuildSteps(build), 0) |
| self.assertEqual(None, build_step.duration) |
| |
| |
| if __name__ == '__main__': |
| logging.basicConfig(level= |
| [logging.WARN, logging.INFO, logging.DEBUG][min(2, sys.argv.count('-v'))]) |
| unittest.main() |