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