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 sys | 9 import sys |
10 import unittest | 10 import unittest |
11 | 11 |
12 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) | 12 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) |
13 from cros_build_lib import RunCommand, Info, Warning, ReinterpretPathForChroot | 13 from cros_build_lib import Die |
| 14 from cros_build_lib import Info |
| 15 from cros_build_lib import ReinterpretPathForChroot |
| 16 from cros_build_lib import RunCommand |
| 17 from cros_build_lib import Warning |
| 18 |
14 | 19 |
15 _KVM_PID_FILE = '/tmp/harness_pid' | 20 _KVM_PID_FILE = '/tmp/harness_pid' |
16 _FULL_VDISK_SIZE = 6072 | 21 _FULL_VDISK_SIZE = 6072 |
17 _FULL_STATEFULFS_SIZE = 2048 | 22 _FULL_STATEFULFS_SIZE = 2048 |
18 | 23 |
19 # Globals to communicate options to unit tests. | 24 # Globals to communicate options to unit tests. |
20 global base_image_path | 25 global base_image_path |
21 global board | 26 global board |
22 global remote | 27 global remote |
23 global target_image_path | 28 global target_image_path |
| 29 global vm_graphics_flag |
| 30 |
24 | 31 |
25 _VERIFY_SUITE = 'suite_Smoke' | 32 _VERIFY_SUITE = 'suite_Smoke' |
26 | 33 |
27 class AUTest(object): | 34 class AUTest(object): |
28 """Abstract interface that defines an Auto Update test.""" | 35 """Abstract interface that defines an Auto Update test.""" |
29 | 36 |
30 def setUp(self): | 37 def setUp(self): |
31 unittest.TestCase.setUp(self) | 38 unittest.TestCase.setUp(self) |
32 # Set these up as they are used often. | 39 # Set these up as they are used often. |
33 self.crosutils = os.path.join(os.path.dirname(__file__), '..') | 40 self.crosutils = os.path.join(os.path.dirname(__file__), '..') |
34 self.crosutilsbin = os.path.join(os.path.dirname(__file__)) | 41 self.crosutilsbin = os.path.join(os.path.dirname(__file__)) |
35 | 42 |
36 def GetStatefulChangeFlag(self, stateful_change): | 43 def GetStatefulChangeFlag(self, stateful_change): |
37 """Returns the flag to pass to image_to_vm for the stateful change.""" | 44 """Returns the flag to pass to image_to_vm for the stateful change.""" |
38 stateful_change_flag = '' | 45 stateful_change_flag = '' |
39 if stateful_change: | 46 if stateful_change: |
40 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change | 47 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change |
41 | 48 |
42 return stateful_change_flag | 49 return stateful_change_flag |
43 | 50 |
| 51 def ParseGenerateTestReportOutput(self, output): |
| 52 """Returns the percentage of tests that passed based on output.""" |
| 53 percent_passed = 0 |
| 54 lines = output.split('\n') |
| 55 |
| 56 for line in lines: |
| 57 if line.startswith("Total PASS:"): |
| 58 # FORMAT: ^TOTAL PASS: num_passed/num_total (percent%)$ |
| 59 percent_passed = line.split()[3].strip('()%') |
| 60 Info('Percent of tests passed %s' % percent_passed) |
| 61 break |
| 62 |
| 63 return int(percent_passed) |
| 64 |
44 def PrepareBase(self): | 65 def PrepareBase(self): |
45 """Prepares target with base_image_path.""" | 66 """Prepares target with base_image_path.""" |
46 pass | 67 pass |
47 | 68 |
48 def UpdateImage(self, image_path, stateful_change='old'): | 69 def UpdateImage(self, image_path, stateful_change='old'): |
49 """Updates target with the image given by the image_path. | 70 """Updates target with the image given by the image_path. |
50 | 71 |
51 Args: | 72 Args: |
52 image_path: Path to the image to update with. This image must be a test | 73 image_path: Path to the image to update with. This image must be a test |
53 image. | 74 image. |
54 stateful_change: How to modify the stateful partition. Values are: | 75 stateful_change: How to modify the stateful partition. Values are: |
55 'old': Don't modify stateful partition. Just update normally. | 76 'old': Don't modify stateful partition. Just update normally. |
56 'clean': Uses clobber-state to wipe the stateful partition with the | 77 'clean': Uses clobber-state to wipe the stateful partition with the |
57 exception of code needed for ssh. | 78 exception of code needed for ssh. |
58 """ | 79 """ |
59 pass | 80 pass |
60 | 81 |
61 def VerifyImage(self): | 82 def VerifyImage(self, percent_required_to_pass): |
62 """Verifies the image is correct.""" | 83 """Verifies the image with tests. |
| 84 |
| 85 Verifies that the test images passes the percent required. |
| 86 |
| 87 Args: |
| 88 percent_required_to_pass: percentage required to pass. This should be |
| 89 fall between 0-100. |
| 90 |
| 91 Returns: |
| 92 Returns the percent that passed. |
| 93 """ |
63 pass | 94 pass |
64 | 95 |
| 96 def CommonVerifyImage(self, unittest, output, percent_required_to_pass): |
| 97 """Helper function for VerifyImage that returns percent of tests passed. |
| 98 |
| 99 Takes output from a test suite, verifies the number of tests passed is |
| 100 sufficient and outputs info. |
| 101 |
| 102 Args: |
| 103 unittest: Handle to the unittest. |
| 104 output: stdout from a test run. |
| 105 percent_required_to_pass: percentage required to pass. This should be |
| 106 fall between 0-100. |
| 107 Returns: |
| 108 percent that passed. |
| 109 """ |
| 110 Info('Output from VerifyImage():') |
| 111 print output |
| 112 percent_passed = self.ParseGenerateTestReportOutput(output) |
| 113 Info('Percent passed: %d vs. Percent required: %d' % ( |
| 114 percent_passed, percent_required_to_pass)) |
| 115 unittest.assertTrue(percent_passed >= |
| 116 percent_required_to_pass) |
| 117 return percent_passed |
| 118 |
65 def testFullUpdateKeepStateful(self): | 119 def testFullUpdateKeepStateful(self): |
66 """Tests if we can update normally. | 120 """Tests if we can update normally. |
67 | 121 |
68 This test checks that we can update by updating the stateful partition | 122 This test checks that we can update by updating the stateful partition |
69 rather than wiping it. | 123 rather than wiping it. |
70 """ | 124 """ |
71 # Prepare and verify the base image has been prepared correctly. | 125 # Just make sure some tests pass on original image. Some old images |
| 126 # don't pass many tests. |
72 self.PrepareBase() | 127 self.PrepareBase() |
73 self.VerifyImage() | 128 # TODO(sosa): move to 100% once we start testing using the autotest paired |
| 129 # with the dev channel. |
| 130 percent_passed = self.VerifyImage(42) |
74 | 131 |
75 # Update to. | 132 # Update to - all tests should pass on new image. |
76 Info('Updating from base image on vm to target image.') | 133 Info('Updating from base image on vm to target image.') |
77 self.UpdateImage(target_image_path) | 134 self.UpdateImage(target_image_path) |
78 self.VerifyImage() | 135 self.VerifyImage(100) |
79 | 136 |
80 # Update from. | 137 # Update from - same percentage should pass that originally passed. |
81 Info('Updating from updated image on vm back to base image.') | 138 Info('Updating from updated image on vm back to base image.') |
82 self.UpdateImage(base_image_path) | 139 self.UpdateImage(base_image_path) |
83 self.VerifyImage() | 140 self.VerifyImage(percent_passed) |
84 | 141 |
85 # TODO(sosa): Re-enable once we have a good way of checking for version | 142 # TODO(sosa): Re-enable once we have a good way of checking for version |
86 # compatability. | 143 # compatibility. |
87 def NotestFullUpdateWipeStateful(self): | 144 def NotestFullUpdateWipeStateful(self): |
88 """Tests if we can update after cleaning the stateful partition. | 145 """Tests if we can update after cleaning the stateful partition. |
89 | 146 |
90 This test checks that we can update successfully after wiping the | 147 This test checks that we can update successfully after wiping the |
91 stateful partition. | 148 stateful partition. |
92 """ | 149 """ |
93 # Prepare and verify the base image has been prepared correctly. | 150 # Just make sure some tests pass on original image. Some old images |
| 151 # don't pass many tests. |
94 self.PrepareBase() | 152 self.PrepareBase() |
95 self.VerifyImage() | 153 # TODO(sosa): move to 100% once we start testing using the autotest paired |
| 154 # with the dev channel. |
| 155 percent_passed = self.VerifyImage(42) |
96 | 156 |
97 # Update to. | 157 # Update to - all tests should pass on new image. |
98 Info('Updating from base image on vm to target image and wiping stateful.') | 158 Info('Updating from base image on vm to target image and wiping stateful.') |
99 self.UpdateImage(target_image_path, 'clean') | 159 self.UpdateImage(target_image_path, 'clean') |
100 self.VerifyImage() | 160 self.VerifyImage(100) |
101 | 161 |
102 # Update from. | 162 # Update from - same percentage should pass that originally passed. |
103 Info('Updating from updated image back to base image and wiping stateful.') | 163 Info('Updating from updated image back to base image and wiping stateful.') |
104 self.UpdateImage(base_image_path, 'clean') | 164 self.UpdateImage(base_image_path, 'clean') |
105 self.VerifyImage() | 165 self.VerifyImage(percent_passed) |
106 | 166 |
107 | 167 |
108 class RealAUTest(unittest.TestCase, AUTest): | 168 class RealAUTest(unittest.TestCase, AUTest): |
109 """Test harness for updating real images.""" | 169 """Test harness for updating real images.""" |
110 | 170 |
111 def setUp(self): | 171 def setUp(self): |
112 AUTest.setUp(self) | 172 AUTest.setUp(self) |
113 | 173 |
| 174 def PrepareBase(self): |
| 175 """Auto-update to base image to prepare for test.""" |
| 176 self.UpdateImage(base_image_path) |
| 177 |
114 def UpdateImage(self, image_path, stateful_change='old'): | 178 def UpdateImage(self, image_path, stateful_change='old'): |
115 """Updates a remote image using image_to_live.sh.""" | 179 """Updates a remote image using image_to_live.sh.""" |
116 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | 180 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
117 | 181 |
118 RunCommand([ | 182 RunCommand([ |
119 '%s/image_to_live.sh' % self.crosutils, | 183 '%s/image_to_live.sh' % self.crosutils, |
120 '--image=%s' % image_path, | 184 '--image=%s' % image_path, |
121 '--remote=%s' % remote, | 185 '--remote=%s' % remote, |
122 stateful_change_flag, | 186 stateful_change_flag, |
123 '--verify', | 187 '--verify', |
124 ], enter_chroot=False) | 188 ], enter_chroot=False) |
125 | 189 |
126 | 190 |
127 def VerifyImage(self): | 191 def VerifyImage(self, percent_required_to_pass): |
128 """Verifies an image using run_remote_tests.sh with verification suite.""" | 192 """Verifies an image using run_remote_tests.sh with verification suite.""" |
129 RunCommand([ | 193 output = RunCommand([ |
130 '%s/run_remote_tests.sh' % self.crosutils, | 194 '%s/run_remote_tests.sh' % self.crosutils, |
131 '--remote=%s' % remote, | 195 '--remote=%s' % remote, |
132 _VERIFY_SUITE, | 196 _VERIFY_SUITE, |
133 ], error_ok=False, enter_chroot=False) | 197 ], error_ok=True, enter_chroot=False, redirect_stdout=True) |
| 198 return self.CommonVerifyImage(self, output, percent_required_to_pass) |
134 | 199 |
135 | 200 |
136 class VirtualAUTest(unittest.TestCase, AUTest): | 201 class VirtualAUTest(unittest.TestCase, AUTest): |
137 """Test harness for updating virtual machines.""" | 202 """Test harness for updating virtual machines.""" |
138 vm_image_path = None | 203 vm_image_path = None |
139 | 204 |
140 def _KillExistingVM(self, pid_file): | 205 def _KillExistingVM(self, pid_file): |
141 if os.path.exists(pid_file): | 206 if os.path.exists(pid_file): |
142 Warning('Existing %s found. Deleting and killing process' % | 207 Warning('Existing %s found. Deleting and killing process' % |
143 pid_file) | 208 pid_file) |
(...skipping 30 matching lines...) Expand all Loading... |
174 self.assertTrue(os.path.exists(self.vm_image_path)) | 239 self.assertTrue(os.path.exists(self.vm_image_path)) |
175 | 240 |
176 def UpdateImage(self, image_path, stateful_change='old'): | 241 def UpdateImage(self, image_path, stateful_change='old'): |
177 """Updates VM image with image_path.""" | 242 """Updates VM image with image_path.""" |
178 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | 243 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
179 | 244 |
180 RunCommand(['%s/cros_run_vm_update' % self.crosutilsbin, | 245 RunCommand(['%s/cros_run_vm_update' % self.crosutilsbin, |
181 '--update_image_path=%s' % image_path, | 246 '--update_image_path=%s' % image_path, |
182 '--vm_image_path=%s' % self.vm_image_path, | 247 '--vm_image_path=%s' % self.vm_image_path, |
183 '--snapshot', | 248 '--snapshot', |
| 249 vm_graphics_flag, |
184 '--persist', | 250 '--persist', |
185 '--kvm_pid=%s' % _KVM_PID_FILE, | 251 '--kvm_pid=%s' % _KVM_PID_FILE, |
186 stateful_change_flag, | 252 stateful_change_flag, |
187 ], enter_chroot=False) | 253 ], enter_chroot=False) |
188 | 254 |
189 def VerifyImage(self): | 255 def VerifyImage(self, percent_required_to_pass): |
190 """Runs vm smoke suite to verify image.""" | 256 """Runs vm smoke suite to verify image.""" |
191 # image_to_live already verifies lsb-release matching. This is just | 257 # image_to_live already verifies lsb-release matching. This is just |
192 # for additional steps. | 258 # for additional steps. |
193 RunCommand(['%s/cros_run_vm_test' % self.crosutilsbin, | 259 output = RunCommand(['%s/cros_run_vm_test' % self.crosutilsbin, |
194 '--image_path=%s' % self.vm_image_path, | 260 '--image_path=%s' % self.vm_image_path, |
195 '--snapshot', | 261 '--snapshot', |
196 '--persist', | 262 '--persist', |
197 '--kvm_pid=%s' % _KVM_PID_FILE, | 263 vm_graphics_flag, |
198 '--test_case=%s' % _VERIFY_SUITE, | 264 '--kvm_pid=%s' % _KVM_PID_FILE, |
199 ], error_ok=False, enter_chroot=False) | 265 '--test_case=%s' % _VERIFY_SUITE, |
| 266 ], error_ok=True, enter_chroot=False, |
| 267 redirect_stdout=True) |
| 268 return self.CommonVerifyImage(self, output, percent_required_to_pass) |
200 | 269 |
201 | 270 |
202 if __name__ == '__main__': | 271 if __name__ == '__main__': |
203 parser = optparse.OptionParser() | 272 parser = optparse.OptionParser() |
204 parser.add_option('-b', '--base_image', | 273 parser.add_option('-b', '--base_image', |
205 help='path to the base image.') | 274 help='path to the base image.') |
206 parser.add_option('-t', '--target_image', | 275 parser.add_option('-t', '--target_image', |
207 help='path to the target image.') | 276 help='path to the target image.') |
208 parser.add_option('-r', '--board', | 277 parser.add_option('-r', '--board', |
209 help='board for the images.') | 278 help='board for the images.') |
210 parser.add_option('-p', '--type', default='vm', | 279 parser.add_option('-p', '--type', default='vm', |
211 help='type of test to run: [vm, real]. Default: vm.') | 280 help='type of test to run: [vm, real]. Default: vm.') |
212 parser.add_option('-m', '--remote', | 281 parser.add_option('-m', '--remote', |
213 help='Remote address for real test.') | 282 help='Remote address for real test.') |
| 283 parser.add_option('--no_graphics', action='store_true', |
| 284 help='Disable graphics for the vm test.') |
214 # Set the usage to include flags. | 285 # Set the usage to include flags. |
215 parser.set_usage(parser.format_help()) | 286 parser.set_usage(parser.format_help()) |
216 # Parse existing sys.argv so we can pass rest to unittest.main. | 287 # Parse existing sys.argv so we can pass rest to unittest.main. |
217 (options, sys.argv) = parser.parse_args(sys.argv) | 288 (options, sys.argv) = parser.parse_args(sys.argv) |
218 | 289 |
219 base_image_path = options.base_image | 290 base_image_path = options.base_image |
220 target_image_path = options.target_image | 291 target_image_path = options.target_image |
221 board = options.board | 292 board = options.board |
222 | 293 |
223 if not base_image_path: | 294 if not base_image_path: |
224 parser.error('Need path to base image for vm.') | 295 parser.error('Need path to base image for vm.') |
| 296 elif not os.path.exists(base_image_path): |
| 297 Die('%s does not exist' % base_image_path) |
225 | 298 |
226 if not target_image_path: | 299 if not target_image_path: |
227 parser.error('Need path to target image to update with.') | 300 parser.error('Need path to target image to update with.') |
| 301 elif not os.path.exists(target_image_path): |
| 302 Die('%s does not exist' % target_image_path) |
228 | 303 |
229 if not board: | 304 if not board: |
230 parser.error('Need board to convert base image to vm.') | 305 parser.error('Need board to convert base image to vm.') |
231 | 306 |
232 return_code = 0 | 307 vm_graphics_flag = '' |
| 308 if options.no_graphics: vm_graphics_flag = '--no_graphics' |
233 | 309 |
234 # Only run the test harness we care about. | 310 # Only run the test harness we care about. |
235 if options.type == 'vm': | 311 if options.type == 'vm': |
236 suite = unittest.TestLoader().loadTestsFromTestCase(VirtualAUTest) | 312 suite = unittest.TestLoader().loadTestsFromTestCase(VirtualAUTest) |
237 return_code = unittest.TextTestRunner(verbosity=2).run(suite) | 313 test_result = unittest.TextTestRunner(verbosity=2).run(suite) |
238 elif options.type == 'real': | 314 elif options.type == 'real': |
239 if not options.remote: | 315 if not options.remote: |
240 parser.error('Real tests require a remote test machine.') | 316 parser.error('Real tests require a remote test machine.') |
241 else: | 317 else: |
242 remote = options.remote | 318 remote = options.remote |
243 | 319 |
244 suite = unittest.TestLoader().loadTestsFromTestCase(RealAUTest) | 320 suite = unittest.TestLoader().loadTestsFromTestCase(RealAUTest) |
245 return_code = unittest.TextTestRunner(verbosity=2).run(suite) | 321 test_result = unittest.TextTestRunner(verbosity=2).run(suite) |
246 else: | 322 else: |
247 parser.error('Could not parse harness type %s.' % options.type) | 323 parser.error('Could not parse harness type %s.' % options.type) |
248 | 324 |
249 sys.exit(return_code) | 325 if not test_result.wasSuccessful(): |
| 326 Die('Test harness was not successful') |
OLD | NEW |