resolve conflict

This commit is contained in:
duanchao.bill
2022-06-09 10:26:59 +08:00
182 changed files with 5457 additions and 1865 deletions

View File

@@ -1,21 +1,22 @@
__version__ = "4.0.0-beta"
__version__ = "v4.1.2"
__description__ = "One-stop solution for HTTP(S) testing."
from httprunner.config import Config
from httprunner.parser import parse_parameters as Parameters
from httprunner.runner import HttpRunner
from httprunner.step import Step
from httprunner.step_request import RunRequest
from httprunner.step_testcase import RunTestCase
from httprunner.step_sql_request import (
RunSqlRequest,
StepSqlRequestValidation,
StepSqlRequestExtraction,
StepSqlRequestValidation,
)
from httprunner.step_testcase import RunTestCase
from httprunner.step_thrift_request import (
RunThriftRequest,
StepThriftRequestValidation,
StepThriftRequestExtraction,
StepThriftRequestValidation,
)
__all__ = [
@@ -28,9 +29,9 @@ __all__ = [
"RunSqlRequest",
"StepSqlRequestValidation",
"StepSqlRequestExtraction",
"RunTestCase",
"Parameters",
"RunThriftRequest",
"StepThriftRequestValidation",
"StepThriftRequestExtraction",
"RunTestCase",
"Parameters",
]

View File

@@ -1,5 +1,5 @@
"""
This module handles compatibility issues between testcase format v2 and v3.
This module handles compatibility issues between testcase format v2, v3 and v4.
"""
import os
import sys
@@ -14,9 +14,8 @@ from httprunner.utils import sort_dict_by_custom_order
def convert_variables(
raw_variables: Union[Dict, Text], test_path: Text
raw_variables: Union[Dict, Text], test_path: Text
) -> Dict[Text, Any]:
if isinstance(raw_variables, Dict):
return raw_variables
@@ -33,6 +32,18 @@ def convert_variables(
)
def _convert_request(request: Dict) -> Dict:
if "body" in request:
content_type = ""
if "headers" in request and "Content-Type" in request["headers"]:
content_type = request["headers"]["Content-Type"]
if content_type.startswith("application/json"):
request["json"] = request.pop("body")
else:
request["data"] = request.pop("body")
return _sort_request_by_custom_order(request)
def _convert_jmespath(raw: Text) -> Text:
if not isinstance(raw, Text):
raise exceptions.TestCaseFormatError(f"Invalid jmespath extractor: {raw}")
@@ -153,6 +164,9 @@ def _ensure_step_attachment(step: Dict) -> Dict:
"name": step["name"],
}
if "request" in step:
test_dict["request"] = _convert_request(step["request"])
if "variables" in step:
test_dict["variables"] = step["variables"]
@@ -181,11 +195,11 @@ def _ensure_step_attachment(step: Dict) -> Dict:
return test_dict
def ensure_testcase_v3_api(api_content: Dict) -> Dict:
logger.info("convert api in v2 to testcase format v3")
def ensure_testcase_v4_api(api_content: Dict) -> Dict:
logger.info("convert api in v2/v3 to testcase format v4")
teststep = {
"request": _sort_request_by_custom_order(api_content["request"]),
"request": _convert_request(api_content["request"]),
}
teststep.update(_ensure_step_attachment(api_content))
@@ -202,8 +216,8 @@ def ensure_testcase_v3_api(api_content: Dict) -> Dict:
}
def ensure_testcase_v3(test_content: Dict) -> Dict:
logger.info("ensure compatibility with testcase format v2")
def ensure_testcase_v4(test_content: Dict) -> Dict:
logger.info("ensure compatibility with testcase format v2/v3")
v3_content = {"config": test_content["config"], "teststeps": []}
@@ -221,7 +235,7 @@ def ensure_testcase_v3(test_content: Dict) -> Dict:
teststep = {}
if "request" in step:
teststep["request"] = _sort_request_by_custom_order(step.pop("request"))
pass
elif "api" in step:
teststep["testcase"] = step.pop("api")
elif "testcase" in step:

View File

