def publish_devices_info_event(online_devices, app_name): devices = [] for device_id, device_info in online_devices.items(): message_info = { 'id': device_id, 'info': { 'name': device_info.device_name, 'model': device_info.model, 'os': device_info.os_version, 'sn': device_info.sn }, } devices.append(message_info) try: app_info = device_info.get_app_info(app_name) if app_info.get('AppName'): message_info['app'] = { 'appName': app_info['AppName'], 'version': app_info['VersionNumber'], 'build': app_info['BuildNumber'], 'packageName': app_info['BundleID'] } except Exception: _log.error(f'Read app info error!\n {traceback.format_exc()}') lyrebird.publish('ios.device', devices, state=True)
def publish_devices_package_info(online_devices, package_name): devices_info_list = [] for device_id, device_info in online_devices.items(): device_detail = online_devices[device_id] if device_detail.device_info is None: continue item = { 'id': device_id, 'info': { 'product': device_detail.product, 'model': device_detail.model, 'os': device_detail.get_release_version(), 'ip': device_detail.get_device_ip(), 'resolution': device_detail.get_device_resolution() } } app = device_info.package_info(package_name) if app.version_name: item['app'] = { 'packageName': package_name, 'startActivity': app.launch_activity, 'version': app.version_name } devices_info_list.append(item) lyrebird.publish('android.device', devices_info_list, state=True)
def check_base(self, obj): try: # 检查base schema check_schema(obj) # 检查url是否有重复项存在 redundant_items = check_url_redundant(obj) if redundant_items: redundant_items_str = '\n'.join(redundant_items) logger.error( f'API-Coverage import API file error: Duplicated API\n' f'{len(redundant_items)} duplicated API:\n' f'{redundant_items_str}\n') resp = context.make_fail_response('导入API有重复项' + str(redundant_items)) lyrebird.publish('api_coverage', 'error', name='import_base') return resp # 获取base内容,解析出base的business等字段 filename = obj.get('business') + obj.get( 'version_name') + '.' + str(obj.get('version_code')) app_context.filename = filename app_context.business = obj.get('business') app_context.version_name = obj.get('version_name') app_context.version_code = obj.get('version_code') return except Exception as e: resp = context.make_fail_response(f'导入文件有误: {e}\n请重新import base') return resp
def execute_command(): if request.method == 'POST': _command = request.json.get('command') if not _command: return make_fail_response('Empty command!') _device_id = request.json.get('device_id', '') device = device_service.devices.get(_device_id) if not device: return make_fail_response('Device not found!') res = device.adb_command_executor(_command) output = res.stdout.decode() err_str = res.stderr.decode() publish_channel = 'android.command' publish_message = { 'command': res.args, 'returncode': res.returncode, 'result': err_str if err_str else output } publish(publish_channel, publish_message) if err_str: return make_fail_response(err_str) else: return make_ok_response(data=output)
def device_controller(device_id, action): controller_actions = {'install': _install_package} if request.method == 'PUT': device = device_service.devices.get(device_id) if not device: return make_fail_response(f'Device {device_id} not found!') if not controller_actions.get(action): return make_fail_response(f'Unknown device action: {action}') action_func = controller_actions.get(action) res = action_func(device, request) publish_channel = 'android.' + action publish_message = { 'command': res.args, 'returncode': res.returncode, 'result': res.stderr.decode() if res.stderr.decode() else res.stdout.decode() } publish(publish_channel, publish_message) if res.returncode != 0: return make_fail_response(res.stderr.decode()) else: return make_ok_response()
def test_state_getter(event_server): import lyrebird lyrebird.application.server['event'] = event_server test_state = lyrebird.state.get('Test') assert test_state == None lyrebird.publish('Test', 'TestMessage', state=True) test_state = lyrebird.state.get('Test') assert test_state == 'TestMessage'
def devices(): check_android_home() res = subprocess.run(f'{adb} devices -l', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output = res.stdout.decode() err_str = res.stderr.decode() online_devices = {} # ADB command error if res.returncode != 0: print('Get devices list error', err_str) return online_devices lines = [line for line in output.split('\n') if line] if len(lines) > 1: for line in lines[1:]: device = Device.from_adb_line(line) online_devices[device.device_id] = device devices_list = [on_device for on_device in list(online_devices.keys())] last_devices_str = lyrebird.state.get( 'android.device') if lyrebird.state.get('android.device') else [] last_devices_list = [ last_device.get('id') for last_device in last_devices_str ] if devices_list != last_devices_list: devices_info_list = [] for device_id in online_devices: device_detail = online_devices[device_id] if device_detail.device_info == None: continue item = { 'id': device_id, 'info': { 'product': device_detail.product, 'model': device_detail.model, 'os': device_detail.get_release_version(), 'ip': device_detail.get_device_ip(), 'resolution': device_detail.get_device_resolution() } } package_name = config.load().package_name app = device.package_info(package_name) if app.version_name: item['app'] = { 'packageName': package_name, 'startActivity': app.launch_activity, 'version': app.version_name } devices_info_list.append(item) lyrebird.publish('android.device', devices_info_list, state=True) return online_devices
def set_filter_conf(self): filter_data = request.form.get('filter_data') try: resp = FilterHandler().save_filer_conf(json.loads(filter_data)) lyrebird.publish('api_coverage', 'operation', name='set_filter') except Exception as e: lyrebird.publish('api_coverage', 'error', name='set_filter') return context.make_fail_response("传入的非json文件" + str(repr(e))) return resp
def take_screenshot(platform, device_id): """ channel: {platform}.cmd """ channel = platform + '.cmd' cmd = {} cmd['cmd'] = 'screenshot' cmd['device_id'] = [device_id] lyrebird.publish(channel.lower(), cmd) return application.make_ok_response(message=f'Take screenshot success!')
def clear_result(self): ResultHandler().clear_cache_result() # 获取基准文件 base_dict = BaseDataHandler().get_base_source() # 初始化正常会进行数据的处理:覆盖率初始化 & API LIST初始化 if not isinstance(base_dict, Response): mergeAlgorithm.first_result_handler(base_dict) mergeAlgorithm.coverage_arithmetic(base_dict) lyrebird.publish('api_coverage', 'operation', name='clear_result') return context.make_ok_response()
def devices(): check_android_home() res = subprocess.run(f'{adb} devices -l', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output = res.stdout.decode() err_str = res.stderr.decode() # ADB command error if len(output) <= 0 < len(err_str): print('Get devices list error', err_str) return [] lines = [line for line in output.split('\n') if line] # online_devices contains information for plugin own online_devices = {} # devices_info contains information for bugit devices_info = [] # no device connected if len(lines) > 1: for line in lines[1:]: device = Device.from_adb_line(line) online_devices[device.device_id] = device for device_id in online_devices: device_detail = online_devices[device_id] item = {} item['id'] = device_id item['info'] = { 'product': device_detail.product, 'model': device_detail.model } if device_detail.device_info == None: continue for line in device_detail.device_info: if 'ro.build.version.release' in line: item['info']['os'] = line[line.rfind('[') + 1:line.rfind(']')].strip() break devices_info.append(item) last_devices_info = lyrebird.state.get('android.device') if last_devices_info: last_devices_list = [last_device.get('id') for last_device in last_devices_info] else: last_devices_list = [] if devices_info: devices_list = [on_device.get('id') for on_device in devices_info] else: devices_list = [] if devices_list != last_devices_list: lyrebird.publish('android.device', devices_info, state=True) return online_devices
def get_screen_shot(self, msg): screen_shots = [] for item in device_service.devices: device = device_service.devices[item] screen_shot_path = device.take_screen_shot() screen_shots.append({ 'id': item, 'screenshot': { 'name': os.path.basename(screen_shot_path), 'path': screen_shot_path } }) lyrebird.publish('ios.screenshot', screen_shots, state=True)
def get_screen_shot(self): screen_shots = [] for item in device_service.devices: device = device_service.devices[item] self.take_screen_shot(item) screen_shots.append({ 'id': item, 'screenshot': { 'name': device.model.replace(' ', '_'), 'path': self.get_screenshot_image(item) } }) lyrebird.publish('ios.screenshot', screen_shots, state=True)
def get_screenshots(self, message): screenshot_list = [] for device_id in device_service.devices: device_detail = device_service.devices[device_id] screenshot_path = device_detail.take_screen_shot() item = {} item['id'] = device_id item['screenshot'] = { 'name': os.path.basename(screenshot_path), 'path': screenshot_path } screenshot_list.append(item) lyrebird.publish('android.screenshot', screenshot_list)
def save_filer_conf(self, conf_obj): self.init_filter_conf() try: check_filter_schema(conf_obj) f = codecs.open(FILTER_CONF, 'w', 'utf-8') f.write(json.dumps(conf_obj)) f.close() # 配置保存后当时生效 app_context.filter_dic = conf_obj return context.make_ok_response() except Exception as e: msg = '过滤请求的配置文件格式有误:' + e.__getattribute__('message') lyrebird.publish('api_coverage', 'error', name='set_filter') return context.make_fail_response(msg)
def adb_command_executor(self, command): command = command.strip() lyrebird.publish('android.command', command) isAdbCommand = command.startswith('adb ') if isAdbCommand: command_adb, command_options = command.split(' ', 1) command = f'{command_adb} -s {self.device_id} {command_options}' p = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return p
def coverage_handler(self): """ 总体覆盖率 """ # 获取handle前的历史覆盖率为做对比用 history_coverage = app_context.coverage['total'] test_len = len( list(filter(lambda x: x.get('status') == 1, app_context.merge_list))) if app_context.coverage['len'] == 0: coverage = 0 else: coverage = round(test_len / app_context.coverage['len'] * 100, 2) # 为了传给Overbridge的socket信息format数据格式 app_context.coverage['total'] = coverage # 覆盖率有变化才emit & publish 覆盖率的变化消息给API-Coverage前端,overbridge前端,和消息总线 if not history_coverage == coverage: handler_time = time.time() # 限制频繁emit io msg,在两次之间大于指定时间间隔才会emit if handler_time - app_context.covtime > app_context.SOCKET_PUSH_INTERVAL: lyrebird.emit('coverage message', app_context.coverage.get('total'), namespace='/api_coverage') app_context.covtime = handler_time by_priority = [ p.get('value') for p in app_context.coverage['priorities'] ] lyrebird.publish( 'coverage', dict(name='coverage', value=app_context.coverage.get('total'), by_priority=by_priority)) app_context.coverage['test_len'] = test_len # 各优先级对应覆盖率 for item_dic in app_context.coverage.get('priorities'): item_length = item_dic.get('len') test_item_length = len( list( filter( lambda x: x.get('priority') == item_dic.get('label') and x.get('status') == 1, app_context.merge_list))) if item_length == 0: coverage = 0 else: coverage = round(test_item_length / item_length * 100, 2) item_dic['value'] = coverage item_dic['test_len'] = test_item_length
def crash_checker(self, line): crash_log_path = os.path.join(crash_dir, 'android_crash_%s.log' % self.device_id) if str(line).find('FATAL EXCEPTION') > 0: self.start_catch_log = True self._log_crash_cache.append(str(line)) lyrebird.publish('crash', 'android', path=crash_log_path, id=self.device_id) elif str(line).find('AndroidRuntime') > 0 and self.start_catch_log: self._log_crash_cache.append(str(line)) else: self.start_catch_log = False with codecs.open(crash_log_path, 'w') as f: f.write('\n'.join(self._log_crash_cache))
def anr_checker(self, line): if ('ANR' not in line) or ('ActivityManager' not in line): return anr_package = line.strip().split()[-2] re_str = "^([a-z_]{1}[a-z0-9_]*(\.[a-z_]{1}[a-z0-9_]*)*)$" # Check pkg name if re.match(re_str, anr_package) is None: return anr_file_name = os.path.join( anr_dir, f'android_anr_{self.device_id}_{anr_package}.log') p = subprocess.run( f'{adb} -s {self.device_id} pull "/data/anr/traces.txt" {anr_file_name}', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if p.returncode != 0: logger.error('Catch ANR log error!\n' + p.stderr.decode()) return # check whether pid of the anr_package exists or not with codecs.open(anr_file_name, 'r', 'utf-8') as f: anr_pid_line = f.readline() # expected anr_pid_line: ----- pid 21335 at 2019-06-24 16:21:15 ----- while 'pid' not in anr_pid_line: anr_pid_line = f.readline() _anr_pid = anr_pid_line.strip().split()[2] anr_package = self.get_package_from_pid(_anr_pid) target_package_name = config.load().package_name if anr_package == target_package_name: with codecs.open(anr_file_name, 'r', 'utf-8') as f: log_anr_cache = f.readlines() anr_info = { 'device_id': self.device_id, 'log': log_anr_cache, 'log_file_path': anr_file_name } lyrebird.publish('android.crash', anr_info) title = f'Application {anr_package} not responding on Android device {self.device_id}!\n' desc = title + 'ANR log:\n\n' + ''.join(log_anr_cache) lyrebird.event.issue(title, desc)
def pubilsh_error_msg(msg): """ 将错误信息通过消息总线发送出去,订阅tracking频道的其他插件会监听到 :param msg: 错误信息详情 """ app_context.error_list.append(msg) lyrebird.publish('tracking.error', msg) lyrebird.publish('tracking.error', msg, state=True) lyrebird.publish('tracking.error_list', app_context.error_list) lyrebird.publish('tracking.error_list', app_context.error_list, state=True)
def get_screenshots(message): if message.get('cmd') != 'screenshot': return screenshot_list = [] device_list = message.get('device_id') for device_id in device_list: device_detail = device_service.devices.get(device_id) if not device_detail: continue screenshot_detail = device_detail.take_screen_shot() screenshot_list.append({ 'id': device_id, 'screenshot': { 'name': os.path.basename(screenshot_detail.get('screen_shot_file')), 'path': screenshot_detail.get('screen_shot_file') } }) publish('android.screenshot', screenshot_list)
def get_screen_shot(message): if message.get('cmd') != 'screenshot': return screen_shots = [] device_list = message.get('device_id') for device_id in device_list: device = device_service.devices.get(device_id) if not device: continue screen_shot_info = device.take_screen_shot() screen_shots.append({ 'id': device_id, 'screenshot': { 'name': os.path.basename(screen_shot_info.get('screen_shot_file')), 'path': screen_shot_info.get('screen_shot_file') } }) lyrebird.publish('ios.screenshot', screen_shots, state=True)
def import_result_handler(self): json_file = request.files.get('json-import') mimetype = json_file.content_type # 读取文件流,注意文件流只能read一次 bytes_obj = json_file.read() try: result_obj = json.loads(bytes_obj) # 获取import文件的sha1 import_sha1 = result_obj.get('base_sha1') if app_context.base_sha1 == import_sha1: # merge import result and cache result # check_result_schema(result_obj) app_context.coverage = json.loads(bytes_obj).get('coverage') mergeAlgorithm.merge_resume(result_obj.get('test_data')) # 放入缓存 # app_context.merge_list = json.loads(bytes_obj).get('test_data') # app_context.coverage = json.loads(bytes_obj).get('coverage') resp = context.make_ok_response() lyrebird.publish('api_coverage', 'operation', name='import_result') else: resp = context.make_fail_response('导入的测试结果和之前选择base不匹配') lyrebird.publish('api_coverage', 'error', name='import_result') except Exception as e: resp = context.make_fail_response('导入文件内容格式有误:' + str(e)) lyrebird.publish('api_coverage', 'error', name='import_result') return resp
def application_controller(device_id, package_name, action): controller_actions = { 'uninstall': _uninstall_package, 'clear': _clear_package, 'stop': _stop_package, 'start': _start_package } if request.method == 'PUT': device = device_service.devices.get(device_id) if not device: return make_fail_response(f'Device {device_id} not found!') package = device.package_info(package_name) if not package: return make_fail_response(f'Application {package_name} not found!') if not controller_actions.get(action): return make_fail_response(f'Unknown application action: {action}') action_func = controller_actions.get(action) res = action_func(device, package, request) publish_channel = 'android.' + action publish_message = { 'command': res.args, 'returncode': res.returncode, 'result': res.stderr.decode() if res.stderr.decode() else res.stdout.decode() } publish(publish_channel, publish_message) if res.returncode != 0: return make_fail_response(res.stderr.decode()) # When adb uninstall <package> fail, the returncode is 0, while the output string contains `Failure` elif 'Failure' in res.stdout.decode(): return make_fail_response(res.stdout.decode()) else: return make_ok_response()
def crash_checker(self, line): if line.find('FATAL EXCEPTION') > 0: self.start_catch_log = True _crashed_pid = [_ for _ in line.strip().split()][2] self._crashed_package = self.get_package_from_pid(_crashed_pid) self._log_crash_cache.append(line) elif line.find('AndroidRuntime') > 0 and self.start_catch_log: self._log_crash_cache.append(line) elif self.start_catch_log: _crash_file = os.path.abspath( os.path.join( crash_dir, f'android_crash_{self.device_id}_{self._crashed_package}.log' )) with codecs.open(_crash_file, 'w', 'utf-8') as f: f.write(''.join(self._log_crash_cache)) target_package_name = config.load().package_name if self._crashed_package == target_package_name: crash_info = { 'device_id': self.device_id, 'log': self._log_crash_cache, 'log_file_path': _crash_file } lyrebird.publish('android.crash', crash_info) title = f'Android device {self.device_id} crashed!\n' desc = title + 'Crash log:\n\n' + ''.join( self._log_crash_cache) lyrebird.event.issue(title, desc) self.start_catch_log = False self._log_crash_cache = [] else: return
def import_base_handler(self): json_file = request.files.get('json-import') mimetype = json_file.content_type # 判断是不是json格式的文件 if mimetype == 'application/json': # 读取文件流,注意文件流只能read一次 bytes_obj = json_file.read() try: check_result = BaseDataHandler().check_base(json.loads(bytes_obj)) if check_result: return check_result self.write_wb(DEFAULT_BASE, bytes_obj) # 读取json文件 json_obj = json.loads(codecs.open(DEFAULT_BASE, 'r', 'utf-8').read()) # 获取文件的sha1 app_context.base_sha1 = self.get_sha1(bytes_obj) # 初次处理,切换后的result mergeAlgorithm.first_result_handler(json_obj) mergeAlgorithm.coverage_arithmetic(json_obj) resp = context.make_ok_response() lyrebird.publish('api_coverage', 'operation', name='import_base') except Exception as e: resp = context.make_fail_response('导入文件内容格式有误:' + str(e)) lyrebird.publish('api_coverage', 'error', name='import_base') else: resp = context.make_fail_response("Error.The selected non - JSON file.") lyrebird.publish('api_coverage', 'error', name='import_base') return resp
def publish_devices_info_event(self, online_devices): devices = [] for item in online_devices: info = online_devices[item] app = online_devices[item].get_app_info( self.get_default_app_name()) devices.append({ 'id': info.device_id, 'info': { 'name': info.device_name, 'model': info.model, 'os': info.os_version, 'sn': info.sn }, 'app': { 'name': app['AppName'], 'version': app['VersionNumber'], 'build': app['BuildNumber'], 'bundleID': app['BundleID'] } }) lyrebird.publish('ios.device', devices, state=True)
def check_base(self, obj): try: # 检查base schema check_schema(obj) # 检查url是否有重复项存在 redundant_items = check_url_redundant(obj) if redundant_items: resp = context.make_fail_response('导入API有重复项' + str(redundant_items)) lyrebird.publish('api_coverage', 'error', name='import_base') return resp # 获取base内容,解析出base的business等字段 filename = obj.get('business') + obj.get('version_name') + '.' + str(obj.get('version_code')) app_context.filename = filename app_context.business = obj.get('business') app_context.version_name = obj.get('version_name') app_context.version_code = obj.get('version_code') return except Exception as e: resp = context.make_fail_response( '导入文件有误:' + '\n' + e.__getattribute__('message') + '\n' + '!请重新import base') return resp
def publish_devices_info_event(self, online_devices, app_name): devices = [] for item in online_devices: device_info = online_devices[item] app_info = online_devices[item].get_app_info(app_name) message_info = { 'id': device_info.device_id, 'info': { 'name': device_info.device_name, 'model': device_info.model, 'os': device_info.os_version, 'sn': device_info.sn }, } if app_info.get('AppName'): message_info['app'] = { 'name': app_info['AppName'], 'version': app_info['VersionNumber'], 'build': app_info['BuildNumber'], 'bundleID': app_info['BundleID'] } devices.append(message_info) lyrebird.publish('ios.device', devices, state=True)
def save_result(self): # 传入文件名 filename = request.form.get('result_name') ResultHandler().save_result(filename) lyrebird.publish('api_coverage', 'operation', name='save_result') return context.make_ok_response()