| #!/usr/bin/python |
| # |
| # Copyright 2010 Google Inc. All Rights Reserved. |
| """Tool script for auto dejagnu.""" |
| |
| __author__ = 'shenhan@google.com (Han Shen)' |
| |
| import getpass |
| import optparse |
| import os |
| from os import path |
| import re |
| import shutil |
| import stat |
| import sys |
| import tempfile |
| import time |
| |
| import lock_machine |
| import tc_enter_chroot |
| from cros_utils import command_executer |
| from cros_utils import constants |
| from cros_utils import logger |
| from cros_utils import misc |
| |
| |
| def ProcessArguments(argv): |
| """Processing/validating script arguments.""" |
| parser = optparse.OptionParser(description=( |
| 'Launches gcc 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 gcc source to mount instead of "auto". ' |
| 'Under "auto" mode, which is the default - gcc is ' |
| 'checked out and built automatically at default ' |
| 'directories. Under "mount" mode ' |
| '- the gcc_source is set to "$chromeos_' |
| 'root/chroot/usr/local/toolchain_root/gcc", which is ' |
| 'the mount point for this option value, the ' |
| 'gcc-build-dir then is computed as ' |
| '"${gcc_source_dir}-build-${ctarget}". In this mode, ' |
| 'a complete gcc build must be performed in the ' |
| 'computed gcc-build-dir beforehand.')) |
| 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('-f', |
| '--flags', |
| dest='flags', |
| help='Optional. Extra run test flags to pass to dejagnu.') |
| parser.add_option('-k', |
| '--keep', |
| dest='keep_intermediate_files', |
| action='store_true', |
| default=False, |
| help=('Optional. Default to false. Do not remove dejagnu ' |
| 'intermediate files after test run.')) |
| parser.add_option('--cleanup', |
| dest='cleanup', |
| default=None, |
| help=('Optional. Values to this option could be ' |
| '\'mount\' (unmount gcc source and ' |
| 'directory directory, ' |
| 'only valid when --mount is given), ' |
| '\'chroot\' (delete chroot) and ' |
| '\'chromeos\' (delete the whole chromeos tree).')) |
| parser.add_option('-t', |
| '--tools', |
| dest='tools', |
| default='gcc,g++', |
| help=('Optional. Specify which tools to check, using ' |
| '","(comma) as separator. A typical value would be ' |
| '"g++" so that only g++ tests are performed. ' |
| 'Defaults to "gcc,g++".')) |
| |
| options, args = parser.parse_args(argv) |
| |
| if not options.chromeos_root: |
| raise SyntaxError('Missing argument for --chromeos_root.') |
| if not options.remote: |
| raise SyntaxError('Missing argument for --remote.') |
| if not options.board: |
| raise SyntaxError('Missing argument for --board.') |
| if options.cleanup == 'mount' and not options.mount: |
| raise SyntaxError('--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 ValueError('Invalid option value for --cleanup') |
| if options.cleanup and options.keep_intermediate_files: |
| raise SyntaxError('Only one of --keep and --cleanup could be given.') |
| |
| return options |
| |
| |
| class DejagnuExecuter(object): |
| """The class wrapper for dejagnu test executer.""" |
| |
| def __init__(self, base_dir, mount, chromeos_root, remote, board, flags, |
| keep_intermediate_files, tools, cleanup): |
| self._l = logger.GetLogger() |
| self._chromeos_root = chromeos_root |
| self._chromeos_chroot = path.join(chromeos_root, 'chroot') |
| if mount: |
| self._gcc_source_dir_to_mount = mount |
| self._gcc_source_dir = path.join(constants.MOUNTED_TOOLCHAIN_ROOT, 'gcc') |
| else: |
| self._gcc_source_dir = None |
| |
| self._remote = remote |
| self._board = board |
| ## Compute target from board |
| self._target = misc.GetCtargetFromBoard(board, chromeos_root) |
| if not self._target: |
| raise ValueError('Unsupported board "%s"' % board) |
| self._executer = command_executer.GetCommandExecuter() |
| self._flags = flags or '' |
| self._base_dir = base_dir |
| self._tmp_abs = None |
| self._keep_intermediate_files = keep_intermediate_files |
| self._tools = tools.split(',') |
| self._cleanup = cleanup |
| |
| 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 MakeCheckString(self): |
| return ' '.join(['check-{0}'.format(t) for t in self._tools if t]) |
| |
| def CleanupIntermediateFiles(self): |
| if self._tmp_abs and path.isdir(self._tmp_abs): |
| if self._keep_intermediate_files: |
| self._l.LogOutput( |
| 'Your intermediate dejagnu files are kept, you can re-run ' |
| 'inside chroot the command:') |
| self._l.LogOutput( |
| ' DEJAGNU={0} make -C {1} {2} RUNTESTFLAGS="--target_board={3} {4}"' \ |
| .format(path.join(self._tmp, 'site.exp'), self._gcc_build_dir, |
| self.MakeCheckString(), self._board, self._flags)) |
| else: |
| self._l.LogOutput('[Cleanup] - Removing temp dir - {0}'.format( |
| self._tmp_abs)) |
| shutil.rmtree(self._tmp_abs) |
| |
| def Cleanup(self): |
| if not self._cleanup: |
| return |
| |
| # Optionally cleanup mounted diretory, chroot and chromeos tree. |
| if self._cleanup == 'mount' or self._cleanup == 'chroot' or \ |
| self._cleanup == 'chromeos': |
| # No exceptions are allowed from this method. |
| try: |
| self._l.LogOutput('[Cleanup] - Unmounting directories ...') |
| self.MountGccSourceAndBuildDir(unmount=True) |
| except: |
| print 'Warning: failed to unmount gcc source/build directory.' |
| |
| 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 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/chromeos.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) |
| |
| def PrepareGcc(self): |
| if self._gcc_source_dir: |
| self.PrepareGccFromCustomizedPath() |
| else: |
| self.PrepareGccDefault() |
| self._l.LogOutput('Gcc source dir - {0}'.format(self._gcc_source_dir)) |
| self._l.LogOutput('Gcc build dir - {0}'.format(self._gcc_top_build_dir)) |
| |
| def PrepareGccFromCustomizedPath(self): |
| """Prepare gcc source, build directory from mounted source.""" |
| # We have these source directories - |
| # _gcc_source_dir |
| # e.g. '/usr/local/toolchain_root/gcc' |
| # _gcc_source_dir_abs |
| # e.g. '/somewhere/chromeos.live/chroot/usr/local/toolchain_root/gcc' |
| # _gcc_source_dir_to_mount |
| # e.g. '/somewhere/gcc' |
| self._gcc_source_dir_abs = path.join(self._chromeos_chroot, |
| self._gcc_source_dir.lstrip('/')) |
| if not path.isdir(self._gcc_source_dir_abs) and \ |
| self._executer.RunCommand( |
| 'sudo mkdir -p {0}'.format(self._gcc_source_dir_abs)): |
| raise RuntimeError("Failed to create \'{0}\' inside chroot.".format( |
| self._gcc_source_dir)) |
| if not (path.isdir(self._gcc_source_dir_to_mount) and |
| path.isdir(path.join(self._gcc_source_dir_to_mount, 'gcc'))): |
| raise RuntimeError('{0} is not a valid gcc source tree.'.format( |
| self._gcc_source_dir_to_mount)) |
| |
| # We have these build directories - |
| # _gcc_top_build_dir |
| # e.g. '/usr/local/toolchain_root/gcc-build-x86_64-cros-linux-gnu' |
| # _gcc_top_build_dir_abs |
| # e.g. '/somewhere/chromeos.live/chroo/tusr/local/toolchain_root/ |
| # gcc-build-x86_64-cros-linux-gnu' |
| # _gcc_build_dir |
| # e.g. '/usr/local/toolchain_root/gcc-build-x86_64-cros-linux-gnu/gcc' |
| # _gcc_build_dir_to_mount |
| # e.g. '/somewhere/gcc-build-x86_64-cros-linux-gnu' |
| self._gcc_top_build_dir = '{0}-build-{1}'.format( |
| self._gcc_source_dir.rstrip('/'), self._target) |
| self._gcc_build_dir = path.join(self._gcc_top_build_dir, 'gcc') |
| self._gcc_build_dir_to_mount = '{0}-build-{1}'.format( |
| self._gcc_source_dir_to_mount, self._target) |
| self._gcc_top_build_dir_abs = path.join(self._chromeos_chroot, |
| self._gcc_top_build_dir.lstrip('/')) |
| if not path.isdir(self._gcc_top_build_dir_abs) and \ |
| self._executer.RunCommand( |
| 'sudo mkdir -p {0}'.format(self._gcc_top_build_dir_abs)): |
| raise RuntimeError('Failed to create \'{0}\' inside chroot.'.format( |
| self._gcc_top_build_dir)) |
| if not (path.isdir(self._gcc_build_dir_to_mount) and path.join( |
| self._gcc_build_dir_to_mount, 'gcc')): |
| raise RuntimeError('{0} is not a valid gcc build tree.'.format( |
| self._gcc_build_dir_to_mount)) |
| |
| # All check passed. Now mount gcc source and build directories. |
| self.MountGccSourceAndBuildDir() |
| |
| def PrepareGccDefault(self): |
| """Auto emerging gcc for building purpose only.""" |
| ret = self._executer.ChrootRunCommandWOutput( |
| self._chromeos_root, 'equery w cross-%s/gcc' % self._target)[1] |
| ret = path.basename(ret.strip()) |
| # ret is expected to be something like 'gcc-4.6.2-r11.ebuild' or |
| # 'gcc-9999.ebuild' parse it. |
| matcher = re.match('((.*)-r\d+).ebuild', ret) |
| if matcher: |
| gccrevision, gccversion = matcher.group(1, 2) |
| elif ret == 'gcc-9999.ebuild': |
| gccrevision = 'gcc-9999' |
| gccversion = 'gcc-9999' |
| else: |
| raise RuntimeError('Failed to get gcc version.') |
| |
| gcc_portage_dir = '/var/tmp/portage/cross-%s/%s/work' % (self._target, |
| gccrevision) |
| self._gcc_source_dir = path.join(gcc_portage_dir, gccversion) |
| self._gcc_top_build_dir = (gcc_portage_dir + '/%s-build-%s') % ( |
| gccversion, self._target) |
| self._gcc_build_dir = path.join(self._gcc_top_build_dir, 'gcc') |
| gcc_build_dir_abs = path.join(self._chromeos_root, 'chroot', |
| self._gcc_build_dir.lstrip('/')) |
| if not path.isdir(gcc_build_dir_abs): |
| ret = self._executer.ChrootRunCommand(self._chromeos_root, ( |
| 'ebuild $(equery w cross-%s/gcc) clean prepare compile' % ( |
| self._target))) |
| if ret: |
| raise RuntimeError('ebuild gcc failed.') |
| |
| def MakeCheck(self): |
| self.MountGccSourceAndBuildDir() |
| cmd = ('cd %s ; ' |
| 'DEJAGNU=%s make %s RUNTESTFLAGS="--target_board=%s %s"' % |
| (self._gcc_build_dir, path.join(self._tmp, 'site.exp'), |
| self.MakeCheckString(), self._board, self._flags)) |
| self._executer.ChrootRunCommand(self._chromeos_root, cmd) |
| |
| def ValidateFailures(self): |
| validate_failures_py = path.join( |
| self._gcc_source_dir, |
| 'contrib/testsuite-management/validate_failures.py') |
| cmd = 'cd {0} ; {1} --build_dir={0}'.format(self._gcc_top_build_dir, |
| validate_failures_py) |
| self.MountGccSourceAndBuildDir() |
| ret = self._executer.ChrootRunCommandWOutput(self._chromeos_root, cmd) |
| if ret[0] != 0: |
| self._l.LogWarning('*** validate_failures.py exited with non-zero code,' |
| 'please run it manually inside chroot - \n' |
| ' ' + cmd) |
| return ret |
| |
| # This method ensures necessary mount points before executing chroot comamnd. |
| def MountGccSourceAndBuildDir(self, unmount=False): |
| mount_points = [tc_enter_chroot.MountPoint(self._gcc_source_dir_to_mount, |
| self._gcc_source_dir_abs, |
| getpass.getuser(), 'ro'), |
| tc_enter_chroot.MountPoint(self._gcc_build_dir_to_mount, |
| self._gcc_top_build_dir_abs, |
| getpass.getuser(), 'rw')] |
| for mp in mount_points: |
| if unmount: |
| if mp.UnMount(): |
| raise RuntimeError('Failed to unmount {0}'.format(mp.mount_dir)) |
| else: |
| self._l.LogOutput('{0} unmounted successfully.'.format(mp.mount_dir)) |
| elif mp.DoMount(): |
| raise RuntimeError( |
| 'Failed to mount {0} onto {1}'.format(mp.external_dir, |
| mp.mount_dir)) |
| else: |
| self._l.LogOutput('{0} mounted successfully.'.format(mp.mount_dir)) |
| |
| # The end of class DejagnuExecuter |
| |
| |
| def TryAcquireMachine(remotes): |
| available_machine = None |
| for r in remotes.split(','): |
| machine = lock_machine.Machine(r) |
| if machine.TryLock(timeout=300, exclusive=True): |
| available_machine = machine |
| break |
| else: |
| logger.GetLogger().LogWarning( |
| '*** Failed to lock machine \'{0}\'.'.format(r)) |
| if not available_machine: |
| raise RuntimeError("Failed to acquire one machine from \'{0}\'.".format( |
| remotes)) |
| return available_machine |
| |
| |
| 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.flags, |
| opts.keep_intermediate_files, opts.tools, 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.PrepareGcc() |
| executer.MakeCheck() |
| ret = executer.ValidateFailures() |
| 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: |
| available_machine.Unlock(exclusive=True) |
| executer.CleanupIntermediateFiles() |
| executer.Cleanup() |
| return ret |
| |
| |
| if __name__ == '__main__': |
| retval = Main(sys.argv)[0] |
| sys.exit(retval) |