mirror of
https://github.com/httprunner/httprunner.git
synced 2026-08-02 12:06:57 +08:00
feat: implement lazy parser for validators
This commit is contained in:
@@ -74,11 +74,9 @@ class HttpRunner(object):
|
||||
return test
|
||||
|
||||
test_suite = unittest.TestSuite()
|
||||
functions = tests_mapping.get("project_mapping", {}).get("functions", {})
|
||||
|
||||
for testcase in tests_mapping["testcases"]:
|
||||
config = testcase.get("config", {})
|
||||
test_runner = runner.Runner(config, functions)
|
||||
test_runner = runner.Runner(config)
|
||||
TestSequense = type('TestSequense', (unittest.TestCase,), {})
|
||||
|
||||
tests = testcase.get("teststeps", [])
|
||||
|
||||
@@ -8,8 +8,9 @@ def main_hrun():
|
||||
from httprunner.__about__ import __description__, __version__
|
||||
from httprunner.api import HttpRunner
|
||||
from httprunner.compat import is_py2
|
||||
from httprunner.validator import validate_json_file
|
||||
from httprunner.utils import (create_scaffold, get_python2_retire_msg,
|
||||
prettify_json_file, validate_json_file)
|
||||
prettify_json_file)
|
||||
|
||||
parser = argparse.ArgumentParser(description=__description__)
|
||||
parser.add_argument(
|
||||
|
||||
@@ -5,18 +5,16 @@ class SessionContext(object):
|
||||
""" HttpRunner session, store runtime variables.
|
||||
|
||||
Examples:
|
||||
>>> functions={...}
|
||||
>>> variables = {"SECRET_KEY": "DebugTalk"}
|
||||
>>> context = SessionContext(functions, variables)
|
||||
>>> context = SessionContext(variables)
|
||||
|
||||
Equivalent to:
|
||||
>>> context = SessionContext(functions)
|
||||
>>> context = SessionContext()
|
||||
>>> context.update_session_variables(variables)
|
||||
|
||||
"""
|
||||
def __init__(self, functions, variables=None):
|
||||
def __init__(self, variables=None):
|
||||
self.session_variables_mapping = utils.ensure_mapping_format(variables or {})
|
||||
self.FUNCTIONS_MAPPING = functions
|
||||
self.init_test_variables()
|
||||
self.validation_results = []
|
||||
|
||||
@@ -63,32 +61,20 @@ class SessionContext(object):
|
||||
"""
|
||||
return parser.parse_lazy_data(content, self.test_variables_mapping)
|
||||
|
||||
def __eval_check_item(self, validator, resp_obj):
|
||||
def __eval_validator_check(self, check_item, resp_obj):
|
||||
""" evaluate check item in validator.
|
||||
|
||||
Args:
|
||||
validator (dict): validator
|
||||
{"check": "status_code", "comparator": "eq", "expect": 201}
|
||||
{"check": "$resp_body_success", "comparator": "eq", "expect": True}
|
||||
resp_obj (object): requests.Response() object
|
||||
check_item: check_item should only be the following 5 formats:
|
||||
1, variable reference, e.g. $token
|
||||
2, function reference, e.g. ${is_status_code_200($status_code)}
|
||||
3, dict or list, maybe containing variable/function reference, e.g. {"var": "$abc"}
|
||||
4, string joined by delimiter. e.g. "status_code", "headers.content-type"
|
||||
5, regex string, e.g. "LB[\d]*(.*)RB[\d]*"
|
||||
|
||||
Returns:
|
||||
dict: validator info
|
||||
{
|
||||
"check": "status_code",
|
||||
"check_value": 200,
|
||||
"expect": 201,
|
||||
"comparator": "eq"
|
||||
}
|
||||
resp_obj: response object
|
||||
|
||||
"""
|
||||
check_item = validator["check"]
|
||||
# check_item should only be the following 5 formats:
|
||||
# 1, variable reference, e.g. $token
|
||||
# 2, function reference, e.g. ${is_status_code_200($status_code)}
|
||||
# 3, dict or list, maybe containing variable/function reference, e.g. {"var": "$abc"}
|
||||
# 4, string joined by delimiter. e.g. "status_code", "headers.content-type"
|
||||
# 5, regex string, e.g. "LB[\d]*(.*)RB[\d]*"
|
||||
if isinstance(check_item, (dict, list)) \
|
||||
or isinstance(check_item, parser.LazyString):
|
||||
# format 1/2/3
|
||||
@@ -97,68 +83,22 @@ class SessionContext(object):
|
||||
# format 4/5
|
||||
check_value = resp_obj.extract_field(check_item)
|
||||
|
||||
validator["check_value"] = check_value
|
||||
return check_value
|
||||
|
||||
# expect_value should only be in 2 types:
|
||||
# 1, variable reference, e.g. $expect_status_code
|
||||
# 2, actual value, e.g. 200
|
||||
expect_value = self.eval_content(validator["expect"])
|
||||
validator["expect"] = expect_value
|
||||
validator["check_result"] = "unchecked"
|
||||
return validator
|
||||
|
||||
def _do_validation(self, validator_dict):
|
||||
""" validate with functions
|
||||
def __eval_validator_expect(self, expect_item):
|
||||
""" evaluate expect item in validator.
|
||||
|
||||
Args:
|
||||
validator_dict (dict): validator dict
|
||||
{
|
||||
"check": "status_code",
|
||||
"check_value": 200,
|
||||
"expect": 201,
|
||||
"comparator": "eq"
|
||||
}
|
||||
expect_item: expect_item should only be in 2 types:
|
||||
1, variable reference, e.g. $expect_status_code
|
||||
2, actual value, e.g. 200
|
||||
|
||||
"""
|
||||
# TODO: move comparator uniform to init_test_suites
|
||||
comparator = utils.get_uniform_comparator(validator_dict["comparator"])
|
||||
validate_func = parser.get_mapping_function(comparator, self.FUNCTIONS_MAPPING)
|
||||
|
||||
check_item = validator_dict["check"]
|
||||
check_value = validator_dict["check_value"]
|
||||
expect_value = validator_dict["expect"]
|
||||
|
||||
if (check_value is None or expect_value is None) \
|
||||
and comparator not in ["is", "eq", "equals", "=="]:
|
||||
raise exceptions.ParamsError("Null value can only be compared with comparator: eq/equals/==")
|
||||
|
||||
validate_msg = "validate: {} {} {}({})".format(
|
||||
check_item,
|
||||
comparator,
|
||||
expect_value,
|
||||
type(expect_value).__name__
|
||||
)
|
||||
|
||||
try:
|
||||
validator_dict["check_result"] = "pass"
|
||||
validate_func(check_value, expect_value)
|
||||
validate_msg += "\t==> pass"
|
||||
logger.log_debug(validate_msg)
|
||||
except (AssertionError, TypeError):
|
||||
validate_msg += "\t==> fail"
|
||||
validate_msg += "\n{}({}) {} {}({})".format(
|
||||
check_value,
|
||||
type(check_value).__name__,
|
||||
comparator,
|
||||
expect_value,
|
||||
type(expect_value).__name__
|
||||
)
|
||||
logger.log_error(validate_msg)
|
||||
validator_dict["check_result"] = "fail"
|
||||
raise exceptions.ValidationFailure(validate_msg)
|
||||
expect_value = self.eval_content(expect_item)
|
||||
return expect_value
|
||||
|
||||
def validate(self, validators, resp_obj):
|
||||
""" make validations
|
||||
""" make validation with comparators
|
||||
"""
|
||||
self.validation_results = []
|
||||
if not validators:
|
||||
@@ -170,19 +110,59 @@ class SessionContext(object):
|
||||
failures = []
|
||||
|
||||
for validator in validators:
|
||||
# evaluate validators with context variable mapping.
|
||||
evaluated_validator = self.__eval_check_item(
|
||||
parser.parse_validator(validator),
|
||||
# validator should be LazyFunction object
|
||||
if not isinstance(validator, parser.LazyFunction):
|
||||
raise exceptions.ValidationFailure(
|
||||
"validator should be parsed first: {}".format(validators))
|
||||
|
||||
# evaluate validator args with context variable mapping.
|
||||
validator_args = validator._args
|
||||
check_item, expect_item = validator_args
|
||||
check_value = self.__eval_validator_check(
|
||||
check_item,
|
||||
resp_obj
|
||||
)
|
||||
expect_value = self.__eval_validator_expect(expect_item)
|
||||
validator._args = [check_value, expect_value]
|
||||
|
||||
comparator = validator._func.__name__
|
||||
validator_dict = {
|
||||
"comparator": comparator,
|
||||
"check": check_item,
|
||||
"check_value": check_value,
|
||||
"expect": expect_item,
|
||||
"expect_value": expect_value
|
||||
}
|
||||
validate_msg = "\nvalidate: {} {} {}({})".format(
|
||||
check_item,
|
||||
comparator,
|
||||
expect_value,
|
||||
type(expect_value).__name__
|
||||
)
|
||||
|
||||
try:
|
||||
self._do_validation(evaluated_validator)
|
||||
except exceptions.ValidationFailure as ex:
|
||||
validator.to_value(self.test_variables_mapping)
|
||||
validator_dict["check_result"] = "pass"
|
||||
validate_msg += "\t==> pass"
|
||||
logger.log_debug(validate_msg)
|
||||
except (AssertionError, TypeError):
|
||||
validate_pass = False
|
||||
failures.append(str(ex))
|
||||
validator_dict["check_result"] = "fail"
|
||||
validate_msg += "\t==> fail"
|
||||
validate_msg += "\n{}({}) {} {}({})".format(
|
||||
check_value,
|
||||
type(check_value).__name__,
|
||||
comparator,
|
||||
expect_value,
|
||||
type(expect_value).__name__
|
||||
)
|
||||
logger.log_error(validate_msg)
|
||||
failures.append(validate_msg)
|
||||
|
||||
self.validation_results.append(evaluated_validator)
|
||||
self.validation_results.append(validator_dict)
|
||||
|
||||
# restore validator args, in case of running multiple times
|
||||
validator._args = validator_args
|
||||
|
||||
if not validate_pass:
|
||||
failures_string = "\n".join([failure for failure in failures])
|
||||
|
||||
@@ -4,7 +4,7 @@ import ast
|
||||
import os
|
||||
import re
|
||||
|
||||
from httprunner import exceptions, utils
|
||||
from httprunner import exceptions, utils, validator
|
||||
from httprunner.compat import basestring, builtin_str, numeric_types, str
|
||||
|
||||
# TODO: change variable notation from $var to {{var}}
|
||||
@@ -105,65 +105,6 @@ def regex_findall_functions(content):
|
||||
return []
|
||||
|
||||
|
||||
def parse_validator(validator):
|
||||
""" parse validator
|
||||
|
||||
Args:
|
||||
validator (dict): validator maybe in two formats:
|
||||
|
||||
format1: this is kept for compatiblity with the previous versions.
|
||||
{"check": "status_code", "comparator": "eq", "expect": 201}
|
||||
{"check": "$resp_body_success", "comparator": "eq", "expect": True}
|
||||
format2: recommended new version
|
||||
{'eq': ['status_code', 201]}
|
||||
{'eq': ['$resp_body_success', True]}
|
||||
|
||||
Returns
|
||||
dict: validator info
|
||||
|
||||
{
|
||||
"check": "status_code",
|
||||
"expect": 201,
|
||||
"comparator": "eq"
|
||||
}
|
||||
|
||||
"""
|
||||
if not isinstance(validator, dict):
|
||||
raise exceptions.ParamsError("invalid validator: {}".format(validator))
|
||||
|
||||
if "check" in validator and len(validator) > 1:
|
||||
# format1
|
||||
check_item = validator.get("check")
|
||||
|
||||
if "expect" in validator:
|
||||
expect_value = validator.get("expect")
|
||||
elif "expected" in validator:
|
||||
expect_value = validator.get("expected")
|
||||
else:
|
||||
raise exceptions.ParamsError("invalid validator: {}".format(validator))
|
||||
|
||||
comparator = validator.get("comparator", "eq")
|
||||
|
||||
elif len(validator) == 1:
|
||||
# format2
|
||||
comparator = list(validator.keys())[0]
|
||||
compare_values = validator[comparator]
|
||||
|
||||
if not isinstance(compare_values, list) or len(compare_values) != 2:
|
||||
raise exceptions.ParamsError("invalid validator: {}".format(validator))
|
||||
|
||||
check_item, expect_value = compare_values
|
||||
|
||||
else:
|
||||
raise exceptions.ParamsError("invalid validator: {}".format(validator))
|
||||
|
||||
return {
|
||||
"check": check_item,
|
||||
"expect": expect_value,
|
||||
"comparator": comparator
|
||||
}
|
||||
|
||||
|
||||
def parse_parameters(parameters, variables_mapping=None, functions_mapping=None):
|
||||
""" parse parameters and generate cartesian product.
|
||||
|
||||
@@ -738,12 +679,9 @@ def _extend_with_api(test_dict, api_def_dict):
|
||||
""" extend test with api definition, test will merge and override api definition.
|
||||
|
||||
Args:
|
||||
test_dict (dict): test block
|
||||
test_dict (dict): test block, this will override api_def_dict
|
||||
api_def_dict (dict): api definition
|
||||
|
||||
Returns:
|
||||
dict: extended test dict.
|
||||
|
||||
Examples:
|
||||
>>> api_def_dict = {
|
||||
"name": "get token 1",
|
||||
@@ -756,6 +694,7 @@ def _extend_with_api(test_dict, api_def_dict):
|
||||
"validate": [{'eq': ['status_code', 201]}, {'len_eq': ['content.token', 16]}]
|
||||
}
|
||||
>>> _extend_with_api(test_dict, api_def_dict)
|
||||
>>> print(test_dict)
|
||||
{
|
||||
"name": "get token 2",
|
||||
"request": {...},
|
||||
@@ -764,9 +703,8 @@ def _extend_with_api(test_dict, api_def_dict):
|
||||
}
|
||||
|
||||
"""
|
||||
# override name
|
||||
api_def_name = api_def_dict.pop("name", "")
|
||||
test_dict["name"] = test_dict.get("name") or api_def_name
|
||||
# override api name
|
||||
test_dict.setdefault("name", api_def_dict.pop("name", ""))
|
||||
|
||||
# override variables
|
||||
def_variables = api_def_dict.pop("variables", [])
|
||||
@@ -777,16 +715,12 @@ def _extend_with_api(test_dict, api_def_dict):
|
||||
|
||||
# merge & override validators TODO: relocate
|
||||
def_raw_validators = api_def_dict.pop("validate", [])
|
||||
ref_raw_validators = test_dict.get("validate", [])
|
||||
def_validators = [
|
||||
parse_validator(validator)
|
||||
for validator in def_raw_validators
|
||||
validator.uniform_validator(_validator)
|
||||
for _validator in def_raw_validators
|
||||
]
|
||||
ref_validators = [
|
||||
parse_validator(validator)
|
||||
for validator in ref_raw_validators
|
||||
]
|
||||
test_dict["validate"] = utils.extend_validators(
|
||||
ref_validators = test_dict.pop("validate", [])
|
||||
test_dict["validate"] = validator.extend_validators(
|
||||
def_validators,
|
||||
ref_validators
|
||||
)
|
||||
@@ -798,7 +732,7 @@ def _extend_with_api(test_dict, api_def_dict):
|
||||
test_dict.get("extract", {})
|
||||
)
|
||||
|
||||
# TODO: merge & override request
|
||||
# merge & override request
|
||||
test_dict["request"] = api_def_dict.pop("request", {})
|
||||
|
||||
# base_url & verify: priority api_def_dict > test_dict
|
||||
@@ -824,8 +758,6 @@ def _extend_with_api(test_dict, api_def_dict):
|
||||
# TODO: extend with other api definition items, e.g. times
|
||||
test_dict.update(api_def_dict)
|
||||
|
||||
return test_dict
|
||||
|
||||
|
||||
def _extend_with_testcase(test_dict, testcase_def_dict):
|
||||
""" extend test with testcase definition
|
||||
@@ -923,6 +855,14 @@ def __prepare_testcase_tests(tests, config, project_mapping):
|
||||
if (not test_dict.get("base_url")) and config_base_url:
|
||||
test_dict["base_url"] = config_base_url
|
||||
|
||||
# unify validators' format
|
||||
if "validate" in test_dict:
|
||||
ref_raw_validators = test_dict.pop("validate", [])
|
||||
test_dict["validate"] = [
|
||||
validator.uniform_validator(_validator)
|
||||
for _validator in ref_raw_validators
|
||||
]
|
||||
|
||||
if "testcase_def" in test_dict:
|
||||
# test_dict is nested testcase
|
||||
|
||||
@@ -954,6 +894,27 @@ def __prepare_testcase_tests(tests, config, project_mapping):
|
||||
check_variables_set = set(test_dict_variables.keys()) \
|
||||
| set(session_variables.keys()) | {"request", "response"}
|
||||
|
||||
# convert validators to lazy function
|
||||
validators = test_dict.pop("validate", [])
|
||||
prepared_validators = []
|
||||
for _validator in validators:
|
||||
function_meta = {
|
||||
"func_name": _validator["comparator"],
|
||||
"args": [
|
||||
_validator["check"],
|
||||
_validator["expect"]
|
||||
],
|
||||
"kwargs": {}
|
||||
}
|
||||
prepared_validators.append(
|
||||
LazyFunction(
|
||||
function_meta,
|
||||
functions,
|
||||
check_variables_set
|
||||
)
|
||||
)
|
||||
test_dict["validate"] = prepared_validators
|
||||
|
||||
# convert variables and functions to lazy object.
|
||||
# raises VariableNotFound if undefined variable exists in test_dict
|
||||
prepared_test_dict = prepare_lazy_data(
|
||||
|
||||
@@ -11,27 +11,40 @@ class Runner(object):
|
||||
""" Running testcases.
|
||||
|
||||
Examples:
|
||||
>>> functions={...}
|
||||
>>> config = {
|
||||
"name": "XXXX",
|
||||
"base_url": "http://127.0.0.1",
|
||||
"verify": False
|
||||
>>> tests_mapping = {
|
||||
"project_mapping": {
|
||||
"functions": {}
|
||||
},
|
||||
"testcases": [
|
||||
{
|
||||
"config": {
|
||||
"name": "XXXX",
|
||||
"base_url": "http://127.0.0.1",
|
||||
"verify": False
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
"name": "test description",
|
||||
"variables": [], # optional
|
||||
"request": {
|
||||
"url": "http://127.0.0.1:5000/api/users/1000",
|
||||
"method": "GET"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
>>> runner = Runner(config, functions)
|
||||
|
||||
>>> test_dict = {
|
||||
"name": "test description",
|
||||
"variables": [], # optional
|
||||
"request": {
|
||||
"url": "http://127.0.0.1:5000/api/users/1000",
|
||||
"method": "GET"
|
||||
}
|
||||
}
|
||||
>>> runner.run_test(test_dict)
|
||||
>>> parsed_tests_mapping = parser.parse_tests(tests_mapping)
|
||||
>>> parsed_testcase = parsed_tests_mapping["testcases"][0]
|
||||
|
||||
>>> test_runner = runner.Runner(parsed_testcase["config"])
|
||||
>>> test_runner.run_test(parsed_testcase["teststeps"][0])
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, config, functions, http_client_session=None):
|
||||
def __init__(self, config, http_client_session=None):
|
||||
""" run testcase or testsuite.
|
||||
|
||||
Args:
|
||||
@@ -50,7 +63,6 @@ class Runner(object):
|
||||
base_url = config.get("base_url")
|
||||
self.verify = config.get("verify", True)
|
||||
self.output = config.get("output", [])
|
||||
self.functions = functions
|
||||
self.validation_results = []
|
||||
|
||||
# testcase setup hooks
|
||||
@@ -59,7 +71,7 @@ class Runner(object):
|
||||
self.testcase_teardown_hooks = config.get("teardown_hooks", [])
|
||||
|
||||
self.http_client_session = http_client_session or HttpSession(base_url)
|
||||
self.session_context = SessionContext(self.functions)
|
||||
self.session_context = SessionContext()
|
||||
|
||||
if testcase_setup_hooks:
|
||||
self.do_hook_actions(testcase_setup_hooks, "setup")
|
||||
@@ -289,7 +301,7 @@ class Runner(object):
|
||||
config = testcase_dict.get("config", {})
|
||||
|
||||
# each teststeps in one testcase (YAML/JSON) share the same session.
|
||||
test_runner = Runner(config, self.functions, self.http_client_session)
|
||||
test_runner = Runner(config, self.http_client_session)
|
||||
|
||||
tests = testcase_dict.get("teststeps", [])
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ logging.getLogger('locust.runners').setLevel(logging.INFO)
|
||||
|
||||
class WebPageTasks(TaskSet):
|
||||
def on_start(self):
|
||||
self.test_runner = Runner(self.locust.config, self.locust.functions, self.client)
|
||||
self.test_runner = Runner(self.locust.config, self.client)
|
||||
|
||||
@task
|
||||
def test_any(self):
|
||||
@@ -38,7 +38,6 @@ class WebPageUser(HttpLocust):
|
||||
|
||||
file_path = "$TESTCASE_FILE"
|
||||
locust_tests = prepare_locust_tests(file_path)
|
||||
functions = locust_tests["functions"]
|
||||
tests = locust_tests["tests"]
|
||||
config = {}
|
||||
|
||||
|
||||
@@ -120,39 +120,6 @@ def query_json(json_content, query, delimiter='.'):
|
||||
return json_content
|
||||
|
||||
|
||||
def get_uniform_comparator(comparator):
|
||||
""" convert comparator alias to uniform name
|
||||
"""
|
||||
if comparator in ["eq", "equals", "==", "is"]:
|
||||
return "equals"
|
||||
elif comparator in ["lt", "less_than"]:
|
||||
return "less_than"
|
||||
elif comparator in ["le", "less_than_or_equals"]:
|
||||
return "less_than_or_equals"
|
||||
elif comparator in ["gt", "greater_than"]:
|
||||
return "greater_than"
|
||||
elif comparator in ["ge", "greater_than_or_equals"]:
|
||||
return "greater_than_or_equals"
|
||||
elif comparator in ["ne", "not_equals"]:
|
||||
return "not_equals"
|
||||
elif comparator in ["str_eq", "string_equals"]:
|
||||
return "string_equals"
|
||||
elif comparator in ["len_eq", "length_equals", "count_eq"]:
|
||||
return "length_equals"
|
||||
elif comparator in ["len_gt", "count_gt", "length_greater_than", "count_greater_than"]:
|
||||
return "length_greater_than"
|
||||
elif comparator in ["len_ge", "count_ge", "length_greater_than_or_equals", \
|
||||
"count_greater_than_or_equals"]:
|
||||
return "length_greater_than_or_equals"
|
||||
elif comparator in ["len_lt", "count_lt", "length_less_than", "count_less_than"]:
|
||||
return "length_less_than"
|
||||
elif comparator in ["len_le", "count_le", "length_less_than_or_equals", \
|
||||
"count_less_than_or_equals"]:
|
||||
return "length_less_than_or_equals"
|
||||
else:
|
||||
return comparator
|
||||
|
||||
|
||||
def deep_update_dict(origin_dict, override_dict):
|
||||
""" update origin dict with override dict recursively
|
||||
e.g. origin_dict = {'a': 1, 'b': {'c': 2, 'd': 4}}
|
||||
@@ -323,78 +290,6 @@ def ensure_mapping_format(variables):
|
||||
raise exceptions.ParamsError("variables format error!")
|
||||
|
||||
|
||||
def _convert_validators_to_mapping(validators):
|
||||
""" convert validators list to mapping.
|
||||
|
||||
Args:
|
||||
validators (list): validators in list
|
||||
|
||||
Returns:
|
||||
dict: validators mapping, use (check, comparator) as key.
|
||||
|
||||
Examples:
|
||||
>>> validators = [
|
||||
{"check": "v1", "expect": 201, "comparator": "eq"},
|
||||
{"check": {"b": 1}, "expect": 200, "comparator": "eq"}
|
||||
]
|
||||
>>> _convert_validators_to_mapping(validators)
|
||||
{
|
||||
("v1", "eq"): {"check": "v1", "expect": 201, "comparator": "eq"},
|
||||
('{"b": 1}', "eq"): {"check": {"b": 1}, "expect": 200, "comparator": "eq"}
|
||||
}
|
||||
|
||||
"""
|
||||
validators_mapping = {}
|
||||
|
||||
for validator in validators:
|
||||
if not isinstance(validator["check"], collections.Hashable):
|
||||
check = json.dumps(validator["check"])
|
||||
else:
|
||||
check = validator["check"]
|
||||
|
||||
key = (check, validator["comparator"])
|
||||
validators_mapping[key] = validator
|
||||
|
||||
return validators_mapping
|
||||
|
||||
|
||||
def extend_validators(raw_validators, override_validators):
|
||||
""" extend raw_validators with override_validators.
|
||||
override_validators will merge and override raw_validators.
|
||||
|
||||
Args:
|
||||
raw_validators (dict):
|
||||
override_validators (dict):
|
||||
|
||||
Returns:
|
||||
list: extended validators
|
||||
|
||||
Examples:
|
||||
>>> raw_validators = [{'eq': ['v1', 200]}, {"check": "s2", "expect": 16, "comparator": "len_eq"}]
|
||||
>>> override_validators = [{"check": "v1", "expect": 201}, {'len_eq': ['s3', 12]}]
|
||||
>>> extend_validators(raw_validators, override_validators)
|
||||
[
|
||||
{"check": "v1", "expect": 201, "comparator": "eq"},
|
||||
{"check": "s2", "expect": 16, "comparator": "len_eq"},
|
||||
{"check": "s3", "expect": 12, "comparator": "len_eq"}
|
||||
]
|
||||
|
||||
"""
|
||||
|
||||
if not raw_validators:
|
||||
return override_validators
|
||||
|
||||
elif not override_validators:
|
||||
return raw_validators
|
||||
|
||||
else:
|
||||
def_validators_mapping = _convert_validators_to_mapping(raw_validators)
|
||||
ref_validators_mapping = _convert_validators_to_mapping(override_validators)
|
||||
|
||||
def_validators_mapping.update(ref_validators_mapping)
|
||||
return list(def_validators_mapping.values())
|
||||
|
||||
|
||||
def extend_variables(raw_variables, override_variables):
|
||||
""" extend raw_variables with override_variables.
|
||||
override_variables will merge and override raw_variables.
|
||||
@@ -581,25 +476,6 @@ def gen_cartesian_product(*args):
|
||||
return product_list
|
||||
|
||||
|
||||
def validate_json_file(file_list):
|
||||
""" validate JSON testcase format
|
||||
"""
|
||||
for json_file in set(file_list):
|
||||
if not json_file.endswith(".json"):
|
||||
logger.log_warning("Only JSON file format can be validated, skip: {}".format(json_file))
|
||||
continue
|
||||
|
||||
logger.color_print("Start to validate JSON file: {}".format(json_file), "GREEN")
|
||||
|
||||
with io.open(json_file) as stream:
|
||||
try:
|
||||
json.load(stream)
|
||||
except ValueError as e:
|
||||
raise SystemExit(e)
|
||||
|
||||
print("OK")
|
||||
|
||||
|
||||
def prettify_json_file(file_list):
|
||||
""" prettify JSON testcase format
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# encoding: utf-8
|
||||
import collections
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import types
|
||||
|
||||
from httprunner import exceptions, logger
|
||||
|
||||
|
||||
""" validate data format
|
||||
TODO: refactor with JSON schema validate
|
||||
@@ -129,6 +134,170 @@ def is_testcase_path(path):
|
||||
return True
|
||||
|
||||
|
||||
###############################################################################
|
||||
## testcase validator utils
|
||||
###############################################################################
|
||||
|
||||
def get_uniform_comparator(comparator):
|
||||
""" convert comparator alias to uniform name
|
||||
"""
|
||||
if comparator in ["eq", "equals", "==", "is"]:
|
||||
return "equals"
|
||||
elif comparator in ["lt", "less_than"]:
|
||||
return "less_than"
|
||||
elif comparator in ["le", "less_than_or_equals"]:
|
||||
return "less_than_or_equals"
|
||||
elif comparator in ["gt", "greater_than"]:
|
||||
return "greater_than"
|
||||
elif comparator in ["ge", "greater_than_or_equals"]:
|
||||
return "greater_than_or_equals"
|
||||
elif comparator in ["ne", "not_equals"]:
|
||||
return "not_equals"
|
||||
elif comparator in ["str_eq", "string_equals"]:
|
||||
return "string_equals"
|
||||
elif comparator in ["len_eq", "length_equals", "count_eq"]:
|
||||
return "length_equals"
|
||||
elif comparator in ["len_gt", "count_gt", "length_greater_than", "count_greater_than"]:
|
||||
return "length_greater_than"
|
||||
elif comparator in ["len_ge", "count_ge", "length_greater_than_or_equals", \
|
||||
"count_greater_than_or_equals"]:
|
||||
return "length_greater_than_or_equals"
|
||||
elif comparator in ["len_lt", "count_lt", "length_less_than", "count_less_than"]:
|
||||
return "length_less_than"
|
||||
elif comparator in ["len_le", "count_le", "length_less_than_or_equals", \
|
||||
"count_less_than_or_equals"]:
|
||||
return "length_less_than_or_equals"
|
||||
else:
|
||||
return comparator
|
||||
|
||||
|
||||
def uniform_validator(validator):
|
||||
""" unify validator
|
||||
|
||||
Args:
|
||||
validator (dict): validator maybe in two formats:
|
||||
|
||||
format1: this is kept for compatiblity with the previous versions.
|
||||
{"check": "status_code", "comparator": "eq", "expect": 201}
|
||||
{"check": "$resp_body_success", "comparator": "eq", "expect": True}
|
||||
format2: recommended new version, {comparator: [check_item, expected_value]}
|
||||
{'eq': ['status_code', 201]}
|
||||
{'eq': ['$resp_body_success', True]}
|
||||
|
||||
Returns
|
||||
dict: validator info
|
||||
|
||||
{
|
||||
"check": "status_code",
|
||||
"expect": 201,
|
||||
"comparator": "equals"
|
||||
}
|
||||
|
||||
"""
|
||||
if not isinstance(validator, dict):
|
||||
raise exceptions.ParamsError("invalid validator: {}".format(validator))
|
||||
|
||||
if "check" in validator and "expect" in validator:
|
||||
# format1
|
||||
check_item = validator["check"]
|
||||
expect_value = validator["expect"]
|
||||
comparator = validator.get("comparator", "eq")
|
||||
|
||||
elif len(validator) == 1:
|
||||
# format2
|
||||
comparator = list(validator.keys())[0]
|
||||
compare_values = validator[comparator]
|
||||
|
||||
if not isinstance(compare_values, list) or len(compare_values) != 2:
|
||||
raise exceptions.ParamsError("invalid validator: {}".format(validator))
|
||||
|
||||
check_item, expect_value = compare_values
|
||||
|
||||
else:
|
||||
raise exceptions.ParamsError("invalid validator: {}".format(validator))
|
||||
|
||||
# uniform comparator, e.g. lt => less_than, eq => equals
|
||||
comparator = get_uniform_comparator(comparator)
|
||||
|
||||
return {
|
||||
"check": check_item,
|
||||
"expect": expect_value,
|
||||
"comparator": comparator
|
||||
}
|
||||
|
||||
|
||||
def _convert_validators_to_mapping(validators):
|
||||
""" convert validators list to mapping.
|
||||
|
||||
Args:
|
||||
validators (list): validators in list
|
||||
|
||||
Returns:
|
||||
dict: validators mapping, use (check, comparator) as key.
|
||||
|
||||
Examples:
|
||||
>>> validators = [
|
||||
{"check": "v1", "expect": 201, "comparator": "eq"},
|
||||
{"check": {"b": 1}, "expect": 200, "comparator": "eq"}
|
||||
]
|
||||
>>> _convert_validators_to_mapping(validators)
|
||||
{
|
||||
("v1", "eq"): {"check": "v1", "expect": 201, "comparator": "eq"},
|
||||
('{"b": 1}', "eq"): {"check": {"b": 1}, "expect": 200, "comparator": "eq"}
|
||||
}
|
||||
|
||||
"""
|
||||
validators_mapping = {}
|
||||
|
||||
for validator in validators:
|
||||
if not isinstance(validator["check"], collections.Hashable):
|
||||
check = json.dumps(validator["check"])
|
||||
else:
|
||||
check = validator["check"]
|
||||
|
||||
key = (check, validator["comparator"])
|
||||
validators_mapping[key] = validator
|
||||
|
||||
return validators_mapping
|
||||
|
||||
|
||||
def extend_validators(raw_validators, override_validators):
|
||||
""" extend raw_validators with override_validators.
|
||||
override_validators will merge and override raw_validators.
|
||||
|
||||
Args:
|
||||
raw_validators (dict):
|
||||
override_validators (dict):
|
||||
|
||||
Returns:
|
||||
list: extended validators
|
||||
|
||||
Examples:
|
||||
>>> raw_validators = [{'eq': ['v1', 200]}, {"check": "s2", "expect": 16, "comparator": "len_eq"}]
|
||||
>>> override_validators = [{"check": "v1", "expect": 201}, {'len_eq': ['s3', 12]}]
|
||||
>>> extend_validators(raw_validators, override_validators)
|
||||
[
|
||||
{"check": "v1", "expect": 201, "comparator": "eq"},
|
||||
{"check": "s2", "expect": 16, "comparator": "len_eq"},
|
||||
{"check": "s3", "expect": 12, "comparator": "len_eq"}
|
||||
]
|
||||
|
||||
"""
|
||||
|
||||
if not raw_validators:
|
||||
return override_validators
|
||||
|
||||
elif not override_validators:
|
||||
return raw_validators
|
||||
|
||||
else:
|
||||
def_validators_mapping = _convert_validators_to_mapping(raw_validators)
|
||||
ref_validators_mapping = _convert_validators_to_mapping(override_validators)
|
||||
|
||||
def_validators_mapping.update(ref_validators_mapping)
|
||||
return list(def_validators_mapping.values())
|
||||
|
||||
|
||||
###############################################################################
|
||||
## validate varibles and functions
|
||||
###############################################################################
|
||||
@@ -157,3 +326,22 @@ def is_variable(tup):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def validate_json_file(file_list):
|
||||
""" validate JSON testcase format
|
||||
"""
|
||||
for json_file in set(file_list):
|
||||
if not json_file.endswith(".json"):
|
||||
logger.log_warning("Only JSON file format can be validated, skip: {}".format(json_file))
|
||||
continue
|
||||
|
||||
logger.color_print("Start to validate JSON file: {}".format(json_file), "GREEN")
|
||||
|
||||
with io.open(json_file) as stream:
|
||||
try:
|
||||
json.load(stream)
|
||||
except ValueError as e:
|
||||
raise SystemExit(e)
|
||||
|
||||
print("OK")
|
||||
|
||||
Reference in New Issue
Block a user