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