blob: af5e56a8ef82707c453676f5d8168d655f21480f [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 self.remote_access.RemoteSh(['gmerge', 'gmerge', '--accept_stable',
172 '--usepkg'])
Chris Sosada9632e2013-03-04 12:28:06 -0800173 except cros_build_lib.RunCommandError as e:
174 logging.error('gmerge test failed. See log for details')
175 raise TestError('gmerge test failed with: %s' % str(e))
176
177
178def main():
179 usage = ('%s <board> <path_to_[test|vm]_image>. '
180 'See --help for more options' % os.path.basename(sys.argv[0]))
181 parser = optparse.OptionParser(usage)
182 parser.add_option('--binhost', metavar='URL',
183 help='binhost override. By default, starts up a devserver '
184 'and uses it as the binhost.')
185 parser.add_option('-v', '--verbose', default=False, action='store_true',
186 help='Print out added debugging information')
187
188 (options, args) = parser.parse_args()
189
190 if len(args) != 2:
191 parser.print_usage()
192 parser.error('Need board and path to test image.')
193
194 board = args[0]
195 image_path = os.path.realpath(args[1])
196
197 test_helper.SetupCommonLoggingFormat(verbose=options.verbose)
198
199 test = DevModeTest(image_path, board, options.binhost)
200 try:
201 test.PrepareTest()
202 test.TestDevInstall()
Chris Sosa928085e2013-03-08 17:25:30 -0800203 test.TestGmerge()
Chris Sosada9632e2013-03-04 12:28:06 -0800204 logging.info('All tests passed.')
205 finally:
206 test.Cleanup()
207
208
209if __name__ == '__main__':
210 main()