blob: ca8549e77d65163672037b1caa9978c0b4215287 [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
Chris Sosa068c1e92013-03-17 22:54:20 -070022import time
Chris Sosada9632e2013-03-04 12:28:06 -080023
24import constants
25sys.path.append(constants.SOURCE_ROOT)
26sys.path.append(constants.CROS_PLATFORM_ROOT)
27
28from chromite.lib import cros_build_lib
Chris Sosa928085e2013-03-08 17:25:30 -080029from chromite.lib import remote_access
Chris Sosada9632e2013-03-04 12:28:06 -080030from crostestutils.lib import dev_server_wrapper
31from crostestutils.lib import mount_helper
32from crostestutils.lib import test_helper
33
34
35_LOCALHOST = 'localhost'
36_PRIVATE_KEY = os.path.join(constants.CROSUTILS_DIR, 'mod_for_test_scripts',
37 'ssh_keys', 'testing_rsa')
38
39class TestError(Exception):
40 """Raised on any error during testing. It being raised is a test failure."""
41
42
43class DevModeTest(object):
44 """Wrapper for dev mode tests."""
45 def __init__(self, image_path, board, binhost):
46 """
47 Args:
48 image_path: Filesystem path to the image to test.
49 board: Board of the image under test.
50 binhost: Binhost override. Binhost as defined here is where dev-install
51 or gmerge go to search for binary packages. By default this will
52 be set to the devserver url of the host running this script.
53 If no override i.e. the default is ok, set to None.
54 """
55 self.image_path = image_path
56 self.board = board
57 self.binhost = binhost
58
59 self.tmpdir = tempfile.mkdtemp('DevModeTest')
Chris Sosada9632e2013-03-04 12:28:06 -080060 self.tmpkvmpid = os.path.join(self.tmpdir, 'kvm_pid')
61
62 self.working_image_path = None
63 self.devserver = None
Chris Sosa928085e2013-03-08 17:25:30 -080064 self.remote_access = None
Chris Sosada9632e2013-03-04 12:28:06 -080065 self.port = None
66
67 def Cleanup(self):
68 """Clean up any state at the end of the test."""
69 try:
70 if self.working_image_path:
71 os.remove(self.working_image_path)
72
73 if self.devserver:
74 self.devserver.Stop()
75
76 self.devserver = None
77
78 cmd = ['%s/bin/cros_stop_vm' % constants.CROSUTILS_DIR,
79 '--kvm_pid', self.tmpkvmpid]
80 cros_build_lib.RunCommand(cmd, debug_level=logging.DEBUG)
81
82 if self.tmpdir:
83 shutil.rmtree(self.tmpdir, ignore_errors=True)
84
85 self.tmpdir = None
86 except Exception:
87 logging.warning('Received error during cleanup', exc_info=True)
88
89 def _SetupSSH(self):
90 """Sets up the necessary items for running ssh."""
91 self.port = self._FindUnusedPort()
Chris Sosa928085e2013-03-08 17:25:30 -080092 self.remote_access = remote_access.RemoteAccess(
93 _LOCALHOST, self.tmpdir, self.port,
94 debug_level=logging.DEBUG, interactive=False)
Chris Sosada9632e2013-03-04 12:28:06 -080095
96 def _WipeStatefulPartition(self):
97 """Deletes everything from the working image path's stateful partition."""
98 r_mount_point = os.path.join(self.tmpdir, 'm')
99 s_mount_point = os.path.join(self.tmpdir, 's')
100 mount_helper.MountImage(self.working_image_path,
101 r_mount_point, s_mount_point, read_only=False,
102 safe=True)
103 # Run in shell mode to interpret '*' as a glob.
104 cros_build_lib.SudoRunCommand('rm -rf %s/*' % s_mount_point, shell=True,
105 debug_level=logging.DEBUG)
106 mount_helper.UnmountImage(r_mount_point, s_mount_point)
107
108 def _FindUnusedPort(self):
109 """Returns a currently unused port."""
110 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
111 s.bind((_LOCALHOST, 0))
112 port = s.getsockname()[1]
113 s.close()
114 return port
115
116 def PrepareTest(self):
117 """Pre-test modification to the image and env to setup test."""
118 logging.info('Setting up the image %s for vm testing.',
119 self.image_path)
120 self._SetupSSH()
121 vm_path = test_helper.CreateVMImage(self.image_path, self.board,
122 full=False)
123
124 logging.info('Making copy of the vm image %s to manipulate.', vm_path)
David James45b55dd2013-04-24 09:16:40 -0700125 self.working_image_path = os.path.join(self.tmpdir,
126 os.path.basename(vm_path))
Chris Sosada9632e2013-03-04 12:28:06 -0800127 shutil.copyfile(vm_path, self.working_image_path)
128 logging.debug('Copy of vm image stored at %s.', self.working_image_path)
129
130 logging.info('Wiping the stateful partition to prepare test.')
131 self._WipeStatefulPartition()
132
133 logging.info('Starting the vm on port %d.', self.port)
134 cmd = ['%s/bin/cros_start_vm' % constants.CROSUTILS_DIR,
135 '--ssh_port', str(self.port),
136 '--image_path', self.working_image_path,
137 '--no_graphics',
138 '--kvm_pid', self.tmpkvmpid]
139 cros_build_lib.RunCommand(cmd, debug_level=logging.DEBUG)
140
Chris Sosa068c1e92013-03-17 22:54:20 -0700141 # TODO(sosa): Super hack, fix with retry_ssh-like functionality.
142 # crbug.com/209719
143 time.sleep(30)
144
Chris Sosada9632e2013-03-04 12:28:06 -0800145 if not self.binhost:
146 logging.info('Starting the devserver.')
147 self.devserver = dev_server_wrapper.DevServerWrapper(self.tmpdir)
148 self.devserver.start()
149 self.devserver.WaitUntilStarted()
150 self.binhost = dev_server_wrapper.DevServerWrapper.GetDevServerURL(
Chris Sosac9447962013-03-12 10:12:29 -0700151 sub_dir='static/pkgroot/%s/packages' % self.board)
Chris Sosada9632e2013-03-04 12:28:06 -0800152
153 logging.info('Using binhost %s', self.binhost)
154
155 def TestDevInstall(self):
156 """Tests that we can run dev-install and have python work afterwards."""
157 try:
158 logging.info('Running dev install in the vm.')
Chris Sosa928085e2013-03-08 17:25:30 -0800159 self.remote_access.RemoteSh(
Chris Sosada9632e2013-03-04 12:28:06 -0800160 ['bash', '-l', '-c',
161 '"/usr/bin/dev_install --yes --binhost %s"' % self.binhost])
162
163 logging.info('Verifying that python works on the image.')
Chris Sosa928085e2013-03-08 17:25:30 -0800164 self.remote_access.RemoteSh(
Chris Sosada9632e2013-03-04 12:28:06 -0800165 ['sudo', '-u', 'chronos', '--',
166 'python', '-c', '"print \'hello world\'"'])
167 except cros_build_lib.RunCommandError as e:
168 self.devserver.PrintLog()
169 logging.error('dev-install test failed. See devserver log above for more '
170 'details.')
171 raise TestError('dev-install test failed with: %s' % str(e))
172
Chris Sosada9632e2013-03-04 12:28:06 -0800173 def TestGmerge(self):
174 """Evaluates whether the test passed or failed."""
Chris Sosa928085e2013-03-08 17:25:30 -0800175 logging.info('Testing that gmerge works on the image after dev install.')
Chris Sosada9632e2013-03-04 12:28:06 -0800176 try:
Chris Sosac9447962013-03-12 10:12:29 -0700177 self.remote_access.RemoteSh(
178 ['gmerge', 'gmerge', '--accept_stable', '--usepkg',
179 '--devserver_url', self.devserver.GetDevServerURL(),
180 '--board', self.board])
Chris Sosada9632e2013-03-04 12:28:06 -0800181 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()
Chris Sosa928085e2013-03-08 17:25:30 -0800211 test.TestGmerge()
Chris Sosada9632e2013-03-04 12:28:06 -0800212 logging.info('All tests passed.')
213 finally:
214 test.Cleanup()
215
216
217if __name__ == '__main__':
218 main()