-
Notifications
You must be signed in to change notification settings - Fork 68
/
xcresult_util.py
137 lines (118 loc) · 5.17 KB
/
xcresult_util.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helper class for parsing xcresult under Xcode 11 or later."""
import json
import os
import subprocess
from xctestrunner.shared import ios_errors
def ExposeXcresult(xcresult_path, output_path):
"""Exposes the files from xcresult.
The files includes the diagnostics files and attachments files.
Args:
xcresult_path: string, path of xcresult bundle.
output_path: string, path of output directory.
"""
root_result_bundle = _GetResultBundleObject(xcresult_path, bundle_id=None)
actions = root_result_bundle['actions']['_values']
action_result = None
for action in actions:
if action['_type']['_name'] == 'ActionRecord':
action_result = action['actionResult']
break
if action_result is None:
raise ios_errors.XcresultError(
'Failed to get "ActionResult" from result bundle %s' %
root_result_bundle)
_ExposeDiagnostics(xcresult_path, output_path, action_result)
_ExposeAttachments(xcresult_path, output_path, action_result)
def _ExposeDiagnostics(xcresult_path, output_path, action_result):
"""Exposes the diagnostics files from the given xcresult file."""
if 'diagnosticsRef' not in action_result:
return
diagnostics_id = action_result['diagnosticsRef']['id']['_value']
subprocess.check_call([
'xcrun', 'xcresulttool', 'export', '--path', xcresult_path,
'--output-path', output_path, '--type', 'directory', '--id',
diagnostics_id
])
def _ExposeAttachments(xcresult_path, output_path, action_result):
"""Exposes the attachments files from the given xcresult file."""
testsref_id = action_result['testsRef']['id']['_value']
test_plan_summaries = _GetResultBundleObject(
xcresult_path, bundle_id=testsref_id)
test_plan_summary = test_plan_summaries['summaries']['_values'][0]
testable_summary = test_plan_summary['testableSummaries']['_values'][0]
# If the app under test crashes in unit test (XCTest) before loading the
# tests, the testable summary won't have tests summary.
if 'tests' not in testable_summary:
return
root_tests_summary = testable_summary['tests']['_values'][0]
failure_test_ref_ids = _GetFailureTestRefs(root_tests_summary)
for test_ref_id in failure_test_ref_ids:
test_summary_result = _GetResultBundleObject(xcresult_path, test_ref_id)
# if the test results in an `expectedFailures` entry, there might be an
# `activitySummaries` field present.
if 'activitySummaries' not in test_summary_result:
continue
activity_summaries = test_summary_result['activitySummaries']['_values']
for activity_summary in activity_summaries:
if 'attachments' in activity_summary:
test_identifier = test_summary_result['identifier']['_value']
for attachment in activity_summary['attachments']['_values']:
file_name = attachment['filename']['_value']
target_file_dir = os.path.join(output_path, 'Attachments',
test_identifier)
if not os.path.exists(target_file_dir):
os.makedirs(target_file_dir)
target_file_path = os.path.join(target_file_dir, file_name)
payload_ref_id = attachment['payloadRef']['id']['_value']
subprocess.check_call([
'xcrun', 'xcresulttool', 'export', '--path', xcresult_path,
'--output-path', target_file_path, '--type', 'file', '--id',
payload_ref_id
])
def _GetResultBundleObject(xcresult_path, bundle_id=None):
"""Gets the result bundle object in json format.
Args:
xcresult_path: string, path of xcresult bundle.
bundle_id: string, id of the result bundle object. If it is None, it is
rootID.
Returns:
A dict, result bundle object in json format.
"""
command = [
'xcrun', 'xcresulttool', 'get', '--format', 'json', '--path',
xcresult_path
]
if bundle_id:
command.extend(['--id', bundle_id])
return json.loads(subprocess.check_output(command).decode('utf-8'))
def _GetFailureTestRefs(test_summary):
"""Gets a list of test summaryRef id of all failure test.
Args:
test_summary: dict, a dict of test summary object.
Returns:
A list of failure test case's summaryRef id.
"""
failure_test_refs = []
if 'subtests' in test_summary:
for sub_test_summary in test_summary['subtests']['_values']:
failure_test_refs.extend(_GetFailureTestRefs(sub_test_summary))
else:
if (('testStatus' not in test_summary or
test_summary['testStatus']['_value'] != 'Success') and
'summaryRef' in test_summary):
summary_ref_id = test_summary['summaryRef']['id']['_value']
failure_test_refs.append(summary_ref_id)
return failure_test_refs