J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 1 | # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | import StringIO |
| 6 | import json |
| 7 | import mox |
| 8 | import time |
| 9 | import unittest |
| 10 | import urllib2 |
| 11 | |
| 12 | import common |
| 13 | from autotest_lib.client.common_lib import global_config |
| 14 | from autotest_lib.server import site_utils |
| 15 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 16 | _DEADBUILD = 'deadboard-release/R33-4966.0.0' |
| 17 | _LIVEBUILD = 'liveboard-release/R32-4920.14.0' |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 18 | |
J. Richard Barnette | 183e429 | 2015-06-19 12:41:10 -0700 | [diff] [blame] | 19 | _STATUS_TEMPLATE = ''' |
| 20 | { |
| 21 | "username": "fizzbin@google.com", |
| 22 | "date": "2013-11-16 00:25:23.511208", |
| 23 | "message": "%s", |
| 24 | "can_commit_freely": %s, |
| 25 | "general_state": "%s" |
| 26 | } |
| 27 | ''' |
| 28 | |
| 29 | |
| 30 | def _make_status(message, can_commit, state): |
| 31 | return _STATUS_TEMPLATE % (message, can_commit, state) |
| 32 | |
| 33 | |
| 34 | def _make_open_status(message, state): |
| 35 | return _make_status(message, 'true', state) |
| 36 | |
| 37 | |
| 38 | def _make_closed_status(message): |
| 39 | return _make_status(message, 'false', 'closed') |
| 40 | |
| 41 | |
| 42 | def _make_deadbuild_status(message): |
| 43 | return _make_status(message, 'false', 'open') |
| 44 | |
| 45 | |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 46 | _OPEN_STATUS_VALUES = [ |
J. Richard Barnette | 183e429 | 2015-06-19 12:41:10 -0700 | [diff] [blame] | 47 | _make_open_status('Lab is up (cross your fingers)', 'open'), |
| 48 | _make_open_status('Lab is on fire', 'throttled'), |
| 49 | _make_open_status('Lab is up despite deadboard', 'open'), |
J. Richard Barnette | 7f215d3 | 2015-06-19 12:44:38 -0700 | [diff] [blame] | 50 | _make_open_status('Lab is up despite .*/R33-4966.0.0', 'open'), |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 51 | ] |
| 52 | |
| 53 | _CLOSED_STATUS_VALUES = [ |
J. Richard Barnette | 183e429 | 2015-06-19 12:41:10 -0700 | [diff] [blame] | 54 | _make_closed_status('Lab is down for spite'), |
| 55 | _make_closed_status('Lab is down even for [%s]' % _LIVEBUILD), |
| 56 | _make_closed_status('Lab is down even for [%s]' % _DEADBUILD), |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 57 | ] |
| 58 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 59 | _DEADBUILD_STATUS_VALUES = [ |
J. Richard Barnette | 183e429 | 2015-06-19 12:41:10 -0700 | [diff] [blame] | 60 | _make_deadbuild_status('Lab is up except for [deadboard-]'), |
J. Richard Barnette | 7f215d3 | 2015-06-19 12:44:38 -0700 | [diff] [blame] | 61 | _make_deadbuild_status('Lab is up except for [board- deadboard-]'), |
| 62 | _make_deadbuild_status('Lab is up except for [.*/R33-]'), |
J. Richard Barnette | 183e429 | 2015-06-19 12:41:10 -0700 | [diff] [blame] | 63 | _make_deadbuild_status('Lab is up except for [deadboard-.*/R33-]'), |
| 64 | _make_deadbuild_status('Lab is up except for [ deadboard-]'), |
| 65 | _make_deadbuild_status('Lab is up except for [deadboard- ]'), |
J. Richard Barnette | 7f215d3 | 2015-06-19 12:44:38 -0700 | [diff] [blame] | 66 | _make_deadbuild_status('Lab is up [first .*/R33- last]'), |
J. Richard Barnette | 183e429 | 2015-06-19 12:41:10 -0700 | [diff] [blame] | 67 | _make_deadbuild_status('liveboard is good, but [deadboard-] is bad'), |
| 68 | _make_deadbuild_status('Lab is up [deadboard- otherboard-]'), |
| 69 | _make_deadbuild_status('Lab is up [otherboard- deadboard-]'), |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 70 | ] |
| 71 | |
| 72 | |
| 73 | _FAKE_URL = 'ignore://not.a.url' |
| 74 | |
| 75 | |
| 76 | class _FakeURLResponse(object): |
| 77 | |
| 78 | """Everything needed to pretend to be a response from urlopen(). |
| 79 | |
| 80 | Creates a StringIO instance to handle the File operations. |
| 81 | |
| 82 | N.B. StringIO is lame: we can't inherit from it (super won't |
| 83 | work), and it doesn't implement __getattr__(), either. So, we |
| 84 | have to manually forward calls to the StringIO object. This |
| 85 | forwards only what empirical testing says is required; YMMV. |
| 86 | |
| 87 | """ |
| 88 | |
| 89 | def __init__(self, code, buffer): |
| 90 | self._stringio = StringIO.StringIO(buffer) |
| 91 | self._code = code |
| 92 | |
| 93 | |
| 94 | def read(self, size=-1): |
| 95 | """Standard file-like read operation. |
| 96 | |
| 97 | @param size size for read operation. |
| 98 | """ |
| 99 | return self._stringio.read(size) |
| 100 | |
| 101 | |
| 102 | def getcode(self): |
| 103 | """Get URL HTTP response code.""" |
| 104 | return self._code |
| 105 | |
| 106 | |
| 107 | class GetStatusTest(mox.MoxTestBase): |
| 108 | |
| 109 | """Test case for _get_lab_status(). |
| 110 | |
| 111 | We mock out dependencies on urllib2 and time.sleep(), and |
| 112 | confirm that the function returns the proper JSON representation |
| 113 | for a pre-defined response. |
| 114 | |
| 115 | """ |
| 116 | |
| 117 | def setUp(self): |
| 118 | super(GetStatusTest, self).setUp() |
| 119 | self.mox.StubOutWithMock(urllib2, 'urlopen') |
| 120 | self.mox.StubOutWithMock(time, 'sleep') |
| 121 | |
| 122 | |
| 123 | def test_success(self): |
| 124 | """Test that successful calls to urlopen() succeed.""" |
| 125 | json_string = _OPEN_STATUS_VALUES[0] |
| 126 | json_value = json.loads(json_string) |
| 127 | urllib2.urlopen(mox.IgnoreArg()).AndReturn( |
| 128 | _FakeURLResponse(200, json_string)) |
| 129 | self.mox.ReplayAll() |
| 130 | result = site_utils._get_lab_status(_FAKE_URL) |
| 131 | self.mox.VerifyAll() |
| 132 | self.assertEqual(json_value, result) |
| 133 | |
| 134 | |
| 135 | def test_retry_ioerror(self): |
| 136 | """Test that an IOError retries at least once.""" |
| 137 | json_string = _OPEN_STATUS_VALUES[0] |
| 138 | json_value = json.loads(json_string) |
| 139 | urllib2.urlopen(mox.IgnoreArg()).AndRaise( |
| 140 | IOError('Fake I/O error for a fake URL')) |
| 141 | time.sleep(mox.IgnoreArg()).AndReturn(None) |
| 142 | urllib2.urlopen(mox.IgnoreArg()).AndReturn( |
| 143 | _FakeURLResponse(200, json_string)) |
| 144 | self.mox.ReplayAll() |
| 145 | result = site_utils._get_lab_status(_FAKE_URL) |
| 146 | self.mox.VerifyAll() |
| 147 | self.assertEqual(json_value, result) |
| 148 | |
| 149 | |
| 150 | def test_retry_http_internal_error(self): |
| 151 | """Test that an HTTP error retries at least once.""" |
| 152 | json_string = _OPEN_STATUS_VALUES[0] |
| 153 | json_value = json.loads(json_string) |
| 154 | urllib2.urlopen(mox.IgnoreArg()).AndReturn( |
| 155 | _FakeURLResponse(500, '')) |
| 156 | time.sleep(mox.IgnoreArg()).AndReturn(None) |
| 157 | urllib2.urlopen(mox.IgnoreArg()).AndReturn( |
| 158 | _FakeURLResponse(200, json_string)) |
| 159 | self.mox.ReplayAll() |
| 160 | result = site_utils._get_lab_status(_FAKE_URL) |
| 161 | self.mox.VerifyAll() |
| 162 | self.assertEqual(json_value, result) |
| 163 | |
| 164 | |
| 165 | def test_failure_ioerror(self): |
| 166 | """Test that there's a failure if urlopen() never succeeds.""" |
| 167 | json_string = _OPEN_STATUS_VALUES[0] |
| 168 | json_value = json.loads(json_string) |
| 169 | for _ in range(site_utils._MAX_LAB_STATUS_ATTEMPTS): |
| 170 | urllib2.urlopen(mox.IgnoreArg()).AndRaise( |
| 171 | IOError('Fake I/O error for a fake URL')) |
| 172 | time.sleep(mox.IgnoreArg()).AndReturn(None) |
| 173 | self.mox.ReplayAll() |
| 174 | result = site_utils._get_lab_status(_FAKE_URL) |
| 175 | self.mox.VerifyAll() |
| 176 | self.assertEqual(None, result) |
| 177 | |
| 178 | |
| 179 | def test_failure_http_internal_error(self): |
| 180 | """Test that there's a failure for a permanent HTTP error.""" |
| 181 | json_string = _OPEN_STATUS_VALUES[0] |
| 182 | json_value = json.loads(json_string) |
| 183 | for _ in range(site_utils._MAX_LAB_STATUS_ATTEMPTS): |
| 184 | urllib2.urlopen(mox.IgnoreArg()).AndReturn( |
| 185 | _FakeURLResponse(404, 'Not here, never gonna be')) |
| 186 | time.sleep(mox.IgnoreArg()).InAnyOrder().AndReturn(None) |
| 187 | self.mox.ReplayAll() |
| 188 | result = site_utils._get_lab_status(_FAKE_URL) |
| 189 | self.mox.VerifyAll() |
| 190 | self.assertEqual(None, result) |
| 191 | |
| 192 | |
| 193 | class DecodeStatusTest(unittest.TestCase): |
| 194 | |
| 195 | """Test case for _decode_lab_status(). |
| 196 | |
| 197 | Testing covers three distinct possible states: |
| 198 | 1. Lab is up. All calls to _decode_lab_status() will |
| 199 | succeed without raising an exception. |
| 200 | 2. Lab is down. All calls to _decode_lab_status() will |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 201 | fail with TestLabException. |
| 202 | 3. Build disabled. Calls to _decode_lab_status() will |
| 203 | succeed, except that board `_DEADBUILD` will raise |
| 204 | TestLabException. |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 205 | |
| 206 | """ |
| 207 | |
| 208 | def _assert_lab_open(self, lab_status): |
| 209 | """Test that open status values are handled properly. |
| 210 | |
| 211 | Test that _decode_lab_status() succeeds when the lab status |
| 212 | is up. |
| 213 | |
| 214 | @param lab_status JSON value describing lab status. |
| 215 | |
| 216 | """ |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 217 | site_utils._decode_lab_status(lab_status, _LIVEBUILD) |
| 218 | site_utils._decode_lab_status(lab_status, _DEADBUILD) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 219 | |
| 220 | |
| 221 | def _assert_lab_closed(self, lab_status): |
| 222 | """Test that closed status values are handled properly. |
| 223 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 224 | Test that _decode_lab_status() raises TestLabException |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 225 | when the lab status is down. |
| 226 | |
| 227 | @param lab_status JSON value describing lab status. |
| 228 | |
| 229 | """ |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 230 | with self.assertRaises(site_utils.TestLabException): |
| 231 | site_utils._decode_lab_status(lab_status, _LIVEBUILD) |
| 232 | with self.assertRaises(site_utils.TestLabException): |
| 233 | site_utils._decode_lab_status(lab_status, _DEADBUILD) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 234 | |
| 235 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 236 | def _assert_lab_deadbuild(self, lab_status): |
| 237 | """Test that disabled builds are handled properly. |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 238 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 239 | Test that _decode_lab_status() raises TestLabException |
| 240 | for build `_DEADBUILD` and succeeds otherwise. |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 241 | |
| 242 | @param lab_status JSON value describing lab status. |
| 243 | |
| 244 | """ |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 245 | site_utils._decode_lab_status(lab_status, _LIVEBUILD) |
| 246 | with self.assertRaises(site_utils.TestLabException): |
| 247 | site_utils._decode_lab_status(lab_status, _DEADBUILD) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 248 | |
| 249 | |
| 250 | def _assert_lab_status(self, test_values, checker): |
| 251 | """General purpose test for _decode_lab_status(). |
| 252 | |
| 253 | Decode each JSON string in `test_values`, and call the |
| 254 | `checker` function to test the corresponding status is |
| 255 | correctly handled. |
| 256 | |
| 257 | @param test_values Array of JSON encoded strings representing |
| 258 | lab status. |
| 259 | @param checker Function to be called against each of the lab |
| 260 | status values in the `test_values` array. |
| 261 | |
| 262 | """ |
| 263 | for s in test_values: |
| 264 | lab_status = json.loads(s) |
| 265 | checker(lab_status) |
| 266 | |
| 267 | |
| 268 | def test_open_lab(self): |
| 269 | """Test that open lab status values are handled correctly.""" |
| 270 | self._assert_lab_status(_OPEN_STATUS_VALUES, |
| 271 | self._assert_lab_open) |
| 272 | |
| 273 | |
| 274 | def test_closed_lab(self): |
| 275 | """Test that closed lab status values are handled correctly.""" |
| 276 | self._assert_lab_status(_CLOSED_STATUS_VALUES, |
| 277 | self._assert_lab_closed) |
| 278 | |
| 279 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 280 | def test_dead_build(self): |
| 281 | """Test that disabled builds are handled correctly.""" |
| 282 | self._assert_lab_status(_DEADBUILD_STATUS_VALUES, |
| 283 | self._assert_lab_deadbuild) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 284 | |
| 285 | |
| 286 | class CheckStatusTest(mox.MoxTestBase): |
| 287 | |
| 288 | """Test case for `check_lab_status()`. |
| 289 | |
| 290 | We mock out dependencies on `global_config.global_config()`, |
| 291 | `_get_lab_status()` and confirm that the function succeeds or |
| 292 | fails as expected. |
| 293 | |
| 294 | N.B. We don't mock `_decode_lab_status()`; if DecodeStatusTest |
| 295 | is failing, this test may fail, too. |
| 296 | |
| 297 | """ |
| 298 | |
| 299 | def setUp(self): |
| 300 | super(CheckStatusTest, self).setUp() |
| 301 | self.mox.StubOutWithMock(global_config.global_config, |
| 302 | 'get_config_value') |
| 303 | self.mox.StubOutWithMock(site_utils, '_get_lab_status') |
| 304 | |
| 305 | |
| 306 | def _setup_not_cautotest(self): |
| 307 | """Set up to mock the "we're not on cautotest" case.""" |
| 308 | global_config.global_config.get_config_value( |
| 309 | 'SERVER', 'hostname').AndReturn('not-cautotest') |
| 310 | |
| 311 | |
| 312 | def _setup_no_status(self): |
| 313 | """Set up to mock lab status as unavailable.""" |
| 314 | global_config.global_config.get_config_value( |
| 315 | 'SERVER', 'hostname').AndReturn('cautotest') |
| 316 | global_config.global_config.get_config_value( |
| 317 | 'CROS', 'lab_status_url').AndReturn(_FAKE_URL) |
| 318 | site_utils._get_lab_status(_FAKE_URL).AndReturn(None) |
| 319 | |
| 320 | |
| 321 | def _setup_lab_status(self, json_string): |
| 322 | """Set up to mock a given lab status. |
| 323 | |
| 324 | @param json_string JSON string for the JSON object to return |
| 325 | from `_get_lab_status()`. |
| 326 | |
| 327 | """ |
| 328 | global_config.global_config.get_config_value( |
| 329 | 'SERVER', 'hostname').AndReturn('cautotest') |
| 330 | global_config.global_config.get_config_value( |
| 331 | 'CROS', 'lab_status_url').AndReturn(_FAKE_URL) |
| 332 | json_value = json.loads(json_string) |
| 333 | site_utils._get_lab_status(_FAKE_URL).AndReturn(json_value) |
| 334 | |
| 335 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 336 | def _try_check_status(self, build): |
| 337 | """Test calling check_lab_status() with `build`.""" |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 338 | try: |
| 339 | self.mox.ReplayAll() |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 340 | site_utils.check_lab_status(build) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 341 | finally: |
| 342 | self.mox.VerifyAll() |
| 343 | |
| 344 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 345 | def test_non_cautotest(self): |
| 346 | """Test a call with a build when the host isn't cautotest.""" |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 347 | self._setup_not_cautotest() |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 348 | self._try_check_status(_LIVEBUILD) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 349 | |
| 350 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 351 | def test_no_lab_status(self): |
| 352 | """Test with a build when `_get_lab_status()` returns `None`.""" |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 353 | self._setup_no_status() |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 354 | self._try_check_status(_LIVEBUILD) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 355 | |
| 356 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 357 | def test_lab_up_live_build(self): |
| 358 | """Test lab open with a build specified.""" |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 359 | self._setup_lab_status(_OPEN_STATUS_VALUES[0]) |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 360 | self._try_check_status(_LIVEBUILD) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 361 | |
| 362 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 363 | def test_lab_down_live_build(self): |
| 364 | """Test lab closed with a build specified.""" |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 365 | self._setup_lab_status(_CLOSED_STATUS_VALUES[0]) |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 366 | with self.assertRaises(site_utils.TestLabException): |
| 367 | self._try_check_status(_LIVEBUILD) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 368 | |
| 369 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 370 | def test_build_disabled_live_build(self): |
| 371 | """Test build disabled with a live build specified.""" |
| 372 | self._setup_lab_status(_DEADBUILD_STATUS_VALUES[0]) |
| 373 | self._try_check_status(_LIVEBUILD) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 374 | |
| 375 | |
J. Richard Barnette | abbe096 | 2013-12-10 18:15:44 -0800 | [diff] [blame] | 376 | def test_build_disabled_dead_build(self): |
| 377 | """Test build disabled with the disabled build specified.""" |
| 378 | self._setup_lab_status(_DEADBUILD_STATUS_VALUES[0]) |
| 379 | with self.assertRaises(site_utils.TestLabException): |
| 380 | self._try_check_status(_DEADBUILD) |
J. Richard Barnette | 266da2a | 2013-11-27 15:09:55 -0800 | [diff] [blame] | 381 | |
| 382 | |
| 383 | if __name__ == '__main__': |
| 384 | unittest.main() |