run_dejagnu.py revision e4b6f220f3cc7d3277daca7bca25f89d648dae8e
1#!/usr/bin/python 2# 3# Copyright 2010 Google Inc. All Rights Reserved. 4 5"""Tool script for auto dejagnu.""" 6 7__author__ = 'shenhan@google.com (Han Shen)' 8 9import getpass 10import optparse 11import os 12from os import path 13import re 14import shutil 15import stat 16import sys 17import tempfile 18import time 19 20import lock_machine 21import tc_enter_chroot 22from utils import command_executer 23from utils import constants 24from utils import logger 25from utils import misc 26 27 28def ProcessArguments(argv): 29 """Processing/validating script arguments.""" 30 parser = optparse.OptionParser(description=( 31 'Launches gcc dejagnu test in chroot for chromeos toolchain, compares ' 32 'the test result with a repository baseline and prints out the result.'), 33 usage='run_dejagnu options') 34 parser.add_option('-c', '--chromeos_root', dest='chromeos_root', 35 help='Required. Specify chromeos root') 36 parser.add_option('-m', '--mount', dest='mount', 37 help=('Specify gcc source to mount instead of "auto". ' 38 'Under "auto" mode, which is the default - gcc is ' 39 'checked out and built automatically at default ' 40 'directories. Under "mount" mode ' 41 '- the gcc_source is set to "$chromeos_' 42 'root/chroot/usr/local/toolchain_root/gcc", which is ' 43 'the mount point for this option value, the ' 44 'gcc-build-dir then is computed as ' 45 '"${gcc_source_dir}-build-${ctarget}". In this mode, ' 46 'a complete gcc build must be performed in the ' 47 'computed gcc-build-dir beforehand.')) 48 parser.add_option('-b', '--board', dest='board', 49 help=('Required. Specify board.')) 50 parser.add_option('-r', '--remote', dest='remote', 51 help=('Required. Specify addresses/names of the board, ' 52 'seperate each address/name using comma(\',\').')) 53 parser.add_option('-f', '--flags', dest='flags', 54 help='Optional. Extra run test flags to pass to dejagnu.') 55 parser.add_option('-k', '--keep', dest='keep_intermediate_files', 56 action='store_true', default=False, 57 help=('Optional. Default to false. Do not remove dejagnu ' 58 'intermediate files after test run.')) 59 parser.add_option('--cleanup', dest='cleanup', default=None, 60 help=('Optional. Values to this option could be ' 61 '\'mount\' (unmount gcc source and ' 62 'directory directory, ' 63 'only valid when --mount is given), ' 64 '\'chroot\' (delete chroot) and ' 65 '\'chromeos\' (delete the whole chromeos tree).')) 66 parser.add_option('-t', '--tools', dest='tools', default='gcc,g++', 67 help=('Optional. Specify which tools to check, using ' 68 '","(comma) as separator. A typical value would be ' 69 '"g++" so that only g++ tests are performed. ' 70 'Defaults to "gcc,g++".')) 71 72 options, args = parser.parse_args(argv) 73 74 if not options.chromeos_root: 75 raise Exception('Missing argument for --chromeos_root.') 76 if not options.remote: 77 raise Exception('Missing argument for --remote.') 78 if not options.board: 79 raise Exception('Missing argument for --board.') 80 if options.cleanup == 'mount' and not options.mount: 81 raise Exception('--cleanup=\'mount\' not valid unless --mount is given.') 82 if options.cleanup and not ( 83 options.cleanup == 'mount' or \ 84 options.cleanup == 'chroot' or options.cleanup == 'chromeos'): 85 raise Exception('Invalid option value for --cleanup') 86 if options.cleanup and options.keep_intermediate_files: 87 raise Exception('Only one of --keep and --cleanup could be given.') 88 89 return options 90 91 92class DejagnuExecuter(object): 93 """The class wrapper for dejagnu test executer.""" 94 95 def __init__(self, base_dir, mount, chromeos_root, remote, board, 96 flags, keep_intermediate_files, tools, cleanup): 97 self._l = logger.GetLogger() 98 self._chromeos_root = chromeos_root 99 self._chromeos_chroot = path.join(chromeos_root, 'chroot') 100 if mount: 101 self._gcc_source_dir_to_mount = mount 102 self._gcc_source_dir = path.join(constants.MOUNTED_TOOLCHAIN_ROOT, 'gcc') 103 else: 104 self._gcc_source_dir = None 105 106 self._remote = remote 107 self._board = board 108 ## Compute target from board 109 self._target = misc.GetCtargetFromBoard(board, chromeos_root) 110 if not self._target: 111 raise Exception('Unsupported board "%s"' % board) 112 self._executer = command_executer.GetCommandExecuter() 113 self._flags = flags or '' 114 self._base_dir = base_dir 115 self._tmp_abs = None 116 self._keep_intermediate_files = keep_intermediate_files 117 self._tools = tools.split(',') 118 self._cleanup = cleanup 119 120 def SetupTestingDir(self): 121 self._tmp_abs = tempfile.mkdtemp(prefix='dejagnu_', dir=path.join( 122 self._chromeos_chroot, 'tmp')) 123 self._tmp = self._tmp_abs[len(self._chromeos_chroot):] 124 self._tmp_testing_rsa = path.join(self._tmp, 'testing_rsa') 125 self._tmp_testing_rsa_abs = path.join(self._tmp_abs, 'testing_rsa') 126 127 def MakeCheckString(self): 128 return ' '.join(['check-{0}'.format(t) for t in self._tools if t]) 129 130 def CleanupIntermediateFiles(self): 131 if self._tmp_abs and path.isdir(self._tmp_abs): 132 if self._keep_intermediate_files: 133 self._l.LogOutput( 134 'Your intermediate dejagnu files are kept, you can re-run ' 135 'inside chroot the command:') 136 self._l.LogOutput( 137 ' DEJAGNU={0} make -C {1} {2} RUNTESTFLAGS="--target_board={3} {4}"' \ 138 .format(path.join(self._tmp, 'site.exp'), self._gcc_build_dir, 139 self.MakeCheckString(), self._board, self._flags)) 140 else: 141 self._l.LogOutput( 142 '[Cleanup] - Removing temp dir - {0}'.format(self._tmp_abs)) 143 shutil.rmtree(self._tmp_abs) 144 145 def Cleanup(self): 146 if not self._cleanup: 147 return 148 149 # Optionally cleanup mounted diretory, chroot and chromeos tree. 150 if self._cleanup == 'mount' or self._cleanup == 'chroot' or \ 151 self._cleanup == 'chromeos': 152 # No exceptions are allowed from this method. 153 try: 154 self._l.LogOutput('[Cleanup] - Unmounting directories ...') 155 self.MountGccSourceAndBuildDir(unmount=True) 156 except: 157 print 'Warning: failed to unmount gcc source/build directory.' 158 159 if self._cleanup == 'chroot' or self._cleanup == 'chromeos': 160 self._l.LogOutput('[Cleanup]: Deleting chroot inside \'{0}\''.format( 161 self._chromeos_root)) 162 command = "cd %s; cros_sdk --delete" % self._chromeos_root 163 rv = self._executer.RunCommand(command) 164 if rv: 165 self._l.LogWarning('Warning - failed to delete chroot.') 166 # Delete .cache - crosbug.com/34956 167 command = "sudo rm -fr %s" % os.path.join(self._chromeos_root, ".cache") 168 rv = self._executer.RunCommand(command) 169 if rv: 170 self._l.LogWarning('Warning - failed to delete \'.cache\'.') 171 172 if self._cleanup == 'chromeos': 173 self._l.LogOutput('[Cleanup]: Deleting chromeos tree \'{0}\' ...'.format( 174 self._chromeos_root)) 175 command = 'rm -fr {0}'.format(self._chromeos_root) 176 rv = self._executer.RunCommand(command) 177 if rv: 178 self._l.LogWarning('Warning - failed to remove chromeos tree.') 179 180 def PrepareTestingRsaKeys(self): 181 if not path.isfile(self._tmp_testing_rsa_abs): 182 shutil.copy(path.join( 183 self._chromeos_root, 184 'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa'), 185 self._tmp_testing_rsa_abs) 186 os.chmod(self._tmp_testing_rsa_abs, stat.S_IRUSR) 187 188 def PrepareTestFiles(self): 189 """Prepare site.exp and board exp files.""" 190 # Create the boards directory. 191 os.mkdir('%s/boards' % self._tmp_abs) 192 193 # Generate the chromeos.exp file. 194 with open('%s/chromeos.exp.in' % self._base_dir, 'r') as template_file: 195 content = template_file.read() 196 substitutions = dict({ 197 '__boardname__': self._board, 198 '__board_hostname__': self._remote, 199 '__tmp_testing_rsa__': self._tmp_testing_rsa, 200 '__tmp_dir__': self._tmp}) 201 for pat, sub in substitutions.items(): 202 content = content.replace(pat, sub) 203 204 board_file_name = '%s/boards/%s.exp' % (self._tmp_abs, self._board) 205 with open(board_file_name, 'w') as board_file: 206 board_file.write(content) 207 208 # Generate the site file 209 with open('%s/site.exp' % self._tmp_abs, 'w') as site_file: 210 site_file.write('set target_list "%s"\n' % self._board) 211 212 def PrepareGcc(self): 213 if self._gcc_source_dir: 214 self.PrepareGccFromCustomizedPath() 215 else: 216 self.PrepareGccDefault() 217 self._l.LogOutput('Gcc source dir - {0}'.format(self._gcc_source_dir)) 218 self._l.LogOutput('Gcc build dir - {0}'.format(self._gcc_top_build_dir)) 219 220 def PrepareGccFromCustomizedPath(self): 221 """Prepare gcc source, build directory from mounted source.""" 222 # We have these source directories - 223 # _gcc_source_dir 224 # e.g. '/usr/local/toolchain_root/gcc' 225 # _gcc_source_dir_abs 226 # e.g. '/somewhere/chromeos.live/chroot/usr/local/toolchain_root/gcc' 227 # _gcc_source_dir_to_mount 228 # e.g. '/somewhere/gcc' 229 self._gcc_source_dir_abs = path.join(self._chromeos_chroot, 230 self._gcc_source_dir.lstrip('/')) 231 if not path.isdir(self._gcc_source_dir_abs) and \ 232 self._executer.RunCommand( 233 'sudo mkdir -p {0}'.format(self._gcc_source_dir_abs)): 234 raise Exception("Failed to create \'{0}\' inside chroot.".format( 235 self._gcc_source_dir)) 236 if not (path.isdir(self._gcc_source_dir_to_mount) and path.isdir( 237 path.join(self._gcc_source_dir_to_mount, 'gcc'))): 238 raise Exception("{0} is not a valid gcc source tree.".format( 239 self._gcc_source_dir_to_mount)) 240 241 # We have these build directories - 242 # _gcc_top_build_dir 243 # e.g. '/usr/local/toolchain_root/gcc-build-x86_64-cros-linux-gnu' 244 # _gcc_top_build_dir_abs 245 # e.g. '/somewhere/chromeos.live/chroo/tusr/local/toolchain_root/ 246 # gcc-build-x86_64-cros-linux-gnu' 247 # _gcc_build_dir 248 # e.g. '/usr/local/toolchain_root/gcc-build-x86_64-cros-linux-gnu/gcc' 249 # _gcc_build_dir_to_mount 250 # e.g. '/somewhere/gcc-build-x86_64-cros-linux-gnu' 251 self._gcc_top_build_dir = '{0}-build-{1}'.format( 252 self._gcc_source_dir.rstrip('/'), self._target) 253 self._gcc_build_dir = path.join(self._gcc_top_build_dir, 'gcc') 254 self._gcc_build_dir_to_mount = '{0}-build-{1}'.format( 255 self._gcc_source_dir_to_mount, self._target) 256 self._gcc_top_build_dir_abs = path.join(self._chromeos_chroot, 257 self._gcc_top_build_dir.lstrip('/')) 258 if not path.isdir(self._gcc_top_build_dir_abs) and \ 259 self._executer.RunCommand( 260 'sudo mkdir -p {0}'.format(self._gcc_top_build_dir_abs)): 261 raise Exception('Failed to create \'{0}\' inside chroot.'.format( 262 self._gcc_top_build_dir)) 263 if not (path.isdir(self._gcc_build_dir_to_mount) and path.join( 264 self._gcc_build_dir_to_mount, 'gcc')): 265 raise Exception('{0} is not a valid gcc build tree.'.format( 266 self._gcc_build_dir_to_mount)) 267 268 # All check passed. Now mount gcc source and build directories. 269 self.MountGccSourceAndBuildDir() 270 271 def PrepareGccDefault(self): 272 """Auto emerging gcc for building purpose only.""" 273 ret = self._executer.ChrootRunCommand( 274 self._chromeos_root, 275 'equery w cross-%s/gcc' % self._target, return_output=True)[1] 276 ret = path.basename(ret.strip()) 277 # ret is expected to be something like 'gcc-4.6.2-r11.ebuild' or 278 # 'gcc-9999.ebuild' parse it. 279 matcher = re.match('((.*)-r\d+).ebuild', ret) 280 if matcher: 281 gccrevision, gccversion = matcher.group(1, 2) 282 elif ret == 'gcc-9999.ebuild': 283 gccrevision = 'gcc-9999' 284 gccversion = 'gcc-9999' 285 else: 286 raise Exception('Failed to get gcc version.') 287 288 gcc_portage_dir = '/var/tmp/portage/cross-%s/%s/work' % ( 289 self._target, gccrevision) 290 self._gcc_source_dir = path.join(gcc_portage_dir, gccversion) 291 self._gcc_top_build_dir = (gcc_portage_dir + '/%s-build-%s') % ( 292 gccversion, self._target) 293 self._gcc_build_dir = path.join(self._gcc_top_build_dir, 'gcc') 294 gcc_build_dir_abs = path.join( 295 self._chromeos_root, 'chroot', self._gcc_build_dir.lstrip('/')) 296 if not path.isdir(gcc_build_dir_abs): 297 ret = self._executer.ChrootRunCommand( 298 self._chromeos_root, 299 ('ebuild $(equery w cross-%s/gcc) clean prepare compile' % ( 300 self._target))) 301 if ret: 302 raise Exception('ebuild gcc failed.') 303 304 def MakeCheck(self): 305 self.MountGccSourceAndBuildDir() 306 cmd = ('cd %s ; ' 307 'DEJAGNU=%s make %s RUNTESTFLAGS="--target_board=%s %s"' % 308 (self._gcc_build_dir, path.join(self._tmp, 'site.exp'), 309 self.MakeCheckString(), self._board, self._flags)) 310 self._executer.ChrootRunCommand(self._chromeos_root, cmd) 311 312 def ValidateFailures(self): 313 validate_failures_py = path.join( 314 self._gcc_source_dir, 315 'contrib/testsuite-management/validate_failures.py') 316 cmd = 'cd {0} ; {1} --build_dir={0}'.format( 317 self._gcc_top_build_dir, validate_failures_py) 318 self.MountGccSourceAndBuildDir() 319 ret = self._executer.ChrootRunCommand( 320 self._chromeos_root, cmd, return_output=True) 321 if ret[0] != 0: 322 self._l.LogWarning('*** validate_failures.py exited with non-zero code,' 323 'please run it manually inside chroot - \n' 324 ' ' + cmd) 325 return ret 326 327 # This method ensures necessary mount points before executing chroot comamnd. 328 def MountGccSourceAndBuildDir(self, unmount=False): 329 mount_points = [tc_enter_chroot.MountPoint( 330 self._gcc_source_dir_to_mount, self._gcc_source_dir_abs, 331 getpass.getuser(), "ro"), 332 tc_enter_chroot.MountPoint( 333 self._gcc_build_dir_to_mount, self._gcc_top_build_dir_abs, 334 getpass.getuser(), "rw"),] 335 for mp in mount_points: 336 if unmount: 337 if mp.UnMount(): 338 raise Exception('Failed to unmount {0}'.format(mp.mount_dir)) 339 else: 340 self._l.LogOutput('{0} unmounted successfully.'.format(mp.mount_dir)) 341 elif mp.DoMount(): 342 raise Exception('Failed to mount {0} onto {1}'.format( 343 mp.external_dir, mp.mount_dir)) 344 else: 345 self._l.LogOutput('{0} mounted successfully.'.format(mp.mount_dir)) 346 347# The end of class DejagnuExecuter 348 349def TryAcquireMachine(remotes): 350 available_machine = None 351 for r in remotes.split(','): 352 machine = lock_machine.Machine(r) 353 if machine.TryLock(timeout=300, exclusive=True): 354 available_machine = machine 355 break 356 else: 357 logger.GetLogger().LogWarning( 358 '*** Failed to lock machine \'{0}\'.'.format(r)) 359 if not available_machine: 360 raise Exception( 361 "Failed to acquire one machine from \'{0}\'.".format(remotes)) 362 return available_machine 363 364def Main(argv): 365 opts = ProcessArguments(argv) 366 available_machine = TryAcquireMachine(opts.remote) 367 executer = DejagnuExecuter(misc.GetRoot(argv[0])[0], 368 opts.mount, opts.chromeos_root, 369 available_machine._name, 370 opts.board, opts.flags, 371 opts.keep_intermediate_files, opts.tools, 372 opts.cleanup) 373 # Return value is a 3- or 4-element tuple 374 # element#1 - exit code 375 # element#2 - stdout 376 # element#3 - stderr 377 # element#4 - exception infor 378 # Some other scripts need these detailed information. 379 ret = (1, '', '') 380 try: 381 executer.SetupTestingDir() 382 executer.PrepareTestingRsaKeys() 383 executer.PrepareTestFiles() 384 executer.PrepareGcc() 385 executer.MakeCheck() 386 ret = executer.ValidateFailures() 387 except Exception as e: 388 # At least log the exception on console. 389 print e 390 # The #4 element encodes the runtime exception. 391 ret = (1, '', '', 'Exception happened during execution: \n' + str(e)) 392 finally: 393 available_machine.Unlock(exclusive=True) 394 executer.CleanupIntermediateFiles() 395 executer.Cleanup() 396 return ret 397 398if __name__ == '__main__': 399 retval = Main(sys.argv)[0] 400 sys.exit(retval) 401