| #! /usr/bin/python |
| |
| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """The gdb dejagnu test wrapper.""" |
| import optparse |
| import os |
| from os import path |
| import re |
| import shutil |
| import stat |
| import sys |
| import tempfile |
| import time |
| |
| from utils import command_executer |
| from utils import logger |
| from utils import misc |
| |
| from run_dejagnu import TryAcquireMachine |
| |
| _VALID_TEST_RESULTS = ['FAIL', 'UNRESOLVED', 'XPASS', |
| 'ERROR', 'UNSUPPORTED', 'PASS'] |
| |
| |
| def ProcessArguments(argv): |
| """Processing/validating script arguments.""" |
| parser = optparse.OptionParser(description=( |
| 'Launches gdb dejagnu test in chroot for chromeos toolchain, compares ' |
| 'the test result with a repository baseline and prints out the result.'), |
| usage='run_dejagnu options') |
| parser.add_option('-c', '--chromeos_root', dest='chromeos_root', |
| help='Required. Specify chromeos root') |
| parser.add_option('-m', '--mount', dest='mount', |
| help=('Specify gdb source to mount instead of "auto". ' |
| 'Under "auto" mode, which is the default - gdb is ' |
| 'checked out and built automatically at default ' |
| 'directories. Under "mount" mode ' |
| '- the gdb_source is set to "$chromeos_' |
| 'root/chroot/usr/local/toolchain_root/gdb", which is ' |
| 'the mount point for this option value.')) |
| parser.add_option('-b', '--board', dest='board', |
| help=('Required. Specify board.')) |
| parser.add_option('-r', '--remote', dest='remote', |
| help=('Required. Specify addresses/names of the board, ' |
| 'seperate each address/name using comma(\',\').')) |
| parser.add_option('--cleanup', dest='cleanup', default=None, |
| help=('Optional. Values to this option could be ' |
| '\'chroot\' (delete chroot) and ' |
| '\'chromeos\' (delete the whole chromeos tree).')) |
| |
| options, args = parser.parse_args(argv) |
| |
| if not options.chromeos_root: |
| raise Exception('Missing argument for --chromeos_root.') |
| if not options.remote: |
| raise Exception('Missing argument for --remote.') |
| if not options.board: |
| raise Exception('Missing argument for --board.') |
| if options.cleanup == 'mount' and not options.mount: |
| raise Exception('--cleanup=\'mount\' not valid unless --mount is given.') |
| if options.cleanup and not ( |
| options.cleanup == 'mount' or |
| options.cleanup == 'chroot' or options.cleanup == 'chromeos'): |
| raise Exception('Invalid option value for --cleanup') |
| |
| return options |
| |
| |
| class DejagnuExecuter(object): |
| """The class wrapper for dejagnu test executer.""" |
| |
| def __init__(self, base_dir, source_dir, chromeos_root, remote, board, |
| cleanup): |
| self._l = logger.GetLogger() |
| self._chromeos_root = chromeos_root |
| self._chromeos_chroot = path.join(chromeos_root, 'chroot') |
| |
| self._remote = remote |
| self._board = board |
| ## Compute target from board |
| self._target = misc.GetCtargetFromBoard(board, chromeos_root) |
| if not self._target: |
| raise Exception('Unsupported board "%s"' % board) |
| self._executer = command_executer.GetCommandExecuter() |
| self._base_dir = base_dir |
| self._tmp_abs = None |
| self._cleanup = cleanup |
| self._sshflag = ('-o StrictHostKeyChecking=no ' + |
| '-o CheckHostIP=no ' + |
| '-o UserKnownHostsFile=$(mktemp) ') |
| |
| if source_dir: |
| self._source_dir = source_dir |
| self._mount_flag = 'USE="mounted_sources"' |
| self.MountSource() |
| else: |
| self._source_dir = None |
| self._mount_flag = '' |
| |
| def SetupTestingDir(self): |
| self._tmp_abs = tempfile.mkdtemp(prefix='dejagnu_', dir=path.join( |
| self._chromeos_chroot, 'tmp')) |
| self._tmp = self._tmp_abs[len(self._chromeos_chroot):] |
| self._tmp_testing_rsa = path.join(self._tmp, 'testing_rsa') |
| self._tmp_testing_rsa_abs = path.join(self._tmp_abs, 'testing_rsa') |
| |
| def PrepareTestingRsaKeys(self): |
| if not path.isfile(self._tmp_testing_rsa_abs): |
| shutil.copy(path.join( |
| self._chromeos_root, |
| 'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa'), |
| self._tmp_testing_rsa_abs) |
| os.chmod(self._tmp_testing_rsa_abs, stat.S_IRUSR) |
| |
| def PrepareTestFiles(self): |
| """Prepare site.exp and board exp files.""" |
| # Create the boards directory. |
| os.mkdir('%s/boards' % self._tmp_abs) |
| |
| # Generate the chromeos.exp file. |
| with open('%s/boards/gdb.exp.in' % self._base_dir, 'r') as template_file: |
| content = template_file.read() |
| |
| substitutions = dict({ |
| '__boardname__': self._board, |
| '__board_hostname__': self._remote, |
| '__tmp_testing_rsa__': self._tmp_testing_rsa, |
| '__tmp_dir__': self._tmp}) |
| for pat, sub in substitutions.items(): |
| content = content.replace(pat, sub) |
| |
| board_file_name = '%s/boards/%s.exp' % (self._tmp_abs, self._board) |
| with open(board_file_name, 'w') as board_file: |
| board_file.write(content) |
| |
| # Generate the site file |
| with open('%s/site.exp' % self._tmp_abs, 'w') as site_file: |
| site_file.write('set target_list "%s"\n' % self._board) |
| |
| with open('%s/boards/gdbserver.sh.in' % self._base_dir, 'r') \ |
| as template_file: |
| content = template_file.read() |
| substitutions = dict({ |
| '__board_hostname__': self._remote, |
| '__tmp_testing_rsa__': self._tmp_testing_rsa, |
| '__tmp_dir__': self._tmp}) |
| for pat, sub in substitutions.items(): |
| content = content.replace(pat, sub) |
| |
| gdbserver_file_name = '%s/boards/gdbserver.sh' % (self._tmp_abs) |
| with open(gdbserver_file_name, 'w') as board_file: |
| board_file.write(content) |
| |
| st = os.stat(gdbserver_file_name) |
| os.chmod(gdbserver_file_name, st.st_mode | stat.S_IXGRP | stat.S_IXUSR) |
| |
| def PrepareGdb(self): |
| self.PrepareGdbDefault() |
| |
| def PrepareGdbDefault(self): |
| ret = self._executer.ChrootRunCommand( |
| self._chromeos_root, |
| 'equery w cross-%s/gdb' % self._target, return_output=True)[1] |
| ret = path.basename(ret.strip()) |
| |
| matcher = re.match(r'(.*).ebuild', ret) |
| if matcher: |
| gdb_reversion = matcher.group(1) |
| else: |
| raise Exception('Failed to get gdb reversion.') |
| gdb_version = gdb_reversion.split('-r')[0] |
| gdb_portage_dir = '/var/tmp/portage/cross-%s/%s/work' % ( |
| self._target, gdb_reversion) |
| self._gdb_source_dir = path.join(gdb_portage_dir, gdb_version) |
| |
| ret = self._executer.ChrootRunCommand( |
| self._chromeos_root, |
| ('sudo %s ebuild $(equery w cross-%s/gdb) clean compile' % ( |
| self._mount_flag, self._target))) |
| if ret: |
| raise Exception('ebuild gdb failed.') |
| |
| def PrepareGdbserver(self): |
| self.PrepareGdbserverDefault() |
| |
| def PrepareGdbserverDefault(self): |
| cmd = ('./setup_board --board {0}; ' |
| '{1} emerge-{0} gdb'.format(self._board, self._mount_flag)) |
| ret = self._executer.ChrootRunCommand( |
| self._chromeos_root, |
| cmd, print_to_console=True) |
| if ret: |
| raise Exception('ebuild gdbserver failed.') |
| |
| cmd = ('scp -i {0} {1} ' |
| '/build/{2}/usr/bin/gdbserver root@{3}:/usr/local/bin/' |
| .format(self._tmp_testing_rsa, self._sshflag, |
| self._board, self._remote)) |
| ret = self._executer.ChrootRunCommand( |
| self._chromeos_root, |
| cmd, print_to_console=True) |
| if ret: |
| raise Exception('copy gdbserver failed.') |
| |
| if self._mount_flag: |
| self.MountSource(unmount=False) |
| |
| def Cleanup(self): |
| if not self._cleanup: |
| return |
| |
| if self._cleanup == 'chroot' or self._cleanup == 'chromeos': |
| self._l.LogOutput('[Cleanup]: Deleting chroot inside \'{0}\''.format( |
| self._chromeos_root)) |
| command = 'cd %s; cros_sdk --delete' % self._chromeos_root |
| rv = self._executer.RunCommand(command) |
| if rv: |
| self._l.LogWarning('Warning - failed to delete chroot.') |
| # Delete .cache - crosbug.com/34956 |
| command = 'sudo rm -fr %s' % os.path.join(self._chromeos_root, '.cache') |
| rv = self._executer.RunCommand(command) |
| if rv: |
| self._l.LogWarning('Warning - failed to delete \'.cache\'.') |
| |
| if self._cleanup == 'chromeos': |
| self._l.LogOutput('[Cleanup]: Deleting chromeos tree \'{0}\' ...'.format( |
| self._chromeos_root)) |
| command = 'rm -fr {0}'.format(self._chromeos_root) |
| rv = self._executer.RunCommand(command) |
| if rv: |
| self._l.LogWarning('Warning - failed to remove chromeos tree.') |
| |
| def MakeCheck(self): |
| cmd = ('ssh -i {0} {1} root@{2} "reboot && exit"' |
| .format(self._tmp_testing_rsa, self._sshflag, |
| self._remote)) |
| self._executer.ChrootRunCommand( |
| self._chromeos_root, cmd) |
| time.sleep(40) |
| |
| cmd = ('ssh -i {0} {1} root@{2} ' |
| '"iptables -A INPUT -p tcp --dport 1234 -j ACCEPT"' |
| .format(self._tmp_testing_rsa, self._sshflag, |
| self._remote)) |
| self._executer.ChrootRunCommand( |
| self._chromeos_root, cmd) |
| |
| cmd = ('cd %s ; ' |
| 'DEJAGNU=%s make check' % |
| (path.join(self._gdb_source_dir, 'gdb'), |
| path.join(self._tmp, 'site.exp'))) |
| ret = self._executer.ChrootRunCommand( |
| self._chromeos_root, cmd) |
| if ret: |
| raise Exception('Make check failed.') |
| |
| # This method ensures necessary mount points before executing chroot comamnd. |
| def MountSource(self, unmount=False): |
| script = os.path.join(self._base_dir, 'build_tc.py') |
| if unmount: |
| mount = '-u' |
| else: |
| mount = '-m' |
| cmd = ('python {0} --chromeos_root={1} ' |
| '--gdb_dir={2} --board={3} {4}' |
| .format(script, self._chromeos_root, |
| self._source_dir, self._board, |
| mount)) |
| rv = self._executer.RunCommand(cmd) |
| if rv: |
| raise Exception('Mount source failed.') |
| |
| def ResultValidate(self): |
| self.PrepareResult() |
| result = [] |
| for key, value in self.base_result.items(): |
| if 'PASS' not in value: |
| continue |
| if key not in self.test_result: |
| continue |
| test_result = self.test_result[key] |
| if 'PASS' not in test_result: |
| result.append(key) |
| return result |
| |
| def PrepareResult(self): |
| test_output = os.path.join(self._gdb_source_dir, 'gdb', |
| 'testsuite', 'gdb.sum') |
| test_output = misc.GetOutsideChrootPath(self._chromeos_root, |
| test_output) |
| base_output = os.path.join(self._base_dir, 'gdb_baseline', self._target) |
| |
| self.test_result = self.ParseResult(test_output) |
| self.base_result = self.ParseResult(base_output) |
| |
| def ParseResult(self, gdb_sum): |
| result = {} |
| multi_keys = {} |
| with open(gdb_sum) as input_sum: |
| for line in input_sum: |
| line = line.strip() |
| r = line.split(':', 1) |
| if r[0] in _VALID_TEST_RESULTS: |
| key = r[1] |
| if r[1] in result: |
| if r[1] in multi_keys: |
| multi_keys[r[1]] += 1 |
| else: |
| multi_keys[r[1]] = 2 |
| key = r[1] + '_____{0}_____'.format(multi_keys[r[1]]) |
| result[key] = r[0] |
| return result |
| |
| |
| def Main(argv): |
| opts = ProcessArguments(argv) |
| available_machine = TryAcquireMachine(opts.remote) |
| executer = DejagnuExecuter(misc.GetRoot(argv[0])[0], |
| opts.mount, opts.chromeos_root, |
| available_machine._name, |
| opts.board, |
| opts.cleanup) |
| # Return value is a 3- or 4-element tuple |
| # element#1 - exit code |
| # element#2 - stdout |
| # element#3 - stderr |
| # element#4 - exception infor |
| # Some other scripts need these detailed information. |
| ret = (1, '', '') |
| try: |
| executer.SetupTestingDir() |
| executer.PrepareTestingRsaKeys() |
| executer.PrepareTestFiles() |
| executer.PrepareGdb() |
| executer.PrepareGdbserver() |
| executer.MakeCheck() |
| result = executer.ResultValidate() |
| print result |
| if result: |
| ret = (1, result, '') |
| else: |
| ret = (0, '', '') |
| |
| except Exception as e: |
| # At least log the exception on console. |
| print e |
| # The #4 element encodes the runtime exception. |
| ret = (1, '', '', 'Exception happened during execution: \n' + str(e)) |
| finally: |
| executer.Cleanup() |
| return ret |
| |
| if __name__ == '__main__': |
| retval = Main(sys.argv)[0] |
| sys.exit(retval) |