blob: c4bb73888a21ee838e3036c0d8acc53fcb178a16 [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
Chris Sosa928085e2013-03-08 17:25:30 -080028from chromite.lib import remote_access
Chris Sosada9632e2013-03-04 12:28:06 -080029from crostestutils.lib import dev_server_wrapper
30from crostestutils.lib import mount_helper
31from crostestutils.lib import test_helper
32
33
34_LOCALHOST = 'localhost'
35_PRIVATE_KEY = os.path.join(constants.CROSUTILS_DIR, 'mod_for_test_scripts',
36 'ssh_keys', 'testing_rsa')
37
38class TestError(Exception):
39 """Raised on any error during testing. It being raised is a test failure."""
40
41
42class DevModeTest(object):
43 """Wrapper for dev mode tests."""
44 def __init__(self, image_path, board, binhost):
45 """
46 Args:
47 image_path: Filesystem path to the image to test.
48 board: Board of the image under test.
49 binhost: Binhost override. Binhost as defined here is where dev-install
50 or gmerge go to search for binary packages. By default this will
51 be set to the devserver url of the host running this script.
52 If no override i.e. the default is ok, set to None.
53 """
54 self.image_path = image_path
55 self.board = board
56 self.binhost = binhost
57
58 self.tmpdir = tempfile.mkdtemp('DevModeTest')
Chris Sosada9632e2013-03-04 12:28:06 -080059 self.tmpkvmpid = os.path.join(self.tmpdir, 'kvm_pid')
60
61 self.working_image_path = None
62 self.devserver = None
Chris Sosa928085e2013-03-08 17:25:30 -080063 self.remote_access = None
Chris Sosada9632e2013-03-04 12:28:06 -080064 self.port = None
65
66 def Cleanup(self):
67 """Clean up any state at the end of the test."""
68 try:
69 if self.working_image_path:
70 os.remove(self.working_image_path)
71
72 if self.devserver:
73 self.devserver.Stop()
74
75 self.devserver = None
76
77 cmd = ['%s/bin/cros_stop_vm' % constants.CROSUTILS_DIR,
78 '--kvm_pid', self.tmpkvmpid]
79 cros_build_lib.RunCommand(cmd, debug_level=logging.DEBUG)
80
81 if self.tmpdir:
82 shutil.rmtree(self.tmpdir, ignore_errors=True)
83
84 self.tmpdir = None
85 except Exception:
86 logging.warning('Received error during cleanup', exc_info=True)
87
88 def _SetupSSH(self):
89 """Sets up the necessary items for running ssh."""
90 self.port = self._FindUnusedPort()
Chris Sosa928085e2013-03-08 17:25:30 -080091 self.remote_access = remote_access.RemoteAccess(
92 _LOCALHOST, self.tmpdir, self.port,
93 debug_level=logging.DEBUG, interactive=False)
Chris Sosada9632e2013-03-04 12:28:06 -080094
95 def _WipeStatefulPartition(self):
96 """Deletes everything from the working image path's stateful partition."""
97 r_mount_point = os.path.join(self.tmpdir, 'm')
98 s_mount_point = os.path.join(self.tmpdir, 's')
99 mount_helper.MountImage(self.working_image_path,
100 r_mount_point, s_mount_point, read_only=False,
101 safe=True)
102 # Run in shell mode to interpret '*' as a glob.
103 cros_build_lib.SudoRunCommand('rm -rf %s/*' % s_mount_point, shell=True,
104 debug_level=logging.DEBUG)
105 mount_helper.UnmountImage(r_mount_point, s_mount_point)
106
107 def _FindUnusedPort(self):
108 """Returns a currently unused port."""
109 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
110 s.bind((_LOCALHOST, 0))
111 port = s.getsockname()[1]
112 s.close()
113 return port
114
115 def PrepareTest(self):
116 """Pre-test modification to the image and env to setup test."""
117 logging.info('Setting up the image %s for vm testing.',
118 self.image_path)
119 self._SetupSSH()
120 vm_path = test_helper.CreateVMImage(self.image_path, self.board,
121 full=False)
122
123 logging.info('Making copy of the vm image %s to manipulate.', vm_path)
124 self.working_image_path = vm_path + '.' + str(self.port)
125 shutil.copyfile(vm_path, self.working_image_path)
126 logging.debug('Copy of vm image stored at %s.', self.working_image_path)
127
128 logging.info('Wiping the stateful partition to prepare test.')
129 self._WipeStatefulPartition()
130
131 logging.info('Starting the vm on port %d.', self.port)
132 cmd = ['%s/bin/cros_start_vm' % constants.CROSUTILS_DIR,
133 '--ssh_port', str(self.port),
134 '--image_path', self.working_image_path,
135 '--no_graphics',
136 '--kvm_pid', self.tmpkvmpid]
137 cros_build_lib.RunCommand(cmd, debug_level=logging.DEBUG)
138
139 if not self.binhost:
140 logging.info('Starting the devserver.')
141 self.devserver = dev_server_wrapper.DevServerWrapper(self.tmpdir)
142 self.devserver.start()
143 self.devserver.WaitUntilStarted()
144 self.binhost = dev_server_wrapper.DevServerWrapper.GetDevServerURL(
145 None, 'static/pkgroot/%s/packages' % self.board)
146
147 logging.info('Using binhost %s', self.binhost)
148
149 def TestDevInstall(self):
150 """Tests that we can run dev-install and have python work afterwards."""
151 try:
152 logging.info('Running dev install in the vm.')
Chris Sosa928085e2013-03-08 17:25:30 -0800153 self.remote_access.RemoteSh(
Chris Sosada9632e2013-03-04 12:28:06 -0800154 ['bash', '-l', '-c',
155 '"/usr/bin/dev_install --yes --binhost %s"' % self.binhost])
156
157 logging.info('Verifying that python works on the image.')
Chris Sosa928085e2013-03-08 17:25:30 -0800158 self.remote_access.RemoteSh(
Chris Sosada9632e2013-03-04 12:28:06 -0800159 ['sudo', '-u', 'chronos', '--',
160 'python', '-c', '"print \'hello world\'"'])
161 except cros_build_lib.RunCommandError as e:
162 self.devserver.PrintLog()
163 logging.error('dev-install test failed. See devserver log above for more '
164 'details.')
165 raise TestError('dev-install test failed with: %s' % str(e))
166
Chris Sosada9632e2013-03-04 12:28:06 -0800167 def TestGmerge(self):
168 """Evaluates whether the test passed or failed."""
Chris Sosa928085e2013-03-08 17:25:30 -0800169 logging.info('Testing that gmerge works on the image after dev install.')
Chris Sosada9632e2013-03-04 12:28:06 -0800170 try:
Chris Sosa928085e2013-03-08 17:25:30 -0800171 # TODO(sosa): Remove this hack that is needed until this is cleaned up.
172 self.remote_access.RemoteSh(['rm', '-rf', '/usr/local/etc/make.profile'])
173 self.remote_access.RemoteSh(['gmerge', 'gmerge', '--accept_stable',
174 '--usepkg'])
Chris Sosada9632e2013-03-04 12:28:06 -0800175 except cros_build_lib.RunCommandError as e:
176 logging.error('gmerge test failed. See log for details')
177 raise TestError('gmerge test failed with: %s' % str(e))
178
179
180def main():
181 usage = ('%s <board> <path_to_[test|vm]_image>. '
182 'See --help for more options' % os.path.basename(sys.argv[0]))
183 parser = optparse.OptionParser(usage)
184 parser.add_option('--binhost', metavar='URL',
185 help='binhost override. By default, starts up a devserver '
186 'and uses it as the binhost.')
187 parser.add_option('-v', '--verbose', default=False, action='store_true',
188 help='Print out added debugging information')
189
190 (options, args) = parser.parse_args()
191
192 if len(args) != 2:
193 parser.print_usage()
194 parser.error('Need board and path to test image.')
195
196 board = args[0]
197 image_path = os.path.realpath(args[1])
198
199 test_helper.SetupCommonLoggingFormat(verbose=options.verbose)
200
201 test = DevModeTest(image_path, board, options.binhost)
202 try:
203 test.PrepareTest()
204 test.TestDevInstall()
Chris Sosa928085e2013-03-08 17:25:30 -0800205 test.TestGmerge()
Chris Sosada9632e2013-03-04 12:28:06 -0800206 logging.info('All tests passed.')
207 finally:
208 test.Cleanup()
209
210
211if __name__ == '__main__':
212 main()