Skip to content

Commit

Permalink
Expose crash reports from xcresult bundle
Browse files Browse the repository at this point in the history
- Expose crash reports from xcresult bundle
- Fix a programming error for missing test_log_dir
  • Loading branch information
albertdai committed Aug 4, 2020
1 parent 81bf468 commit a9dd528
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 12 deletions.
103 changes: 94 additions & 9 deletions xctestrunner/test_runner/xcresult_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,38 @@
"""Helper class for parsing xcresult under Xcode 11 or later."""

import json
import os
import subprocess

from xctestrunner.shared import ios_errors


def ExposeDiagnosticsRef(xcresult_path, output_path):
"""Exposes the DiagnosticsRef files from the given xcresult file."""
output = subprocess.check_output([
'xcrun', 'xcresulttool', 'get', '--format', 'json', '--path',
xcresult_path
])
result_bundle_json = json.loads(output)
actions = result_bundle_json['actions']['_values']
def ExpoesXcresult(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' % output)
'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']
Expand All @@ -45,3 +55,78 @@ def ExposeDiagnosticsRef(xcresult_path, output_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)
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))


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
10 changes: 7 additions & 3 deletions xctestrunner/test_runner/xctest_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,13 @@ def RunTest(self, device_id, os_version=None):
result_bundle_path=result_bundle_path)
# The xcresult only contains raw data in Xcode 11 or later.
if xcode_info_util.GetXcodeVersionNumber() >= 1100:
xcresult_util.ExposeDiagnosticsRef(result_bundle_path, test_log_dir)
if not self._keep_xcresult_data:
shutil.rmtree(result_bundle_path)
expose_xcresult = os.path.join(self._output_dir, 'ExposeXcresult')
try:
xcresult_util.ExpoesXcresult(result_bundle_path, expose_xcresult)
if not self._keep_xcresult_data:
shutil.rmtree(result_bundle_path)
except subprocess.CalledProcessError as e:
logging.warning(e.output)
return exit_code
elif self._logic_test_bundle:
return logic_test_util.RunLogicTestOnSim(
Expand Down

0 comments on commit a9dd528

Please sign in to comment.