blob: b47f4cc2b9bde7d1b09ed6279d99751f84ad51ac [file] [log] [blame]
Chris Sosada9632e2013-03-04 12:28:06 -08001#!/usr/bin/python
2#
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Integration test to test the basic functionality of dev-install and gmerge.
8
9This module contains a test that runs some sanity integration tests against
10a VM. First it starts a VM test image and turns it into a base image by wiping
11all of the stateful partition. Once done, runs dev_install to restore the
12stateful partition and then runs gmerge.
13"""
14
15import logging
16import optparse
17import os
18import shutil
19import socket
20import sys
21import tempfile
22
23import constants
24sys.path.append(constants.SOURCE_ROOT)
25sys.path.append(constants.CROS_PLATFORM_ROOT)
26
27from chromite.lib import cros_build_lib
28from crostestutils.lib import dev_server_wrapper
29from crostestutils.lib import mount_helper
30from crostestutils.lib import test_helper
31
32
33_LOCALHOST = 'localhost'
34_PRIVATE_KEY = os.path.join(constants.CROSUTILS_DIR, 'mod_for_test_scripts',
35 'ssh_keys', 'testing_rsa')
36
37class TestError(Exception):
38 """Raised on any error during testing. It being raised is a test failure."""
39
40
41class DevModeTest(object):
42 """Wrapper for dev mode tests."""
43 def __init__(self, image_path, board, binhost):
44 """
45 Args:
46 image_path: Filesystem path to the image to test.
47 board: Board of the image under test.
48 binhost: Binhost override. Binhost as defined here is where dev-install
49 or gmerge go to search for binary packages. By default this will
50 be set to the devserver url of the host running this script.
51 If no override i.e. the default is ok, set to None.
52 """
53 self.image_path = image_path
54 self.board = board
55 self.binhost = binhost
56
57 self.tmpdir = tempfile.mkdtemp('DevModeTest')
58 self.tmpsshkey = os.path.join(self.tmpdir, 'testing_rsa')
59 self.tmpkvmpid = os.path.join(self.tmpdir, 'kvm_pid')
60
61 self.working_image_path = None
62 self.devserver = None
63 self.port = None
64
65 def Cleanup(self):
66 """Clean up any state at the end of the test."""
67 try:
68 if self.working_image_path:
69 os.remove(self.working_image_path)
70
71 if self.devserver:
72 self.devserver.Stop()
73
74 self.devserver = None
75
76 cmd = ['%s/bin/cros_stop_vm' % constants.CROSUTILS_DIR,
77 '--kvm_pid', self.tmpkvmpid]
78 cros_build_lib.RunCommand(cmd, debug_level=logging.DEBUG)
79
80 if self.tmpdir:
81 shutil.rmtree(self.tmpdir, ignore_errors=True)
82
83 self.tmpdir = None
84 except Exception:
85 logging.warning('Received error during cleanup', exc_info=True)
86
87 def _SetupSSH(self):
88 """Sets up the necessary items for running ssh."""
89 self.port = self._FindUnusedPort()
90 shutil.copyfile(_PRIVATE_KEY, self.tmpsshkey)
91 os.chmod(self.tmpsshkey, 0400)
92
93 def _RunSSHCommand(self, command, **kwargs):
94 """Runs ssh command (a list) using RunCommand (kwargs for RunCommand)."""
95 assert isinstance(command, list)
96 assert self.port and self.tmpsshkey, 'Tried to run ssh before ssh was setup'
97 kwargs.update(dict(debug_level=logging.DEBUG))
98 return cros_build_lib.RunCommand(
99 ['ssh', '-n', '-p', str(self.port), '-i', self.tmpsshkey,
100 'root@%s' % _LOCALHOST, '--'] + command, **kwargs)
101
102 def _WipeStatefulPartition(self):
103 """Deletes everything from the working image path's stateful partition."""
104 r_mount_point = os.path.join(self.tmpdir, 'm')
105 s_mount_point = os.path.join(self.tmpdir, 's')
106 mount_helper.MountImage(self.working_image_path,
107 r_mount_point, s_mount_point, read_only=False,
108 safe=True)
109 # Run in shell mode to interpret '*' as a glob.
110 cros_build_lib.SudoRunCommand('rm -rf %s/*' % s_mount_point, shell=True,
111 debug_level=logging.DEBUG)
112 mount_helper.UnmountImage(r_mount_point, s_mount_point)
113
114 def _FindUnusedPort(self):
115 """Returns a currently unused port."""
116 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
117 s.bind((_LOCALHOST, 0))
118 port = s.getsockname()[1]
119 s.close()
120 return port
121
122 def PrepareTest(self):
123 """Pre-test modification to the image and env to setup test."""
124 logging.info('Setting up the image %s for vm testing.',
125 self.image_path)
126 self._SetupSSH()
127 vm_path = test_helper.CreateVMImage(self.image_path, self.board,
128 full=False)
129
130 logging.info('Making copy of the vm image %s to manipulate.', vm_path)
131 self.working_image_path = vm_path + '.' + str(self.port)
132 shutil.copyfile(vm_path, self.working_image_path)
133 logging.debug('Copy of vm image stored at %s.', self.working_image_path)
134
135 logging.info('Wiping the stateful partition to prepare test.')
136 self._WipeStatefulPartition()
137
138 logging.info('Starting the vm on port %d.', self.port)
139 cmd = ['%s/bin/cros_start_vm' % constants.CROSUTILS_DIR,
140 '--ssh_port', str(self.port),
141 '--image_path', self.working_image_path,
142 '--no_graphics',
143 '--kvm_pid', self.tmpkvmpid]
144 cros_build_lib.RunCommand(cmd, debug_level=logging.DEBUG)
145
146 if not self.binhost:
147 logging.info('Starting the devserver.')
148 self.devserver = dev_server_wrapper.DevServerWrapper(self.tmpdir)
149 self.devserver.start()
150 self.devserver.WaitUntilStarted()
151 self.binhost = dev_server_wrapper.DevServerWrapper.GetDevServerURL(
152 None, 'static/pkgroot/%s/packages' % self.board)
153
154 logging.info('Using binhost %s', self.binhost)
155
156 def TestDevInstall(self):
157 """Tests that we can run dev-install and have python work afterwards."""
158 try:
159 logging.info('Running dev install in the vm.')
160 self._RunSSHCommand(
161 ['bash', '-l', '-c',
162 '"/usr/bin/dev_install --yes --binhost %s"' % self.binhost])
163
164 logging.info('Verifying that python works on the image.')
165 self._RunSSHCommand(
166 ['sudo', '-u', 'chronos', '--',
167 'python', '-c', '"print \'hello world\'"'])
168 except cros_build_lib.RunCommandError as e:
169 self.devserver.PrintLog()
170 logging.error('dev-install test failed. See devserver log above for more '
171 'details.')
172 raise TestError('dev-install test failed with: %s' % str(e))
173
174 # TODO(sosa): Currently not run as emerge is not respecting package.provided
175 # after dev_install in a VM. Cannot repro on a device.
176 def TestGmerge(self):
177 """Evaluates whether the test passed or failed."""
178 logging.info('Verifying that python works on the image after dev install.')
179 try:
180 self._RunSSHCommand(['gmerge', 'gmerge', '--accept_stable', '--usepkg'])
181 except cros_build_lib.RunCommandError as e:
182 logging.error('gmerge test failed. See log for details')
183 raise TestError('gmerge test failed with: %s' % str(e))
184
185
186def main():
187 usage = ('%s <board> <path_to_[test|vm]_image>. '
188 'See --help for more options' % os.path.basename(sys.argv[0]))
189 parser = optparse.OptionParser(usage)
190 parser.add_option('--binhost', metavar='URL',
191 help='binhost override. By default, starts up a devserver '
192 'and uses it as the binhost.')
193 parser.add_option('-v', '--verbose', default=False, action='store_true',
194 help='Print out added debugging information')
195
196 (options, args) = parser.parse_args()
197
198 if len(args) != 2:
199 parser.print_usage()
200 parser.error('Need board and path to test image.')
201
202 board = args[0]
203 image_path = os.path.realpath(args[1])
204
205 test_helper.SetupCommonLoggingFormat(verbose=options.verbose)
206
207 test = DevModeTest(image_path, board, options.binhost)
208 try:
209 test.PrepareTest()
210 test.TestDevInstall()
211 logging.info('All tests passed.')
212 finally:
213 test.Cleanup()
214
215
216if __name__ == '__main__':
217 main()