blob: 928a54bf72a4254d756631907ac35b65007fc381 [file] [log] [blame]
Yiming Chen3c0103a2015-03-31 11:32:35 -07001# Copyright 2015 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Module for integration VM tests for CLI commands.
6
7This module contains the basic functionalities for setting up a VM and testing
8the CLI commands.
9"""
10
11from __future__ import print_function
12
Ralph Nathan90475a12015-05-20 13:19:01 -070013from chromite.cli import deploy
Yiming Chen3c0103a2015-03-31 11:32:35 -070014from chromite.lib import cros_build_lib
15from chromite.lib import cros_logging as logging
16from chromite.lib import remote_access
17from chromite.lib import vm
18
19
20class Error(Exception):
21 """Base exception for CLI command VM tests."""
22
23
Yiming Chen818d8f22015-04-29 11:25:24 -070024class SetupError(Error):
25 """Raised when error occurs during test environment setup."""
26
27
28class TestError(Error):
29 """Raised when a command test has failed."""
30
31
Yiming Chen3c0103a2015-03-31 11:32:35 -070032class CommandError(Error):
Yiming Chen818d8f22015-04-29 11:25:24 -070033 """Raised when error occurs during a command test."""
Yiming Chen3c0103a2015-03-31 11:32:35 -070034
35
36def _PrintCommandLog(command, content):
37 """Print out the log |content| for |command|."""
38 if content:
39 logging.info('\n----------- Start of %s log -----------\n%s\n'
40 '----------- End of %s log -----------',
41 command, content.rstrip(), command)
42
43
44def TestCommandDecorator(command_name):
45 """Decorator that runs the command test function."""
46
47 def Decorator(test_function):
48 """Inner decorator that actually wraps the function."""
49
50 def Wrapper(command_test):
51 """Wrapper for the test function."""
52 command = cros_build_lib.CmdToStr(command_test.BuildCommand(command_name))
53 logging.info('Running test for %s.', command)
54 try:
55 test_function(command_test)
56 logging.info('Test for %s passed.', command)
57 except CommandError as e:
58 _PrintCommandLog(command, str(e))
Yiming Chen818d8f22015-04-29 11:25:24 -070059 raise TestError('Test for %s failed.' % command)
Yiming Chen3c0103a2015-03-31 11:32:35 -070060
61 return Wrapper
62
63 return Decorator
64
65
66class CommandVMTest(object):
67 """Base class for CLI command VM tests.
68
69 This class provides the abstract interface for testing CLI commands on a VM.
70 The sub-class must define the BuildCommand method in order to be usable. And
71 the test functions must use the TestCommandDecorator decorator.
72 """
73
74 def __init__(self, board, image_path):
75 """Initializes CommandVMTest.
76
77 Args:
78 board: Board for the VM to run tests.
79 image_path: Path to the image for the VM to run tests.
80 """
81 self.board = board
82 self.image_path = image_path
83 self.working_image_path = None
84 self.vm = None
85
86 def BuildCommand(self, command, device=None, pos_args=None, opt_args=None):
87 """Builds a CLI command.
88
89 Args:
90 command: The sub-command to build on (e.g. 'flash', 'deploy').
91 device: The device's address for the command.
92 pos_args: A list of positional arguments for the command.
93 opt_args: A list of optional arguments for the command.
94 """
95 raise NotImplementedError()
96
97 def SetUp(self):
98 """Creates and starts the VM instance for testing."""
99 try:
100 logging.info('Setting up the VM for testing.')
101 self.working_image_path = vm.CreateVMImage(
102 image=self.image_path, board=self.board, updatable=True)
103 self.vm = vm.VMInstance(self.working_image_path)
104 self.vm.Start()
105 logging.info('The VM has been successfully set up. Ready to run tests.')
106 except vm.VMError as e:
Yiming Chen818d8f22015-04-29 11:25:24 -0700107 raise SetupError('Failed to set up the VM for testing: %s' % e)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700108
109 def TearDown(self):
110 """Stops the VM instance after testing."""
111 try:
112 logging.info('Stopping the VM.')
113 if self.vm:
114 self.vm.Stop()
115 logging.info('The VM has been stopped.')
116 except vm.VMStopError as e:
117 logging.warning('Failed to stop the VM: %s', e)
118
Yiming Chen818d8f22015-04-29 11:25:24 -0700119 @TestCommandDecorator('devices')
120 def TestDevices(self):
121 """Tests the devices command."""
122 logging.info('Test to use devices command to set a user-friendly alias '
123 'name for the VM device.')
124 alias = 'vm_device'
125 cmd = self.BuildCommand('devices', device=self.vm.device_addr,
126 pos_args=['alias', alias])
127 result = cros_build_lib.RunCommand(cmd, capture_output=True,
128 error_code_ok=True)
129 if result.returncode:
130 logging.error('Failed to set an alias for the VM device.')
131 raise CommandError(result.error)
132
133 # Verify that the alias is set correctly.
134 with remote_access.ChromiumOSDeviceHandler(
135 remote_access.LOCALHOST, port=self.vm.port) as device:
136 if device.alias != alias:
137 logging.error('VM alias is "%s", which is not expected.', device.alias)
138 raise CommandError()
139
Yiming Chen320f7ac2015-04-02 16:14:00 -0700140 @TestCommandDecorator('shell')
141 def TestShell(self):
142 """Tests the shell command."""
143 # The path and content of a temporary file for testing shell command.
144 path = '/tmp/shell-test'
145 content = 'shell command test file'
146
147 cmd = self.BuildCommand('shell', device=self.vm.device_addr,
148 opt_args=['--no-known-hosts'])
149
150 logging.info('Test to use shell command to write a file to the VM device.')
151 write_cmd = cmd + ['--', 'echo "%s" > %s' % (content, path)]
152 result = cros_build_lib.RunCommand(write_cmd, capture_output=True,
153 error_code_ok=True)
154 if result.returncode:
Yiming Chen320f7ac2015-04-02 16:14:00 -0700155 logging.error('Failed to write the file to the VM device.')
156 raise CommandError(result.error)
157
158 logging.info('Test to use shell command to read a file on the VM device.')
159 read_cmd = cmd + ['--', 'cat %s' % path]
160 result = cros_build_lib.RunCommand(read_cmd, capture_output=True,
161 error_code_ok=True)
162 if result.returncode or result.output.rstrip() != content:
163 logging.error('Failed to read the file on the VM device.')
164 raise CommandError(result.error)
165
166 logging.info('Test to use shell command to remove a file on the VM device.')
167 remove_cmd = cmd + ['--', 'rm %s' % path]
168 result = cros_build_lib.RunCommand(remove_cmd, capture_output=True,
169 error_code_ok=True)
170 if result.returncode:
171 logging.error('Failed to remove the file on the VM device.')
172 raise CommandError(result.error)
173
Yiming Chen665e3e22015-04-09 16:42:49 -0700174 @TestCommandDecorator('debug')
175 def TestDebug(self):
176 """Tests the debug command."""
177 logging.info('Test to start and debug a new process on the VM device.')
178 exe_path = '/bin/bash'
179 start_cmd = self.BuildCommand('debug', device=self.vm.device_addr,
180 opt_args=['--exe', exe_path])
181 result = cros_build_lib.RunCommand(start_cmd, capture_output=True,
182 error_code_ok=True, input='\n')
183 if result.returncode:
184 logging.error('Failed to start and debug a new process on the VM device.')
185 raise CommandError(result.error)
186
187 logging.info('Test to attach a running process on the VM device.')
188 with remote_access.ChromiumOSDeviceHandler(
189 remote_access.LOCALHOST, port=self.vm.port) as device:
Yiming Chen818d8f22015-04-29 11:25:24 -0700190 exe = 'update_engine'
191 pids = device.GetRunningPids(exe, full_path=False)
192 if not pids:
193 logging.error('Failed to find any running process to debug.')
Yiming Chen665e3e22015-04-09 16:42:49 -0700194 raise CommandError()
Yiming Chen818d8f22015-04-29 11:25:24 -0700195 pid = pids[0]
196 attach_cmd = self.BuildCommand('debug', device=self.vm.device_addr,
197 opt_args=['--pid', str(pid)])
198 result = cros_build_lib.RunCommand(attach_cmd, capture_output=True,
199 error_code_ok=True, input='\n')
200 if result.returncode:
201 logging.error('Failed to attach a running process on the VM device.')
202 raise CommandError(result.error)
Yiming Chen665e3e22015-04-09 16:42:49 -0700203
Yiming Chen3c0103a2015-03-31 11:32:35 -0700204 @TestCommandDecorator('flash')
205 def TestFlash(self):
206 """Tests the flash command."""
207 # We explicitly disable reboot after the update because VMs sometimes do
208 # not come back after reboot. The flash command does not need to verify
209 # the integrity of the updated image. We have AU tests for that.
210 cmd = self.BuildCommand('flash', device=self.vm.device_addr,
211 pos_args=['latest'],
212 opt_args=['--no-wipe', '--no-reboot'])
213
214 logging.info('Test to flash the VM device with the latest image.')
215 result = cros_build_lib.RunCommand(cmd, capture_output=True,
216 error_code_ok=True)
217 if result.returncode:
218 logging.error('Failed to flash the VM device.')
219 raise CommandError(result.error)
220
221 @TestCommandDecorator('deploy')
222 def TestDeploy(self):
223 """Tests the deploy command."""
224 packages = ['dev-python/cherrypy', 'app-portage/portage-utils']
225 # Set the installation root to /usr/local so that the command does not
226 # attempt to remount rootfs (which leads to VM reboot).
227 cmd = self.BuildCommand('deploy', device=self.vm.device_addr,
Ralph Nathan90475a12015-05-20 13:19:01 -0700228 pos_args=packages, opt_args=['--log-level=info',
229 '--root=/usr/local'])
Yiming Chen3c0103a2015-03-31 11:32:35 -0700230
231 logging.info('Test to uninstall packages on the VM device.')
Ralph Nathan90475a12015-05-20 13:19:01 -0700232 with cros_build_lib.OutputCapturer() as output:
233 result = cros_build_lib.RunCommand(cmd + ['--unmerge'],
234 error_code_ok=True)
235
Yiming Chen3c0103a2015-03-31 11:32:35 -0700236 if result.returncode:
237 logging.error('Failed to uninstall packages on the VM device.')
238 raise CommandError(result.error)
239
Ralph Nathan90475a12015-05-20 13:19:01 -0700240 captured_output = output.GetStdout() + output.GetStderr()
241 for event in deploy.BrilloDeployOperation.UNMERGE_EVENTS:
242 if event not in captured_output:
243 logging.error('Strings used by deploy.BrilloDeployOperation to update '
244 'the progress bar have been changed. Please update the '
245 'strings in UNMERGE_EVENTS')
246 raise CommandError()
247
Yiming Chen3c0103a2015-03-31 11:32:35 -0700248 logging.info('Test to install packages on the VM device.')
Ralph Nathan90475a12015-05-20 13:19:01 -0700249 with cros_build_lib.OutputCapturer() as output:
250 result = cros_build_lib.RunCommand(cmd, error_code_ok=True)
251
Yiming Chen3c0103a2015-03-31 11:32:35 -0700252 if result.returncode:
253 logging.error('Failed to install packages on the VM device.')
254 raise CommandError(result.error)
255
Ralph Nathan90475a12015-05-20 13:19:01 -0700256 captured_output = output.GetStdout() + output.GetStderr()
257 for event in deploy.BrilloDeployOperation.MERGE_EVENTS:
258 if event not in captured_output:
259 logging.error('Strings used by deploy.BrilloDeployOperation to update '
260 'the progress bar have been changed. Please update the '
261 'strings in MERGE_EVENTS')
262 raise CommandError()
263
Yiming Chen3c0103a2015-03-31 11:32:35 -0700264 # Verify that the packages are installed.
265 with remote_access.ChromiumOSDeviceHandler(
266 remote_access.LOCALHOST, port=self.vm.port) as device:
267 try:
268 device.RunCommand(['python', '-c', '"import cherrypy"'])
269 device.RunCommand(['qmerge', '-h'])
Yiming Chen3c0103a2015-03-31 11:32:35 -0700270 except cros_build_lib.RunCommandError as e:
271 logging.error('Unable to verify packages installed on VM: %s', e)
272 raise CommandError()
273
274 def RunTests(self):
275 """Calls the test functions."""
Yiming Chen818d8f22015-04-29 11:25:24 -0700276 self.TestDevices()
Yiming Chen320f7ac2015-04-02 16:14:00 -0700277 self.TestShell()
Yiming Chen665e3e22015-04-09 16:42:49 -0700278 self.TestDebug()
Yiming Chen3c0103a2015-03-31 11:32:35 -0700279 self.TestFlash()
280 self.TestDeploy()
281
282 def Run(self):
283 """Runs the tests."""
284 try:
285 self.SetUp()
286 self.RunTests()
287 logging.info('All tests completed successfully.')
Yiming Chen3c0103a2015-03-31 11:32:35 -0700288 finally:
289 self.TearDown()