blob: 1423a5bb4233f42e580ff5b899a24f38e5db790a [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
Mike Frysinger4a243fb2020-02-21 02:56:35 -050014import sys
15
Ralph Nathan90475a12015-05-20 13:19:01 -070016from chromite.cli import deploy
Achuith Bhandarkar075d2062019-01-04 12:38:42 -080017from chromite.lib import constants
Yiming Chen3c0103a2015-03-31 11:32:35 -070018from chromite.lib import cros_build_lib
19from chromite.lib import cros_logging as logging
20from chromite.lib import remote_access
21from chromite.lib import vm
Mike Frysinger99d9ab02019-10-22 20:21:20 -040022from chromite.utils import outcap
Yiming Chen3c0103a2015-03-31 11:32:35 -070023
24
Mike Frysinger4a243fb2020-02-21 02:56:35 -050025assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
26
27
Yiming Chen3c0103a2015-03-31 11:32:35 -070028class Error(Exception):
29 """Base exception for CLI command VM tests."""
30
31
Yiming Chen818d8f22015-04-29 11:25:24 -070032class SetupError(Error):
33 """Raised when error occurs during test environment setup."""
34
35
36class TestError(Error):
37 """Raised when a command test has failed."""
38
39
Yiming Chen3c0103a2015-03-31 11:32:35 -070040class CommandError(Error):
Yiming Chen818d8f22015-04-29 11:25:24 -070041 """Raised when error occurs during a command test."""
Yiming Chen3c0103a2015-03-31 11:32:35 -070042
43
44def _PrintCommandLog(command, content):
45 """Print out the log |content| for |command|."""
46 if content:
47 logging.info('\n----------- Start of %s log -----------\n%s\n'
48 '----------- End of %s log -----------',
49 command, content.rstrip(), command)
50
51
52def TestCommandDecorator(command_name):
53 """Decorator that runs the command test function."""
54
55 def Decorator(test_function):
56 """Inner decorator that actually wraps the function."""
57
58 def Wrapper(command_test):
59 """Wrapper for the test function."""
60 command = cros_build_lib.CmdToStr(command_test.BuildCommand(command_name))
61 logging.info('Running test for %s.', command)
62 try:
63 test_function(command_test)
64 logging.info('Test for %s passed.', command)
65 except CommandError as e:
66 _PrintCommandLog(command, str(e))
Yiming Chen818d8f22015-04-29 11:25:24 -070067 raise TestError('Test for %s failed.' % command)
Yiming Chen3c0103a2015-03-31 11:32:35 -070068
69 return Wrapper
70
71 return Decorator
72
73
74class CommandVMTest(object):
75 """Base class for CLI command VM tests.
76
77 This class provides the abstract interface for testing CLI commands on a VM.
78 The sub-class must define the BuildCommand method in order to be usable. And
79 the test functions must use the TestCommandDecorator decorator.
80 """
81
82 def __init__(self, board, image_path):
83 """Initializes CommandVMTest.
84
85 Args:
86 board: Board for the VM to run tests.
87 image_path: Path to the image for the VM to run tests.
88 """
89 self.board = board
90 self.image_path = image_path
Achuith Bhandarkar075d2062019-01-04 12:38:42 -080091 self.port = None
92 self.device_addr = None
Yiming Chen3c0103a2015-03-31 11:32:35 -070093
94 def BuildCommand(self, command, device=None, pos_args=None, opt_args=None):
95 """Builds a CLI command.
96
97 Args:
98 command: The sub-command to build on (e.g. 'flash', 'deploy').
99 device: The device's address for the command.
100 pos_args: A list of positional arguments for the command.
101 opt_args: A list of optional arguments for the command.
102 """
103 raise NotImplementedError()
104
105 def SetUp(self):
106 """Creates and starts the VM instance for testing."""
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800107 self.port = remote_access.GetUnusedPort()
108 self.device_addr = 'ssh://%s:%d' % (remote_access.LOCALHOST, self.port)
109 vm_path = vm.CreateVMImage(image=self.image_path, board=self.board,
110 updatable=True)
111 vm_cmd = ['./cros_vm', '--ssh-port=%d' % self.port, '--copy-on-write',
112 '--image-path=%s' % vm_path, '--start']
Mike Frysinger45602c72019-09-22 02:15:11 -0400113 cros_build_lib.run(vm_cmd, cwd=constants.CHROMITE_BIN_DIR)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700114
115 def TearDown(self):
116 """Stops the VM instance after testing."""
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800117 if not self.port:
118 return
Mike Frysinger45602c72019-09-22 02:15:11 -0400119 cros_build_lib.run(['./cros_vm', '--stop', '--ssh-port=%d' % self.port],
120 cwd=constants.CHROMITE_BIN_DIR,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500121 check=False)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700122
Yiming Chen320f7ac2015-04-02 16:14:00 -0700123 @TestCommandDecorator('shell')
124 def TestShell(self):
125 """Tests the shell command."""
126 # The path and content of a temporary file for testing shell command.
127 path = '/tmp/shell-test'
128 content = 'shell command test file'
129
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800130 cmd = self.BuildCommand('shell', device=self.device_addr,
Yiming Chen320f7ac2015-04-02 16:14:00 -0700131 opt_args=['--no-known-hosts'])
132
133 logging.info('Test to use shell command to write a file to the VM device.')
134 write_cmd = cmd + ['--', 'echo "%s" > %s' % (content, path)]
Mike Frysinger45602c72019-09-22 02:15:11 -0400135 result = cros_build_lib.run(write_cmd, capture_output=True,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500136 check=False)
Yiming Chen320f7ac2015-04-02 16:14:00 -0700137 if result.returncode:
Yiming Chen320f7ac2015-04-02 16:14:00 -0700138 logging.error('Failed to write the file to the VM device.')
139 raise CommandError(result.error)
140
141 logging.info('Test to use shell command to read a file on the VM device.')
142 read_cmd = cmd + ['--', 'cat %s' % path]
Shao-Chuan Lee8e76ccd2020-03-04 10:41:02 +0900143 result = cros_build_lib.run(read_cmd, capture_output=True, encoding='utf-8',
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500144 check=False)
Yiming Chen320f7ac2015-04-02 16:14:00 -0700145 if result.returncode or result.output.rstrip() != content:
146 logging.error('Failed to read the file on the VM device.')
147 raise CommandError(result.error)
148
149 logging.info('Test to use shell command to remove a file on the VM device.')
150 remove_cmd = cmd + ['--', 'rm %s' % path]
Mike Frysinger45602c72019-09-22 02:15:11 -0400151 result = cros_build_lib.run(remove_cmd, capture_output=True,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500152 check=False)
Yiming Chen320f7ac2015-04-02 16:14:00 -0700153 if result.returncode:
154 logging.error('Failed to remove the file on the VM device.')
155 raise CommandError(result.error)
156
Yiming Chen665e3e22015-04-09 16:42:49 -0700157 @TestCommandDecorator('debug')
158 def TestDebug(self):
159 """Tests the debug command."""
160 logging.info('Test to start and debug a new process on the VM device.')
161 exe_path = '/bin/bash'
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800162 start_cmd = self.BuildCommand('debug', device=self.device_addr,
Yiming Chen665e3e22015-04-09 16:42:49 -0700163 opt_args=['--exe', exe_path])
Mike Frysinger45602c72019-09-22 02:15:11 -0400164 result = cros_build_lib.run(start_cmd, capture_output=True,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500165 check=False, input='\n')
Yiming Chen665e3e22015-04-09 16:42:49 -0700166 if result.returncode:
167 logging.error('Failed to start and debug a new process on the VM device.')
168 raise CommandError(result.error)
169
170 logging.info('Test to attach a running process on the VM device.')
171 with remote_access.ChromiumOSDeviceHandler(
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800172 remote_access.LOCALHOST, port=self.port) as device:
Yiming Chen818d8f22015-04-29 11:25:24 -0700173 exe = 'update_engine'
174 pids = device.GetRunningPids(exe, full_path=False)
175 if not pids:
176 logging.error('Failed to find any running process to debug.')
Yiming Chen665e3e22015-04-09 16:42:49 -0700177 raise CommandError()
Yiming Chen818d8f22015-04-29 11:25:24 -0700178 pid = pids[0]
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800179 attach_cmd = self.BuildCommand('debug', device=self.device_addr,
Yiming Chen818d8f22015-04-29 11:25:24 -0700180 opt_args=['--pid', str(pid)])
Mike Frysinger45602c72019-09-22 02:15:11 -0400181 result = cros_build_lib.run(attach_cmd, capture_output=True,
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500182 check=False, input='\n')
Yiming Chen818d8f22015-04-29 11:25:24 -0700183 if result.returncode:
184 logging.error('Failed to attach a running process on the VM device.')
185 raise CommandError(result.error)
Yiming Chen665e3e22015-04-09 16:42:49 -0700186
Yiming Chen3c0103a2015-03-31 11:32:35 -0700187 @TestCommandDecorator('flash')
188 def TestFlash(self):
189 """Tests the flash command."""
190 # We explicitly disable reboot after the update because VMs sometimes do
191 # not come back after reboot. The flash command does not need to verify
192 # the integrity of the updated image. We have AU tests for that.
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800193 cmd = self.BuildCommand('flash', device=self.device_addr,
Yiming Chen3c0103a2015-03-31 11:32:35 -0700194 pos_args=['latest'],
195 opt_args=['--no-wipe', '--no-reboot'])
196
197 logging.info('Test to flash the VM device with the latest image.')
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500198 result = cros_build_lib.run(cmd, capture_output=True, check=False)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700199 if result.returncode:
200 logging.error('Failed to flash the VM device.')
201 raise CommandError(result.error)
202
203 @TestCommandDecorator('deploy')
204 def TestDeploy(self):
205 """Tests the deploy command."""
206 packages = ['dev-python/cherrypy', 'app-portage/portage-utils']
207 # Set the installation root to /usr/local so that the command does not
208 # attempt to remount rootfs (which leads to VM reboot).
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800209 cmd = self.BuildCommand('deploy', device=self.device_addr,
Ralph Nathan90475a12015-05-20 13:19:01 -0700210 pos_args=packages, opt_args=['--log-level=info',
211 '--root=/usr/local'])
Yiming Chen3c0103a2015-03-31 11:32:35 -0700212
213 logging.info('Test to uninstall packages on the VM device.')
Mike Frysinger99d9ab02019-10-22 20:21:20 -0400214 with outcap.OutputCapturer() as output:
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500215 result = cros_build_lib.run(cmd + ['--unmerge'], check=False)
Ralph Nathan90475a12015-05-20 13:19:01 -0700216
Yiming Chen3c0103a2015-03-31 11:32:35 -0700217 if result.returncode:
218 logging.error('Failed to uninstall packages on the VM device.')
219 raise CommandError(result.error)
220
Ralph Nathan90475a12015-05-20 13:19:01 -0700221 captured_output = output.GetStdout() + output.GetStderr()
222 for event in deploy.BrilloDeployOperation.UNMERGE_EVENTS:
223 if event not in captured_output:
224 logging.error('Strings used by deploy.BrilloDeployOperation to update '
225 'the progress bar have been changed. Please update the '
226 'strings in UNMERGE_EVENTS')
227 raise CommandError()
228
Yiming Chen3c0103a2015-03-31 11:32:35 -0700229 logging.info('Test to install packages on the VM device.')
Mike Frysinger99d9ab02019-10-22 20:21:20 -0400230 with outcap.OutputCapturer() as output:
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500231 result = cros_build_lib.run(cmd, check=False)
Ralph Nathan90475a12015-05-20 13:19:01 -0700232
Yiming Chen3c0103a2015-03-31 11:32:35 -0700233 if result.returncode:
234 logging.error('Failed to install packages on the VM device.')
235 raise CommandError(result.error)
236
Ralph Nathan90475a12015-05-20 13:19:01 -0700237 captured_output = output.GetStdout() + output.GetStderr()
238 for event in deploy.BrilloDeployOperation.MERGE_EVENTS:
239 if event not in captured_output:
240 logging.error('Strings used by deploy.BrilloDeployOperation to update '
241 'the progress bar have been changed. Please update the '
242 'strings in MERGE_EVENTS')
243 raise CommandError()
244
Yiming Chen3c0103a2015-03-31 11:32:35 -0700245 # Verify that the packages are installed.
246 with remote_access.ChromiumOSDeviceHandler(
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800247 remote_access.LOCALHOST, port=self.port) as device:
Yiming Chen3c0103a2015-03-31 11:32:35 -0700248 try:
249 device.RunCommand(['python', '-c', '"import cherrypy"'])
250 device.RunCommand(['qmerge', '-h'])
Yiming Chen3c0103a2015-03-31 11:32:35 -0700251 except cros_build_lib.RunCommandError as e:
252 logging.error('Unable to verify packages installed on VM: %s', e)
253 raise CommandError()
254
255 def RunTests(self):
256 """Calls the test functions."""
Yiming Chen320f7ac2015-04-02 16:14:00 -0700257 self.TestShell()
Achuith Bhandarkar3fa46de2019-07-24 12:07:40 -0700258 # TestDebug broken (crbug.com/863122)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700259 self.TestFlash()
Achuith Bhandarkar4153ab32019-08-01 12:21:59 -0700260 self.TestDeploy()
Yiming Chen3c0103a2015-03-31 11:32:35 -0700261
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()