• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2#
3# Copyright 2010 Google Inc. All Rights Reserved.
4"""Tool script for auto dejagnu."""
5
6__author__ = 'shenhan@google.com (Han Shen)'
7
8import getpass
9import optparse
10import os
11from os import path
12import re
13import shutil
14import stat
15import sys
16import tempfile
17import time
18
19import lock_machine
20import tc_enter_chroot
21from cros_utils import command_executer
22from cros_utils import constants
23from cros_utils import logger
24from cros_utils import misc
25
26
27def 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
109class 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
367def 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
383def 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
416if __name__ == '__main__':
417  retval = Main(sys.argv)[0]
418  sys.exit(retval)
419