コード例 #1
0
class TestRunner(object):
    Resolver.configure('tornado.netutil.ThreadedResolver')

    def __init__(self, pid=0, jid=0):
        self.default_headers = {
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'User-Agent':
            'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
            'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4',
            'Cookie': ''
        }
        self.headers = dict()
        self.setting = SettingModule()
        self.common_func = CommonFunction()
        self.option_func = OptionsFunction()
        self.pid = pid
        self.jid = jid

    # 获取请求头
    def get_headers(self, headers=''):
        flag, headers = self.common_func.convert_to_list_or_dict(
            string=headers, s_type='dict')
        self.headers = dict()
        for key in self.default_headers:
            self.headers[key] = self.default_headers[key]
        if flag:
            for key in headers.keys():
                self.headers[key] = headers[key]
        else:
            headers = headers.splitlines()
            for header in headers:
                header = header.strip().split(sep=':', maxsplit=1)
                if len(header) == 2:
                    name = header[0].strip()
                    value = header[1].strip()
                    self.headers[name] = value
        return self.headers

    # 加解密操作
    @gen.coroutine
    def __do_crypt(self, do='encrypt', body=None, crypt_key=''):
        crypt_info = yield self.option_func.get_crypt_info(pid=self.pid, do=do)
        if not crypt_info:
            return body
        func = crypt_info.function
        try:
            if func:
                if crypt_key:
                    flag, body = self.common_func.convert_to_list_or_dict(
                        string=body, s_type='dict')
                    if not flag and isinstance(body, str) and re.match(
                            r'^.*=.*(&.*=.*)*$', body) is not None:
                        body = self.common_func.url_query_decode(body)
                        if isinstance(body, dict):
                            flag = True
                    if flag:
                        source = body[crypt_key]
                        if not isinstance(source, str):
                            try:
                                source = json.dumps(source, ensure_ascii=False)
                            except Exception as e:
                                log.warning(e)
                                if isinstance(source, bytes):
                                    source = source.decode('utf8',
                                                           errors='ignore')
                                else:
                                    source = str(source)
                        body[crypt_key] = func(source, crypt_info['key'],
                                               crypt_info['iv'],
                                               crypt_info['mode'])
                else:
                    if not isinstance(body, str):
                        try:
                            body = json.dumps(body, ensure_ascii=False)
                        except Exception as e:
                            log.warning(e)
                            if isinstance(body, bytes):
                                body = body.decode('utf8', errors='ignore')
                            else:
                                body = str(body)
                    body = func(body, crypt_info['key'], crypt_info['iv'],
                                crypt_info['mode'])
        except Exception as e:
            log.warning(e)
        return body

    # 尝试将请求数据转换成字典
    def __parse_body_arguments(self, body):
        flag, body = self.common_func.convert_to_list_or_dict(string=body,
                                                              s_type='dict')
        if not flag and isinstance(body, str) and re.match(
                r'^.*=.*(&.*=.*)*$', body) is not None:
            body = self.common_func.url_query_decode(body)
            if isinstance(body, dict):
                flag = True
        if not flag:
            self.headers = self.headers if len(
                self.headers) != 0 else self.default_headers
            try:
                request_body = dict()
                if re.match(r'^.*=.*(&.*=.*)*$', body) is not None:
                    parse_body_arguments(
                        content_type=self.headers['Content-Type'],
                        body=body,
                        arguments=request_body,
                        files=request_body,
                        headers=self.headers)
                if len(request_body) > 0:
                    body = request_body
                for key in body:
                    if isinstance(body[key], list):
                        body[key] = body[key][0]
                        body[key] = body[key].decode('utf8', errors='ignore')
                flag = True
            except Exception as e:
                log.warning(e)
                flag = False
        return flag, body

    # 获取请求响应数据
    @gen.coroutine
    def __get_body(self, body='', do='encrypt', name='', crypt_key=''):
        try:
            body = ast.literal_eval(body)
        except Exception as e:
            log.warning(e)
            if do == 'encrypt' and crypt_key != '':
                flag, body = self.__parse_body_arguments(body)
        if isinstance(body, dict):
            if name != 'none':
                if do == 'encrypt' and crypt_key == '':
                    body = urlencode(body, encoding='utf8', quote_via=quote)
                body = yield self.__do_crypt(do=do,
                                             body=body,
                                             crypt_key=crypt_key)
            if isinstance(body, dict):
                if do == 'encrypt' and self.headers['Content-Type'].find(
                        'x-www-form-urlencoded') != -1:
                    body = urlencode(body, encoding='utf8', quote_via=quote)
                else:
                    body = json.dumps(body, ensure_ascii=False)
        else:
            if name != 'none':
                body = yield self.__do_crypt(do=do, body=body, crypt_key='')
            if isinstance(body, dict):
                body = json.dumps(body, ensure_ascii=False)
        return body

    # 解析Host配置
    @gen.coroutine
    def __parse_host(self, url='', env='none'):
        urls = self.common_func.url_split(url=url)
        host = urls.host
        ips, total = yield self.setting.get_settings_list(pid=self.pid,
                                                          s_type='host',
                                                          name=host,
                                                          pj_status=1,
                                                          limit=None)
        for row in ips:
            if env != 'none':
                url = '{}://{}:{}{}'.format(urls.scheme, env, urls.port,
                                            urls.path)
                break
            elif row.status == 1:
                url = '{}://{}:{}{}'.format(urls.scheme, row.value, urls.port,
                                            urls.path)
                break
        self.headers['Host'] = urls.netloc
        return url

    # 解析接口返回值全字段检查配置
    def __parse_check_key(self, check_key):
        keys = []
        top = []
        rex = re.compile(
            r'^\[\w+=\d\|(int|float|num|str|/.*/|date|time|datetime|list|dict)('
            r',\w+=\d\|(int|float|num|str|/.*/|date|time|datetime|list|dict))*\]$'
        )
        for row in check_key.splitlines():
            row = row.strip().split(sep='.', maxsplit=1)
            if len(row) == 2:
                if re.match(rex, row[1]) is not None:
                    deeps = [row[1]]
                else:
                    deeps = row[1].split(sep='.', maxsplit=1)
                if re.match(r'^\[\d+\]$', deeps[0]) is None:
                    top.append('{}=1|dict'.format(row[0]))
                else:
                    top.append('{}=1|list'.format(row[0]))
                deep = '{}.'.format(row[0])
                tmp_key = []
                for i in range(len(row[1].split('.'))):
                    if len(deeps) == 2 and re.match(rex, deeps[1]) is not None:
                        deep += '{}.'.format(deeps[0])
                        for j in deeps[1][1:-1].split(','):
                            tmp_key.append(j)
                        break
                    elif re.match(rex, deeps[0]) is not None:
                        for j in deeps[0][1:-1].split(','):
                            tmp_key.append(j)
                        break
                    deep += '{}.'.format(deeps[0])
                    if len(deeps) == 2:
                        if re.match(rex, deeps[1]) is not None:
                            deeps = deeps[1]
                        else:
                            deeps = deeps[1].split(sep='.', maxsplit=1)
                keys.append(dict(deep=deep[:-1], keys=tmp_key, result=dict()))
            else:
                top.append(row[0])
        keys.append(dict(deep='top', keys=list(set(top)), result=dict()))
        return keys

    # 返回值全字段检查结果判断
    def __check_key_result(self, body, check_key, key, k):
        check_key[k]['result'][key[0]] = True
        key[1] = key[1].split(sep='|', maxsplit=1)
        require = key[1][0]
        key_type = key[1][1]
        if isinstance(body[key[0]], str):
            body[key[0]] = body[key[0]].strip()
        if require == '1' and (body[key[0]] == '' or body[key[0]] is None):
            check_key[k]['result'][key[0]] = False
        elif body[key[0]] != '' and body[key[0]] is not None:
            if re.match(r'^/.*/$', key_type):
                rex = re.compile(key_type[1:-1])
                if re.match(rex, body[key[0]]) is None:
                    check_key[k]['result'][key[0]] = False
            elif key_type == 'int' and not isinstance(body[key[0]], int):
                check_key[k]['result'][key[0]] = False
            elif key_type == 'float' and not isinstance(body[key[0]], float):
                check_key[k]['result'][key[0]] = False
            elif key_type == 'num' and not isinstance(
                    body[key[0]], int) and not isinstance(body[key[0]], float):
                check_key[k]['result'][key[0]] = False
            elif key_type == 'str' and not isinstance(body[key[0]], str):
                check_key[k]['result'][key[0]] = False
            elif key_type == 'list' and not isinstance(body[key[0]], list):
                check_key[k]['result'][key[0]] = False
            elif key_type == 'dict' and not isinstance(body[key[0]], dict):
                check_key[k]['result'][key[0]] = False
            elif key_type == 'date' and re.match(r'^\d{4}-\d{2}-\d{2}$',
                                                 body[key[0]]) is None:
                check_key[k]['result'][key[0]] = False
            elif key_type == 'time' and re.match(r'^\d{2}:\d{2}:\d{2}$',
                                                 body[key[0]]) is None:
                check_key[k]['result'][key[0]] = False
            elif key_type == 'datetime' and re.match(
                    r'^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$',
                    body[key[0]]) is None:
                check_key[k]['result'][key[0]] = False
        if not check_key[k]['result'][key[0]]:
            check_key[k]['result']['key_result'] = False
        return body, check_key

    # 解析响应内容
    @gen.coroutine
    def __parse_response(self,
                         response,
                         name='',
                         crypt_key='',
                         checkpoint='',
                         check_key='',
                         correlation='',
                         method='GET',
                         url=''):
        body = response.body if response else ''
        request_body = response.request.body if response else ''
        if isinstance(body, bytes):
            body = body.decode('utf8', errors='ignore')
        if isinstance(request_body, bytes):
            request_body = request_body.decode('utf8', errors='ignore')
        headers_dict = dict(response.headers if response else '')
        headers = ''
        for key in headers_dict:
            headers += '{}: {}\r\n'.format(key, headers_dict[key])
        request_headers_dict = dict(
            response.request.headers if response else '')
        request_headers = ''
        for key in request_headers_dict:
            request_headers += '{}: {}\r\n'.format(key,
                                                   request_headers_dict[key])
        if response:
            error = response.error if not response.error else str(
                response.error)
        else:
            error = str(httpclient.HTTPError(599, 'Timeout while connecting'))
        resp = dict(
            body=body,
            code=response.code if response else 599,
            effective_url=response.effective_url if response else '',
            error=error,
            headers=headers,
            request_headers=request_headers,
            request_body=request_body,
            reason=response.reason if response else 'Timeout while connecting',
            request_time=response.request_time if response else '',
            time_info=response.time_info if response else '',
            method=method,
            url=url)
        resp['body_decrypt'] = yield self.__get_body(body,
                                                     do='decrypt',
                                                     name=name,
                                                     crypt_key=crypt_key)
        resp['checkpoint'] = []
        if checkpoint != '':
            if re.match(r'^/.*/$', checkpoint) is not None:
                rex = re.compile(checkpoint[1:-1])
                if re.findall(rex, resp['body_decrypt']):
                    resp['checkpoint'].append(
                        dict(result=True, checkpoint=checkpoint))
                else:
                    log.warning('检查点 {} 检查不通过'.format(checkpoint))
                    resp['checkpoint'].append(
                        dict(result=False, checkpoint=checkpoint))
            else:
                for check in checkpoint.split('|'):
                    check = check.strip()
                    if resp['body_decrypt'].find(check) != -1:
                        resp['checkpoint'].append(
                            dict(result=True, checkpoint=check))
                    else:
                        log.warning('检查点 {} 检查不通过'.format(check))
                        resp['checkpoint'].append(
                            dict(result=False, checkpoint=check))
        resp['check_key'] = []
        if check_key != '':
            check_key = self.__parse_check_key(check_key)
            for k in range(len(check_key)):
                body = resp['body_decrypt']
                check_key[k]['result']['key_result'] = True
                if check_key[k]['deep'] == 'top':
                    for key in check_key[k]['keys']:
                        if re.match(r'^\[\d+\]$', key) is not None:
                            key = int(key[1:-1])
                            flag, body = self.common_func.convert_to_list_or_dict(
                                body, 'list')
                            if not flag:
                                log.warning('返回值全字段检查 被检查数据格式不是List, 无法继续')
                                if body in ['', '[]', '{}', [], {}]:
                                    check_key[k]['result']['ERROR'] = '被检查数据为空'
                                else:
                                    check_key[k]['result'][
                                        'ERROR'] = '被检查数据类型不是List'
                                check_key[k]['result']['key_result'] = False
                                continue
                            try:
                                body[key]
                            except Exception as e:
                                log.warning(e)
                                check_key[k]['result'][
                                    'ERROR'] = '被检查数据格式不包含List类型'
                                check_key[k]['result']['key_result'] = False
                                continue
                        else:
                            flag, body = self.common_func.convert_to_list_or_dict(
                                body, 'dict')
                            if not flag and isinstance(body, str) and re.match(
                                    r'^.*=.*(&.*=.*)*$', body) is not None:
                                body = self.common_func.url_query_decode(body)
                                if isinstance(body, dict):
                                    flag = True
                            if not flag:
                                log.warning('返回值全字段检查 被检查数据格式不是Dict, 无法继续')
                                if body in ['', '[]', '{}', [], {}]:
                                    check_key[k]['result']['ERROR'] = '被检查数据为空'
                                else:
                                    check_key[k]['result'][
                                        'ERROR'] = '被检查数据类型不是Dict'
                                check_key[k]['result']['key_result'] = False
                                continue
                            key = key.split(sep='=', maxsplit=1)
                            if key[0] not in body.keys():
                                log.warning('返回值全字段检查 {} 检查不通过'.format(
                                    check_key[k]['keys']))
                                check_key[k]['result'][key[0]] = '字段不存在'
                                check_key[k]['result']['key_result'] = False
                                continue
                            else:
                                body, check_key = self.__check_key_result(
                                    body=body,
                                    check_key=check_key,
                                    key=key,
                                    k=k)
                else:
                    keys = check_key[k]['keys']
                    flag, keys = self.common_func.convert_to_list_or_dict(
                        keys, 'list')
                    for key in check_key[k]['deep'].split('.'):
                        if re.match(r'^\[\d+\]$', key) is not None:
                            key = int(key[1:-1])
                            flag, body = self.common_func.convert_to_list_or_dict(
                                body, 'list')
                            if not flag:
                                log.warning('返回值全字段检查 被检查数据格式不是List, 无法继续')
                                if body in ['', '[]', '{}', [], {}]:
                                    check_key[k]['result']['ERROR'] = '被检查数据为空'
                                else:
                                    check_key[k]['result'][
                                        'ERROR'] = '被检查数据类型不是List'
                                check_key[k]['result']['key_result'] = False
                                continue
                        elif not isinstance(body, dict):
                            flag, body = self.common_func.convert_to_list_or_dict(
                                body, 'dict')
                            if not flag and isinstance(body, str) and re.match(
                                    r'^.*=.*(&.*=.*)*$', body) is not None:
                                body = self.common_func.url_query_decode(body)
                                if isinstance(body, dict):
                                    flag = True
                            if not flag:
                                log.warning('返回值全字段检查 被检查数据格式不是Dict, 无法继续')
                                if body in ['', '[]', '{}', [], {}]:
                                    check_key[k]['result']['ERROR'] = '被检查数据为空'
                                else:
                                    check_key[k]['result'][
                                        'ERROR'] = '被检查数据类型不是Dict'
                                check_key[k]['result']['key_result'] = False
                                continue
                        try:
                            body = body[key]
                        except Exception as e:
                            log.warning(e)
                            check_key[k]['result'][
                                'ERROR'] = '被检查数据格式不包含List类型'
                            check_key[k]['result']['key_result'] = False
                            continue
                    flag, body = self.common_func.convert_to_list_or_dict(
                        body, 'dict')
                    if not flag and isinstance(body, str) and re.match(
                            r'^.*=.*(&.*=.*)*$', body) is not None:
                        body = self.common_func.url_query_decode(body)
                        if isinstance(body, dict):
                            flag = True
                    if not flag:
                        log.warning('返回值全字段检查 被检查数据格式不是Dict, 无法继续')
                        if body in ['', '[]', '{}', [], {}]:
                            check_key[k]['result']['ERROR'] = '被检查数据为空'
                        else:
                            check_key[k]['result']['ERROR'] = '被检查数据类型不是Dict'
                        check_key[k]['result']['key_result'] = False
                        continue
                    for key in keys:
                        key = key.split(sep='=', maxsplit=1)
                        if key[0] not in body.keys():
                            log.warning('返回值全字段检查 {} 检查不通过'.format(
                                check_key[k]['keys']))
                            check_key[k]['result'][key[0]] = '字段不存在'
                            check_key[k]['result']['key_result'] = False
                            continue
                        else:
                            body, check_key = self.__check_key_result(
                                body=body, check_key=check_key, key=key, k=k)
            resp['check_key'] = check_key
        resp['correlation'] = dict()
        if response and correlation != '':
            correlations = correlation.split('|')
            correlation = dict()
            for corr in correlations:
                body = resp['body_decrypt']
                cor = corr.split(sep='=', maxsplit=1)
                key = cor[0].strip()
                c_type = 'string'
                words = cor[1].strip()
                if re.match(r'^int\(.+\)$', words) is not None:
                    c_type = 1
                    words = words[4:-1]
                elif re.match(r'^float\(.+\)$', words) is not None:
                    c_type = 1.00
                    words = words[6:-1]
                word = words.split('.')
                correlation[key] = word
                for k in word:
                    if k == 'response_headers' and len(word) != 1:
                        header = words.split(sep='.', maxsplit=1)
                        if len(header) == 2:
                            header_key = header[1]
                            if re.match(r'^/.*/$', header_key) is not None:
                                rex = re.compile(header_key[1:-1])
                                if isinstance(resp['headers'], bytes):
                                    resp['headers'] = resp['headers'].decode(
                                        'utf8', errors='ignore')
                                result = re.findall(rex, resp['headers'])
                                if result:
                                    if isinstance(result[0], tuple):
                                        body = result[0][0]
                                    else:
                                        body = ''
                                        for row in result:
                                            row = row if row != '' else '\n'
                                            body += row
                                else:
                                    body = ''
                            else:
                                body = response.headers.get(header_key)
                            break
                    if k == 'response_body' and len(word) == 1:
                        if isinstance(resp['body'], bytes):
                            resp['body'] = resp['body'].decode('utf8',
                                                               errors='ignore')
                        body = escape.xhtml_escape(resp['body'])
                        break
                    if re.match(r'^/.*/$', words) is not None:
                        rex = re.compile(words[1:-1])
                        if isinstance(resp['body'], bytes):
                            resp['body'] = resp['body'].decode('utf8',
                                                               errors='ignore')
                        result = re.findall(rex, resp['body'])
                        if result:
                            if isinstance(result[0], tuple):
                                body = escape.xhtml_escape(result[0][0])
                            else:
                                body = escape.xhtml_escape(result[0])
                        else:
                            body = ''
                        break
                    if re.match(r'^\[\d+\]$', k) is not None:
                        k = int(k[1:-1])
                        flag, body = self.common_func.convert_to_list_or_dict(
                            body, 'list')
                        if not flag:
                            log.warning('响应数据格式不是List, 无法继续')
                            body = ''
                            break
                    elif not isinstance(body, dict):
                        flag, body = self.common_func.convert_to_list_or_dict(
                            body, 'dict')
                        if not flag and isinstance(body, str) and re.match(
                                r'^.*=.*(&.*=.*)*$', body) is not None:
                            body = self.common_func.url_query_decode(body)
                            if isinstance(body, dict):
                                flag = True
                        if not flag:
                            log.warning('响应数据格式不是Dict, 无法继续')
                            body = ''
                            break
                    try:
                        body = body[k]
                    except Exception as e:
                        log.warning(e)
                        body = ''
                        break
                correlation[key] = body
                try:
                    if isinstance(c_type, int):
                        correlation[key] = int(
                            float(re.sub(r'[^\d+\.]', '', body)))
                    elif isinstance(c_type, float):
                        correlation[key] = float(re.sub(r'[^\d+\.]', '', body))
                except Exception as e:
                    log.warning(e)
            resp['correlation'] = correlation
        flag = True
        if resp['error'] is not None and resp['code'] != 302 and resp[
                'code'] != 301:
            flag = False
        if resp['check_key']:
            for line in resp['check_key']:
                if not line['result']['key_result']:
                    flag = False
                    break
        if resp['checkpoint']:
            for line in resp['checkpoint']:
                if not line['result']:
                    flag = False
                    break
        resp['test_result'] = flag
        return resp

    # 解析自定义参数配置
    @gen.coroutine
    def __parse_custom_param(self, headers, body, correlation_result={}):
        correlation_result = dict(self.common_func.default_param(),
                                  **correlation_result)
        params = yield self.option_func.get_custom_param(
            pid=self.pid, correlation=correlation_result)
        if isinstance(headers, bytes):
            headers = headers.decode('utf8', errors='ignore')
        if isinstance(body, bytes):
            body = body.decode('utf8', errors='ignore')
        for key in correlation_result:
            if not isinstance(correlation_result[key], str):
                correlation_result[key] = str(correlation_result[key])
            if headers.find(key) != -1:
                headers = headers.replace(key, correlation_result[key])
            if body.find(key) != -1:
                body = body.replace(key, correlation_result[key])
        for param in params:
            if headers.find('{%s}' % param['name']) != -1:
                if param['type'] == 'Function':
                    func = param['function']
                    flag, body_dict = self.__parse_body_arguments(body)
                    encrypt = yield self.option_func.get_crypt_info(self.pid)
                    headers = headers.replace('{%s}' % param['name'],
                                              func(body_dict, params, encrypt))
                else:
                    headers = headers.replace('{%s}' % param['name'],
                                              param['value'])
            if body.find('{%s}' % param['name']) != -1:
                if param['type'] == 'Function':
                    func = param['function']
                    flag, body_dict = self.__parse_body_arguments(body)
                    encrypt = yield self.option_func.get_crypt_info(self.pid)
                    body = body.replace('{%s}' % param['name'],
                                        func(body_dict, params, encrypt))
                else:
                    body = body.replace('{%s}' % param['name'], param['value'])
        return headers, body

    # 请求操作
    @gen.coroutine
    def __request_url(self,
                      url='',
                      env='none',
                      method='GET',
                      body='',
                      follow_redirects=True):
        test_client = httpclient.AsyncHTTPClient(max_clients=100)
        argv = dict(method=method,
                    headers=self.headers,
                    follow_redirects=False,
                    request_timeout=600,
                    validate_cert=False,
                    raise_error=False)
        url = yield self.__parse_host(url=url, env=env)
        if method == 'GET':
            url = '{}?{}'.format(url, body)
        elif method == 'POST':
            argv['body'] = body
        try:
            log.info('开始请求接口 {}'.format(url))
            response = yield test_client.fetch(url, **argv)
        except httpclient.HTTPError as e:
            response = e.response
            log.warning('请求接口 {} 异常# {}'.format(
                url, str(response.error if response else e)))
        # test_client.close()
        if response and response.code in [301, 302]:
            for cookie in response.headers.get_list('Set-Cookie'):
                self.headers['Cookie'] += '{};'.format(cookie)
            url = response.headers.get('Location')
            log.info('{} {} {}'.format(response.code, response.reason, url))
            if response.reason.find('Moved') >= 0:
                response = yield self.__request_url(
                    url=url,
                    env=env,
                    method=method,
                    body=body,
                    follow_redirects=follow_redirects)
            elif follow_redirects:
                response = yield self.__request_url(
                    url=url,
                    env=env,
                    method='GET',
                    follow_redirects=follow_redirects)
        log.info('结束请求接口 {}'.format(url))
        return response

    # 生成测试报告
    @gen.coroutine
    def __gen_report(self, job_name, test_suites, start_time, end_time):
        setting = yield self.setting.get_settings_by_range(pid=self.pid,
                                                           s_type='log',
                                                           start=start_time,
                                                           end=end_time,
                                                           sort=self.jid)
        if setting:
            elapsed_time = end_time - start_time
            start_time = time.strftime(
                '%Y-%m-%d %H:%M:%S', time.gmtime(float(start_time) + 3600 * 8))
            end_time = time.strftime('%Y-%m-%d %H:%M:%S',
                                     time.gmtime(float(end_time) + 3600 * 8))
            url, total = yield self.setting.get_settings_list(pid=self.pid,
                                                              s_type='url',
                                                              limit=None)
            overview = dict(name=job_name,
                            start_time=start_time,
                            end_time=end_time,
                            elapsed_time=elapsed_time,
                            total=total,
                            total_test=len(test_suites),
                            success_test=0,
                            fail_test=0,
                            success_rate='0.0 %',
                            report_time=time.strftime('%Y-%m-%d %H:%M:%S'))
            for suite in test_suites:
                suite['total_test'] = len(suite['cases'])
                suite['success_test'] = 0
                suite['fail_test'] = 0
                suite['report'] = []
                suite['result'] = True
            for row in setting:
                res = json.loads(row.value)
                for suite in test_suites:
                    if suite['suite_id'] == row.status:
                        if res['test_result']:
                            suite['success_test'] += 1
                        else:
                            suite['fail_test'] += 1
                            suite['result'] = False
                        suite['report'].append(res)
            for suite in test_suites:
                suite['result'] = suite['result'] if suite[
                    'success_test'] + suite['fail_test'] == suite[
                        'total_test'] else False
                if suite['result']:
                    overview['success_test'] += 1
                else:
                    overview['fail_test'] += 1
            overview['success_rate'] = '{:.2f} %'.format(
                overview['success_test'] / overview['total_test'] * 100)
            result = dict(overview=overview, report=test_suites)
            report_id, msg = yield self.setting.add_setting(
                pid=self.pid,
                s_type='report',
                sort=self.jid,
                name=time.time(),
                value=json.dumps(result, ensure_ascii=False))
            return report_id
        else:
            return False

    # 执行单接口测试
    @gen.coroutine
    def run_test(self,
                 url='',
                 label='',
                 comment='',
                 method='GET',
                 headers='',
                 body='',
                 crypt='none',
                 encrypt_content='',
                 no_test=False,
                 check_key='',
                 decrypt_content='',
                 checkpoint='',
                 env='none',
                 correlation='',
                 correlation_result={},
                 follow_redirects=True):
        headers, body = yield self.__parse_custom_param(
            headers=headers, body=body, correlation_result=correlation_result)
        self.get_headers(headers)
        request_body = yield self.__get_body(body=body,
                                             do='encrypt',
                                             name=crypt,
                                             crypt_key=encrypt_content)
        if no_test:
            return request_body
        resp = yield self.__request_url(url=url,
                                        env=env,
                                        method=method,
                                        body=request_body,
                                        follow_redirects=follow_redirects)
        response = yield self.__parse_response(response=resp,
                                               name=crypt,
                                               crypt_key=decrypt_content,
                                               checkpoint=checkpoint,
                                               check_key=check_key,
                                               correlation=correlation,
                                               method=method,
                                               url=url)
        response['label'] = label
        response['comment'] = comment
        if not resp:
            response['request_body'] = request_body
            for key in self.headers:
                response['request_headers'] += '{}: {}\r\n'.format(
                    key, self.headers[key])
        elif method == 'GET':
            response['request_body'] = request_body
        log.info('响应返回 {}'.format(json.dumps(response, ensure_ascii=False)))
        return response

    # 执行多接口测试
    @gen.coroutine
    def __run_all_test(self, job_name, test_suites):
        if not test_suites:
            return False
        start_time = time.time()
        correlation_result = dict()
        for test in test_suites:
            cases = yield self.setting.get_settings_by_ids(test['cases'])
            for url_info in cases:
                try:
                    url_info = json.loads(url_info.value)
                    resp = yield self.run_test(
                        correlation_result=correlation_result, **url_info)
                    yield self.setting.add_setting(pid=self.pid,
                                                   s_type='log',
                                                   name=time.time(),
                                                   sort=self.jid,
                                                   value=json.dumps(
                                                       resp,
                                                       ensure_ascii=False),
                                                   status=test['suite_id'])
                    correlation_result = dict(correlation_result,
                                              **resp['correlation'])
                except Exception as e:
                    log.error(e)
        end_time = time.time()
        report_id = yield self.__gen_report(job_name, test_suites, start_time,
                                            end_time)
        return report_id

    # 执行排队任务
    @gen.coroutine
    def run_job(self):
        job = yield self.setting.get_setting_by_id(sid=self.jid)
        if job:
            start_time = time.time()
            job_value = json.loads(job.value)
            start_strftime = time.strftime(
                '%Y-%m-%d %H:%M:%S', time.gmtime(float(start_time) + 3600 * 8))
            job_value['overview']['start_time'] = start_strftime
            yield self.setting.edit_setting(sid=job.id,
                                            status=2,
                                            value=json.dumps(
                                                job_value, ensure_ascii=False))
            test_suites = []
            suites = yield self.setting.get_settings_by_ids(
                job_value['testsuite'])
            for suite in suites:
                tests = dict(suite_id=suite.id,
                             suite_name=suite.name,
                             cases=json.loads(suite.value)['cases'])
                test_suites.append(tests)
            result = yield self.__run_all_test(job_value['name'], test_suites)
            if result:
                status = 3
                job_value['lastreport'] = result
            else:
                status = 5
            end_time = time.time()
            elapsed_time = end_time - start_time
            end_time = time.strftime('%Y-%m-%d %H:%M:%S',
                                     time.gmtime(float(end_time) + 3600 * 8))
            job_value['overview']['end_time'] = end_time
            job_value['overview']['elapsed_time'] = elapsed_time
            name = float(job.name)
            if job_value['overview']['cycle_time'] != 0:
                while name < time.time():
                    name += job_value['overview']['cycle_time']
                job_value['overview']['plan_time'] = time.strftime(
                    '%Y-%m-%d %H:%M:%S', time.gmtime(name + 3600 * 8))
                status = 0
            yield self.setting.edit_setting(sid=job.id,
                                            status=status,
                                            name=name,
                                            value=json.dumps(
                                                job_value, ensure_ascii=False))
            yield Mail().send_html_report(result)
