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