mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 11:29:48 +08:00
change: format code with balck
This commit is contained in:
@@ -36,10 +36,7 @@ class HttpRunner(object):
|
||||
|
||||
"""
|
||||
self.exception_stage = "initialize HttpRunner()"
|
||||
kwargs = {
|
||||
"failfast": True,
|
||||
"resultclass": report.HtmlTestResult
|
||||
}
|
||||
kwargs = {"failfast": True, "resultclass": report.HtmlTestResult}
|
||||
|
||||
logger.remove()
|
||||
log_level = log_level.upper()
|
||||
@@ -57,6 +54,7 @@ class HttpRunner(object):
|
||||
def _add_test(test_runner: TestCaseRunner):
|
||||
""" add test to testcase.
|
||||
"""
|
||||
|
||||
def test(self):
|
||||
try:
|
||||
test_runner.run()
|
||||
@@ -79,7 +77,7 @@ class HttpRunner(object):
|
||||
|
||||
test_runner = TestCaseRunner().init(testcase)
|
||||
|
||||
TestSequense = type('TestSequense', (unittest.TestCase,), {})
|
||||
TestSequense = type("TestSequense", (unittest.TestCase,), {})
|
||||
test_method = _add_test(test_runner)
|
||||
setattr(TestSequense, "test_method_name", test_method)
|
||||
|
||||
@@ -89,7 +87,9 @@ class HttpRunner(object):
|
||||
|
||||
return prepared_testcases
|
||||
|
||||
def _run_suite(self, prepared_testcases: List[unittest.TestSuite]) -> List[TestCaseSummary]:
|
||||
def _run_suite(
|
||||
self, prepared_testcases: List[unittest.TestSuite]
|
||||
) -> List[TestCaseSummary]:
|
||||
""" run prepared testcases
|
||||
"""
|
||||
tests_results: List[TestCaseSummary] = []
|
||||
@@ -131,9 +131,7 @@ class HttpRunner(object):
|
||||
|
||||
"""
|
||||
testsuite_summary = TestSuiteSummary(
|
||||
success=True,
|
||||
platform=report.get_platform(),
|
||||
testcases=[]
|
||||
success=True, platform=report.get_platform(), testcases=[]
|
||||
)
|
||||
testsuite_summary.stat.total = len(tests_results)
|
||||
testsuite_summary.stat.success = 0
|
||||
@@ -148,11 +146,16 @@ class HttpRunner(object):
|
||||
testsuite_summary.success &= testcase_summary.success
|
||||
testsuite_summary.testcases.append(testcase_summary)
|
||||
|
||||
total_duration = tests_results[-1].time.start_at + tests_results[-1].time.duration \
|
||||
- tests_results[0].time.start_at
|
||||
total_duration = (
|
||||
tests_results[-1].time.start_at
|
||||
+ tests_results[-1].time.duration
|
||||
- tests_results[0].time.start_at
|
||||
)
|
||||
|
||||
testsuite_summary.time.start_at = tests_results[0].time.start_at
|
||||
testsuite_summary.time.start_at_iso_format = tests_results[0].time.start_at_iso_format
|
||||
testsuite_summary.time.start_at_iso_format = tests_results[
|
||||
0
|
||||
].time.start_at_iso_format
|
||||
testsuite_summary.time.duration = total_duration
|
||||
|
||||
return testsuite_summary
|
||||
@@ -166,7 +169,7 @@ class HttpRunner(object):
|
||||
if self.save_tests:
|
||||
utils.dump_json_file(
|
||||
tests_mapping,
|
||||
utils.prepare_log_file_abs_path(self.test_path, "loaded.json")
|
||||
utils.prepare_log_file_abs_path(self.test_path, "loaded.json"),
|
||||
)
|
||||
|
||||
# prepare testcases
|
||||
@@ -187,13 +190,12 @@ class HttpRunner(object):
|
||||
if self.save_tests:
|
||||
utils.dump_json_file(
|
||||
self._summary.dict(),
|
||||
utils.prepare_log_file_abs_path(self.test_path, "summary.json")
|
||||
utils.prepare_log_file_abs_path(self.test_path, "summary.json"),
|
||||
)
|
||||
# save variables and export data
|
||||
vars_out = self.get_vars_out()
|
||||
utils.dump_json_file(
|
||||
vars_out,
|
||||
utils.prepare_log_file_abs_path(self.test_path, "io.json")
|
||||
vars_out, utils.prepare_log_file_abs_path(self.test_path, "io.json")
|
||||
)
|
||||
|
||||
return self._summary
|
||||
@@ -266,7 +268,9 @@ class HttpRunner(object):
|
||||
if loader.is_test_path(path_or_tests):
|
||||
return self.run_path(path_or_tests, dot_env_path, mapping)
|
||||
|
||||
project_working_directory = path_or_tests.get("project_meta", {}).get("PWD", os.getcwd())
|
||||
project_working_directory = path_or_tests.get("project_meta", {}).get(
|
||||
"PWD", os.getcwd()
|
||||
)
|
||||
loader.init_pwd(project_working_directory)
|
||||
return self.run_tests(path_or_tests)
|
||||
|
||||
|
||||
@@ -4,12 +4,25 @@ from httprunner.api import HttpRunner
|
||||
|
||||
|
||||
class TestHttpRunner(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.runner = HttpRunner()
|
||||
|
||||
def test_run_testcase_by_path(self):
|
||||
summary = self.runner.run_path("examples/postman_echo/request_methods/")
|
||||
def test_run_testcase_by_path_request_only(self):
|
||||
summary = self.runner.run_path(
|
||||
"examples/postman_echo/request_methods/request_with_variables.yml"
|
||||
)
|
||||
self.assertTrue(summary.success)
|
||||
self.assertEqual(summary.testcases[0].name, "request methods testcase with variables")
|
||||
self.assertEqual(
|
||||
summary.testcases[0].name, "request methods testcase with variables"
|
||||
)
|
||||
self.assertGreater(summary.stat.total, 1)
|
||||
|
||||
def test_run_testcase_by_path_ref_testcase(self):
|
||||
summary = self.runner.run_path(
|
||||
"examples/postman_echo/request_methods/request_with_testcase_reference.yml"
|
||||
)
|
||||
self.assertTrue(summary.success)
|
||||
self.assertEqual(
|
||||
summary.testcases[0].name, "request methods testcase with variables"
|
||||
)
|
||||
self.assertGreater(summary.stat.total, 1)
|
||||
|
||||
@@ -8,13 +8,7 @@ app = FastAPI()
|
||||
|
||||
@app.get("/hrun/version")
|
||||
async def get_hrun_version():
|
||||
return {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"result": {
|
||||
"HttpRunner": __version__
|
||||
}
|
||||
}
|
||||
return {"code": 0, "message": "success", "result": {"HttpRunner": __version__}}
|
||||
|
||||
|
||||
app.include_router(deps.router)
|
||||
|
||||
@@ -9,11 +9,7 @@ runner = HttpRunner()
|
||||
|
||||
@router.post("/hrun/debug/testcase", tags=["debug"])
|
||||
async def debug_single_testcase(project_meta: ProjectMeta, testcase: TestCase):
|
||||
resp = {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"result": {}
|
||||
}
|
||||
resp = {"code": 0, "message": "success", "result": {}}
|
||||
|
||||
project_meta_json = project_meta.dict(by_alias=True)
|
||||
if project_meta.debugtalk_py:
|
||||
@@ -27,10 +23,7 @@ async def debug_single_testcase(project_meta: ProjectMeta, testcase: TestCase):
|
||||
project_meta_json["functions"][func_name] = locals()[func_name]
|
||||
|
||||
testcase_json = testcase.dict(by_alias=True)
|
||||
tests_mapping = {
|
||||
"project_meta": project_meta_json,
|
||||
"testcases": [testcase_json]
|
||||
}
|
||||
tests_mapping = {"project_meta": project_meta_json, "testcases": [testcase_json]}
|
||||
|
||||
summary = runner.run_tests(tests_mapping)
|
||||
if not summary["success"]:
|
||||
|
||||
@@ -8,13 +8,12 @@ client = TestClient(app)
|
||||
|
||||
|
||||
class TestDebug(unittest.TestCase):
|
||||
|
||||
def test_debug_single_testcase(self):
|
||||
json_data = {
|
||||
"project_meta": {
|
||||
"debugtalk_py": "\ndef hello(name):\n print(f'hello, {name}')\n",
|
||||
"variables": {},
|
||||
"env": {}
|
||||
"env": {},
|
||||
},
|
||||
"testcase": {
|
||||
"config": {
|
||||
@@ -24,7 +23,7 @@ class TestDebug(unittest.TestCase):
|
||||
"variables": {},
|
||||
"setup_hooks": [],
|
||||
"teardown_hooks": [],
|
||||
"export": []
|
||||
"export": [],
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
@@ -38,13 +37,13 @@ class TestDebug(unittest.TestCase):
|
||||
"cookies": {},
|
||||
"timeout": 30,
|
||||
"allow_redirects": True,
|
||||
"verify": False
|
||||
"verify": False,
|
||||
},
|
||||
"extract": {},
|
||||
"validate": []
|
||||
"validate": [],
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
response = client.post("/hrun/debug/testcase", json=json_data)
|
||||
assert response.status_code == 200
|
||||
|
||||
@@ -23,15 +23,11 @@ def stdout_io(stdout=None):
|
||||
async def debug_python(request: Request):
|
||||
body = await request.body()
|
||||
|
||||
if request.headers.get('content-transfer-encoding') == "base64":
|
||||
if request.headers.get("content-transfer-encoding") == "base64":
|
||||
# TODO: decode base64
|
||||
pass
|
||||
|
||||
resp = {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"result": ""
|
||||
}
|
||||
resp = {"code": 0, "message": "success", "result": ""}
|
||||
try:
|
||||
with stdout_io() as s:
|
||||
exec(body, globals())
|
||||
|
||||
@@ -10,11 +10,7 @@ router = APIRouter()
|
||||
|
||||
@router.get("/hrun/deps", tags=["deps"])
|
||||
async def get_installed_dependenies():
|
||||
resp = {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"result": {}
|
||||
}
|
||||
resp = {"code": 0, "message": "success", "result": {}}
|
||||
for p in pkg_resources.working_set:
|
||||
resp["result"][p.project_name] = p.version
|
||||
|
||||
@@ -23,11 +19,7 @@ async def get_installed_dependenies():
|
||||
|
||||
@router.post("/hrun/deps", tags=["deps"])
|
||||
async def install_dependenies(deps: List[str]):
|
||||
resp = {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"result": {}
|
||||
}
|
||||
resp = {"code": 0, "message": "success", "result": {}}
|
||||
for dep in deps:
|
||||
try:
|
||||
p = subprocess.run(["pip", "install", dep])
|
||||
|
||||
@@ -13,8 +13,9 @@ from httprunner.exceptions import ParamsError
|
||||
def gen_random_string(str_len):
|
||||
""" generate random string with specified length
|
||||
"""
|
||||
return ''.join(
|
||||
random.choice(string.ascii_letters + string.digits) for _ in range(str_len))
|
||||
return "".join(
|
||||
random.choice(string.ascii_letters + string.digits) for _ in range(str_len)
|
||||
)
|
||||
|
||||
|
||||
def get_timestamp(str_len=13):
|
||||
@@ -36,4 +37,3 @@ def sleep(n_secs):
|
||||
""" sleep n seconds
|
||||
"""
|
||||
time.sleep(n_secs)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ if len(sys.argv) >= 2 and sys.argv[1] == "locusts":
|
||||
# monkey patch ssl at beginning to avoid RecursionError when running locust.
|
||||
try:
|
||||
from gevent import monkey
|
||||
|
||||
monkey.patch_ssl()
|
||||
from locust.main import main as _
|
||||
except ImportError:
|
||||
@@ -27,42 +28,42 @@ from httprunner.ext.locusts import init_parser_locusts, main_locusts
|
||||
|
||||
|
||||
def init_parser_run(subparsers):
|
||||
sub_parser_run = subparsers.add_parser(
|
||||
"run", help="Run HttpRunner testcases.")
|
||||
sub_parser_run = subparsers.add_parser("run", help="Run HttpRunner testcases.")
|
||||
|
||||
sub_parser_run.add_argument(
|
||||
'testfile_paths', nargs='*',
|
||||
help="Specify api/testcase/testsuite file paths to run.")
|
||||
"testfile_paths",
|
||||
nargs="*",
|
||||
help="Specify api/testcase/testsuite file paths to run.",
|
||||
)
|
||||
sub_parser_run.add_argument(
|
||||
'--log-level', default='INFO',
|
||||
help="Specify logging level, default is INFO.")
|
||||
"--log-level", default="INFO", help="Specify logging level, default is INFO."
|
||||
)
|
||||
sub_parser_run.add_argument("--log-file", help="Write logs to specified file path.")
|
||||
sub_parser_run.add_argument(
|
||||
'--log-file',
|
||||
help="Write logs to specified file path.")
|
||||
"--dot-env-path",
|
||||
help="Specify .env file path, which is useful for keeping sensitive data.",
|
||||
)
|
||||
sub_parser_run.add_argument(
|
||||
'--dot-env-path',
|
||||
help="Specify .env file path, which is useful for keeping sensitive data.")
|
||||
"--report-template", help="Specify report template path."
|
||||
)
|
||||
sub_parser_run.add_argument("--report-dir", help="Specify report save directory.")
|
||||
sub_parser_run.add_argument(
|
||||
'--report-template',
|
||||
help="Specify report template path.")
|
||||
"--report-file",
|
||||
help="Specify report file path, this has higher priority than specifying report dir.",
|
||||
)
|
||||
sub_parser_run.add_argument(
|
||||
'--report-dir',
|
||||
help="Specify report save directory.")
|
||||
sub_parser_run.add_argument(
|
||||
'--report-file',
|
||||
help="Specify report file path, this has higher priority than specifying report dir.")
|
||||
sub_parser_run.add_argument(
|
||||
'--save-tests', action='store_true', default=False,
|
||||
help="Save loaded/parsed/vars_out/summary json data to JSON files.")
|
||||
"--save-tests",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Save loaded/parsed/vars_out/summary json data to JSON files.",
|
||||
)
|
||||
|
||||
return sub_parser_run
|
||||
|
||||
|
||||
def main_run(args):
|
||||
runner = HttpRunner(
|
||||
save_tests=args.save_tests,
|
||||
log_level=args.log_level,
|
||||
log_file=args.log_file
|
||||
save_tests=args.save_tests, log_level=args.log_level, log_file=args.log_file
|
||||
)
|
||||
|
||||
err_code = 0
|
||||
@@ -73,11 +74,13 @@ def main_run(args):
|
||||
runner.gen_html_report(
|
||||
report_template=args.report_template,
|
||||
report_dir=report_dir,
|
||||
report_file=args.report_file
|
||||
report_file=args.report_file,
|
||||
)
|
||||
err_code |= (0 if testsuite_summary and testsuite_summary.success else 1)
|
||||
err_code |= 0 if testsuite_summary and testsuite_summary.success else 1
|
||||
except Exception as ex:
|
||||
logger.error(f"!!!!!!!!!! exception stage: {runner.exception_stage} !!!!!!!!!!\n{str(ex)}")
|
||||
logger.error(
|
||||
f"!!!!!!!!!! exception stage: {runner.exception_stage} !!!!!!!!!!\n{str(ex)}"
|
||||
)
|
||||
err_code = 1
|
||||
|
||||
sys.exit(err_code)
|
||||
@@ -88,10 +91,10 @@ def main():
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description=__description__)
|
||||
parser.add_argument(
|
||||
'-V', '--version', dest='version', action='store_true',
|
||||
help="show version")
|
||||
"-V", "--version", dest="version", action="store_true", help="show version"
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(help='sub-command help')
|
||||
subparsers = parser.add_subparsers(help="sub-command help")
|
||||
sub_parser_run = init_parser_run(subparsers)
|
||||
sub_parser_scaffold = init_parser_scaffold(subparsers)
|
||||
sub_parser_har2case = init_har2case_parser(subparsers)
|
||||
@@ -153,5 +156,5 @@ def main_hrun_alias():
|
||||
main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -6,7 +6,6 @@ from httprunner.cli import main
|
||||
|
||||
|
||||
class TestCli(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.captured_output = io.StringIO()
|
||||
sys.stdout = self.captured_output
|
||||
@@ -23,6 +22,7 @@ class TestCli(unittest.TestCase):
|
||||
self.assertEqual(cm.exception.code, 0)
|
||||
|
||||
from httprunner import __version__
|
||||
|
||||
self.assertIn(__version__, self.captured_output.getvalue().strip())
|
||||
|
||||
def test_show_help(self):
|
||||
@@ -34,4 +34,5 @@ class TestCli(unittest.TestCase):
|
||||
self.assertEqual(cm.exception.code, 0)
|
||||
|
||||
from httprunner import __description__
|
||||
|
||||
self.assertIn(__description__, self.captured_output.getvalue().strip())
|
||||
|
||||
@@ -4,8 +4,12 @@ import requests
|
||||
import urllib3
|
||||
from loguru import logger
|
||||
from requests import Request, Response
|
||||
from requests.exceptions import (InvalidSchema, InvalidURL, MissingSchema,
|
||||
RequestException)
|
||||
from requests.exceptions import (
|
||||
InvalidSchema,
|
||||
InvalidURL,
|
||||
MissingSchema,
|
||||
RequestException,
|
||||
)
|
||||
|
||||
from httprunner.schema import RequestData, ResponseData
|
||||
from httprunner.schema import SessionData, ReqRespData
|
||||
@@ -15,9 +19,8 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
|
||||
class ApiResponse(Response):
|
||||
|
||||
def raise_for_status(self):
|
||||
if hasattr(self, 'error') and self.error:
|
||||
if hasattr(self, "error") and self.error:
|
||||
raise self.error
|
||||
Response.raise_for_status(self)
|
||||
|
||||
@@ -25,6 +28,7 @@ class ApiResponse(Response):
|
||||
def get_req_resp_record(resp_obj: Response) -> ReqRespData:
|
||||
""" get request and response info from Response() object.
|
||||
"""
|
||||
|
||||
def log_print(req_or_resp, r_type):
|
||||
msg = f"\n================== {r_type} details ==================\n"
|
||||
for key, value in req_or_resp.dict().items():
|
||||
@@ -36,14 +40,12 @@ def get_req_resp_record(resp_obj: Response) -> ReqRespData:
|
||||
method=resp_obj.request.method,
|
||||
url=resp_obj.request.url,
|
||||
headers=dict(resp_obj.request.headers),
|
||||
body=resp_obj.request.body
|
||||
body=resp_obj.request.body,
|
||||
)
|
||||
|
||||
request_body = resp_obj.request.body
|
||||
if request_body:
|
||||
request_content_type = lower_dict_keys(
|
||||
request_data.headers
|
||||
).get("content-type")
|
||||
request_content_type = lower_dict_keys(request_data.headers).get("content-type")
|
||||
if request_content_type and "multipart/form-data" in request_content_type:
|
||||
# upload file type
|
||||
request_data.body = "upload file stream (OMITTED)"
|
||||
@@ -76,16 +78,13 @@ def get_req_resp_record(resp_obj: Response) -> ReqRespData:
|
||||
encoding=resp_obj.encoding,
|
||||
headers=resp_headers,
|
||||
content_type=content_type,
|
||||
body=response_body
|
||||
body=response_body,
|
||||
)
|
||||
|
||||
# log response details in debug mode
|
||||
log_print(response_data, "response")
|
||||
|
||||
req_resp_data = ReqRespData(
|
||||
request=request_data,
|
||||
response=response_data
|
||||
)
|
||||
req_resp_data = ReqRespData(request=request_data, response=response_data)
|
||||
return req_resp_data
|
||||
|
||||
|
||||
@@ -98,6 +97,7 @@ class HttpSession(requests.Session):
|
||||
This is a slightly extended version of `python-request <http://python-requests.org>`_'s
|
||||
:py:class:`requests.Session` class and mostly this class works exactly the same.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(HttpSession, self).__init__()
|
||||
self.data = SessionData()
|
||||
@@ -173,8 +173,7 @@ class HttpSession(requests.Session):
|
||||
# record request and response histories, include 30X redirection
|
||||
response_list = response.history + [response]
|
||||
self.data.req_resps = [
|
||||
get_req_resp_record(resp_obj)
|
||||
for resp_obj in response_list
|
||||
get_req_resp_record(resp_obj) for resp_obj in response_list
|
||||
]
|
||||
|
||||
try:
|
||||
|
||||
@@ -20,19 +20,27 @@ def init_har2case_parser(subparsers):
|
||||
""" HAR converter: parse command line options and run commands.
|
||||
"""
|
||||
parser = subparsers.add_parser(
|
||||
"har2case", help="Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner.")
|
||||
parser.add_argument('har_source_file', nargs='?',
|
||||
help="Specify HAR source file")
|
||||
"har2case",
|
||||
help="Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner.",
|
||||
)
|
||||
parser.add_argument("har_source_file", nargs="?", help="Specify HAR source file")
|
||||
parser.add_argument(
|
||||
'-2y', '--to-yml', '--to-yaml',
|
||||
dest='to_yaml', action='store_true',
|
||||
help="Convert to YAML format, if not specified, convert to JSON format by default.")
|
||||
"-2y",
|
||||
"--to-yml",
|
||||
"--to-yaml",
|
||||
dest="to_yaml",
|
||||
action="store_true",
|
||||
help="Convert to YAML format, if not specified, convert to JSON format by default.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--filter', help="Specify filter keyword, only url include filter string will be converted.")
|
||||
"--filter",
|
||||
help="Specify filter keyword, only url include filter string will be converted.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--exclude',
|
||||
"--exclude",
|
||||
help="Specify exclude keyword, url that includes exclude string will be ignored, "
|
||||
"multiple keywords can be joined with '|'")
|
||||
"multiple keywords can be joined with '|'",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
@@ -48,8 +56,6 @@ def main_har2case(args):
|
||||
sys.exit(1)
|
||||
|
||||
output_file_type = "YML" if args.to_yaml else "JSON"
|
||||
HarParser(
|
||||
har_source_file, args.filter, args.exclude
|
||||
).gen_testcase(output_file_type)
|
||||
HarParser(har_source_file, args.filter, args.exclude).gen_testcase(output_file_type)
|
||||
|
||||
return 0
|
||||
|
||||
@@ -28,12 +28,11 @@ IGNORE_REQUEST_HEADERS = [
|
||||
":authority",
|
||||
":method",
|
||||
":scheme",
|
||||
":path"
|
||||
":path",
|
||||
]
|
||||
|
||||
|
||||
class HarParser(object):
|
||||
|
||||
def __init__(self, har_file_path, filter_str=None, exclude_str=None):
|
||||
self.har_file_path = har_file_path
|
||||
self.filter_str = filter_str
|
||||
@@ -76,7 +75,7 @@ class HarParser(object):
|
||||
|
||||
parsed_object = urlparse.urlparse(url)
|
||||
if request_params:
|
||||
parsed_object = parsed_object._replace(query='')
|
||||
parsed_object = parsed_object._replace(query="")
|
||||
teststep_dict["request"]["url"] = parsed_object.geturl()
|
||||
teststep_dict["request"]["params"] = request_params
|
||||
else:
|
||||
@@ -241,7 +240,7 @@ class HarParser(object):
|
||||
|
||||
encoding = resp_content_dict.get("encoding")
|
||||
if encoding and encoding == "base64":
|
||||
content = base64.b64decode(text).decode('utf-8')
|
||||
content = base64.b64decode(text).decode("utf-8")
|
||||
else:
|
||||
content = text
|
||||
|
||||
@@ -249,7 +248,9 @@ class HarParser(object):
|
||||
resp_content_json = json.loads(content)
|
||||
except JSONDecodeError:
|
||||
logger.warning(
|
||||
"response content can not be loaded as json: {}".format(content.encode("utf-8"))
|
||||
"response content can not be loaded as json: {}".format(
|
||||
content.encode("utf-8")
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
@@ -285,11 +286,7 @@ class HarParser(object):
|
||||
}
|
||||
|
||||
"""
|
||||
teststep_dict = {
|
||||
"name": "",
|
||||
"request": {},
|
||||
"validate": []
|
||||
}
|
||||
teststep_dict = {"name": "", "request": {}, "validate": []}
|
||||
|
||||
self.__make_request_url(teststep_dict, entry_json)
|
||||
self.__make_request_method(teststep_dict, entry_json)
|
||||
@@ -302,16 +299,14 @@ class HarParser(object):
|
||||
def _prepare_config(self):
|
||||
""" prepare config block.
|
||||
"""
|
||||
return {
|
||||
"name": "testcase description",
|
||||
"variables": {}
|
||||
}
|
||||
return {"name": "testcase description", "variables": {}}
|
||||
|
||||
def _prepare_teststeps(self):
|
||||
""" make teststep list.
|
||||
teststeps list are parsed from HAR log entries list.
|
||||
|
||||
"""
|
||||
|
||||
def is_exclude(url, exclude_str):
|
||||
exclude_str_list = exclude_str.split("|")
|
||||
for exclude_str in exclude_str_list:
|
||||
@@ -330,9 +325,7 @@ class HarParser(object):
|
||||
if is_exclude(url, self.exclude_str):
|
||||
continue
|
||||
|
||||
teststeps.append(
|
||||
self._prepare_teststep(entry_json)
|
||||
)
|
||||
teststeps.append(self._prepare_teststep(entry_json))
|
||||
|
||||
return teststeps
|
||||
|
||||
@@ -344,10 +337,7 @@ class HarParser(object):
|
||||
config = self._prepare_config()
|
||||
teststeps = self._prepare_teststeps()
|
||||
|
||||
testcase = {
|
||||
"config": config,
|
||||
"teststeps": teststeps
|
||||
}
|
||||
testcase = {"config": config, "teststeps": teststeps}
|
||||
return testcase
|
||||
|
||||
def gen_testcase(self, file_type="JSON"):
|
||||
|
||||
@@ -6,7 +6,6 @@ from httprunner.ext.har2case.utils_test import TestUtils
|
||||
|
||||
|
||||
class TestHar(TestUtils):
|
||||
|
||||
def setUp(self):
|
||||
self.har_path = os.path.join(os.path.dirname(__file__), "data", "demo.har")
|
||||
self.har_parser = HarParser(self.har_path)
|
||||
@@ -22,18 +21,10 @@ class TestHar(TestUtils):
|
||||
validator["eq"][0]: validator["eq"][1]
|
||||
for validator in teststep_dict["validate"]
|
||||
}
|
||||
self.assertEqual(
|
||||
validators_mapping["status_code"], 200
|
||||
)
|
||||
self.assertEqual(
|
||||
validators_mapping["content.IsSuccess"], True
|
||||
)
|
||||
self.assertEqual(
|
||||
validators_mapping["content.Code"], 200
|
||||
)
|
||||
self.assertEqual(
|
||||
validators_mapping["content.Message"], None
|
||||
)
|
||||
self.assertEqual(validators_mapping["status_code"], 200)
|
||||
self.assertEqual(validators_mapping["content.IsSuccess"], True)
|
||||
self.assertEqual(validators_mapping["content.Code"], 200)
|
||||
self.assertEqual(validators_mapping["content.Message"], None)
|
||||
|
||||
def test_prepare_teststeps(self):
|
||||
teststeps = self.har_parser._prepare_teststeps()
|
||||
@@ -43,16 +34,14 @@ class TestHar(TestUtils):
|
||||
self.assertIn("validate", teststeps[0])
|
||||
|
||||
def test_gen_testcase_yaml(self):
|
||||
yaml_file = os.path.join(
|
||||
os.path.dirname(__file__), "data", "demo.yaml")
|
||||
yaml_file = os.path.join(os.path.dirname(__file__), "data", "demo.yaml")
|
||||
|
||||
self.har_parser.gen_testcase(file_type="YAML")
|
||||
self.assertTrue(os.path.isfile(yaml_file))
|
||||
os.remove(yaml_file)
|
||||
|
||||
def test_gen_testcase_json(self):
|
||||
json_file = os.path.join(
|
||||
os.path.dirname(__file__), "data", "demo.json")
|
||||
json_file = os.path.join(os.path.dirname(__file__), "data", "demo.json")
|
||||
|
||||
self.har_parser.gen_testcase(file_type="JSON")
|
||||
self.assertTrue(os.path.isfile(json_file))
|
||||
@@ -64,7 +53,7 @@ class TestHar(TestUtils):
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(
|
||||
teststeps[0]["request"]["url"],
|
||||
"https://httprunner.top/api/v1/Account/Login"
|
||||
"https://httprunner.top/api/v1/Account/Login",
|
||||
)
|
||||
|
||||
filter_str = "debugtalk"
|
||||
@@ -78,7 +67,7 @@ class TestHar(TestUtils):
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(
|
||||
teststeps[0]["request"]["url"],
|
||||
"https://httprunner.top/api/v1/Account/Login"
|
||||
"https://httprunner.top/api/v1/Account/Login",
|
||||
)
|
||||
|
||||
exclude_str = "httprunner"
|
||||
@@ -98,20 +87,13 @@ class TestHar(TestUtils):
|
||||
self.assertEqual(teststeps, [])
|
||||
|
||||
def test_make_request_data_params(self):
|
||||
testcase_dict = {
|
||||
"name": "",
|
||||
"request": {},
|
||||
"validate": []
|
||||
}
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {
|
||||
"mimeType": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"params": [
|
||||
{"name": "a", "value": 1},
|
||||
{"name": "b", "value": "2"}
|
||||
]
|
||||
"params": [{"name": "a", "value": 1}, {"name": "b", "value": "2"}],
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -120,53 +102,32 @@ class TestHar(TestUtils):
|
||||
self.assertEqual(testcase_dict["request"]["data"]["b"], "2")
|
||||
|
||||
def test_make_request_data_json(self):
|
||||
testcase_dict = {
|
||||
"name": "",
|
||||
"request": {},
|
||||
"validate": []
|
||||
}
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {
|
||||
"mimeType": "application/json; charset=utf-8",
|
||||
"text": "{\"a\":\"1\",\"b\":\"2\"}"
|
||||
"text": '{"a":"1","b":"2"}',
|
||||
},
|
||||
}
|
||||
}
|
||||
self.har_parser._make_request_data(testcase_dict, entry_json)
|
||||
self.assertEqual(
|
||||
testcase_dict["request"]["json"],
|
||||
{'a': '1', 'b': '2'}
|
||||
)
|
||||
self.assertEqual(testcase_dict["request"]["json"], {"a": "1", "b": "2"})
|
||||
|
||||
def test_make_request_data_text_empty(self):
|
||||
testcase_dict = {
|
||||
"name": "",
|
||||
"request": {},
|
||||
"validate": []
|
||||
}
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {
|
||||
"mimeType": "application/json; charset=utf-8",
|
||||
"text": ""
|
||||
},
|
||||
"postData": {"mimeType": "application/json; charset=utf-8", "text": ""},
|
||||
}
|
||||
}
|
||||
self.har_parser._make_request_data(testcase_dict, entry_json)
|
||||
self.assertEqual(
|
||||
testcase_dict["request"]["data"],
|
||||
""
|
||||
)
|
||||
self.assertEqual(testcase_dict["request"]["data"], "")
|
||||
|
||||
def test_make_validate(self):
|
||||
testcase_dict = {
|
||||
"name": "",
|
||||
"request": {},
|
||||
"validate": []
|
||||
}
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {},
|
||||
"response": {
|
||||
@@ -174,7 +135,7 @@ class TestHar(TestUtils):
|
||||
"headers": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json; charset=utf-8"
|
||||
"value": "application/json; charset=utf-8",
|
||||
},
|
||||
],
|
||||
"content": {
|
||||
@@ -182,23 +143,21 @@ class TestHar(TestUtils):
|
||||
"mimeType": "application/json; charset=utf-8",
|
||||
# raw response content text is application/jose type
|
||||
"text": "ZXlKaGJHY2lPaUpTVTBFeFh6VWlMQ0psYm1NaU9pSkJNVEk0UTBKRExV",
|
||||
"encoding": "base64"
|
||||
}
|
||||
}
|
||||
"encoding": "base64",
|
||||
},
|
||||
},
|
||||
}
|
||||
self.har_parser._make_validate(testcase_dict, entry_json)
|
||||
self.assertEqual(
|
||||
testcase_dict["validate"][0],
|
||||
{"eq": ["status_code", 200]}
|
||||
)
|
||||
self.assertEqual(testcase_dict["validate"][0], {"eq": ["status_code", 200]})
|
||||
self.assertEqual(
|
||||
testcase_dict["validate"][1],
|
||||
{"eq": ["headers.Content-Type", "application/json; charset=utf-8"]}
|
||||
{"eq": ["headers.Content-Type", "application/json; charset=utf-8"]},
|
||||
)
|
||||
|
||||
def test_make_testcase(self):
|
||||
har_path = os.path.join(
|
||||
os.path.dirname(__file__), "data", "demo-quickstart.har")
|
||||
os.path.dirname(__file__), "data", "demo-quickstart.har"
|
||||
)
|
||||
har_parser = HarParser(har_path)
|
||||
testcase = har_parser._make_testcase()
|
||||
self.assertIsInstance(testcase, dict)
|
||||
|
||||
@@ -50,10 +50,9 @@ def x_www_form_urlencoded(post_data):
|
||||
|
||||
"""
|
||||
if isinstance(post_data, dict):
|
||||
return "&".join([
|
||||
u"{}={}".format(key, value)
|
||||
for key, value in post_data.items()
|
||||
])
|
||||
return "&".join(
|
||||
[u"{}={}".format(key, value) for key, value in post_data.items()]
|
||||
)
|
||||
else:
|
||||
return post_data
|
||||
|
||||
@@ -98,10 +97,7 @@ def convert_list_to_dict(origin_list):
|
||||
{"v": "1", "w": "2"}
|
||||
|
||||
"""
|
||||
return {
|
||||
item["name"]: item.get("value")
|
||||
for item in origin_list
|
||||
}
|
||||
return {item["name"]: item.get("value") for item in origin_list}
|
||||
|
||||
|
||||
def dump_yaml(testcase, yaml_file):
|
||||
@@ -109,8 +105,10 @@ def dump_yaml(testcase, yaml_file):
|
||||
"""
|
||||
logging.info("dump testcase to YAML format.")
|
||||
|
||||
with io.open(yaml_file, 'w', encoding="utf-8") as outfile:
|
||||
yaml.dump(testcase, outfile, allow_unicode=True, default_flow_style=False, indent=4)
|
||||
with io.open(yaml_file, "w", encoding="utf-8") as outfile:
|
||||
yaml.dump(
|
||||
testcase, outfile, allow_unicode=True, default_flow_style=False, indent=4
|
||||
)
|
||||
|
||||
logging.info("Generate YAML testcase successfully: {}".format(yaml_file))
|
||||
|
||||
@@ -120,7 +118,7 @@ def dump_json(testcase, json_file):
|
||||
"""
|
||||
logging.info("dump testcase to JSON format.")
|
||||
|
||||
with io.open(json_file, 'w', encoding="utf-8") as outfile:
|
||||
with io.open(json_file, "w", encoding="utf-8") as outfile:
|
||||
my_json_str = json.dumps(testcase, ensure_ascii=False, indent=4)
|
||||
if isinstance(my_json_str, bytes):
|
||||
my_json_str = my_json_str.decode("utf-8")
|
||||
|
||||
@@ -6,11 +6,11 @@ from httprunner.ext.har2case import utils
|
||||
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
|
||||
@staticmethod
|
||||
def create_har_file(file_name, content):
|
||||
file_path = os.path.join(
|
||||
os.path.dirname(__file__), "data", "{}.har".format(file_name))
|
||||
os.path.dirname(__file__), "data", "{}.har".format(file_name)
|
||||
)
|
||||
with open(file_path, "w") as f:
|
||||
f.write(json.dumps(content))
|
||||
|
||||
@@ -24,7 +24,9 @@ class TestUtils(unittest.TestCase):
|
||||
self.assertIn("response", log_entries[0])
|
||||
|
||||
def test_load_har_log_key_error(self):
|
||||
empty_json_file_path = TestUtils.create_har_file(file_name="empty_json", content={})
|
||||
empty_json_file_path = TestUtils.create_har_file(
|
||||
file_name="empty_json", content={}
|
||||
)
|
||||
with self.assertRaises(SystemExit):
|
||||
utils.load_har_log_entries(empty_json_file_path)
|
||||
os.remove(empty_json_file_path)
|
||||
@@ -35,21 +37,14 @@ class TestUtils(unittest.TestCase):
|
||||
utils.load_har_log_entries(empty_file_path)
|
||||
os.remove(empty_file_path)
|
||||
|
||||
|
||||
# def test_x_www_form_urlencoded(self):
|
||||
# origin_dict = {"a":1, "b": "2"}
|
||||
# self.assertIn("a=1", utils.x_www_form_urlencoded(origin_dict))
|
||||
# self.assertIn("b=2", utils.x_www_form_urlencoded(origin_dict))
|
||||
|
||||
def test_convert_list_to_dict(self):
|
||||
origin_list = [
|
||||
{"name": "v", "value": "1"},
|
||||
{"name": "w", "value": "2"}
|
||||
]
|
||||
self.assertEqual(
|
||||
utils.convert_list_to_dict(origin_list),
|
||||
{"v": "1", "w": "2"}
|
||||
)
|
||||
origin_list = [{"name": "v", "value": "1"}, {"name": "w", "value": "2"}]
|
||||
self.assertEqual(utils.convert_list_to_dict(origin_list), {"v": "1", "w": "2"})
|
||||
|
||||
def test_convert_x_www_form_urlencoded_to_dict(self):
|
||||
origin_str = "a=1&b=2"
|
||||
|
||||
@@ -4,27 +4,39 @@ import sys
|
||||
from loguru import logger
|
||||
|
||||
from httprunner import __version__
|
||||
from httprunner.ext.locusts.core import start_locust_main, parse_locustfile, quick_run_locusts, start_master, \
|
||||
start_slaves
|
||||
from httprunner.ext.locusts.core import (
|
||||
start_locust_main,
|
||||
parse_locustfile,
|
||||
quick_run_locusts,
|
||||
start_master,
|
||||
start_slaves,
|
||||
)
|
||||
|
||||
CPU_COUNT = multiprocessing.cpu_count()
|
||||
|
||||
|
||||
def init_parser_locusts(subparsers):
|
||||
sub_parser_locusts = subparsers.add_parser(
|
||||
"locusts", help="Run load test with locust.")
|
||||
"locusts", help="Run load test with locust."
|
||||
)
|
||||
sub_parser_locusts.add_argument(
|
||||
'--locust-help', action='store_true', default=False,
|
||||
help="Show locust help.")
|
||||
sub_parser_locusts.add_argument('test_file', nargs='?',
|
||||
help="Specify YAML/JSON testcase file.")
|
||||
"--locust-help", action="store_true", default=False, help="Show locust help."
|
||||
)
|
||||
sub_parser_locusts.add_argument(
|
||||
"--master", action='store_true', default=False, help="Start locust master.")
|
||||
"test_file", nargs="?", help="Specify YAML/JSON testcase file."
|
||||
)
|
||||
sub_parser_locusts.add_argument(
|
||||
"--slaves", type=int, help="Specify locust slave number.")
|
||||
"--master", action="store_true", default=False, help="Start locust master."
|
||||
)
|
||||
sub_parser_locusts.add_argument(
|
||||
"--quickstart", action='store_true', default=False,
|
||||
help=f"Start locust master with {CPU_COUNT} slaves.")
|
||||
"--slaves", type=int, help="Specify locust slave number."
|
||||
)
|
||||
sub_parser_locusts.add_argument(
|
||||
"--quickstart",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help=f"Start locust master with {CPU_COUNT} slaves.",
|
||||
)
|
||||
return sub_parser_locusts
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ def parse_locustfile(file_path):
|
||||
file_suffix = os.path.splitext(file_path)[1]
|
||||
if file_suffix == ".py":
|
||||
locustfile_path = file_path
|
||||
elif file_suffix in ['.yaml', '.yml', '.json']:
|
||||
elif file_suffix in [".yaml", ".yml", ".json"]:
|
||||
locustfile_path = gen_locustfile(file_path)
|
||||
else:
|
||||
# '' or other suffix
|
||||
@@ -31,16 +31,17 @@ def parse_locustfile(file_path):
|
||||
def gen_locustfile(testcase_file_path):
|
||||
""" generate locustfile from template.
|
||||
"""
|
||||
locustfile_path = 'locustfile.py'
|
||||
locustfile_path = "locustfile.py"
|
||||
template_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
"locustfile_template.py"
|
||||
os.path.dirname(os.path.realpath(__file__)), "locustfile_template.py"
|
||||
)
|
||||
|
||||
with io.open(template_path, encoding='utf-8') as template:
|
||||
with io.open(locustfile_path, 'w', encoding='utf-8') as locustfile:
|
||||
with io.open(template_path, encoding="utf-8") as template:
|
||||
with io.open(locustfile_path, "w", encoding="utf-8") as locustfile:
|
||||
template_content = template.read()
|
||||
template_content = template_content.replace("$TESTCASE_FILE", testcase_file_path)
|
||||
template_content = template_content.replace(
|
||||
"$TESTCASE_FILE", testcase_file_path
|
||||
)
|
||||
locustfile.write(template_content)
|
||||
|
||||
return locustfile_path
|
||||
@@ -49,6 +50,7 @@ def gen_locustfile(testcase_file_path):
|
||||
def start_locust_main():
|
||||
logger.info(f"run command: {sys.argv}")
|
||||
from locust.main import main
|
||||
|
||||
main()
|
||||
|
||||
|
||||
@@ -94,7 +96,5 @@ def quick_run_locusts(slave_num):
|
||||
logger.info(f"Start locust master with {slave_num} slaves ...")
|
||||
|
||||
processes = init_slave_processes(slave_num)
|
||||
processes.append(
|
||||
multiprocessing.Process(target=start_master, args=(sys.argv,))
|
||||
)
|
||||
processes.append(multiprocessing.Process(target=start_master, args=(sys.argv,)))
|
||||
[process.join() for process in processes]
|
||||
|
||||
@@ -9,8 +9,8 @@ from httprunner.ext.locusts.utils import prepare_locust_tests
|
||||
from httprunner.runner import Runner
|
||||
|
||||
logging.getLogger().setLevel(logging.CRITICAL)
|
||||
logging.getLogger('locust.main').setLevel(logging.INFO)
|
||||
logging.getLogger('locust.runners').setLevel(logging.INFO)
|
||||
logging.getLogger("locust.main").setLevel(logging.INFO)
|
||||
logging.getLogger("locust.runners").setLevel(logging.INFO)
|
||||
|
||||
|
||||
class WebPageTasks(TaskSet):
|
||||
@@ -28,7 +28,7 @@ class WebPageTasks(TaskSet):
|
||||
request_type=self.test_runner.exception_request_type,
|
||||
name=self.test_runner.exception_name,
|
||||
response_time=0,
|
||||
exception=ex
|
||||
exception=ex,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -5,15 +5,13 @@ from httprunner.ext.locusts.utils import prepare_locust_tests
|
||||
|
||||
|
||||
class TestLocust(unittest.TestCase):
|
||||
|
||||
def test_prepare_locust_tests(self):
|
||||
path = os.path.join(
|
||||
os.path.dirname(__file__), "data", "demo_locusts.yml")
|
||||
path = os.path.join(os.path.dirname(__file__), "data", "demo_locusts.yml")
|
||||
locust_tests = prepare_locust_tests(path)
|
||||
self.assertEqual(len(locust_tests), 2 + 3)
|
||||
name_list = [
|
||||
"create user 1000 and check result.",
|
||||
"create user 1001 and check result."
|
||||
"create user 1001 and check result.",
|
||||
]
|
||||
self.assertIn(locust_tests[0]["config"]["name"], name_list)
|
||||
self.assertIn(locust_tests[4]["config"]["name"], name_list)
|
||||
|
||||
@@ -6,8 +6,11 @@ from loguru import logger
|
||||
|
||||
def init_parser_scaffold(subparsers):
|
||||
sub_parser_scaffold = subparsers.add_parser(
|
||||
"startproject", help="Create a new project with template structure.")
|
||||
sub_parser_scaffold.add_argument("project_name", type=str, nargs="?", help="Specify new project name.")
|
||||
"startproject", help="Create a new project with template structure."
|
||||
)
|
||||
sub_parser_scaffold.add_argument(
|
||||
"project_name", type=str, nargs="?", help="Specify new project name."
|
||||
)
|
||||
return sub_parser_scaffold
|
||||
|
||||
|
||||
@@ -15,7 +18,9 @@ def create_scaffold(project_name):
|
||||
""" create scaffold with specified project name.
|
||||
"""
|
||||
if os.path.isdir(project_name):
|
||||
logger.warning(f"Folder {project_name} exists, please specify a new folder name.")
|
||||
logger.warning(
|
||||
f"Folder {project_name} exists, please specify a new folder name."
|
||||
)
|
||||
return
|
||||
|
||||
logger.info(f"Start to create new project: {project_name}")
|
||||
@@ -27,7 +32,7 @@ def create_scaffold(project_name):
|
||||
logger.info(msg)
|
||||
|
||||
def create_file(path, file_content=""):
|
||||
with open(path, 'w') as f:
|
||||
with open(path, "w") as f:
|
||||
f.write(file_content)
|
||||
msg = f"created file: {path}"
|
||||
logger.info(msg)
|
||||
@@ -92,24 +97,16 @@ testcases:
|
||||
variables:
|
||||
device_sn: $device_sn
|
||||
"""
|
||||
ignore_content = "\n".join([
|
||||
".env",
|
||||
"reports/*",
|
||||
"__pycache__/*",
|
||||
"*.pyc",
|
||||
".python-version",
|
||||
"logs/*"
|
||||
])
|
||||
ignore_content = "\n".join(
|
||||
[".env", "reports/*", "__pycache__/*", "*.pyc", ".python-version", "logs/*"]
|
||||
)
|
||||
demo_debugtalk_content = """
|
||||
import time
|
||||
|
||||
def sleep(n_secs):
|
||||
time.sleep(n_secs)
|
||||
"""
|
||||
demo_env_content = "\n".join([
|
||||
"USERNAME=leolee",
|
||||
"PASSWORD=123456"
|
||||
])
|
||||
demo_env_content = "\n".join(["USERNAME=leolee", "PASSWORD=123456"])
|
||||
|
||||
create_folder(project_name)
|
||||
create_folder(os.path.join(project_name, "api"))
|
||||
@@ -117,8 +114,14 @@ def sleep(n_secs):
|
||||
create_folder(os.path.join(project_name, "testsuites"))
|
||||
create_folder(os.path.join(project_name, "reports"))
|
||||
create_file(os.path.join(project_name, "api", "demo_api.yml"), demo_api_content)
|
||||
create_file(os.path.join(project_name, "testcases", "demo_testcase.yml"), demo_testcase_content)
|
||||
create_file(os.path.join(project_name, "testsuites", "demo_testsuite.yml"), demo_testsuite_content)
|
||||
create_file(
|
||||
os.path.join(project_name, "testcases", "demo_testcase.yml"),
|
||||
demo_testcase_content,
|
||||
)
|
||||
create_file(
|
||||
os.path.join(project_name, "testsuites", "demo_testsuite.yml"),
|
||||
demo_testsuite_content,
|
||||
)
|
||||
create_file(os.path.join(project_name, "debugtalk.py"), demo_debugtalk_content)
|
||||
create_file(os.path.join(project_name, ".env"), demo_env_content)
|
||||
create_file(os.path.join(project_name, ".gitignore"), ignore_content)
|
||||
|
||||
@@ -6,7 +6,6 @@ from httprunner.ext.scaffold import create_scaffold
|
||||
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
|
||||
def test_create_scaffold(self):
|
||||
project_name = "projectABC"
|
||||
create_scaffold(project_name)
|
||||
|
||||
@@ -96,7 +96,9 @@ def prepare_upload_test(test_dict):
|
||||
test_dict["variables"]["m_encoder"] = "${multipart_encoder(" + params_str + ")}"
|
||||
|
||||
test_dict["request"].setdefault("headers", {})
|
||||
test_dict["request"]["headers"]["Content-Type"] = "${multipart_content_type($m_encoder)}"
|
||||
test_dict["request"]["headers"][
|
||||
"Content-Type"
|
||||
] = "${multipart_content_type($m_encoder)}"
|
||||
|
||||
test_dict["request"]["data"] = "$m_encoder"
|
||||
|
||||
@@ -122,6 +124,7 @@ def multipart_encoder(**kwargs):
|
||||
else:
|
||||
# value is not absolute file path, check if it is relative file path
|
||||
from httprunner.loader import get_pwd
|
||||
|
||||
_file_path = os.path.join(get_pwd(), value)
|
||||
is_exists_file = os.path.isfile(_file_path)
|
||||
|
||||
@@ -130,7 +133,7 @@ def multipart_encoder(**kwargs):
|
||||
filename = os.path.basename(_file_path)
|
||||
mime_type = get_filetype(_file_path)
|
||||
# TODO: fix ResourceWarning for unclosed file
|
||||
file_handler = open(_file_path, 'rb')
|
||||
file_handler = open(_file_path, "rb")
|
||||
fields_dict[key] = (filename, file_handler, mime_type)
|
||||
else:
|
||||
fields_dict[key] = value
|
||||
|
||||
@@ -11,8 +11,10 @@ HttpRunner loader
|
||||
from httprunner.loader.buildup import load_cases, load_project_data
|
||||
from httprunner.loader.check import is_test_path
|
||||
from httprunner.loader.load import load_csv_file, load_builtin_functions
|
||||
from httprunner.loader.locate import get_project_working_directory as get_pwd, \
|
||||
init_project_working_directory as init_pwd
|
||||
from httprunner.loader.locate import (
|
||||
get_project_working_directory as get_pwd,
|
||||
init_project_working_directory as init_pwd,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"is_test_path",
|
||||
@@ -21,5 +23,5 @@ __all__ = [
|
||||
"load_csv_file",
|
||||
"load_builtin_functions",
|
||||
"load_project_data",
|
||||
"load_cases"
|
||||
"load_cases",
|
||||
]
|
||||
|
||||
@@ -4,14 +4,18 @@ import os
|
||||
from loguru import logger
|
||||
|
||||
from httprunner import exceptions, utils
|
||||
from httprunner.loader.load import load_module_functions, load_file, load_dot_env_file, \
|
||||
load_folder_files
|
||||
from httprunner.loader.locate import init_project_working_directory, get_project_working_directory
|
||||
from httprunner.loader.load import (
|
||||
load_module_functions,
|
||||
load_file,
|
||||
load_dot_env_file,
|
||||
load_folder_files,
|
||||
)
|
||||
from httprunner.loader.locate import (
|
||||
init_project_working_directory,
|
||||
get_project_working_directory,
|
||||
)
|
||||
|
||||
tests_def_mapping = {
|
||||
"api": {},
|
||||
"testcases": {}
|
||||
}
|
||||
tests_def_mapping = {"api": {}, "testcases": {}}
|
||||
|
||||
|
||||
def load_debugtalk_functions():
|
||||
@@ -71,10 +75,7 @@ def __extend_with_testcase_ref(raw_testinfo):
|
||||
if testcase_path not in tests_def_mapping["testcases"]:
|
||||
# make compatible with Windows/Linux
|
||||
pwd = get_project_working_directory()
|
||||
testcase_path = os.path.join(
|
||||
pwd,
|
||||
*testcase_path.split("/")
|
||||
)
|
||||
testcase_path = os.path.join(pwd, *testcase_path.split("/"))
|
||||
loaded_testcase = load_file(testcase_path)
|
||||
|
||||
# TODO: validate with pydantic
|
||||
@@ -82,7 +83,8 @@ def __extend_with_testcase_ref(raw_testinfo):
|
||||
testcase_dict = load_testcase(loaded_testcase)
|
||||
else:
|
||||
raise exceptions.FileFormatError(
|
||||
f"Invalid format testcase: {testcase_path}")
|
||||
f"Invalid format testcase: {testcase_path}"
|
||||
)
|
||||
|
||||
tests_def_mapping["testcases"][testcase_path] = testcase_dict
|
||||
else:
|
||||
@@ -174,10 +176,7 @@ def load_testcase(raw_testcase):
|
||||
|
||||
"""
|
||||
raw_teststeps = raw_testcase.pop("teststeps")
|
||||
raw_testcase["teststeps"] = [
|
||||
load_teststep(teststep)
|
||||
for teststep in raw_teststeps
|
||||
]
|
||||
raw_testcase["teststeps"] = [load_teststep(teststep) for teststep in raw_teststeps]
|
||||
return raw_testcase
|
||||
|
||||
|
||||
@@ -305,7 +304,9 @@ def load_project_data(test_path, dot_env_path=None):
|
||||
environments and debugtalk.py functions.
|
||||
|
||||
"""
|
||||
debugtalk_path, project_working_directory = init_project_working_directory(test_path)
|
||||
debugtalk_path, project_working_directory = init_project_working_directory(
|
||||
test_path
|
||||
)
|
||||
|
||||
project_meta = {}
|
||||
|
||||
@@ -325,7 +326,9 @@ def load_project_data(test_path, dot_env_path=None):
|
||||
# locate PWD and load debugtalk.py functions
|
||||
project_meta["PWD"] = project_working_directory
|
||||
project_meta["functions"] = debugtalk_functions
|
||||
project_meta["test_path"] = os.path.abspath(test_path)[len(project_working_directory) + 1:]
|
||||
project_meta["test_path"] = os.path.abspath(test_path)[
|
||||
len(project_working_directory) + 1 :
|
||||
]
|
||||
|
||||
return project_meta
|
||||
|
||||
@@ -384,9 +387,7 @@ def load_cases(path: str, dot_env_path: str = None):
|
||||
|
||||
"""
|
||||
|
||||
tests_mapping = {
|
||||
"project_meta": load_project_data(path, dot_env_path)
|
||||
}
|
||||
tests_mapping = {"project_meta": load_project_data(path, dot_env_path)}
|
||||
|
||||
def __load_file_content(path):
|
||||
loaded_content = None
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
@@ -7,14 +6,15 @@ from httprunner.loader import buildup
|
||||
|
||||
|
||||
class TestModuleLoader(unittest.TestCase):
|
||||
|
||||
def test_filter_module_functions(self):
|
||||
module_functions = buildup.load_module_functions(buildup)
|
||||
self.assertIn("load_module_functions", module_functions)
|
||||
self.assertNotIn("is_py3", module_functions)
|
||||
|
||||
def test_load_debugtalk_module(self):
|
||||
project_meta = buildup.load_project_data(os.path.join(os.getcwd(), "httprunner"))
|
||||
project_meta = buildup.load_project_data(
|
||||
os.path.join(os.getcwd(), "httprunner")
|
||||
)
|
||||
self.assertNotIn("alter_response", project_meta["functions"])
|
||||
|
||||
project_meta = buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
|
||||
@@ -28,33 +28,23 @@ class TestModuleLoader(unittest.TestCase):
|
||||
project_meta = buildup.load_project_data("tests/data/demo_testcase.yml")
|
||||
project_working_directory = project_meta["PWD"]
|
||||
debugtalk_functions = project_meta["functions"]
|
||||
self.assertEqual(
|
||||
project_working_directory,
|
||||
os.path.join(os.getcwd(), "tests")
|
||||
)
|
||||
self.assertEqual(project_working_directory, os.path.join(os.getcwd(), "tests"))
|
||||
self.assertIn("gen_md5", debugtalk_functions)
|
||||
|
||||
project_meta = buildup.load_project_data("tests/base.py")
|
||||
project_working_directory = project_meta["PWD"]
|
||||
debugtalk_functions = project_meta["functions"]
|
||||
self.assertEqual(
|
||||
project_working_directory,
|
||||
os.path.join(os.getcwd(), "tests")
|
||||
)
|
||||
self.assertEqual(project_working_directory, os.path.join(os.getcwd(), "tests"))
|
||||
self.assertIn("gen_md5", debugtalk_functions)
|
||||
|
||||
project_meta = buildup.load_project_data("httprunner/__init__.py")
|
||||
project_working_directory = project_meta["PWD"]
|
||||
debugtalk_functions = project_meta["functions"]
|
||||
self.assertEqual(
|
||||
project_working_directory,
|
||||
os.getcwd()
|
||||
)
|
||||
self.assertEqual(project_working_directory, os.getcwd())
|
||||
self.assertEqual(debugtalk_functions, {})
|
||||
|
||||
|
||||
class TestSuiteLoader(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.project_meta = buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
|
||||
@@ -64,15 +54,10 @@ class TestSuiteLoader(unittest.TestCase):
|
||||
raw_test = {
|
||||
"name": "create user (override).",
|
||||
"api": "api/create_user.yml",
|
||||
"variables": [
|
||||
{"uid": "999"}
|
||||
]
|
||||
"variables": [{"uid": "999"}],
|
||||
}
|
||||
teststep = buildup.load_teststep(raw_test)
|
||||
self.assertEqual(
|
||||
"create user (override).",
|
||||
teststep["name"]
|
||||
)
|
||||
self.assertEqual("create user (override).", teststep["name"])
|
||||
self.assertIn("api_def", teststep)
|
||||
api_def = teststep["api_def"]
|
||||
self.assertEqual(api_def["name"], "create user")
|
||||
@@ -82,15 +67,10 @@ class TestSuiteLoader(unittest.TestCase):
|
||||
raw_test = {
|
||||
"name": "setup and reset all (override).",
|
||||
"testcase": "testcases/setup.yml",
|
||||
"variables": [
|
||||
{"device_sn": "$device_sn"}
|
||||
]
|
||||
"variables": [{"device_sn": "$device_sn"}],
|
||||
}
|
||||
testcase = buildup.load_teststep(raw_test)
|
||||
self.assertEqual(
|
||||
"setup and reset all (override).",
|
||||
testcase["name"]
|
||||
)
|
||||
self.assertEqual("setup and reset all (override).", testcase["name"])
|
||||
tests = testcase["testcase_def"]["teststeps"]
|
||||
self.assertEqual(len(tests), 2)
|
||||
self.assertEqual(tests[0]["name"], "get token (setup)")
|
||||
@@ -106,7 +86,7 @@ class TestSuiteLoader(unittest.TestCase):
|
||||
def test_load_test_file_testcase(self):
|
||||
for loaded_content in [
|
||||
buildup.load_test_file("tests/testcases/setup.yml"),
|
||||
buildup.load_test_file("tests/testcases/setup.json")
|
||||
buildup.load_test_file("tests/testcases/setup.json"),
|
||||
]:
|
||||
self.assertEqual(loaded_content["type"], "testcase")
|
||||
self.assertIn("path", loaded_content)
|
||||
@@ -118,113 +98,100 @@ class TestSuiteLoader(unittest.TestCase):
|
||||
def test_load_test_file_testsuite(self):
|
||||
for loaded_content in [
|
||||
buildup.load_test_file("tests/testsuites/create_users.yml"),
|
||||
buildup.load_test_file("tests/testsuites/create_users.json")
|
||||
buildup.load_test_file("tests/testsuites/create_users.json"),
|
||||
]:
|
||||
self.assertEqual(loaded_content["type"], "testsuite")
|
||||
|
||||
testcases = loaded_content["testcases"]
|
||||
self.assertEqual(len(testcases), 2)
|
||||
self.assertIn('create user 1000 and check result.', testcases)
|
||||
self.assertIn('testcase_def', testcases["create user 1000 and check result."])
|
||||
self.assertIn("create user 1000 and check result.", testcases)
|
||||
self.assertIn(
|
||||
"testcase_def", testcases["create user 1000 and check result."]
|
||||
)
|
||||
self.assertEqual(
|
||||
testcases["create user 1000 and check result."]["testcase_def"]["config"]["name"],
|
||||
"create user and check result."
|
||||
testcases["create user 1000 and check result."]["testcase_def"][
|
||||
"config"
|
||||
]["name"],
|
||||
"create user and check result.",
|
||||
)
|
||||
|
||||
def test_load_tests_api_file(self):
|
||||
path = os.path.join(
|
||||
os.getcwd(), 'tests/api/create_user.yml')
|
||||
path = os.path.join(os.getcwd(), "tests/api/create_user.yml")
|
||||
tests_mapping = loader.load_cases(path)
|
||||
api_list = tests_mapping["apis"]
|
||||
self.assertEqual(len(api_list), 1)
|
||||
self.assertEqual(api_list[0]["request"]["url"], "/api/users/$uid")
|
||||
|
||||
def test_load_tests_testcase_file_2(self):
|
||||
testcase_file_path = os.path.join(
|
||||
os.getcwd(), 'tests/data/demo_testcase.yml')
|
||||
testcase_file_path = os.path.join(os.getcwd(), "tests/data/demo_testcase.yml")
|
||||
tests_mapping = loader.load_cases(testcase_file_path)
|
||||
testcases = tests_mapping["testcases"]
|
||||
self.assertIsInstance(testcases, list)
|
||||
self.assertEqual(testcases[0]["config"]["name"], '123t$var_a')
|
||||
self.assertIn(
|
||||
"sum_two",
|
||||
tests_mapping["project_meta"]["functions"]
|
||||
self.assertEqual(testcases[0]["config"]["name"], "123t$var_a")
|
||||
self.assertIn("sum_two", tests_mapping["project_meta"]["functions"])
|
||||
self.assertEqual(
|
||||
testcases[0]["config"]["variables"]["var_c"], "${sum_two($var_a, $var_b)}"
|
||||
)
|
||||
self.assertEqual(
|
||||
testcases[0]["config"]["variables"]["var_c"],
|
||||
"${sum_two($var_a, $var_b)}"
|
||||
)
|
||||
self.assertEqual(
|
||||
testcases[0]["config"]["variables"]["PROJECT_KEY"],
|
||||
"${ENV(PROJECT_KEY)}"
|
||||
testcases[0]["config"]["variables"]["PROJECT_KEY"], "${ENV(PROJECT_KEY)}"
|
||||
)
|
||||
|
||||
def test_load_tests_testcase_file_with_api_ref(self):
|
||||
path = os.path.join(
|
||||
os.getcwd(), 'tests/data/demo_testcase_layer.yml')
|
||||
path = os.path.join(os.getcwd(), "tests/data/demo_testcase_layer.yml")
|
||||
tests_mapping = loader.load_cases(path)
|
||||
project_meta = tests_mapping["project_meta"]
|
||||
testcases_list = tests_mapping["testcases"]
|
||||
self.assertIn('device_sn', testcases_list[0]["config"]["variables"])
|
||||
self.assertIn("device_sn", testcases_list[0]["config"]["variables"])
|
||||
self.assertIn("gen_md5", project_meta["functions"])
|
||||
self.assertIn("base_url", testcases_list[0]["config"])
|
||||
test_dict0 = testcases_list[0]["teststeps"][0]
|
||||
self.assertEqual(
|
||||
"get token with $user_agent, $app_version",
|
||||
test_dict0["name"]
|
||||
)
|
||||
self.assertEqual("get token with $user_agent, $app_version", test_dict0["name"])
|
||||
self.assertIn("/api/get-token", test_dict0["api_def"]["request"]["url"])
|
||||
self.assertIn(
|
||||
{'eq': ['status_code', 200]},
|
||||
test_dict0["validate"]
|
||||
)
|
||||
self.assertIn({"eq": ["status_code", 200]}, test_dict0["validate"])
|
||||
|
||||
def test_load_tests_testsuite_file_with_testcase_ref(self):
|
||||
path = os.path.join(
|
||||
os.getcwd(), 'tests/testsuites/create_users.yml')
|
||||
path = os.path.join(os.getcwd(), "tests/testsuites/create_users.yml")
|
||||
tests_mapping = loader.load_cases(path)
|
||||
project_meta = tests_mapping["project_meta"]
|
||||
testsuites_list = tests_mapping["testsuites"]
|
||||
|
||||
self.assertEqual("create users with uid", testsuites_list[0]["config"]["name"])
|
||||
self.assertEqual(
|
||||
"create users with uid",
|
||||
testsuites_list[0]["config"]["name"]
|
||||
)
|
||||
self.assertEqual(
|
||||
'${gen_random_string(15)}',
|
||||
testsuites_list[0]["config"]["variables"]['device_sn']
|
||||
"${gen_random_string(15)}",
|
||||
testsuites_list[0]["config"]["variables"]["device_sn"],
|
||||
)
|
||||
self.assertIn(
|
||||
"create user 1000 and check result.",
|
||||
testsuites_list[0]["testcases"]
|
||||
"create user 1000 and check result.", testsuites_list[0]["testcases"]
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
testsuites_list[0]["testcases"]["create user 1000 and check result."]["testcase_def"]["config"]["name"],
|
||||
"create user and check result."
|
||||
testsuites_list[0]["testcases"]["create user 1000 and check result."][
|
||||
"testcase_def"
|
||||
]["config"]["name"],
|
||||
"create user and check result.",
|
||||
)
|
||||
|
||||
def test_load_tests_folder_path(self):
|
||||
# absolute folder path
|
||||
path = os.path.join(os.getcwd(), 'tests/data')
|
||||
path = os.path.join(os.getcwd(), "tests/data")
|
||||
tests_mapping = loader.load_cases(path)
|
||||
testcase_list_1 = tests_mapping["testcases"]
|
||||
self.assertGreater(len(testcase_list_1), 4)
|
||||
|
||||
# relative folder path
|
||||
path = 'tests/data/'
|
||||
path = "tests/data/"
|
||||
tests_mapping = loader.load_cases(path)
|
||||
testcase_list_2 = tests_mapping["testcases"]
|
||||
self.assertEqual(len(testcase_list_1), len(testcase_list_2))
|
||||
|
||||
def test_load_tests_path_not_exist(self):
|
||||
# absolute folder path
|
||||
path = os.path.join(os.getcwd(), 'tests/data_not_exist')
|
||||
path = os.path.join(os.getcwd(), "tests/data_not_exist")
|
||||
with self.assertRaises(exceptions.FileNotFound):
|
||||
loader.load_cases(path)
|
||||
|
||||
# relative folder path
|
||||
path = 'tests/data_not_exist'
|
||||
path = "tests/data_not_exist"
|
||||
with self.assertRaises(exceptions.FileNotFound):
|
||||
loader.load_cases(path)
|
||||
|
||||
@@ -232,11 +199,5 @@ class TestSuiteLoader(unittest.TestCase):
|
||||
buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
|
||||
self.assertIn("gen_md5", self.project_meta["functions"])
|
||||
self.assertEqual(self.project_meta["env"]["PROJECT_KEY"], "ABCDEFGH")
|
||||
self.assertEqual(
|
||||
os.path.basename(self.project_meta["PWD"]),
|
||||
"tests"
|
||||
)
|
||||
self.assertEqual(
|
||||
os.path.basename(self.project_meta["test_path"]),
|
||||
"" # FIXME
|
||||
)
|
||||
self.assertEqual(os.path.basename(self.project_meta["PWD"]), "tests")
|
||||
self.assertEqual(os.path.basename(self.project_meta["test_path"]), "") # FIXME
|
||||
|
||||
@@ -30,7 +30,7 @@ def is_test_path(path):
|
||||
if os.path.isfile(path):
|
||||
# path is a file
|
||||
file_suffix = os.path.splitext(path)[1].lower()
|
||||
if file_suffix not in ['.json', '.yaml', '.yml']:
|
||||
if file_suffix not in [".json", ".yaml", ".yml"]:
|
||||
# path is not json/yaml file
|
||||
return False
|
||||
else:
|
||||
|
||||
@@ -14,7 +14,7 @@ from httprunner.loader.locate import get_project_working_directory
|
||||
try:
|
||||
# PyYAML version >= 5.1
|
||||
# ref: https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation
|
||||
yaml.warnings({'YAMLLoadWarning': False})
|
||||
yaml.warnings({"YAMLLoadWarning": False})
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@@ -22,7 +22,7 @@ except AttributeError:
|
||||
def _load_yaml_file(yaml_file):
|
||||
""" load yaml file and check file content format
|
||||
"""
|
||||
with io.open(yaml_file, 'r', encoding='utf-8') as stream:
|
||||
with io.open(yaml_file, "r", encoding="utf-8") as stream:
|
||||
try:
|
||||
yaml_content = yaml.load(stream)
|
||||
except yaml.YAMLError as ex:
|
||||
@@ -35,7 +35,7 @@ def _load_yaml_file(yaml_file):
|
||||
def _load_json_file(json_file):
|
||||
""" load json file and check file content format
|
||||
"""
|
||||
with io.open(json_file, encoding='utf-8') as data_file:
|
||||
with io.open(json_file, encoding="utf-8") as data_file:
|
||||
try:
|
||||
json_content = json.load(data_file)
|
||||
except json.JSONDecodeError:
|
||||
@@ -81,7 +81,7 @@ def load_csv_file(csv_file):
|
||||
|
||||
csv_content_list = []
|
||||
|
||||
with io.open(csv_file, encoding='utf-8') as csvfile:
|
||||
with io.open(csv_file, encoding="utf-8") as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
for row in reader:
|
||||
csv_content_list.append(row)
|
||||
@@ -94,9 +94,9 @@ def load_file(file_path):
|
||||
raise exceptions.FileNotFound(f"{file_path} does not exist.")
|
||||
|
||||
file_suffix = os.path.splitext(file_path)[1].lower()
|
||||
if file_suffix == '.json':
|
||||
if file_suffix == ".json":
|
||||
return _load_json_file(file_path)
|
||||
elif file_suffix in ['.yaml', '.yml']:
|
||||
elif file_suffix in [".yaml", ".yml"]:
|
||||
return _load_yaml_file(file_path)
|
||||
elif file_suffix == ".csv":
|
||||
return load_csv_file(file_path)
|
||||
@@ -132,7 +132,7 @@ def load_folder_files(folder_path, recursive=True):
|
||||
filenames_list = []
|
||||
|
||||
for filename in filenames:
|
||||
if not filename.endswith(('.yml', '.yaml', '.json')):
|
||||
if not filename.endswith((".yml", ".yaml", ".json")):
|
||||
continue
|
||||
|
||||
filenames_list.append(filename)
|
||||
@@ -172,7 +172,7 @@ def load_dot_env_file(dot_env_path):
|
||||
logger.info(f"Loading environment variables from {dot_env_path}")
|
||||
env_variables_mapping = {}
|
||||
|
||||
with io.open(dot_env_path, 'r', encoding='utf-8') as fp:
|
||||
with io.open(dot_env_path, "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
# maxsplit=1
|
||||
if "=" in line:
|
||||
@@ -216,4 +216,3 @@ def load_builtin_functions():
|
||||
""" load builtin module functions
|
||||
"""
|
||||
return load_module_functions(builtin)
|
||||
|
||||
|
||||
@@ -7,11 +7,10 @@ from httprunner.loader.buildup import load_test_file
|
||||
|
||||
|
||||
class TestFileLoader(unittest.TestCase):
|
||||
|
||||
def test_load_yaml_file_file_format_error(self):
|
||||
yaml_tmp_file = "tests/data/tmp.yml"
|
||||
# create empty yaml file
|
||||
with open(yaml_tmp_file, 'w') as f:
|
||||
with open(yaml_tmp_file, "w") as f:
|
||||
f.write("")
|
||||
|
||||
with self.assertRaises(exceptions.FileFormatError):
|
||||
@@ -20,7 +19,7 @@ class TestFileLoader(unittest.TestCase):
|
||||
os.remove(yaml_tmp_file)
|
||||
|
||||
# create invalid format yaml file
|
||||
with open(yaml_tmp_file, 'w') as f:
|
||||
with open(yaml_tmp_file, "w") as f:
|
||||
f.write("abc")
|
||||
|
||||
with self.assertRaises(exceptions.FileFormatError):
|
||||
@@ -31,7 +30,7 @@ class TestFileLoader(unittest.TestCase):
|
||||
def test_load_json_file_file_format_error(self):
|
||||
json_tmp_file = "tests/data/tmp.json"
|
||||
# create empty file
|
||||
with open(json_tmp_file, 'w') as f:
|
||||
with open(json_tmp_file, "w") as f:
|
||||
f.write("")
|
||||
|
||||
with self.assertRaises(exceptions.FileFormatError):
|
||||
@@ -40,7 +39,7 @@ class TestFileLoader(unittest.TestCase):
|
||||
os.remove(json_tmp_file)
|
||||
|
||||
# create empty json file
|
||||
with open(json_tmp_file, 'w') as f:
|
||||
with open(json_tmp_file, "w") as f:
|
||||
f.write("{}")
|
||||
|
||||
with self.assertRaises(exceptions.FileFormatError):
|
||||
@@ -49,7 +48,7 @@ class TestFileLoader(unittest.TestCase):
|
||||
os.remove(json_tmp_file)
|
||||
|
||||
# create invalid format json file
|
||||
with open(json_tmp_file, 'w') as f:
|
||||
with open(json_tmp_file, "w") as f:
|
||||
f.write("abc")
|
||||
|
||||
with self.assertRaises(exceptions.FileFormatError):
|
||||
@@ -58,40 +57,38 @@ class TestFileLoader(unittest.TestCase):
|
||||
os.remove(json_tmp_file)
|
||||
|
||||
def test_load_testcases_bad_filepath(self):
|
||||
testcase_file_path = os.path.join(os.getcwd(), 'tests/data/demo')
|
||||
testcase_file_path = os.path.join(os.getcwd(), "tests/data/demo")
|
||||
with self.assertRaises(exceptions.FileNotFound):
|
||||
load.load_file(testcase_file_path)
|
||||
|
||||
def test_load_csv_file_one_parameter(self):
|
||||
csv_file_path = os.path.join(
|
||||
os.getcwd(), 'tests/data/user_agent.csv')
|
||||
csv_file_path = os.path.join(os.getcwd(), "tests/data/user_agent.csv")
|
||||
csv_content = load.load_file(csv_file_path)
|
||||
self.assertEqual(
|
||||
csv_content,
|
||||
[
|
||||
{'user_agent': 'iOS/10.1'},
|
||||
{'user_agent': 'iOS/10.2'},
|
||||
{'user_agent': 'iOS/10.3'}
|
||||
]
|
||||
{"user_agent": "iOS/10.1"},
|
||||
{"user_agent": "iOS/10.2"},
|
||||
{"user_agent": "iOS/10.3"},
|
||||
],
|
||||
)
|
||||
|
||||
def test_load_csv_file_multiple_parameters(self):
|
||||
csv_file_path = os.path.join(
|
||||
os.getcwd(), 'tests/data/account.csv')
|
||||
csv_file_path = os.path.join(os.getcwd(), "tests/data/account.csv")
|
||||
csv_content = load.load_file(csv_file_path)
|
||||
self.assertEqual(
|
||||
csv_content,
|
||||
[
|
||||
{'username': 'test1', 'password': '111111'},
|
||||
{'username': 'test2', 'password': '222222'},
|
||||
{'username': 'test3', 'password': '333333'}
|
||||
]
|
||||
{"username": "test1", "password": "111111"},
|
||||
{"username": "test2", "password": "222222"},
|
||||
{"username": "test3", "password": "333333"},
|
||||
],
|
||||
)
|
||||
|
||||
def test_load_folder_files(self):
|
||||
folder = os.path.join(os.getcwd(), 'tests')
|
||||
file1 = os.path.join(os.getcwd(), 'tests', 'test_utils.py')
|
||||
file2 = os.path.join(os.getcwd(), 'tests', 'api', 'reset_all.yml')
|
||||
folder = os.path.join(os.getcwd(), "tests")
|
||||
file1 = os.path.join(os.getcwd(), "tests", "test_utils.py")
|
||||
file2 = os.path.join(os.getcwd(), "tests", "api", "reset_all.yml")
|
||||
|
||||
files = load.load_folder_files(folder, recursive=False)
|
||||
self.assertEqual(files, [])
|
||||
@@ -107,25 +104,21 @@ class TestFileLoader(unittest.TestCase):
|
||||
self.assertEqual([], files)
|
||||
|
||||
def test_load_dot_env_file(self):
|
||||
dot_env_path = os.path.join(
|
||||
os.getcwd(), "tests", ".env"
|
||||
)
|
||||
dot_env_path = os.path.join(os.getcwd(), "tests", ".env")
|
||||
env_variables_mapping = load.load_dot_env_file(dot_env_path)
|
||||
self.assertIn("PROJECT_KEY", env_variables_mapping)
|
||||
self.assertEqual(env_variables_mapping["UserName"], "debugtalk")
|
||||
|
||||
def test_load_custom_dot_env_file(self):
|
||||
dot_env_path = os.path.join(
|
||||
os.getcwd(), "tests", "data", "test.env"
|
||||
)
|
||||
dot_env_path = os.path.join(os.getcwd(), "tests", "data", "test.env")
|
||||
env_variables_mapping = load.load_dot_env_file(dot_env_path)
|
||||
self.assertIn("PROJECT_KEY", env_variables_mapping)
|
||||
self.assertEqual(env_variables_mapping["UserName"], "test")
|
||||
self.assertEqual(env_variables_mapping["content_type"], "application/json; charset=UTF-8")
|
||||
self.assertEqual(
|
||||
env_variables_mapping["content_type"], "application/json; charset=UTF-8"
|
||||
)
|
||||
|
||||
def test_load_env_path_not_exist(self):
|
||||
dot_env_path = os.path.join(
|
||||
os.getcwd(), "tests", "data",
|
||||
)
|
||||
dot_env_path = os.path.join(os.getcwd(), "tests", "data",)
|
||||
env_variables_mapping = load.load_dot_env_file(dot_env_path)
|
||||
self.assertEqual(env_variables_mapping, {})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
@@ -7,7 +6,6 @@ from httprunner.loader import locate
|
||||
|
||||
|
||||
class TestLoaderLocate(unittest.TestCase):
|
||||
|
||||
def test_locate_file(self):
|
||||
with self.assertRaises(exceptions.FileNotFound):
|
||||
locate.locate_file(os.getcwd(), "debugtalk.py")
|
||||
@@ -18,23 +16,21 @@ class TestLoaderLocate(unittest.TestCase):
|
||||
start_path = os.path.join(os.getcwd(), "tests")
|
||||
self.assertEqual(
|
||||
locate.locate_file(start_path, "debugtalk.py"),
|
||||
os.path.join(
|
||||
os.getcwd(), "tests/debugtalk.py"
|
||||
)
|
||||
os.path.join(os.getcwd(), "tests/debugtalk.py"),
|
||||
)
|
||||
self.assertEqual(
|
||||
locate.locate_file("tests/", "debugtalk.py"),
|
||||
os.path.join(os.getcwd(), "tests", "debugtalk.py")
|
||||
os.path.join(os.getcwd(), "tests", "debugtalk.py"),
|
||||
)
|
||||
self.assertEqual(
|
||||
locate.locate_file("tests", "debugtalk.py"),
|
||||
os.path.join(os.getcwd(), "tests", "debugtalk.py")
|
||||
os.path.join(os.getcwd(), "tests", "debugtalk.py"),
|
||||
)
|
||||
self.assertEqual(
|
||||
locate.locate_file("tests/base.py", "debugtalk.py"),
|
||||
os.path.join(os.getcwd(), "tests", "debugtalk.py")
|
||||
os.path.join(os.getcwd(), "tests", "debugtalk.py"),
|
||||
)
|
||||
self.assertEqual(
|
||||
locate.locate_file("tests/data/demo_testcase.yml", "debugtalk.py"),
|
||||
os.path.join(os.getcwd(), "tests", "debugtalk.py")
|
||||
os.path.join(os.getcwd(), "tests", "debugtalk.py"),
|
||||
)
|
||||
|
||||
@@ -68,9 +68,7 @@ def regex_findall_variables(content: Text) -> List[Text]:
|
||||
try:
|
||||
vars_list = []
|
||||
for var_tuple in variable_regex_compile.findall(content):
|
||||
vars_list.append(
|
||||
var_tuple[0] or var_tuple[1]
|
||||
)
|
||||
vars_list.append(var_tuple[0] or var_tuple[1])
|
||||
return vars_list
|
||||
except TypeError:
|
||||
return []
|
||||
@@ -160,20 +158,17 @@ def parse_function_params(params: Text) -> Dict:
|
||||
{'args': [1, 2], 'kwargs': {'a':3, 'b':4}}
|
||||
|
||||
"""
|
||||
function_meta = {
|
||||
"args": [],
|
||||
"kwargs": {}
|
||||
}
|
||||
function_meta = {"args": [], "kwargs": {}}
|
||||
|
||||
params_str = params.strip()
|
||||
if params_str == "":
|
||||
return function_meta
|
||||
|
||||
args_list = params_str.split(',')
|
||||
args_list = params_str.split(",")
|
||||
for arg in args_list:
|
||||
arg = arg.strip()
|
||||
if '=' in arg:
|
||||
key, value = arg.split('=')
|
||||
if "=" in arg:
|
||||
key, value = arg.split("=")
|
||||
function_meta["kwargs"][key.strip()] = parse_string_value(value.strip())
|
||||
else:
|
||||
function_meta["args"].append(parse_string_value(arg))
|
||||
@@ -181,7 +176,9 @@ def parse_function_params(params: Text) -> Dict:
|
||||
return function_meta
|
||||
|
||||
|
||||
def get_mapping_variable(variable_name: Text, variables_mapping: VariablesMapping) -> Any:
|
||||
def get_mapping_variable(
|
||||
variable_name: Text, variables_mapping: VariablesMapping
|
||||
) -> Any:
|
||||
""" get variable from variables_mapping.
|
||||
|
||||
Args:
|
||||
@@ -199,10 +196,14 @@ def get_mapping_variable(variable_name: Text, variables_mapping: VariablesMappin
|
||||
try:
|
||||
return variables_mapping[variable_name]
|
||||
except KeyError:
|
||||
raise exceptions.VariableNotFound(f"{variable_name} not found in {variables_mapping}")
|
||||
raise exceptions.VariableNotFound(
|
||||
f"{variable_name} not found in {variables_mapping}"
|
||||
)
|
||||
|
||||
|
||||
def get_mapping_function(function_name: Text, functions_mapping: FunctionsMapping) -> Callable:
|
||||
def get_mapping_function(
|
||||
function_name: Text, functions_mapping: FunctionsMapping
|
||||
) -> Callable:
|
||||
""" get function from functions_mapping,
|
||||
if not found, then try to check if builtin function.
|
||||
|
||||
@@ -229,6 +230,7 @@ def get_mapping_function(function_name: Text, functions_mapping: FunctionsMappin
|
||||
elif function_name in ["multipart_encoder", "multipart_content_type"]:
|
||||
# extension for upload test
|
||||
from httprunner.ext import uploader
|
||||
|
||||
return getattr(uploader, function_name)
|
||||
|
||||
try:
|
||||
@@ -248,9 +250,10 @@ def get_mapping_function(function_name: Text, functions_mapping: FunctionsMappin
|
||||
|
||||
|
||||
def parse_string(
|
||||
raw_string: Text,
|
||||
variables_mapping: VariablesMapping,
|
||||
functions_mapping: FunctionsMapping) -> Any:
|
||||
raw_string: Text,
|
||||
variables_mapping: VariablesMapping,
|
||||
functions_mapping: FunctionsMapping,
|
||||
) -> Any:
|
||||
""" parse string content with variables and functions mapping.
|
||||
|
||||
Args:
|
||||
@@ -344,9 +347,10 @@ def parse_string(
|
||||
|
||||
|
||||
def parse_data(
|
||||
raw_data: Any,
|
||||
variables_mapping: VariablesMapping = None,
|
||||
functions_mapping: FunctionsMapping = None) -> Any:
|
||||
raw_data: Any,
|
||||
variables_mapping: VariablesMapping = None,
|
||||
functions_mapping: FunctionsMapping = None,
|
||||
) -> Any:
|
||||
""" parse raw data with evaluated variables mapping.
|
||||
Notice: variables_mapping should not contain any variable or function.
|
||||
"""
|
||||
@@ -359,8 +363,7 @@ def parse_data(
|
||||
|
||||
elif isinstance(raw_data, (list, set, tuple)):
|
||||
return [
|
||||
parse_data(item, variables_mapping, functions_mapping)
|
||||
for item in raw_data
|
||||
parse_data(item, variables_mapping, functions_mapping) for item in raw_data
|
||||
]
|
||||
|
||||
elif isinstance(raw_data, dict):
|
||||
@@ -378,8 +381,8 @@ def parse_data(
|
||||
|
||||
|
||||
def parse_variables_mapping(
|
||||
variables_mapping: VariablesMapping,
|
||||
functions_mapping: FunctionsMapping = None) -> VariablesMapping:
|
||||
variables_mapping: VariablesMapping, functions_mapping: FunctionsMapping = None
|
||||
) -> VariablesMapping:
|
||||
|
||||
parsed_variables: VariablesMapping = {}
|
||||
|
||||
@@ -401,9 +404,7 @@ def parse_variables_mapping(
|
||||
|
||||
# check if reference variable not in variables_mapping
|
||||
not_defined_variables = [
|
||||
v_name
|
||||
for v_name in variables
|
||||
if v_name not in variables_mapping
|
||||
v_name for v_name in variables if v_name not in variables_mapping
|
||||
]
|
||||
if not_defined_variables:
|
||||
# e.g. {"varA": "123$varB", "varB": "456$varC"}
|
||||
@@ -412,7 +413,8 @@ def parse_variables_mapping(
|
||||
|
||||
try:
|
||||
parsed_value = parse_data(
|
||||
var_value, parsed_variables, functions_mapping)
|
||||
var_value, parsed_variables, functions_mapping
|
||||
)
|
||||
except exceptions.VariableNotFound:
|
||||
continue
|
||||
|
||||
|
||||
@@ -6,27 +6,15 @@ from httprunner.exceptions import VariableNotFound, FunctionNotFound
|
||||
|
||||
|
||||
class TestParserBasic(unittest.TestCase):
|
||||
|
||||
def test_parse_variables_mapping(self):
|
||||
variables = {
|
||||
"varA": "$varB",
|
||||
"varB": "$varC",
|
||||
"varC": "123",
|
||||
"a": 1,
|
||||
"b": 2
|
||||
}
|
||||
variables = {"varA": "$varB", "varB": "$varC", "varC": "123", "a": 1, "b": 2}
|
||||
parsed_variables = parser.parse_variables_mapping(variables)
|
||||
print(parsed_variables)
|
||||
self.assertEqual(parsed_variables["varA"], "123")
|
||||
self.assertEqual(parsed_variables["varB"], "123")
|
||||
|
||||
def test_parse_variables_mapping_exception(self):
|
||||
variables = {
|
||||
"varA": "$varB",
|
||||
"varB": "$varC",
|
||||
"a": 1,
|
||||
"b": 2
|
||||
}
|
||||
variables = {"varA": "$varB", "varB": "$varC", "a": 1, "b": 2}
|
||||
with self.assertRaises(VariableNotFound):
|
||||
parser.parse_variables_mapping(variables)
|
||||
|
||||
@@ -38,125 +26,77 @@ class TestParserBasic(unittest.TestCase):
|
||||
self.assertEqual(parser.parse_string_value("${func}"), "${func}")
|
||||
|
||||
def test_extract_variables(self):
|
||||
self.assertEqual(
|
||||
parser.extract_variables("$var"),
|
||||
{"var"}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.extract_variables("$var123"),
|
||||
{"var123"}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.extract_variables("$var_name"),
|
||||
{"var_name"}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.extract_variables("var"),
|
||||
set()
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.extract_variables("a$var"),
|
||||
{"var"}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.extract_variables("$v ar"),
|
||||
{"v"}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.extract_variables(" "),
|
||||
set()
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.extract_variables("$abc*"),
|
||||
{"abc"}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.extract_variables("${func()}"),
|
||||
set()
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.extract_variables("${func(1,2)}"),
|
||||
set()
|
||||
)
|
||||
self.assertEqual(parser.extract_variables("$var"), {"var"})
|
||||
self.assertEqual(parser.extract_variables("$var123"), {"var123"})
|
||||
self.assertEqual(parser.extract_variables("$var_name"), {"var_name"})
|
||||
self.assertEqual(parser.extract_variables("var"), set())
|
||||
self.assertEqual(parser.extract_variables("a$var"), {"var"})
|
||||
self.assertEqual(parser.extract_variables("$v ar"), {"v"})
|
||||
self.assertEqual(parser.extract_variables(" "), set())
|
||||
self.assertEqual(parser.extract_variables("$abc*"), {"abc"})
|
||||
self.assertEqual(parser.extract_variables("${func()}"), set())
|
||||
self.assertEqual(parser.extract_variables("${func(1,2)}"), set())
|
||||
self.assertEqual(
|
||||
parser.extract_variables("${gen_md5($TOKEN, $data, $random)}"),
|
||||
{"TOKEN", "data", "random"}
|
||||
{"TOKEN", "data", "random"},
|
||||
)
|
||||
|
||||
def test_parse_function_params(self):
|
||||
self.assertEqual(parser.parse_function_params(""), {"args": [], "kwargs": {}})
|
||||
self.assertEqual(parser.parse_function_params("5"), {"args": [5], "kwargs": {}})
|
||||
self.assertEqual(
|
||||
parser.parse_function_params(""),
|
||||
{'args': [], 'kwargs': {}}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_function_params("5"),
|
||||
{'args': [5], 'kwargs': {}}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_function_params("1, 2"),
|
||||
{'args': [1, 2], 'kwargs': {}}
|
||||
parser.parse_function_params("1, 2"), {"args": [1, 2], "kwargs": {}}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_function_params("a=1, b=2"),
|
||||
{'args': [], 'kwargs': {'a': 1, 'b': 2}}
|
||||
{"args": [], "kwargs": {"a": 1, "b": 2}},
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_function_params("a= 1, b =2"),
|
||||
{'args': [], 'kwargs': {'a': 1, 'b': 2}}
|
||||
{"args": [], "kwargs": {"a": 1, "b": 2}},
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_function_params("1, 2, a=3, b=4"),
|
||||
{'args': [1, 2], 'kwargs': {'a': 3, 'b': 4}}
|
||||
{"args": [1, 2], "kwargs": {"a": 3, "b": 4}},
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_function_params("$request, 123"),
|
||||
{'args': ["$request", 123], 'kwargs': {}}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_function_params(" "),
|
||||
{'args': [], 'kwargs': {}}
|
||||
{"args": ["$request", 123], "kwargs": {}},
|
||||
)
|
||||
self.assertEqual(parser.parse_function_params(" "), {"args": [], "kwargs": {}})
|
||||
self.assertEqual(
|
||||
parser.parse_function_params("hello world, a=3, b=4"),
|
||||
{'args': ["hello world"], 'kwargs': {'a': 3, 'b': 4}}
|
||||
{"args": ["hello world"], "kwargs": {"a": 3, "b": 4}},
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_function_params("$request, 12 3"),
|
||||
{'args': ["$request", '12 3'], 'kwargs': {}}
|
||||
{"args": ["$request", "12 3"], "kwargs": {}},
|
||||
)
|
||||
|
||||
def test_extract_functions(self):
|
||||
self.assertEqual(parser.regex_findall_functions("${func()}"), [("func", "")])
|
||||
self.assertEqual(parser.regex_findall_functions("${func(5)}"), [("func", "5")])
|
||||
self.assertEqual(
|
||||
parser.regex_findall_functions("${func()}"),
|
||||
[("func", "")]
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.regex_findall_functions("${func(5)}"),
|
||||
[("func", "5")]
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.regex_findall_functions("${func(a=1, b=2)}"),
|
||||
[("func", "a=1, b=2")]
|
||||
parser.regex_findall_functions("${func(a=1, b=2)}"), [("func", "a=1, b=2")]
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.regex_findall_functions("${func(1, $b, c=$x, d=4)}"),
|
||||
[("func", "1, $b, c=$x, d=4")]
|
||||
[("func", "1, $b, c=$x, d=4")],
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.regex_findall_functions("/api/1000?_t=${get_timestamp()}"),
|
||||
[("get_timestamp", "")]
|
||||
[("get_timestamp", "")],
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.regex_findall_functions("/api/${add(1, 2)}"),
|
||||
[("add", "1, 2")]
|
||||
parser.regex_findall_functions("/api/${add(1, 2)}"), [("add", "1, 2")]
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.regex_findall_functions("/api/${add(1, 2)}?_t=${get_timestamp()}"),
|
||||
[('add', '1, 2'), ('get_timestamp', '')]
|
||||
[("add", "1, 2"), ("get_timestamp", "")],
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.regex_findall_functions("abc${func(1, 2, a=3, b=4)}def"),
|
||||
[('func', '1, 2, a=3, b=4')]
|
||||
[("func", "1, 2, a=3, b=4")],
|
||||
)
|
||||
|
||||
def test_parse_data_string_with_variables(self):
|
||||
@@ -166,67 +106,35 @@ class TestParserBasic(unittest.TestCase):
|
||||
"var_3": 123,
|
||||
"var_4": {"a": 1},
|
||||
"var_5": True,
|
||||
"var_6": None
|
||||
"var_6": None,
|
||||
}
|
||||
self.assertEqual(parser.parse_data("$var_1", variables_mapping), "abc")
|
||||
self.assertEqual(parser.parse_data("${var_1}", variables_mapping), "abc")
|
||||
self.assertEqual(parser.parse_data("var_1", variables_mapping), "var_1")
|
||||
self.assertEqual(parser.parse_data("$var_1#XYZ", variables_mapping), "abc#XYZ")
|
||||
self.assertEqual(
|
||||
parser.parse_data("$var_1", variables_mapping),
|
||||
"abc"
|
||||
parser.parse_data("${var_1}#XYZ", variables_mapping), "abc#XYZ"
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("${var_1}", variables_mapping),
|
||||
"abc"
|
||||
parser.parse_data("/$var_1/$var_2/var3", variables_mapping), "/abc/def/var3"
|
||||
)
|
||||
self.assertEqual(parser.parse_data("$var_3", variables_mapping), 123)
|
||||
self.assertEqual(parser.parse_data("$var_4", variables_mapping), {"a": 1})
|
||||
self.assertEqual(parser.parse_data("$var_5", variables_mapping), True)
|
||||
self.assertEqual(parser.parse_data("abc$var_5", variables_mapping), "abcTrue")
|
||||
self.assertEqual(
|
||||
parser.parse_data("var_1", variables_mapping),
|
||||
"var_1"
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("$var_1#XYZ", variables_mapping),
|
||||
"abc#XYZ"
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("${var_1}#XYZ", variables_mapping),
|
||||
"abc#XYZ"
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("/$var_1/$var_2/var3", variables_mapping),
|
||||
"/abc/def/var3"
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("$var_3", variables_mapping),
|
||||
123
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("$var_4", variables_mapping),
|
||||
{"a": 1}
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("$var_5", variables_mapping),
|
||||
True
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("abc$var_5", variables_mapping),
|
||||
"abcTrue"
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("abc$var_4", variables_mapping),
|
||||
"abc{'a': 1}"
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("$var_6", variables_mapping),
|
||||
None
|
||||
parser.parse_data("abc$var_4", variables_mapping), "abc{'a': 1}"
|
||||
)
|
||||
self.assertEqual(parser.parse_data("$var_6", variables_mapping), None)
|
||||
|
||||
with self.assertRaises(VariableNotFound):
|
||||
parser.parse_data("/api/$SECRET_KEY", variables_mapping)
|
||||
|
||||
self.assertEqual(
|
||||
parser.parse_data(["$var_1", "$var_2"], variables_mapping),
|
||||
["abc", "def"]
|
||||
parser.parse_data(["$var_1", "$var_2"], variables_mapping), ["abc", "def"]
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data({"$var_1": "$var_2"}, variables_mapping),
|
||||
{"abc": "def"}
|
||||
parser.parse_data({"$var_1": "$var_2"}, variables_mapping), {"abc": "def"}
|
||||
)
|
||||
|
||||
# format: $var
|
||||
@@ -286,52 +194,56 @@ class TestParserBasic(unittest.TestCase):
|
||||
}
|
||||
self.assertEqual(
|
||||
parser.parse_data("/$var_1/$var_2/$var_1", variables_mapping),
|
||||
"/abc/def/abc"
|
||||
"/abc/def/abc",
|
||||
)
|
||||
|
||||
variables_mapping = {
|
||||
"userid": 100,
|
||||
"data": 1498
|
||||
}
|
||||
variables_mapping = {"userid": 100, "data": 1498}
|
||||
content = "/users/$userid/training/$data?userId=$userid&data=$data"
|
||||
self.assertEqual(
|
||||
parser.parse_data(content, variables_mapping),
|
||||
"/users/100/training/1498?userId=100&data=1498"
|
||||
"/users/100/training/1498?userId=100&data=1498",
|
||||
)
|
||||
|
||||
variables_mapping = {
|
||||
"user": 100,
|
||||
"userid": 1000,
|
||||
"data": 1498
|
||||
}
|
||||
variables_mapping = {"user": 100, "userid": 1000, "data": 1498}
|
||||
content = "/users/$user/$userid/$data?userId=$userid&data=$data"
|
||||
self.assertEqual(
|
||||
parser.parse_data(content, variables_mapping),
|
||||
"/users/100/1000/1498?userId=1000&data=1498"
|
||||
"/users/100/1000/1498?userId=1000&data=1498",
|
||||
)
|
||||
|
||||
def test_parse_data_string_with_functions(self):
|
||||
import random, string
|
||||
|
||||
functions_mapping = {
|
||||
"gen_random_string": lambda str_len: ''.join(random.choice(string.ascii_letters + string.digits) \
|
||||
for _ in range(str_len))
|
||||
"gen_random_string": lambda str_len: "".join(
|
||||
random.choice(string.ascii_letters + string.digits)
|
||||
for _ in range(str_len)
|
||||
)
|
||||
}
|
||||
result = parser.parse_data("${gen_random_string(5)}", functions_mapping=functions_mapping)
|
||||
result = parser.parse_data(
|
||||
"${gen_random_string(5)}", functions_mapping=functions_mapping
|
||||
)
|
||||
self.assertEqual(len(result), 5)
|
||||
|
||||
add_two_nums = lambda a, b=1: a + b
|
||||
functions_mapping["add_two_nums"] = add_two_nums
|
||||
self.assertEqual(
|
||||
parser.parse_data("${add_two_nums(1)}", functions_mapping=functions_mapping),
|
||||
2
|
||||
parser.parse_data(
|
||||
"${add_two_nums(1)}", functions_mapping=functions_mapping
|
||||
),
|
||||
2,
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("${add_two_nums(1, 2)}", functions_mapping=functions_mapping),
|
||||
3
|
||||
parser.parse_data(
|
||||
"${add_two_nums(1, 2)}", functions_mapping=functions_mapping
|
||||
),
|
||||
3,
|
||||
)
|
||||
self.assertEqual(
|
||||
parser.parse_data("/api/${add_two_nums(1, 2)}", functions_mapping=functions_mapping),
|
||||
"/api/3"
|
||||
parser.parse_data(
|
||||
"/api/${add_two_nums(1, 2)}", functions_mapping=functions_mapping
|
||||
),
|
||||
"/api/3",
|
||||
)
|
||||
|
||||
with self.assertRaises(FunctionNotFound):
|
||||
@@ -343,28 +255,38 @@ class TestParserBasic(unittest.TestCase):
|
||||
"var_3": 123,
|
||||
"var_4": {"a": 1},
|
||||
"var_5": True,
|
||||
"var_6": None
|
||||
}
|
||||
functions_mapping = {
|
||||
"func1": lambda x, y: str(x) + str(y)
|
||||
"var_6": None,
|
||||
}
|
||||
functions_mapping = {"func1": lambda x, y: str(x) + str(y)}
|
||||
|
||||
value = parser.parse_data("${func1($var_1, $var_3)}", variables_mapping, functions_mapping)
|
||||
value = parser.parse_data(
|
||||
"${func1($var_1, $var_3)}", variables_mapping, functions_mapping
|
||||
)
|
||||
self.assertEqual(value, "abc123")
|
||||
|
||||
value = parser.parse_data("ABC${func1($var_1, $var_3)}DE", variables_mapping, functions_mapping)
|
||||
value = parser.parse_data(
|
||||
"ABC${func1($var_1, $var_3)}DE", variables_mapping, functions_mapping
|
||||
)
|
||||
self.assertEqual(value, "ABCabc123DE")
|
||||
|
||||
value = parser.parse_data("ABC${func1($var_1, $var_3)}$var_5", variables_mapping, functions_mapping)
|
||||
value = parser.parse_data(
|
||||
"ABC${func1($var_1, $var_3)}$var_5", variables_mapping, functions_mapping
|
||||
)
|
||||
self.assertEqual(value, "ABCabc123True")
|
||||
|
||||
value = parser.parse_data("ABC${func1($var_1, $var_3)}DE$var_4", variables_mapping, functions_mapping)
|
||||
value = parser.parse_data(
|
||||
"ABC${func1($var_1, $var_3)}DE$var_4", variables_mapping, functions_mapping
|
||||
)
|
||||
self.assertEqual(value, "ABCabc123DE{'a': 1}")
|
||||
|
||||
value = parser.parse_data("ABC$var_5${func1($var_1, $var_3)}", variables_mapping, functions_mapping)
|
||||
value = parser.parse_data(
|
||||
"ABC$var_5${func1($var_1, $var_3)}", variables_mapping, functions_mapping
|
||||
)
|
||||
self.assertEqual(value, "ABCTrueabc123")
|
||||
|
||||
value = parser.parse_data("ABC${ord(a)}DEF${len(abcd)}", variables_mapping, functions_mapping)
|
||||
value = parser.parse_data(
|
||||
"ABC${ord(a)}DEF${len(abcd)}", variables_mapping, functions_mapping
|
||||
)
|
||||
self.assertEqual(value, "ABC97DEF4")
|
||||
|
||||
def test_parse_data_func_var_duplicate(self):
|
||||
@@ -374,22 +296,26 @@ class TestParserBasic(unittest.TestCase):
|
||||
"var_3": 123,
|
||||
"var_4": {"a": 1},
|
||||
"var_5": True,
|
||||
"var_6": None
|
||||
}
|
||||
functions_mapping = {
|
||||
"func1": lambda x, y: str(x) + str(y)
|
||||
"var_6": None,
|
||||
}
|
||||
functions_mapping = {"func1": lambda x, y: str(x) + str(y)}
|
||||
value = parser.parse_data(
|
||||
"ABC${func1($var_1, $var_3)}--${func1($var_1, $var_3)}",
|
||||
variables_mapping, functions_mapping)
|
||||
variables_mapping,
|
||||
functions_mapping,
|
||||
)
|
||||
self.assertEqual(value, "ABCabc123--abc123")
|
||||
|
||||
value = parser.parse_data("ABC${func1($var_1, $var_3)}$var_1", variables_mapping, functions_mapping)
|
||||
value = parser.parse_data(
|
||||
"ABC${func1($var_1, $var_3)}$var_1", variables_mapping, functions_mapping
|
||||
)
|
||||
self.assertEqual(value, "ABCabc123abc")
|
||||
|
||||
value = parser.parse_data(
|
||||
"ABC${func1($var_1, $var_3)}$var_1--${func1($var_1, $var_3)}$var_1",
|
||||
variables_mapping, functions_mapping)
|
||||
variables_mapping,
|
||||
functions_mapping,
|
||||
)
|
||||
self.assertEqual(value, "ABCabc123abc--abc123abc")
|
||||
|
||||
def test_parse_data_func_abnormal(self):
|
||||
@@ -399,20 +325,22 @@ class TestParserBasic(unittest.TestCase):
|
||||
"var_3": 123,
|
||||
"var_4": {"a": 1},
|
||||
"var_5": True,
|
||||
"var_6": None
|
||||
}
|
||||
functions_mapping = {
|
||||
"func1": lambda x, y: str(x) + str(y)
|
||||
"var_6": None,
|
||||
}
|
||||
functions_mapping = {"func1": lambda x, y: str(x) + str(y)}
|
||||
|
||||
# {
|
||||
value = parser.parse_data("ABC$var_1{", variables_mapping, functions_mapping)
|
||||
self.assertEqual(value, "ABCabc{")
|
||||
|
||||
value = parser.parse_data("{ABC$var_1{}a}", variables_mapping, functions_mapping)
|
||||
value = parser.parse_data(
|
||||
"{ABC$var_1{}a}", variables_mapping, functions_mapping
|
||||
)
|
||||
self.assertEqual(value, "{ABCabc{}a}")
|
||||
|
||||
value = parser.parse_data("AB{C$var_1{}a}", variables_mapping, functions_mapping)
|
||||
value = parser.parse_data(
|
||||
"AB{C$var_1{}a}", variables_mapping, functions_mapping
|
||||
)
|
||||
self.assertEqual(value, "AB{Cabc{}a}")
|
||||
|
||||
# }
|
||||
@@ -452,27 +380,21 @@ class TestParserBasic(unittest.TestCase):
|
||||
|
||||
def test_parse_data_request(self):
|
||||
content = {
|
||||
'request': {
|
||||
'url': '/api/users/$uid',
|
||||
'method': "$method",
|
||||
'headers': {'token': '$token'},
|
||||
'data': {
|
||||
"request": {
|
||||
"url": "/api/users/$uid",
|
||||
"method": "$method",
|
||||
"headers": {"token": "$token"},
|
||||
"data": {
|
||||
"null": None,
|
||||
"true": True,
|
||||
"false": False,
|
||||
"empty_str": "",
|
||||
"value": "abc${add_one(3)}def"
|
||||
}
|
||||
"value": "abc${add_one(3)}def",
|
||||
},
|
||||
}
|
||||
}
|
||||
variables_mapping = {
|
||||
"uid": 1000,
|
||||
"method": "POST",
|
||||
"token": "abc123"
|
||||
}
|
||||
functions_mapping = {
|
||||
"add_one": lambda x: x + 1
|
||||
}
|
||||
variables_mapping = {"uid": 1000, "method": "POST", "token": "abc123"}
|
||||
functions_mapping = {"add_one": lambda x: x + 1}
|
||||
result = parser.parse_data(content, variables_mapping, functions_mapping)
|
||||
self.assertEqual("/api/users/1000", result["request"]["url"])
|
||||
self.assertEqual("abc123", result["request"]["headers"]["token"])
|
||||
@@ -488,11 +410,11 @@ class TestParserBasic(unittest.TestCase):
|
||||
"uid": "1000",
|
||||
"random": "A2dEx",
|
||||
"authorization": "a83de0ff8d2e896dbd8efb81ba14e17d",
|
||||
"data": {"name": "user", "password": "123456"}
|
||||
"data": {"name": "user", "password": "123456"},
|
||||
}
|
||||
functions = {
|
||||
"add_two_nums": lambda a, b=1: a + b,
|
||||
"get_timestamp": lambda: int(time.time() * 1000)
|
||||
"get_timestamp": lambda: int(time.time() * 1000),
|
||||
}
|
||||
testcase_template = {
|
||||
"url": "http://127.0.0.1:5000/api/users/$uid/${add_two_nums(1,2)}",
|
||||
@@ -501,28 +423,17 @@ class TestParserBasic(unittest.TestCase):
|
||||
"Content-Type": "application/json",
|
||||
"authorization": "$authorization",
|
||||
"random": "$random",
|
||||
"sum": "${add_two_nums(1, 2)}"
|
||||
"sum": "${add_two_nums(1, 2)}",
|
||||
},
|
||||
"body": "$data"
|
||||
"body": "$data",
|
||||
}
|
||||
parsed_testcase = parser.parse_data(testcase_template, variables, functions)
|
||||
self.assertEqual(
|
||||
parsed_testcase["url"],
|
||||
"http://127.0.0.1:5000/api/users/1000/3"
|
||||
parsed_testcase["url"], "http://127.0.0.1:5000/api/users/1000/3"
|
||||
)
|
||||
self.assertEqual(
|
||||
parsed_testcase["headers"]["authorization"],
|
||||
variables["authorization"]
|
||||
)
|
||||
self.assertEqual(
|
||||
parsed_testcase["headers"]["random"],
|
||||
variables["random"]
|
||||
)
|
||||
self.assertEqual(
|
||||
parsed_testcase["body"],
|
||||
variables["data"]
|
||||
)
|
||||
self.assertEqual(
|
||||
parsed_testcase["headers"]["sum"],
|
||||
3
|
||||
parsed_testcase["headers"]["authorization"], variables["authorization"]
|
||||
)
|
||||
self.assertEqual(parsed_testcase["headers"]["random"], variables["random"])
|
||||
self.assertEqual(parsed_testcase["body"], variables["data"])
|
||||
self.assertEqual(parsed_testcase["headers"]["sum"], 3)
|
||||
|
||||
@@ -16,5 +16,5 @@ __all__ = [
|
||||
"get_summary",
|
||||
"stringify_summary",
|
||||
"HtmlTestResult",
|
||||
"gen_html_report"
|
||||
"gen_html_report",
|
||||
]
|
||||
|
||||
@@ -9,7 +9,4 @@ HttpRunner html report
|
||||
from httprunner.report.html.result import HtmlTestResult
|
||||
from httprunner.report.html.gen_report import gen_html_report
|
||||
|
||||
__all__ = [
|
||||
"HtmlTestResult",
|
||||
"gen_html_report"
|
||||
]
|
||||
__all__ = ["HtmlTestResult", "gen_html_report"]
|
||||
|
||||
@@ -8,7 +8,12 @@ from httprunner.exceptions import SummaryEmpty
|
||||
from httprunner.schema import TestSuiteSummary
|
||||
|
||||
|
||||
def gen_html_report(testsuite_summary: TestSuiteSummary, report_template=None, report_dir=None, report_file=None):
|
||||
def gen_html_report(
|
||||
testsuite_summary: TestSuiteSummary,
|
||||
report_template=None,
|
||||
report_dir=None,
|
||||
report_file=None,
|
||||
):
|
||||
""" render html report with specified report name and template
|
||||
|
||||
Args:
|
||||
@@ -24,8 +29,7 @@ def gen_html_report(testsuite_summary: TestSuiteSummary, report_template=None, r
|
||||
|
||||
if not report_template:
|
||||
report_template = os.path.join(
|
||||
os.path.abspath(os.path.dirname(__file__)),
|
||||
"template.html"
|
||||
os.path.abspath(os.path.dirname(__file__)), "template.html"
|
||||
)
|
||||
logger.debug("No html report template specified, use default.")
|
||||
else:
|
||||
@@ -39,22 +43,22 @@ def gen_html_report(testsuite_summary: TestSuiteSummary, report_template=None, r
|
||||
else:
|
||||
report_dir = report_dir or os.path.join(os.getcwd(), "reports")
|
||||
# fix #826: Windows does not support file name include ":"
|
||||
report_file_name = "{}.html".format(testsuite_summary.time.start_at_iso_format.replace(":", "").replace("-", ""))
|
||||
report_file_name = "{}.html".format(
|
||||
testsuite_summary.time.start_at_iso_format.replace(":", "").replace("-", "")
|
||||
)
|
||||
|
||||
if not os.path.isdir(report_dir):
|
||||
os.makedirs(report_dir)
|
||||
|
||||
report_path = os.path.join(report_dir, report_file_name)
|
||||
with io.open(report_template, "r", encoding='utf-8') as fp_r:
|
||||
with io.open(report_template, "r", encoding="utf-8") as fp_r:
|
||||
template_content = fp_r.read()
|
||||
with io.open(report_path, 'w', encoding='utf-8') as fp_w:
|
||||
with io.open(report_path, "w", encoding="utf-8") as fp_w:
|
||||
rendered_content = Template(
|
||||
template_content,
|
||||
extensions=["jinja2.ext.loopcontrols"]
|
||||
template_content, extensions=["jinja2.ext.loopcontrols"]
|
||||
).render(testsuite_summary.dict())
|
||||
fp_w.write(rendered_content)
|
||||
|
||||
logger.info(f"Generated Html report: {report_path}")
|
||||
|
||||
return report_path
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ class HtmlTestResult(unittest.TextTestResult):
|
||||
""" A html result class that can generate formatted html results, used by TextTestRunner.
|
||||
Each testcase is corresponding to one HtmlTestResult instance
|
||||
"""
|
||||
|
||||
def __init__(self, stream, descriptions, verbosity):
|
||||
super(HtmlTestResult, self).__init__(stream, descriptions, verbosity)
|
||||
self.name = ""
|
||||
@@ -15,7 +16,7 @@ class HtmlTestResult(unittest.TextTestResult):
|
||||
self.attachment = ""
|
||||
self.step_datas = None
|
||||
|
||||
def _record_test(self, test, status, attachment=''):
|
||||
def _record_test(self, test, status, attachment=""):
|
||||
self.name = test.shortDescription()
|
||||
self.status = status
|
||||
self.attachment = attachment
|
||||
@@ -31,32 +32,32 @@ class HtmlTestResult(unittest.TextTestResult):
|
||||
|
||||
def addSuccess(self, test):
|
||||
super(HtmlTestResult, self).addSuccess(test)
|
||||
self._record_test(test, 'success')
|
||||
self._record_test(test, "success")
|
||||
print("")
|
||||
|
||||
def addError(self, test, err):
|
||||
super(HtmlTestResult, self).addError(test, err)
|
||||
self._record_test(test, 'error', self._exc_info_to_string(err, test))
|
||||
self._record_test(test, "error", self._exc_info_to_string(err, test))
|
||||
print("")
|
||||
|
||||
def addFailure(self, test, err):
|
||||
super(HtmlTestResult, self).addFailure(test, err)
|
||||
self._record_test(test, 'failure', self._exc_info_to_string(err, test))
|
||||
self._record_test(test, "failure", self._exc_info_to_string(err, test))
|
||||
print("")
|
||||
|
||||
def addSkip(self, test, reason):
|
||||
super(HtmlTestResult, self).addSkip(test, reason)
|
||||
self._record_test(test, 'skipped', reason)
|
||||
self._record_test(test, "skipped", reason)
|
||||
print("")
|
||||
|
||||
def addExpectedFailure(self, test, err):
|
||||
super(HtmlTestResult, self).addExpectedFailure(test, err)
|
||||
self._record_test(test, 'ExpectedFailure', self._exc_info_to_string(err, test))
|
||||
self._record_test(test, "ExpectedFailure", self._exc_info_to_string(err, test))
|
||||
print("")
|
||||
|
||||
def addUnexpectedSuccess(self, test):
|
||||
super(HtmlTestResult, self).addUnexpectedSuccess(test)
|
||||
self._record_test(test, 'UnexpectedSuccess')
|
||||
self._record_test(test, "UnexpectedSuccess")
|
||||
print("")
|
||||
|
||||
@property
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
<div class="content">
|
||||
<h3>Name: {{ session_data.name }}</h3>
|
||||
|
||||
{% for req_resp in session_data.req_resp %}
|
||||
{% for req_resp in session_data.req_resps %}
|
||||
|
||||
{% if loop.index > 1 %}
|
||||
<div class="separator">==================================== redirect to ====================================</div>
|
||||
|
||||
@@ -12,40 +12,34 @@ def prepare_event_kwargs(event_name, params):
|
||||
""" prepare report event kwargs"""
|
||||
|
||||
kwargs = {
|
||||
"headers": {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
"headers": {"content-type": "application/json"},
|
||||
"json": {
|
||||
"user": {
|
||||
"user_unique_id": str(uuid.getnode())
|
||||
},
|
||||
"user": {"user_unique_id": str(uuid.getnode())},
|
||||
"header": {
|
||||
"app_id": 173519,
|
||||
"os_name": platform.system(),
|
||||
"os_version": platform.release(),
|
||||
"app_version": __version__ # HttpRunner version
|
||||
"app_version": __version__, # HttpRunner version
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"event": event_name,
|
||||
"params": json.dumps(params),
|
||||
"time": int(time.time())
|
||||
"time": int(time.time()),
|
||||
}
|
||||
],
|
||||
"verbose": 1
|
||||
}
|
||||
"verbose": 1,
|
||||
},
|
||||
}
|
||||
return kwargs
|
||||
|
||||
|
||||
def report_event(event_name, success=True):
|
||||
params = {
|
||||
"success": 1 if success else 0
|
||||
}
|
||||
params = {"success": 1 if success else 0}
|
||||
kwargs = prepare_event_kwargs(event_name, params)
|
||||
resp = requests.post("http://mcs.snssdk.com/v1/json", **kwargs)
|
||||
print("resp---", resp.json())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
report_event("loader")
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import json
|
||||
from base64 import b64encode
|
||||
from collections import Iterable
|
||||
from typing import List
|
||||
|
||||
from jinja2 import escape
|
||||
from requests.cookies import RequestsCookieJar
|
||||
|
||||
from httprunner.schema import TestSuiteSummary
|
||||
from httprunner.schema import TestSuiteSummary, SessionData
|
||||
|
||||
|
||||
def dumps_json(value):
|
||||
@@ -125,8 +126,7 @@ def __stringify_response(response_data):
|
||||
if key == "body" and "image" in response_data["content_type"]:
|
||||
# display image
|
||||
value = "data:{};base64,{}".format(
|
||||
response_data["content_type"],
|
||||
b64encode(value).decode(encoding)
|
||||
response_data["content_type"], b64encode(value).decode(encoding)
|
||||
)
|
||||
else:
|
||||
value = escape(value.decode(encoding))
|
||||
@@ -152,8 +152,19 @@ def stringify_summary(testsuite_summary: TestSuiteSummary):
|
||||
testcase_summary.name = f"testcase {index}"
|
||||
|
||||
step_datas = testcase_summary.step_datas
|
||||
for session_data in step_datas:
|
||||
req_resp_list = session_data.req_resp
|
||||
for req_resp in req_resp_list:
|
||||
__stringify_request(req_resp["request"])
|
||||
__stringify_response(req_resp["response"])
|
||||
for step_data in step_datas:
|
||||
if isinstance(step_data.data, list):
|
||||
# List[SessionData]
|
||||
session_data_list: List[SessionData] = step_data.data
|
||||
for session_data in session_data_list:
|
||||
req_resp_list = session_data.req_resps
|
||||
for req_resp in req_resp_list:
|
||||
__stringify_request(req_resp.request)
|
||||
__stringify_response(req_resp.response)
|
||||
else:
|
||||
# SessionData
|
||||
session_data: SessionData = step_data.data
|
||||
req_resp_list = session_data.req_resps
|
||||
for req_resp in req_resp_list:
|
||||
__stringify_request(req_resp.request)
|
||||
__stringify_response(req_resp.response)
|
||||
|
||||
@@ -10,10 +10,9 @@ def get_platform():
|
||||
return {
|
||||
"httprunner_version": __version__,
|
||||
"python_version": "{} {}".format(
|
||||
platform.python_implementation(),
|
||||
platform.python_version()
|
||||
platform.python_implementation(), platform.python_version()
|
||||
),
|
||||
"platform": platform.platform()
|
||||
"platform": platform.platform(),
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +32,10 @@ def aggregate_stat(origin_stat, new_stat):
|
||||
origin_stat["start_at"] = min(origin_stat["start_at"], new_stat["start_at"])
|
||||
elif key == "duration":
|
||||
# duration = max_end_time - min_start_time
|
||||
max_end_time = max(origin_stat["start_at"] + origin_stat["duration"],
|
||||
new_stat["start_at"] + new_stat["duration"])
|
||||
max_end_time = max(
|
||||
origin_stat["start_at"] + origin_stat["duration"],
|
||||
new_stat["start_at"] + new_stat["duration"],
|
||||
)
|
||||
min_start_time = min(origin_stat["start_at"], new_stat["start_at"])
|
||||
origin_stat["duration"] = max_end_time - min_start_time
|
||||
else:
|
||||
@@ -65,11 +66,11 @@ def get_summary(result: HtmlTestResult) -> TestCaseSummary:
|
||||
time=TestCaseTime(
|
||||
start_at=result.start_at,
|
||||
start_at_iso_format=start_at_iso_format,
|
||||
duration=result.duration
|
||||
duration=result.duration,
|
||||
),
|
||||
name=result.name,
|
||||
status=result.status,
|
||||
attachment=result.attachment,
|
||||
in_out=TestCaseInOut(),
|
||||
step_datas=result.step_datas
|
||||
step_datas=result.step_datas,
|
||||
)
|
||||
|
||||
@@ -28,15 +28,28 @@ def get_uniform_comparator(comparator: Text):
|
||||
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"]:
|
||||
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"]:
|
||||
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"]:
|
||||
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
|
||||
@@ -90,15 +103,10 @@ def uniform_validator(validator):
|
||||
# uniform comparator, e.g. lt => less_than, eq => equals
|
||||
assert_method = get_uniform_comparator(comparator)
|
||||
|
||||
return {
|
||||
"check": check_item,
|
||||
"expect": expect_value,
|
||||
"assert": assert_method
|
||||
}
|
||||
return {"check": check_item, "expect": expect_value, "assert": assert_method}
|
||||
|
||||
|
||||
class ResponseObject(object):
|
||||
|
||||
def __init__(self, resp_obj: requests.Response):
|
||||
""" initialize with a requests.Response object
|
||||
|
||||
@@ -110,7 +118,7 @@ class ResponseObject(object):
|
||||
self.resp_obj_meta = {
|
||||
"status_code": resp_obj.status_code,
|
||||
"headers": resp_obj.headers,
|
||||
"body": resp_obj.json()
|
||||
"body": resp_obj.json(),
|
||||
}
|
||||
self.validation_results: Dict = {}
|
||||
|
||||
@@ -126,10 +134,12 @@ class ResponseObject(object):
|
||||
logger.info(f"extract mapping: {extract_mapping}")
|
||||
return extract_mapping
|
||||
|
||||
def validate(self,
|
||||
validators: Validators,
|
||||
variables_mapping: VariablesMapping = None,
|
||||
functions_mapping: FunctionsMapping = None) -> NoReturn:
|
||||
def validate(
|
||||
self,
|
||||
validators: Validators,
|
||||
variables_mapping: VariablesMapping = None,
|
||||
functions_mapping: FunctionsMapping = None,
|
||||
) -> NoReturn:
|
||||
|
||||
self.validation_results = {}
|
||||
if not validators:
|
||||
@@ -166,7 +176,7 @@ class ResponseObject(object):
|
||||
"check": check_item,
|
||||
"check_value": check_value,
|
||||
"expect": expect_item,
|
||||
"expect_value": expect_value
|
||||
"expect_value": expect_value,
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -178,11 +188,13 @@ class ResponseObject(object):
|
||||
validate_pass = False
|
||||
validator_dict["check_result"] = "fail"
|
||||
validate_msg += "\t==> fail"
|
||||
validate_msg += f"\n" \
|
||||
f"check_item: {check_item}\n" \
|
||||
f"check_value: {check_value}({type(check_value).__name__})\n" \
|
||||
f"assert_method: {assert_method}\n" \
|
||||
f"expect_value: {expect_value}({type(expect_value).__name__})"
|
||||
validate_msg += (
|
||||
f"\n"
|
||||
f"check_item: {check_item}\n"
|
||||
f"check_value: {check_value}({type(check_value).__name__})\n"
|
||||
f"assert_method: {assert_method}\n"
|
||||
f"expect_value: {expect_value}({type(expect_value).__name__})"
|
||||
)
|
||||
logger.error(validate_msg)
|
||||
failures.append(validate_msg)
|
||||
|
||||
|
||||
@@ -7,7 +7,13 @@ from httprunner.client import HttpSession
|
||||
from httprunner.exceptions import ValidationFailure, ParamsError
|
||||
from httprunner.parser import build_url, parse_data, parse_variables_mapping
|
||||
from httprunner.response import ResponseObject
|
||||
from httprunner.schema import TestsConfig, TestStep, VariablesMapping, TestCase, StepData
|
||||
from httprunner.schema import (
|
||||
TestsConfig,
|
||||
TestStep,
|
||||
VariablesMapping,
|
||||
TestCase,
|
||||
StepData,
|
||||
)
|
||||
|
||||
|
||||
class TestCaseRunner(object):
|
||||
@@ -18,7 +24,7 @@ class TestCaseRunner(object):
|
||||
step_datas: List[StepData] = []
|
||||
validation_results: Dict = {}
|
||||
session_variables: Dict = {}
|
||||
success: bool = True # indicate testcase execution result
|
||||
success: bool = True # indicate testcase execution result
|
||||
|
||||
def init(self, testcase: TestCase) -> "TestCaseRunner":
|
||||
self.config = testcase.config
|
||||
@@ -35,13 +41,13 @@ class TestCaseRunner(object):
|
||||
|
||||
def __run_step_request(self, step: TestStep):
|
||||
"""run teststep: request"""
|
||||
step_data = StepData(
|
||||
name=step.name
|
||||
)
|
||||
step_data = StepData(name=step.name)
|
||||
|
||||
# parse
|
||||
request_dict = step.request.dict()
|
||||
parsed_request_dict = parse_data(request_dict, step.variables, self.config.functions)
|
||||
parsed_request_dict = parse_data(
|
||||
request_dict, step.variables, self.config.functions
|
||||
)
|
||||
|
||||
# prepare arguments
|
||||
method = parsed_request_dict.pop("method")
|
||||
@@ -109,13 +115,11 @@ class TestCaseRunner(object):
|
||||
|
||||
def __run_step_testcase(self, step):
|
||||
"""run teststep: referenced testcase"""
|
||||
step_data = StepData(
|
||||
name=step.name
|
||||
)
|
||||
step_data = StepData(name=step.name)
|
||||
step_variables = step.variables
|
||||
testcase: TestCaseRunner = step.testcase() # TODO: fix
|
||||
testcase: TestCaseRunner = step.testcase() # TODO: fix
|
||||
case_result = testcase.with_variables(**step_variables).run()
|
||||
step_data.data = case_result.step_datas # list of step data
|
||||
step_data.data = case_result.step_datas # list of step data
|
||||
step_data.export = case_result.get_export_variables()
|
||||
step_data.success = case_result.success
|
||||
self.success &= case_result.success
|
||||
@@ -131,7 +135,9 @@ class TestCaseRunner(object):
|
||||
elif step.testcase:
|
||||
step_data = self.__run_step_testcase(step)
|
||||
else:
|
||||
raise ParamsError(f"teststep is neither a request nor a referenced testcase: {step.dict()}")
|
||||
raise ParamsError(
|
||||
f"teststep is neither a request nor a referenced testcase: {step.dict()}"
|
||||
)
|
||||
|
||||
self.step_datas.append(step_data)
|
||||
return step_data.export
|
||||
@@ -146,7 +152,9 @@ class TestCaseRunner(object):
|
||||
# update with session variables extracted from former step
|
||||
step.variables.update(self.session_variables)
|
||||
# parse variables
|
||||
step.variables = parse_variables_mapping(step.variables, self.config.functions)
|
||||
step.variables = parse_variables_mapping(
|
||||
step.variables, self.config.functions
|
||||
)
|
||||
# run step
|
||||
extract_mapping = self.__run_step(step)
|
||||
# save extracted variables to session variables
|
||||
@@ -163,7 +171,8 @@ class TestCaseRunner(object):
|
||||
for var_name in self.config.export:
|
||||
if var_name not in self.session_variables:
|
||||
raise ParamsError(
|
||||
f"failed to export variable {var_name} from session variables {self.session_variables}")
|
||||
f"failed to export variable {var_name} from session variables {self.session_variables}"
|
||||
)
|
||||
|
||||
export_vars_mapping[var_name] = self.session_variables[var_name]
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ Env = Dict[Text, Any]
|
||||
|
||||
|
||||
class MethodEnum(Text, Enum):
|
||||
GET = 'GET'
|
||||
POST = 'POST'
|
||||
GET = "GET"
|
||||
POST = "POST"
|
||||
PUT = "PUT"
|
||||
DELETE = "DELETE"
|
||||
HEAD = "HEAD"
|
||||
@@ -44,6 +44,7 @@ class TestsConfig(BaseModel):
|
||||
|
||||
class Request(BaseModel):
|
||||
"""requests.Request model"""
|
||||
|
||||
method: MethodEnum = MethodEnum.GET
|
||||
url: Url
|
||||
params: Dict[Text, Text] = {}
|
||||
@@ -125,6 +126,7 @@ class ReqRespData(BaseModel):
|
||||
|
||||
class SessionData(BaseModel):
|
||||
"""request session data, including request, response, validators and stat data"""
|
||||
|
||||
success: bool = False
|
||||
# in most cases, req_resps only contains one request & response
|
||||
# while when 30X redirect occurs, req_resps will contain multiple request & response
|
||||
@@ -135,8 +137,9 @@ class SessionData(BaseModel):
|
||||
|
||||
class StepData(BaseModel):
|
||||
"""teststep data, each step maybe corresponding to one request or one testcase"""
|
||||
|
||||
success: bool = False
|
||||
name: Text = "" # teststep name
|
||||
name: Text = "" # teststep name
|
||||
data: Union[SessionData, List[SessionData]] = None
|
||||
export: Dict = {}
|
||||
|
||||
|
||||
@@ -80,10 +80,7 @@ def lower_dict_keys(origin_dict):
|
||||
if not origin_dict or not isinstance(origin_dict, dict):
|
||||
return origin_dict
|
||||
|
||||
return {
|
||||
key.lower(): value
|
||||
for key, value in origin_dict.items()
|
||||
}
|
||||
return {key.lower(): value for key, value in origin_dict.items()}
|
||||
|
||||
|
||||
def deepcopy_dict(data):
|
||||
@@ -230,6 +227,7 @@ def omit_long_data(body, omit_len=512):
|
||||
def dump_json_file(json_data: Union[dict, list], json_file_abs_path: str) -> None:
|
||||
""" dump json data to file
|
||||
"""
|
||||
|
||||
class PythonObjectEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
try:
|
||||
@@ -242,14 +240,14 @@ def dump_json_file(json_data: Union[dict, list], json_file_abs_path: str) -> Non
|
||||
os.makedirs(file_foder_path)
|
||||
|
||||
try:
|
||||
with io.open(json_file_abs_path, 'w', encoding='utf-8') as outfile:
|
||||
with io.open(json_file_abs_path, "w", encoding="utf-8") as outfile:
|
||||
json.dump(
|
||||
json_data,
|
||||
outfile,
|
||||
indent=4,
|
||||
separators=(',', ':'),
|
||||
separators=(",", ":"),
|
||||
ensure_ascii=False,
|
||||
cls=PythonObjectEncoder
|
||||
cls=PythonObjectEncoder,
|
||||
)
|
||||
|
||||
msg = f"dump file: {json_file_abs_path}"
|
||||
@@ -268,7 +266,9 @@ def prepare_log_file_abs_path(test_path: str, file_name: str) -> str:
|
||||
if not test_path:
|
||||
# running passed in testcase/testsuite data structure
|
||||
dump_file_name = f"tests_mapping.{file_name}"
|
||||
dumped_json_file_abs_path = os.path.join(current_working_dir, "logs", dump_file_name)
|
||||
dumped_json_file_abs_path = os.path.join(
|
||||
current_working_dir, "logs", dump_file_name
|
||||
)
|
||||
return dumped_json_file_abs_path
|
||||
|
||||
# both test_path and pwd_dir_path are absolute path
|
||||
|
||||
@@ -6,18 +6,16 @@ from httprunner import loader, utils
|
||||
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
|
||||
def test_set_os_environ(self):
|
||||
self.assertNotIn("abc", os.environ)
|
||||
variables_mapping = {
|
||||
"abc": "123"
|
||||
}
|
||||
variables_mapping = {"abc": "123"}
|
||||
utils.set_os_environ(variables_mapping)
|
||||
self.assertIn("abc", os.environ)
|
||||
self.assertEqual(os.environ["abc"], "123")
|
||||
|
||||
def current_validators(self):
|
||||
from httprunner.builtin import comparators
|
||||
|
||||
functions_mapping = loader.load.load_module_functions(comparators)
|
||||
|
||||
functions_mapping["equals"](None, None)
|
||||
@@ -35,17 +33,17 @@ class TestUtils(unittest.TestCase):
|
||||
functions_mapping["not_equals"](123, "123")
|
||||
|
||||
functions_mapping["length_equals"]("123", 3)
|
||||
# Because the Numbers in a CSV file are by default treated as strings,
|
||||
# Because the Numbers in a CSV file are by default treated as strings,
|
||||
# you need to convert them to Numbers, and we'll test that out here.
|
||||
functions_mapping["length_equals"]("123", '3')
|
||||
functions_mapping["length_equals"]("123", "3")
|
||||
with self.assertRaises(AssertionError):
|
||||
functions_mapping["length_equals"]("123", 'abc')
|
||||
functions_mapping["length_equals"]("123", "abc")
|
||||
functions_mapping["length_greater_than"]("123", 2)
|
||||
functions_mapping["length_greater_than_or_equals"]("123", 3)
|
||||
|
||||
functions_mapping["contains"]("123abc456", "3ab")
|
||||
functions_mapping["contains"](['1', '2'], "1")
|
||||
functions_mapping["contains"]({'a':1, 'b':2}, "a")
|
||||
functions_mapping["contains"](["1", "2"], "1")
|
||||
functions_mapping["contains"]({"a": 1, "b": 2}, "a")
|
||||
functions_mapping["contained_by"]("3ab", "123abc456")
|
||||
|
||||
functions_mapping["regex_match"]("123abc456", "^123\w+456$")
|
||||
@@ -72,10 +70,7 @@ class TestUtils(unittest.TestCase):
|
||||
request_dict = {
|
||||
"url": "http://127.0.0.1:5000",
|
||||
"METHOD": "POST",
|
||||
"Headers": {
|
||||
"Accept": "application/json",
|
||||
"User-Agent": "ios/9.3"
|
||||
}
|
||||
"Headers": {"Accept": "application/json", "User-Agent": "ios/9.3"},
|
||||
}
|
||||
new_request_dict = utils.lower_dict_keys(request_dict)
|
||||
self.assertIn("method", new_request_dict)
|
||||
@@ -93,64 +88,43 @@ class TestUtils(unittest.TestCase):
|
||||
|
||||
def test_deepcopy_dict(self):
|
||||
license_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(__file__)),
|
||||
"LICENSE"
|
||||
os.path.dirname(os.path.dirname(__file__)), "LICENSE"
|
||||
)
|
||||
data = {
|
||||
'a': 1,
|
||||
'b': [2, 4],
|
||||
'c': lambda x: x+1,
|
||||
'd': open(license_path),
|
||||
'f': {
|
||||
'f1': {'a1': 2},
|
||||
'f2': io.open(license_path, 'rb'),
|
||||
}
|
||||
"a": 1,
|
||||
"b": [2, 4],
|
||||
"c": lambda x: x + 1,
|
||||
"d": open(license_path),
|
||||
"f": {"f1": {"a1": 2}, "f2": io.open(license_path, "rb"),},
|
||||
}
|
||||
new_data = utils.deepcopy_dict(data)
|
||||
data["a"] = 0
|
||||
self.assertEqual(new_data["a"], 1)
|
||||
data["f"]["f1"] = 123
|
||||
self.assertEqual(new_data["f"]["f1"], {'a1': 2})
|
||||
self.assertEqual(new_data["f"]["f1"], {"a1": 2})
|
||||
self.assertNotEqual(id(new_data["b"]), id(data["b"]))
|
||||
self.assertEqual(id(new_data["c"]), id(data["c"]))
|
||||
# self.assertEqual(id(new_data["d"]), id(data["d"]))
|
||||
|
||||
def test_cartesian_product_one(self):
|
||||
parameters_content_list = [
|
||||
[
|
||||
{"a": 1},
|
||||
{"a": 2}
|
||||
]
|
||||
]
|
||||
parameters_content_list = [[{"a": 1}, {"a": 2}]]
|
||||
product_list = utils.gen_cartesian_product(*parameters_content_list)
|
||||
self.assertEqual(
|
||||
product_list,
|
||||
[
|
||||
{"a": 1},
|
||||
{"a": 2}
|
||||
]
|
||||
)
|
||||
self.assertEqual(product_list, [{"a": 1}, {"a": 2}])
|
||||
|
||||
def test_cartesian_product_multiple(self):
|
||||
parameters_content_list = [
|
||||
[
|
||||
{"a": 1},
|
||||
{"a": 2}
|
||||
],
|
||||
[
|
||||
{"x": 111, "y": 112},
|
||||
{"x": 121, "y": 122}
|
||||
]
|
||||
[{"a": 1}, {"a": 2}],
|
||||
[{"x": 111, "y": 112}, {"x": 121, "y": 122}],
|
||||
]
|
||||
product_list = utils.gen_cartesian_product(*parameters_content_list)
|
||||
self.assertEqual(
|
||||
product_list,
|
||||
[
|
||||
{'a': 1, 'x': 111, 'y': 112},
|
||||
{'a': 1, 'x': 121, 'y': 122},
|
||||
{'a': 2, 'x': 111, 'y': 112},
|
||||
{'a': 2, 'x': 121, 'y': 122}
|
||||
]
|
||||
{"a": 1, "x": 111, "y": 112},
|
||||
{"a": 1, "x": 121, "y": 122},
|
||||
{"a": 2, "x": 111, "y": 112},
|
||||
{"a": 2, "x": 121, "y": 122},
|
||||
],
|
||||
)
|
||||
|
||||
def test_cartesian_product_empty(self):
|
||||
@@ -159,15 +133,7 @@ class TestUtils(unittest.TestCase):
|
||||
self.assertEqual(product_list, [])
|
||||
|
||||
def test_print_info(self):
|
||||
info_mapping = {
|
||||
"a": 1,
|
||||
"t": (1, 2),
|
||||
"b": {
|
||||
"b1": 123
|
||||
},
|
||||
"c": None,
|
||||
"d": [4, 5]
|
||||
}
|
||||
info_mapping = {"a": 1, "t": (1, 2), "b": {"b1": 123}, "c": None, "d": [4, 5]}
|
||||
utils.print_info(info_mapping)
|
||||
|
||||
def test_prepare_dump_json_file_path_for_folder(self):
|
||||
@@ -175,7 +141,7 @@ class TestUtils(unittest.TestCase):
|
||||
test_path = os.path.join("tests", "httpbin", "a.b.c")
|
||||
self.assertEqual(
|
||||
utils.prepare_log_file_abs_path(test_path, "loaded.json"),
|
||||
os.path.join(os.getcwd(), "logs", "tests/httpbin/a.b.c/all.loaded.json")
|
||||
os.path.join(os.getcwd(), "logs", "tests/httpbin/a.b.c/all.loaded.json"),
|
||||
)
|
||||
|
||||
def test_prepare_dump_json_file_path_for_file(self):
|
||||
@@ -183,12 +149,12 @@ class TestUtils(unittest.TestCase):
|
||||
test_path = os.path.join("tests", "httpbin", "a.b.c", "rpc.yml")
|
||||
self.assertEqual(
|
||||
utils.prepare_log_file_abs_path(test_path, "loaded.json"),
|
||||
os.path.join(os.getcwd(), "logs", "tests/httpbin/a.b.c/rpc.loaded.json")
|
||||
os.path.join(os.getcwd(), "logs", "tests/httpbin/a.b.c/rpc.loaded.json"),
|
||||
)
|
||||
|
||||
def test_prepare_dump_json_file_path_for_passed_testcase(self):
|
||||
test_path = ""
|
||||
self.assertEqual(
|
||||
utils.prepare_log_file_abs_path(test_path, "loaded.json"),
|
||||
os.path.join(os.getcwd(), "logs", "tests_mapping.loaded.json")
|
||||
os.path.join(os.getcwd(), "logs", "tests_mapping.loaded.json"),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user