gdb_dejagnu.py revision 34d759e2a60ad7ce7da18031b9a7d9f31d658ef6
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
7"""The gdb dejagnu test wrapper."""
8import optparse
9import os
10from os import path
11import re
12import shutil
13import stat
14import sys
15import tempfile
16import time
17
18from utils import command_executer
19from utils import logger
20from utils import misc
21
22from run_dejagnu import TryAcquireMachine
23
24_VALID_TEST_RESULTS = ['FAIL', 'UNRESOLVED', 'XPASS',
25                       'ERROR', 'UNSUPPORTED', 'PASS']
26
27
28def ProcessArguments(argv):
29  """Processing/validating script arguments."""
30  parser = optparse.OptionParser(description=(
31      'Launches gdb dejagnu test in chroot for chromeos toolchain, compares '
32      'the test result with a repository baseline and prints out the result.'),
33                                 usage='run_dejagnu options')
34  parser.add_option('-c', '--chromeos_root', dest='chromeos_root',
35                    help='Required. Specify chromeos root')
36  parser.add_option('-m', '--mount', dest='mount',
37                    help=('Specify gdb source to mount instead of "auto". '
38                          'Under "auto" mode, which is the default - gdb is '
39                          'checked out and built automatically at default '
40                          'directories. Under "mount" mode '
41                          '- the gdb_source is set to "$chromeos_'
42                          'root/chroot/usr/local/toolchain_root/gdb", which is '
43                          'the mount point for this option value.'))
44  parser.add_option('-b', '--board', dest='board',
45                    help=('Required. Specify board.'))
46  parser.add_option('-r', '--remote', dest='remote',
47                    help=('Required. Specify addresses/names of the board, '
48                          'seperate each address/name using comma(\',\').'))
49  parser.add_option('--cleanup', dest='cleanup', default=None,
50                    help=('Optional. Values to this option could be '
51                          '\'chroot\' (delete chroot) and '
52                          '\'chromeos\' (delete the whole chromeos tree).'))
53
54  options, args = parser.parse_args(argv)
55
56  if not options.chromeos_root:
57    raise Exception('Missing argument for --chromeos_root.')
58  if not options.remote:
59    raise Exception('Missing argument for --remote.')
60  if not options.board:
61    raise Exception('Missing argument for --board.')
62  if options.cleanup == 'mount' and not options.mount:
63    raise Exception('--cleanup=\'mount\' not valid unless --mount is given.')
64  if options.cleanup and not (
65      options.cleanup == 'mount' or
66      options.cleanup == 'chroot' or options.cleanup == 'chromeos'):
67    raise Exception('Invalid option value for --cleanup')
68  if options.cleanup and options.keep_intermediate_files:
69    raise Exception('Only one of --keep and --cleanup could be given.')
70
71  return options
72
73
74class DejagnuExecuter(object):
75  """The class wrapper for dejagnu test executer."""
76
77  def __init__(self, base_dir, source_dir, chromeos_root, remote, board,
78               cleanup):
79    self._l = logger.GetLogger()
80    self._chromeos_root = chromeos_root
81    self._chromeos_chroot = path.join(chromeos_root, 'chroot')
82
83    self._remote = remote
84    self._board = board
85    ## Compute target from board
86    self._target = misc.GetCtargetFromBoard(board, chromeos_root)
87    if not self._target:
88      raise Exception('Unsupported board "%s"' % board)
89    self._executer = command_executer.GetCommandExecuter()
90    self._base_dir = base_dir
91    self._tmp_abs = None
92    self._cleanup = cleanup
93    self._sshflag = ('-o StrictHostKeyChecking=no' +
94                     '-o CheckHostIP=no' +
95                     '-o UserKnownHostsFile=$(mktemp)')
96
97    if source_dir:
98      self._source_dir = source_dir
99      self._mount_flag = 'USE="mounted_sources"'
100      self.MountSource()
101    else:
102      self._source_dir = None
103      self._mount_flag = ''
104
105  def SetupTestingDir(self):
106    self._tmp_abs = tempfile.mkdtemp(prefix='dejagnu_', dir=path.join(
107        self._chromeos_chroot, 'tmp'))
108    self._tmp = self._tmp_abs[len(self._chromeos_chroot):]
109    self._tmp_testing_rsa = path.join(self._tmp, 'testing_rsa')
110    self._tmp_testing_rsa_abs = path.join(self._tmp_abs, 'testing_rsa')
111
112  def PrepareTestingRsaKeys(self):
113    if not path.isfile(self._tmp_testing_rsa_abs):
114      shutil.copy(path.join(
115          self._chromeos_root,
116          'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa'),
117                  self._tmp_testing_rsa_abs)
118      os.chmod(self._tmp_testing_rsa_abs, stat.S_IRUSR)
119
120  def PrepareTestFiles(self):
121    """Prepare site.exp and board exp files."""
122    # Create the boards directory.
123    os.mkdir('%s/boards' % self._tmp_abs)
124
125    # Generate the chromeos.exp file.
126    with open('%s/boards/gdb.exp.in' % self._base_dir, 'r') as template_file:
127      content = template_file.read()
128    substitutions = dict({
129        '__boardname__': self._board,
130        '__board_hostname__': self._remote,
131        '__tmp_testing_rsa__': self._tmp_testing_rsa,
132        '__tmp_dir__': self._tmp})
133    for pat, sub in substitutions.items():
134      content = content.replace(pat, sub)
135
136    board_file_name = '%s/boards/%s.exp' % (self._tmp_abs, self._board)
137    with open(board_file_name, 'w') as board_file:
138      board_file.write(content)
139
140    # Generate the site file
141    with open('%s/site.exp' % self._tmp_abs, 'w') as site_file:
142      site_file.write('set target_list "%s"\n' % self._board)
143
144    with open('%s/boards/gdbserver.sh.in' % self._base_dir, 'r') \
145        as template_file:
146      content = template_file.read()
147    substitutions = dict({
148        '__board_hostname__': self._remote,
149        '__tmp_testing_rsa__': self._tmp_testing_rsa,
150        '__tmp_dir__': self._tmp})
151    for pat, sub in substitutions.items():
152      content = content.replace(pat, sub)
153
154    gdbserver_file_name = '%s/boards/gdbserver.sh' % (self._tmp_abs)
155    with open(gdbserver_file_name, 'w') as board_file:
156      board_file.write(content)
157
158    st = os.stat(gdbserver_file_name)
159    os.chmod(gdbserver_file_name, st.st_mode | stat.S_IXGRP | stat.S_IXUSR)
160
161  def PrepareGdb(self):
162    self.PrepareGdbDefault()
163
164  def PrepareGdbDefault(self):
165    ret = self._executer.ChrootRunCommand(
166        self._chromeos_root,
167        'equery w cross-%s/gdb' % self._target, return_output=True)[1]
168    ret = path.basename(ret.strip())
169
170    matcher = re.match(r'(.*).ebuild', ret)
171    if matcher:
172      gdb_reversion = matcher.group(1)
173    else:
174      raise Exception('Failed to get gdb reversion.')
175    gdb_version = gdb_reversion.split('-r')[0]
176    gdb_portage_dir = '/var/tmp/portage/cross-%s/%s/work' % (
177        self._target, gdb_reversion)
178    self._gdb_source_dir = path.join(gdb_portage_dir, gdb_version)
179
180    ret = self._executer.ChrootRunCommand(
181        self._chromeos_root,
182        ('sudo %s ebuild $(equery w cross-%s/gdb) clean compile' % (
183            self._mount_flag, self._target)))
184    if ret:
185      raise Exception('ebuild gdb failed.')
186
187  def PrepareGdbserver(self):
188    self.PrepareGdbserverDefault()
189
190  def PrepareGdbserverDefault(self):
191    cmd = ('./setup_board --board {0}; '
192           '{1} emerge-{0} gdb'.format(self._board, self._mount_flag))
193    ret = self._executer.ChrootRunCommand(
194        self._chromeos_root,
195        cmd, print_to_console=True)
196    if ret:
197      raise Exception('ebuild gdbserver failed.')
198
199    cmd = ('scp -i {0}  {1} '
200           '/build/{2}/usr/bin/gdbserver root@{3}:/usr/local/bin/'
201           .format(self._tmp_testing_rsa, self._sshflag,
202                   self._board, self._remote))
203    ret = self._executer.ChrootRunCommand(
204        self._chromeos_root,
205        cmd, print_to_console=True)
206    if ret:
207      raise Exception('copy gdbserver failed.')
208
209    if self._mount_flag:
210      self.MountSource(unmount=False)
211
212  def Cleanup(self):
213    if not self._cleanup:
214      return
215
216    if self._cleanup == 'chroot' or self._cleanup == 'chromeos':
217      self._l.LogOutput('[Cleanup]: Deleting chroot inside \'{0}\''.format(
218          self._chromeos_root))
219      command = 'cd %s; cros_sdk --delete' % self._chromeos_root
220      rv = self._executer.RunCommand(command)
221      if rv:
222        self._l.LogWarning('Warning - failed to delete chroot.')
223      # Delete .cache - crosbug.com/34956
224      command = 'sudo rm -fr %s' % os.path.join(self._chromeos_root, '.cache')
225      rv = self._executer.RunCommand(command)
226      if rv:
227        self._l.LogWarning('Warning - failed to delete \'.cache\'.')
228
229    if self._cleanup == 'chromeos':
230      self._l.LogOutput('[Cleanup]: Deleting chromeos tree \'{0}\' ...'.format(
231          self._chromeos_root))
232      command = 'rm -fr {0}'.format(self._chromeos_root)
233      rv = self._executer.RunCommand(command)
234      if rv:
235        self._l.LogWarning('Warning - failed to remove chromeos tree.')
236
237  def MakeCheck(self):
238    cmd = ('ssh -i {0} {1}  root@{2} "reboot && exit"'
239           .format(self._tmp_testing_rsa, self._sshflag,
240                   self._remote))
241    self._executer.ChrootRunCommand(
242        self._chromeos_root, cmd)
243    time.sleep(40)
244
245    cmd = ('ssh -i {0} {1}  root@{2} '
246           '"iptables -A INPUT -p tcp --dport 1234 -j ACCEPT"'
247           .format(self._tmp_testing_rsa, self._sshflag,
248                   self._remote))
249    self._executer.ChrootRunCommand(
250        self._chromeos_root, cmd)
251
252    cmd = ('cd %s ; '
253           'DEJAGNU=%s make check' %
254           (path.join(self._gdb_source_dir, 'gdb'),
255            path.join(self._tmp, 'site.exp')))
256    ret = self._executer.ChrootRunCommand(
257        self._chromeos_root, cmd)
258    if ret:
259      raise Exception('Make check failed.')
260
261  # This method ensures necessary mount points before executing chroot comamnd.
262  def MountSource(self, unmount=False):
263    script = os.path.join(self._base_dir, 'build_tc.py')
264    if unmount:
265      mount = '-u'
266    else:
267      mount = '-m'
268    cmd = ('python {0} --chromeos_root={1} '
269           '--gdb_dir={2} --board={3} {4}'
270           .format(script, self._chromeos_root,
271                   self._source_dir, self._board,
272                   mount))
273    rv = self._executer.RunCommand(cmd)
274    if rv:
275      raise Exception('Mount source failed.')
276
277  def ResultValidate(self):
278    self.PrepareResult()
279    result = 0
280    for key, value in self.base_result.items():
281      if 'PASS' not in value:
282        continue
283      test_result = self.test_result[key]
284      if 'PASS' not in test_result:
285        result = 1
286    return result
287
288  def PrepareResult(self):
289    test_output = os.path.join(self._gdb_source_dir, 'gdb',
290                               'testsuite', 'gdb.sum')
291    test_output = misc.GetOutsideChrootPath(self._chromeos_root,
292                                            test_output)
293    base_output = os.path.join(self._base_dir, 'gdb_baseline', self._target)
294
295    self.test_result = self.ParseResult(test_output)
296    self.base_result = self.ParseResult(base_output)
297
298  def ParseResult(self, gdb_sum):
299    result = {}
300    multi_keys = {}
301    with open(gdb_sum) as input_sum:
302      for line in input_sum:
303        line = line.strip()
304        r = line.split(':', 1)
305        if r[0] in _VALID_TEST_RESULTS:
306          key = r[1]
307          if r[1] in result:
308            if r[1] in multi_keys:
309              multi_keys[r[1]] += 1
310            else:
311              multi_keys[r[1]] = 2
312            key = r[1] + '_____{0}_____'.format(multi_keys[r[1]])
313          result[key] = r[0]
314    return result
315
316
317def Main(argv):
318  opts = ProcessArguments(argv)
319  available_machine = TryAcquireMachine(opts.remote)
320  executer = DejagnuExecuter(misc.GetRoot(argv[0])[0],
321                             opts.mount, opts.chromeos_root,
322                             available_machine,
323                             opts.board,
324                             opts.cleanup)
325  # Return value is a 3- or 4-element tuple
326  #   element#1 - exit code
327  #   element#2 - stdout
328  #   element#3 - stderr
329  #   element#4 - exception infor
330  # Some other scripts need these detailed information.
331  ret = (1, '', '')
332  try:
333    executer.SetupTestingDir()
334    executer.PrepareTestingRsaKeys()
335    executer.PrepareTestFiles()
336    executer.PrepareGdb()
337    executer.PrepareGdbserver()
338    executer.MakeCheck()
339    print executer.ResultValidate()
340   # ret = executer.ValidateFailures()
341  except Exception as e:
342    # At least log the exception on console.
343    print e
344    # The #4 element encodes the runtime exception.
345    ret = (1, '', '', 'Exception happened during execution: \n' + str(e))
346  finally:
347    executer.Cleanup()
348    return ret
349
350if __name__ == '__main__':
351  retval = Main(sys.argv)[0]
352  sys.exit(retval)
353