blob: 9ea77ebd044a3d3ac7e9cb3a39525f1adc737b90 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Yiming Chen3c0103a2015-03-31 11:32:35 -07002# Copyright 2015 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Module for integration VM tests for CLI commands.
7
8This module contains the basic functionalities for setting up a VM and testing
9the CLI commands.
10"""
11
12from __future__ import print_function
13
Ralph Nathan90475a12015-05-20 13:19:01 -070014from chromite.cli import deploy
Yiming Chen3c0103a2015-03-31 11:32:35 -070015from chromite.lib import cros_build_lib
16from chromite.lib import cros_logging as logging
17from chromite.lib import remote_access
18from chromite.lib import vm
19
20
21class Error(Exception):
22 """Base exception for CLI command VM tests."""
23
24
Yiming Chen818d8f22015-04-29 11:25:24 -070025class SetupError(Error):
26 """Raised when error occurs during test environment setup."""
27
28
29class TestError(Error):
30 """Raised when a command test has failed."""
31
32
Yiming Chen3c0103a2015-03-31 11:32:35 -070033class CommandError(Error):
Yiming Chen818d8f22015-04-29 11:25:24 -070034 """Raised when error occurs during a command test."""
Yiming Chen3c0103a2015-03-31 11:32:35 -070035
36
37def _PrintCommandLog(command, content):
38 """Print out the log |content| for |command|."""
39 if content:
40 logging.info('\n----------- Start of %s log -----------\n%s\n'
41 '----------- End of %s log -----------',
42 command, content.rstrip(), command)
43
44
45def TestCommandDecorator(command_name):
46 """Decorator that runs the command test function."""
47
48 def Decorator(test_function):
49 """Inner decorator that actually wraps the function."""
50
51 def Wrapper(command_test):
52 """Wrapper for the test function."""
53 command = cros_build_lib.CmdToStr(command_test.BuildCommand(command_name))
54 logging.info('Running test for %s.', command)
55 try:
56 test_function(command_test)
57 logging.info('Test for %s passed.', command)
58 except CommandError as e:
59 _PrintCommandLog(command, str(e))
Yiming Chen818d8f22015-04-29 11:25:24 -070060 raise TestError('Test for %s failed.' % command)
Yiming Chen3c0103a2015-03-31 11:32:35 -070061
62 return Wrapper
63
64 return Decorator
65
66
67class CommandVMTest(object):
68 """Base class for CLI command VM tests.
69
70 This class provides the abstract interface for testing CLI commands on a VM.
71 The sub-class must define the BuildCommand method in order to be usable. And
72 the test functions must use the TestCommandDecorator decorator.
73 """
74
75 def __init__(self, board, image_path):
76 """Initializes CommandVMTest.
77
78 Args:
79 board: Board for the VM to run tests.
80 image_path: Path to the image for the VM to run tests.
81 """
82 self.board = board
83 self.image_path = image_path
84 self.working_image_path = None
85 self.vm = None
86
87 def BuildCommand(self, command, device=None, pos_args=None, opt_args=None):
88 """Builds a CLI command.
89
90 Args:
91 command: The sub-command to build on (e.g. 'flash', 'deploy').
92 device: The device's address for the command.
93 pos_args: A list of positional arguments for the command.
94 opt_args: A list of optional arguments for the command.
95 """
96 raise NotImplementedError()
97
98 def SetUp(self):
99 """Creates and starts the VM instance for testing."""
100 try:
101 logging.info('Setting up the VM for testing.')
102 self.working_image_path = vm.CreateVMImage(
103 image=self.image_path, board=self.board, updatable=True)
104 self.vm = vm.VMInstance(self.working_image_path)
105 self.vm.Start()
106 logging.info('The VM has been successfully set up. Ready to run tests.')
107 except vm.VMError as e:
Yiming Chen818d8f22015-04-29 11:25:24 -0700108 raise SetupError('Failed to set up the VM for testing: %s' % e)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700109
110 def TearDown(self):
111 """Stops the VM instance after testing."""
112 try:
113 logging.info('Stopping the VM.')
114 if self.vm:
115 self.vm.Stop()
116 logging.info('The VM has been stopped.')
117 except vm.VMStopError as e:
118 logging.warning('Failed to stop the VM: %s', e)
119
Yiming Chen320f7ac2015-04-02 16:14:00 -0700120 @TestCommandDecorator('shell')
121 def TestShell(self):
122 """Tests the shell command."""
123 # The path and content of a temporary file for testing shell command.
124 path = '/tmp/shell-test'
125 content = 'shell command test file'
126
127 cmd = self.BuildCommand('shell', device=self.vm.device_addr,
128 opt_args=['--no-known-hosts'])
129
130 logging.info('Test to use shell command to write a file to the VM device.')
131 write_cmd = cmd + ['--', 'echo "%s" > %s' % (content, path)]
132 result = cros_build_lib.RunCommand(write_cmd, capture_output=True,
133 error_code_ok=True)
134 if result.returncode:
Yiming Chen320f7ac2015-04-02 16:14:00 -0700135 logging.error('Failed to write the file to the VM device.')
136 raise CommandError(result.error)
137
138 logging.info('Test to use shell command to read a file on the VM device.')
139 read_cmd = cmd + ['--', 'cat %s' % path]
140 result = cros_build_lib.RunCommand(read_cmd, capture_output=True,
141 error_code_ok=True)
142 if result.returncode or result.output.rstrip() != content:
143 logging.error('Failed to read the file on the VM device.')
144 raise CommandError(result.error)
145
146 logging.info('Test to use shell command to remove a file on the VM device.')
147 remove_cmd = cmd + ['--', 'rm %s' % path]
148 result = cros_build_lib.RunCommand(remove_cmd, capture_output=True,
149 error_code_ok=True)
150 if result.returncode:
151 logging.error('Failed to remove the file on the VM device.')
152 raise CommandError(result.error)
153
Yiming Chen665e3e22015-04-09 16:42:49 -0700154 @TestCommandDecorator('debug')
155 def TestDebug(self):
156 """Tests the debug command."""
157 logging.info('Test to start and debug a new process on the VM device.')
158 exe_path = '/bin/bash'
159 start_cmd = self.BuildCommand('debug', device=self.vm.device_addr,
160 opt_args=['--exe', exe_path])
161 result = cros_build_lib.RunCommand(start_cmd, capture_output=True,
162 error_code_ok=True, input='\n')
163 if result.returncode:
164 logging.error('Failed to start and debug a new process on the VM device.')
165 raise CommandError(result.error)
166
167 logging.info('Test to attach a running process on the VM device.')
168 with remote_access.ChromiumOSDeviceHandler(
169 remote_access.LOCALHOST, port=self.vm.port) as device:
Yiming Chen818d8f22015-04-29 11:25:24 -0700170 exe = 'update_engine'
171 pids = device.GetRunningPids(exe, full_path=False)
172 if not pids:
173 logging.error('Failed to find any running process to debug.')
Yiming Chen665e3e22015-04-09 16:42:49 -0700174 raise CommandError()
Yiming Chen818d8f22015-04-29 11:25:24 -0700175 pid = pids[0]
176 attach_cmd = self.BuildCommand('debug', device=self.vm.device_addr,
177 opt_args=['--pid', str(pid)])
178 result = cros_build_lib.RunCommand(attach_cmd, capture_output=True,
179 error_code_ok=True, input='\n')
180 if result.returncode:
181 logging.error('Failed to attach a running process on the VM device.')
182 raise CommandError(result.error)
Yiming Chen665e3e22015-04-09 16:42:49 -0700183
Yiming Chen3c0103a2015-03-31 11:32:35 -0700184 @TestCommandDecorator('flash')
185 def TestFlash(self):
186 """Tests the flash command."""
187 # We explicitly disable reboot after the update because VMs sometimes do
188 # not come back after reboot. The flash command does not need to verify
189 # the integrity of the updated image. We have AU tests for that.
190 cmd = self.BuildCommand('flash', device=self.vm.device_addr,
191 pos_args=['latest'],
192 opt_args=['--no-wipe', '--no-reboot'])
193
194 logging.info('Test to flash the VM device with the latest image.')
195 result = cros_build_lib.RunCommand(cmd, capture_output=True,
196 error_code_ok=True)
197 if result.returncode:
198 logging.error('Failed to flash the VM device.')
199 raise CommandError(result.error)
200
201 @TestCommandDecorator('deploy')
202 def TestDeploy(self):
203 """Tests the deploy command."""
204 packages = ['dev-python/cherrypy', 'app-portage/portage-utils']
205 # Set the installation root to /usr/local so that the command does not
206 # attempt to remount rootfs (which leads to VM reboot).
207 cmd = self.BuildCommand('deploy', device=self.vm.device_addr,
Ralph Nathan90475a12015-05-20 13:19:01 -0700208 pos_args=packages, opt_args=['--log-level=info',
209 '--root=/usr/local'])
Yiming Chen3c0103a2015-03-31 11:32:35 -0700210
211 logging.info('Test to uninstall packages on the VM device.')
Ralph Nathan90475a12015-05-20 13:19:01 -0700212 with cros_build_lib.OutputCapturer() as output:
213 result = cros_build_lib.RunCommand(cmd + ['--unmerge'],
214 error_code_ok=True)
215
Yiming Chen3c0103a2015-03-31 11:32:35 -0700216 if result.returncode:
217 logging.error('Failed to uninstall packages on the VM device.')
218 raise CommandError(result.error)
219
Ralph Nathan90475a12015-05-20 13:19:01 -0700220 captured_output = output.GetStdout() + output.GetStderr()
221 for event in deploy.BrilloDeployOperation.UNMERGE_EVENTS:
222 if event not in captured_output:
223 logging.error('Strings used by deploy.BrilloDeployOperation to update '
224 'the progress bar have been changed. Please update the '
225 'strings in UNMERGE_EVENTS')
226 raise CommandError()
227
Yiming Chen3c0103a2015-03-31 11:32:35 -0700228 logging.info('Test to install packages on the VM device.')
Ralph Nathan90475a12015-05-20 13:19:01 -0700229 with cros_build_lib.OutputCapturer() as output:
230 result = cros_build_lib.RunCommand(cmd, error_code_ok=True)
231
Yiming Chen3c0103a2015-03-31 11:32:35 -0700232 if result.returncode:
233 logging.error('Failed to install packages on the VM device.')
234 raise CommandError(result.error)
235
Ralph Nathan90475a12015-05-20 13:19:01 -0700236 captured_output = output.GetStdout() + output.GetStderr()
237 for event in deploy.BrilloDeployOperation.MERGE_EVENTS:
238 if event not in captured_output:
239 logging.error('Strings used by deploy.BrilloDeployOperation to update '
240 'the progress bar have been changed. Please update the '
241 'strings in MERGE_EVENTS')
242 raise CommandError()
243
Yiming Chen3c0103a2015-03-31 11:32:35 -0700244 # 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 Chen320f7ac2015-04-02 16:14:00 -0700256 self.TestShell()
Matthias Kaehlckea45e2dc2018-07-13 09:16:20 -0700257 # 'cros debug' is currently broken (https://crbug.com/863122)
258 # 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()