blob: 835adb15fabc5c8ff50a6e1251c8282a973520a6 [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
13from chromite.lib import cros_build_lib
14from chromite.lib import cros_logging as logging
15from chromite.lib import remote_access
16from chromite.lib import vm
17
18
19class Error(Exception):
20 """Base exception for CLI command VM tests."""
21
22
Yiming Chen818d8f22015-04-29 11:25:24 -070023class SetupError(Error):
24 """Raised when error occurs during test environment setup."""
25
26
27class TestError(Error):
28 """Raised when a command test has failed."""
29
30
Yiming Chen3c0103a2015-03-31 11:32:35 -070031class CommandError(Error):
Yiming Chen818d8f22015-04-29 11:25:24 -070032 """Raised when error occurs during a command test."""
Yiming Chen3c0103a2015-03-31 11:32:35 -070033
34
35def _PrintCommandLog(command, content):
36 """Print out the log |content| for |command|."""
37 if content:
38 logging.info('\n----------- Start of %s log -----------\n%s\n'
39 '----------- End of %s log -----------',
40 command, content.rstrip(), command)
41
42
43def TestCommandDecorator(command_name):
44 """Decorator that runs the command test function."""
45
46 def Decorator(test_function):
47 """Inner decorator that actually wraps the function."""
48
49 def Wrapper(command_test):
50 """Wrapper for the test function."""
51 command = cros_build_lib.CmdToStr(command_test.BuildCommand(command_name))
52 logging.info('Running test for %s.', command)
53 try:
54 test_function(command_test)
55 logging.info('Test for %s passed.', command)
56 except CommandError as e:
57 _PrintCommandLog(command, str(e))
Yiming Chen818d8f22015-04-29 11:25:24 -070058 raise TestError('Test for %s failed.' % command)
Yiming Chen3c0103a2015-03-31 11:32:35 -070059
60 return Wrapper
61
62 return Decorator
63
64
65class CommandVMTest(object):
66 """Base class for CLI command VM tests.
67
68 This class provides the abstract interface for testing CLI commands on a VM.
69 The sub-class must define the BuildCommand method in order to be usable. And
70 the test functions must use the TestCommandDecorator decorator.
71 """
72
73 def __init__(self, board, image_path):
74 """Initializes CommandVMTest.
75
76 Args:
77 board: Board for the VM to run tests.
78 image_path: Path to the image for the VM to run tests.
79 """
80 self.board = board
81 self.image_path = image_path
82 self.working_image_path = None
83 self.vm = None
84
85 def BuildCommand(self, command, device=None, pos_args=None, opt_args=None):
86 """Builds a CLI command.
87
88 Args:
89 command: The sub-command to build on (e.g. 'flash', 'deploy').
90 device: The device's address for the command.
91 pos_args: A list of positional arguments for the command.
92 opt_args: A list of optional arguments for the command.
93 """
94 raise NotImplementedError()
95
96 def SetUp(self):
97 """Creates and starts the VM instance for testing."""
98 try:
99 logging.info('Setting up the VM for testing.')
100 self.working_image_path = vm.CreateVMImage(
101 image=self.image_path, board=self.board, updatable=True)
102 self.vm = vm.VMInstance(self.working_image_path)
103 self.vm.Start()
104 logging.info('The VM has been successfully set up. Ready to run tests.')
105 except vm.VMError as e:
Yiming Chen818d8f22015-04-29 11:25:24 -0700106 raise SetupError('Failed to set up the VM for testing: %s' % e)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700107
108 def TearDown(self):
109 """Stops the VM instance after testing."""
110 try:
111 logging.info('Stopping the VM.')
112 if self.vm:
113 self.vm.Stop()
114 logging.info('The VM has been stopped.')
115 except vm.VMStopError as e:
116 logging.warning('Failed to stop the VM: %s', e)
117
Yiming Chen818d8f22015-04-29 11:25:24 -0700118 @TestCommandDecorator('devices')
119 def TestDevices(self):
120 """Tests the devices command."""
121 logging.info('Test to use devices command to set a user-friendly alias '
122 'name for the VM device.')
123 alias = 'vm_device'
124 cmd = self.BuildCommand('devices', device=self.vm.device_addr,
125 pos_args=['alias', alias])
126 result = cros_build_lib.RunCommand(cmd, capture_output=True,
127 error_code_ok=True)
128 if result.returncode:
129 logging.error('Failed to set an alias for the VM device.')
130 raise CommandError(result.error)
131
132 # Verify that the alias is set correctly.
133 with remote_access.ChromiumOSDeviceHandler(
134 remote_access.LOCALHOST, port=self.vm.port) as device:
135 if device.alias != alias:
136 logging.error('VM alias is "%s", which is not expected.', device.alias)
137 raise CommandError()
138
Yiming Chen320f7ac2015-04-02 16:14:00 -0700139 @TestCommandDecorator('shell')
140 def TestShell(self):
141 """Tests the shell command."""
142 # The path and content of a temporary file for testing shell command.
143 path = '/tmp/shell-test'
144 content = 'shell command test file'
145
146 cmd = self.BuildCommand('shell', device=self.vm.device_addr,
147 opt_args=['--no-known-hosts'])
148
149 logging.info('Test to use shell command to write a file to the VM device.')
150 write_cmd = cmd + ['--', 'echo "%s" > %s' % (content, path)]
151 result = cros_build_lib.RunCommand(write_cmd, capture_output=True,
152 error_code_ok=True)
153 if result.returncode:
Yiming Chen320f7ac2015-04-02 16:14:00 -0700154 logging.error('Failed to write the file to the VM device.')
155 raise CommandError(result.error)
156
157 logging.info('Test to use shell command to read a file on the VM device.')
158 read_cmd = cmd + ['--', 'cat %s' % path]
159 result = cros_build_lib.RunCommand(read_cmd, capture_output=True,
160 error_code_ok=True)
161 if result.returncode or result.output.rstrip() != content:
162 logging.error('Failed to read the file on the VM device.')
163 raise CommandError(result.error)
164
165 logging.info('Test to use shell command to remove a file on the VM device.')
166 remove_cmd = cmd + ['--', 'rm %s' % path]
167 result = cros_build_lib.RunCommand(remove_cmd, capture_output=True,
168 error_code_ok=True)
169 if result.returncode:
170 logging.error('Failed to remove the file on the VM device.')
171 raise CommandError(result.error)
172
Yiming Chen665e3e22015-04-09 16:42:49 -0700173 @TestCommandDecorator('debug')
174 def TestDebug(self):
175 """Tests the debug command."""
176 logging.info('Test to start and debug a new process on the VM device.')
177 exe_path = '/bin/bash'
178 start_cmd = self.BuildCommand('debug', device=self.vm.device_addr,
179 opt_args=['--exe', exe_path])
180 result = cros_build_lib.RunCommand(start_cmd, capture_output=True,
181 error_code_ok=True, input='\n')
182 if result.returncode:
183 logging.error('Failed to start and debug a new process on the VM device.')
184 raise CommandError(result.error)
185
186 logging.info('Test to attach a running process on the VM device.')
187 with remote_access.ChromiumOSDeviceHandler(
188 remote_access.LOCALHOST, port=self.vm.port) as device:
Yiming Chen818d8f22015-04-29 11:25:24 -0700189 exe = 'update_engine'
190 pids = device.GetRunningPids(exe, full_path=False)
191 if not pids:
192 logging.error('Failed to find any running process to debug.')
Yiming Chen665e3e22015-04-09 16:42:49 -0700193 raise CommandError()
Yiming Chen818d8f22015-04-29 11:25:24 -0700194 pid = pids[0]
195 attach_cmd = self.BuildCommand('debug', device=self.vm.device_addr,
196 opt_args=['--pid', str(pid)])
197 result = cros_build_lib.RunCommand(attach_cmd, capture_output=True,
198 error_code_ok=True, input='\n')
199 if result.returncode:
200 logging.error('Failed to attach a running process on the VM device.')
201 raise CommandError(result.error)
Yiming Chen665e3e22015-04-09 16:42:49 -0700202
Yiming Chen3c0103a2015-03-31 11:32:35 -0700203 @TestCommandDecorator('flash')
204 def TestFlash(self):
205 """Tests the flash command."""
206 # We explicitly disable reboot after the update because VMs sometimes do
207 # not come back after reboot. The flash command does not need to verify
208 # the integrity of the updated image. We have AU tests for that.
209 cmd = self.BuildCommand('flash', device=self.vm.device_addr,
210 pos_args=['latest'],
211 opt_args=['--no-wipe', '--no-reboot'])
212
213 logging.info('Test to flash the VM device with the latest image.')
214 result = cros_build_lib.RunCommand(cmd, capture_output=True,
215 error_code_ok=True)
216 if result.returncode:
217 logging.error('Failed to flash the VM device.')
218 raise CommandError(result.error)
219
220 @TestCommandDecorator('deploy')
221 def TestDeploy(self):
222 """Tests the deploy command."""
223 packages = ['dev-python/cherrypy', 'app-portage/portage-utils']
224 # Set the installation root to /usr/local so that the command does not
225 # attempt to remount rootfs (which leads to VM reboot).
226 cmd = self.BuildCommand('deploy', device=self.vm.device_addr,
227 pos_args=packages, opt_args=['--root=/usr/local'])
228
229 logging.info('Test to uninstall packages on the VM device.')
230 result = cros_build_lib.RunCommand(cmd + ['--unmerge'],
231 capture_output=True,
232 error_code_ok=True)
233 if result.returncode:
234 logging.error('Failed to uninstall packages on the VM device.')
235 raise CommandError(result.error)
236
237 logging.info('Test to install packages on the VM device.')
238 result = cros_build_lib.RunCommand(cmd, capture_output=True,
239 error_code_ok=True)
240 if result.returncode:
241 logging.error('Failed to install packages on the VM device.')
242 raise CommandError(result.error)
243
244 # Verify that the packages are installed.
245 with remote_access.ChromiumOSDeviceHandler(
246 remote_access.LOCALHOST, port=self.vm.port) as device:
247 try:
248 device.RunCommand(['python', '-c', '"import cherrypy"'])
249 device.RunCommand(['qmerge', '-h'])
Yiming Chen3c0103a2015-03-31 11:32:35 -0700250 except cros_build_lib.RunCommandError as e:
251 logging.error('Unable to verify packages installed on VM: %s', e)
252 raise CommandError()
253
254 def RunTests(self):
255 """Calls the test functions."""
Yiming Chen818d8f22015-04-29 11:25:24 -0700256 self.TestDevices()
Yiming Chen320f7ac2015-04-02 16:14:00 -0700257 self.TestShell()
Yiming Chen665e3e22015-04-09 16:42:49 -0700258 self.TestDebug()
Yiming Chen3c0103a2015-03-31 11:32:35 -0700259 self.TestFlash()
260 self.TestDeploy()
261
262 def Run(self):
263 """Runs the tests."""
264 try:
265 self.SetUp()
266 self.RunTests()
267 logging.info('All tests completed successfully.')
Yiming Chen3c0103a2015-03-31 11:32:35 -0700268 finally:
269 self.TearDown()