Home | History | Annotate | Download | only in dejagnu
      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