blob: 9ff30a32ea466af134a6cc79bf2c86bce851c58e [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 Chen3c0103a2015-03-31 11:32:35 -0700144 @TestCommandDecorator('flash')
145 def TestFlash(self):
146 """Tests the flash command."""
147 # We explicitly disable reboot after the update because VMs sometimes do
148 # not come back after reboot. The flash command does not need to verify
149 # the integrity of the updated image. We have AU tests for that.
150 cmd = self.BuildCommand('flash', device=self.vm.device_addr,
151 pos_args=['latest'],
152 opt_args=['--no-wipe', '--no-reboot'])
153
154 logging.info('Test to flash the VM device with the latest image.')
155 result = cros_build_lib.RunCommand(cmd, capture_output=True,
156 error_code_ok=True)
157 if result.returncode:
158 logging.error('Failed to flash the VM device.')
159 raise CommandError(result.error)
160
161 @TestCommandDecorator('deploy')
162 def TestDeploy(self):
163 """Tests the deploy command."""
164 packages = ['dev-python/cherrypy', 'app-portage/portage-utils']
165 # Set the installation root to /usr/local so that the command does not
166 # attempt to remount rootfs (which leads to VM reboot).
167 cmd = self.BuildCommand('deploy', device=self.vm.device_addr,
168 pos_args=packages, opt_args=['--root=/usr/local'])
169
170 logging.info('Test to uninstall packages on the VM device.')
171 result = cros_build_lib.RunCommand(cmd + ['--unmerge'],
172 capture_output=True,
173 error_code_ok=True)
174 if result.returncode:
175 logging.error('Failed to uninstall packages on the VM device.')
176 raise CommandError(result.error)
177
178 logging.info('Test to install packages on the VM device.')
179 result = cros_build_lib.RunCommand(cmd, capture_output=True,
180 error_code_ok=True)
181 if result.returncode:
182 logging.error('Failed to install packages on the VM device.')
183 raise CommandError(result.error)
184
185 # Verify that the packages are installed.
186 with remote_access.ChromiumOSDeviceHandler(
187 remote_access.LOCALHOST, port=self.vm.port) as device:
188 try:
189 device.RunCommand(['python', '-c', '"import cherrypy"'])
190 device.RunCommand(['qmerge', '-h'])
191 except remote_access.SSHConnectionError as e:
192 logging.error('Unable to connect to the VM to verify packages: %s', e)
193 raise CommandError()
194 except cros_build_lib.RunCommandError as e:
195 logging.error('Unable to verify packages installed on VM: %s', e)
196 raise CommandError()
197
198 def RunTests(self):
199 """Calls the test functions."""
Yiming Chen320f7ac2015-04-02 16:14:00 -0700200 self.TestShell()
Yiming Chen3c0103a2015-03-31 11:32:35 -0700201 self.TestFlash()
202 self.TestDeploy()
203
204 def Run(self):
205 """Runs the tests."""
206 try:
207 self.SetUp()
208 self.RunTests()
209 logging.info('All tests completed successfully.')
210 except Exception as e:
211 logging.error(e)
212 finally:
213 self.TearDown()