@@ -26,6 +26,53 @@ class TestCompat(unittest.TestCase):
with self.assertRaises(exceptions.TestCaseFormatError):
compat.convert_variables(None, "examples/data/a-b.c/1.yml")
def test_convert_request(self):
request_with_json_body = {
"method": "POST",
"url": "https://postman-echo.com/post",
"headers": {
"Content-Type": "application/json"
},
"body": {
"k1": "v1",
"k2": "v2"
}
}
self.assertEqual(
compat._convert_request(request_with_json_body),
{
"method": "POST",
"url": "https://postman-echo.com/post",
"headers": {
"Content-Type": "application/json"
},
"json": {
"k1": "v1",
"k2": "v2"
}
}
)
request_with_text_body = {
"method": "POST",
"url": "https://postman-echo.com/post",
"headers": {
"Content-Type": "text/plain"
},
"body": "have a nice day"
}
self.assertEqual(
compat._convert_request(request_with_text_body),
{
"method": "POST",
"url": "https://postman-echo.com/post",
"headers": {
"Content-Type": "text/plain"
},
"data": "have a nice day"
}
)
def test_convert_jmespath(self):
self.assertEqual(compat._convert_jmespath("content.abc"), "body.abc")
self.assertEqual(compat._convert_jmespath("json.abc"), "body.abc")
@@ -85,7 +132,7 @@ class TestCompat(unittest.TestCase):
[{"eq": ["body[0].name", 201]}],
)
def test_ensure_testcase_v3_api(self):
def test_ensure_testcase_v4_api(self):
api_content = {
"name": "get with params",
"request": {
@@ -98,7 +145,7 @@ class TestCompat(unittest.TestCase):
"validate": [{"eq": ["content.varB", 200]}, {"lt": ["json.0.varC", 0]}],
}
self.assertEqual(
compat.ensure_testcase_v3_api(api_content),
compat.ensure_testcase_v4_api(api_content),
{
"config": {
"name": "get with params",
@@ -126,7 +173,7 @@ class TestCompat(unittest.TestCase):
},
)
def test_ensure_testcase_v3(self):
def test_ensure_testcase_v4(self):
testcase_content = {
"config": {"name": "xxx", "base_url": "https://httpbin.org"},
"teststeps": [
@@ -150,7 +197,7 @@ class TestCompat(unittest.TestCase):
],
}
self.assertEqual(
compat.ensure_testcase_v3(testcase_content),
compat.ensure_testcase_v4(testcase_content),
{
"config": {"name": "xxx", "base_url": "https://httpbin.org"},
"teststeps": [

View File

@@ -11,8 +11,8 @@ from httprunner import __version__, exceptions
from httprunner.compat import (
convert_variables,
ensure_path_sep,
ensure_testcase_v3,
ensure_testcase_v3_api,
ensure_testcase_v4,
ensure_testcase_v4_api,
)
from httprunner.loader import (
convert_relative_project_root_dir,
@@ -33,7 +33,7 @@ pytest_files_made_cache_mapping: Dict[Text, Text] = {}
pytest_files_run_set: Set = set()
__TEMPLATE__ = jinja2.Template(
"""# NOTE: Generated By HttpRunner v{{ version }}
"""# NOTE: Generated By HttpRunner {{ version }}
# FROM: {{ testcase_path }}
{% if imports_list and diff_levels > 0 %}
@@ -332,8 +332,8 @@ def make_teststep_chain_style(teststep: Dict) -> Text:
def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
"""convert valid testcase dict to pytest file path"""
# ensure compatibility with testcase format v2
testcase = ensure_testcase_v3(testcase)
# ensure compatibility with testcase format v2/v3
testcase = ensure_testcase_v4(testcase)
# validate testcase format
load_testcase(testcase)
@@ -373,9 +373,9 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
if not isinstance(test_content, Dict):
raise exceptions.TestCaseFormatError(f"Invalid teststep: {teststep}")
# api in v2 format, convert to v3 testcase
# api in v2/v3 format, convert to v4 testcase
if "request" in test_content and "name" in test_content:
test_content = ensure_testcase_v3_api(test_content)
test_content = ensure_testcase_v4_api(test_content)
test_content.setdefault("config", {})["path"] = ref_testcase_path
ref_testcase_python_abs_path = make_testcase(test_content)
@@ -473,9 +473,9 @@ def __make(tests_path: Text):
)
continue
# api in v2 format, convert to v3 testcase
# api in v2/v3 format, convert to v4 testcase
if "request" in test_content and "name" in test_content:
test_content = ensure_testcase_v3_api(test_content)
test_content = ensure_testcase_v4_api(test_content)
if "config" not in test_content:
logger.warning(

View File

@@ -29,17 +29,17 @@ class MethodEnum(Text, Enum):
class ProtoType(Enum):
pBinary = 1
pCyBinary = 2
pCompact = 3
pJson = 4
Binary = 1
CyBinary = 2
Compact = 3
Json = 4
class TransType(Enum):
tBuffered = 1
tCyBuffered = 2
tFramed = 3
tCyFramed = 4
Buffered = 1
CyBuffered = 2
Framed = 3
CyFramed = 4
# configs for thrift rpc
@@ -56,8 +56,8 @@ class TConfigThrift(BaseModel):
ip: Text = "127.0.0.1"
port: int = 9000
service_name: Text = None
proto_type: ProtoType = ProtoType.pBinary
trans_type: TransType = TransType.tBuffered
proto_type: ProtoType = ProtoType.Binary
trans_type: TransType = TransType.Buffered
# configs for db

View File

@@ -91,9 +91,11 @@ class SessionRunner(object):
def with_thrift_client(self, thrift_client) -> "SessionRunner":
self.thrift_client = thrift_client
return self
def with_db_engine(self, db_engine):
def with_db_engine(self, db_engine) -> "SessionRunner":
self.db_engine = db_engine
return self
def __parse_config(self, param: Dict = None) -> None:
# parse config variables

View File

@@ -1,20 +1,23 @@
from typing import Union
from httprunner import HttpRunner
from httprunner.models import StepResult, TRequest, TStep, TestCase
from httprunner.runner import HttpRunner
from httprunner.step_request import (
RequestWithOptionalArgs,
StepRequestExtraction,
StepRequestValidation,
)
from httprunner.step_testcase import StepRefCase
from httprunner.step_sql_request import (
RunSqlRequest,
StepSqlRequestValidation,
StepSqlRequestExtraction,
StepSqlRequestValidation,
)
from httprunner.step_testcase import StepRefCase
from httprunner.step_thrift_request import (
RunThriftRequest,
StepThriftRequestExtraction,
StepThriftRequestValidation,
)
from httprunner.step_thrift_request import RunThriftRequest,StepThriftRequestValidation,StepThriftRequestExtraction
class Step(object):
@@ -30,7 +33,7 @@ class Step(object):
StepSqlRequestExtraction,
RunThriftRequest,
StepThriftRequestValidation,
StepThriftRequestExtraction
StepThriftRequestExtraction,
],
):
self.__step = step

View File

@@ -1,22 +1,45 @@
# -*- coding: utf-8 -*-
import sys
import time
from typing import Text
from loguru import logger
from httprunner import utils
from httprunner.exceptions import SqlMethodNotSupport
from httprunner.exceptions import ValidationFailure
from httprunner.models import IStep, StepResult, TStep
from httprunner.models import TSqlRequest, SqlMethodEnum
from httprunner.models import SqlMethodEnum, TSqlRequest
from httprunner.response import SqlResponseObject
from httprunner.runner import HttpRunner, USE_ALLURE
from httprunner.step_request import (
call_hooks,
StepRequestExtraction,
StepRequestValidation,
call_hooks,
)
from httprunner.database.engine import DBEngine
from httprunner.exceptions import SqlMethodNotSupport
try:
import sqlalchemy
import pymysql
SQL_READY = True
except ModuleNotFoundError:
SQL_READY = False
def ensure_sql_ready():
if SQL_READY:
return
msg = """
uploader extension dependencies uninstalled, install first and try again.
install with pip:
$ pip install sqlalchemy pymysql
or you can install httprunner with optional upload dependencies:
$ pip install "httprunner[sql]"
"""
logger.error(msg)
sys.exit(1)
def run_step_sql_request(runner: HttpRunner, step: TStep) -> StepResult:
@@ -52,6 +75,9 @@ def run_step_sql_request(runner: HttpRunner, step: TStep) -> StepResult:
)
if not runner.db_engine:
ensure_sql_ready()
from httprunner.database.engine import DBEngine
runner.db_engine = DBEngine(
f'mysql+pymysql://{parsed_request_dict["db_config"]["user"]}:'
f'{parsed_request_dict["db_config"]["password"]}@{parsed_request_dict["db_config"]["ip"]}:'

View File

@@ -1,21 +1,52 @@
# -*- coding: utf-8 -*-
import platform
import sys
import time
from typing import Text, Union
from loguru import logger
from httprunner import utils
from httprunner.exceptions import ValidationFailure
from httprunner.models import IStep, StepResult, TStep, ProtoType, TransType
from httprunner.models import (
IStep,
ProtoType,
StepResult,
TStep,
TThriftRequest,
TransType,
)
from httprunner.response import ThriftResponseObject
from httprunner.runner import HttpRunner, USE_ALLURE
from httprunner.step_request import (
call_hooks,
StepRequestExtraction,
StepRequestValidation,
)
from httprunner.models import TThriftRequest
from httprunner.response import ThriftResponseObject
from httprunner.thrift.thrift_client import ThriftClient
try:
import thriftpy2
from thrift.Thrift import TType
THRIFT_READY = True
except ModuleNotFoundError:
THRIFT_READY = False
def ensure_thrift_ready():
assert platform.system() != "Windows", "Sorry,thrift not support Windows for now"
if THRIFT_READY:
return
msg = """
uploader extension dependencies uninstalled, install first and try again.
install with pip:
$ pip install cython thriftpy2 thrift
or you can install httprunner with optional upload dependencies:
$ pip install "httprunner[thrift]"
"""
logger.error(msg)
sys.exit(1)
def run_step_thrift_request(runner: HttpRunner, step: TStep) -> StepResult:
@@ -71,6 +102,9 @@ def run_step_thrift_request(runner: HttpRunner, step: TStep) -> StepResult:
if not runner.thrift_client:
runner.thrift_client = parsed_request_dict["thrift_client"]
if not runner.thrift_client:
ensure_thrift_ready()
from httprunner.thrift.thrift_client import ThriftClient
runner.thrift_client = ThriftClient(
thrift_file=parsed_request_dict["idl_path"],
service_name=parsed_request_dict["service_name"],
@@ -195,7 +229,9 @@ class RunThriftRequest(IStep):
return self
def setup_hook(self, hook: Text, assign_var_name: Text = None) -> "RunTestCase":
def setup_hook(
self, hook: Text, assign_var_name: Text = None
) -> "RunThriftRequest":
if assign_var_name:
self.__step.setup_hooks.append({assign_var_name: hook})
else:

View File

@@ -307,7 +307,7 @@ class MyJSONEncoder(json.JSONEncoder):
chunks = self.iterencode(o, _one_shot=True)
if not isinstance(chunks, (list, tuple)):
chunks = list(chunks)
# add by braver(braver@bytedance.com)
# add by braver
# todo: fix 'utf8' codec can't decode byte 0x91 in position 3: invalid start byte"
if self.skip_nonutf8_value: # 缺省为false
tmp_chunks = []
@@ -324,7 +324,7 @@ class MyJSONEncoder(json.JSONEncoder):
class ThriftJSONEncoder(json.JSONEncoder):
"""
add by braver(Braver@bytedance.com)
add by braver
"""
def __init__(
@@ -377,7 +377,7 @@ class ThriftJSONEncoder(json.JSONEncoder):
chunks = self.iterencode(o, _one_shot=True)
if not isinstance(chunks, (list, tuple)):
chunks = list(chunks)
# add by braver(braver@bytedance.com)
# add by braver
# todo: fix 'utf8' codec can't decode byte 0x91 in position 3: invalid start byte"
if self.skip_nonutf8_value: # 缺省为false
tmp_chunks = []

View File

@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import enum
import json
from loguru import logger
import thriftpy2
from loguru import logger
from thriftpy2.protocol import (
TBinaryProtocolFactory,
TCompactProtocolFactory,
@@ -18,23 +19,22 @@ from thriftpy2.transport import (
TCyFramedTransportFactory,
TFramedTransportFactory,
)
from thriftpy2.utils import deserialize
from httprunner.thrift.data_convertor import json2thrift, thrift2json, thrift2dict
from httprunner.thrift.data_convertor import json2thrift, thrift2dict
class ProtoType(enum.Enum):
pBinary = 1
pCyBinary = 2
pCompact = 3
pJson = 4
Binary = 1
CyBinary = 2
Compact = 3
Json = 4
class TransType(enum.Enum):
tBuffered = 1
tCyBuffered = 2
tFramed = 3
tCyFramed = 4
Buffered = 1
CyBuffered = 2
Framed = 3
CyFramed = 4
class RequestFormat(enum.Enum):
@@ -43,24 +43,24 @@ class RequestFormat(enum.Enum):
def get_proto_factory(proto_type):
if proto_type == ProtoType.pBinary:
if proto_type == ProtoType.Binary:
return TBinaryProtocolFactory()
if proto_type == ProtoType.pCyBinary:
if proto_type == ProtoType.CyBinary:
return TCyBinaryProtocolFactory()
if proto_type == ProtoType.pCompact:
if proto_type == ProtoType.Compact:
return TCompactProtocolFactory()
if proto_type == ProtoType.pJson:
if proto_type == ProtoType.Json:
return TJSONProtocolFactory()
def get_trans_factory(trans_type):
if trans_type == TransType.tBuffered:
if trans_type == TransType.Buffered:
return TBufferedTransportFactory()
if trans_type == TransType.tCyBuffered:
if trans_type == TransType.CyBuffered:
return TCyBufferedTransportFactory()
if trans_type == TransType.tFramed:
if trans_type == TransType.Framed:
return TFramedTransportFactory()
if trans_type == TransType.tCyFramed:
if trans_type == TransType.CyFramed:
return TCyFramedTransportFactory()
@@ -73,8 +73,8 @@ class ThriftClient(object):
port,
include_dirs=None,
timeout=3000,
proto_type=ProtoType.pCyBinary,
trans_type=TransType.tCyBuffered,
proto_type=ProtoType.CyBinary,
trans_type=TransType.CyBuffered,
):
self.thrift_file = thrift_file
self.include_dirs = include_dirs