1#! /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"""The gdb dejagnu test wrapper.""" 7import optparse 8import os 9from os import path 10import re 11import shutil 12import stat 13import sys 14import tempfile 15import time 16 17from cros_utils import command_executer 18from cros_utils import logger 19from cros_utils import misc 20 21from run_dejagnu import TryAcquireMachine 22 23_VALID_TEST_RESULTS = ['FAIL', 'UNRESOLVED', 'XPASS', 'ERROR', 'UNSUPPORTED', 24 'PASS'] 25 26 27def ProcessArguments(argv): 28 """Processing/validating script arguments.""" 29 parser = optparse.OptionParser(description=( 30 'Launches gdb dejagnu test in chroot for chromeos toolchain, compares ' 31 'the test result with a repository baseline and prints out the result.'), 32 usage='run_dejagnu options') 33 parser.add_option('-c', 34 '--chromeos_root', 35 dest='chromeos_root', 36 help='Required. Specify chromeos root') 37 parser.add_option('-m', 38 '--mount', 39 dest='mount', 40 help=('Specify gdb source to mount instead of "auto". ' 41 'Under "auto" mode, which is the default - gdb is ' 42 'checked out and built automatically at default ' 43 'directories. Under "mount" mode ' 44 '- the gdb_source is set to "$chromeos_' 45 'root/chroot/usr/local/toolchain_root/gdb", which is ' 46 'the mount point for this option value.')) 47 parser.add_option('-b', 48 '--board', 49 dest='board', 50 help=('Required. Specify board.')) 51 parser.add_option('-r', 52 '--remote', 53 dest='remote', 54 help=('Required. Specify addresses/names of the board, ' 55 'seperate each address/name using comma(\',\').')) 56 parser.add_option('--cleanup', 57 dest='cleanup', 58 default=None, 59 help=('Optional. Values to this option could be ' 60 '\'chroot\' (delete chroot) and ' 61 '\'chromeos\' (delete the whole chromeos tree).')) 62 63 options, args = parser.parse_args(argv) 64 65 if not options.chromeos_root: 66 raise SyntaxError('Missing argument for --chromeos_root.') 67 if not options.remote: 68 raise SyntaxError('Missing argument for --remote.') 69 if not options.board: 70 raise SyntaxError('Missing argument for --board.') 71 if options.cleanup == 'mount' and not options.mount: 72 raise SyntaxError('--cleanup=\'mount\' not valid unless --mount is given.') 73 if options.cleanup and not (options.cleanup == 'mount' or 74 options.cleanup == 'chroot' or 75 options.cleanup == 'chromeos'): 76 raise SyntaxError('Invalid option value for --cleanup') 77 78 return options 79 80 81class DejagnuExecuter(object): 82 """The class wrapper for dejagnu test executer.""" 83 84 def __init__(self, base_dir, source_dir, chromeos_root, remote, board, 85 cleanup): 86 self._l = logger.GetLogger() 87 self._chromeos_root = chromeos_root 88 self._chromeos_chroot = path.join(chromeos_root, 'chroot') 89 90 self._remote = remote 91 self._board = board 92 ## Compute target from board 93 self._target = misc.GetCtargetFromBoard(board, chromeos_root) 94 if not self._target: 95 raise RuntimeError('Unsupported board "%s"' % board) 96 self._executer = command_executer.GetCommandExecuter() 97 self._base_dir = base_dir 98 self._tmp_abs = None 99 self._cleanup = cleanup 100 self._sshflag = ('-o StrictHostKeyChecking=no ' + '-o CheckHostIP=no ' + 101 '-o UserKnownHostsFile=$(mktemp) ') 102 103 if source_dir: 104 self._source_dir = source_dir 105 self._mount_flag = 'USE="mounted_sources"' 106 self.MountSource() 107 else: 108 self._source_dir = None 109 self._mount_flag = '' 110 111 def SetupTestingDir(self): 112 self._tmp_abs = tempfile.mkdtemp( 113 prefix='dejagnu_', 114 dir=path.join(self._chromeos_chroot, 'tmp')) 115 self._tmp = self._tmp_abs[len(self._chromeos_chroot):] 116 self._tmp_testing_rsa = path.join(self._tmp, 'testing_rsa') 117 self._tmp_testing_rsa_abs = path.join(self._tmp_abs, 'testing_rsa') 118 119 def PrepareTestingRsaKeys(self): 120 if not path.isfile(self._tmp_testing_rsa_abs): 121 shutil.copy( 122 path.join(self._chromeos_root, 123 'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa'), 124 self._tmp_testing_rsa_abs) 125 os.chmod(self._tmp_testing_rsa_abs, stat.S_IRUSR) 126 127 def PrepareTestFiles(self): 128 """Prepare site.exp and board exp files.""" 129 # Create the boards directory. 130 os.mkdir('%s/boards' % self._tmp_abs) 131 132 # Generate the chromeos.exp file. 133 with open('%s/boards/gdb.exp.in' % self._base_dir, 'r') as template_file: 134 content = template_file.read() 135 136 substitutions = dict({ 137 '__boardname__': self._board, 138 '__board_hostname__': self._remote, 139 '__tmp_testing_rsa__': self._tmp_testing_rsa, 140 '__tmp_dir__': self._tmp 141 }) 142 for pat, sub in substitutions.items(): 143 content = content.replace(pat, sub) 144 145 board_file_name = '%s/boards/%s.exp' % (self._tmp_abs, self._board) 146 with open(board_file_name, 'w') as board_file: 147 board_file.write(content) 148 149 # Generate the site file 150 with open('%s/site.exp' % self._tmp_abs, 'w') as site_file: 151 site_file.write('set target_list "%s"\n' % self._board) 152 153 with open('%s/boards/gdbserver.sh.in' % self._base_dir, 'r') \ 154 as template_file: 155 content = template_file.read() 156 substitutions = dict({ 157 '__board_hostname__': self._remote, 158 '__tmp_testing_rsa__': self._tmp_testing_rsa, 159 '__tmp_dir__': self._tmp 160 }) 161 for pat, sub in substitutions.items(): 162 content = content.replace(pat, sub) 163 164 gdbserver_file_name = '%s/boards/gdbserver.sh' % (self._tmp_abs) 165 with open(gdbserver_file_name, 'w') as board_file: 166 board_file.write(content) 167 168 st = os.stat(gdbserver_file_name) 169 os.chmod(gdbserver_file_name, st.st_mode | stat.S_IXGRP | stat.S_IXUSR) 170 171 def PrepareGdb(self): 172 self.PrepareGdbDefault() 173 174 def PrepareGdbDefault(self): 175 ret = self._executer.ChrootRunCommandWOutput( 176 self._chromeos_root, 'equery w cross-%s/gdb' % self._target)[1] 177 ret = path.basename(ret.strip()) 178 179 matcher = re.match(r'(.*).ebuild', ret) 180 if matcher: 181 gdb_reversion = matcher.group(1) 182 else: 183 raise RuntimeError('Failed to get gdb reversion.') 184 gdb_version = gdb_reversion.split('-r')[0] 185 gdb_portage_dir = '/var/tmp/portage/cross-%s/%s/work' % (self._target, 186 gdb_reversion) 187 self._gdb_source_dir = path.join(gdb_portage_dir, gdb_version) 188 189 ret = self._executer.ChrootRunCommand(self._chromeos_root, ( 190 'sudo %s ebuild $(equery w cross-%s/gdb) clean compile' % ( 191 self._mount_flag, self._target))) 192 if ret: 193 raise RuntimeError('ebuild gdb failed.') 194 195 def PrepareGdbserver(self): 196 self.PrepareGdbserverDefault() 197 198 def PrepareGdbserverDefault(self): 199 cmd = ('./setup_board --board {0}; ' 200 '{1} emerge-{0} gdb'.format(self._board, self._mount_flag)) 201 ret = self._executer.ChrootRunCommand(self._chromeos_root, 202 cmd, 203 print_to_console=True) 204 if ret: 205 raise RuntimeError('ebuild gdbserver failed.') 206 207 cmd = ('scp -i {0} {1} ' 208 '/build/{2}/usr/bin/gdbserver root@{3}:/usr/local/bin/'.format( 209 self._tmp_testing_rsa, self._sshflag, self._board, self._remote)) 210 ret = self._executer.ChrootRunCommand(self._chromeos_root, 211 cmd, 212 print_to_console=True) 213 if ret: 214 raise RuntimeError('copy gdbserver failed.') 215 216 if self._mount_flag: 217 self.MountSource(unmount=False) 218 219 def Cleanup(self): 220 if not self._cleanup: 221 return 222 223 if self._cleanup == 'chroot' or self._cleanup == 'chromeos': 224 self._l.LogOutput('[Cleanup]: Deleting chroot inside \'{0}\''.format( 225 self._chromeos_root)) 226 command = 'cd %s; cros_sdk --delete' % self._chromeos_root 227 rv = self._executer.RunCommand(command) 228 if rv: 229 self._l.LogWarning('Warning - failed to delete chroot.') 230 # Delete .cache - crosbug.com/34956 231 command = 'sudo rm -fr %s' % os.path.join(self._chromeos_root, '.cache') 232 rv = self._executer.RunCommand(command) 233 if rv: 234 self._l.LogWarning('Warning - failed to delete \'.cache\'.') 235 236 if self._cleanup == 'chromeos': 237 self._l.LogOutput('[Cleanup]: Deleting chromeos tree \'{0}\' ...'.format( 238 self._chromeos_root)) 239 command = 'rm -fr {0}'.format(self._chromeos_root) 240 rv = self._executer.RunCommand(command) 241 if rv: 242 self._l.LogWarning('Warning - failed to remove chromeos tree.') 243 244 def MakeCheck(self): 245 cmd = ('ssh -i {0} {1} root@{2} "reboot && exit"' 246 .format(self._tmp_testing_rsa, self._sshflag, self._remote)) 247 self._executer.ChrootRunCommand(self._chromeos_root, cmd) 248 time.sleep(40) 249 250 cmd = ('ssh -i {0} {1} root@{2} ' 251 '"iptables -A INPUT -p tcp --dport 1234 -j ACCEPT"'.format( 252 self._tmp_testing_rsa, self._sshflag, self._remote)) 253 self._executer.ChrootRunCommand(self._chromeos_root, cmd) 254 255 cmd = ('cd %s ; ' 256 'DEJAGNU=%s make check' % (path.join(self._gdb_source_dir, 'gdb'), 257 path.join(self._tmp, 'site.exp'))) 258 ret = self._executer.ChrootRunCommand(self._chromeos_root, cmd) 259 if ret: 260 raise RuntimeError('Make check failed.') 261 262 # This method ensures necessary mount points before executing chroot comamnd. 263 def MountSource(self, unmount=False): 264 script = os.path.join(self._base_dir, 'build_tc.py') 265 if unmount: 266 mount = '-u' 267 else: 268 mount = '-m' 269 cmd = ('python {0} --chromeos_root={1} ' 270 '--gdb_dir={2} --board={3} {4}'.format(script, self._chromeos_root, 271 self._source_dir, self._board, 272 mount)) 273 rv = self._executer.RunCommand(cmd) 274 if rv: 275 raise RuntimeError('Mount source failed.') 276 277 def ResultValidate(self): 278 self.PrepareResult() 279 result = [] 280 for key, value in self.base_result.items(): 281 if 'PASS' not in value: 282 continue 283 if key not in self.test_result: 284 continue 285 test_result = self.test_result[key] 286 if 'PASS' not in test_result: 287 result.append(key) 288 return result 289 290 def PrepareResult(self): 291 test_output = os.path.join(self._gdb_source_dir, 'gdb', 'testsuite', 292 'gdb.sum') 293 test_output = misc.GetOutsideChrootPath(self._chromeos_root, test_output) 294 base_output = os.path.join(self._base_dir, 'gdb_baseline', self._target) 295 296 self.test_result = self.ParseResult(test_output) 297 self.base_result = self.ParseResult(base_output) 298 299 def ParseResult(self, gdb_sum): 300 result = {} 301 multi_keys = {} 302 with open(gdb_sum) as input_sum: 303 for line in input_sum: 304 line = line.strip() 305 r = line.split(':', 1) 306 if r[0] in _VALID_TEST_RESULTS: 307 key = r[1] 308 if r[1] in result: 309 if r[1] in multi_keys: 310 multi_keys[r[1]] += 1 311 else: 312 multi_keys[r[1]] = 2 313 key = r[1] + '_____{0}_____'.format(multi_keys[r[1]]) 314 result[key] = r[0] 315 return result 316 317 318def Main(argv): 319 opts = ProcessArguments(argv) 320 available_machine = TryAcquireMachine(opts.remote) 321 executer = DejagnuExecuter( 322 misc.GetRoot(argv[0])[0], opts.mount, opts.chromeos_root, 323 available_machine._name, opts.board, opts.cleanup) 324 # Return value is a 3- or 4-element tuple 325 # element#1 - exit code 326 # element#2 - stdout 327 # element#3 - stderr 328 # element#4 - exception infor 329 # Some other scripts need these detailed information. 330 ret = (1, '', '') 331 try: 332 executer.SetupTestingDir() 333 executer.PrepareTestingRsaKeys() 334 executer.PrepareTestFiles() 335 executer.PrepareGdb() 336 executer.PrepareGdbserver() 337 executer.MakeCheck() 338 result = executer.ResultValidate() 339 print result 340 if result: 341 ret = (1, result, '') 342 else: 343 ret = (0, '', '') 344 345 except Exception as e: 346 # At least log the exception on console. 347 print e 348 # The #4 element encodes the runtime exception. 349 ret = (1, '', '', 'Exception happened during execution: \n' + str(e)) 350 finally: 351 executer.Cleanup() 352 return ret 353 354 355if __name__ == '__main__': 356 retval = Main(sys.argv)[0] 357 sys.exit(retval) 358