コード例 #2
0
ファイル: mail.py プロジェクト: zhangjianhua346/OpenTest
    def send_html_report(self, rid):
        if rid:
            func_option = OptionsFunc()
            settings = SettingModule()
            lists = []
            pid = 0
            project = ''
            name = ''
            report_time = ''
            report_url = yield func_option.get_option_by_name('report_url')
            report_url = report_url if report_url else ''
            setting = yield settings.get_setting_by_id(rid)
            if setting and setting.type == 'report':
                report = json.loads(setting.value)
                pid = setting.project_id
                project = setting.project_name
                name = report['overview']['name']
                report_time = report['overview']['report_time']
                lists = report['report']
            for row in lists:
                if row['report']:
                    row['success_rate'] = '{:.2f} %'.format(row['success_test'] / row['total_test'] * 100)
                else:
                    row['success_rate'] = '0.00 %'
            lists = munchify(lists)
            for i in range(len(lists)):
                param = dict(id=i+1, suite_name=lists[i].suite_name, project=project, total_test=lists[i].total_test,
                             success_test=lists[i].success_test, fail_test=lists[i].fail_test, rid=rid,
                             success_rate=lists[i].success_rate, suite_id=lists[i].suite_id, report_url=report_url)
                if lists[i].result:
                    param['result'] = '<span class ="label label-info">通过</span>'
                else:
                    param['result'] = '<span class ="label label-danger">失败</span>'
                rows = """
                   <tr>
                       <td>{id}</td>
                       <td>{suite_name}</td>
                       <td>{project}</td>
                       <td>{total_test}</td>
                       <td>{success_test}</td>
                       <td>{fail_test}</td>
                       <td>{success_rate}</td>
                       <td>{result}</td>
                       <td><a class ="btn btn-primary" href="{report_url}/admin/interface-test/reports/list/{rid}/{suite_id}">查看详情</a></td>
                   </tr>""".format(**param)
                style = """
    <style type="text/css">
        .report-body * {
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box;
        }
        .report-body {
            padding-top: 50px;
            padding-bottom: 40px;
            background-color: #eee;
            font-family: "Microsoft Yahei";
            font-size: 14px;
            line-height: 1.42857143;
            color: #333;
        }
        .report-body .container-fluid {
            margin-right: auto;
            margin-left: auto;
        }
        .report-body .well {
            min-height: 20px;
            padding: 19px;
            margin-bottom: 20px;
            background-color: #f5f5f5;
            border: 1px solid #e3e3e3;
            border-radius: 4px;
            box-shadow: inset 0 1px 1px rgba(0,0,0,.05);
        }
        .report-body .table-responsive {
            min-height: .01%;
            overflow-x: auto;
        }
        .report-body table {
            background-color: transparent;
            border-spacing: 0;
            border-collapse: collapse;
            border-color: grey;
        }
        .report-body thead {
            display: table-header-group;
            vertical-align: middle;
            vertical-align: middle;
        }
        .report-body tr {
            display: table-row;
            vertical-align: inherit;
            border-color: inherit;
        }
        .report-body th {
            text-align: left;
            font-weight: bold;
            display: table-cell;
        }
        .report-body tbody {
            display: table-row-group;
            vertical-align: middle;
            border-color: inherit;
        }
        .report-body .table {
            width: 100%;
            max-width: 100%;
            margin-bottom: 20px;
        }
        .report-body .table>thead:first-child>tr:first-child>th {
            border-top: 0;
        }
        .report-body .table>thead>tr>th {
            vertical-align: bottom;
            border-bottom: 2px solid #ddd;
            padding: 8px;
            line-height: 1.42857143;
        }
        .report-body .table>tbody>tr>td {
            padding: 8px;
            line-height: 1.42857143;
            vertical-align: top;
            border-top: 1px solid #ddd;
            display: table-cell;
        }
        .report-body .table-striped>tbody>tr:nth-of-type(odd) {
            background-color: #f9f9f9;
        }
        .report-body .label-info {
            background-color: #5bc0de;
        }
        .report-body .label-danger {
            background-color: #d9534f;
        }
        .report-body .label {
            display: inline;
            padding: .2em .6em .3em;
            font-size: 75%;
            font-weight: 700;
            line-height: 1;
            color: #fff;
            text-align: center;
            white-space: nowrap;
            vertical-align: baseline;
            border-radius: .25em;
        }
        .report-body .btn-primary {
            color: #fff;
            background-color: #337ab7;
            border-color: #2e6da4;
        }
        .report-body .btn {
            display: inline-block;
            padding: 6px 12px;
            margin-bottom: 0;
            font-size: 14px;
            font-weight: 400;
            line-height: 1.42857143;
            text-align: center;
            white-space: nowrap;
            vertical-align: middle;
            -ms-touch-action: manipulation;
            touch-action: manipulation;
            cursor: pointer;
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
            background-image: none;
            border: 1px solid transparent;
            border-radius: 4px;
        }
        .report-body a {
            color: #337ab7;
            text-decoration: none;
        }
    </style>
"""
                content = """
<div class="report-body">
    <div class="container-fluid well">
        <h2 style="text-align: center;">任务 {name} 测试报告 / 报告时间 {report_time}</h2>
        <div class="table-responsive">
            <table class="table table-striped table-hover">
                <thead>
                    <tr>
                        <th>#</th>
                        <th>用例名称</th>
                        <th>所属项目</th>
                        <th>测试接口数</th>
                        <th>通过数</th>
                        <th>失败数</th>
                        <th>通过率</th>
                        <th>测试结果</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {rows}
                </tbody>
            </table>
        </div>
    </div>
    {style}
</div>""".format(**dict(style=style, name=name, report_time=report_time, rows=rows))
                smtp_server = yield func_option.get_option_by_name('smtp_host')
                smtp_port = yield func_option.get_option_by_name('smtp_port')
                use_ssl = yield func_option.get_option_by_name('use_ssl')
                smtp_user = yield func_option.get_option_by_name('smtp_user')
                smtp_password = yield func_option.get_option_by_name('smtp_password')
                mail_from = yield func_option.get_option_by_name('mail_from')
                mail_report = yield func_option.get_option_by_name('mail_report')
                company = yield func_option.get_option_by_name('company')
                self.smtp_server = smtp_server if smtp_server else self.smtp_server
                self.smtp_port = smtp_port if smtp_port else self.smtp_port
                self.use_ssl = True if use_ssl == 'on' else self.use_ssl
                self.smtp_user = smtp_user if smtp_user else self.smtp_user
                self.smtp_password = smtp_password if smtp_password else self.smtp_password
                self.mail_from = mail_from if mail_from else self.mail_from
                mail_report = True if mail_report == 'on' else False
                users = yield ProjectModule().get_project(pid=pid, status=1)
                if users:
                    mail_to = []
                    users = json.loads(users.user) if users.user else []
                    for user in users:
                        user = json.loads(user)
                        if user['mail']:
                            mail_to.append(user['email'])
                    if mail_to and mail_report:
                        result, msg = yield self.send_mail(subject='[{}][测试报告]'.format(company),
                                                           message=content, to=mail_to)
                        if result:
                            log.info('发送任务 {} 测试报告邮件成功'.format(name))
                        else:
                            log.warning('发送任务 {} 测试报告邮件失败# {}'.format(name, msg))