├── .idea
└── vcs.xml
├── README.md
├── Results_ZIP
└── iOS_20181226152906.zip
├── iOSCrashAnalysis
├── BaseIosCrash.py
├── BaseIosPhone.py
├── CrashExport.py
├── FileOperate.py
├── __pycache__
│ ├── BaseIosPhone.cpython-36.pyc
│ ├── CrashExport.cpython-36.pyc
│ ├── FileOperate.cpython-36.pyc
│ ├── getPakeage.cpython-36.pyc
│ ├── mysql_monkey.cpython-36.pyc
│ └── mysql_operation.cpython-36.pyc
├── crash_mail.py
├── getPakeage.py
├── mysql_monkey.py
└── symbolicatecrash
├── iOSMonkey.pptx
└── monkey.py
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 1228 新增内容
2 | a.自动获取真机设备名及duid
3 | b.iOS crashreport解析优化及测试结果DB存储
4 | c.测试失败邮件通知
5 | d.Web页面结果展示及支持crash log下载
6 |
7 | ## 1.环境
8 | Mac mini:10.12.6
9 | xcode:9.2
10 | python:python3.6
11 |
12 | ## 2.备注
13 | a.FastMonkey相关问题参照@zhangzhao_lenovo 大神的帖子:https://testerhome.com/topics/9524,此处不再赘述!
14 | b.相关扫盲贴:
15 | https://testerhome.com/topics/9810
16 | http://cdn2.jianshu.io/p/2cbdb50411ae
17 | c.ios-deploy,用于命令安装iOS app ,https://www.npmjs.com/package/ios-deploy
18 | d.FastMonkey设置为非sevrer模式
19 |
20 | ## 3.简单说明下脚本流程
21 | 自动化打包机打包->定时检测最新安装包->自动安装待测app->执行monkey->解析crashreport->DB存储->Web展示
22 |
23 | ## 4.脚本:
24 | https://github.com/Lemonzhulixin/iOS-monkey.git
25 |
26 | ```javascript
27 | # -*- coding: UTF8 -*-
28 | from iOSCrashAnalysis.CrashExport import CrashExport
29 | from iOSCrashAnalysis.getPakeage import getPakeage
30 | from iOSCrashAnalysis import mysql_monkey
31 | from iOSCrashAnalysis.FileOperate import *
32 | from iOSCrashAnalysis.BaseIosPhone import get_ios_devices,get_ios_PhoneInfo
33 | from iOSCrashAnalysis.FileOperate import FileFilt
34 |
35 |
36 | PATH = lambda p: os.path.abspath(
37 | os.path.join(os.path.dirname(__file__), p)
38 | )
39 |
40 | def monkey(devicename):
41 | cmd_monkey = "xcodebuild -project /Users/iOS_Team/.jenkins/workspace/iOS_Monkey_VivaVideo/XCTestWD/XCTestWD/XCTestWD.xcodeproj " \
42 | "-scheme XCTestWDUITests " \
43 | "-destination 'platform=iOS,name=" + devicename + "' " + \
44 | "XCTESTWD_PORT=8001 " + \
45 | "clean test"
46 |
47 | print(cmd_monkey)
48 | try:
49 | os.system(cmd_monkey)
50 | except Exception as msg:
51 | print('error message:', msg)
52 | raise
53 |
54 | if __name__ == '__main__':
55 | print('获取设备信息')
56 | # dev_list = []
57 | # devices = get_ios_devices()
58 | # for i in range(len(devices)):
59 | # duid = get_ios_devices()[i]
60 | # dev = get_ios_PhoneInfo(duid)
61 | # dev_list.append(dev)
62 | # print(dev_list)
63 |
64 | deviceName = 'iPhone2140'
65 | deviceID = 'e80251f0e66967f51add3ad0cdc389933715c3ed'
66 | release = '9.3.2'
67 |
68 | print('远程复制ipa文件到本地')
69 | start_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
70 | cmd_copy = 'sshpass -p ios scp -r iOS_Team@10.0.35.xx:/Users/iOS_Team/XiaoYing_AutoBuild/XiaoYing/XiaoYingApp/fastlane/output_ipa/ ~/Desktop'
71 | os.system(cmd_copy)
72 |
73 | print('安装ipa测试包到设备')
74 | path = "/Users/iOS_Team/Desktop/output_ipa/"
75 | file_format = ['.ipa']
76 | ipa_path = getPakeage().get_ipa(path, file_format)
77 | getPakeage().install(path, file_format, deviceID)
78 |
79 | print("启动monkey")
80 | monkey(deviceName)
81 |
82 | print('解析crash report')
83 | find_str = 'XiaoYing-' # 待测app crashreport文件关键字
84 | file_format1 = [".ips"] # 导出的crash文件后缀
85 | file_format2 = [".crash"] # 解析后的crash文件后缀
86 | CrashExport(deviceID, find_str, file_format1, file_format2)
87 | end_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
88 |
89 | print('测试结果数据解析并DB存储')
90 | loacl_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
91 | iOS_tag = 'iOS_' + loacl_time
92 |
93 | print('插入数据到device表')
94 | deviceData = {
95 | 'name': deviceName,
96 | 'serial_number': deviceID,
97 | 'version': release,
98 | 'status': 1,
99 | 'tag': 'iOS'
100 | }
101 |
102 | print('插入数据到apk信息表')
103 | # ipa_path = '/Users/zhulixin/Desktop/output_ipa/day_inke_release_xiaoying.ipa'
104 | ipainfo = getPakeage().getIpaInfo(ipa_path)
105 | apkData = {
106 | 'app_name': ipainfo[0],
107 | 'ver_name': ipainfo[2],
108 | 'ver_code': ipainfo[3],
109 | 'file_name': 'day_inke_release_xiaoying.ipa',
110 | 'file_path': ipa_path,
111 | 'build_time': start_time,
112 | 'tag': iOS_tag
113 | }
114 |
115 | print('插入数据到task表')
116 | taskData = {
117 | 'start_time': start_time,
118 | 'end_time': end_time,
119 | 'app_name': ipainfo[0],
120 | 'devices': 1,
121 | 'test_count': None,
122 | 'pass_count': None,
123 | 'fail_count': None,
124 | 'passing_rate': None,
125 | 'tag': iOS_tag,
126 | 'info': None
127 | }
128 |
129 | print('插入数据到results表')
130 | # f = FileFilt()
131 | # f.FindFile(find_str, file_format1, './CrashInfo/')
132 | # crash_count = len(f.fileList)
133 | # result = 1
134 | # if crash_count:
135 | # result = 0
136 |
137 | resultData = {
138 | 'result_id': start_time + '-monkey-' + ipainfo[0],
139 | 'start_time': start_time,
140 | 'end_time': end_time,
141 | 'device_name': deviceName,
142 | 'apk_id': None,
143 | 'result': None,
144 | 'status': None,
145 | 'CRASHs': None,
146 | 'ANRs': None,
147 | 'tag': iOS_tag,
148 | 'device_log':None,
149 | 'monkey_log': None,
150 | 'monkey_loop': None,
151 | 'cmd':None,
152 | 'seed': None
153 | }
154 |
155 | # print('deviceData:', deviceData)
156 | # mysql_monkey.insert_record_to_phones(deviceData)
157 |
158 | print('apkData:', apkData)
159 | mysql_monkey.insert_record_to_apks(apkData)
160 |
161 | print('taskData:', taskData)
162 | mysql_monkey.insert_record_to_tasks(taskData)
163 |
164 | print('resultData:', resultData)
165 | mysql_monkey.insert_record_to_results(resultData)
166 |
167 | print("压缩测试结果并传")
168 | f = FileFilt()
169 | results_file = f.zip_report(loacl_time, './CrashInfo/', './Results_ZIP/')
170 | url = 'http://10.0.32.xx:5100/api/v1/iOS-monkey'
171 | files = {'file': open(results_file, 'rb')}
172 | response = requests.post(url, files=files)
173 | json = response.json()
174 |
175 | print("删除本次的测试结果")
176 | f.DelFolder('./CrashInfo/')
177 | print("xxxxxxxxxxxxxxxxxxxxxxxxx Finish Test xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
178 | ```
179 |
180 | ## 5.Jenkins 部署定时任务
181 |
182 | ## 6.待优化
183 | a.多设备执行
184 | b.设备系统日志获取及web展示
185 | c.操作日志获取及web展示
186 |
187 | 最后感谢@zhangzhao_lenovo 开源的FastMonkey工具,赞!
188 |
--------------------------------------------------------------------------------
/Results_ZIP/iOS_20181226152906.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lemonzhulixin/iOS-monkey/f0a25e7cf10c60a8d53cbd8dc0818226cd17fcdc/Results_ZIP/iOS_20181226152906.zip
--------------------------------------------------------------------------------
/iOSCrashAnalysis/BaseIosCrash.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: UTF-8 -*-
3 | import re
4 | import sys, getopt
5 | import subprocess
6 | import os
7 |
8 | PATH = lambda p: os.path.abspath(
9 | os.path.join(os.path.dirname(__file__), p)
10 | )
11 |
12 | def getUUID(text):
13 | uuid = re.findall('<(.*)>', text)[0].upper()
14 | UUID = uuid[0:8] + '-' + uuid[8:12] + '-' + uuid[12:16] + '-' + uuid[16:20] + "-" + uuid[20:32]
15 | return UUID
16 |
17 |
18 | def analyzeCrashLog(inputfile, outputfile):
19 | crashlog = subprocess.getoutput('grep --after-context=1000 "Binary Images:" ' + inputfile + ' | grep "XiaoYing arm"')
20 | print(crashlog)
21 | uuid = getUUID(crashlog)
22 | print(uuid)
23 | # ttt = commands.getstatus('sshpass -p ios ssh iOS_Team@10.0.35.21')
24 |
25 | path = subprocess.getoutput('mdfind "com_apple_xcode_dsym_uuids == " ' + uuid)
26 | # path = path.replace(' ','\\ ')
27 | path = path + '/dSYMs/XiaoYing.app.dSYM'
28 |
29 | path = "'" + path + "'"
30 | print(path)
31 | analysisPath = '/Users/iOS_Team/.jenkins/workspace/iOS_Monkey_VivaVideo/iOSCrashAnalysis/'
32 | outname = os.path.splitext(inputfile)[0]
33 |
34 | ttt = subprocess.getoutput(
35 | analysisPath + 'symbolicatecrash ' + inputfile + ' -d ' + path + ' -o ' + outname + '.crash')
36 | print(ttt)
37 |
38 |
39 | def main(argv):
40 | inputfile = ''
41 | outputfile = ''
42 | try:
43 | opts, args = getopt.getopt(argv, "hi:o:", ["ifile=", "ofile="])
44 | except getopt.GetoptError:
45 | sys.exit(2)
46 |
47 | for opt, arg in opts:
48 | if opt == '-h':
49 | sys.exit()
50 | elif opt in ("-i", "--ifile"):
51 | inputfile = arg
52 | elif opt in ("-o", "--ofile"):
53 | outputfile = arg
54 |
55 | print('输入的文件为:', inputfile)
56 | print('输出的文件为:', outputfile)
57 | analyzeCrashLog(inputfile, outputfile)
58 |
59 |
60 | if __name__ == "__main__":
61 | main(sys.argv[1:])
--------------------------------------------------------------------------------
/iOSCrashAnalysis/BaseIosPhone.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 |
3 | import os
4 |
5 | '''
6 | 获取ios下的硬件信息
7 | '''
8 |
9 |
10 | def get_ios_devices():
11 | devices = []
12 | result = subprocess.Popen("ideviceinfo -k UniqueDeviceID", shell=True, stdout=subprocess.PIPE,
13 | stderr=subprocess.PIPE).stdout.readlines()
14 |
15 | for item in result:
16 | t = item.decode().split("\n")
17 | if len(t) >= 2:
18 | devices.append(t[0])
19 | return devices
20 |
21 |
22 | def get_ios_version(duid):
23 | command = "ideviceinfo -u %s -k ProductVersion" % duid
24 | result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
25 | stderr=subprocess.PIPE).stdout.readlines()
26 | for item in result:
27 | t = item.decode().split("\n")
28 | if len(t) >= 2:
29 | return t[0]
30 |
31 |
32 | def get_ios_product_name(duid):
33 | command = "ideviceinfo -u %s -k DeviceName" % duid
34 | result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
35 | stderr=subprocess.PIPE).stdout.readlines()
36 | for item in result:
37 | t = item.decode().split("\n")
38 | if len(t) >= 2:
39 | return t[0]
40 |
41 | def get_ios_product_type(duid):
42 | command = "ideviceinfo -u %s -k ProductType" % duid
43 | result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
44 | stderr=subprocess.PIPE).stdout.readlines()
45 | for item in result:
46 | t = item.decode().split("\n")
47 | if len(t) >= 2:
48 | return t[0]
49 |
50 | def get_ios_product_os(duid):
51 | command = "ideviceinfo -u %s -k ProductName" % duid
52 | result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
53 | stderr=subprocess.PIPE).stdout.readlines()
54 | for item in result:
55 | t = item.decode().split("\n")
56 | if len(t) >= 2:
57 | return t[0]
58 |
59 | def get_ios_PhoneInfo(duid):
60 | name = get_ios_product_name(duid)
61 | release = get_ios_version(duid)
62 | type = get_ios_product_type(duid)
63 | result = {"release": release, "device": name, "duid": duid, "type": type}
64 | return result
65 |
66 | #编译facebook的wda到真机
67 | def build_wda_ios(duid):
68 | os.popen(
69 | "xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination id=" + duid + " test")
70 |
71 |
72 | if __name__ == '__main__':
73 | dev_list = []
74 | devices = get_ios_devices()
75 | for i in range(len(devices)):
76 | duid = get_ios_devices()[i]
77 | dev = get_ios_PhoneInfo(duid)
78 | dev_list.append(dev)
79 | print(dev_list)
80 |
81 |
82 |
--------------------------------------------------------------------------------
/iOSCrashAnalysis/CrashExport.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | import os
3 | from iOSCrashAnalysis import FileOperate
4 |
5 | PATH = lambda p: os.path.abspath(
6 | os.path.join(os.path.dirname(__file__), p)
7 | )
8 |
9 |
10 | def CrashExport(deviceID, find_str, format1, format2):
11 | print("============开始导出crashreport==========")
12 | resultPath = ''.join(['./CrashInfo/'])
13 | beforePath = os.path.join(resultPath + 'temp')
14 | if not os.path.exists(beforePath):
15 | os.makedirs(beforePath)
16 |
17 | afterPath = os.path.join(resultPath)
18 | if not os.path.exists(afterPath):
19 | os.makedirs(afterPath)
20 | print("导出设备中的所有crash文件")
21 | exportReport = 'idevicecrashreport -u ' + deviceID + ' ' + beforePath + '/'
22 | print(exportReport)
23 | os.system(exportReport) # 导出设备中的crash
24 |
25 | print("============开始过滤并解析待测app相关crashreport==========")
26 | f = FileOperate.FileFilt()
27 | f.FindFile(find_str, format1, beforePath)
28 |
29 | # if len(f.fileList) > 0:
30 | # mailpath = '/Users/zhulixin/Desktop/iOS-monkey/iOSCrashAnalysis/crash_mail.py'
31 | # cmd_mail = 'python ' + mailpath + ' "fail" "VivaVideo iOS UI autotest failed" "出现了新的crash,查看地址: http://10.0.32.6:8082/UIAuto/ios"'
32 | # print('发送邮件')
33 | # os.system(cmd_mail)
34 |
35 | for file in f.fileList:
36 | inputFile = os.path.abspath(file)
37 | analysisPath = ''.join(["./iOSCrashAnalysis/"])
38 | cmd_analysis = 'python3 ' + analysisPath + 'BaseIosCrash.py' + ' -i ' + inputFile
39 | os.system(cmd_analysis)
40 |
41 | # 移动解析完成的crashreport和原始ips文件到新的文件夹
42 | f.MoveFile(find_str, format1, beforePath, afterPath)
43 | f.MoveFile(find_str, format2, beforePath, afterPath)
44 | print("============crashreport解析完成==========")
45 |
46 | # 删除所有解析之前的crash文件,若不想删除,注掉即可
47 | print("============删除所有解析之前的crash文件==========")
48 | f.DelFolder(beforePath)
49 | os.rmdir(beforePath)
50 |
51 |
52 | if __name__ == '__main__':
53 | find_str = 'XiaoYing-' # 待测app crashreport文件关键字
54 | file_format1 = [".ips"] # 导出的crash文件后缀
55 | file_format2 = [".crash"] # 解析后的crash文件后缀
56 | deviceID = 'e80251f0e66967f51add3ad0cdc389933715c3ed'
57 | deviceName = 'iPhone2140'
58 |
59 | CrashExport(deviceID,find_str,file_format1,file_format2)
--------------------------------------------------------------------------------
/iOSCrashAnalysis/FileOperate.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | import re
4 | import zipfile
5 | import time
6 | import requests
7 |
8 | class FileFilt:
9 | fileList = []
10 | counter = 0
11 | def __init__(self):
12 | pass
13 |
14 | def FindFile(self, find_str, file_format, path, filtrate=1):
15 | for s in os.listdir(path):#返回指定目录下的所有文件和目录名
16 | newDir = os.path.join(path, s) #将多个路径组合后返回,第一个绝对路径之前的参数将被忽略;os.path.join('路径','文件名.txt')
17 | if os.path.isfile(newDir): #如果path是一个存在的文件,返回True。否则返回False。
18 | if filtrate:
19 | if newDir and (os.path.splitext(newDir)[1] in file_format) \
20 | and (find_str in os.path.splitext(newDir)[0]): #os.path.splitext():分离文件名与扩展名
21 | self.fileList.append(newDir)
22 | self.counter += 1
23 | else:
24 | self.fileList.append(newDir)
25 | self.counter += 1
26 |
27 | def MoveFile(self, find_str, file_format, path, newpath, filtrate=1):
28 | for s in os.listdir(path):
29 | newDir = os.path.join(path, s)
30 | if os.path.isfile(newDir):
31 | if filtrate:
32 | if newDir and (os.path.splitext(newDir)[1] in file_format) \
33 | and (find_str in os.path.splitext(newDir)[0]):
34 | self.fileList.append(newDir)
35 | self.counter += 1
36 | shutil.move(newDir, newpath)
37 | else:
38 | self.fileList.append(newDir)
39 | self.counter += 1
40 |
41 | def DelFolder(self, delDir):
42 | delList = os.listdir(delDir)
43 | for f in delList:
44 | filePath = os.path.join(delDir, f)
45 | if os.path.isfile(filePath):
46 | os.remove(filePath)
47 | print(filePath + " was removed!")
48 | elif os.path.isdir(filePath):
49 | shutil.rmtree(filePath, True)
50 | print("Directory: " + filePath + " was removed!")
51 |
52 | def FilePath(self, file_path):
53 | for cur_dir, included_file in os.walk(file_path):
54 | if included_file:
55 | for file in included_file:
56 | print(cur_dir + "\\" + file)
57 |
58 | def zip_report(self,loacl_time, path, newpath):
59 | '''压缩TestReport文件夹
60 | path = "./TestReport" # 要压缩的文件夹路径
61 | newpath = './TestReport_ZIP/' # 压缩后输出文件路径
62 | '''
63 | if not os.path.exists(newpath):
64 | os.mkdir(newpath)
65 | zipName = newpath + 'iOS_' + loacl_time + '.zip' # 压缩后文件夹的名字
66 | z = zipfile.ZipFile(zipName, 'w', zipfile.ZIP_DEFLATED) # 参数一:文件夹名
67 | for dirpath, dirnames, filenames in os.walk(path):
68 | fpath = dirpath.replace(path, '')
69 | fpath = fpath and fpath + os.sep or ''
70 | for filename in filenames:
71 | z.write(os.path.join(dirpath, filename), fpath + filename)
72 | # z.write(os.path.join(dirpath, filename))
73 | z.close()
74 | print('Generate zip_report file %s completed........ ' % zipName)
75 | return zipName
76 |
77 |
78 | if __name__ == "__main__":
79 | pass
80 |
81 | # afterPath = '/Users/zhulixin/Desktop/UItest/Results/crashInfo/Before'
82 | # # f = FileFilt()
83 | # # f.FilePath(afterPath)
84 | #
85 | # os.rmdir(afterPath)
86 |
87 | # find_str = 'XiaoYing-'
88 | # file_format = '.ips'
89 | # b = FileFilt()
90 | # b.FindFile(find_str,file_format, path="/Users/zhulixin/new")
91 | # for file in b.fileList:
92 | # filepath = os.path.abspath(file) #绝对路径
93 | # print(filepath)
94 |
95 |
96 | url = 'http://10.0.34.xxx:5100/api/v1/report'
97 | files = {'file': open('/Users/iOS_Team/.jenkins/workspace/iOS_UI_VivaVideo/UItest/Results_ZIP/iOS_20181127171700.zip', 'rb')}
98 | response = requests.post(url, files=files)
99 | json = response.json()
100 |
101 |
--------------------------------------------------------------------------------
/iOSCrashAnalysis/__pycache__/BaseIosPhone.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lemonzhulixin/iOS-monkey/f0a25e7cf10c60a8d53cbd8dc0818226cd17fcdc/iOSCrashAnalysis/__pycache__/BaseIosPhone.cpython-36.pyc
--------------------------------------------------------------------------------
/iOSCrashAnalysis/__pycache__/CrashExport.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lemonzhulixin/iOS-monkey/f0a25e7cf10c60a8d53cbd8dc0818226cd17fcdc/iOSCrashAnalysis/__pycache__/CrashExport.cpython-36.pyc
--------------------------------------------------------------------------------
/iOSCrashAnalysis/__pycache__/FileOperate.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lemonzhulixin/iOS-monkey/f0a25e7cf10c60a8d53cbd8dc0818226cd17fcdc/iOSCrashAnalysis/__pycache__/FileOperate.cpython-36.pyc
--------------------------------------------------------------------------------
/iOSCrashAnalysis/__pycache__/getPakeage.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lemonzhulixin/iOS-monkey/f0a25e7cf10c60a8d53cbd8dc0818226cd17fcdc/iOSCrashAnalysis/__pycache__/getPakeage.cpython-36.pyc
--------------------------------------------------------------------------------
/iOSCrashAnalysis/__pycache__/mysql_monkey.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lemonzhulixin/iOS-monkey/f0a25e7cf10c60a8d53cbd8dc0818226cd17fcdc/iOSCrashAnalysis/__pycache__/mysql_monkey.cpython-36.pyc
--------------------------------------------------------------------------------
/iOSCrashAnalysis/__pycache__/mysql_operation.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lemonzhulixin/iOS-monkey/f0a25e7cf10c60a8d53cbd8dc0818226cd17fcdc/iOSCrashAnalysis/__pycache__/mysql_operation.cpython-36.pyc
--------------------------------------------------------------------------------
/iOSCrashAnalysis/crash_mail.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | #-*-coding:utf-8-*-
3 | import smtplib
4 | import os
5 | import sys
6 |
7 | from email.mime.multipart import MIMEMultipart
8 | from email.mime.application import MIMEApplication
9 | from email.mime.text import MIMEText
10 |
11 | #邮件配置
12 | MAIL_FROM_ADDRESS = "xxxxx@quvideo.com"
13 | MAIL_PASSWORD = "Xxxxxx"
14 | SMTP_SERVER = "smtp.exmail.qq.com"
15 |
16 | #接收邮件的邮箱
17 | MAIL_TO_ADDRESS_APPSTORE = ['xxxx.xu@quvideo.com']
18 |
19 | #发送邮件
20 | #mail_info包含:mail_subject, mail_message
21 | def send_Email(mail_info, receiver):
22 |
23 | print ('*******开始发送邮件****')
24 |
25 | #邮件接受者
26 | mail_receiver = receiver
27 |
28 | #根据不同邮箱配置 host,user,和pwd
29 | mail_host = SMTP_SERVER
30 | mail_port = 465
31 | mail_user = MAIL_FROM_ADDRESS
32 | mail_pwd = MAIL_PASSWORD
33 |
34 | mail_to = ','.join(mail_receiver)
35 |
36 | msg = MIMEMultipart()
37 |
38 | #文本内容
39 | message = mail_info['mail_message']
40 | subject = mail_info['mail_subject']
41 | body = MIMEText(message, _subtype='html', _charset='utf-8')
42 | msg.attach(body)
43 |
44 | # 文件类型的附件
45 | appendix = mail_info['mail_file']
46 | if len(appendix) > 0:
47 |
48 | filename = os.path.basename(appendix)
49 | filepart = MIMEApplication(open(appendix, 'rb').read())
50 | filepart.add_header('Content-Disposition', 'attachment', filename=filename)
51 | msg.attach(filepart)
52 |
53 |
54 | msg['To'] = mail_to
55 | msg['from'] = mail_user
56 | msg['subject'] = subject
57 |
58 | try:
59 | s = smtplib.SMTP()
60 | # 设置为调试模式,就是在会话过程中会有输出信息
61 | s.set_debuglevel(1)
62 | s.connect(mail_host)
63 | s.login(mail_user, mail_pwd)
64 | s.sendmail(mail_user, mail_receiver, msg.as_string())
65 | s.close()
66 |
67 | print ('*******邮件发送成功*******')
68 | except Exception as e:
69 | print(e)
70 |
71 | def send_Email_to_developer(mail_info):
72 | send_Email(mail_info, MAIL_TO_ADDRESS_APPSTORE)
73 |
74 | def main():
75 | print("auto_email")
76 |
77 | if len(sys.argv) > 2:
78 |
79 | #邮件类型 测试成功 测试失败
80 | fuction_type = sys.argv[1]
81 | #邮寄的title
82 | subject = sys.argv[2]
83 | #邮寄的内容
84 | message = sys.argv[3]
85 | #邮件附件
86 | if len(sys.argv) > 4:
87 | appendix = sys.argv[4]
88 | else:
89 | appendix = ''
90 |
91 | if fuction_type == "fail":
92 |
93 | mail_info = {
94 | 'mail_subject' : subject,
95 | 'mail_message' : message,
96 | 'mail_file' : appendix,
97 | }
98 |
99 | send_Email_to_developer(mail_info)
100 |
101 | print("-------------测试出现Crash-------------")
102 |
103 | elif fuction_type == "success":
104 |
105 | mail_info = {
106 | 'mail_subject' : subject,
107 | 'mail_message' : message
108 | }
109 | send_Email_to_developer(mail_info)
110 |
111 | print("-------------success-------------")
112 | else:
113 | print("Fail operation", fuction_type)
114 | else:
115 | print("Fail operation")
116 |
117 | # 执行
118 | main()
119 |
120 |
--------------------------------------------------------------------------------
/iOSCrashAnalysis/getPakeage.py:
--------------------------------------------------------------------------------
1 | import os
2 | import zipfile, plistlib, re
3 |
4 | class getPakeage:
5 | def __init__(self):
6 | pass
7 |
8 | def dirlist(self, path, allfile):
9 | filelist = os.listdir(path)
10 | for filename in filelist:
11 | filepath = os.path.join(path, filename)
12 | if os.path.isdir(filepath):
13 | getPakeage().dirlist(filepath, allfile)
14 | else:
15 | allfile.append(filepath)
16 | return allfile
17 |
18 | def get_ipa(self, path,file_format):
19 | files = getPakeage().dirlist(path, [])
20 | ipa_list = []
21 | for ipa in files:
22 | if (os.path.splitext(ipa)[1] in file_format):
23 | t = os.path.getctime(ipa)
24 | ipa_list.append([ipa, t])
25 | order = sorted(ipa_list, key=lambda e: e[1], reverse=True)
26 | ipa_path = order[0][0]
27 | return ipa_path
28 |
29 | def install(self,path,file_format,duid):
30 | ipa_path = getPakeage().get_ipa(path,file_format)
31 | cmd = 'ios-deploy –r -b ' + '"' + ipa_path + '"' + ' -i ' + duid
32 | print('安装待测试的app', cmd)
33 | try:
34 | os.system(cmd)
35 | except Exception as msg:
36 | print('error message:', msg)
37 | raise
38 |
39 | def find_plist_path(self,zip_file):
40 | name_list = zip_file.namelist()
41 | pattern = re.compile(r'Payload/[^/]*.app/Info.plist')
42 | for path in name_list:
43 | m = pattern.match(path)
44 | if m is not None:
45 | return m.group()
46 |
47 | def getIpaInfo(self, ipa_path):
48 | ipa_file = zipfile.ZipFile(ipa_path)
49 | plist_path = getPakeage().find_plist_path(ipa_file)
50 | plist_data = ipa_file.read(plist_path)
51 | plist_root = plistlib.loads(plist_data)
52 |
53 | name = plist_root['CFBundleDisplayName']
54 | bundleID = plist_root['CFBundleIdentifier']
55 | version = plist_root['CFBundleShortVersionString']
56 | appKey = plist_root['XiaoYingAppKey']
57 | miniOSVersion = plist_root['MinimumOSVersion']
58 | print("=====getIpaInfo=========")
59 | print('appName: %s' % name)
60 | print('bundleId: %s' % bundleID)
61 | print('appVersion: %s' % version)
62 | print('appKey: %s' % appKey)
63 | print('miniOSVersion: %s' % miniOSVersion)
64 | return name, bundleID, version, appKey, miniOSVersion
65 |
66 | if __name__ == '__main__':
67 |
68 | cmd_copy = 'sshpass -p ios scp -r iOS_Team@10.0.35.xx:/Users/iOS_Team/XiaoYing_AutoBuild/XiaoYing/XiaoYingApp/fastlane/output_ipa/ ~/Desktop'
69 |
70 | print('远程复制ipa文件到本地')
71 | os.system(cmd_copy)
72 |
73 | path = "/Users/zhulixin/Desktop/output_ipa/"
74 | file_format = ['.ipa']
75 | duid = 'abab40339eaf2274aaf1ef068e11d6f85d84aae1'
76 | devicename = 'iPhone2146'
77 | ipa_path = getPakeage().get_ipa(path, file_format)
78 | print(ipa_path)
79 |
80 | getPakeage().install(path, file_format, duid)
81 |
82 | getPakeage().getIpaInfo(ipa_path)
83 |
84 |
--------------------------------------------------------------------------------
/iOSCrashAnalysis/mysql_monkey.py:
--------------------------------------------------------------------------------
1 | import mysql.connector
2 |
3 | def upload_sql(sql, value):
4 | db_host = '10.0.32.xxx'
5 | port = 8060
6 | my_connect = mysql.connector.connect(
7 | host=db_host,
8 | port=port,
9 | db='qxxxx',
10 | user='rxxx',
11 | password='rxxx',
12 | charset='utf8'
13 | )
14 | my_cursor = my_connect.cursor()
15 | try:
16 | my_cursor.execute(sql, value)
17 | my_connect.commit()
18 | except mysql.connector.Error as err:
19 | print("Failed inserting by errorcode {}".format(err))
20 | my_cursor.close()
21 | my_connect.close()
22 |
23 |
24 | def insert_record_to_phones(value):
25 | """
26 | 插入数据到phones表
27 | :param value:
28 | value = {
29 | 'name': 'NCE_TL10',
30 | 'serial_number': 'BCD9XA1732301914',
31 | 'version': '6.0',
32 | 'status': 1,
33 | 'tag': 'Android'
34 | }
35 | :return:
36 | """
37 | my_sql = "INSERT INTO monkey_phones (phone_id, name, serial_number, version, status, tag) " \
38 | "VALUES (NULL, %(name)s, %(serial_number)s, %(version)s, %(status)s, %(tag)s)"
39 | upload_sql(my_sql, value)
40 | return
41 |
42 | def insert_record_to_apks(value):
43 | """
44 | 插入数据到apks表
45 | :param value:
46 | value = {
47 | 'app_name': 'com.quvideo.xiaoying',
48 | 'ver_name': '7.5.5',
49 | 'ver_code': '6705050',
50 | 'file_name': 'XiaoYing_V7.5.5_0-xiaoyingtest-OthersAbroadDebug-2018-11-20_08_37_07.apk',
51 | 'file_path': '/Users/iOS_Team/Desktop/QuTestMonkey/app/static/apks/xiaoying/XiaoYing_V7.5.5_0-xiaoyingtest-OthersAbroadDebug-2018-11-20_08_37_07.apk',
52 | 'build_time': datetime.now().strftime('%Y%m%d%H%M%S'),
53 | 'tag': 'Android'
54 | }
55 | :return:
56 | """
57 | my_sql = "INSERT INTO monkey_apks (apk_id, app_name, ver_name, ver_code, file_name, file_path, build_time, tag) " \
58 | "VALUES (NULL, %(app_name)s, %(ver_name)s, %(ver_code)s, %(file_name)s, %(file_path)s," \
59 | " %(build_time)s, %(tag)s)"
60 | upload_sql(my_sql, value)
61 | return
62 |
63 | def insert_record_to_tasks(value):
64 | """
65 | 插入数据到tasks表
66 | :param value:
67 | value = {
68 | 'start_time': datetime.now().strftime('%Y%m%d%H%M%S'),
69 | 'end_time': datetime.now().strftime('%Y%m%d%H%M%S'),
70 | 'app_name': '小影',
71 | 'devices': None
72 | 'test_count': 1,
73 | 'pass_count': 1,
74 | 'fail_count': 0,
75 | 'passing_rate': 1,
76 | 'tag': datetime.now().strftime('%Y%m%d%H%M%S') + '-monkey',
77 | 'info': None
78 | }
79 | :return:
80 | """
81 | my_sql = "INSERT INTO monkey_tasks (task_id, start_time, end_time, app_name, devices, test_count, pass_count," \
82 | " fail_count, passing_rate, info, tag) " \
83 | "VALUES (NULL, %(start_time)s, %(end_time)s, %(app_name)s, %(devices)s, %(test_count)s," \
84 | " %(pass_count)s, %(fail_count)s, %(passing_rate)s, %(info)s, %(tag)s)"
85 | upload_sql(my_sql, value)
86 | return
87 |
88 | def insert_record_to_results(value):
89 | """
90 | 插入数据到results表
91 | :param value:
92 | result_data = {
93 | 'result_id': '20181208163031-monkey-小影-0',
94 | 'start_time': '20181108163032',
95 | 'end_time': None,
96 | 'device_name': 'BIBEYDSCO7OBSWVW',
97 | #1 PASS
98 | 'result': 1,
99 | 'status': 0,
100 | 'CRASHs': 0,
101 | 'ANRs': 0,
102 | 'tag': '20181108163031-monkey',
103 | 'device_log': 'http://10.0.32.6:5100/static/logs/devicelogs/com.quvideo.xiaoying/device-BIBEYDSCO7OBSWVW-20181108163031-小影-0.log',
104 | 'monkey_log': 'http://10.0.32.6:5100/static/logs/monkeylogs/com.quvideo.xiaoying/monkey-BIBEYDSCO7OBSWVW-20181108163031-小影-0.log',
105 | 'monkey_loop': 10,
106 | 'cmd': '--throttle 1000 --pct-touch 70 --pct-motion 5 --pct-trackball 5 --pct-appswitch 20 --kill-process-after-error --monitor-native-crashes --ignore-crashes --ignore-timeouts',
107 | 'seed': 3905
108 | }
109 | :return:
110 | """
111 | my_sql = "INSERT INTO monkey_results (result_id, start_time, end_time, device_name, apk_id, result, status, CRASHs," \
112 | " ANRs, tag, device_log, monkey_log, monkey_loop, cmd, seed) " \
113 | "VALUES (%(result_id)s, %(start_time)s, %(end_time)s, %(device_name)s, %(apk_id)s, %(result)s," \
114 | " %(status)s, %(CRASHs)s, %(ANRs)s, %(tag)s, %(device_log)s, %(monkey_log)s, %(monkey_loop)s," \
115 | " %(cmd)s, %(seed)s)"
116 | upload_sql(my_sql, value)
117 | return
--------------------------------------------------------------------------------
/iOSCrashAnalysis/symbolicatecrash:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl -w
2 | #
3 | # This script parses a crashdump file and attempts to resolve addresses into function names.
4 | #
5 | # It finds symbol-rich binaries by:
6 | # a) searching in Spotlight to find .dSYM files by UUID, then finding the executable from there.
7 | # That finds the symbols for binaries that a developer has built with "DWARF with dSYM File".
8 | # b) searching in various SDK directories.
9 | #
10 | # Copyright (c) 2008-2015 Apple Inc. All Rights Reserved.
11 | #
12 | #
13 |
14 | use strict;
15 | use warnings;
16 | use Getopt::Long;
17 | use Cwd qw(realpath);
18 | use List::MoreUtils qw(uniq);
19 | use File::Basename qw(basename);
20 | use File::Glob ':glob';
21 | use Env qw(DEVELOPER_DIR);
22 | use Config;
23 | no warnings "portable";
24 |
25 | require bigint;
26 | if($Config{ivsize} < 8) {
27 | bigint->import(qw(hex));
28 | }
29 |
30 | #############################
31 |
32 | # Forward definitons
33 | sub usage();
34 |
35 | #############################
36 |
37 | # read and parse command line
38 | my $opt_help = 0;
39 | my $opt_verbose = 0;
40 | my $opt_output = "-";
41 | my @opt_dsyms = ();
42 | my $opt_spotlight = 1;
43 |
44 | Getopt::Long::Configure ("bundling");
45 | GetOptions ("help|h" => \$opt_help,
46 | "verbose|v" => \$opt_verbose,
47 | "output|o=s" => \$opt_output,
48 | "dsym|d=s" => \@opt_dsyms,
49 | "spotlight!" => \$opt_spotlight)
50 | or die("Error in command line arguments\n");
51 |
52 | usage() if $opt_help;
53 |
54 | #############################
55 |
56 | # have this thing to de-HTMLize Leopard-era plists
57 | my %entity2char = (
58 | # Some normal chars that have special meaning in SGML context
59 | amp => '&', # ampersand
60 | 'gt' => '>', # greater than
61 | 'lt' => '<', # less than
62 | quot => '"', # double quote
63 | apos => "'", # single quote
64 | );
65 |
66 | #############################
67 |
68 | if(!defined($DEVELOPER_DIR)) {
69 | die "Error: \"DEVELOPER_DIR\" is not defined";
70 | }
71 |
72 |
73 | # We will find these tools once we can guess the right SDK
74 | my $otool = undef;
75 | my $atos = undef;
76 | my $symbolstool = undef;
77 | my $size = undef;
78 |
79 |
80 | #############################
81 | # run the script
82 |
83 | symbolicate_log(@ARGV);
84 |
85 | exit 0;
86 |
87 | #############################
88 |
89 | # begin subroutines
90 |
91 | sub HELP_MESSAGE() {
92 | usage();
93 | }
94 |
95 | sub usage() {
96 | print STDERR < [SYMBOL_PATH ...]
99 |
100 | The crash log to be symbolicated. If "-", then the log will be read from stdin
101 | Additional search paths in which to search for symbol rich binaries
102 | -o | --output The symbolicated log will be written to OUTPUT_FILE. Defaults to "-" (i.e. stdout) if not specified
103 | -d | --dsym Adds additional dSYM that will be consulted if and when a binary's UUID matches (may be specified more than once)
104 | -h | --help Display this help message
105 | -v | --verbose Enables additional output
106 | EOF
107 | exit 1;
108 | }
109 |
110 | ##############
111 |
112 | sub getToolPath {
113 | my ($toolName, $sdkGuess) = @_;
114 |
115 | if (!defined($sdkGuess)) {
116 | $sdkGuess = "macosx";
117 | }
118 |
119 | my $toolPath = `'/usr/bin/xcrun' -sdk $sdkGuess -find $toolName`;
120 | if (!defined($toolPath) || $? != 0) {
121 | if ($sdkGuess eq "macosx") {
122 | die "Error: can't find tool named '$toolName' in the $sdkGuess SDK or any fallback SDKs";
123 | } elsif ($sdkGuess eq "iphoneos") {
124 | print STDERR "## Warning: can't find tool named '$toolName' in iOS SDK, falling back to searching the Mac OS X SDK\n";
125 | return getToolPath($toolName, "macosx");
126 | } else {
127 | print STDERR "## Warning: can't find tool named '$toolName' in the $sdkGuess SDK, falling back to searching the iOS SDK\n";
128 | return getToolPath($toolName, "iphoneos");
129 | }
130 | }
131 |
132 | chomp $toolPath;
133 | print STDERR "$toolName path is '$toolPath'\n" if $opt_verbose;
134 |
135 | return $toolPath;
136 | }
137 |
138 | ##############
139 |
140 | sub getSymbolDirPaths {
141 | my ($hwModel, $osVersion, $osBuild) = @_;
142 |
143 | print STDERR "(\$hwModel, \$osVersion, \$osBuild) = ($hwModel, $osVersion, $osBuild)\n" if $opt_verbose;
144 |
145 | my $versionPattern = "{$hwModel $osVersion ($osBuild),$osVersion ($osBuild),$osVersion,$osBuild}";
146 | #my $versionPattern = '*';
147 | print STDERR "\$versionPattern = $versionPattern\n" if $opt_verbose;
148 |
149 | my @result = grep { -e && -d } bsd_glob('{/System,,~}/Library/Developer/Xcode/*DeviceSupport/'.$versionPattern.'/Symbols*', GLOB_BRACE | GLOB_TILDE);
150 |
151 | foreach my $foundPath (`mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode' || kMDItemCFBundleIdentifier == 'com.apple.Xcode'"`) {
152 | chomp $foundPath;
153 | my @pathResults = grep { -e && -d && !/Simulator/ } bsd_glob($foundPath.'/Contents/Developer/Platforms/*.platform/DeviceSupport/'.$versionPattern.'/Symbols*/');
154 | push(@result, @pathResults);
155 | }
156 |
157 | print STDERR "Symbol directory paths: @result\n" if $opt_verbose;
158 | return @result;
159 | }
160 |
161 | sub getSymbolPathAndArchFor_searchpaths {
162 | my ($bin,$path,$build,$uuid,@extra_search_paths) = @_;
163 | my @results;
164 |
165 | if (! (defined $bin && length($bin)) && !(defined $path && length($path)) ) {
166 | return undef;
167 | }
168 |
169 | for my $item (@extra_search_paths) {
170 | my $glob = "$item" . "{";
171 | if (defined $bin && length($bin)) {
172 | $glob .= "$bin,*/$bin,";
173 | }
174 | if (defined $path && length($path)) {
175 | $glob .= "$path,";
176 | }
177 | $glob .= "}*";
178 | #print STDERR "\nSearching pattern: [$glob]...\n" if $opt_verbose;
179 | push(@results, grep { -e && (! -d) } bsd_glob ($glob, GLOB_BRACE));
180 | }
181 |
182 | for my $out_path (@results) {
183 | my $arch = archForUUID($out_path, $uuid);
184 | if (defined($arch) && length($arch)) {
185 | return ($out_path, $arch);
186 | }
187 | }
188 |
189 | return undef;
190 | }
191 |
192 | sub getSymbolPathFor_uuid{
193 | my ($uuid, $uuidsPath) = @_;
194 | $uuid or return undef;
195 | $uuid =~ /(.{4})(.{4})(.{4})(.{4})(.{4})(.{4})(.{8})/;
196 | return Cwd::realpath("$uuidsPath/$1/$2/$3/$4/$5/$6/$7");
197 | }
198 |
199 | # Convert a uuid from the canonical format, like "C42A118D-722D-2625-F235-7463535854FD",
200 | # to crash log format like "c42a118d722d2625f2357463535854fd".
201 | sub getCrashLogUUIDForCanonicalUUID{
202 | my ($uuid) = @_;
203 |
204 | $uuid = lc($uuid);
205 | $uuid =~ s/\-//g;
206 |
207 | return $uuid;
208 | }
209 |
210 | # Convert a uuid from the crash log, like "c42a118d722d2625f2357463535854fd",
211 | # to canonical format like "C42A118D-722D-2625-F235-7463535854FD".
212 | sub getCanonicalUUIDForCrashLogUUID{
213 | my ($uuid) = @_;
214 |
215 | my $cononical_uuid = uc($uuid); # uuid's in Spotlight database and from other tools are all uppercase
216 | $cononical_uuid =~ /(.{8})(.{4})(.{4})(.{4})(.{12})/;
217 | $cononical_uuid = "$1-$2-$3-$4-$5";
218 |
219 | return $cononical_uuid;
220 | }
221 |
222 |
223 | # Look up a dsym file by UUID in Spotlight, then find the executable from the dsym.
224 | sub getSymbolPathAndArchFor_dsymUuid{
225 | my ($uuid) = @_;
226 | $uuid or return undef;
227 |
228 | # Convert a uuid from the crash log, like "c42a118d722d2625f2357463535854fd",
229 | # to canonical format like "C42A118D-722D-2625-F235-7463535854FD".
230 | my $canonical_uuid = getCanonicalUUIDForCrashLogUUID($uuid);
231 |
232 | # Do the search in Spotlight.
233 | my $cmd = "mdfind \"com_apple_xcode_dsym_uuids == $canonical_uuid\"";
234 | print STDERR "Running $cmd\n" if $opt_verbose;
235 |
236 | my @dsym_paths = ();
237 | my @archive_paths = ();
238 |
239 | foreach my $dsymdir (split(/\n/, `$cmd`)) {
240 | $cmd = "mdls -name com_apple_xcode_dsym_paths ".quotemeta($dsymdir);
241 | print STDERR "Running $cmd\n" if $opt_verbose;
242 |
243 | my $com_apple_xcode_dsym_paths = `$cmd`;
244 | $com_apple_xcode_dsym_paths =~ s/^com_apple_xcode_dsym_paths\ \= \(\n//;
245 | $com_apple_xcode_dsym_paths =~ s/\n\)//;
246 |
247 | my @subpaths = split(/,\n/, $com_apple_xcode_dsym_paths);
248 | map(s/^[[:space:]]*\"//, @subpaths);
249 | map(s/\"[[:space:]]*$//, @subpaths);
250 |
251 | push(@dsym_paths, map($dsymdir."/".$_, @subpaths));
252 |
253 | if($dsymdir =~ m/\.xcarchive$/) {
254 | push(@archive_paths, $dsymdir);
255 | }
256 | }
257 |
258 | @dsym_paths = uniq(@dsym_paths);
259 |
260 | if ( @dsym_paths >= 1 ) {
261 | foreach my $dsym_path (@dsym_paths) {
262 | my $arch = archForUUID($dsym_path, $uuid);
263 | if (defined($arch) && length($arch)) {
264 | print STDERR "Found dSYM $dsym_path ($arch)\n" if $opt_verbose;
265 | return ($dsym_path, $arch);
266 | }
267 | }
268 | }
269 |
270 | print STDERR "Did not find dsym for $uuid\n" if $opt_verbose;
271 | return undef;
272 | }
273 |
274 | #########
275 |
276 | sub archForUUID {
277 | my ($path, $uuid) = @_;
278 |
279 | if ( ! -f $path ) {
280 | print STDERR "## $path doesn't exist \n" if $opt_verbose;
281 | return undef;
282 | }
283 |
284 | my $cmd;
285 |
286 |
287 | $cmd = "/usr/bin/file '$path'";
288 | print STDERR "Running $cmd\n" if $opt_verbose;
289 | my $file_result = `$cmd`;
290 | my $is_dsym = index($file_result, "dSYM companion file") >= 0;
291 |
292 | my $canonical_uuid = getCanonicalUUIDForCrashLogUUID($uuid);
293 | my $architectures = "armv[4-8][tfsk]?|arm64|i386|x86_64\\S?";
294 | my $arch;
295 |
296 | $cmd = "'$symbolstool' -uuid '$path'";
297 | print STDERR "Running $cmd\n" if $opt_verbose;
298 |
299 | my $symbols_result = `$cmd`;
300 | if($symbols_result =~ /$canonical_uuid\s+($architectures)/) {
301 | $arch = $1;
302 | print STDERR "## $path contains $uuid ($arch)\n" if $opt_verbose;
303 | } else {
304 | print STDERR "## $path doesn't contain $uuid\n" if $opt_verbose;
305 | return undef;
306 | }
307 |
308 | $cmd = "'$otool' -arch $arch -l '$path'";
309 |
310 | print STDERR "Running $cmd\n" if $opt_verbose;
311 |
312 | my $TEST_uuid = `$cmd`;
313 | if ( $TEST_uuid =~ /uuid ((0x[0-9A-Fa-f]{2}\s+?){16})/ || $TEST_uuid =~ /uuid ([^\s]+)\s/ ) {
314 | my $test = $1;
315 |
316 | if ( $test =~ /^0x/ ) {
317 | # old style 0xnn 0xnn 0xnn ... on two lines
318 | $test = join("", split /\s*0x/, $test);
319 |
320 | $test =~ s/0x//g; ## remove 0x
321 | $test =~ s/\s//g; ## remove spaces
322 | } else {
323 | # new style XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
324 | $test =~ s/-//g; ## remove -
325 | $test = lc($test);
326 | }
327 |
328 | if ( $test eq $uuid ) {
329 |
330 | if ( $is_dsym ) {
331 | return $arch;
332 | } else {
333 | ## See that it isn't stripped. Even fully stripped apps have one symbol, so ensure that there is more than one.
334 | my ($nlocalsym) = $TEST_uuid =~ /nlocalsym\s+([0-9A-Fa-f]+)/;
335 | my ($nextdefsym) = $TEST_uuid =~ /nextdefsym\s+([0-9A-Fa-f]+)/;
336 | my $totalsym = $nextdefsym + $nlocalsym;
337 | print STDERR "\nNumber of symbols in $path: $nextdefsym + $nlocalsym = $totalsym\n" if $opt_verbose;
338 | return $arch if ( $totalsym > 1 );
339 |
340 | print STDERR "## $path appears to be stripped, skipping.\n" if $opt_verbose;
341 | }
342 | } else {
343 | print STDERR "Given UUID $uuid for '$path' is really UUID $test\n" if $opt_verbose;
344 | }
345 | } else {
346 | print STDERR "Can't understand the output from otool ($TEST_uuid -> $cmd)\n";
347 | return undef;
348 | }
349 |
350 | return undef;
351 | }
352 |
353 | sub getSymbolPathAndArchFor_manualDSYM {
354 | my ($uuid) = @_;
355 | my @dsym_machos = ();
356 |
357 | for my $dsym_path (@opt_dsyms) {
358 | if( -d $dsym_path ) {
359 | #test_path is a directory, assume it's a dSYM bundle and find the mach-o file(s) within
360 | push @dsym_machos, bsd_glob("$dsym_path/Contents/Resources/DWARF/*");
361 | next;
362 | }
363 |
364 | if ( -f $dsym_path ) {
365 | #test_path is a file, assume it's a dSYM macho file
366 | push @dsym_machos, $dsym_path;
367 | next;
368 | }
369 | }
370 |
371 | #Check the uuid's of each of the found files
372 | for my $macho_path (@dsym_machos) {
373 |
374 | print STDERR "Checking “$macho_path”\n";
375 |
376 | my $arch = archForUUID($macho_path, $uuid);
377 | if (defined($arch) && length($arch)) {
378 | print STDERR "$macho_path matches $uuid ($arch)\n";
379 | return ($macho_path, $arch);
380 | } else {
381 | print STDERR "$macho_path does not match $uuid\n";
382 | }
383 | }
384 |
385 | return undef;
386 | }
387 |
388 | sub getSymbolPathAndArchFor {
389 | my ($path,$build,$uuid,@extra_search_paths) = @_;
390 |
391 | # derive a few more parameters...
392 | my $bin = ($path =~ /^.*?([^\/]+)$/)[0]; # basename
393 |
394 | # Look in any of the manually-passed dSYMs
395 | if( @opt_dsyms ) {
396 | print STDERR "-- [$uuid] CHECK (manual)\n" if $opt_verbose;
397 | my ($out_path, $arch) = getSymbolPathAndArchFor_manualDSYM($uuid);
398 | if(defined($out_path) && length($out_path) && defined($arch) && length($arch)) {
399 | print STDERR "-- [$uuid] MATCH (manual): $out_path ($arch)\n" if $opt_verbose;
400 | return ($out_path, $arch);
401 | }
402 | print STDERR "-- [$uuid] NO MATCH (manual)\n\n" if $opt_verbose;
403 | }
404 |
405 | # Look for a UUID match in the cache directory
406 | my $uuidsPath = "/Volumes/Build/UUIDToSymbolMap";
407 | if ( -d $uuidsPath ) {
408 | print STDERR "-- [$uuid] CHECK (uuid cache)\n" if $opt_verbose;
409 | my $out_path = getSymbolPathFor_uuid($uuid, $uuidsPath);
410 | if(defined($out_path) && length($out_path)) {
411 | my $arch = archForUUID($out_path, $uuid);
412 | if (defined($arch) && length($arch)) {
413 | print STDERR "-- [$uuid] MATCH (uuid cache): $out_path ($arch)\n" if $opt_verbose;
414 | return ($out_path, $arch);
415 | }
416 | }
417 | print STDERR "-- [$uuid] NO MATCH (uuid cache)\n\n" if $opt_verbose;
418 | }
419 |
420 | # Look in the search paths (e.g. the device support directories)
421 | print STDERR "-- [$uuid] CHECK (device support)\n" if $opt_verbose;
422 | for my $func ( \&getSymbolPathAndArchFor_searchpaths, ) {
423 | my ($out_path, $arch) = &$func($bin,$path,$build,$uuid,@extra_search_paths);
424 | if ( defined($out_path) && length($out_path) && defined($arch) && length($arch) ) {
425 | print STDERR "-- [$uuid] MATCH (device support): $out_path ($arch)\n" if $opt_verbose;
426 | return ($out_path, $arch);
427 | }
428 | }
429 | print STDERR "-- [$uuid] NO MATCH (device support)\n\n" if $opt_verbose;
430 |
431 | # Ask spotlight
432 | if( $opt_spotlight ) {
433 | print STDERR "-- [$uuid] CHECK (spotlight)\n" if $opt_verbose;
434 | my ($out_path, $arch) = getSymbolPathAndArchFor_dsymUuid($uuid);
435 |
436 | if(defined($out_path) && length($out_path) && defined($arch) && length($arch)) {
437 | print STDERR "-- [$uuid] MATCH (spotlight): $out_path ($arch)\n" if $opt_verbose;
438 | return ($out_path, $arch);
439 | }
440 | print STDERR "-- [$uuid] NO MATCH (spotlight)\n\n" if $opt_verbose;
441 | }
442 |
443 | print STDERR "-- [$uuid] NO MATCH\n\n" if $opt_verbose;
444 |
445 | print STDERR "## Warning: Can't find any unstripped binary that matches version of $path\n" if $opt_verbose;
446 | print STDERR "\n" if $opt_verbose;
447 |
448 | return undef;
449 | }
450 |
451 | ###########################
452 | # crashlog parsing
453 | ###########################
454 |
455 | # options:
456 | # - regex: don't escape regex metas in name
457 | # - continuous: don't reset pos when done.
458 | # - multiline: expect content to be on many lines following name
459 | # - nocolon: when multiline, the header line does not contain a colon
460 | sub parse_section {
461 | my ($log_ref, $name, %arg ) = @_;
462 | my $content;
463 |
464 | $name = quotemeta($name)
465 | unless $arg{regex};
466 |
467 | my $colon = ':';
468 | if ($arg{nocolon}) {
469 | $colon = ''
470 | }
471 |
472 | # content is thing from name to end of line...
473 | if( $$log_ref =~ m{ ^($name)$colon [[:blank:]]* (.*?) $ }mgx ) {
474 | $content = $2;
475 | $name = $1;
476 | $name =~ s/^\s+//;
477 |
478 | # or thing after that line.
479 | if($arg{multiline}) {
480 | $content = $1 if( $$log_ref =~ m{
481 | \G\n # from end of last thing...
482 | (.*?)
483 | (?:\n\s*\n|$) # until next blank line or the end
484 | }sgx );
485 | }
486 | }
487 |
488 | pos($$log_ref) = 0
489 | unless $arg{continuous};
490 |
491 | return ($name,$content) if wantarray;
492 | return $content;
493 | }
494 |
495 | # convenience method over above
496 | sub parse_sections {
497 | my ($log_ref,$re,%arg) = @_;
498 |
499 | my ($name,$content);
500 | my %sections = ();
501 |
502 | while(1) {
503 | ($name,$content) = parse_section($log_ref,$re, regex=>1,continuous=>1,%arg);
504 | last unless defined $content;
505 | $sections{$name} = $content;
506 | }
507 |
508 | pos($$log_ref) = 0;
509 | return \%sections;
510 | }
511 |
512 | sub parse_threads {
513 | my ($log_ref,%arg) = @_;
514 |
515 | my $nocolon = 0;
516 | my $stack_delimeter = 'Thread\s+\d+\s?(Highlighted|Crashed)?'; # Crash reports
517 |
518 | if ($arg{event_type}) {
519 | # Spindump reports
520 | if ($arg{event_type} eq "cpu usage" ||
521 | $arg{event_type} eq "wakeups" ||
522 | $arg{event_type} eq "disk writes" ||
523 | $arg{event_type} eq "powerstats") {
524 |
525 | # Microstackshots report
526 | $stack_delimeter = 'Powerstats\sfor:.*';
527 | $nocolon = 1;
528 | } else {
529 | # Regular spindump
530 | $stack_delimeter = '\s+Thread\s+\S+(\s+DispatchQueue\s+\S+)?';
531 | $nocolon = 1;
532 | }
533 | }
534 |
535 | return parse_sections($log_ref,$stack_delimeter,multiline=>1,nocolon=>$nocolon)
536 | }
537 |
538 | sub parse_processes {
539 | my ($log_ref, $is_spindump_report, $event_type) = @_;
540 |
541 | if (! $is_spindump_report) {
542 | # Crash Reports only have one process
543 | return ($log_ref);
544 | }
545 |
546 | my $process_delimeter;
547 |
548 | if ($event_type eq "cpu usage" ||
549 | $event_type eq "wakeups" ||
550 | $event_type eq "disk writes" ||
551 | $event_type eq "powerstats") {
552 |
553 | # Microstackshots report
554 | $process_delimeter = '^Powerstats\s+for';
555 | } else {
556 | # Regular spindump
557 | $process_delimeter = '^Process';
558 | }
559 |
560 | return \split(/(?=$process_delimeter)/m, $$log_ref);
561 | }
562 |
563 | sub parse_images {
564 | my ($log_ref, $report_version, $is_spindump_report) = @_;
565 |
566 | my $section = parse_section($log_ref,'Binary Images Description',multiline=>1);
567 | if (!defined($section)) {
568 | $section = parse_section($log_ref,'\\s*Binary\\s*Images',multiline=>1,regex=>1); # new format
569 | }
570 | if (!defined($section)) {
571 | die "Error: Can't find \"Binary Images\" section in log file";
572 | }
573 |
574 | my @lines = split /\n/, $section;
575 | scalar @lines or die "Can't find binary images list: $$log_ref" if !$is_spindump_report;
576 |
577 | my %images = ();
578 | my ($pat, $app, %captures);
579 |
580 | #To get all the architectures for string matching.
581 | my $architectures = "armv[4-8][tfsk]?|arm64|i386|x86_64\\S?";
582 |
583 | # Once Perl 5.10 becomes the default in Mac OS X, named regexp
584 | # capture buffers of the style (?pattern) would make this
585 | # code much more sane.
586 | if(! $is_spindump_report) {
587 | if($report_version == 102 || $report_version == 103) { # Leopard GM
588 | $pat = '
589 | ^\s* (\w+) \s* \- \s* (\w+) \s* (?# the range base and extent [1,2] )
590 | (\+)? (?# the application may have a + in front of the name [3] )
591 | (.+) (?# bundle name [4] )
592 | \s+ .+ \(.+\) \s* (?# the versions--generally "??? [???]" )
593 | \([[:xdigit:]]{32})?\>? (?# possible UUID [5] )
594 | \s* (\/.*)\s*$ (?# first fwdslash to end we hope is path [6] )
595 | ';
596 | %captures = ( 'base' => \$1, 'extent' => \$2, 'plus' => \$3,
597 | 'bundlename' => \$4, 'uuid' => \$5, 'path' => \$6);
598 | }
599 | elsif($report_version == 104 || $report_version == 105) { # Kirkwood
600 | # 0x182155000 - 0x1824c6fff CoreFoundation arm64 /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
601 | $pat = '
602 | ^\s* (\w+) \s* \- \s* (\w+) \s* (?# the range base and extent [1,2] )
603 | (\+)? (?# the application may have a + in front of the name [3] )
604 | (.+) (?# bundle name [4] )
605 | \s+ ('.$architectures.') \s+ (?# the image arch [5] )
606 | \([[:xdigit:]]{32})?\>? (?# possible UUID [6] )
607 | \s* (\/.*)\s*$ (?# first fwdslash to end we hope is path [7] )
608 | ';
609 | %captures = ( 'base' => \$1, 'extent' => \$2, 'plus' => \$3,
610 | 'bundlename' => \$4, 'arch' => \$5, 'uuid' => \$6,
611 | 'path' => \$7);
612 | }
613 | else {
614 | die "Unsupported crash log version: $report_version";
615 | }
616 | }
617 | else { # Spindump reports
618 | # 0x7fffa5f55000 - 0x7fffa63ddff7 com.apple.CoreFoundation 6.9 (1333.19) <08238AC4-4618-39AC-878B-B1562CD6B235> /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
619 | $pat = '
620 | ^ (?# Beginning of the line )
621 | \s* \*? (?# indent and kernel dot)
622 | (\S+) \s* \- \s* (\S+) (?# the range base and extent [1,2] )
623 | \s+ (.+?) (?# bundle name [3] )
624 | (?: \s+ (\S+) )? (?# optional short version [4] )
625 | (?: \s+ \( (\S+) \) )? (?# optional version [5] )
626 | \s+ \< ( .* ) \> (?# UUID [6] )
627 | (?: \s+ (\/.*) )? (?# optional path [7] )
628 | \s*$ (?# End of the line )
629 | ';
630 | %captures = ( 'base' => \$1, 'extent' => \$2, 'bundleid' => \$3,
631 | 'shortversion' => \$4, 'version' => \$5, 'uuid' => \$6,
632 | 'path' => \$7);
633 | }
634 |
635 | for my $line (@lines) {
636 | next if $line =~ /PEF binary:/; # ignore these
637 |
638 | $line =~ s/(&(\w+);?)/$entity2char{$2} || $1/eg;
639 |
640 | if ($line =~ /$pat/ox) {
641 |
642 | # Dereference references
643 | my %image;
644 | while((my $key, my $val) = each(%captures)) {
645 | $image{$key} = ${$captures{$key}} || '';
646 | #print STDERR "image{$key} = $image{$key}\n";
647 | }
648 |
649 | if (defined $image{bundleid} && $image{bundleid} eq "???") {
650 | delete $image{bundleid};
651 | }
652 |
653 | if (! defined $image{bundlename}) {
654 | # (Only occurs in spindump)
655 | # Match what string frames will use as the binary's identifier
656 | if (defined $image{path} && $image{path} ne '') {
657 | $image{bundlename} = ($image{path} =~ /^.*?([^\/]+)$/)[0]; # basename of path
658 | } elsif (defined $image{bundleid} && $image{bundleid} ne '') {
659 | $image{bundlename} = $image{bundleid};
660 | } else {
661 | $image{bundlename} = "<$image{uuid}>";
662 | }
663 | }
664 |
665 | if ($image{extent} eq "???") {
666 | $image{extent} = '';
667 | }
668 |
669 | # Spindump uses canonical UUID, but the rest of the code here expects CrashLog style UUIDs
670 | $image{uuid} = getCrashLogUUIDForCanonicalUUID($image{uuid});
671 |
672 | # Just take the first instance. That tends to be the app.
673 | my $bundlename = $image{bundlename};
674 | $app = $bundlename if (!defined $app && defined $image{plus} && length $image{plus});
675 |
676 | # frameworks and apps (and whatever) may share the same name, so disambiguate
677 | if ( defined($images{$bundlename}) ) {
678 | # follow the chain of hash items until the end
679 | my $nextIDKey = $bundlename;
680 | while ( length($nextIDKey) ) {
681 | last if ( !length($images{$nextIDKey}{nextID}) );
682 | $nextIDKey = $images{$nextIDKey}{nextID};
683 | }
684 |
685 | # add ourselves to that chain
686 | $images{$nextIDKey}{nextID} = $image{base};
687 |
688 | # and store under the key we just recorded
689 | $bundlename = $bundlename . $image{base};
690 | }
691 |
692 | # we are the end of the nextID chain
693 | $image{nextID} = "";
694 |
695 | $images{$bundlename} = \%image;
696 | }
697 | }
698 |
699 | return (\%images, $app);
700 | }
701 |
702 | # if this is actually a partial binary identifier we know about, then
703 | # return the full name. else return undef.
704 | my %_partial_cache = ();
705 | sub resolve_partial_id {
706 | my ($bundle,$images) = @_;
707 | # is this partial? note: also stripping elipsis here
708 | return undef unless $bundle =~ s/^\.\.\.//;
709 | return $_partial_cache{$bundle} if exists $_partial_cache{$bundle};
710 |
711 | my $re = qr/\Q$bundle\E$/;
712 | for (keys %$images) {
713 | if( /$re/ ) {
714 | $_partial_cache{$bundle} = $_;
715 | return $_;
716 | }
717 | }
718 | return undef;
719 | }
720 |
721 | sub fixup_last_exception_backtrace {
722 | my ($log_ref,$exception,$images) = @_;
723 | my $repl = $exception;
724 | if ($exception =~ m/^.0x/) {
725 | my @lines = split / /, substr($exception, 1, length($exception)-2);
726 | my $counter = 0;
727 | $repl = "";
728 | for my $line (@lines) {
729 | my ($image,$image_base) = findImageByAddress($images, $line);
730 | my $offset = hex($line) - hex($image_base);
731 | my $formattedTrace = sprintf("%-3d %-30s\t0x%08x %s + %d", $counter, $image, hex($line), $image_base, $offset);
732 | $repl .= $formattedTrace . "\n";
733 | ++$counter;
734 | }
735 | $log_ref = replace_chunk($log_ref, $exception, $repl);
736 | # may need to do this a second time since there could be First throw call stack too
737 | $log_ref = replace_chunk($log_ref, $exception, $repl);
738 | }
739 | return ($log_ref, $repl);
740 | }
741 |
742 | #sub parse_last_exception_backtrace {
743 | # print STDERR "Parsing last exception backtrace\n" if $opt_verbose;
744 | # my ($backtrace,$images, $inHex) = @_;
745 | # my @lines = split /\n/,$backtrace;
746 | #
747 | # my %frames = ();
748 | #
749 | # # these two have to be parallel; we'll lookup by hex, and replace decimal if needed
750 | # my @hexAddr;
751 | # my @replAddr;
752 | #
753 | # for my $line (@lines) {
754 | # # end once we're done with the frames
755 | # last if $line =~ /\)/;
756 | # last if !length($line);
757 | #
758 | # if ($inHex && $line =~ /0x([[:xdigit:]]+)/) {
759 | # push @hexAddr, sprintf("0x%08s", $1);
760 | # push @replAddr, "0x".$1;
761 | # }
762 | # elsif ($line =~ /(\d+)/) {
763 | # push @hexAddr, sprintf("0x%08x", $1);
764 | # push @replAddr, $1;
765 | # }
766 | # }
767 | #
768 | # # we don't have a hint as to the binary assignment of these frames
769 | # # map_addresses will do it for us
770 | # return map_addresses(\@hexAddr,$images,\@replAddr);
771 | #}
772 |
773 | # returns an oddly-constructed hash:
774 | # 'string-to-replace' => { bundle=>..., address=>... }
775 | sub parse_backtrace {
776 | my ($backtrace,$images,$decrement,$is_spindump_report) = @_;
777 | my @lines = split /\n/,$backtrace;
778 |
779 | my %frames = ();
780 |
781 | if ( ! $is_spindump_report ) {
782 | # Crash report
783 |
784 | my $is_first = 1;
785 |
786 | for my $line (@lines) {
787 | if( $line =~ m{
788 | ^\d+ \s+ # stack frame number
789 | (\S.*?) \s+ # bundle [1]
790 | ( # description to replace [2]
791 | (0x\w+) \s+ # address [3]
792 | 0x\w+ \s+ # library address
793 | (?: \+ \s+ (\d+))? # offset [4], optional
794 | .* # remainder of description
795 | ) # end of capture
796 | \s* # new line
797 | $ # end of line
798 | }x ) {
799 | my($bundle,$replace,$address,$offset) = ($1,$2,$3,$4);
800 | #print STDERR "Parse_bt: $bundle,$replace,$address\n" if ($opt_verbose);
801 |
802 | # disambiguate within our hash of binaries
803 | $bundle = findImageByNameAndAddress($images, $bundle, $address);
804 |
805 | # skip unless we know about the image of this frame
806 | next unless
807 | $$images{$bundle} or
808 | $bundle = resolve_partial_id($bundle,$images);
809 |
810 | my $raw_address = $address;
811 | if($decrement && !$is_first) {
812 | $address = sprintf("0x%X", (hex($address) & ~1) - 1);
813 | }
814 |
815 | $frames{$replace} = {
816 | 'address' => $address,
817 | 'raw_address' => $raw_address,
818 | 'bundle' => $bundle,
819 | };
820 |
821 | if (defined $offset) {
822 | $frames{$replace}{offset} = $offset
823 | }
824 |
825 | $is_first = 0;
826 | }
827 | # else { print STDERR "unable to parse backtrace line $line\n" }
828 | }
829 |
830 | } else {
831 | # Spindump report
832 |
833 | my $previousFrame;
834 | my $previousIndentLength;
835 |
836 | for my $line (@lines) {
837 | # *138 unix_syscall64 + 675 (systemcalls.c:376,10 in kernel.development + 6211555) [0xffffff80007ec7e3] 1-138
838 | if( $line =~ m{
839 | ^ # Start of line
840 | ( \s* \*? ) # indent and kernel dot [1]
841 | ( \d+ ) \s+ # count [2]
842 | ( # Start of string to replace (symbol, binary, address) [3]
843 | ( .+? ) # symbol [4]
844 | (?: \s* \+ \s* (\d+) )? # offset from symbol [5], optional
845 | (?: \s+ \( # Start of binary info, entire section optional
846 | (?: ( .*? ) \s+ in \s+ )? # source info [6], optional
847 | (.+?) # Binary name (or UUID, if no name) [7]
848 | (?: \s* \+ \s* (\d+) )? # Offset in binary [8], optional
849 | \) )? # End of binary info, entire section optional
850 | \s* \[ (.+) \] # address [9]
851 | ) # End of string to replace
852 | (?: \s+ \(.*\) )? # state [10], optional
853 | (?: \s+ # Start of timeline info, entire section optional
854 | (\d+) # Start time index [11]
855 | (?: \s* \- \s* (\d+))? # End time index [12], optional
856 | )? # End of timeline info, entire section optional
857 | $ # End of line
858 | }x ) {
859 | my($indent,$count,$replace,$symbol,$offsetInSymbol,$sourceInfo,$binaryName,$offsetInBinary,$address,$state,$timeIndexStart,$timeIndexEnd) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);
860 | # print STDERR "Parse_bt $line:\n$indent,$count,$symbol,$offsetInSymbol,$sourceInfo,$binaryName,$offsetInBinary,$address,$timeIndexStart,$timeIndexEnd\n" if ($opt_verbose);
861 |
862 | next if defined $sourceInfo; # Don't bother trying to sybolicate frames that already have source info
863 |
864 | next unless defined $binaryName;
865 |
866 | # disambiguate within our hash of binaries
867 | my $binaryKey = findImageByNameAndAddress($images, $binaryName, $address);
868 |
869 | # skip unless we know about the image of this frame
870 | next unless
871 | $$images{$binaryName};
872 |
873 | $frames{$replace} = {
874 | 'address' => $address, # To be fixed up for non-leaf frames in the next loop
875 | 'raw_address' => $address,
876 | 'bundle' => $binaryKey,
877 | };
878 |
879 | # Fixed up symbolication address the non-leaf previous frame
880 | if (defined $previousFrame && defined $previousIndentLength &&
881 | length $indent > $previousIndentLength) {
882 |
883 | $$previousFrame{'address'} = sprintf("0x%X", (hex($$previousFrame{'address'}) & ~1) - 1);
884 |
885 | # print STDERR "Updated symbolication address: $$previousFrame{'raw_address'} -> $$previousFrame{'address'}\n";
886 | }
887 | $previousIndentLength = length $indent;
888 | $previousFrame = $frames{$replace};
889 | }
890 | # else { print STDERR "unable to parse backtrace line $line\n" }
891 | }
892 |
893 |
894 | }
895 |
896 | return \%frames;
897 | }
898 |
899 | sub slurp_file {
900 | my ($file) = @_;
901 | my $data;
902 | my $fh;
903 | my $readingFromStdin = 0;
904 |
905 | local $/ = undef;
906 |
907 | # - or "" mean read from stdin, otherwise use the given filename
908 | if($file && $file ne '-') {
909 | open $fh,"<",$file or die "while reading $file, $! : ";
910 | } else {
911 | open $fh,"<&STDIN" or die "while readin STDIN, $! : ";
912 | $readingFromStdin = 1;
913 | }
914 |
915 | $data = <$fh>;
916 |
917 |
918 | # Replace DOS-style line endings
919 | $data =~ s/\r\n/\n/g;
920 |
921 | # Replace Mac-style line endings
922 | $data =~ s/\r/\n/g;
923 |
924 | # Replace "NO-BREAK SPACE" (these often get inserted when copying from Safari)
925 | # \xC2\xA0 == U+00A0
926 | $data =~ s/\xc2\xa0/ /g;
927 |
928 | close $fh or die $!;
929 | return \$data;
930 | }
931 |
932 | sub parse_OSVersion {
933 | my ($log_ref) = @_;
934 | my $section = parse_section($log_ref,'OS Version');
935 | if ( $section =~ /\s([0-9\.]+)\s+\(Build (\w+)/ ) {
936 | return ($1, $2)
937 | }
938 | if ( $section =~ /\s([0-9\.]+)\s+\((\w+)/ ) {
939 | return ($1, $2)
940 | }
941 | if ( $section =~ /\s([0-9\.]+)/ ) {
942 | return ($1, "")
943 | }
944 | die "Error: can't parse OS Version string $section";
945 | }
946 |
947 | sub parse_HardwareModel {
948 | my ($log_ref) = @_;
949 | my $model = parse_section($log_ref, 'Hardware Model');
950 | if (!defined($model)) {
951 | $model = parse_section($log_ref, 'Hardware model'); # spindump format
952 | }
953 |
954 | $model or return undef;
955 | # HACK: replace the comma in model names because bsd_glob can't handle commas (even escaped ones) in
956 | # the {} groups
957 | $model =~ s/,/\?/g;
958 | $model =~ /(\S+)/;
959 | return $1;
960 | }
961 |
962 | sub parse_SDKGuess {
963 | my ($log_ref) = @_;
964 |
965 | # It turns out that most SDKs are named "lowercased(HardwareModelWithoutNumbers) + os",
966 | # so attempt to form a valid SDK name from that. Any code that uses this must NOT rely
967 | # on this guess being accurate and should fallback to whatever logic makes sense for the situation
968 | my $model = parse_HardwareModel($log_ref);
969 | $model or return undef;
970 |
971 | $model =~ /(\D+)\d/;
972 | $1 or return undef;
973 |
974 | my $sdk = lc($1) . "os";
975 | if($sdk eq "ipodos" || $sdk eq "ipados") {
976 | $sdk = "iphoneos";
977 | }
978 | if ( $sdk =~ /mac/) {
979 | $sdk = "macosx";
980 | }
981 |
982 | return $sdk;
983 | }
984 |
985 | sub parse_event_type {
986 | my ($log_ref) = @_;
987 | my $event = parse_section($log_ref,'Event');
988 | return $event;
989 | }
990 |
991 | sub parse_steps {
992 | my ($log_ref) = @_;
993 | my $steps = parse_section($log_ref,'Steps');
994 | $steps or return undef;
995 | $steps =~ /(\d+)/;
996 | return $1;
997 | }
998 |
999 | sub parse_report_version {
1000 | my ($log_ref) = @_;
1001 | my $version = parse_section($log_ref,'Report Version');
1002 | $version or return undef;
1003 | $version =~ /(\d+)/;
1004 | return $1;
1005 | }
1006 | sub findImageByAddress {
1007 | my ($images,$address) = @_;
1008 | my $image;
1009 |
1010 | for $image (values %$images) {
1011 | if ( hex($address) >= hex($$image{base}) && hex($address) <= hex($$image{extent}) )
1012 | {
1013 | return ($$image{bundlename},$$image{base});
1014 | }
1015 | }
1016 |
1017 | print STDERR "Unable to map $address\n" if $opt_verbose;
1018 |
1019 | return undef;
1020 | }
1021 |
1022 | sub findImageByNameAndAddress {
1023 | my ($images,$bundle,$address) = @_;
1024 | my $key = $bundle;
1025 |
1026 | #print STDERR "findImageByNameAndAddress($bundle,$address) ... ";
1027 |
1028 | my $binary = $$images{$bundle};
1029 |
1030 | while($$binary{nextID} && length($$binary{nextID}) ) {
1031 | last if ( hex($address) >= hex($$binary{base}) && hex($address) <= hex($$binary{extent}) );
1032 |
1033 | $key = $key . $$binary{nextID};
1034 | $binary = $$images{$key};
1035 | }
1036 |
1037 | #print STDERR "$key\n";
1038 | return $key;
1039 | }
1040 |
1041 | sub prune_used_images {
1042 | my ($images,$bt) = @_;
1043 |
1044 | # make a list of images actually used in backtrace
1045 | my $images_used = {};
1046 | for(values %$bt) {
1047 | #print STDERR "Pruning: $images, $$_{bundle}, $$_{address}\n" if ($opt_verbose);
1048 | my $imagename = findImageByNameAndAddress($images, $$_{bundle}, $$_{address});
1049 | $$images_used{$imagename} = $$images{$imagename};
1050 | }
1051 |
1052 | # overwrite the incoming image list with that;
1053 | %$images = %$images_used;
1054 | }
1055 |
1056 | # fetch symbolled binaries
1057 | # array of binary image ranges and names
1058 | # the OS build
1059 | # the name of the crashed program
1060 | # undef
1061 | # array of possible directories to locate symboled files in
1062 | sub fetch_symbolled_binaries {
1063 | our %uuid_cache; # Global cache of UUIDs we've already searched for
1064 |
1065 | print STDERR "Finding Symbols:\n" if $opt_verbose;
1066 |
1067 | my ($images,$build,$bundle,@extra_search_paths) = @_;
1068 |
1069 | # fetch paths to symbolled binaries. or ignore that lib if we can't
1070 | # find it
1071 | for my $b (keys %$images) {
1072 | my $lib = $$images{$b};
1073 | my $symbol;
1074 | my $arch;
1075 |
1076 | if (defined $uuid_cache{$$lib{uuid}}) {
1077 | ($symbol, $arch) = @{$uuid_cache{$$lib{uuid}}};
1078 | if ( $symbol ) {
1079 | $$lib{symbol} = $symbol;
1080 | if ( ! (defined $$lib{arch} && length $$lib{arch}) ) {
1081 | if (defined $arch && length($arch)) {
1082 | print STDERR "Already found $b: @{$uuid_cache{$$lib{uuid}}}\n" if $opt_verbose;
1083 | $$lib{arch} = $arch;
1084 | } else {
1085 | print STDERR "Already checked and failed to find $b (found $symbol, nob can't determine arch)\n" if $opt_verbose;
1086 | delete $$images{$b};
1087 | next;
1088 | }
1089 | } else {
1090 | print STDERR "Already found $b: @{$uuid_cache{$$lib{uuid}}}\n" if $opt_verbose;
1091 | }
1092 | } else {
1093 | print STDERR "Already checked and failed to find $b\n" if $opt_verbose;
1094 | delete $$images{$b};
1095 | next;
1096 | }
1097 | } else {
1098 |
1099 |
1100 | print STDERR "-- [$$lib{uuid}] fetching symbol file for $b\n" if $opt_verbose;
1101 |
1102 | $symbol = $$lib{symbol};
1103 | if ($symbol) {
1104 | print STDERR "-- [$$lib{uuid}] found in cache\n" if $opt_verbose;
1105 | } else {
1106 | ($symbol, $arch) = getSymbolPathAndArchFor($$lib{path},$build,$$lib{uuid},@extra_search_paths);
1107 | @{$uuid_cache{$$lib{uuid}}} = ($symbol, $arch);
1108 | if ( $symbol ) {
1109 | $$lib{symbol} = $symbol;
1110 | if ( ! (defined $$lib{arch} && length $$lib{arch}) ) {
1111 | if (defined $arch && length($arch)) {
1112 | print STDERR "Set $$lib{uuid} to $arch\n" if $opt_verbose;
1113 | $$lib{arch} = $arch;
1114 | } else {
1115 | delete $$images{$b};
1116 | next;
1117 | }
1118 | }
1119 | } else {
1120 | delete $$images{$b};
1121 | next;
1122 | }
1123 | }
1124 | }
1125 |
1126 | # check for sliding. set slide offset if so
1127 | open my($ph),"-|", "'$size' -m -l -x '$symbol'" or die $!;
1128 | my $real_base = (
1129 | grep { $_ }
1130 | map { (/_TEXT.*vmaddr\s+(\w+)/)[0] } <$ph>
1131 | )[0];
1132 | close $ph;
1133 | if ($?) {
1134 |
1135 | # 13T5280f: My crash logs aren't symbolicating
1136 | # System libraries were not being symbolicated because /usr/bin/size is always failing.
1137 | # That's /usr/bin/size doesn't like LC_SEGMENT_SPLIT_INFO command 12
1138 | #
1139 | # Until that's fixed, just hope for the best and assume no sliding. I've been informed that since
1140 | # this scripts always deals with post-mortem crash files instead of running processes, sliding shouldn't
1141 | # happen in practice. Nevertheless, we should probably add this sanity check back in once we 21604022
1142 | # gets resolved.
1143 | $real_base = $$lib{base}
1144 |
1145 | # call to size failed. Don't use this image in symbolication; don't die
1146 | # delete $$images{$b};
1147 | #print STDERR "Error in symbol file for $symbol\n"; # and log it
1148 | # next;
1149 | }
1150 |
1151 | if($$lib{base} ne $real_base) {
1152 | $$lib{slide} = hex($real_base) - hex($$lib{base});
1153 | }
1154 | }
1155 |
1156 | print STDERR keys(%$images) . " binary images were found.\n" if $opt_verbose;
1157 | }
1158 |
1159 | # run atos
1160 | sub symbolize_frames {
1161 | my ($images,$bt,$is_spindump_report) = @_;
1162 |
1163 | # create mapping of framework => address => bt frame (adjust for slid)
1164 | # and for framework => arch
1165 | my %frames_to_lookup = ();
1166 | my %arch_map = ();
1167 | my %base_map = ();
1168 | my %image_map = ();
1169 |
1170 | for my $k (keys %$bt) {
1171 | my $frame = $$bt{$k};
1172 | my $lib = $$images{$$frame{bundle}};
1173 | unless($lib) {
1174 | # don't know about it, can't symbol
1175 | # should have already been warned about this!
1176 | # print STDERR "Skipping unknown $$frame{bundle}\n";
1177 | delete $$bt{$k};
1178 | next;
1179 | }
1180 |
1181 | # list of address to lookup, mapped to the frame object, for
1182 | # each library
1183 | $frames_to_lookup{$$lib{symbol}}{$$frame{address}} = $frame;
1184 | $arch_map{$$lib{symbol}} = $$lib{arch};
1185 | $base_map{$$lib{symbol}} = $$lib{base};
1186 | $image_map{$$lib{symbol}} = $lib;
1187 | }
1188 |
1189 | # run atos for each library
1190 | while(my($symbol,$frames) = each(%frames_to_lookup)) {
1191 | # escape the symbol path if it contains single quotes
1192 | my $escapedSymbol = $symbol;
1193 | $escapedSymbol =~ s/\'/\'\\'\'/g;
1194 |
1195 | # run atos with the addresses and binary files we just gathered
1196 | my $arch = $arch_map{$symbol};
1197 | my $base = $base_map{$symbol};
1198 | my $lib = $image_map{$symbol};
1199 | my $cmd = "'$atos' -arch $arch -l $base -o '$escapedSymbol' @{[ keys %$frames ]} | ";
1200 |
1201 | print STDERR "Running $cmd\n" if $opt_verbose;
1202 |
1203 | open my($ph),$cmd or die $!;
1204 | my @symbolled_frames = map { chomp; $_ } <$ph>;
1205 | close $ph or die $!;
1206 |
1207 | my $references = 0;
1208 |
1209 | foreach my $symbolled_frame (@symbolled_frames) {
1210 |
1211 | my ($library, $source) = ($symbolled_frame =~ /\s*\(in (.*?)\)(?:\s*\((.*?)\))?/);
1212 | $symbolled_frame =~ s/\s*\(in .*?\)//; # clean up -- don't need to repeat the lib here
1213 |
1214 | if ($is_spindump_report) {
1215 | # Source is formatted differently for spindump
1216 | $symbolled_frame =~ s/\s*\(.*?\)//; # remove source info from symbol string
1217 |
1218 | # Spindump may not have had library names, pick them up here
1219 | if (defined $library && !(defined $$lib{path} && length($$lib{path})) && !(defined $$lib{new_path} && length($$lib{new_path})) ) {
1220 | $$lib{new_path} = $library;
1221 | print STDERR "Found new name for $$lib{uuid}: $$lib{new_path}\n" if ( $opt_verbose );
1222 | }
1223 | }
1224 |
1225 |
1226 | # find the correct frame -- the order should match since we got the address list with keys
1227 | my ($k,$frame) = each(%$frames);
1228 |
1229 | if ( $symbolled_frame !~ /^\d/ ) {
1230 | # only symbolicate if we fetched something other than an address
1231 |
1232 | my $offset = $$frame{offset};
1233 | if (defined $offset) {
1234 | # add offset from unsymbolicated frame after symbolicated name
1235 | $symbolled_frame =~ s|(.+)\(|$1."+ ".$offset." ("|e;
1236 | }
1237 |
1238 | if ($is_spindump_report) {
1239 | # Spindump formatting
1240 | if (defined $library) {
1241 | $symbolled_frame .= " (";
1242 | if (defined $source) {
1243 | $symbolled_frame .= "$source in ";
1244 | }
1245 | $symbolled_frame .= "$library + " . (hex($$frame{raw_address}) - hex($base)) . ")";
1246 | }
1247 | $symbolled_frame .= " [$$frame{raw_address}]";
1248 | }
1249 |
1250 | $$frame{symbolled} = $symbolled_frame;
1251 | $references++;
1252 | }
1253 |
1254 | }
1255 |
1256 | if ( $references == 0 ) {
1257 | if ( ! $is_spindump_report) { # Bad addresses aren't uncommon in microstackshots and stackshots
1258 | print STDERR "## Warning: Unable to symbolicate from required binary: $symbol\n";
1259 | }
1260 | }
1261 | }
1262 |
1263 | # just run through and remove elements for which we didn't find a
1264 | # new mapping:
1265 | while(my($k,$v) = each(%$bt)) {
1266 | delete $$bt{$k} unless defined $$v{symbolled};
1267 | }
1268 | }
1269 |
1270 | # run the final regex to symbolize the log
1271 | sub replace_symbolized_frames {
1272 | my ($log_ref,$bt,$images,$is_spindump_report) = @_;
1273 | my $re = join "|" , map { quotemeta } keys %$bt;
1274 |
1275 | # spindump's symbolled string already includes the raw address
1276 | my $log = $$log_ref;
1277 | $log =~ s#$re#
1278 | my $frame = $$bt{$&};
1279 | (! $is_spindump_report ? $$frame{raw_address} . " " : "") . $$frame{symbolled};
1280 | #esg;
1281 |
1282 | $log =~ s/(&(\w+);?)/$entity2char{$2} || $1/eg;
1283 |
1284 |
1285 | if ($is_spindump_report) {
1286 | # Spindump may not have image names, so add any names we found
1287 |
1288 | my @images_to_replace_keys = grep { defined $$images{$_}{new_path} } keys %$images;
1289 |
1290 | if (scalar(@images_to_replace_keys)) {
1291 |
1292 | print STDERR "" . scalar(@images_to_replace_keys) . " images with new names:\n" if ( $opt_verbose );
1293 | if ( $opt_verbose ) { print STDERR "$_\n" for @images_to_replace_keys; }
1294 |
1295 | # First, replace in frames that we couldn't symbolicate
1296 | # 2 ??? ( + 196600) [0x1051e3ff8]
1297 | # becomes
1298 | # 2 ??? (BackBoard + 196600) [0x1051e3ff8]
1299 | my $image_re = join "|" , map { quotemeta } @images_to_replace_keys;
1300 | $image_re = "\\(($image_re)"; # Open paren precedes UUID in frames
1301 |
1302 | $log =~ s#$image_re#
1303 | "(" . $$images{$1}{new_path}
1304 | #esg;
1305 |
1306 | $log =~ s/(&(\w+);?)/$entity2char{$2} || $1/eg;
1307 |
1308 | # Second, replace in image infos
1309 | # 0x1051b4000 - ??? ???
1310 | # becomes
1311 | # 0x1051b4000 - ??? ??? BackBoard
1312 | $image_re = join "|" , map { quotemeta } @images_to_replace_keys;
1313 | $image_re = "\\s($image_re)"; # Whitespace precedes image infos
1314 |
1315 | $log =~ s#$image_re#
1316 | "$& " . $$images{$1}{new_path}
1317 | #esg;
1318 |
1319 | $log =~ s/(&(\w+);?)/$entity2char{$2} || $1/eg;
1320 |
1321 | }
1322 | }
1323 |
1324 |
1325 | return \$log;
1326 | }
1327 |
1328 | sub replace_chunk {
1329 | my ($log_ref,$old,$new) = @_;
1330 | my $log = $$log_ref;
1331 | my $re = quotemeta $old;
1332 | $log =~ s/$re/$new/;
1333 | return \$log;
1334 | }
1335 |
1336 | #############
1337 |
1338 | sub output_log($) {
1339 | my ($log_ref) = @_;
1340 |
1341 | if($opt_output && $opt_output ne "-") {
1342 | close STDOUT;
1343 | open STDOUT, '>', $opt_output;
1344 | }
1345 |
1346 | print $$log_ref;
1347 | }
1348 |
1349 | #############
1350 |
1351 | sub symbolicate_log {
1352 | my ($file,@extra_search_paths) = @_;
1353 |
1354 | print STDERR "Symbolicating $file ...\n" if ( $opt_verbose && defined $file);
1355 | print STDERR "Symbolicating stdin ...\n" if ( $opt_verbose && ! defined $file);
1356 |
1357 | my $log_ref = slurp_file($file);
1358 |
1359 | print STDERR length($$log_ref)." characters read.\n" if ( $opt_verbose );
1360 |
1361 | # get the version number
1362 | my $report_version = parse_report_version($log_ref);
1363 | $report_version or die "No crash report version in $file";
1364 |
1365 | # setup the tool paths we will need
1366 | my $sdkGuess = parse_SDKGuess($log_ref);
1367 | print STDERR "SDK guess for tool search is '$sdkGuess'\n" if $opt_verbose;
1368 | $otool = getToolPath("otool", $sdkGuess);
1369 | $atos = getToolPath("atos", $sdkGuess);
1370 | $symbolstool = getToolPath("symbols", $sdkGuess);
1371 | $size = getToolPath("size", $sdkGuess);
1372 |
1373 | # spindump-based reports will have an "Steps:" line.
1374 | # ReportCrash-based reports will not
1375 | my $steps = parse_steps($log_ref);
1376 | my $is_spindump_report = defined $steps;
1377 |
1378 | my $event_type;
1379 | if ($is_spindump_report) {
1380 |
1381 | # Spindump's format changes depending on the event (microstackshots vs regular spindump)
1382 | $event_type = parse_event_type($log_ref);
1383 | $event_type = $event_type || "manual";
1384 |
1385 | # Cut off spindump's binary format
1386 | $$log_ref =~ s/Spindump binary format.*$//s;
1387 | }
1388 |
1389 | # extract hardware model
1390 | my $model = parse_HardwareModel($log_ref);
1391 | print STDERR "Hardware Model $model\n" if $opt_verbose;
1392 |
1393 | # extract build
1394 | my ($version, $build) = parse_OSVersion($log_ref);
1395 | print STDERR "OS Version $version Build $build\n" if $opt_verbose;
1396 |
1397 | my @process_sections = parse_processes($log_ref, $is_spindump_report, $event_type);
1398 |
1399 | my $header;
1400 | my $multiple_processes = 0;
1401 | if (scalar(@process_sections) > 1) {
1402 | # If we found multiple process sections, the first section is just the report's header
1403 | $header = shift @process_sections;
1404 |
1405 | print STDERR "Found " . scalar(@process_sections) . " process sections\n" if $opt_verbose;
1406 | $multiple_processes = 1;
1407 | }
1408 |
1409 | my $symbolicated_something = 0;
1410 |
1411 | for my $process_section (@process_sections) {
1412 | if ($multiple_processes) {
1413 | print STDERR "Processing " . ($$process_section =~ /^.*:\s+(.*)/)[0] . "\n";
1414 | }
1415 |
1416 |
1417 | # read the binary images
1418 | my ($images,$first_bundle) = parse_images($process_section, $report_version, $is_spindump_report);
1419 |
1420 | if ( $opt_verbose ) {
1421 | print STDERR keys(%$images) . " binary images referenced:\n";
1422 | foreach (keys(%$images)) {
1423 | print STDERR $_;
1424 | print STDERR "\t\t(";
1425 | print STDERR $$images{$_}{path};
1426 | print STDERR ")\n";
1427 | }
1428 | print STDERR "\n";
1429 | }
1430 |
1431 | my $bt = {};
1432 | my $threads = parse_threads($process_section,event_type=>$event_type);
1433 | print STDERR "Num stacks found: " . scalar(keys %$threads) . "\n" if $opt_verbose;
1434 | for my $thread (values %$threads) {
1435 | # merge all of the frames from all backtraces into one
1436 | # collection
1437 | my $b = parse_backtrace($thread,$images,0,$is_spindump_report);
1438 | @$bt{keys %$b} = values %$b;
1439 | }
1440 |
1441 | my $exception = parse_section($process_section,'Last Exception Backtrace', multiline=>1);
1442 | if (defined $exception) {
1443 | ($process_section, $exception) = fixup_last_exception_backtrace($process_section, $exception, $images);
1444 | #my $e = parse_last_exception_backtrace($exception, $images, 1);
1445 | my $e = parse_backtrace($exception, $images,1,$is_spindump_report);
1446 |
1447 | # treat these frames in the same was as any thread
1448 | @$bt{keys %$e} = values %$e;
1449 | }
1450 |
1451 | # sort out just the images needed for this backtrace
1452 | prune_used_images($images,$bt);
1453 | if ( $opt_verbose ) {
1454 | print STDERR keys(%$images) . " binary images remain after pruning:\n";
1455 | foreach my $junk (keys(%$images)) {
1456 | print STDERR $junk;
1457 | print STDERR ", ";
1458 | }
1459 | print STDERR "\n";
1460 | }
1461 |
1462 | @extra_search_paths = (@extra_search_paths, getSymbolDirPaths($model, $version, $build));
1463 | fetch_symbolled_binaries($images,$build,$first_bundle,@extra_search_paths);
1464 |
1465 | # If we didn't get *any* symbolled binaries, just print out the original crash log.
1466 | my $imageCount = keys(%$images);
1467 | if ($imageCount == 0) {
1468 | next;
1469 | }
1470 |
1471 | # run atos
1472 | symbolize_frames($images,$bt,$is_spindump_report);
1473 |
1474 | if(keys %$bt) {
1475 | # run our fancy regex
1476 | $process_section = replace_symbolized_frames($process_section,$bt,$images,$is_spindump_report);
1477 |
1478 | $symbolicated_something = 1;
1479 | } else {
1480 | # There were no symbols found, don't change the section
1481 | }
1482 | }
1483 |
1484 | if ($symbolicated_something) {
1485 | if (defined $header) {
1486 | output_log($header);
1487 | }
1488 |
1489 | output_log($_) for @process_sections;
1490 | } else {
1491 | #There were no symbols found
1492 | print STDERR "No symbolic information found\n";
1493 | output_log($log_ref);
1494 | }
1495 |
1496 | }
1497 |
--------------------------------------------------------------------------------
/iOSMonkey.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lemonzhulixin/iOS-monkey/f0a25e7cf10c60a8d53cbd8dc0818226cd17fcdc/iOSMonkey.pptx
--------------------------------------------------------------------------------
/monkey.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF8 -*-
2 | from iOSCrashAnalysis.CrashExport import CrashExport
3 | from iOSCrashAnalysis.getPakeage import getPakeage
4 | from iOSCrashAnalysis import mysql_monkey
5 | from iOSCrashAnalysis.FileOperate import *
6 | from iOSCrashAnalysis.BaseIosPhone import get_ios_devices,get_ios_PhoneInfo
7 | from iOSCrashAnalysis.FileOperate import FileFilt
8 |
9 |
10 |
11 | PATH = lambda p: os.path.abspath(
12 | os.path.join(os.path.dirname(__file__), p)
13 | )
14 |
15 | def monkey(devicename):
16 | cmd_monkey = "xcodebuild -project /Users/iOS_Team/.jenkins/workspace/iOS_Monkey_VivaVideo/XCTestWD/XCTestWD/XCTestWD.xcodeproj " \
17 | "-scheme XCTestWDUITests " \
18 | "-destination 'platform=iOS,name=" + devicename + "' " + \
19 | "XCTESTWD_PORT=8001 " + \
20 | "clean test"
21 |
22 | print(cmd_monkey)
23 | try:
24 | os.system(cmd_monkey)
25 | except Exception as msg:
26 | print('error message:', msg)
27 | raise
28 |
29 | if __name__ == '__main__':
30 | print('获取设备信息')
31 | # dev_list = []
32 | # devices = get_ios_devices()
33 | # for i in range(len(devices)):
34 | # duid = get_ios_devices()[i]
35 | # dev = get_ios_PhoneInfo(duid)
36 | # dev_list.append(dev)
37 | # print(dev_list)
38 |
39 | deviceName = 'iPhone2140'
40 | deviceID = 'e80251f0e66967f51add3ad0cdc389933715c3ed'
41 | release = '9.3.2'
42 |
43 | print('远程复制ipa文件到本地')
44 | start_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
45 | cmd_copy = 'sshpass -p ios scp -r iOS_Team@10.0.35.xxx:/Users/iOS_Team/XiaoYing_AutoBuild/XiaoYing/XiaoYingApp/fastlane/output_ipa/ ~/Desktop'
46 | os.system(cmd_copy)
47 |
48 | print('安装ipa测试包到设备')
49 | path = "/Users/iOS_Team/Desktop/output_ipa/"
50 | file_format = ['.ipa']
51 | ipa_path = getPakeage().get_ipa(path, file_format)
52 | getPakeage().install(path, file_format, deviceID)
53 |
54 | print("启动monkey")
55 | monkey(deviceName)
56 |
57 | print('解析crash report')
58 | find_str = 'XiaoYing-' # 待测app crashreport文件关键字
59 | file_format1 = [".ips"] # 导出的crash文件后缀
60 | file_format2 = [".crash"] # 解析后的crash文件后缀
61 | CrashExport(deviceID, find_str, file_format1, file_format2)
62 | end_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
63 |
64 | print('测试结果数据解析并DB存储')
65 | loacl_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
66 | iOS_tag = 'iOS_' + loacl_time
67 |
68 | print('插入数据到device表')
69 | deviceData = {
70 | 'name': deviceName,
71 | 'serial_number': deviceID,
72 | 'version': release,
73 | 'status': 1,
74 | 'tag': 'iOS'
75 | }
76 |
77 | print('插入数据到apk信息表')
78 | # ipa_path = '/Users/zhulixin/Desktop/output_ipa/day_inke_release_xiaoying.ipa'
79 | ipainfo = getPakeage().getIpaInfo(ipa_path)
80 | apkData = {
81 | 'app_name': ipainfo[0],
82 | 'ver_name': ipainfo[2],
83 | 'ver_code': ipainfo[3],
84 | 'file_name': 'day_inke_release_xiaoying.ipa',
85 | 'file_path': ipa_path,
86 | 'build_time': start_time,
87 | 'tag': iOS_tag
88 | }
89 |
90 | print('插入数据到task表')
91 | taskData = {
92 | 'start_time': start_time,
93 | 'end_time': end_time,
94 | 'app_name': ipainfo[0],
95 | 'devices': 1,
96 | 'test_count': None,
97 | 'pass_count': None,
98 | 'fail_count': None,
99 | 'passing_rate': None,
100 | 'tag': iOS_tag,
101 | 'info': None
102 | }
103 |
104 | print('插入数据到results表')
105 | # f = FileFilt()
106 | # f.FindFile(find_str, file_format1, './CrashInfo/')
107 | # crash_count = len(f.fileList)
108 | # result = 1
109 | # if crash_count:
110 | # result = 0
111 |
112 | resultData = {
113 | 'result_id': start_time + '-monkey-' + ipainfo[0],
114 | 'start_time': start_time,
115 | 'end_time': end_time,
116 | 'device_name': deviceName,
117 | 'apk_id': None,
118 | 'result': None,
119 | 'status': None,
120 | 'CRASHs': None,
121 | 'ANRs': None,
122 | 'tag': iOS_tag,
123 | 'device_log':None,
124 | 'monkey_log': None,
125 | 'monkey_loop': None,
126 | 'cmd':None,
127 | 'seed': None
128 | }
129 |
130 | # print('deviceData:', deviceData)
131 | # mysql_monkey.insert_record_to_phones(deviceData)
132 |
133 | print('apkData:', apkData)
134 | mysql_monkey.insert_record_to_apks(apkData)
135 |
136 | print('taskData:', taskData)
137 | mysql_monkey.insert_record_to_tasks(taskData)
138 |
139 | print('resultData:', resultData)
140 | mysql_monkey.insert_record_to_results(resultData)
141 |
142 | print("压缩测试结果并传")
143 | f = FileFilt()
144 | results_file = f.zip_report(loacl_time, './CrashInfo/', './Results_ZIP/')
145 | url = 'http://10.0.32.xxx:5100/api/v1/iOS-monkey'
146 | files = {'file': open(results_file, 'rb')}
147 | response = requests.post(url, files=files)
148 | json = response.json()
149 |
150 | print("删除本次的测试结果")
151 | f.DelFolder('./CrashInfo/')
152 | print("xxxxxxxxxxxxxxxxxxxxxxxxx Finish Test xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
153 |
--------------------------------------------------------------------------------