blob: 5599fb0a04411c352683b80af73c471d5401e222 [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
Ralph Nathan90475a12015-05-20 13:19:01 -070011from chromite.cli import deploy
Achuith Bhandarkar075d2062019-01-04 12:38:42 -080012from chromite.lib import constants
Yiming Chen3c0103a2015-03-31 11:32:35 -070013from 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
Mike Frysinger99d9ab02019-10-22 20:21:20 -040017from chromite.utils import outcap
Yiming Chen3c0103a2015-03-31 11:32:35 -070018
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
Achuith Bhandarkar075d2062019-01-04 12:38:42 -080083 self.port = None
84 self.device_addr = None
Yiming Chen3c0103a2015-03-31 11:32:35 -070085
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."""
Achuith Bhandarkar075d2062019-01-04 12:38:42 -080099 self.port = remote_access.GetUnusedPort()
100 self.device_addr = 'ssh://%s:%d' % (remote_access.LOCALHOST, self.port)
101 vm_path = vm.CreateVMImage(image=self.image_path, board=self.board,
102 updatable=True)
103 vm_cmd = ['./cros_vm', '--ssh-port=%d' % self.port, '--copy-on-write',
Shao-Chuan Leed60df3a2020-06-29 14:04:13 +0900104 '--board=%s' % self.board,
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800105 '--image-path=%s' % vm_path, '--start']
Mike Frysinger45602c72019-09-22 02:15:11 -0400106 cros_build_lib.run(vm_cmd, cwd=constants.CHROMITE_BIN_DIR)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700107
108 def TearDown(self):
109 """Stops the VM instance after testing."""
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800110 if not self.port:
111 return
Mike Frysinger45602c72019-09-22 02:15:11 -0400112 cros_build_lib.run(['./cros_vm', '--stop', '--ssh-port=%d' % self.port],
113 cwd=constants.CHROMITE_BIN_DIR,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500114 check=False)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700115
Yiming Chen320f7ac2015-04-02 16:14:00 -0700116 @TestCommandDecorator('shell')
117 def TestShell(self):
118 """Tests the shell command."""
119 # The path and content of a temporary file for testing shell command.
120 path = '/tmp/shell-test'
121 content = 'shell command test file'
122
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800123 cmd = self.BuildCommand('shell', device=self.device_addr,
Yiming Chen320f7ac2015-04-02 16:14:00 -0700124 opt_args=['--no-known-hosts'])
125
126 logging.info('Test to use shell command to write a file to the VM device.')
127 write_cmd = cmd + ['--', 'echo "%s" > %s' % (content, path)]
Mike Frysinger45602c72019-09-22 02:15:11 -0400128 result = cros_build_lib.run(write_cmd, capture_output=True,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500129 check=False)
Yiming Chen320f7ac2015-04-02 16:14:00 -0700130 if result.returncode:
Yiming Chen320f7ac2015-04-02 16:14:00 -0700131 logging.error('Failed to write the file to the VM device.')
132 raise CommandError(result.error)
133
134 logging.info('Test to use shell command to read a file on the VM device.')
135 read_cmd = cmd + ['--', 'cat %s' % path]
Shao-Chuan Lee8e76ccd2020-03-04 10:41:02 +0900136 result = cros_build_lib.run(read_cmd, capture_output=True, encoding='utf-8',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500137 check=False)
Yiming Chen320f7ac2015-04-02 16:14:00 -0700138 if result.returncode or result.output.rstrip() != content:
139 logging.error('Failed to read the file on the VM device.')
140 raise CommandError(result.error)
141
142 logging.info('Test to use shell command to remove a file on the VM device.')
143 remove_cmd = cmd + ['--', 'rm %s' % path]
Mike Frysinger45602c72019-09-22 02:15:11 -0400144 result = cros_build_lib.run(remove_cmd, capture_output=True,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500145 check=False)
Yiming Chen320f7ac2015-04-02 16:14:00 -0700146 if result.returncode:
147 logging.error('Failed to remove the file on the VM device.')
148 raise CommandError(result.error)
149
Yiming Chen665e3e22015-04-09 16:42:49 -0700150 @TestCommandDecorator('debug')
151 def TestDebug(self):
152 """Tests the debug command."""
153 logging.info('Test to start and debug a new process on the VM device.')
154 exe_path = '/bin/bash'
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800155 start_cmd = self.BuildCommand('debug', device=self.device_addr,
Yiming Chen665e3e22015-04-09 16:42:49 -0700156 opt_args=['--exe', exe_path])
Mike Frysinger45602c72019-09-22 02:15:11 -0400157 result = cros_build_lib.run(start_cmd, capture_output=True,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500158 check=False, input='\n')
Yiming Chen665e3e22015-04-09 16:42:49 -0700159 if result.returncode:
160 logging.error('Failed to start and debug a new process on the VM device.')
161 raise CommandError(result.error)
162
163 logging.info('Test to attach a running process on the VM device.')
164 with remote_access.ChromiumOSDeviceHandler(
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800165 remote_access.LOCALHOST, port=self.port) as device:
Yiming Chen818d8f22015-04-29 11:25:24 -0700166 exe = 'update_engine'
167 pids = device.GetRunningPids(exe, full_path=False)
168 if not pids:
169 logging.error('Failed to find any running process to debug.')
Yiming Chen665e3e22015-04-09 16:42:49 -0700170 raise CommandError()
Yiming Chen818d8f22015-04-29 11:25:24 -0700171 pid = pids[0]
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800172 attach_cmd = self.BuildCommand('debug', device=self.device_addr,
Yiming Chen818d8f22015-04-29 11:25:24 -0700173 opt_args=['--pid', str(pid)])
Mike Frysinger45602c72019-09-22 02:15:11 -0400174 result = cros_build_lib.run(attach_cmd, capture_output=True,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500175 check=False, input='\n')
Yiming Chen818d8f22015-04-29 11:25:24 -0700176 if result.returncode:
177 logging.error('Failed to attach a running process on the VM device.')
178 raise CommandError(result.error)
Yiming Chen665e3e22015-04-09 16:42:49 -0700179
Yiming Chen3c0103a2015-03-31 11:32:35 -0700180 @TestCommandDecorator('flash')
181 def TestFlash(self):
182 """Tests the flash command."""
183 # We explicitly disable reboot after the update because VMs sometimes do
184 # not come back after reboot. The flash command does not need to verify
185 # the integrity of the updated image. We have AU tests for that.
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800186 cmd = self.BuildCommand('flash', device=self.device_addr,
Yiming Chen3c0103a2015-03-31 11:32:35 -0700187 pos_args=['latest'],
188 opt_args=['--no-wipe', '--no-reboot'])
189
190 logging.info('Test to flash the VM device with the latest image.')
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500191 result = cros_build_lib.run(cmd, capture_output=True, check=False)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700192 if result.returncode:
193 logging.error('Failed to flash the VM device.')
194 raise CommandError(result.error)
195
196 @TestCommandDecorator('deploy')
197 def TestDeploy(self):
198 """Tests the deploy command."""
199 packages = ['dev-python/cherrypy', 'app-portage/portage-utils']
200 # Set the installation root to /usr/local so that the command does not
201 # attempt to remount rootfs (which leads to VM reboot).
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800202 cmd = self.BuildCommand('deploy', device=self.device_addr,
Ralph Nathan90475a12015-05-20 13:19:01 -0700203 pos_args=packages, opt_args=['--log-level=info',
204 '--root=/usr/local'])
Yiming Chen3c0103a2015-03-31 11:32:35 -0700205
206 logging.info('Test to uninstall packages on the VM device.')
Mike Frysinger99d9ab02019-10-22 20:21:20 -0400207 with outcap.OutputCapturer() as output:
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500208 result = cros_build_lib.run(cmd + ['--unmerge'], check=False)
Ralph Nathan90475a12015-05-20 13:19:01 -0700209
Yiming Chen3c0103a2015-03-31 11:32:35 -0700210 if result.returncode:
211 logging.error('Failed to uninstall packages on the VM device.')
212 raise CommandError(result.error)
213
Ralph Nathan90475a12015-05-20 13:19:01 -0700214 captured_output = output.GetStdout() + output.GetStderr()
215 for event in deploy.BrilloDeployOperation.UNMERGE_EVENTS:
216 if event not in captured_output:
217 logging.error('Strings used by deploy.BrilloDeployOperation to update '
218 'the progress bar have been changed. Please update the '
219 'strings in UNMERGE_EVENTS')
220 raise CommandError()
221
Yiming Chen3c0103a2015-03-31 11:32:35 -0700222 logging.info('Test to install packages on the VM device.')
Mike Frysinger99d9ab02019-10-22 20:21:20 -0400223 with outcap.OutputCapturer() as output:
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500224 result = cros_build_lib.run(cmd, check=False)
Ralph Nathan90475a12015-05-20 13:19:01 -0700225
Yiming Chen3c0103a2015-03-31 11:32:35 -0700226 if result.returncode:
227 logging.error('Failed to install packages on the VM device.')
228 raise CommandError(result.error)
229
Ralph Nathan90475a12015-05-20 13:19:01 -0700230 captured_output = output.GetStdout() + output.GetStderr()
231 for event in deploy.BrilloDeployOperation.MERGE_EVENTS:
232 if event not in captured_output:
233 logging.error('Strings used by deploy.BrilloDeployOperation to update '
234 'the progress bar have been changed. Please update the '
235 'strings in MERGE_EVENTS')
236 raise CommandError()
237
Yiming Chen3c0103a2015-03-31 11:32:35 -0700238 # Verify that the packages are installed.
239 with remote_access.ChromiumOSDeviceHandler(
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800240 remote_access.LOCALHOST, port=self.port) as device:
Yiming Chen3c0103a2015-03-31 11:32:35 -0700241 try:
Mike Frysinger3459bf52020-03-31 00:52:11 -0400242 device.run(['python', '-c', '"import cherrypy"'])
243 device.run(['qmerge', '-h'])
Yiming Chen3c0103a2015-03-31 11:32:35 -0700244 except cros_build_lib.RunCommandError as e:
245 logging.error('Unable to verify packages installed on VM: %s', e)
246 raise CommandError()
247
248 def RunTests(self):
249 """Calls the test functions."""
Yiming Chen320f7ac2015-04-02 16:14:00 -0700250 self.TestShell()
Achuith Bhandarkar3fa46de2019-07-24 12:07:40 -0700251 # TestDebug broken (crbug.com/863122)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700252 self.TestFlash()
Achuith Bhandarkar4153ab32019-08-01 12:21:59 -0700253 self.TestDeploy()
Yiming Chen3c0103a2015-03-31 11:32:35 -0700254
255 def Run(self):
256 """Runs the tests."""
257 try:
258 self.SetUp()
259 self.RunTests()
260 logging.info('All tests completed successfully.')
Yiming Chen3c0103a2015-03-31 11:32:35 -0700261 finally:
262 self.TearDown()