Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 | 2 |
| 3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 import optparse | 7 import optparse |
| 8 import os | 8 import os |
| 9 import shutil | |
| 9 import sys | 10 import sys |
| 10 import unittest | 11 import unittest |
| 12 import urllib | |
| 11 | 13 |
| 12 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) | 14 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) |
| 13 from cros_build_lib import Die | 15 from cros_build_lib import Die |
| 14 from cros_build_lib import Info | 16 from cros_build_lib import Info |
| 15 from cros_build_lib import ReinterpretPathForChroot | 17 from cros_build_lib import ReinterpretPathForChroot |
| 16 from cros_build_lib import RunCommand | 18 from cros_build_lib import RunCommand |
| 19 from cros_build_lib import RunCommandCaptureOutput | |
| 17 from cros_build_lib import Warning | 20 from cros_build_lib import Warning |
| 18 | 21 |
| 19 # VM Constants. | 22 # VM Constants. |
| 20 _FULL_VDISK_SIZE = 6072 | 23 _FULL_VDISK_SIZE = 6072 |
| 21 _FULL_STATEFULFS_SIZE = 3074 | 24 _FULL_STATEFULFS_SIZE = 3074 |
| 22 _KVM_PID_FILE = '/tmp/harness_pid' | 25 _KVM_PID_FILE = '/tmp/harness_pid' |
| 23 _VERIFY_SUITE = 'suite_Smoke' | 26 _VERIFY_SUITE = 'suite_Smoke' |
| 24 | 27 |
| 25 # Globals to communicate options to unit tests. | 28 # Globals to communicate options to unit tests. |
| 26 global base_image_path | 29 global base_image_path |
| 27 global board | 30 global board |
| 28 global remote | 31 global remote |
| 29 global target_image_path | 32 global target_image_path |
| 30 global vm_graphics_flag | 33 global vm_graphics_flag |
| 31 | 34 |
| 35 class UpdateException(Exception): | |
| 36 """Exception thrown when UpdateImage or UpdatePayload fail""" | |
| 37 def __init__(self, code, stdout): | |
| 38 self.code = code | |
| 39 self.stdout = stdout | |
| 32 | 40 |
| 33 class AUTest(object): | 41 class AUTest(object): |
| 34 """Abstract interface that defines an Auto Update test.""" | 42 """Abstract interface that defines an Auto Update test.""" |
| 35 source_image = '' | 43 source_image = '' |
| 36 use_delta_updates = False | 44 use_delta_updates = False |
| 37 | 45 |
| 38 def setUp(self): | 46 def setUp(self): |
| 39 unittest.TestCase.setUp(self) | 47 unittest.TestCase.setUp(self) |
| 40 # Set these up as they are used often. | 48 # Set these up as they are used often. |
| 41 self.crosutils = os.path.join(os.path.dirname(__file__), '..') | 49 self.crosutils = os.path.join(os.path.dirname(__file__), '..') |
| 42 self.crosutilsbin = os.path.join(os.path.dirname(__file__)) | 50 self.crosutilsbin = os.path.join(os.path.dirname(__file__)) |
| 51 self.download = os.path.join(self.crosutilsbin, 'latest_download') | |
|
sosa
2010/12/01 18:37:39
self.download_folder?
dgarrett
2010/12/02 22:20:20
Done.
| |
| 43 | 52 |
| 44 def GetStatefulChangeFlag(self, stateful_change): | 53 def GetStatefulChangeFlag(self, stateful_change): |
| 45 """Returns the flag to pass to image_to_vm for the stateful change.""" | 54 """Returns the flag to pass to image_to_vm for the stateful change.""" |
| 46 stateful_change_flag = '' | 55 stateful_change_flag = '' |
| 47 if stateful_change: | 56 if stateful_change: |
| 48 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change | 57 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change |
| 49 | 58 |
| 50 return stateful_change_flag | 59 return stateful_change_flag |
| 51 | 60 |
| 52 def ParseGenerateTestReportOutput(self, output): | 61 def ParseGenerateTestReportOutput(self, output): |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 67 def TryDeltaAndFallbackToFull(self, src_image, image, stateful_change='old'): | 76 def TryDeltaAndFallbackToFull(self, src_image, image, stateful_change='old'): |
| 68 """Tries the delta update first if set and falls back to full update.""" | 77 """Tries the delta update first if set and falls back to full update.""" |
| 69 if self.use_delta_updates: | 78 if self.use_delta_updates: |
| 70 try: | 79 try: |
| 71 self.source_image = src_image | 80 self.source_image = src_image |
| 72 self.UpdateImage(image) | 81 self.UpdateImage(image) |
| 73 except: | 82 except: |
| 74 Warning('Delta update failed, disabling delta updates and retrying.') | 83 Warning('Delta update failed, disabling delta updates and retrying.') |
| 75 self.use_delta_updates = False | 84 self.use_delta_updates = False |
| 76 self.source_image = '' | 85 self.source_image = '' |
| 77 self.UpdateImage(image) | 86 self.UpdateImageReportError(image) |
| 78 else: | 87 else: |
| 79 self.UpdateImage(image) | 88 self.UpdateImageReportError(image) |
| 89 | |
| 90 def UpdateImageReportError(self, image_path, stateful_change='old'): | |
|
sosa
2010/12/01 18:37:39
needs docstring .. may also want to make private
dgarrett
2010/12/02 22:20:20
Done.
| |
| 91 try: | |
| 92 self.UpdateImage(image_path, stateful_change) | |
| 93 except UpdateException as err: | |
| 94 # If the update fails, print it out | |
| 95 print err.stdout | |
|
sosa
2010/12/01 18:37:39
Use warning?
dgarrett
2010/12/02 22:20:20
Done.
| |
| 96 raise | |
| 80 | 97 |
| 81 def PrepareBase(self): | 98 def PrepareBase(self): |
| 82 """Prepares target with base_image_path.""" | 99 """Prepares target with base_image_path.""" |
| 83 pass | 100 pass |
| 84 | 101 |
| 85 def UpdateImage(self, image_path, stateful_change='old'): | 102 def UpdateImage(self, image_path, stateful_change='old'): |
| 86 """Updates target with the image given by the image_path. | 103 """Updates target with the image given by the image_path. |
| 87 | 104 |
| 88 Args: | 105 Args: |
| 89 image_path: Path to the image to update with. This image must be a test | 106 image_path: Path to the image to update with. This image must be a test |
| 90 image. | 107 image. |
| 91 stateful_change: How to modify the stateful partition. Values are: | 108 stateful_change: How to modify the stateful partition. Values are: |
| 92 'old': Don't modify stateful partition. Just update normally. | 109 'old': Don't modify stateful partition. Just update normally. |
| 93 'clean': Uses clobber-state to wipe the stateful partition with the | 110 'clean': Uses clobber-state to wipe the stateful partition with the |
| 94 exception of code needed for ssh. | 111 exception of code needed for ssh. |
| 95 """ | 112 """ |
| 96 pass | 113 pass |
| 97 | 114 |
| 115 def UpdatePayload(self, update_path, stateful_change='old'): | |
|
sosa
2010/12/01 18:37:39
Maybe UpdateUsingPayload
dgarrett
2010/12/02 22:20:20
Done.
| |
| 116 """Updates target with the pre-generated update stored in update_path | |
| 117 | |
| 118 Args: | |
| 119 update_path: Path to the image to update with. This directory should | |
| 120 contain both update.gz, and stateful.image.gz | |
| 121 """ | |
| 122 pass | |
| 123 | |
| 98 def VerifyImage(self, percent_required_to_pass): | 124 def VerifyImage(self, percent_required_to_pass): |
| 99 """Verifies the image with tests. | 125 """Verifies the image with tests. |
| 100 | 126 |
| 101 Verifies that the test images passes the percent required. | 127 Verifies that the test images passes the percent required. |
| 102 | 128 |
| 103 Args: | 129 Args: |
| 104 percent_required_to_pass: percentage required to pass. This should be | 130 percent_required_to_pass: percentage required to pass. This should be |
| 105 fall between 0-100. | 131 fall between 0-100. |
| 106 | 132 |
| 107 Returns: | 133 Returns: |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 171 # Update to - all tests should pass on new image. | 197 # Update to - all tests should pass on new image. |
| 172 Info('Updating from base image on vm to target image and wiping stateful.') | 198 Info('Updating from base image on vm to target image and wiping stateful.') |
| 173 self.TryDeltaAndFallbackToFull(base_image_path, target_image_path, 'clean') | 199 self.TryDeltaAndFallbackToFull(base_image_path, target_image_path, 'clean') |
| 174 self.VerifyImage(100) | 200 self.VerifyImage(100) |
| 175 | 201 |
| 176 # Update from - same percentage should pass that originally passed. | 202 # Update from - same percentage should pass that originally passed. |
| 177 Info('Updating from updated image back to base image and wiping stateful.') | 203 Info('Updating from updated image back to base image and wiping stateful.') |
| 178 self.TryDeltaAndFallbackToFull(target_image_path, base_image_path, 'clean') | 204 self.TryDeltaAndFallbackToFull(target_image_path, base_image_path, 'clean') |
| 179 self.VerifyImage(percent_passed) | 205 self.VerifyImage(percent_passed) |
| 180 | 206 |
| 207 def testPartialUpdate(self): | |
| 208 """Tests what happens if we attempt to update with a truncated payload. | |
|
sosa
2010/12/01 18:37:39
Only needs to be a one liner (i.e end triple quote
dgarrett
2010/12/02 22:20:20
Done.
| |
| 209 """ | |
| 210 # Preload with the version we are trying to test. | |
| 211 self.UpdateImageReportError(target_image_path) | |
|
sosa
2010/12/01 18:37:39
Move into PrepareBase and use PrepareBase (e.g. we
| |
| 212 percent_passed = self.VerifyImage(100) | |
| 213 | |
| 214 # Image can be updated at: | |
| 215 # ~chrome-eng/chromeos/localmirror/autest-images | |
| 216 url = 'http://gsdview.appspot.com/chromeos-localmirror/autest-images/truncat ed_image.gz' | |
|
sosa
2010/12/01 18:37:39
80 chars
dgarrett
2010/12/02 22:20:20
Done.
| |
| 217 payload = os.path.join(self.download, 'truncated_image.gz') | |
| 218 | |
| 219 # Read from the URL and write to the local file | |
| 220 shutil.copyfileobj(urllib.urlopen(url), open(payload, 'w')) | |
|
sosa
2010/12/01 18:37:39
Maybe use urllib.urlretrieve?
dgarrett
2010/12/02 22:20:20
Nice, I looked for something like that, but missed
| |
| 221 | |
| 222 # This update is expected to fail... | |
| 223 try: | |
| 224 self.UpdatePayload(payload) | |
| 225 except UpdateException as err: | |
| 226 # Will raise ValueError if expected is not found. | |
| 227 try: | |
| 228 expectedMsg='download_hash_data == update_check_response_hash failed' | |
|
sosa
2010/12/01 18:37:39
Might be easier to read with an expected regex rat
| |
| 229 err.stdout.index(expectedMsg) | |
| 230 except ValueError: | |
| 231 print "Didn't find '%s' in:" % expectedMsg | |
|
sosa
2010/12/01 18:37:39
Use Info or Warning
dgarrett
2010/12/02 22:20:20
Done.
| |
| 232 print err.stdout | |
| 233 raise | |
| 234 return | |
| 235 | |
| 236 unittest.fail('We managed to update with a partial payload') | |
| 237 | |
| 238 def testCorruptedUpdate(self): | |
| 239 """Tests what happens if we attempt to update with a corrupted payload. | |
|
sosa
2010/12/01 18:37:39
Don't need a second line for docstring
dgarrett
2010/12/02 22:20:20
Done.
| |
| 240 """ | |
| 241 # Preload with the version we are trying to test. | |
| 242 self.UpdateImageReportError(target_image_path) | |
|
sosa
2010/12/01 18:37:39
PrepareBase
dgarrett
2010/12/02 22:20:20
Done.
| |
| 243 percent_passed = self.VerifyImage(100) | |
| 244 | |
| 245 # Image can be updated at: | |
| 246 # ~chrome-eng/chromeos/localmirror/autest-images | |
| 247 url = 'http://gsdview.appspot.com/chromeos-localmirror/autest-images/corrupt ed_image.gz' | |
|
sosa
2010/12/01 18:37:39
80 chars
dgarrett
2010/12/02 22:20:20
Done.
| |
| 248 payload = os.path.join(self.download, 'corrupted.gz') | |
| 249 | |
| 250 # Read from the URL and write to the local file | |
| 251 shutil.copyfileobj(urllib.urlopen(url), open(payload, 'w')) | |
| 252 | |
| 253 # This update is expected to fail... | |
| 254 try: | |
| 255 self.UpdatePayload(payload) | |
| 256 except UpdateException as err: | |
| 257 # Will raise ValueError if expected is not found. | |
| 258 try: | |
| 259 # There are a number of different errors that can occur here, but | |
| 260 # we're just trying to test the whole thing. | |
| 261 expectedMsg='zlib inflate() error:-3' | |
|
sosa
2010/12/01 18:37:39
expected_msg
dgarrett
2010/12/02 22:20:20
Done.
| |
| 262 err.stdout.index(expectedMsg) | |
| 263 except ValueError: | |
| 264 print "Didn't find '%s' in:" % expectedMsg | |
| 265 print err.stdoutKeepStateful | |
| 266 raise | |
| 267 return | |
| 268 | |
| 269 unittest.fail('We managed to update with a corrupted payload') | |
|
sosa
2010/12/01 18:37:39
This code seems almost the same as the test before
dgarrett
2010/12/02 22:20:20
Done.
| |
| 181 | 270 |
| 182 class RealAUTest(unittest.TestCase, AUTest): | 271 class RealAUTest(unittest.TestCase, AUTest): |
| 183 """Test harness for updating real images.""" | 272 """Test harness for updating real images.""" |
| 184 | 273 |
| 185 def setUp(self): | 274 def setUp(self): |
| 186 AUTest.setUp(self) | 275 AUTest.setUp(self) |
| 187 | 276 |
| 188 def PrepareBase(self): | 277 def PrepareBase(self): |
| 189 """Auto-update to base image to prepare for test.""" | 278 """Auto-update to base image to prepare for test.""" |
| 190 self.UpdateImage(base_image_path) | 279 self.UpdateImageReportError(base_image_path) |
| 191 | 280 |
| 192 def UpdateImage(self, image_path, stateful_change='old'): | 281 def UpdateImage(self, image_path, stateful_change='old'): |
| 193 """Updates a remote image using image_to_live.sh.""" | 282 """Updates a remote image using image_to_live.sh.""" |
| 194 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | 283 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
| 195 | 284 |
| 196 RunCommand([ | 285 (code, stdout, stderr) = RunCommandCaptureOutput([ |
| 197 '%s/image_to_live.sh' % self.crosutils, | 286 '%s/image_to_live.sh' % self.crosutils, |
| 198 '--image=%s' % image_path, | 287 '--image=%s' % image_path, |
| 199 '--remote=%s' % remote, | 288 '--remote=%s' % remote, |
| 200 stateful_change_flag, | 289 stateful_change_flag, |
| 201 '--verify', | 290 '--verify', |
| 202 '--src_image=%s' % self.source_image, | 291 '--src_image=%s' % self.source_image |
| 203 ], enter_chroot=False) | 292 ]) |
| 204 | 293 |
| 294 if code != 0: | |
| 295 raise UpdateException(code, stdout) | |
| 296 | |
| 297 def UpdatePayload(self, update_path, stateful_change='old'): | |
| 298 """Updates a remote image using image_to_live.sh.""" | |
| 299 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | |
| 300 | |
| 301 (code, stdout, stderr) = RunCommandCaptureOutput([ | |
| 302 '%s/image_to_live.sh' % self.crosutils, | |
| 303 '--payload=%s' % update_path, | |
| 304 '--remote=%s' % remote, | |
| 305 stateful_change_flag, | |
| 306 '--verify', | |
| 307 ]) | |
| 308 | |
| 309 if code != 0: | |
| 310 raise UpdateException(code, stdout) | |
| 205 | 311 |
| 206 def VerifyImage(self, percent_required_to_pass): | 312 def VerifyImage(self, percent_required_to_pass): |
| 207 """Verifies an image using run_remote_tests.sh with verification suite.""" | 313 """Verifies an image using run_remote_tests.sh with verification suite.""" |
| 208 output = RunCommand([ | 314 output = RunCommand([ |
| 209 '%s/run_remote_tests.sh' % self.crosutils, | 315 '%s/run_remote_tests.sh' % self.crosutils, |
| 210 '--remote=%s' % remote, | 316 '--remote=%s' % remote, |
| 211 _VERIFY_SUITE, | 317 _VERIFY_SUITE, |
| 212 ], error_ok=True, enter_chroot=False, redirect_stdout=True) | 318 ], error_ok=True, enter_chroot=False, redirect_stdout=True) |
| 213 return self.CommonVerifyImage(self, output, percent_required_to_pass) | 319 return self.CommonVerifyImage(self, output, percent_required_to_pass) |
| 214 | 320 |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 252 Info('Using existing VM image %s' % self.vm_image_path) | 358 Info('Using existing VM image %s' % self.vm_image_path) |
| 253 | 359 |
| 254 self.assertTrue(os.path.exists(self.vm_image_path)) | 360 self.assertTrue(os.path.exists(self.vm_image_path)) |
| 255 | 361 |
| 256 def UpdateImage(self, image_path, stateful_change='old'): | 362 def UpdateImage(self, image_path, stateful_change='old'): |
| 257 """Updates VM image with image_path.""" | 363 """Updates VM image with image_path.""" |
| 258 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | 364 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
| 259 if self.source_image == base_image_path: | 365 if self.source_image == base_image_path: |
| 260 self.source_image = self.vm_image_path | 366 self.source_image = self.vm_image_path |
| 261 | 367 |
| 262 RunCommand(['%s/cros_run_vm_update' % self.crosutilsbin, | 368 (code, stdout, stderr) = RunCommandCaptureOutput([ |
| 263 '--update_image_path=%s' % image_path, | 369 '%s/cros_run_vm_update' % self.crosutilsbin, |
| 264 '--vm_image_path=%s' % self.vm_image_path, | 370 '--update_image_path=%s' % image_path, |
| 265 '--snapshot', | 371 '--vm_image_path=%s' % self.vm_image_path, |
| 266 vm_graphics_flag, | 372 '--snapshot', |
| 267 '--persist', | 373 vm_graphics_flag, |
| 268 '--kvm_pid=%s' % _KVM_PID_FILE, | 374 '--persist', |
| 269 stateful_change_flag, | 375 '--kvm_pid=%s' % _KVM_PID_FILE, |
| 270 '--src_image=%s' % self.source_image, | 376 stateful_change_flag, |
| 271 ], enter_chroot=False) | 377 '--src_image=%s' % self.source_image, |
| 378 ]) | |
| 379 | |
| 380 if code != 0: | |
| 381 raise UpdateException(code, stdout) | |
| 382 | |
| 383 def UpdatePayload(self, update_path, stateful_change='old'): | |
| 384 """Updates a remote image using image_to_live.sh.""" | |
| 385 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | |
| 386 | |
| 387 (code, stdout, stderr) = RunCommandCaptureOutput([ | |
|
sosa
2010/12/01 18:37:39
Does this work for VM's? The port isn't correct .
dgarrett
2010/12/02 22:20:20
It did not, and the fix was larger than I was expe
| |
| 388 '%s/image_to_live.sh' % self.crosutils, | |
| 389 '--payload=%s' % update_path, | |
| 390 stateful_change_flag, | |
| 391 '--for_vm', | |
| 392 '--verify', | |
| 393 ]) | |
| 394 | |
| 395 if code != 0: | |
| 396 raise UpdateException(code, stdout) | |
| 272 | 397 |
| 273 def VerifyImage(self, percent_required_to_pass): | 398 def VerifyImage(self, percent_required_to_pass): |
| 274 """Runs vm smoke suite to verify image.""" | 399 """Runs vm smoke suite to verify image.""" |
| 275 # image_to_live already verifies lsb-release matching. This is just | 400 # image_to_live already verifies lsb-release matching. This is just |
| 276 # for additional steps. | 401 # for additional steps. |
| 277 | 402 |
| 278 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin, | 403 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin, |
| 279 '--image_path=%s' % self.vm_image_path, | 404 '--image_path=%s' % self.vm_image_path, |
| 280 '--snapshot', | 405 '--snapshot', |
| 281 '--persist', | 406 '--persist', |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 348 else: | 473 else: |
| 349 remote = options.remote | 474 remote = options.remote |
| 350 | 475 |
| 351 suite = unittest.TestLoader().loadTestsFromTestCase(RealAUTest) | 476 suite = unittest.TestLoader().loadTestsFromTestCase(RealAUTest) |
| 352 test_result = unittest.TextTestRunner(verbosity=2).run(suite) | 477 test_result = unittest.TextTestRunner(verbosity=2).run(suite) |
| 353 else: | 478 else: |
| 354 parser.error('Could not parse harness type %s.' % options.type) | 479 parser.error('Could not parse harness type %s.' % options.type) |
| 355 | 480 |
| 356 if not test_result.wasSuccessful(): | 481 if not test_result.wasSuccessful(): |
| 357 Die('Test harness was not successful') | 482 Die('Test harness was not successful') |
| OLD | NEW |