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.""" 7 import optparse 8 import os 9 from os import path 10 import re 11 import shutil 12 import stat 13 import sys 14 import tempfile 15 import time 16 17 from cros_utils import command_executer 18 from cros_utils import logger 19 from cros_utils import misc 20 21 from run_dejagnu import TryAcquireMachine 22 23 _VALID_TEST_RESULTS = ['FAIL', 'UNRESOLVED', 'XPASS', 'ERROR', 'UNSUPPORTED', 24 'PASS'] 25 26 27 def 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 81 class 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 318 def 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 355 if __name__ == '__main__': 356 retval = Main(sys.argv)[0] 357 sys.exit(retval) 358