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