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

Side by Side Diff: bin/cros_au_test_harness.py

Issue 6015013: Major cleanup of cros_au_test_harness code. (Closed) Base URL: http://git.chromium.org/git/crosutils.git@master
Patch Set: rename Created 9 years, 11 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
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 3 # Copyright (c) 2011 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 """This module runs a suite of Auto Update tests.
8
9 The tests can be run on either a virtual machine or actual device depending
10 on parameters given. Specific tests can be run by invoking --test_prefix.
11 Verbose is useful for many of the tests if you want to see individual commands
12 being run during the update process.
13 """
14
7 import optparse 15 import optparse
8 import os 16 import os
9 import re 17 import re
10 import sys 18 import sys
11 import thread 19 import thread
12 import time 20 import time
13 import unittest 21 import unittest
14 import urllib 22 import urllib
15 23
16 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) 24 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib'))
17 from cros_build_lib import Die 25 from cros_build_lib import Die
18 from cros_build_lib import Info 26 from cros_build_lib import Info
19 from cros_build_lib import ReinterpretPathForChroot 27 from cros_build_lib import ReinterpretPathForChroot
20 from cros_build_lib import RunCommand 28 from cros_build_lib import RunCommand
21 from cros_build_lib import RunCommandCaptureOutput 29 from cros_build_lib import RunCommandCaptureOutput
22 from cros_build_lib import Warning 30 from cros_build_lib import Warning
23 31
24 import cros_test_proxy 32 import cros_test_proxy
25 33
26 # VM Constants.
27 _FULL_VDISK_SIZE = 6072
28 _FULL_STATEFULFS_SIZE = 3074
29 _KVM_PID_FILE = '/tmp/harness_pid'
30 _VERIFY_SUITE = 'suite_Smoke'
31
32 # Globals to communicate options to unit tests.
33 global base_image_path
34 global board
35 global remote
36 global target_image_path
37 global vm_graphics_flag
38 34
39 class UpdateException(Exception): 35 class UpdateException(Exception):
40 """Exception thrown when UpdateImage or UpdateUsingPayload fail""" 36 """Exception thrown when _UpdateImage or _UpdateUsingPayload fail"""
41 def __init__(self, code, stdout): 37 def __init__(self, code, stdout):
42 self.code = code 38 self.code = code
43 self.stdout = stdout 39 self.stdout = stdout
44 40
41
45 class AUTest(object): 42 class AUTest(object):
46 """Abstract interface that defines an Auto Update test.""" 43 """Abstract interface that defines an Auto Update test."""
47 source_image = ''
48 use_delta_updates = False
49 verbose = False 44 verbose = False
50 45
51 def setUp(self): 46 def setUp(self):
52 unittest.TestCase.setUp(self) 47 unittest.TestCase.setUp(self)
53 # Set these up as they are used often. 48 # Set these up as they are used often.
54 self.crosutils = os.path.join(os.path.dirname(__file__), '..') 49 self.crosutils = os.path.join(os.path.dirname(__file__), '..')
55 self.crosutilsbin = os.path.join(os.path.dirname(__file__)) 50 self.crosutilsbin = os.path.join(os.path.dirname(__file__))
56 self.download_folder = os.path.join(self.crosutils, 'latest_download') 51 self.download_folder = os.path.join(self.crosutils, 'latest_download')
57 if not os.path.exists(self.download_folder): 52 if not os.path.exists(self.download_folder):
58 os.makedirs(self.download_folder) 53 os.makedirs(self.download_folder)
59 54
55 # -------- Helper functions ---------
56
60 def GetStatefulChangeFlag(self, stateful_change): 57 def GetStatefulChangeFlag(self, stateful_change):
61 """Returns the flag to pass to image_to_vm for the stateful change.""" 58 """Returns the flag to pass to image_to_vm for the stateful change."""
62 stateful_change_flag = '' 59 stateful_change_flag = ''
63 if stateful_change: 60 if stateful_change:
64 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change 61 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change
65 62
66 return stateful_change_flag 63 return stateful_change_flag
67 64
68 def ParseGenerateTestReportOutput(self, output): 65 def _ParseGenerateTestReportOutput(self, output):
69 """Returns the percentage of tests that passed based on output.""" 66 """Returns the percentage of tests that passed based on output."""
70 percent_passed = 0 67 percent_passed = 0
71 lines = output.split('\n') 68 lines = output.split('\n')
72 69
73 for line in lines: 70 for line in lines:
74 if line.startswith("Total PASS:"): 71 if line.startswith("Total PASS:"):
75 # FORMAT: ^TOTAL PASS: num_passed/num_total (percent%)$ 72 # FORMAT: ^TOTAL PASS: num_passed/num_total (percent%)$
76 percent_passed = line.split()[3].strip('()%') 73 percent_passed = line.split()[3].strip('()%')
77 Info('Percent of tests passed %s' % percent_passed) 74 Info('Percent of tests passed %s' % percent_passed)
78 break 75 break
79 76
80 return int(percent_passed) 77 return int(percent_passed)
81 78
82 # TODO(sosa) - Remove try and convert function to DeltaUpdateImage(). 79 def AssertEnoughTestsPassed(self, unittest, output, percent_required_to_pass):
83 def TryDeltaAndFallbackToFull(self, src_image, image, stateful_change='old'): 80 """Helper function that asserts a sufficient number of tests passed.
84 """Tries the delta update first if set and falls back to full update."""
85 if self.use_delta_updates:
86 try:
87 self.source_image = src_image
88 self._UpdateImageReportError(image, stateful_change)
89 except:
90 Warning('Delta update failed, disabling delta updates and retrying.')
91 self.use_delta_updates = False
92 self.source_image = ''
93 self._UpdateImageReportError(image, stateful_change)
94 else:
95 self._UpdateImageReportError(image, stateful_change)
96 81
97 def _UpdateImageReportError(self, image_path, stateful_change='old', 82 Args:
98 proxy_port=None): 83 unittest: Handle to the unittest.
99 """Calls UpdateImage and reports any error to the console. 84 output: stdout from a test run.
85 percent_required_to_pass: percentage required to pass. This should be
86 fall between 0-100.
87 Returns:
88 percent that passed.
89 """
90 Info('Output from VerifyImage():')
91 print >> sys.stderr, output
92 sys.stderr.flush()
93 percent_passed = self._ParseGenerateTestReportOutput(output)
94 Info('Percent passed: %d vs. Percent required: %d' % (
95 percent_passed, percent_required_to_pass))
96 unittest.assertTrue(percent_passed >= percent_required_to_pass)
97 return percent_passed
100 98
101 Still throws the exception. 99 def PerformUpdate(self, image_path, src_image_path='', stateful_change='old',
100 proxy_port=None):
101 """Performs an update using _UpdateImage and reports any error.
102
103 Subclasses should not override this method but override _UpdateImage
104 instead.
105
106 Args:
107 image_path: Path to the image to update with. This image must be a test
108 image.
109 src_image_path: Optional. If set, perform a delta update using the
110 image specified by the path as the source image.
111 stateful_change: How to modify the stateful partition. Values are:
112 'old': Don't modify stateful partition. Just update normally.
113 'clean': Uses clobber-state to wipe the stateful partition with the
114 exception of code needed for ssh.
115 proxy_port: Port to have the client connect to. For use with
116 CrosTestProxy.
117 Raises an UpdateException if _UpdateImage returns an error.
102 """ 118 """
103 try: 119 try:
104 self.UpdateImage(image_path, stateful_change, proxy_port) 120 if not self.use_delta_updates:
121 src_image_path = ''
122
123 self._UpdateImage(image_path, src_image_path, stateful_change, proxy_port)
105 except UpdateException as err: 124 except UpdateException as err:
106 # If the update fails, print it out 125 # If the update fails, print it out
107 Warning(err.stdout) 126 Warning(err.stdout)
108 raise 127 raise
109 128
110 def _AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): 129 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg):
111 """Attempt a payload update, expect it to fail with expected log""" 130 """Attempt a payload update, expect it to fail with expected log"""
112 try: 131 try:
113 self.UpdateUsingPayload(payload) 132 self._UpdateUsingPayload(payload)
114 except UpdateException as err: 133 except UpdateException as err:
115 # Will raise ValueError if expected is not found. 134 # Will raise ValueError if expected is not found.
116 if re.search(re.escape(expected_msg), err.stdout, re.MULTILINE): 135 if re.search(re.escape(expected_msg), err.stdout, re.MULTILINE):
117 return 136 return
118 137
119 Warning("Didn't find '%s' in:" % expected_msg) 138 Warning("Didn't find '%s' in:" % expected_msg)
120 Warning(err.stdout) 139 Warning(err.stdout)
121 self.fail('We managed to update when failure was expected') 140 self.fail('We managed to update when failure was expected')
122 141
123 def _AttemptUpdateWithFilter(self, filter): 142 def AttemptUpdateWithFilter(self, filter):
124 """Update through a proxy, with a specified filter, and expect success.""" 143 """Update through a proxy, with a specified filter, and expect success."""
125 144
126 self.PrepareBase(target_image_path) 145 self.PrepareBase(self.target_image_path)
127 146
128 # The devserver runs at port 8080 by default. We assume that here, and 147 # The devserver runs at port 8080 by default. We assume that here, and
129 # start our proxy at 8081. We then tell our update tools to have the 148 # start our proxy at 8081. We then tell our update tools to have the
130 # client connect to 8081 instead of 8080. 149 # client connect to 8081 instead of 8080.
131 proxy_port = 8081 150 proxy_port = 8081
132 proxy = cros_test_proxy.CrosTestProxy(port_in=proxy_port, 151 proxy = cros_test_proxy.CrosTestProxy(port_in=proxy_port,
133 address_out='127.0.0.1', 152 address_out='127.0.0.1',
134 port_out=8080, 153 port_out=8080,
135 filter=filter) 154 filter=filter)
136 proxy.serve_forever_in_thread() 155 proxy.serve_forever_in_thread()
137 156
138 # This update is expected to fail... 157 # This update is expected to fail...
139 try: 158 try:
140 self._UpdateImageReportError(target_image_path, proxy_port=proxy_port) 159 self.PerformUpdate(self.target_image_path, self.base_image_path,
160 proxy_port=proxy_port)
141 finally: 161 finally:
142 proxy.shutdown() 162 proxy.shutdown()
143 163
164 # -------- Functions that subclasses should override ---------
165
166 @classmethod
167 def ProcessOptions(cls, parser, options):
168 """Processes options.
169
170 Static method that should be called from main. Subclasses should also
171 call their parent method if they override it.
172 """
173 cls.verbose = options.verbose
174 cls.base_image_path = options.base_image
175 cls.target_image_path = options.target_image
176 cls.use_delta_updates = options.delta
177 if options.quick_test:
178 cls.verify_suite = 'build_RootFilesystemSize'
179 else:
180 cls.verify_suite = 'suite_Smoke'
181
182 # Sanity checks.
183 if not cls.base_image_path:
184 parser.error('Need path to base image for vm.')
185 elif not os.path.exists(cls.base_image_path):
186 Die('%s does not exist' % cls.base_image_path)
187
188 if not cls.target_image_path:
189 parser.error('Need path to target image to update with.')
190 elif not os.path.exists(cls.target_image_path):
191 Die('%s does not exist' % cls.target_image_path)
192
144 def PrepareBase(self, image_path): 193 def PrepareBase(self, image_path):
145 """Prepares target with base_image_path.""" 194 """Prepares target with base_image_path."""
146 pass 195 pass
147 196
148 def UpdateImage(self, image_path, stateful_change='old', proxy_port=None): 197 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
149 """Updates target with the image given by the image_path. 198 proxy_port=None):
199 """Implementation of an actual update.
150 200
151 Args: 201 See PerformUpdate for description of args. Subclasses must override this
152 image_path: Path to the image to update with. This image must be a test 202 method with the correct update procedure for the class.
153 image.
154 stateful_change: How to modify the stateful partition. Values are:
155 'old': Don't modify stateful partition. Just update normally.
156 'clean': Uses clobber-state to wipe the stateful partition with the
157 exception of code needed for ssh.
158 proxy_port: Port to have the client connect to. For use with
159 CrosTestProxy.
160 """ 203 """
161 pass 204 pass
162 205
163 def UpdateUsingPayload(self, 206 def _UpdateUsingPayload(self, update_path, stateful_change='old',
164 update_path,
165 stateful_change='old',
166 proxy_port=None): 207 proxy_port=None):
167 """Updates target with the pre-generated update stored in update_path 208 """Updates target with the pre-generated update stored in update_path.
209
210 Subclasses must override this method with the correct update procedure for
211 the class.
168 212
169 Args: 213 Args:
170 update_path: Path to the image to update with. This directory should 214 update_path: Path to the image to update with. This directory should
171 contain both update.gz, and stateful.image.gz 215 contain both update.gz, and stateful.image.gz
172 proxy_port: Port to have the client connect to. For use with 216 proxy_port: Port to have the client connect to. For use with
173 CrosTestProxy. 217 CrosTestProxy.
174 """ 218 """
175 pass 219 pass
176 220
177 def VerifyImage(self, percent_required_to_pass): 221 def VerifyImage(self, percent_required_to_pass):
178 """Verifies the image with tests. 222 """Verifies the image with tests.
179 223
180 Verifies that the test images passes the percent required. 224 Verifies that the test images passes the percent required. Subclasses must
225 override this method with the correct update procedure for the class.
181 226
182 Args: 227 Args:
183 percent_required_to_pass: percentage required to pass. This should be 228 percent_required_to_pass: percentage required to pass. This should be
184 fall between 0-100. 229 fall between 0-100.
185 230
186 Returns: 231 Returns:
187 Returns the percent that passed. 232 Returns the percent that passed.
188 """ 233 """
189 pass 234 pass
190 235
191 def CommonVerifyImage(self, unittest, output, percent_required_to_pass): 236 # -------- Tests ---------
192 """Helper function for VerifyImage that returns percent of tests passed.
193 237
194 Takes output from a test suite, verifies the number of tests passed is 238 def testUpdateKeepStateful(self):
195 sufficient and outputs info.
196
197 Args:
198 unittest: Handle to the unittest.
199 output: stdout from a test run.
200 percent_required_to_pass: percentage required to pass. This should be
201 fall between 0-100.
202 Returns:
203 percent that passed.
204 """
205 Info('Output from VerifyImage():')
206 print >> sys.stderr, output
207 sys.stderr.flush()
208 percent_passed = self.ParseGenerateTestReportOutput(output)
209 Info('Percent passed: %d vs. Percent required: %d' % (
210 percent_passed, percent_required_to_pass))
211 unittest.assertTrue(percent_passed >=
212 percent_required_to_pass)
213 return percent_passed
214
215 def testFullUpdateKeepStateful(self):
216 """Tests if we can update normally. 239 """Tests if we can update normally.
217 240
218 This test checks that we can update by updating the stateful partition 241 This test checks that we can update by updating the stateful partition
219 rather than wiping it. 242 rather than wiping it.
220 """ 243 """
221 # Just make sure some tests pass on original image. Some old images 244 # Just make sure some tests pass on original image. Some old images
222 # don't pass many tests. 245 # don't pass many tests.
223 self.PrepareBase(base_image_path) 246 self.PrepareBase(self.base_image_path)
224 # TODO(sosa): move to 100% once we start testing using the autotest paired 247 # TODO(sosa): move to 100% once we start testing using the autotest paired
225 # with the dev channel. 248 # with the dev channel.
226 percent_passed = self.VerifyImage(10) 249 percent_passed = self.VerifyImage(10)
227 250
228 # Update to - all tests should pass on new image. 251 # Update to - all tests should pass on new image.
229 Info('Updating from base image on vm to target image.') 252 Info('Updating from base image on vm to target image.')
230 self.TryDeltaAndFallbackToFull(base_image_path, target_image_path) 253 self.PerformUpdate(self.target_image_path, self.base_image_path)
231 self.VerifyImage(100) 254 self.VerifyImage(100)
232 255
233 # Update from - same percentage should pass that originally passed. 256 # Update from - same percentage should pass that originally passed.
234 Info('Updating from updated image on vm back to base image.') 257 Info('Updating from updated image on vm back to base image.')
235 self.TryDeltaAndFallbackToFull(target_image_path, base_image_path) 258 self.PerformUpdate(self.base_image_path, self.target_image_path)
236 self.VerifyImage(percent_passed) 259 self.VerifyImage(percent_passed)
237 260
238 def testFullUpdateWipeStateful(self): 261 def testUpdateWipeStateful(self):
239 """Tests if we can update after cleaning the stateful partition. 262 """Tests if we can update after cleaning the stateful partition.
240 263
241 This test checks that we can update successfully after wiping the 264 This test checks that we can update successfully after wiping the
242 stateful partition. 265 stateful partition.
243 """ 266 """
244 # Just make sure some tests pass on original image. Some old images 267 # Just make sure some tests pass on original image. Some old images
245 # don't pass many tests. 268 # don't pass many tests.
246 self.PrepareBase(base_image_path) 269 self.PrepareBase(self.base_image_path)
247 # TODO(sosa): move to 100% once we start testing using the autotest paired 270 # TODO(sosa): move to 100% once we start testing using the autotest paired
248 # with the dev channel. 271 # with the dev channel.
249 percent_passed = self.VerifyImage(10) 272 percent_passed = self.VerifyImage(10)
250 273
251 # Update to - all tests should pass on new image. 274 # Update to - all tests should pass on new image.
252 Info('Updating from base image on vm to target image and wiping stateful.') 275 Info('Updating from base image on vm to target image and wiping stateful.')
253 self.TryDeltaAndFallbackToFull(base_image_path, target_image_path, 'clean') 276 self.PerformUpdate(self.target_image_path, self.base_image_path, 'clean')
254 self.VerifyImage(100) 277 self.VerifyImage(100)
255 278
256 # Update from - same percentage should pass that originally passed. 279 # Update from - same percentage should pass that originally passed.
257 Info('Updating from updated image back to base image and wiping stateful.') 280 Info('Updating from updated image back to base image and wiping stateful.')
258 self.TryDeltaAndFallbackToFull(target_image_path, base_image_path, 'clean') 281 self.PerformUpdate(self.base_image_path, self.target_image_path, 'clean')
259 self.VerifyImage(percent_passed) 282 self.VerifyImage(percent_passed)
260 283
261 def testPartialUpdate(self): 284 def testPartialUpdate(self):
262 """Tests what happens if we attempt to update with a truncated payload.""" 285 """Tests what happens if we attempt to update with a truncated payload."""
263 # Preload with the version we are trying to test. 286 # Preload with the version we are trying to test.
264 self.PrepareBase(target_image_path) 287 self.PrepareBase(self.target_image_path)
265 288
266 # Image can be updated at: 289 # Image can be updated at:
267 # ~chrome-eng/chromeos/localmirror/autest-images 290 # ~chrome-eng/chromeos/localmirror/autest-images
268 url = 'http://gsdview.appspot.com/chromeos-localmirror/' \ 291 url = 'http://gsdview.appspot.com/chromeos-localmirror/' \
269 'autest-images/truncated_image.gz' 292 'autest-images/truncated_image.gz'
270 payload = os.path.join(self.download_folder, 'truncated_image.gz') 293 payload = os.path.join(self.download_folder, 'truncated_image.gz')
271 294
272 # Read from the URL and write to the local file 295 # Read from the URL and write to the local file
273 urllib.urlretrieve(url, payload) 296 urllib.urlretrieve(url, payload)
274 297
275 expected_msg = 'download_hash_data == update_check_response_hash failed' 298 expected_msg = 'download_hash_data == update_check_response_hash failed'
276 self._AttemptUpdateWithPayloadExpectedFailure(payload, expected_msg) 299 self.AttemptUpdateWithPayloadExpectedFailure(payload, expected_msg)
277 300
278 def testCorruptedUpdate(self): 301 def testCorruptedUpdate(self):
279 """Tests what happens if we attempt to update with a corrupted payload.""" 302 """Tests what happens if we attempt to update with a corrupted payload."""
280 # Preload with the version we are trying to test. 303 # Preload with the version we are trying to test.
281 self.PrepareBase(target_image_path) 304 self.PrepareBase(self.target_image_path)
282 305
283 # Image can be updated at: 306 # Image can be updated at:
284 # ~chrome-eng/chromeos/localmirror/autest-images 307 # ~chrome-eng/chromeos/localmirror/autest-images
285 url = 'http://gsdview.appspot.com/chromeos-localmirror/' \ 308 url = 'http://gsdview.appspot.com/chromeos-localmirror/' \
286 'autest-images/corrupted_image.gz' 309 'autest-images/corrupted_image.gz'
287 payload = os.path.join(self.download_folder, 'corrupted.gz') 310 payload = os.path.join(self.download_folder, 'corrupted.gz')
288 311
289 # Read from the URL and write to the local file 312 # Read from the URL and write to the local file
290 urllib.urlretrieve(url, payload) 313 urllib.urlretrieve(url, payload)
291 314
292 # This update is expected to fail... 315 # This update is expected to fail...
293 expected_msg = 'zlib inflate() error:-3' 316 expected_msg = 'zlib inflate() error:-3'
294 self._AttemptUpdateWithPayloadExpectedFailure(payload, expected_msg) 317 self.AttemptUpdateWithPayloadExpectedFailure(payload, expected_msg)
295 318
296 def testInterruptedUpdate(self): 319 def testInterruptedUpdate(self):
297 """Tests what happens if we interrupt payload delivery 3 times.""" 320 """Tests what happens if we interrupt payload delivery 3 times."""
298 321
299 class InterruptionFilter(cros_test_proxy.Filter): 322 class InterruptionFilter(cros_test_proxy.Filter):
300 """This filter causes the proxy to interrupt the download 3 times 323 """This filter causes the proxy to interrupt the download 3 times
301 324
302 It does this by closing the first three connections to transfer 325 It does this by closing the first three connections to transfer
303 2M total in the outbound connection after they transfer the 326 2M total in the outbound connection after they transfer the
304 2M. 327 2M.
(...skipping 13 matching lines...) Expand all
318 outbound will be closed. 341 outbound will be closed.
319 """ 342 """
320 if self.close_count < 3: 343 if self.close_count < 3:
321 if self.data_size > (2 * 1024 * 1024): 344 if self.data_size > (2 * 1024 * 1024):
322 self.close_count += 1 345 self.close_count += 1
323 return None 346 return None
324 347
325 self.data_size += len(data) 348 self.data_size += len(data)
326 return data 349 return data
327 350
328 self._AttemptUpdateWithFilter(InterruptionFilter()) 351 self.AttemptUpdateWithFilter(InterruptionFilter())
329 352
330 def testDelayedUpdate(self): 353 def testDelayedUpdate(self):
331 """Tests what happens if some data is delayed during update delivery""" 354 """Tests what happens if some data is delayed during update delivery"""
332 355
333 class DelayedFilter(cros_test_proxy.Filter): 356 class DelayedFilter(cros_test_proxy.Filter):
334 """Causes intermittent delays in data transmission. 357 """Causes intermittent delays in data transmission.
335 358
336 It does this by inserting 3 20 second delays when transmitting 359 It does this by inserting 3 20 second delays when transmitting
337 data after 2M has been sent. 360 data after 2M has been sent.
338 """ 361 """
339 def setup(self): 362 def setup(self):
340 """Called once at the start of each connection.""" 363 """Called once at the start of each connection."""
341 self.data_size = 0 364 self.data_size = 0
342 self.delay_count = 0 365 self.delay_count = 0
343 366
344 def OutBound(self, data): 367 def OutBound(self, data):
345 """Called once per packet for outgoing data. 368 """Called once per packet for outgoing data.
346 369
347 The first three packets after we reach 2M transferred 370 The first three packets after we reach 2M transferred
348 are delayed by 20 seconds. 371 are delayed by 20 seconds.
349 """ 372 """
350 if self.delay_count < 3: 373 if self.delay_count < 3:
351 if self.data_size > (2 * 1024 * 1024): 374 if self.data_size > (2 * 1024 * 1024):
352 self.delay_count += 1 375 self.delay_count += 1
353 time.sleep(20) 376 time.sleep(20)
354 377
355 self.data_size += len(data) 378 self.data_size += len(data)
356 return data 379 return data
357 380
358 self._AttemptUpdateWithFilter(DelayedFilter()) 381 self.AttemptUpdateWithFilter(DelayedFilter())
359 382
360 def SimpleTest(self): 383 def SimpleTest(self):
361 """A simple update that updates once from a base image to a target. 384 """A simple update that updates once from a base image to a target.
362 385
363 We explicitly don't use test prefix so that isn't run by default. Can be 386 We explicitly don't use test prefix so that isn't run by default. Can be
364 run using test_prefix option. 387 run using test_prefix option.
365 """ 388 """
366 self.PrepareBase(base_image_path) 389 self.PrepareBase(self.base_image_path)
367 self.UpdateImage(target_image_path) 390 self.PerformUpdate(self.target_image_path, self.base_image_path)
368 self.VerifyImage(100) 391 self.VerifyImage(100)
369 392
370 393
371 class RealAUTest(unittest.TestCase, AUTest): 394 class RealAUTest(unittest.TestCase, AUTest):
372 """Test harness for updating real images.""" 395 """Test harness for updating real images."""
373 396
374 def setUp(self): 397 def setUp(self):
375 AUTest.setUp(self) 398 AUTest.setUp(self)
376 399
400 @classmethod
401 def ProcessOptions(cls, parser, options):
402 """Processes non-vm-specific options."""
403 AUTest.ProcessOptions(parser, options)
404 cls.remote = options.remote
405
406 if not cls.remote:
407 parser.error('We require a remote address for real tests.')
408
377 def PrepareBase(self, image_path): 409 def PrepareBase(self, image_path):
378 """Auto-update to base image to prepare for test.""" 410 """Auto-update to base image to prepare for test."""
379 self._UpdateImageReportError(image_path) 411 self.PerformUpdate(image_path)
380 412
381 def UpdateImage(self, image_path, stateful_change='old', proxy_port=None): 413 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
414 proxy_port=None):
382 """Updates a remote image using image_to_live.sh.""" 415 """Updates a remote image using image_to_live.sh."""
383 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 416 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
384 cmd = ['%s/image_to_live.sh' % self.crosutils, 417 cmd = ['%s/image_to_live.sh' % self.crosutils,
385 '--image=%s' % image_path, 418 '--image=%s' % image_path,
386 '--remote=%s' % remote, 419 '--remote=%s' % self.remote,
387 stateful_change_flag, 420 stateful_change_flag,
388 '--verify', 421 '--verify',
389 '--src_image=%s' % self.source_image 422 '--src_image=%s' % src_image_path
390 ] 423 ]
391 424
392 if proxy_port: 425 if proxy_port:
393 cmd.append('--proxy_port=%s' % proxy_port) 426 cmd.append('--proxy_port=%s' % proxy_port)
394 427
395 if self.verbose: 428 if self.verbose:
396 try: 429 try:
397 RunCommand(cmd) 430 RunCommand(cmd)
398 except Exception, e: 431 except Exception, e:
399 raise UpdateException(1, e.message) 432 raise UpdateException(1, e.message)
400 else: 433 else:
401 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) 434 (code, stdout, stderr) = RunCommandCaptureOutput(cmd)
402 if code != 0: 435 if code != 0:
403 raise UpdateException(code, stdout) 436 raise UpdateException(code, stdout)
404 437
405 def UpdateUsingPayload(self, 438 def _UpdateUsingPayload(self, update_path, stateful_change='old',
406 update_path,
407 stateful_change='old',
408 proxy_port=None): 439 proxy_port=None):
409 """Updates a remote image using image_to_live.sh.""" 440 """Updates a remote image using image_to_live.sh."""
410 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 441 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
411 cmd = ['%s/image_to_live.sh' % self.crosutils, 442 cmd = ['%s/image_to_live.sh' % self.crosutils,
412 '--payload=%s' % update_path, 443 '--payload=%s' % update_path,
413 '--remote=%s' % remote, 444 '--remote=%s' % self.remote,
414 stateful_change_flag, 445 stateful_change_flag,
415 '--verify', 446 '--verify',
416 ] 447 ]
417 448
418 if proxy_port: 449 if proxy_port:
419 cmd.append('--proxy_port=%s' % proxy_port) 450 cmd.append('--proxy_port=%s' % proxy_port)
420 451
421 if self.verbose: 452 if self.verbose:
422 try: 453 try:
423 RunCommand(cmd) 454 RunCommand(cmd)
424 except Exception, e: 455 except Exception, e:
425 raise UpdateException(1, e.message) 456 raise UpdateException(1, e.message)
426 else: 457 else:
427 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) 458 (code, stdout, stderr) = RunCommandCaptureOutput(cmd)
428 if code != 0: 459 if code != 0:
429 raise UpdateException(code, stdout) 460 raise UpdateException(code, stdout)
430 461
431 def VerifyImage(self, percent_required_to_pass): 462 def VerifyImage(self, percent_required_to_pass):
432 """Verifies an image using run_remote_tests.sh with verification suite.""" 463 """Verifies an image using run_remote_tests.sh with verification suite."""
433 output = RunCommand([ 464 output = RunCommand([
434 '%s/run_remote_tests.sh' % self.crosutils, 465 '%s/run_remote_tests.sh' % self.crosutils,
435 '--remote=%s' % remote, 466 '--remote=%s' % self.remote,
436 _VERIFY_SUITE, 467 self.verify_suite,
437 ], error_ok=True, enter_chroot=False, redirect_stdout=True) 468 ], error_ok=True, enter_chroot=False, redirect_stdout=True)
438 return self.CommonVerifyImage(self, output, percent_required_to_pass) 469 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass)
439 470
440 471
441 class VirtualAUTest(unittest.TestCase, AUTest): 472 class VirtualAUTest(unittest.TestCase, AUTest):
442 """Test harness for updating virtual machines.""" 473 """Test harness for updating virtual machines."""
443 vm_image_path = None 474 vm_image_path = None
444 475
476 # VM Constants.
477 _FULL_VDISK_SIZE = 6072
478 _FULL_STATEFULFS_SIZE = 3074
479 _KVM_PID_FILE = '/tmp/harness_pid'
480
445 def _KillExistingVM(self, pid_file): 481 def _KillExistingVM(self, pid_file):
446 if os.path.exists(pid_file): 482 if os.path.exists(pid_file):
447 Warning('Existing %s found. Deleting and killing process' % 483 Warning('Existing %s found. Deleting and killing process' %
448 pid_file) 484 pid_file)
449 RunCommand(['./cros_stop_vm', '--kvm_pid=%s' % pid_file], 485 RunCommand(['./cros_stop_vm', '--kvm_pid=%s' % pid_file],
450 cwd=self.crosutilsbin) 486 cwd=self.crosutilsbin)
451 487
452 assert not os.path.exists(pid_file) 488 assert not os.path.exists(pid_file)
453 489
454 def setUp(self): 490 def setUp(self):
455 """Unit test overriden method. Is called before every test.""" 491 """Unit test overriden method. Is called before every test."""
456 AUTest.setUp(self) 492 AUTest.setUp(self)
457 self._KillExistingVM(_KVM_PID_FILE) 493 self._KillExistingVM(self._KVM_PID_FILE)
494
495 @classmethod
496 def ProcessOptions(cls, parser, options):
497 """Processes vm-specific options."""
498 AUTest.ProcessOptions(parser, options)
499 cls.board = options.board
500
501 # Communicate flags to tests.
502 cls.graphics_flag = ''
503 if options.no_graphics: cls.graphics_flag = '--no_graphics'
504
505 if not cls.board:
506 parser.error('Need board to convert base image to vm.')
458 507
459 def PrepareBase(self, image_path): 508 def PrepareBase(self, image_path):
460 """Creates an update-able VM based on base image.""" 509 """Creates an update-able VM based on base image."""
461 self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( 510 self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname(
462 image_path) 511 image_path)
463 512
464 Info('Creating: %s' % self.vm_image_path) 513 Info('Creating: %s' % self.vm_image_path)
465 514
466 if not os.path.exists(self.vm_image_path): 515 if not os.path.exists(self.vm_image_path):
467 Info('Qemu image %s not found, creating one.' % self.vm_image_path) 516 Info('Qemu image %s not found, creating one.' % self.vm_image_path)
468 RunCommand(['%s/image_to_vm.sh' % self.crosutils, 517 RunCommand(['%s/image_to_vm.sh' % self.crosutils,
469 '--full', 518 '--full',
470 '--from=%s' % ReinterpretPathForChroot( 519 '--from=%s' % ReinterpretPathForChroot(
471 os.path.dirname(image_path)), 520 os.path.dirname(image_path)),
472 '--vdisk_size=%s' % _FULL_VDISK_SIZE, 521 '--vdisk_size=%s' % self._FULL_VDISK_SIZE,
473 '--statefulfs_size=%s' % _FULL_STATEFULFS_SIZE, 522 '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE,
474 '--board=%s' % board, 523 '--board=%s' % self.board,
475 '--test_image'], enter_chroot=True) 524 '--test_image'], enter_chroot=True)
476 else: 525 else:
477 Info('Using existing VM image %s' % self.vm_image_path) 526 Info('Using existing VM image %s' % self.vm_image_path)
478 527
479
480 Info('Testing for %s' % self.vm_image_path) 528 Info('Testing for %s' % self.vm_image_path)
481
482 self.assertTrue(os.path.exists(self.vm_image_path)) 529 self.assertTrue(os.path.exists(self.vm_image_path))
483 530
484 def UpdateImage(self, image_path, stateful_change='old', proxy_port=None): 531 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
532 proxy_port=None):
485 """Updates VM image with image_path.""" 533 """Updates VM image with image_path."""
486 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 534 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
487 if self.source_image == base_image_path: 535 if src_image_path == self.base_image_path:
488 self.source_image = self.vm_image_path 536 src_image_path = self.vm_image_path
489 537
490 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, 538 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin,
491 '--update_image_path=%s' % image_path, 539 '--update_image_path=%s' % image_path,
492 '--vm_image_path=%s' % self.vm_image_path, 540 '--vm_image_path=%s' % self.vm_image_path,
493 '--snapshot', 541 '--snapshot',
494 vm_graphics_flag, 542 self.graphics_flag,
495 '--persist', 543 '--persist',
496 '--kvm_pid=%s' % _KVM_PID_FILE, 544 '--kvm_pid=%s' % self._KVM_PID_FILE,
497 stateful_change_flag, 545 stateful_change_flag,
498 '--src_image=%s' % self.source_image, 546 '--src_image=%s' % src_image_path,
499 ] 547 ]
500 548
501 if proxy_port: 549 if proxy_port:
502 cmd.append('--proxy_port=%s' % proxy_port) 550 cmd.append('--proxy_port=%s' % proxy_port)
503 551
504 if self.verbose: 552 if self.verbose:
505 try: 553 try:
506 RunCommand(cmd) 554 RunCommand(cmd)
507 except Exception, e: 555 except Exception, e:
508 raise UpdateException(1, e.message) 556 raise UpdateException(1, e.message)
509 else: 557 else:
510 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) 558 (code, stdout, stderr) = RunCommandCaptureOutput(cmd)
511 if code != 0: 559 if code != 0:
512 raise UpdateException(code, stdout) 560 raise UpdateException(code, stdout)
513 561
514 def UpdateUsingPayload(self, 562 def _UpdateUsingPayload(self, update_path, stateful_change='old',
515 update_path,
516 stateful_change='old',
517 proxy_port=None): 563 proxy_port=None):
518 """Updates a remote image using image_to_live.sh.""" 564 """Updates a vm image using cros_run_vm_update."""
519 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 565 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
520 if self.source_image == base_image_path:
521 self.source_image = self.vm_image_path
522
523 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, 566 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin,
524 '--payload=%s' % update_path, 567 '--payload=%s' % update_path,
525 '--vm_image_path=%s' % self.vm_image_path, 568 '--vm_image_path=%s' % self.vm_image_path,
526 '--snapshot', 569 '--snapshot',
527 vm_graphics_flag, 570 self.graphics_flag,
528 '--persist', 571 '--persist',
529 '--kvm_pid=%s' % _KVM_PID_FILE, 572 '--kvm_pid=%s' % self._KVM_PID_FILE,
530 stateful_change_flag, 573 stateful_change_flag,
531 '--src_image=%s' % self.source_image,
532 ] 574 ]
533 575
534 if proxy_port: 576 if proxy_port:
535 cmd.append('--proxy_port=%s' % proxy_port) 577 cmd.append('--proxy_port=%s' % proxy_port)
536 578
537 if self.verbose: 579 if self.verbose:
538 try: 580 try:
539 RunCommand(cmd) 581 RunCommand(cmd)
540 except Exception, e: 582 except Exception, e:
541 raise UpdateException(1, e.message) 583 raise UpdateException(1, e.message)
542 else: 584 else:
543 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) 585 (code, stdout, stderr) = RunCommandCaptureOutput(cmd)
544 if code != 0: 586 if code != 0:
545 raise UpdateException(code, stdout) 587 raise UpdateException(code, stdout)
546 588
547 def VerifyImage(self, percent_required_to_pass): 589 def VerifyImage(self, percent_required_to_pass):
548 """Runs vm smoke suite to verify image.""" 590 """Runs vm smoke suite to verify image."""
549 # image_to_live already verifies lsb-release matching. This is just 591 # image_to_live already verifies lsb-release matching. This is just
550 # for additional steps. 592 # for additional steps.
551 593
552 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin, 594 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin,
553 '--image_path=%s' % self.vm_image_path, 595 '--image_path=%s' % self.vm_image_path,
554 '--snapshot', 596 '--snapshot',
555 '--persist', 597 '--persist',
556 '--kvm_pid=%s' % _KVM_PID_FILE, 598 '--kvm_pid=%s' % self._KVM_PID_FILE,
557 _VERIFY_SUITE, 599 self.verify_suite,
558 ] 600 ]
559 601
560 if vm_graphics_flag: 602 if self.graphics_flag:
561 commandWithArgs.append(vm_graphics_flag) 603 commandWithArgs.append(self.graphics_flag)
562 604
563 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False, 605 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False,
564 redirect_stdout=True) 606 redirect_stdout=True)
565 return self.CommonVerifyImage(self, output, percent_required_to_pass) 607 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass)
566 608
567 609
568 if __name__ == '__main__': 610 def main():
569 parser = optparse.OptionParser() 611 parser = optparse.OptionParser()
570 parser.add_option('-b', '--base_image', 612 parser.add_option('-b', '--base_image',
571 help='path to the base image.') 613 help='path to the base image.')
572 parser.add_option('-r', '--board', 614 parser.add_option('-r', '--board',
573 help='board for the images.') 615 help='board for the images.')
574 parser.add_option('--no_delta', action='store_false', default=True, 616 parser.add_option('--no_delta', action='store_false', default=True,
575 dest='delta', 617 dest='delta',
576 help='Disable using delta updates.') 618 help='Disable using delta updates.')
577 parser.add_option('--no_graphics', action='store_true', 619 parser.add_option('--no_graphics', action='store_true',
578 help='Disable graphics for the vm test.') 620 help='Disable graphics for the vm test.')
579 parser.add_option('-m', '--remote', 621 parser.add_option('-m', '--remote',
580 help='Remote address for real test.') 622 help='Remote address for real test.')
581 parser.add_option('-q', '--quick_test', default=False, action='store_true', 623 parser.add_option('-q', '--quick_test', default=False, action='store_true',
582 help='Use a basic test to verify image.') 624 help='Use a basic test to verify image.')
583 parser.add_option('-t', '--target_image', 625 parser.add_option('-t', '--target_image',
584 help='path to the target image.') 626 help='path to the target image.')
585 parser.add_option('--test_prefix', default='test', 627 parser.add_option('--test_prefix', default='test',
586 help='Only runs tests with specific prefix i.e. ' 628 help='Only runs tests with specific prefix i.e. '
587 'testFullUpdateWipeStateful.') 629 'testFullUpdateWipeStateful.')
588 parser.add_option('-p', '--type', default='vm', 630 parser.add_option('-p', '--type', default='vm',
589 help='type of test to run: [vm, real]. Default: vm.') 631 help='type of test to run: [vm, real]. Default: vm.')
590 parser.add_option('--verbose', default=False, action='store_true', 632 parser.add_option('--verbose', default=False, action='store_true',
591 help='Print out rather than capture output as much as ' 633 help='Print out rather than capture output as much as '
592 'possible.') 634 'possible.')
593 # Set the usage to include flags. 635 (options, leftover_args) = parser.parse_args()
594 parser.set_usage(parser.format_help())
595 # Parse existing sys.argv so we can pass rest to unittest.main.
596 (options, sys.argv) = parser.parse_args(sys.argv)
597 636
598 AUTest.verbose = options.verbose 637 if leftover_args:
599 base_image_path = options.base_image 638 parser.error('Found extra options we do not support: %s' % leftover_args)
600 target_image_path = options.target_image
601 board = options.board
602
603 if not base_image_path:
604 parser.error('Need path to base image for vm.')
605 elif not os.path.exists(base_image_path):
606 Die('%s does not exist' % base_image_path)
607
608 if not target_image_path:
609 parser.error('Need path to target image to update with.')
610 elif not os.path.exists(target_image_path):
611 Die('%s does not exist' % target_image_path)
612
613 if not board:
614 parser.error('Need board to convert base image to vm.')
615
616 # Communicate flags to tests.
617 vm_graphics_flag = ''
618 if options.no_graphics: vm_graphics_flag = '--no_graphics'
619 if options.quick_test: _VERIFY_SUITE = 'build_RootFilesystemSize'
620 AUTest.use_delta_updates = options.delta
621
622 # Only run the test harness we care about.
623 test_loader = unittest.TestLoader()
624 test_loader.testMethodPrefix = options.test_prefix
625 639
626 if options.type == 'vm': test_class = VirtualAUTest 640 if options.type == 'vm': test_class = VirtualAUTest
627 elif options.type == 'real': test_class = RealAUTest 641 elif options.type == 'real': test_class = RealAUTest
628 else: parser.error('Could not parse harness type %s.' % options.type) 642 else: parser.error('Could not parse harness type %s.' % options.type)
629 643
630 remote = options.remote 644 test_class.ProcessOptions(parser, options)
631 645
646 test_loader = unittest.TestLoader()
647 test_loader.testMethodPrefix = options.test_prefix
632 test_suite = test_loader.loadTestsFromTestCase(test_class) 648 test_suite = test_loader.loadTestsFromTestCase(test_class)
633 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) 649 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite)
634 650
635 if not test_result.wasSuccessful(): 651 if not test_result.wasSuccessful():
636 Die('Test harness was not successful') 652 Die('Test harness was not successful')
653
654
655 if __name__ == '__main__':
656 main()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698