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

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