1#!/usr/bin/python2.4
2#
3#
4# Copyright 2008, The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18"""Module that assists in parsing the output of "am instrument" commands run on
19the device."""
20
21import re
22import string
23
24
25def ParseAmInstrumentOutput(result):
26  """Given the raw output of an "am instrument" command that targets and
27  InstrumentationTestRunner, return structured data.
28
29  Args:
30    result (string): Raw output of "am instrument"
31
32  Return
33  (test_results, inst_finished_bundle)
34
35  test_results (list of am_output_parser.TestResult)
36  inst_finished_bundle (dict): Key/value pairs contained in the bundle that is
37    passed into ActivityManager.finishInstrumentation(). Included in this bundle is the return
38    code of the Instrumentation process, any error codes reported by the
39    activity manager, and any results explicity added by the instrumentation
40    code.
41  """
42
43  re_status_code = re.compile(r'INSTRUMENTATION_STATUS_CODE: (?P<status_code>-?\d)$')
44  test_results = []
45  inst_finished_bundle = {}
46
47  result_block_string = ""
48  for line in result.splitlines():
49    result_block_string += line + '\n'
50
51    if "INSTRUMENTATION_STATUS_CODE:" in line:
52      test_result = TestResult(result_block_string)
53      if test_result.GetStatusCode() == 1: # The test started
54        pass
55      elif test_result.GetStatusCode() in [0, -1, -2]:
56        test_results.append(test_result)
57      else:
58        pass
59      result_block_string = ""
60    if "INSTRUMENTATION_CODE:" in line:
61      inst_finished_bundle = _ParseInstrumentationFinishedBundle(result_block_string)
62      result_block_string = ""
63
64  return (test_results, inst_finished_bundle)
65
66
67def _ParseInstrumentationFinishedBundle(result):
68  """Given the raw output of "am instrument" returns a dictionary of the
69  key/value pairs from the bundle passed into
70  ActivityManager.finishInstrumentation().
71
72  Args:
73    result (string): Raw output of "am instrument"
74
75  Return:
76  inst_finished_bundle (dict): Key/value pairs contained in the bundle that is
77    passed into ActivityManager.finishInstrumentation(). Included in this bundle is the return
78    code of the Instrumentation process, any error codes reported by the
79    activity manager, and any results explicity added by the instrumentation
80    code.
81  """
82
83  re_result = re.compile(r'INSTRUMENTATION_RESULT: ([^=]+)=(.*)$')
84  re_code = re.compile(r'INSTRUMENTATION_CODE: (\-?\d)$')
85  result_dict = {}
86  key = ''
87  val = ''
88  last_tag = ''
89
90  for line in result.split('\n'):
91    line = line.strip(string.whitespace)
92    if re_result.match(line):
93      last_tag = 'INSTRUMENTATION_RESULT'
94      key = re_result.search(line).group(1).strip(string.whitespace)
95      if key.startswith('performance.'):
96        key = key[len('performance.'):]
97      val = re_result.search(line).group(2).strip(string.whitespace)
98      try:
99        result_dict[key] = float(val)
100      except ValueError:
101        result_dict[key] = val
102      except TypeError:
103        result_dict[key] = val
104    elif re_code.match(line):
105      last_tag = 'INSTRUMENTATION_CODE'
106      key = 'code'
107      val = re_code.search(line).group(1).strip(string.whitespace)
108      result_dict[key] = val
109    elif 'INSTRUMENTATION_ABORTED:' in line:
110      last_tag = 'INSTRUMENTATION_ABORTED'
111      key = 'INSTRUMENTATION_ABORTED'
112      val = ''
113      result_dict[key] = val
114    elif last_tag == 'INSTRUMENTATION_RESULT':
115      result_dict[key] += '\n' + line
116
117  if not result_dict.has_key('code'):
118    result_dict['code'] = '0'
119    result_dict['shortMsg'] = "No result returned from instrumentation"
120
121  return result_dict
122
123
124class TestResult(object):
125  """A class that contains information about a single test result."""
126
127  def __init__(self, result_block_string):
128    """
129    Args:
130      result_block_string (string): Is a single "block" of output. A single
131      "block" would be either a "test started" status report, or a "test
132      finished" status report.
133    """
134
135    self._test_name = None
136    self._status_code = None
137    self._failure_reason = None
138    self._fields_map = {}
139
140    re_status_code = re.search(r'INSTRUMENTATION_STATUS_CODE: '
141        '(?P<status_code>1|0|-1|-2)', result_block_string)
142    re_fields = re.compile(r'INSTRUMENTATION_STATUS: '
143        '(?P<key>[\w.]+)=(?P<value>.*?)(?=\nINSTRUMENTATION_STATUS)', re.DOTALL)
144
145    for field in re_fields.finditer(result_block_string):
146      key, value = (field.group('key').strip(), field.group('value').strip())
147      if key.startswith('performance.'):
148        key = key[len('performance.'):]
149      self._fields_map[key] = value
150    self._fields_map.setdefault('class')
151    self._fields_map.setdefault('test')
152
153    self._test_name = '%s:%s' % (self._fields_map['class'],
154                                 self._fields_map['test'])
155    self._status_code = int(re_status_code.group('status_code'))
156    if 'stack' in self._fields_map:
157      self._failure_reason = self._fields_map['stack']
158
159  def GetTestName(self):
160    return self._test_name
161
162  def GetStatusCode(self):
163    return self._status_code
164
165  def GetFailureReason(self):
166    return self._failure_reason
167
168  def GetResultFields(self):
169    return self._fields_map
170