blob: e1a6aee25d407bc52d3cac9c985927cbb6234dc3 [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
Achuith Bhandarkar075d2062019-01-04 12:38:42 -080015from chromite.lib import constants
Yiming Chen3c0103a2015-03-31 11:32:35 -070016from chromite.lib import cros_build_lib
17from chromite.lib import cros_logging as logging
18from chromite.lib import remote_access
19from chromite.lib import vm
Mike Frysinger99d9ab02019-10-22 20:21:20 -040020from chromite.utils import outcap
Yiming Chen3c0103a2015-03-31 11:32:35 -070021
22
23class Error(Exception):
24 """Base exception for CLI command VM tests."""
25
26
Yiming Chen818d8f22015-04-29 11:25:24 -070027class SetupError(Error):
28 """Raised when error occurs during test environment setup."""
29
30
31class TestError(Error):
32 """Raised when a command test has failed."""
33
34
Yiming Chen3c0103a2015-03-31 11:32:35 -070035class CommandError(Error):
Yiming Chen818d8f22015-04-29 11:25:24 -070036 """Raised when error occurs during a command test."""
Yiming Chen3c0103a2015-03-31 11:32:35 -070037
38
39def _PrintCommandLog(command, content):
40 """Print out the log |content| for |command|."""
41 if content:
42 logging.info('\n----------- Start of %s log -----------\n%s\n'
43 '----------- End of %s log -----------',
44 command, content.rstrip(), command)
45
46
47def TestCommandDecorator(command_name):
48 """Decorator that runs the command test function."""
49
50 def Decorator(test_function):
51 """Inner decorator that actually wraps the function."""
52
53 def Wrapper(command_test):
54 """Wrapper for the test function."""
55 command = cros_build_lib.CmdToStr(command_test.BuildCommand(command_name))
56 logging.info('Running test for %s.', command)
57 try:
58 test_function(command_test)
59 logging.info('Test for %s passed.', command)
60 except CommandError as e:
61 _PrintCommandLog(command, str(e))
Yiming Chen818d8f22015-04-29 11:25:24 -070062 raise TestError('Test for %s failed.' % command)
Yiming Chen3c0103a2015-03-31 11:32:35 -070063
64 return Wrapper
65
66 return Decorator
67
68
69class CommandVMTest(object):
70 """Base class for CLI command VM tests.
71
72 This class provides the abstract interface for testing CLI commands on a VM.
73 The sub-class must define the BuildCommand method in order to be usable. And
74 the test functions must use the TestCommandDecorator decorator.
75 """
76
77 def __init__(self, board, image_path):
78 """Initializes CommandVMTest.
79
80 Args:
81 board: Board for the VM to run tests.
82 image_path: Path to the image for the VM to run tests.
83 """
84 self.board = board
85 self.image_path = image_path
Achuith Bhandarkar075d2062019-01-04 12:38:42 -080086 self.port = None
87 self.device_addr = None
Yiming Chen3c0103a2015-03-31 11:32:35 -070088
89 def BuildCommand(self, command, device=None, pos_args=None, opt_args=None):
90 """Builds a CLI command.
91
92 Args:
93 command: The sub-command to build on (e.g. 'flash', 'deploy').
94 device: The device's address for the command.
95 pos_args: A list of positional arguments for the command.
96 opt_args: A list of optional arguments for the command.
97 """
98 raise NotImplementedError()
99
100 def SetUp(self):
101 """Creates and starts the VM instance for testing."""
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800102 self.port = remote_access.GetUnusedPort()
103 self.device_addr = 'ssh://%s:%d' % (remote_access.LOCALHOST, self.port)
104 vm_path = vm.CreateVMImage(image=self.image_path, board=self.board,
105 updatable=True)
106 vm_cmd = ['./cros_vm', '--ssh-port=%d' % self.port, '--copy-on-write',
107 '--image-path=%s' % vm_path, '--start']
Mike Frysinger45602c72019-09-22 02:15:11 -0400108 cros_build_lib.run(vm_cmd, cwd=constants.CHROMITE_BIN_DIR)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700109
110 def TearDown(self):
111 """Stops the VM instance after testing."""
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800112 if not self.port:
113 return
Mike Frysinger45602c72019-09-22 02:15:11 -0400114 cros_build_lib.run(['./cros_vm', '--stop', '--ssh-port=%d' % self.port],
115 cwd=constants.CHROMITE_BIN_DIR,
116 error_code_ok=True)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700117
Yiming Chen320f7ac2015-04-02 16:14:00 -0700118 @TestCommandDecorator('shell')
119 def TestShell(self):
120 """Tests the shell command."""
121 # The path and content of a temporary file for testing shell command.
122 path = '/tmp/shell-test'
123 content = 'shell command test file'
124
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800125 cmd = self.BuildCommand('shell', device=self.device_addr,
Yiming Chen320f7ac2015-04-02 16:14:00 -0700126 opt_args=['--no-known-hosts'])
127
128 logging.info('Test to use shell command to write a file to the VM device.')
129 write_cmd = cmd + ['--', 'echo "%s" > %s' % (content, path)]
Mike Frysinger45602c72019-09-22 02:15:11 -0400130 result = cros_build_lib.run(write_cmd, capture_output=True,
131 error_code_ok=True)
Yiming Chen320f7ac2015-04-02 16:14:00 -0700132 if result.returncode:
Yiming Chen320f7ac2015-04-02 16:14:00 -0700133 logging.error('Failed to write the file to the VM device.')
134 raise CommandError(result.error)
135
136 logging.info('Test to use shell command to read a file on the VM device.')
137 read_cmd = cmd + ['--', 'cat %s' % path]
Mike Frysinger45602c72019-09-22 02:15:11 -0400138 result = cros_build_lib.run(read_cmd, capture_output=True,
139 error_code_ok=True)
Yiming Chen320f7ac2015-04-02 16:14:00 -0700140 if result.returncode or result.output.rstrip() != content:
141 logging.error('Failed to read the file on the VM device.')
142 raise CommandError(result.error)
143
144 logging.info('Test to use shell command to remove a file on the VM device.')
145 remove_cmd = cmd + ['--', 'rm %s' % path]
Mike Frysinger45602c72019-09-22 02:15:11 -0400146 result = cros_build_lib.run(remove_cmd, capture_output=True,
147 error_code_ok=True)
Yiming Chen320f7ac2015-04-02 16:14:00 -0700148 if result.returncode:
149 logging.error('Failed to remove the file on the VM device.')
150 raise CommandError(result.error)
151
Yiming Chen665e3e22015-04-09 16:42:49 -0700152 @TestCommandDecorator('debug')
153 def TestDebug(self):
154 """Tests the debug command."""
155 logging.info('Test to start and debug a new process on the VM device.')
156 exe_path = '/bin/bash'
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800157 start_cmd = self.BuildCommand('debug', device=self.device_addr,
Yiming Chen665e3e22015-04-09 16:42:49 -0700158 opt_args=['--exe', exe_path])
Mike Frysinger45602c72019-09-22 02:15:11 -0400159 result = cros_build_lib.run(start_cmd, capture_output=True,
160 error_code_ok=True, input='\n')
Yiming Chen665e3e22015-04-09 16:42:49 -0700161 if result.returncode:
162 logging.error('Failed to start and debug a new process on the VM device.')
163 raise CommandError(result.error)
164
165 logging.info('Test to attach a running process on the VM device.')
166 with remote_access.ChromiumOSDeviceHandler(
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800167 remote_access.LOCALHOST, port=self.port) as device:
Yiming Chen818d8f22015-04-29 11:25:24 -0700168 exe = 'update_engine'
169 pids = device.GetRunningPids(exe, full_path=False)
170 if not pids:
171 logging.error('Failed to find any running process to debug.')
Yiming Chen665e3e22015-04-09 16:42:49 -0700172 raise CommandError()
Yiming Chen818d8f22015-04-29 11:25:24 -0700173 pid = pids[0]
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800174 attach_cmd = self.BuildCommand('debug', device=self.device_addr,
Yiming Chen818d8f22015-04-29 11:25:24 -0700175 opt_args=['--pid', str(pid)])
Mike Frysinger45602c72019-09-22 02:15:11 -0400176 result = cros_build_lib.run(attach_cmd, capture_output=True,
177 error_code_ok=True, input='\n')
Yiming Chen818d8f22015-04-29 11:25:24 -0700178 if result.returncode:
179 logging.error('Failed to attach a running process on the VM device.')
180 raise CommandError(result.error)
Yiming Chen665e3e22015-04-09 16:42:49 -0700181
Yiming Chen3c0103a2015-03-31 11:32:35 -0700182 @TestCommandDecorator('flash')
183 def TestFlash(self):
184 """Tests the flash command."""
185 # We explicitly disable reboot after the update because VMs sometimes do
186 # not come back after reboot. The flash command does not need to verify
187 # the integrity of the updated image. We have AU tests for that.
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800188 cmd = self.BuildCommand('flash', device=self.device_addr,
Yiming Chen3c0103a2015-03-31 11:32:35 -0700189 pos_args=['latest'],
190 opt_args=['--no-wipe', '--no-reboot'])
191
192 logging.info('Test to flash the VM device with the latest image.')
Mike Frysinger45602c72019-09-22 02:15:11 -0400193 result = cros_build_lib.run(cmd, capture_output=True, error_code_ok=True)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700194 if result.returncode:
195 logging.error('Failed to flash the VM device.')
196 raise CommandError(result.error)
197
198 @TestCommandDecorator('deploy')
199 def TestDeploy(self):
200 """Tests the deploy command."""
201 packages = ['dev-python/cherrypy', 'app-portage/portage-utils']
202 # Set the installation root to /usr/local so that the command does not
203 # attempt to remount rootfs (which leads to VM reboot).
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800204 cmd = self.BuildCommand('deploy', device=self.device_addr,
Ralph Nathan90475a12015-05-20 13:19:01 -0700205 pos_args=packages, opt_args=['--log-level=info',
206 '--root=/usr/local'])
Yiming Chen3c0103a2015-03-31 11:32:35 -0700207
208 logging.info('Test to uninstall packages on the VM device.')
Mike Frysinger99d9ab02019-10-22 20:21:20 -0400209 with outcap.OutputCapturer() as output:
Mike Frysinger45602c72019-09-22 02:15:11 -0400210 result = cros_build_lib.run(cmd + ['--unmerge'], error_code_ok=True)
Ralph Nathan90475a12015-05-20 13:19:01 -0700211
Yiming Chen3c0103a2015-03-31 11:32:35 -0700212 if result.returncode:
213 logging.error('Failed to uninstall packages on the VM device.')
214 raise CommandError(result.error)
215
Ralph Nathan90475a12015-05-20 13:19:01 -0700216 captured_output = output.GetStdout() + output.GetStderr()
217 for event in deploy.BrilloDeployOperation.UNMERGE_EVENTS:
218 if event not in captured_output:
219 logging.error('Strings used by deploy.BrilloDeployOperation to update '
220 'the progress bar have been changed. Please update the '
221 'strings in UNMERGE_EVENTS')
222 raise CommandError()
223
Yiming Chen3c0103a2015-03-31 11:32:35 -0700224 logging.info('Test to install packages on the VM device.')
Mike Frysinger99d9ab02019-10-22 20:21:20 -0400225 with outcap.OutputCapturer() as output:
Mike Frysinger45602c72019-09-22 02:15:11 -0400226 result = cros_build_lib.run(cmd, error_code_ok=True)
Ralph Nathan90475a12015-05-20 13:19:01 -0700227
Yiming Chen3c0103a2015-03-31 11:32:35 -0700228 if result.returncode:
229 logging.error('Failed to install packages on the VM device.')
230 raise CommandError(result.error)
231
Ralph Nathan90475a12015-05-20 13:19:01 -0700232 captured_output = output.GetStdout() + output.GetStderr()
233 for event in deploy.BrilloDeployOperation.MERGE_EVENTS:
234 if event not in captured_output:
235 logging.error('Strings used by deploy.BrilloDeployOperation to update '
236 'the progress bar have been changed. Please update the '
237 'strings in MERGE_EVENTS')
238 raise CommandError()
239
Yiming Chen3c0103a2015-03-31 11:32:35 -0700240 # Verify that the packages are installed.
241 with remote_access.ChromiumOSDeviceHandler(
Achuith Bhandarkar075d2062019-01-04 12:38:42 -0800242 remote_access.LOCALHOST, port=self.port) as device:
Yiming Chen3c0103a2015-03-31 11:32:35 -0700243 try:
244 device.RunCommand(['python', '-c', '"import cherrypy"'])
245 device.RunCommand(['qmerge', '-h'])
Yiming Chen3c0103a2015-03-31 11:32:35 -0700246 except cros_build_lib.RunCommandError as e:
247 logging.error('Unable to verify packages installed on VM: %s', e)
248 raise CommandError()
249
250 def RunTests(self):
251 """Calls the test functions."""
Yiming Chen320f7ac2015-04-02 16:14:00 -0700252 self.TestShell()
Achuith Bhandarkar3fa46de2019-07-24 12:07:40 -0700253 # TestDebug broken (crbug.com/863122)
Yiming Chen3c0103a2015-03-31 11:32:35 -0700254 self.TestFlash()
Achuith Bhandarkar4153ab32019-08-01 12:21:59 -0700255 self.TestDeploy()
Yiming Chen3c0103a2015-03-31 11:32:35 -0700256
257 def Run(self):
258 """Runs the tests."""
259 try:
260 self.SetUp()
261 self.RunTests()
262 logging.info('All tests completed successfully.')
Yiming Chen3c0103a2015-03-31 11:32:35 -0700263 finally:
264 self.TearDown()