mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-08 08:57:40 +08:00
Merge pull request #869 from httprunner/dev
3.0 dev **Added** - feat: dump log for each testcase **Changed** - replace logging with [loguru](https://github.com/Delgan/loguru) - remove support for Python 2.7 - replace string format with f-string - remove dependency colorama and colorlog - generate reports/logs folder in current working directory
This commit is contained in:
@@ -10,7 +10,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
max-parallel: 6
|
max-parallel: 6
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
|
python-version: [3.6, 3.7, 3.8]
|
||||||
os: [ubuntu-latest, macos-latest] # TODO: windows-latest
|
os: [ubuntu-latest, macos-latest] # TODO: windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
max-parallel: 12
|
max-parallel: 12
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [2.7, 3.5, 3.6, 3.7] # TODO: 3.8
|
python-version: [3.6, 3.7] # TODO: 3.8
|
||||||
os: [ubuntu-latest, macos-latest] # TODO: windows-latest
|
os: [ubuntu-latest, macos-latest] # TODO: windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
# Release History
|
# Release History
|
||||||
|
|
||||||
|
## 3.0.0 (2020-03-10)
|
||||||
|
|
||||||
|
**Added**
|
||||||
|
|
||||||
|
- feat: dump log for each testcase
|
||||||
|
|
||||||
|
**Changed**
|
||||||
|
|
||||||
|
- replace logging with [loguru](https://github.com/Delgan/loguru)
|
||||||
|
- remove support for Python 2.7
|
||||||
|
- replace string format with f-string
|
||||||
|
- remove dependency colorama and colorlog
|
||||||
|
- generate reports/logs folder in current working directory
|
||||||
|
|
||||||
## 2.5.7 (2020-02-21)
|
## 2.5.7 (2020-02-21)
|
||||||
|
|
||||||
**Changed**
|
**Changed**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = "2.5.7"
|
__version__ = "3.0.0-alpha"
|
||||||
__description__ = "One-stop solution for HTTP(S) testing."
|
__description__ = "One-stop solution for HTTP(S) testing."
|
||||||
|
|
||||||
__all__ = ["__version__", "__description__"]
|
__all__ = ["__version__", "__description__"]
|
||||||
|
|||||||
+41
-19
@@ -1,9 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
from sentry_sdk import capture_message
|
from sentry_sdk import capture_message
|
||||||
|
|
||||||
from httprunner import (__version__, exceptions, loader, logger, parser,
|
from httprunner import (__version__, exceptions, loader, parser,
|
||||||
report, runner, utils)
|
report, runner, utils)
|
||||||
|
|
||||||
|
|
||||||
@@ -32,18 +34,23 @@ class HttpRunner(object):
|
|||||||
log_file (str): log file path.
|
log_file (str): log file path.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger.setup_logger(log_level, log_file)
|
|
||||||
|
|
||||||
self.exception_stage = "initialize HttpRunner()"
|
self.exception_stage = "initialize HttpRunner()"
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"failfast": failfast,
|
"failfast": failfast,
|
||||||
"resultclass": report.HtmlTestResult
|
"resultclass": report.HtmlTestResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.remove()
|
||||||
|
log_level = log_level.upper()
|
||||||
|
logger.add(sys.stdout, level=log_level)
|
||||||
|
if log_file:
|
||||||
|
logger.add(log_file, level=log_level)
|
||||||
|
|
||||||
self.unittest_runner = unittest.TextTestRunner(**kwargs)
|
self.unittest_runner = unittest.TextTestRunner(**kwargs)
|
||||||
self.test_loader = unittest.TestLoader()
|
self.test_loader = unittest.TestLoader()
|
||||||
self.save_tests = save_tests
|
self.save_tests = save_tests
|
||||||
self._summary = None
|
self._summary = None
|
||||||
self.project_working_directory = None
|
self.project_mapping = None
|
||||||
|
|
||||||
def _add_tests(self, testcases):
|
def _add_tests(self, testcases):
|
||||||
""" initialize testcase with Runner() and add to test suite.
|
""" initialize testcase with Runner() and add to test suite.
|
||||||
@@ -99,7 +106,7 @@ class HttpRunner(object):
|
|||||||
times = int(times)
|
times = int(times)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise exceptions.ParamsError(
|
raise exceptions.ParamsError(
|
||||||
"times should be digit, given: {}".format(times))
|
f"times should be digit, given: {times}")
|
||||||
|
|
||||||
for times_index in range(times):
|
for times_index in range(times):
|
||||||
# suppose one testcase should not have more than 9999 steps,
|
# suppose one testcase should not have more than 9999 steps,
|
||||||
@@ -128,9 +135,16 @@ class HttpRunner(object):
|
|||||||
"""
|
"""
|
||||||
tests_results = []
|
tests_results = []
|
||||||
|
|
||||||
for testcase in test_suite:
|
for index, testcase in enumerate(test_suite):
|
||||||
|
log_handler = None
|
||||||
|
if self.save_tests:
|
||||||
|
logs_file_abs_path = utils.prepare_log_file_abs_path(
|
||||||
|
self.project_mapping, f"testcase_{index+1}.log"
|
||||||
|
)
|
||||||
|
log_handler = logger.add(logs_file_abs_path, level="DEBUG")
|
||||||
|
|
||||||
testcase_name = testcase.config.get("name")
|
testcase_name = testcase.config.get("name")
|
||||||
logger.log_info("Start to run testcase: {}".format(testcase_name))
|
logger.info(f"Start to run testcase: {testcase_name}")
|
||||||
|
|
||||||
result = self.unittest_runner.run(testcase)
|
result = self.unittest_runner.run(testcase)
|
||||||
if result.wasSuccessful():
|
if result.wasSuccessful():
|
||||||
@@ -138,6 +152,9 @@ class HttpRunner(object):
|
|||||||
else:
|
else:
|
||||||
tests_results.insert(0, (testcase, result))
|
tests_results.insert(0, (testcase, result))
|
||||||
|
|
||||||
|
if self.save_tests and log_handler:
|
||||||
|
logger.remove(log_handler)
|
||||||
|
|
||||||
return tests_results
|
return tests_results
|
||||||
|
|
||||||
def _aggregate(self, tests_results):
|
def _aggregate(self, tests_results):
|
||||||
@@ -162,7 +179,7 @@ class HttpRunner(object):
|
|||||||
"details": []
|
"details": []
|
||||||
}
|
}
|
||||||
|
|
||||||
for tests_result in tests_results:
|
for index, tests_result in enumerate(tests_results):
|
||||||
testcase, result = tests_result
|
testcase, result = tests_result
|
||||||
testcase_summary = report.get_summary(result)
|
testcase_summary = report.get_summary(result)
|
||||||
|
|
||||||
@@ -178,6 +195,12 @@ class HttpRunner(object):
|
|||||||
report.aggregate_stat(summary["stat"]["teststeps"], testcase_summary["stat"])
|
report.aggregate_stat(summary["stat"]["teststeps"], testcase_summary["stat"])
|
||||||
report.aggregate_stat(summary["time"], testcase_summary["time"])
|
report.aggregate_stat(summary["time"], testcase_summary["time"])
|
||||||
|
|
||||||
|
if self.save_tests:
|
||||||
|
logs_file_abs_path = utils.prepare_log_file_abs_path(
|
||||||
|
self.project_mapping, f"testcase_{index + 1}.log"
|
||||||
|
)
|
||||||
|
testcase_summary["log"] = logs_file_abs_path
|
||||||
|
|
||||||
summary["details"].append(testcase_summary)
|
summary["details"].append(testcase_summary)
|
||||||
|
|
||||||
return summary
|
return summary
|
||||||
@@ -186,26 +209,25 @@ class HttpRunner(object):
|
|||||||
""" run testcase/testsuite data
|
""" run testcase/testsuite data
|
||||||
"""
|
"""
|
||||||
capture_message("start to run tests")
|
capture_message("start to run tests")
|
||||||
project_mapping = tests_mapping.get("project_mapping", {})
|
self.project_mapping = tests_mapping.get("project_mapping", {})
|
||||||
self.project_working_directory = project_mapping.get("PWD", os.getcwd())
|
|
||||||
|
|
||||||
if self.save_tests:
|
if self.save_tests:
|
||||||
utils.dump_logs(tests_mapping, project_mapping, "loaded")
|
utils.dump_logs(tests_mapping, self.project_mapping, "loaded")
|
||||||
|
|
||||||
# parse tests
|
# parse tests
|
||||||
self.exception_stage = "parse tests"
|
self.exception_stage = "parse tests"
|
||||||
parsed_testcases = parser.parse_tests(tests_mapping)
|
parsed_testcases = parser.parse_tests(tests_mapping)
|
||||||
parse_failed_testfiles = parser.get_parse_failed_testfiles()
|
parse_failed_testfiles = parser.get_parse_failed_testfiles()
|
||||||
if parse_failed_testfiles:
|
if parse_failed_testfiles:
|
||||||
logger.log_warning("parse failures occurred ...")
|
logger.warning("parse failures occurred ...")
|
||||||
utils.dump_logs(parse_failed_testfiles, project_mapping, "parse_failed")
|
utils.dump_logs(parse_failed_testfiles, self.project_mapping, "parse_failed")
|
||||||
|
|
||||||
if len(parsed_testcases) == 0:
|
if len(parsed_testcases) == 0:
|
||||||
logger.log_error("failed to parse all cases, abort.")
|
logger.error("failed to parse all cases, abort.")
|
||||||
raise exceptions.ParseTestsFailure
|
raise exceptions.ParseTestsFailure
|
||||||
|
|
||||||
if self.save_tests:
|
if self.save_tests:
|
||||||
utils.dump_logs(parsed_testcases, project_mapping, "parsed")
|
utils.dump_logs(parsed_testcases, self.project_mapping, "parsed")
|
||||||
|
|
||||||
# add tests to test suite
|
# add tests to test suite
|
||||||
self.exception_stage = "add tests to test suite"
|
self.exception_stage = "add tests to test suite"
|
||||||
@@ -224,10 +246,10 @@ class HttpRunner(object):
|
|||||||
report.stringify_summary(self._summary)
|
report.stringify_summary(self._summary)
|
||||||
|
|
||||||
if self.save_tests:
|
if self.save_tests:
|
||||||
utils.dump_logs(self._summary, project_mapping, "summary")
|
utils.dump_logs(self._summary, self.project_mapping, "summary")
|
||||||
# save variables and export data
|
# save variables and export data
|
||||||
vars_out = self.get_vars_out()
|
vars_out = self.get_vars_out()
|
||||||
utils.dump_logs(vars_out, project_mapping, "io")
|
utils.dump_logs(vars_out, self.project_mapping, "io")
|
||||||
|
|
||||||
return self._summary
|
return self._summary
|
||||||
|
|
||||||
@@ -295,7 +317,7 @@ class HttpRunner(object):
|
|||||||
dict: result summary
|
dict: result summary
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger.log_info("HttpRunner version: {}".format(__version__))
|
logger.info(f"HttpRunner version: {__version__}")
|
||||||
if loader.is_test_path(path_or_tests):
|
if loader.is_test_path(path_or_tests):
|
||||||
return self.run_path(path_or_tests, dot_env_path, mapping)
|
return self.run_path(path_or_tests, dot_env_path, mapping)
|
||||||
elif loader.is_test_content(path_or_tests):
|
elif loader.is_test_content(path_or_tests):
|
||||||
@@ -303,4 +325,4 @@ class HttpRunner(object):
|
|||||||
loader.init_pwd(project_working_directory)
|
loader.init_pwd(project_working_directory)
|
||||||
return self.run_tests(path_or_tests)
|
return self.run_tests(path_or_tests)
|
||||||
else:
|
else:
|
||||||
raise exceptions.ParamsError("Invalid testcase path or testcases: {}".format(path_or_tests))
|
raise exceptions.ParamsError(f"Invalid testcase path or testcases: {path_or_tests}")
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from httprunner import __version__
|
||||||
|
from .routers import deps, debugtalk, debug
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/hrun/version")
|
||||||
|
async def get_hrun_version():
|
||||||
|
return {
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"result": {
|
||||||
|
"HttpRunner": __version__
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
app.include_router(deps.router)
|
||||||
|
app.include_router(debugtalk.router)
|
||||||
|
app.include_router(debug.router)
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from httprunner.api import HttpRunner
|
||||||
|
from httprunner.schema import ProjectMeta, TestCase
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
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": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
project_meta_json = project_meta.dict(by_alias=True)
|
||||||
|
if project_meta.debugtalk_py:
|
||||||
|
origin_local_keys = list(locals().keys()).copy()
|
||||||
|
exec(project_meta.debugtalk_py, {}, locals())
|
||||||
|
new_local_keys = list(locals().keys()).copy()
|
||||||
|
new_added_keys = set(new_local_keys) - set(origin_local_keys)
|
||||||
|
new_added_keys.remove("origin_local_keys")
|
||||||
|
project_meta_json["functions"] = {}
|
||||||
|
for func_name in new_added_keys:
|
||||||
|
project_meta_json["functions"][func_name] = locals()[func_name]
|
||||||
|
|
||||||
|
testcase_json = testcase.dict(by_alias=True)
|
||||||
|
tests_mapping = {
|
||||||
|
"project_mapping": project_meta_json,
|
||||||
|
"testcases": [testcase_json]
|
||||||
|
}
|
||||||
|
|
||||||
|
summary = runner.run_tests(tests_mapping)
|
||||||
|
if not summary["success"]:
|
||||||
|
resp["code"] = 1
|
||||||
|
resp["message"] = "fail"
|
||||||
|
|
||||||
|
resp["result"] = summary
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
# @router.post("/hrun/debug/api", tags=["debug"])
|
||||||
|
# async def debug_single_api():
|
||||||
|
# resp = {
|
||||||
|
# "code": 0,
|
||||||
|
# "message": "success",
|
||||||
|
# "result": {}
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # tests_mapping
|
||||||
|
#
|
||||||
|
# # summary = runner.run_tests(tests_mapping)
|
||||||
|
#
|
||||||
|
# return resp
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @router.post("/hrun/debug/testcases", tags=["debug"])
|
||||||
|
# async def debug_multiple_testcases(project_meta: ProjectMeta, testcases: TestCases):
|
||||||
|
# tests_mapping = {
|
||||||
|
# "project_mapping": project_meta,
|
||||||
|
# "testcases": testcases
|
||||||
|
# }
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from starlette.testclient import TestClient
|
||||||
|
|
||||||
|
from httprunner.app.main import app
|
||||||
|
|
||||||
|
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": {}
|
||||||
|
},
|
||||||
|
"testcase": {
|
||||||
|
"config": {
|
||||||
|
"name": "test demo for debug service",
|
||||||
|
"verify": False,
|
||||||
|
"base_url": "",
|
||||||
|
"variables": {},
|
||||||
|
"setup_hooks": [],
|
||||||
|
"teardown_hooks": [],
|
||||||
|
"export": []
|
||||||
|
},
|
||||||
|
"teststeps": [
|
||||||
|
{
|
||||||
|
"name": "get index page",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "https://httpbin.org/",
|
||||||
|
"params": {},
|
||||||
|
"headers": {},
|
||||||
|
"json": {},
|
||||||
|
"cookies": {},
|
||||||
|
"timeout": 30,
|
||||||
|
"allow_redirects": True,
|
||||||
|
"verify": False
|
||||||
|
},
|
||||||
|
"extract": {},
|
||||||
|
"validate": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response = client.post("/hrun/debug/testcase", json=json_data)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json()["code"] == 0
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import contextlib
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from starlette.requests import Request
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def stdout_io(stdout=None):
|
||||||
|
old = sys.stdout
|
||||||
|
if stdout is None:
|
||||||
|
stdout = StringIO()
|
||||||
|
sys.stdout = stdout
|
||||||
|
yield stdout
|
||||||
|
sys.stdout = old
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/hrun/debug/debugtalk_py", tags=["debugtalk"])
|
||||||
|
async def debug_python(request: Request):
|
||||||
|
body = await request.body()
|
||||||
|
|
||||||
|
if request.headers.get('content-transfer-encoding') == "base64":
|
||||||
|
# TODO: decode base64
|
||||||
|
pass
|
||||||
|
|
||||||
|
resp = {
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"result": ""
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
with stdout_io() as s:
|
||||||
|
exec(body, globals())
|
||||||
|
output = s.getvalue()
|
||||||
|
resp["result"] = output
|
||||||
|
except Exception as ex:
|
||||||
|
resp["code"] = 1
|
||||||
|
resp["message"] = "fail"
|
||||||
|
resp["result"] = str(ex)
|
||||||
|
logging.error(resp)
|
||||||
|
|
||||||
|
return resp
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import pkg_resources
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/hrun/deps", tags=["deps"])
|
||||||
|
async def get_installed_dependenies():
|
||||||
|
resp = {
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"result": {}
|
||||||
|
}
|
||||||
|
for p in pkg_resources.working_set:
|
||||||
|
resp["result"][p.project_name] = p.version
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/hrun/deps", tags=["deps"])
|
||||||
|
async def install_dependenies(deps: List[str]):
|
||||||
|
resp = {
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"result": {}
|
||||||
|
}
|
||||||
|
for dep in deps:
|
||||||
|
try:
|
||||||
|
p = subprocess.run(["pip", "install", dep])
|
||||||
|
assert p.returncode == 0
|
||||||
|
resp["result"][dep] = True
|
||||||
|
except (AssertionError, subprocess.SubprocessError):
|
||||||
|
resp["result"][dep] = False
|
||||||
|
resp["code"] = 1
|
||||||
|
resp["message"] = "fail"
|
||||||
|
logging.error(f"failed to install dependency: {dep}")
|
||||||
|
|
||||||
|
return resp
|
||||||
@@ -4,8 +4,6 @@ Built-in validate comparators.
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from httprunner.compat import basestring, builtin_str, integer_types
|
|
||||||
|
|
||||||
|
|
||||||
def equals(check_value, expect_value):
|
def equals(check_value, expect_value):
|
||||||
assert check_value == expect_value
|
assert check_value == expect_value
|
||||||
@@ -32,46 +30,46 @@ def not_equals(check_value, expect_value):
|
|||||||
|
|
||||||
|
|
||||||
def string_equals(check_value, expect_value):
|
def string_equals(check_value, expect_value):
|
||||||
assert builtin_str(check_value) == builtin_str(expect_value)
|
assert str(check_value) == str(expect_value)
|
||||||
|
|
||||||
|
|
||||||
def length_equals(check_value, expect_value):
|
def length_equals(check_value, expect_value):
|
||||||
assert isinstance(expect_value, integer_types)
|
assert isinstance(expect_value, int)
|
||||||
expect_len = _cast_to_int(expect_value)
|
expect_len = _cast_to_int(expect_value)
|
||||||
assert len(check_value) == expect_len
|
assert len(check_value) == expect_len
|
||||||
|
|
||||||
|
|
||||||
def length_greater_than(check_value, expect_value):
|
def length_greater_than(check_value, expect_value):
|
||||||
assert isinstance(expect_value, integer_types)
|
assert isinstance(expect_value, int)
|
||||||
expect_len = _cast_to_int(expect_value)
|
expect_len = _cast_to_int(expect_value)
|
||||||
assert len(check_value) > expect_len
|
assert len(check_value) > expect_len
|
||||||
|
|
||||||
|
|
||||||
def length_greater_than_or_equals(check_value, expect_value):
|
def length_greater_than_or_equals(check_value, expect_value):
|
||||||
assert isinstance(expect_value, integer_types)
|
assert isinstance(expect_value, int)
|
||||||
expect_len = _cast_to_int(expect_value)
|
expect_len = _cast_to_int(expect_value)
|
||||||
assert len(check_value) >= expect_len
|
assert len(check_value) >= expect_len
|
||||||
|
|
||||||
|
|
||||||
def length_less_than(check_value, expect_value):
|
def length_less_than(check_value, expect_value):
|
||||||
assert isinstance(expect_value, integer_types)
|
assert isinstance(expect_value, int)
|
||||||
expect_len = _cast_to_int(expect_value)
|
expect_len = _cast_to_int(expect_value)
|
||||||
assert len(check_value) < expect_len
|
assert len(check_value) < expect_len
|
||||||
|
|
||||||
|
|
||||||
def length_less_than_or_equals(check_value, expect_value):
|
def length_less_than_or_equals(check_value, expect_value):
|
||||||
assert isinstance(expect_value, integer_types)
|
assert isinstance(expect_value, int)
|
||||||
expect_len = _cast_to_int(expect_value)
|
expect_len = _cast_to_int(expect_value)
|
||||||
assert len(check_value) <= expect_len
|
assert len(check_value) <= expect_len
|
||||||
|
|
||||||
|
|
||||||
def contains(check_value, expect_value):
|
def contains(check_value, expect_value):
|
||||||
assert isinstance(check_value, (list, tuple, dict, basestring))
|
assert isinstance(check_value, (list, tuple, dict, str, bytes))
|
||||||
assert expect_value in check_value
|
assert expect_value in check_value
|
||||||
|
|
||||||
|
|
||||||
def contained_by(check_value, expect_value):
|
def contained_by(check_value, expect_value):
|
||||||
assert isinstance(expect_value, (list, tuple, dict, basestring))
|
assert isinstance(expect_value, (list, tuple, dict, str, bytes))
|
||||||
assert check_value in expect_value
|
assert check_value in expect_value
|
||||||
|
|
||||||
|
|
||||||
@@ -79,7 +77,7 @@ def type_match(check_value, expect_value):
|
|||||||
def get_type(name):
|
def get_type(name):
|
||||||
if isinstance(name, type):
|
if isinstance(name, type):
|
||||||
return name
|
return name
|
||||||
elif isinstance(name, basestring):
|
elif isinstance(name, str):
|
||||||
try:
|
try:
|
||||||
return __builtins__[name]
|
return __builtins__[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -91,17 +89,17 @@ def type_match(check_value, expect_value):
|
|||||||
|
|
||||||
|
|
||||||
def regex_match(check_value, expect_value):
|
def regex_match(check_value, expect_value):
|
||||||
assert isinstance(expect_value, basestring)
|
assert isinstance(expect_value, str)
|
||||||
assert isinstance(check_value, basestring)
|
assert isinstance(check_value, str)
|
||||||
assert re.match(expect_value, check_value)
|
assert re.match(expect_value, check_value)
|
||||||
|
|
||||||
|
|
||||||
def startswith(check_value, expect_value):
|
def startswith(check_value, expect_value):
|
||||||
assert builtin_str(check_value).startswith(builtin_str(expect_value))
|
assert str(check_value).startswith(str(expect_value))
|
||||||
|
|
||||||
|
|
||||||
def endswith(check_value, expect_value):
|
def endswith(check_value, expect_value):
|
||||||
assert builtin_str(check_value).endswith(builtin_str(expect_value))
|
assert str(check_value).endswith(str(expect_value))
|
||||||
|
|
||||||
|
|
||||||
def _cast_to_int(expect_value):
|
def _cast_to_int(expect_value):
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import random
|
|||||||
import string
|
import string
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from httprunner.compat import builtin_str, integer_types
|
|
||||||
from httprunner.exceptions import ParamsError
|
from httprunner.exceptions import ParamsError
|
||||||
|
|
||||||
|
|
||||||
@@ -21,8 +20,8 @@ def gen_random_string(str_len):
|
|||||||
def get_timestamp(str_len=13):
|
def get_timestamp(str_len=13):
|
||||||
""" get timestamp string, length can only between 0 and 16
|
""" get timestamp string, length can only between 0 and 16
|
||||||
"""
|
"""
|
||||||
if isinstance(str_len, integer_types) and 0 < str_len < 17:
|
if isinstance(str_len, int) and 0 < str_len < 17:
|
||||||
return builtin_str(time.time()).replace(".", "")[:str_len]
|
return str(time.time()).replace(".", "")[:str_len]
|
||||||
|
|
||||||
raise ParamsError("timestamp length can only between 0 and 16.")
|
raise ParamsError("timestamp length can only between 0 and 16.")
|
||||||
|
|
||||||
|
|||||||
+8
-13
@@ -3,14 +3,13 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import __description__, __version__, exceptions
|
from httprunner import __description__, __version__, exceptions
|
||||||
from httprunner.api import HttpRunner
|
from httprunner.api import HttpRunner
|
||||||
from httprunner.compat import is_py2
|
|
||||||
from httprunner.loader import load_cases
|
from httprunner.loader import load_cases
|
||||||
from httprunner.logger import color_print, log_error
|
|
||||||
from httprunner.report import gen_html_report
|
from httprunner.report import gen_html_report
|
||||||
from httprunner.utils import (create_scaffold, get_python2_retire_msg,
|
from httprunner.utils import (create_scaffold,
|
||||||
prettify_json_file, init_sentry_sdk)
|
prettify_json_file, init_sentry_sdk)
|
||||||
|
|
||||||
init_sentry_sdk()
|
init_sentry_sdk()
|
||||||
@@ -19,9 +18,6 @@ init_sentry_sdk()
|
|||||||
def main():
|
def main():
|
||||||
""" API test: parse command line options and run commands.
|
""" API test: parse command line options and run commands.
|
||||||
"""
|
"""
|
||||||
if is_py2:
|
|
||||||
color_print(get_python2_retire_msg(), "YELLOW")
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description=__description__)
|
parser = argparse.ArgumentParser(description=__description__)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-V', '--version', dest='version', action='store_true',
|
'-V', '--version', dest='version', action='store_true',
|
||||||
@@ -71,19 +67,19 @@ def main():
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if args.version:
|
if args.version:
|
||||||
color_print("{}".format(__version__), "GREEN")
|
print(f"{__version__}")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if args.validate:
|
if args.validate:
|
||||||
for validate_path in args.validate:
|
for validate_path in args.validate:
|
||||||
try:
|
try:
|
||||||
color_print("validate test file: {}".format(validate_path), "GREEN")
|
logger.info(f"validate test file: {validate_path}")
|
||||||
load_cases(validate_path, args.dot_env_path)
|
load_cases(validate_path, args.dot_env_path)
|
||||||
except exceptions.MyBaseError as ex:
|
except exceptions.MyBaseError as ex:
|
||||||
log_error(str(ex))
|
logger.error(str(ex))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
color_print("done!", "BLUE")
|
logger.info("done!")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if args.prettify:
|
if args.prettify:
|
||||||
@@ -106,7 +102,7 @@ def main():
|
|||||||
try:
|
try:
|
||||||
for path in args.testfile_paths:
|
for path in args.testfile_paths:
|
||||||
summary = runner.run(path, dot_env_path=args.dot_env_path)
|
summary = runner.run(path, dot_env_path=args.dot_env_path)
|
||||||
report_dir = args.report_dir or os.path.join(runner.project_working_directory, "reports")
|
report_dir = args.report_dir or os.path.join(os.getcwd(), "reports")
|
||||||
gen_html_report(
|
gen_html_report(
|
||||||
summary,
|
summary,
|
||||||
report_template=args.report_template,
|
report_template=args.report_template,
|
||||||
@@ -115,8 +111,7 @@ def main():
|
|||||||
)
|
)
|
||||||
err_code |= (0 if summary and summary["success"] else 1)
|
err_code |= (0 if summary and summary["success"] else 1)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
color_print("!!!!!!!!!! exception stage: {} !!!!!!!!!!".format(runner.exception_stage), "YELLOW")
|
logger.error(f"!!!!!!!!!! exception stage: {runner.exception_stage} !!!!!!!!!!\n{str(ex)}")
|
||||||
color_print(str(ex), "RED")
|
|
||||||
sentry_sdk.capture_exception(ex)
|
sentry_sdk.capture_exception(ex)
|
||||||
err_code = 1
|
err_code = 1
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import io
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from httprunner.cli import main
|
from httprunner.cli import main
|
||||||
from httprunner.compat import io
|
|
||||||
|
|
||||||
|
|
||||||
class TestCli(unittest.TestCase):
|
class TestCli(unittest.TestCase):
|
||||||
+13
-14
@@ -4,11 +4,12 @@ import time
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
import urllib3
|
import urllib3
|
||||||
|
from loguru import logger
|
||||||
from requests import Request, Response
|
from requests import Request, Response
|
||||||
from requests.exceptions import (InvalidSchema, InvalidURL, MissingSchema,
|
from requests.exceptions import (InvalidSchema, InvalidURL, MissingSchema,
|
||||||
RequestException)
|
RequestException)
|
||||||
|
|
||||||
from httprunner import logger, response
|
from httprunner import response
|
||||||
from httprunner.utils import lower_dict_keys, omit_long_data
|
from httprunner.utils import lower_dict_keys, omit_long_data
|
||||||
|
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
@@ -18,10 +19,10 @@ def get_req_resp_record(resp_obj):
|
|||||||
""" get request and response info from Response() object.
|
""" get request and response info from Response() object.
|
||||||
"""
|
"""
|
||||||
def log_print(req_resp_dict, r_type):
|
def log_print(req_resp_dict, r_type):
|
||||||
msg = "\n================== {} details ==================\n".format(r_type)
|
msg = f"\n================== {r_type} details ==================\n"
|
||||||
for key, value in req_resp_dict[r_type].items():
|
for key, value in req_resp_dict[r_type].items():
|
||||||
msg += "{:<16} : {}\n".format(key, repr(value))
|
msg += "{:<16} : {}\n".format(key, repr(value))
|
||||||
logger.log_debug(msg)
|
logger.debug(msg)
|
||||||
|
|
||||||
req_resp_dict = {
|
req_resp_dict = {
|
||||||
"request": {},
|
"request": {},
|
||||||
@@ -214,15 +215,13 @@ class HttpSession(requests.Session):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except RequestException as e:
|
except RequestException as ex:
|
||||||
logger.log_error(u"{exception}".format(exception=str(e)))
|
logger.error(f"{str(ex)}")
|
||||||
else:
|
else:
|
||||||
logger.log_info(
|
logger.info(
|
||||||
"""status_code: {}, response_time(ms): {} ms, response_length: {} bytes\n""".format(
|
f"status_code: {response.status_code}, "
|
||||||
response.status_code,
|
f"response_time(ms): {response_time_ms} ms, "
|
||||||
response_time_ms,
|
f"response_length: {content_size} bytes\n"
|
||||||
content_size
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@@ -234,9 +233,9 @@ class HttpSession(requests.Session):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
msg = "processed request:\n"
|
msg = "processed request:\n"
|
||||||
msg += "> {method} {url}\n".format(method=method, url=url)
|
msg += f"> {method} {url}\n"
|
||||||
msg += "> kwargs: {kwargs}".format(kwargs=kwargs)
|
msg += f"> kwargs: {kwargs}"
|
||||||
logger.log_debug(msg)
|
logger.debug(msg)
|
||||||
return requests.Session.request(self, method, url, **kwargs)
|
return requests.Session.request(self, method, url, **kwargs)
|
||||||
except (MissingSchema, InvalidSchema, InvalidURL):
|
except (MissingSchema, InvalidSchema, InvalidURL):
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
# encoding: utf-8
|
|
||||||
|
|
||||||
"""
|
|
||||||
httprunner.compat
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module handles import compatibility issues between Python 2 and
|
|
||||||
Python 3.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
import json
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# -------
|
|
||||||
# Pythons
|
|
||||||
# -------
|
|
||||||
|
|
||||||
# Syntax sugar.
|
|
||||||
_ver = sys.version_info
|
|
||||||
|
|
||||||
#: Python 2.x?
|
|
||||||
is_py2 = (_ver[0] == 2)
|
|
||||||
|
|
||||||
#: Python 3.x?
|
|
||||||
is_py3 = (_ver[0] == 3)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------
|
|
||||||
# Specifics
|
|
||||||
# ---------
|
|
||||||
|
|
||||||
try:
|
|
||||||
JSONDecodeError = json.JSONDecodeError
|
|
||||||
except AttributeError:
|
|
||||||
JSONDecodeError = ValueError
|
|
||||||
|
|
||||||
if is_py2:
|
|
||||||
builtin_str = str
|
|
||||||
bytes = str
|
|
||||||
str = unicode
|
|
||||||
basestring = basestring
|
|
||||||
numeric_types = (int, long, float)
|
|
||||||
integer_types = (int, long)
|
|
||||||
|
|
||||||
FileNotFoundError = IOError
|
|
||||||
import StringIO as io
|
|
||||||
|
|
||||||
elif is_py3:
|
|
||||||
builtin_str = str
|
|
||||||
str = str
|
|
||||||
bytes = bytes
|
|
||||||
basestring = (str, bytes)
|
|
||||||
numeric_types = (int, float)
|
|
||||||
integer_types = (int,)
|
|
||||||
|
|
||||||
FileNotFoundError = FileNotFoundError
|
|
||||||
import io as io
|
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
# encoding: utf-8
|
|
||||||
|
|
||||||
from httprunner.compat import JSONDecodeError, FileNotFoundError
|
|
||||||
|
|
||||||
""" failure type exceptions
|
""" failure type exceptions
|
||||||
these exceptions will mark test as failure
|
these exceptions will mark test as failure
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ import multiprocessing
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import __version__
|
from httprunner import __version__
|
||||||
from httprunner import logger
|
|
||||||
from httprunner.utils import init_sentry_sdk
|
from httprunner.utils import init_sentry_sdk
|
||||||
|
|
||||||
init_sentry_sdk()
|
init_sentry_sdk()
|
||||||
@@ -31,7 +32,7 @@ def parse_locustfile(file_path):
|
|||||||
if file_path is a YAML/JSON file, convert it to locustfile
|
if file_path is a YAML/JSON file, convert it to locustfile
|
||||||
"""
|
"""
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
logger.color_print("file path invalid, exit.", "RED")
|
logger.error("file path invalid, exit.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
file_suffix = os.path.splitext(file_path)[1]
|
file_suffix = os.path.splitext(file_path)[1]
|
||||||
@@ -41,7 +42,7 @@ def parse_locustfile(file_path):
|
|||||||
locustfile_path = gen_locustfile(file_path)
|
locustfile_path = gen_locustfile(file_path)
|
||||||
else:
|
else:
|
||||||
# '' or other suffix
|
# '' or other suffix
|
||||||
logger.color_print("file type should be YAML/JSON/Python, exit.", "RED")
|
logger.error("file type should be YAML/JSON/Python, exit.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
return locustfile_path
|
return locustfile_path
|
||||||
@@ -105,7 +106,7 @@ def run_locusts_with_processes(sys_argv, processes_count):
|
|||||||
def main():
|
def main():
|
||||||
""" Performance test with locust: parse command line options and run commands.
|
""" Performance test with locust: parse command line options and run commands.
|
||||||
"""
|
"""
|
||||||
print("HttpRunner version: {}".format(__version__))
|
print(f"HttpRunner version: {__version__}")
|
||||||
sys.argv[0] = 'locust'
|
sys.argv[0] = 'locust'
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
sys.argv.extend(["-h"])
|
sys.argv.extend(["-h"])
|
||||||
@@ -126,11 +127,13 @@ def main():
|
|||||||
loglevel_index = get_arg_index("-L", "--loglevel")
|
loglevel_index = get_arg_index("-L", "--loglevel")
|
||||||
if loglevel_index and loglevel_index < len(sys.argv):
|
if loglevel_index and loglevel_index < len(sys.argv):
|
||||||
loglevel = sys.argv[loglevel_index]
|
loglevel = sys.argv[loglevel_index]
|
||||||
|
loglevel = loglevel.upper()
|
||||||
else:
|
else:
|
||||||
# default
|
# default
|
||||||
loglevel = "WARNING"
|
loglevel = "WARNING"
|
||||||
|
|
||||||
logger.setup_logger(loglevel)
|
logger.remove()
|
||||||
|
logger.add(sys.stdout, level=loglevel)
|
||||||
|
|
||||||
# get testcase file path
|
# get testcase file path
|
||||||
try:
|
try:
|
||||||
@@ -147,7 +150,7 @@ def main():
|
|||||||
""" locusts -f locustfile.py --processes 4
|
""" locusts -f locustfile.py --processes 4
|
||||||
"""
|
"""
|
||||||
if "--no-web" in sys.argv:
|
if "--no-web" in sys.argv:
|
||||||
logger.log_error("conflict parameter args: --processes & --no-web. \nexit.")
|
logger.error("conflict parameter args: --processes & --no-web. \nexit.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
processes_index = sys.argv.index('--processes')
|
processes_index = sys.argv.index('--processes')
|
||||||
@@ -157,7 +160,7 @@ def main():
|
|||||||
locusts -f locustfile.py --processes
|
locusts -f locustfile.py --processes
|
||||||
"""
|
"""
|
||||||
processes_count = multiprocessing.cpu_count()
|
processes_count = multiprocessing.cpu_count()
|
||||||
logger.log_warning("processes count not specified, use {} by default.".format(processes_count))
|
logger.warning(f"processes count not specified, use {processes_count} by default.")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
""" locusts -f locustfile.py --processes 4 """
|
""" locusts -f locustfile.py --processes 4 """
|
||||||
@@ -166,7 +169,7 @@ def main():
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
""" locusts -f locustfile.py --processes -P 8888 """
|
""" locusts -f locustfile.py --processes -P 8888 """
|
||||||
processes_count = multiprocessing.cpu_count()
|
processes_count = multiprocessing.cpu_count()
|
||||||
logger.log_warning("processes count not specified, use {} by default.".format(processes_count))
|
logger.warning(f"processes count not specified, use {processes_count} by default.")
|
||||||
|
|
||||||
sys.argv.pop(processes_index)
|
sys.argv.pop(processes_index)
|
||||||
run_locusts_with_processes(sys.argv, processes_count)
|
run_locusts_with_processes(sys.argv, processes_count)
|
||||||
|
|||||||
@@ -85,12 +85,12 @@ def prepare_upload_test(test_dict):
|
|||||||
"""
|
"""
|
||||||
upload_json = test_dict["request"].pop("upload", {})
|
upload_json = test_dict["request"].pop("upload", {})
|
||||||
if not upload_json:
|
if not upload_json:
|
||||||
raise ParamsError("invalid upload info: {}".format(upload_json))
|
raise ParamsError(f"invalid upload info: {upload_json}")
|
||||||
|
|
||||||
params_list = []
|
params_list = []
|
||||||
for key, value in upload_json.items():
|
for key, value in upload_json.items():
|
||||||
test_dict["variables"][key] = value
|
test_dict["variables"][key] = value
|
||||||
params_list.append("{}=${}".format(key, key))
|
params_list.append(f"{key}=${key}")
|
||||||
|
|
||||||
params_str = ", ".join(params_list)
|
params_str = ", ".join(params_list)
|
||||||
test_dict["variables"]["m_encoder"] = "${multipart_encoder(" + params_str + ")}"
|
test_dict["variables"]["m_encoder"] = "${multipart_encoder(" + params_str + ")}"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from httprunner import exceptions, logger, utils
|
from loguru import logger
|
||||||
|
|
||||||
|
from httprunner import exceptions, utils
|
||||||
from httprunner.loader.check import JsonSchemaChecker
|
from httprunner.loader.check import JsonSchemaChecker
|
||||||
from httprunner.loader.load import load_module_functions, load_file, load_dot_env_file, \
|
from httprunner.loader.load import load_module_functions, load_file, load_dot_env_file, \
|
||||||
load_folder_files
|
load_folder_files
|
||||||
@@ -53,7 +55,7 @@ def __extend_with_api_ref(raw_testinfo):
|
|||||||
if api_name in tests_def_mapping["api"]:
|
if api_name in tests_def_mapping["api"]:
|
||||||
block = tests_def_mapping["api"][api_name]
|
block = tests_def_mapping["api"][api_name]
|
||||||
elif not os.path.isfile(api_name):
|
elif not os.path.isfile(api_name):
|
||||||
raise exceptions.ApiNotFound("{} not found!".format(api_name))
|
raise exceptions.ApiNotFound(f"{api_name} not found!")
|
||||||
else:
|
else:
|
||||||
block = load_file(api_name)
|
block = load_file(api_name)
|
||||||
|
|
||||||
@@ -84,7 +86,7 @@ def __extend_with_testcase_ref(raw_testinfo):
|
|||||||
testcase_dict = load_testcase_v2(loaded_testcase)
|
testcase_dict = load_testcase_v2(loaded_testcase)
|
||||||
else:
|
else:
|
||||||
raise exceptions.FileFormatError(
|
raise exceptions.FileFormatError(
|
||||||
"Invalid format testcase: {}".format(testcase_path))
|
f"Invalid format testcase: {testcase_path}")
|
||||||
|
|
||||||
tests_def_mapping["testcases"][testcase_path] = testcase_dict
|
tests_def_mapping["testcases"][testcase_path] = testcase_dict
|
||||||
else:
|
else:
|
||||||
@@ -186,8 +188,8 @@ def load_testcase(raw_testcase):
|
|||||||
elif key == "test":
|
elif key == "test":
|
||||||
tests.append(load_teststep(test_block))
|
tests.append(load_teststep(test_block))
|
||||||
else:
|
else:
|
||||||
logger.log_warning(
|
logger.warning(
|
||||||
"unexpected block key: {}. block key should only be 'config' or 'test'.".format(key)
|
f"unexpected block key: {key}. block key should only be 'config' or 'test'."
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -417,7 +419,7 @@ def load_project_data(test_path, dot_env_path=None):
|
|||||||
# locate PWD and load debugtalk.py functions
|
# locate PWD and load debugtalk.py functions
|
||||||
project_mapping["PWD"] = project_working_directory
|
project_mapping["PWD"] = project_working_directory
|
||||||
project_mapping["functions"] = debugtalk_functions
|
project_mapping["functions"] = debugtalk_functions
|
||||||
project_mapping["test_path"] = os.path.abspath(test_path)
|
project_mapping["test_path"] = os.path.abspath(test_path)[len(project_working_directory)+1:]
|
||||||
|
|
||||||
return project_mapping
|
return project_mapping
|
||||||
|
|
||||||
@@ -485,9 +487,9 @@ def load_cases(path, dot_env_path=None):
|
|||||||
try:
|
try:
|
||||||
loaded_content = load_test_file(path)
|
loaded_content = load_test_file(path)
|
||||||
except exceptions.ApiNotFound as ex:
|
except exceptions.ApiNotFound as ex:
|
||||||
logger.log_warning("Invalid api reference in {}: {}".format(path, ex))
|
logger.warning(f"Invalid api reference in {path}: {ex}")
|
||||||
except exceptions.FileFormatError:
|
except exceptions.FileFormatError:
|
||||||
logger.log_warning("Invalid test file format: {}".format(path))
|
logger.warning(f"Invalid test file format: {path}")
|
||||||
|
|
||||||
if not loaded_content:
|
if not loaded_content:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -281,5 +281,11 @@ class TestSuiteLoader(unittest.TestCase):
|
|||||||
buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
|
buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
|
||||||
self.assertIn("gen_md5", self.project_mapping["functions"])
|
self.assertIn("gen_md5", self.project_mapping["functions"])
|
||||||
self.assertEqual(self.project_mapping["env"]["PROJECT_KEY"], "ABCDEFGH")
|
self.assertEqual(self.project_mapping["env"]["PROJECT_KEY"], "ABCDEFGH")
|
||||||
self.assertEqual(self.project_mapping["PWD"], os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
|
self.assertEqual(
|
||||||
self.assertEqual(self.project_mapping["test_path"], os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
|
os.path.basename(self.project_mapping["PWD"]),
|
||||||
|
"tests"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
os.path.basename(self.project_mapping["test_path"]),
|
||||||
|
"tests"
|
||||||
|
)
|
||||||
@@ -4,8 +4,9 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
|
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import exceptions, logger
|
from httprunner import exceptions
|
||||||
|
|
||||||
schemas_root_dir = os.path.join(os.path.dirname(__file__), "schemas")
|
schemas_root_dir = os.path.join(os.path.dirname(__file__), "schemas")
|
||||||
common_schema_path = os.path.join(schemas_root_dir, "common.schema.json")
|
common_schema_path = os.path.join(schemas_root_dir, "common.schema.json")
|
||||||
@@ -50,7 +51,7 @@ class JsonSchemaChecker(object):
|
|||||||
try:
|
try:
|
||||||
jsonschema.validate(content, scheme, resolver=resolver)
|
jsonschema.validate(content, scheme, resolver=resolver)
|
||||||
except jsonschema.exceptions.ValidationError as ex:
|
except jsonschema.exceptions.ValidationError as ex:
|
||||||
logger.log_error(str(ex))
|
logger.error(str(ex))
|
||||||
raise exceptions.FileFormatError
|
raise exceptions.FileFormatError
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import os
|
|||||||
import types
|
import types
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import builtin
|
from httprunner import builtin
|
||||||
from httprunner import exceptions, logger, utils
|
from httprunner import exceptions, utils
|
||||||
from httprunner.loader.locate import get_project_working_directory
|
from httprunner.loader.locate import get_project_working_directory
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -25,7 +26,7 @@ def _load_yaml_file(yaml_file):
|
|||||||
try:
|
try:
|
||||||
yaml_content = yaml.load(stream)
|
yaml_content = yaml.load(stream)
|
||||||
except yaml.YAMLError as ex:
|
except yaml.YAMLError as ex:
|
||||||
logger.log_error(str(ex))
|
logger.error(str(ex))
|
||||||
raise exceptions.FileFormatError
|
raise exceptions.FileFormatError
|
||||||
|
|
||||||
return yaml_content
|
return yaml_content
|
||||||
@@ -37,9 +38,9 @@ def _load_json_file(json_file):
|
|||||||
with io.open(json_file, encoding='utf-8') as data_file:
|
with io.open(json_file, encoding='utf-8') as data_file:
|
||||||
try:
|
try:
|
||||||
json_content = json.load(data_file)
|
json_content = json.load(data_file)
|
||||||
except exceptions.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
err_msg = u"JSONDecodeError: JSON file format error: {}".format(json_file)
|
err_msg = f"JSONDecodeError: JSON file format error: {json_file}"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.FileFormatError(err_msg)
|
raise exceptions.FileFormatError(err_msg)
|
||||||
|
|
||||||
return json_content
|
return json_content
|
||||||
@@ -90,7 +91,7 @@ def load_csv_file(csv_file):
|
|||||||
|
|
||||||
def load_file(file_path):
|
def load_file(file_path):
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
raise exceptions.FileNotFound("{} does not exist.".format(file_path))
|
raise exceptions.FileNotFound(f"{file_path} does not exist.")
|
||||||
|
|
||||||
file_suffix = os.path.splitext(file_path)[1].lower()
|
file_suffix = os.path.splitext(file_path)[1].lower()
|
||||||
if file_suffix == '.json':
|
if file_suffix == '.json':
|
||||||
@@ -101,8 +102,7 @@ def load_file(file_path):
|
|||||||
return load_csv_file(file_path)
|
return load_csv_file(file_path)
|
||||||
else:
|
else:
|
||||||
# '' or other suffix
|
# '' or other suffix
|
||||||
err_msg = u"Unsupported file format: {}".format(file_path)
|
logger.warning(f"Unsupported file format: {file_path}")
|
||||||
logger.log_warning(err_msg)
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ def load_dot_env_file(dot_env_path):
|
|||||||
if not os.path.isfile(dot_env_path):
|
if not os.path.isfile(dot_env_path):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
logger.log_info("Loading environment variables from {}".format(dot_env_path))
|
logger.info(f"Loading environment variables from {dot_env_path}")
|
||||||
env_variables_mapping = {}
|
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:
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from httprunner import exceptions, logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from httprunner import exceptions
|
||||||
|
|
||||||
project_working_directory = None
|
project_working_directory = None
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ def locate_file(start_path, file_name):
|
|||||||
elif os.path.isdir(start_path):
|
elif os.path.isdir(start_path):
|
||||||
start_dir_path = start_path
|
start_dir_path = start_path
|
||||||
else:
|
else:
|
||||||
raise exceptions.FileNotFound("invalid path: {}".format(start_path))
|
raise exceptions.FileNotFound(f"invalid path: {start_path}")
|
||||||
|
|
||||||
file_path = os.path.join(start_dir_path, file_name)
|
file_path = os.path.join(start_dir_path, file_name)
|
||||||
if os.path.isfile(file_path):
|
if os.path.isfile(file_path):
|
||||||
@@ -34,14 +36,14 @@ def locate_file(start_path, file_name):
|
|||||||
|
|
||||||
# current working directory
|
# current working directory
|
||||||
if os.path.abspath(start_dir_path) == os.getcwd():
|
if os.path.abspath(start_dir_path) == os.getcwd():
|
||||||
raise exceptions.FileNotFound("{} not found in {}".format(file_name, start_path))
|
raise exceptions.FileNotFound(f"{file_name} not found in {start_path}")
|
||||||
|
|
||||||
# system root dir
|
# system root dir
|
||||||
# Windows, e.g. 'E:\\'
|
# Windows, e.g. 'E:\\'
|
||||||
# Linux/Darwin, '/'
|
# Linux/Darwin, '/'
|
||||||
parent_dir = os.path.dirname(start_dir_path)
|
parent_dir = os.path.dirname(start_dir_path)
|
||||||
if parent_dir == start_dir_path:
|
if parent_dir == start_dir_path:
|
||||||
raise exceptions.FileNotFound("{} not found in {}".format(file_name, start_path))
|
raise exceptions.FileNotFound(f"{file_name} not found in {start_path}")
|
||||||
|
|
||||||
# locate recursive upward
|
# locate recursive upward
|
||||||
return locate_file(parent_dir, file_name)
|
return locate_file(parent_dir, file_name)
|
||||||
@@ -85,8 +87,8 @@ def init_project_working_directory(test_path):
|
|||||||
|
|
||||||
def prepare_path(path):
|
def prepare_path(path):
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
err_msg = "path not exist: {}".format(path)
|
err_msg = f"path not exist: {path}"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.FileNotFound(err_msg)
|
raise exceptions.FileNotFound(err_msg)
|
||||||
|
|
||||||
if not os.path.isabs(path):
|
if not os.path.isabs(path):
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from colorama import Fore, init
|
|
||||||
from colorlog import ColoredFormatter
|
|
||||||
|
|
||||||
init(autoreset=True)
|
|
||||||
|
|
||||||
LOG_LEVEL = "INFO"
|
|
||||||
LOG_FILE_PATH = ""
|
|
||||||
|
|
||||||
log_colors_config = {
|
|
||||||
'DEBUG': 'cyan',
|
|
||||||
'INFO': 'green',
|
|
||||||
'WARNING': 'yellow',
|
|
||||||
'ERROR': 'red',
|
|
||||||
'CRITICAL': 'red',
|
|
||||||
}
|
|
||||||
loggers = {}
|
|
||||||
|
|
||||||
|
|
||||||
def setup_logger(log_level, log_file=None):
|
|
||||||
global LOG_LEVEL
|
|
||||||
LOG_LEVEL = log_level
|
|
||||||
|
|
||||||
if log_file:
|
|
||||||
global LOG_FILE_PATH
|
|
||||||
LOG_FILE_PATH = log_file
|
|
||||||
|
|
||||||
|
|
||||||
def get_logger(name=None):
|
|
||||||
"""setup logger with ColoredFormatter."""
|
|
||||||
name = name or "httprunner"
|
|
||||||
logger_key = "".join([name, LOG_LEVEL, LOG_FILE_PATH])
|
|
||||||
if logger_key in loggers:
|
|
||||||
return loggers[logger_key]
|
|
||||||
|
|
||||||
_logger = logging.getLogger(name)
|
|
||||||
|
|
||||||
log_level = LOG_LEVEL
|
|
||||||
level = getattr(logging, log_level.upper(), None)
|
|
||||||
if not level:
|
|
||||||
color_print("Invalid log level: %s" % log_level, "RED")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# hide traceback when log level is INFO/WARNING/ERROR/CRITICAL
|
|
||||||
if level >= logging.INFO:
|
|
||||||
sys.tracebacklimit = 0
|
|
||||||
|
|
||||||
_logger.setLevel(level)
|
|
||||||
if LOG_FILE_PATH:
|
|
||||||
log_dir = os.path.dirname(LOG_FILE_PATH)
|
|
||||||
if not os.path.isdir(log_dir):
|
|
||||||
os.makedirs(log_dir)
|
|
||||||
handler = logging.FileHandler(LOG_FILE_PATH, encoding="utf-8")
|
|
||||||
else:
|
|
||||||
handler = logging.StreamHandler(sys.stdout)
|
|
||||||
|
|
||||||
formatter = ColoredFormatter(
|
|
||||||
u"%(log_color)s%(bg_white)s%(levelname)-8s%(reset)s %(message)s",
|
|
||||||
datefmt=None,
|
|
||||||
reset=True,
|
|
||||||
log_colors=log_colors_config
|
|
||||||
)
|
|
||||||
handler.setFormatter(formatter)
|
|
||||||
_logger.addHandler(handler)
|
|
||||||
|
|
||||||
loggers[logger_key] = _logger
|
|
||||||
return _logger
|
|
||||||
|
|
||||||
|
|
||||||
def coloring(text, color="WHITE"):
|
|
||||||
fore_color = getattr(Fore, color.upper())
|
|
||||||
return fore_color + text
|
|
||||||
|
|
||||||
|
|
||||||
def color_print(msg, color="WHITE"):
|
|
||||||
fore_color = getattr(Fore, color.upper())
|
|
||||||
print(fore_color + msg)
|
|
||||||
|
|
||||||
|
|
||||||
def log_with_color(level):
|
|
||||||
""" log with color by different level
|
|
||||||
"""
|
|
||||||
def wrapper(text):
|
|
||||||
color = log_colors_config[level.upper()]
|
|
||||||
_logger = get_logger()
|
|
||||||
getattr(_logger, level.lower())(coloring(text, color))
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
log_debug = log_with_color("debug")
|
|
||||||
log_info = log_with_color("info")
|
|
||||||
log_warning = log_with_color("warning")
|
|
||||||
log_error = log_with_color("error")
|
|
||||||
log_critical = log_with_color("critical")
|
|
||||||
+16
-16
@@ -6,9 +6,9 @@ import collections
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import exceptions, utils, loader
|
from httprunner import exceptions, utils, loader
|
||||||
from httprunner import logger
|
|
||||||
from httprunner.compat import basestring, numeric_types, str
|
|
||||||
|
|
||||||
# use $$ to escape $ notation
|
# use $$ to escape $ notation
|
||||||
dolloar_regex_compile = re.compile(r"\$\$")
|
dolloar_regex_compile = re.compile(r"\$\$")
|
||||||
@@ -45,7 +45,7 @@ def parse_string_value(str_value):
|
|||||||
def is_var_or_func_exist(content):
|
def is_var_or_func_exist(content):
|
||||||
""" check if variable or function exist
|
""" check if variable or function exist
|
||||||
"""
|
"""
|
||||||
if not isinstance(content, basestring):
|
if not isinstance(content, str):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -286,7 +286,7 @@ def uniform_validator(validator):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(validator, dict):
|
if not isinstance(validator, dict):
|
||||||
raise exceptions.ParamsError("invalid validator: {}".format(validator))
|
raise exceptions.ParamsError(f"invalid validator: {validator}")
|
||||||
|
|
||||||
if "check" in validator and "expect" in validator:
|
if "check" in validator and "expect" in validator:
|
||||||
# format1
|
# format1
|
||||||
@@ -300,12 +300,12 @@ def uniform_validator(validator):
|
|||||||
compare_values = validator[comparator]
|
compare_values = validator[comparator]
|
||||||
|
|
||||||
if not isinstance(compare_values, list) or len(compare_values) != 2:
|
if not isinstance(compare_values, list) or len(compare_values) != 2:
|
||||||
raise exceptions.ParamsError("invalid validator: {}".format(validator))
|
raise exceptions.ParamsError(f"invalid validator: {validator}")
|
||||||
|
|
||||||
check_item, expect_value = compare_values
|
check_item, expect_value = compare_values
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise exceptions.ParamsError("invalid validator: {}".format(validator))
|
raise exceptions.ParamsError(f"invalid validator: {validator}")
|
||||||
|
|
||||||
# uniform comparator, e.g. lt => less_than, eq => equals
|
# uniform comparator, e.g. lt => less_than, eq => equals
|
||||||
comparator = get_uniform_comparator(comparator)
|
comparator = get_uniform_comparator(comparator)
|
||||||
@@ -410,7 +410,7 @@ def get_mapping_variable(variable_name, variables_mapping):
|
|||||||
try:
|
try:
|
||||||
return variables_mapping[variable_name]
|
return variables_mapping[variable_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise exceptions.VariableNotFound("{} is not found.".format(variable_name))
|
raise exceptions.VariableNotFound(f"{variable_name} is not found.")
|
||||||
|
|
||||||
|
|
||||||
def get_mapping_function(function_name, functions_mapping):
|
def get_mapping_function(function_name, functions_mapping):
|
||||||
@@ -455,7 +455,7 @@ def get_mapping_function(function_name, functions_mapping):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise exceptions.FunctionNotFound("{} is not found.".format(function_name))
|
raise exceptions.FunctionNotFound(f"{function_name} is not found.")
|
||||||
|
|
||||||
|
|
||||||
def parse_function_params(params):
|
def parse_function_params(params):
|
||||||
@@ -578,12 +578,12 @@ class LazyFunction(object):
|
|||||||
if self._kwargs:
|
if self._kwargs:
|
||||||
args_string += ", "
|
args_string += ", "
|
||||||
str_kwargs = [
|
str_kwargs = [
|
||||||
"{}={}".format(key, str(value))
|
f"{key}={str(value)}"
|
||||||
for key, value in self._kwargs.items()
|
for key, value in self._kwargs.items()
|
||||||
]
|
]
|
||||||
args_string += ", ".join(str_kwargs)
|
args_string += ", ".join(str_kwargs)
|
||||||
|
|
||||||
return "LazyFunction({}({}))".format(self.func_name, args_string)
|
return f"LazyFunction({self.func_name}({args_string}))"
|
||||||
|
|
||||||
def __prepare_cache_key(self, args, kwargs):
|
def __prepare_cache_key(self, args, kwargs):
|
||||||
return self.func_name, repr(args), repr(kwargs)
|
return self.func_name, repr(args), repr(kwargs)
|
||||||
@@ -698,7 +698,7 @@ class LazyString(object):
|
|||||||
self._string += escape_braces(remain_string)
|
self._string += escape_braces(remain_string)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "LazyString({})".format(self.raw_string)
|
return f"LazyString({self.raw_string})"
|
||||||
|
|
||||||
def to_value(self, variables_mapping=None):
|
def to_value(self, variables_mapping=None):
|
||||||
""" parse lazy data with evaluated variables mapping.
|
""" parse lazy data with evaluated variables mapping.
|
||||||
@@ -734,7 +734,7 @@ def prepare_lazy_data(content, functions_mapping=None, check_variables_set=None,
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# TODO: refactor type check
|
# TODO: refactor type check
|
||||||
if content is None or isinstance(content, (numeric_types, bool, type)):
|
if content is None or isinstance(content, (int, float, bool, type)):
|
||||||
return content
|
return content
|
||||||
|
|
||||||
elif isinstance(content, (list, set, tuple)):
|
elif isinstance(content, (list, set, tuple)):
|
||||||
@@ -767,7 +767,7 @@ def prepare_lazy_data(content, functions_mapping=None, check_variables_set=None,
|
|||||||
|
|
||||||
return parsed_content
|
return parsed_content
|
||||||
|
|
||||||
elif isinstance(content, basestring):
|
elif isinstance(content, str):
|
||||||
# content is in string format here
|
# content is in string format here
|
||||||
if not is_var_or_func_exist(content):
|
if not is_var_or_func_exist(content):
|
||||||
# content is neither variable nor function
|
# content is neither variable nor function
|
||||||
@@ -787,7 +787,7 @@ def parse_lazy_data(content, variables_mapping=None):
|
|||||||
Notice: variables_mapping should not contain any variable or function.
|
Notice: variables_mapping should not contain any variable or function.
|
||||||
"""
|
"""
|
||||||
# TODO: refactor type check
|
# TODO: refactor type check
|
||||||
if content is None or isinstance(content, (numeric_types, bool, type)):
|
if content is None or isinstance(content, (int, float, bool, type)):
|
||||||
return content
|
return content
|
||||||
|
|
||||||
elif isinstance(content, LazyString):
|
elif isinstance(content, LazyString):
|
||||||
@@ -1056,7 +1056,7 @@ def __prepare_config(config, project_mapping, session_variables_set=None):
|
|||||||
override_variables = utils.deepcopy_dict(project_mapping.get("variables", {}))
|
override_variables = utils.deepcopy_dict(project_mapping.get("variables", {}))
|
||||||
functions = project_mapping.get("functions", {})
|
functions = project_mapping.get("functions", {})
|
||||||
|
|
||||||
if isinstance(raw_config_variables, basestring) and function_regex_compile.match(
|
if isinstance(raw_config_variables, str) and function_regex_compile.match(
|
||||||
raw_config_variables):
|
raw_config_variables):
|
||||||
# config variables are generated by calling function
|
# config variables are generated by calling function
|
||||||
# e.g.
|
# e.g.
|
||||||
@@ -1247,7 +1247,7 @@ def _parse_testcase(testcase, project_mapping, session_variables_set=None):
|
|||||||
testcase_type = testcase["type"]
|
testcase_type = testcase["type"]
|
||||||
testcase_path = testcase.get("path")
|
testcase_path = testcase.get("path")
|
||||||
|
|
||||||
logger.log_error("failed to parse testcase: {}, error: {}".format(testcase_path, ex))
|
logger.error(f"failed to parse testcase: {testcase_path}, error: {ex}")
|
||||||
|
|
||||||
global parse_failed_testfiles
|
global parse_failed_testfiles
|
||||||
if testcase_type not in parse_failed_testfiles:
|
if testcase_type not in parse_failed_testfiles:
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import os
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import logger
|
|
||||||
from httprunner.exceptions import SummaryEmpty
|
from httprunner.exceptions import SummaryEmpty
|
||||||
|
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ def gen_html_report(summary, report_template=None, report_dir=None, report_file=
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not summary["time"] or summary["stat"]["testcases"]["total"] == 0:
|
if not summary["time"] or summary["stat"]["testcases"]["total"] == 0:
|
||||||
logger.log_error("test result summary is empty ! {}".format(summary))
|
logger.error(f"test result summary is empty ! {summary}")
|
||||||
raise SummaryEmpty
|
raise SummaryEmpty
|
||||||
|
|
||||||
if not report_template:
|
if not report_template:
|
||||||
@@ -27,11 +27,11 @@ def gen_html_report(summary, report_template=None, report_dir=None, report_file=
|
|||||||
os.path.abspath(os.path.dirname(__file__)),
|
os.path.abspath(os.path.dirname(__file__)),
|
||||||
"template.html"
|
"template.html"
|
||||||
)
|
)
|
||||||
logger.log_debug("No html report template specified, use default.")
|
logger.debug("No html report template specified, use default.")
|
||||||
else:
|
else:
|
||||||
logger.log_info("render with html report template: {}".format(report_template))
|
logger.info(f"render with html report template: {report_template}")
|
||||||
|
|
||||||
logger.log_info("Start to render Html report ...")
|
logger.info("Start to render Html report ...")
|
||||||
|
|
||||||
start_at_timestamp = summary["time"]["start_at"]
|
start_at_timestamp = summary["time"]["start_at"]
|
||||||
utc_time_iso_8601_str = datetime.utcfromtimestamp(start_at_timestamp).isoformat()
|
utc_time_iso_8601_str = datetime.utcfromtimestamp(start_at_timestamp).isoformat()
|
||||||
@@ -58,7 +58,7 @@ def gen_html_report(summary, report_template=None, report_dir=None, report_file=
|
|||||||
).render(summary)
|
).render(summary)
|
||||||
fp_w.write(rendered_content)
|
fp_w.write(rendered_content)
|
||||||
|
|
||||||
logger.log_info("Generated Html report: {}".format(report_path))
|
logger.info(f"Generated Html report: {report_path}")
|
||||||
|
|
||||||
return report_path
|
return report_path
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from httprunner import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
class HtmlTestResult(unittest.TextTestResult):
|
class HtmlTestResult(unittest.TextTestResult):
|
||||||
@@ -27,7 +27,7 @@ class HtmlTestResult(unittest.TextTestResult):
|
|||||||
def startTest(self, test):
|
def startTest(self, test):
|
||||||
""" add start test time """
|
""" add start test time """
|
||||||
super(HtmlTestResult, self).startTest(test)
|
super(HtmlTestResult, self).startTest(test)
|
||||||
logger.color_print(test.shortDescription(), "yellow")
|
logger.info(test.shortDescription())
|
||||||
|
|
||||||
def addSuccess(self, test):
|
def addSuccess(self, test):
|
||||||
super(HtmlTestResult, self).addSuccess(test)
|
super(HtmlTestResult, self).addSuccess(test)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
import json
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from collections import Iterable
|
from collections import Iterable
|
||||||
|
|
||||||
from jinja2 import escape
|
from jinja2 import escape
|
||||||
from requests.cookies import RequestsCookieJar
|
from requests.cookies import RequestsCookieJar
|
||||||
|
|
||||||
from httprunner.compat import basestring, bytes, json, numeric_types, JSONDecodeError
|
|
||||||
|
|
||||||
|
|
||||||
def dumps_json(value):
|
def dumps_json(value):
|
||||||
""" dumps json value to indented string
|
""" dumps json value to indented string
|
||||||
@@ -67,13 +66,13 @@ def __stringify_request(request_data):
|
|||||||
# request body is in json format
|
# request body is in json format
|
||||||
value = json.loads(value)
|
value = json.loads(value)
|
||||||
value = dumps_json(value)
|
value = dumps_json(value)
|
||||||
except JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
value = escape(value)
|
value = escape(value)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif not isinstance(value, (basestring, numeric_types, Iterable)):
|
elif not isinstance(value, (str, bytes, int, float, Iterable)):
|
||||||
# class instance, e.g. MultipartEncoder()
|
# class instance, e.g. MultipartEncoder()
|
||||||
value = repr(value)
|
value = repr(value)
|
||||||
|
|
||||||
@@ -132,7 +131,7 @@ def __stringify_response(response_data):
|
|||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif not isinstance(value, (basestring, numeric_types, Iterable)):
|
elif not isinstance(value, (str, bytes, int, float, Iterable)):
|
||||||
# class instance, e.g. MultipartEncoder()
|
# class instance, e.g. MultipartEncoder()
|
||||||
value = repr(value)
|
value = repr(value)
|
||||||
|
|
||||||
@@ -205,7 +204,7 @@ def stringify_summary(summary):
|
|||||||
for index, suite_summary in enumerate(summary["details"]):
|
for index, suite_summary in enumerate(summary["details"]):
|
||||||
|
|
||||||
if not suite_summary.get("name"):
|
if not suite_summary.get("name"):
|
||||||
suite_summary["name"] = "testcase {}".format(index)
|
suite_summary["name"] = f"testcase {index}"
|
||||||
|
|
||||||
for record in suite_summary.get("records"):
|
for record in suite_summary.get("records"):
|
||||||
meta_datas = record['meta_datas']
|
meta_datas = record['meta_datas']
|
||||||
|
|||||||
+69
-50
@@ -1,10 +1,11 @@
|
|||||||
|
import json
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import jsonpath
|
import jsonpath
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import exceptions, logger, utils
|
from httprunner import exceptions, utils
|
||||||
from httprunner.compat import basestring, is_py2
|
|
||||||
|
|
||||||
text_extractor_regexp_compile = re.compile(r".*\(.*\).*")
|
text_extractor_regexp_compile = re.compile(r".*\(.*\).*")
|
||||||
|
|
||||||
@@ -32,13 +33,24 @@ class ResponseObject(object):
|
|||||||
self.__dict__[key] = value
|
self.__dict__[key] = value
|
||||||
return value
|
return value
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
err_msg = "ResponseObject does not have attribute: {}".format(key)
|
err_msg = f"ResponseObject does not have attribute: {key}"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ParamsError(err_msg)
|
raise exceptions.ParamsError(err_msg)
|
||||||
|
|
||||||
def _extract_field_with_jsonpath(self, field):
|
def _extract_field_with_jsonpath(self, field: str) -> list:
|
||||||
"""
|
""" extract field from response content with jsonpath expression.
|
||||||
JSONPath Docs: https://goessner.net/articles/JsonPath/
|
JSONPath Docs: https://goessner.net/articles/JsonPath/
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field: jsonpath expression, e.g. $.code, $..items.*.id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list that extracted from json response example. 1) [200] 2) [1, 2]
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
exceptions.ExtractFailure: If no content matched with jsonpath expression.
|
||||||
|
|
||||||
|
Examples:
|
||||||
For example, response body like below:
|
For example, response body like below:
|
||||||
{
|
{
|
||||||
"code": 200,
|
"code": 200,
|
||||||
@@ -56,14 +68,24 @@ class ResponseObject(object):
|
|||||||
"message": "success"
|
"message": "success"
|
||||||
}
|
}
|
||||||
|
|
||||||
:param field: Jsonpath expression, e.g. 1)$.code 2) $..items.*.id
|
>>> _extract_field_with_regex("$.code")
|
||||||
:return: A list that extracted from json repsonse example. 1) [200] 2) [1, 2]
|
[200]
|
||||||
|
>>> _extract_field_with_regex("$..items.*.id")
|
||||||
|
[1, 2]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
result = jsonpath.jsonpath(self.parsed_body(), field)
|
try:
|
||||||
if result:
|
json_body = self.json
|
||||||
|
assert json_body
|
||||||
|
|
||||||
|
result = jsonpath.jsonpath(json_body, field)
|
||||||
|
assert result
|
||||||
return result
|
return result
|
||||||
else:
|
except (AssertionError, exceptions.JSONDecodeError):
|
||||||
raise exceptions.ExtractFailure("\tjsonpath {} get nothing\n".format(field))
|
err_msg = f"Failed to extract data with jsonpath! => {field}\n"
|
||||||
|
err_msg += f"response body: {self.text}\n"
|
||||||
|
logger.error(err_msg)
|
||||||
|
raise exceptions.ExtractFailure(err_msg)
|
||||||
|
|
||||||
def _extract_field_with_regex(self, field):
|
def _extract_field_with_regex(self, field):
|
||||||
""" extract field from response content with regex.
|
""" extract field from response content with regex.
|
||||||
@@ -87,9 +109,9 @@ class ResponseObject(object):
|
|||||||
"""
|
"""
|
||||||
matched = re.search(field, self.text)
|
matched = re.search(field, self.text)
|
||||||
if not matched:
|
if not matched:
|
||||||
err_msg = u"Failed to extract data with regex! => {}\n".format(field)
|
err_msg = f"Failed to extract data with regex! => {field}\n"
|
||||||
err_msg += u"response body: {}\n".format(self.text)
|
err_msg += f"response body: {self.text}\n"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ExtractFailure(err_msg)
|
raise exceptions.ExtractFailure(err_msg)
|
||||||
|
|
||||||
return matched.group(1)
|
return matched.group(1)
|
||||||
@@ -120,8 +142,8 @@ class ResponseObject(object):
|
|||||||
if top_query in ["status_code", "encoding", "ok", "reason", "url"]:
|
if top_query in ["status_code", "encoding", "ok", "reason", "url"]:
|
||||||
if sub_query:
|
if sub_query:
|
||||||
# status_code.XX
|
# status_code.XX
|
||||||
err_msg = u"Failed to extract: {}\n".format(field)
|
err_msg = f"Failed to extract: {field}\n"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ParamsError(err_msg)
|
raise exceptions.ParamsError(err_msg)
|
||||||
|
|
||||||
return getattr(self, top_query)
|
return getattr(self, top_query)
|
||||||
@@ -136,27 +158,27 @@ class ResponseObject(object):
|
|||||||
try:
|
try:
|
||||||
return cookies[sub_query]
|
return cookies[sub_query]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
err_msg = u"Failed to extract cookie! => {}\n".format(field)
|
err_msg = f"Failed to extract cookie! => {field}\n"
|
||||||
err_msg += u"response cookies: {}\n".format(cookies)
|
err_msg += f"response cookies: {cookies}\n"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ExtractFailure(err_msg)
|
raise exceptions.ExtractFailure(err_msg)
|
||||||
|
|
||||||
# elapsed
|
# elapsed
|
||||||
elif top_query == "elapsed":
|
elif top_query == "elapsed":
|
||||||
available_attributes = u"available attributes: days, seconds, microseconds, total_seconds"
|
available_attributes = u"available attributes: days, seconds, microseconds, total_seconds"
|
||||||
if not sub_query:
|
if not sub_query:
|
||||||
err_msg = u"elapsed is datetime.timedelta instance, attribute should also be specified!\n"
|
err_msg = "elapsed is datetime.timedelta instance, attribute should also be specified!\n"
|
||||||
err_msg += available_attributes
|
err_msg += available_attributes
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ParamsError(err_msg)
|
raise exceptions.ParamsError(err_msg)
|
||||||
elif sub_query in ["days", "seconds", "microseconds"]:
|
elif sub_query in ["days", "seconds", "microseconds"]:
|
||||||
return getattr(self.elapsed, sub_query)
|
return getattr(self.elapsed, sub_query)
|
||||||
elif sub_query == "total_seconds":
|
elif sub_query == "total_seconds":
|
||||||
return self.elapsed.total_seconds()
|
return self.elapsed.total_seconds()
|
||||||
else:
|
else:
|
||||||
err_msg = "{} is not valid datetime.timedelta attribute.\n".format(sub_query)
|
err_msg = f"{sub_query} is not valid datetime.timedelta attribute.\n"
|
||||||
err_msg += available_attributes
|
err_msg += available_attributes
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ParamsError(err_msg)
|
raise exceptions.ParamsError(err_msg)
|
||||||
|
|
||||||
# headers
|
# headers
|
||||||
@@ -169,16 +191,16 @@ class ResponseObject(object):
|
|||||||
try:
|
try:
|
||||||
return headers[sub_query]
|
return headers[sub_query]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
err_msg = u"Failed to extract header! => {}\n".format(field)
|
err_msg = f"Failed to extract header! => {field}\n"
|
||||||
err_msg += u"response headers: {}\n".format(headers)
|
err_msg += f"response headers: {headers}\n"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ExtractFailure(err_msg)
|
raise exceptions.ExtractFailure(err_msg)
|
||||||
|
|
||||||
# response body
|
# response body
|
||||||
elif top_query in ["body", "content", "text", "json"]:
|
elif top_query in ["body", "content", "text", "json"]:
|
||||||
try:
|
try:
|
||||||
body = self.json
|
body = self.json
|
||||||
except exceptions.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
body = self.text
|
body = self.text
|
||||||
|
|
||||||
if not sub_query:
|
if not sub_query:
|
||||||
@@ -193,9 +215,9 @@ class ResponseObject(object):
|
|||||||
return utils.query_json(body, sub_query)
|
return utils.query_json(body, sub_query)
|
||||||
else:
|
else:
|
||||||
# content = "<html>abcdefg</html>", content.xxx
|
# content = "<html>abcdefg</html>", content.xxx
|
||||||
err_msg = u"Failed to extract attribute from response body! => {}\n".format(field)
|
err_msg = f"Failed to extract attribute from response body! => {field}\n"
|
||||||
err_msg += u"response body: {}\n".format(body)
|
err_msg += f"response body: {body}\n"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ExtractFailure(err_msg)
|
raise exceptions.ExtractFailure(err_msg)
|
||||||
|
|
||||||
# new set response attributes in teardown_hooks
|
# new set response attributes in teardown_hooks
|
||||||
@@ -214,30 +236,30 @@ class ResponseObject(object):
|
|||||||
return utils.query_json(attributes, sub_query)
|
return utils.query_json(attributes, sub_query)
|
||||||
else:
|
else:
|
||||||
# content = "attributes.new_attribute_not_exist"
|
# content = "attributes.new_attribute_not_exist"
|
||||||
err_msg = u"Failed to extract cumstom set attribute from teardown hooks! => {}\n".format(field)
|
err_msg = f"Failed to extract cumstom set attribute from teardown hooks! => {field}\n"
|
||||||
err_msg += u"response set attributes: {}\n".format(attributes)
|
err_msg += f"response set attributes: {attributes}\n"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.TeardownHooksFailure(err_msg)
|
raise exceptions.TeardownHooksFailure(err_msg)
|
||||||
|
|
||||||
# others
|
# others
|
||||||
else:
|
else:
|
||||||
err_msg = u"Failed to extract attribute from response! => {}\n".format(field)
|
err_msg = f"Failed to extract attribute from response! => {field}\n"
|
||||||
err_msg += u"available response attributes: status_code, cookies, elapsed, headers, content, " \
|
err_msg += "available response attributes: status_code, cookies, elapsed, headers, content, " \
|
||||||
u"text, json, encoding, ok, reason, url.\n\n"
|
"text, json, encoding, ok, reason, url.\n\n"
|
||||||
err_msg += u"If you want to set attribute in teardown_hooks, take the following example as reference:\n"
|
err_msg += "If you want to set attribute in teardown_hooks, take the following example as reference:\n"
|
||||||
err_msg += u"response.new_attribute = 'new_attribute_value'\n"
|
err_msg += "response.new_attribute = 'new_attribute_value'\n"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ParamsError(err_msg)
|
raise exceptions.ParamsError(err_msg)
|
||||||
|
|
||||||
def extract_field(self, field):
|
def extract_field(self, field):
|
||||||
""" extract value from requests.Response.
|
""" extract value from requests.Response.
|
||||||
"""
|
"""
|
||||||
if not isinstance(field, basestring):
|
if not isinstance(field, str):
|
||||||
err_msg = u"Invalid extractor! => {}\n".format(field)
|
err_msg = f"Invalid extractor! => {field}\n"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ParamsError(err_msg)
|
raise exceptions.ParamsError(err_msg)
|
||||||
|
|
||||||
msg = "extract: {}".format(field)
|
msg = f"extract: {field}"
|
||||||
|
|
||||||
if field.startswith("$"):
|
if field.startswith("$"):
|
||||||
value = self._extract_field_with_jsonpath(field)
|
value = self._extract_field_with_jsonpath(field)
|
||||||
@@ -246,11 +268,8 @@ class ResponseObject(object):
|
|||||||
else:
|
else:
|
||||||
value = self._extract_field_with_delimiter(field)
|
value = self._extract_field_with_delimiter(field)
|
||||||
|
|
||||||
if is_py2 and isinstance(value, unicode):
|
msg += f"\t=> {value}"
|
||||||
value = value.encode("utf-8")
|
logger.debug(msg)
|
||||||
|
|
||||||
msg += "\t=> {}".format(value)
|
|
||||||
logger.log_debug(msg)
|
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -274,7 +293,7 @@ class ResponseObject(object):
|
|||||||
if not extractors:
|
if not extractors:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
logger.log_debug("start to extract from response object.")
|
logger.debug("start to extract from response object.")
|
||||||
extracted_variables_mapping = OrderedDict()
|
extracted_variables_mapping = OrderedDict()
|
||||||
extract_binds_order_dict = utils.ensure_mapping_format(extractors)
|
extract_binds_order_dict = utils.ensure_mapping_format(extractors)
|
||||||
|
|
||||||
|
|||||||
+23
-24
@@ -3,7 +3,9 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from unittest.case import SkipTest
|
from unittest.case import SkipTest
|
||||||
|
|
||||||
from httprunner import exceptions, logger, response, utils
|
from loguru import logger
|
||||||
|
|
||||||
|
from httprunner import exceptions, response, utils
|
||||||
from httprunner.client import HttpSession
|
from httprunner.client import HttpSession
|
||||||
from httprunner.context import SessionContext
|
from httprunner.context import SessionContext
|
||||||
from httprunner.validator import Validator
|
from httprunner.validator import Validator
|
||||||
@@ -116,12 +118,12 @@ class Runner(object):
|
|||||||
elif "skipIf" in test_dict:
|
elif "skipIf" in test_dict:
|
||||||
skip_if_condition = test_dict["skipIf"]
|
skip_if_condition = test_dict["skipIf"]
|
||||||
if self.session_context.eval_content(skip_if_condition):
|
if self.session_context.eval_content(skip_if_condition):
|
||||||
skip_reason = "{} evaluate to True".format(skip_if_condition)
|
skip_reason = f"{skip_if_condition} evaluate to True"
|
||||||
|
|
||||||
elif "skipUnless" in test_dict:
|
elif "skipUnless" in test_dict:
|
||||||
skip_unless_condition = test_dict["skipUnless"]
|
skip_unless_condition = test_dict["skipUnless"]
|
||||||
if not self.session_context.eval_content(skip_unless_condition):
|
if not self.session_context.eval_content(skip_unless_condition):
|
||||||
skip_reason = "{} evaluate to False".format(skip_unless_condition)
|
skip_reason = f"{skip_unless_condition} evaluate to False"
|
||||||
|
|
||||||
if skip_reason:
|
if skip_reason:
|
||||||
raise SkipTest(skip_reason)
|
raise SkipTest(skip_reason)
|
||||||
@@ -140,7 +142,7 @@ class Runner(object):
|
|||||||
hook_type (HookTypeEnum): setup/teardown
|
hook_type (HookTypeEnum): setup/teardown
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger.log_debug("call {} hook actions.".format(hook_type.name))
|
logger.debug(f"call {hook_type.name} hook actions.")
|
||||||
for action in actions:
|
for action in actions:
|
||||||
|
|
||||||
if isinstance(action, dict) and len(action) == 1:
|
if isinstance(action, dict) and len(action) == 1:
|
||||||
@@ -148,17 +150,14 @@ class Runner(object):
|
|||||||
# {"var": "${func()}"}
|
# {"var": "${func()}"}
|
||||||
var_name, hook_content = list(action.items())[0]
|
var_name, hook_content = list(action.items())[0]
|
||||||
hook_content_eval = self.session_context.eval_content(hook_content)
|
hook_content_eval = self.session_context.eval_content(hook_content)
|
||||||
logger.log_debug(
|
logger.debug(
|
||||||
"assignment with hook: {} = {} => {}".format(
|
f"assignment with hook: {var_name} = {hook_content} => {hook_content_eval}")
|
||||||
var_name, hook_content, hook_content_eval
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.session_context.update_test_variables(
|
self.session_context.update_test_variables(
|
||||||
var_name, hook_content_eval
|
var_name, hook_content_eval
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# format 2
|
# format 2
|
||||||
logger.log_debug("call hook function: {}".format(action))
|
logger.debug(f"call hook function: {action}")
|
||||||
# TODO: check hook function if valid
|
# TODO: check hook function if valid
|
||||||
self.session_context.eval_content(action)
|
self.session_context.eval_content(action)
|
||||||
|
|
||||||
@@ -230,9 +229,8 @@ class Runner(object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise exceptions.ParamsError("URL or METHOD missed!")
|
raise exceptions.ParamsError("URL or METHOD missed!")
|
||||||
|
|
||||||
logger.log_info("{method} {url}".format(method=method, url=parsed_url))
|
logger.info(f"{method} {parsed_url}")
|
||||||
logger.log_debug(
|
logger.debug(f"request kwargs(raw): {parsed_test_request}")
|
||||||
"request kwargs(raw): {kwargs}".format(kwargs=parsed_test_request))
|
|
||||||
|
|
||||||
# request
|
# request
|
||||||
resp = self.http_client_session.request(
|
resp = self.http_client_session.request(
|
||||||
@@ -248,21 +246,22 @@ class Runner(object):
|
|||||||
|
|
||||||
# log request
|
# log request
|
||||||
err_msg += "====== request details ======\n"
|
err_msg += "====== request details ======\n"
|
||||||
err_msg += "url: {}\n".format(parsed_url)
|
err_msg += f"url: {parsed_url}\n"
|
||||||
err_msg += "method: {}\n".format(method)
|
err_msg += f"method: {method}\n"
|
||||||
err_msg += "headers: {}\n".format(parsed_test_request.pop("headers", {}))
|
headers = parsed_test_request.pop("headers", {})
|
||||||
|
err_msg += f"headers: {headers}\n"
|
||||||
for k, v in parsed_test_request.items():
|
for k, v in parsed_test_request.items():
|
||||||
v = utils.omit_long_data(v)
|
v = utils.omit_long_data(v)
|
||||||
err_msg += "{}: {}\n".format(k, repr(v))
|
err_msg += f"{k}: {repr(v)}\n"
|
||||||
|
|
||||||
err_msg += "\n"
|
err_msg += "\n"
|
||||||
|
|
||||||
# log response
|
# log response
|
||||||
err_msg += "====== response details ======\n"
|
err_msg += "====== response details ======\n"
|
||||||
err_msg += "status_code: {}\n".format(resp_obj.status_code)
|
err_msg += f"status_code: {resp_obj.status_code}\n"
|
||||||
err_msg += "headers: {}\n".format(resp_obj.headers)
|
err_msg += f"headers: {resp_obj.headers}\n"
|
||||||
err_msg += "body: {}\n".format(repr(resp_obj.text))
|
err_msg += f"body: {repr(resp_obj.text)}\n"
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
|
|
||||||
# teardown hooks
|
# teardown hooks
|
||||||
teardown_hooks = test_dict.get("teardown_hooks", [])
|
teardown_hooks = test_dict.get("teardown_hooks", [])
|
||||||
@@ -395,9 +394,9 @@ class Runner(object):
|
|||||||
output = {}
|
output = {}
|
||||||
for variable in output_variables_list:
|
for variable in output_variables_list:
|
||||||
if variable not in variables_mapping:
|
if variable not in variables_mapping:
|
||||||
logger.log_warning(
|
logger.warning(
|
||||||
"variable '{}' can not be found in variables mapping, "
|
f"variable '{variable}' can not be found in variables mapping, "
|
||||||
"failed to export!".format(variable)
|
"failed to export!"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from .testcase import ProjectMeta, TestCase, TestCases
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from httprunner.schema import common
|
||||||
|
|
||||||
|
|
||||||
|
class Api(BaseModel):
|
||||||
|
name: common.Name
|
||||||
|
request: common.Request
|
||||||
|
variables: common.Variables
|
||||||
|
base_url: common.BaseUrl
|
||||||
|
setup_hooks: common.Hook
|
||||||
|
teardown_hooks: common.Hook
|
||||||
|
extract: common.Extract
|
||||||
|
validate: common.Validate
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from typing import Dict, List, Any, Tuple
|
||||||
|
|
||||||
|
from pydantic import BaseModel, HttpUrl, Field
|
||||||
|
|
||||||
|
Name = str
|
||||||
|
Url = HttpUrl
|
||||||
|
BaseUrl = str
|
||||||
|
Variables = Dict[str, Any]
|
||||||
|
Headers = Dict[str, str]
|
||||||
|
Verify = bool
|
||||||
|
Hook = List[str]
|
||||||
|
Export = List[str]
|
||||||
|
Extract = Dict[str, str]
|
||||||
|
Validate = List[Dict]
|
||||||
|
Env = Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
class MethodEnum(str, Enum):
|
||||||
|
GET = 'GET'
|
||||||
|
POST = 'POST'
|
||||||
|
PUT = "PUT"
|
||||||
|
DELETE = "DELETE"
|
||||||
|
HEAD = "HEAD"
|
||||||
|
OPTIONS = "OPTIONS"
|
||||||
|
PATCH = "PATCH"
|
||||||
|
CONNECT = "CONNECT"
|
||||||
|
TRACE = "TRACE"
|
||||||
|
|
||||||
|
|
||||||
|
class TestsConfig(BaseModel):
|
||||||
|
name: Name
|
||||||
|
verify: Verify = False
|
||||||
|
base_url: BaseUrl = ""
|
||||||
|
variables: Variables = {}
|
||||||
|
setup_hooks: Hook = []
|
||||||
|
teardown_hooks: Hook = []
|
||||||
|
export: Export = []
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "used in testcase/testsuite to configure common fields",
|
||||||
|
"verify": False,
|
||||||
|
"base_url": "https://httpbin.org"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Request(BaseModel):
|
||||||
|
method: MethodEnum = MethodEnum.GET
|
||||||
|
url: Url
|
||||||
|
params: Dict[str, str] = {}
|
||||||
|
headers: Headers = {}
|
||||||
|
req_json: Dict = Field({}, alias="json")
|
||||||
|
cookies: Dict[str, str] = {}
|
||||||
|
timeout: int = 120
|
||||||
|
allow_redirects: bool = True
|
||||||
|
verify: Verify = False
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
from typing import Dict, List, Text
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from httprunner.schema import common
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectMeta(BaseModel):
|
||||||
|
debugtalk_py: Text = ""
|
||||||
|
variables: common.Variables = {}
|
||||||
|
env: common.Env = {}
|
||||||
|
|
||||||
|
|
||||||
|
class TestStep(BaseModel):
|
||||||
|
name: common.Name
|
||||||
|
request: common.Request
|
||||||
|
extract: Dict[str, str] = {}
|
||||||
|
validation: common.Validate = Field([], alias="validate")
|
||||||
|
|
||||||
|
|
||||||
|
class TestCase(BaseModel):
|
||||||
|
config: common.TestsConfig
|
||||||
|
teststeps: List[TestStep]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"name": "testcase name"
|
||||||
|
},
|
||||||
|
"teststeps": [
|
||||||
|
{
|
||||||
|
"name": "api 1",
|
||||||
|
"api": "/path/to/api1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "api 2",
|
||||||
|
"api": "/path/to/api2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"name": "demo testcase",
|
||||||
|
"variables": {
|
||||||
|
"device_sn": "ABC",
|
||||||
|
"username": "${ENV(USERNAME)}",
|
||||||
|
"password": "${ENV(PASSWORD)}"
|
||||||
|
},
|
||||||
|
"base_url": "http://127.0.0.1:5000"
|
||||||
|
},
|
||||||
|
"teststeps": [
|
||||||
|
{
|
||||||
|
"name": "demo step 1",
|
||||||
|
"api": "path/to/api1.yml",
|
||||||
|
"variables": {
|
||||||
|
"user_agent": "iOS/10.3",
|
||||||
|
"device_sn": "$device_sn"
|
||||||
|
},
|
||||||
|
"extract": [
|
||||||
|
{
|
||||||
|
"token": "content.token"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"validate": [
|
||||||
|
{
|
||||||
|
"eq": ["status_code", 200]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "demo step 2",
|
||||||
|
"api": "path/to/api2.yml",
|
||||||
|
"variables": {
|
||||||
|
"token": "$token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TestCases = List[TestCase]
|
||||||
+39
-72
@@ -8,12 +8,11 @@ import json
|
|||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from httprunner import exceptions, logger, __version__
|
from httprunner import exceptions, __version__
|
||||||
from httprunner.compat import basestring, bytes, is_py2
|
|
||||||
from httprunner.exceptions import ParamsError
|
from httprunner.exceptions import ParamsError
|
||||||
|
|
||||||
absolute_http_url_regexp = re.compile(r"^https?://", re.I)
|
absolute_http_url_regexp = re.compile(r"^https?://", re.I)
|
||||||
@@ -22,7 +21,7 @@ absolute_http_url_regexp = re.compile(r"^https?://", re.I)
|
|||||||
def init_sentry_sdk():
|
def init_sentry_sdk():
|
||||||
sentry_sdk.init(
|
sentry_sdk.init(
|
||||||
dsn="https://cc6dd86fbe9f4e7fbd95248cfcff114d@sentry.io/1862849",
|
dsn="https://cc6dd86fbe9f4e7fbd95248cfcff114d@sentry.io/1862849",
|
||||||
release="httprunner@{}".format(__version__)
|
release=f"httprunner@{__version__}"
|
||||||
)
|
)
|
||||||
|
|
||||||
with sentry_sdk.configure_scope() as scope:
|
with sentry_sdk.configure_scope() as scope:
|
||||||
@@ -34,7 +33,7 @@ def set_os_environ(variables_mapping):
|
|||||||
"""
|
"""
|
||||||
for variable in variables_mapping:
|
for variable in variables_mapping:
|
||||||
os.environ[variable] = variables_mapping[variable]
|
os.environ[variable] = variables_mapping[variable]
|
||||||
logger.log_debug("Set OS environment variable: {}".format(variable))
|
logger.debug(f"Set OS environment variable: {variable}")
|
||||||
|
|
||||||
|
|
||||||
def unset_os_environ(variables_mapping):
|
def unset_os_environ(variables_mapping):
|
||||||
@@ -42,7 +41,7 @@ def unset_os_environ(variables_mapping):
|
|||||||
"""
|
"""
|
||||||
for variable in variables_mapping:
|
for variable in variables_mapping:
|
||||||
os.environ.pop(variable)
|
os.environ.pop(variable)
|
||||||
logger.log_debug("Unset OS environment variable: {}".format(variable))
|
logger.debug(f"Unset OS environment variable: {variable}")
|
||||||
|
|
||||||
|
|
||||||
def get_os_environ(variable_name):
|
def get_os_environ(variable_name):
|
||||||
@@ -109,24 +108,24 @@ def query_json(json_content, query, delimiter='.'):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
raise_flag = False
|
raise_flag = False
|
||||||
response_body = u"response body: {}\n".format(json_content)
|
response_body = f"response body: {json_content}\n"
|
||||||
try:
|
try:
|
||||||
for key in query.split(delimiter):
|
for key in query.split(delimiter):
|
||||||
if isinstance(json_content, (list, basestring)):
|
if isinstance(json_content, (list, str, bytes)):
|
||||||
json_content = json_content[int(key)]
|
json_content = json_content[int(key)]
|
||||||
elif isinstance(json_content, dict):
|
elif isinstance(json_content, dict):
|
||||||
json_content = json_content[key]
|
json_content = json_content[key]
|
||||||
else:
|
else:
|
||||||
logger.log_error(
|
logger.error(
|
||||||
"invalid type value: {}({})".format(json_content, type(json_content)))
|
f"invalid type value: {json_content}({type(json_content)})")
|
||||||
raise_flag = True
|
raise_flag = True
|
||||||
except (KeyError, ValueError, IndexError):
|
except (KeyError, ValueError, IndexError):
|
||||||
raise_flag = True
|
raise_flag = True
|
||||||
|
|
||||||
if raise_flag:
|
if raise_flag:
|
||||||
err_msg = u"Failed to extract! => {}\n".format(query)
|
err_msg = f"Failed to extract! => {query}\n"
|
||||||
err_msg += response_body
|
err_msg += response_body
|
||||||
logger.log_error(err_msg)
|
logger.error(err_msg)
|
||||||
raise exceptions.ExtractFailure(err_msg)
|
raise exceptions.ExtractFailure(err_msg)
|
||||||
|
|
||||||
return json_content
|
return json_content
|
||||||
@@ -355,38 +354,32 @@ def print_info(info_mapping):
|
|||||||
elif value is None:
|
elif value is None:
|
||||||
value = "None"
|
value = "None"
|
||||||
|
|
||||||
if is_py2:
|
|
||||||
if isinstance(key, unicode):
|
|
||||||
key = key.encode("utf-8")
|
|
||||||
if isinstance(value, unicode):
|
|
||||||
value = value.encode("utf-8")
|
|
||||||
|
|
||||||
content += content_format.format(key, value)
|
content += content_format.format(key, value)
|
||||||
|
|
||||||
content += "-" * 48 + "\n"
|
content += "-" * 48 + "\n"
|
||||||
logger.log_info(content)
|
logger.info(content)
|
||||||
|
|
||||||
|
|
||||||
def create_scaffold(project_name):
|
def create_scaffold(project_name):
|
||||||
""" create scaffold with specified project name.
|
""" create scaffold with specified project name.
|
||||||
"""
|
"""
|
||||||
if os.path.isdir(project_name):
|
if os.path.isdir(project_name):
|
||||||
logger.log_warning(u"Folder {} exists, please specify a new folder name.".format(project_name))
|
logger.warning(f"Folder {project_name} exists, please specify a new folder name.")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.color_print("Start to create new project: {}".format(project_name), "GREEN")
|
logger.info(f"Start to create new project: {project_name}")
|
||||||
logger.color_print("CWD: {}\n".format(os.getcwd()), "BLUE")
|
logger.info(f"CWD: {os.getcwd()}")
|
||||||
|
|
||||||
def create_folder(path):
|
def create_folder(path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
msg = "created folder: {}".format(path)
|
msg = f"created folder: {path}"
|
||||||
logger.color_print(msg, "BLUE")
|
logger.info(msg)
|
||||||
|
|
||||||
def create_file(path, file_content=""):
|
def create_file(path, file_content=""):
|
||||||
with open(path, 'w') as f:
|
with open(path, 'w') as f:
|
||||||
f.write(file_content)
|
f.write(file_content)
|
||||||
msg = "created file: {}".format(path)
|
msg = f"created file: {path}"
|
||||||
logger.color_print(msg, "BLUE")
|
logger.info(msg)
|
||||||
|
|
||||||
demo_api_content = """
|
demo_api_content = """
|
||||||
name: demo api
|
name: demo api
|
||||||
@@ -526,14 +519,14 @@ def prettify_json_file(file_list):
|
|||||||
"""
|
"""
|
||||||
for json_file in set(file_list):
|
for json_file in set(file_list):
|
||||||
if not json_file.endswith(".json"):
|
if not json_file.endswith(".json"):
|
||||||
logger.log_warning("Only JSON file format can be prettified, skip: {}".format(json_file))
|
logger.warning(f"Only JSON file format can be prettified, skip: {json_file}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.color_print("Start to prettify JSON file: {}".format(json_file), "GREEN")
|
logger.info(f"Start to prettify JSON file: {json_file}")
|
||||||
|
|
||||||
dir_path = os.path.dirname(json_file)
|
dir_path = os.path.dirname(json_file)
|
||||||
file_name, file_suffix = os.path.splitext(os.path.basename(json_file))
|
file_name, file_suffix = os.path.splitext(os.path.basename(json_file))
|
||||||
outfile = os.path.join(dir_path, "{}.pretty.json".format(file_name))
|
outfile = os.path.join(dir_path, f"{file_name}.pretty.json")
|
||||||
|
|
||||||
with io.open(json_file, 'r', encoding='utf-8') as stream:
|
with io.open(json_file, 'r', encoding='utf-8') as stream:
|
||||||
try:
|
try:
|
||||||
@@ -545,13 +538,13 @@ def prettify_json_file(file_list):
|
|||||||
json.dump(obj, out, indent=4, separators=(',', ': '))
|
json.dump(obj, out, indent=4, separators=(',', ': '))
|
||||||
out.write('\n')
|
out.write('\n')
|
||||||
|
|
||||||
print("success: {}".format(outfile))
|
print(f"success: {outfile}")
|
||||||
|
|
||||||
|
|
||||||
def omit_long_data(body, omit_len=512):
|
def omit_long_data(body, omit_len=512):
|
||||||
""" omit too long str/bytes
|
""" omit too long str/bytes
|
||||||
"""
|
"""
|
||||||
if not isinstance(body, basestring):
|
if not isinstance(body, (str, bytes)):
|
||||||
return body
|
return body
|
||||||
|
|
||||||
body_len = len(body)
|
body_len = len(body)
|
||||||
@@ -560,7 +553,7 @@ def omit_long_data(body, omit_len=512):
|
|||||||
|
|
||||||
omitted_body = body[0:omit_len]
|
omitted_body = body[0:omit_len]
|
||||||
|
|
||||||
appendix_str = " ... OMITTED {} CHARACTORS ...".format(body_len - omit_len)
|
appendix_str = f" ... OMITTED {body_len - omit_len} CHARACTORS ..."
|
||||||
if isinstance(body, bytes):
|
if isinstance(body, bytes):
|
||||||
appendix_str = appendix_str.encode("utf-8")
|
appendix_str = appendix_str.encode("utf-8")
|
||||||
|
|
||||||
@@ -583,18 +576,6 @@ def dump_json_file(json_data, json_file_abs_path):
|
|||||||
|
|
||||||
try:
|
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:
|
||||||
if is_py2:
|
|
||||||
outfile.write(
|
|
||||||
unicode(json.dumps(
|
|
||||||
json_data,
|
|
||||||
indent=4,
|
|
||||||
separators=(',', ':'),
|
|
||||||
encoding="utf8",
|
|
||||||
ensure_ascii=False,
|
|
||||||
cls=PythonObjectEncoder
|
|
||||||
))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
json.dump(
|
json.dump(
|
||||||
json_data,
|
json_data,
|
||||||
outfile,
|
outfile,
|
||||||
@@ -604,38 +585,37 @@ def dump_json_file(json_data, json_file_abs_path):
|
|||||||
cls=PythonObjectEncoder
|
cls=PythonObjectEncoder
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = "dump file: {}".format(json_file_abs_path)
|
msg = f"dump file: {json_file_abs_path}"
|
||||||
logger.color_print(msg, "BLUE")
|
logger.info(msg)
|
||||||
|
|
||||||
except TypeError as ex:
|
except TypeError as ex:
|
||||||
msg = "Failed to dump json file: {}\nReason: {}".format(json_file_abs_path, ex)
|
msg = f"Failed to dump json file: {json_file_abs_path}\nReason: {ex}"
|
||||||
logger.color_print(msg, "RED")
|
logger.error(msg)
|
||||||
|
|
||||||
|
|
||||||
def prepare_dump_json_file_abs_path(project_mapping, tag_name):
|
def prepare_log_file_abs_path(project_mapping, file_name):
|
||||||
""" prepare dump json file absolute path.
|
""" prepare dump json file absolute path.
|
||||||
"""
|
"""
|
||||||
pwd_dir_path = project_mapping.get("PWD") or os.getcwd()
|
current_working_dir = os.getcwd()
|
||||||
test_path = project_mapping.get("test_path")
|
test_path = project_mapping.get("test_path")
|
||||||
|
|
||||||
if not test_path:
|
if not test_path:
|
||||||
# running passed in testcase/testsuite data structure
|
# running passed in testcase/testsuite data structure
|
||||||
dump_file_name = "tests_mapping.{}.json".format(tag_name)
|
dump_file_name = f"tests_mapping.{file_name}"
|
||||||
dumped_json_file_abs_path = os.path.join(pwd_dir_path, "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
|
return dumped_json_file_abs_path
|
||||||
|
|
||||||
# both test_path and pwd_dir_path are absolute path
|
# both test_path and pwd_dir_path are absolute path
|
||||||
logs_dir_path = os.path.join(pwd_dir_path, "logs")
|
logs_dir_path = os.path.join(current_working_dir, "logs")
|
||||||
test_path_relative_path = test_path[len(pwd_dir_path)+1:]
|
|
||||||
|
|
||||||
if os.path.isdir(test_path):
|
if os.path.isdir(test_path):
|
||||||
file_foder_path = os.path.join(logs_dir_path, test_path_relative_path)
|
file_foder_path = os.path.join(logs_dir_path, test_path)
|
||||||
dump_file_name = "all.{}.json".format(tag_name)
|
dump_file_name = f"all.{file_name}"
|
||||||
else:
|
else:
|
||||||
file_relative_folder_path, test_file = os.path.split(test_path_relative_path)
|
file_relative_folder_path, test_file = os.path.split(test_path)
|
||||||
file_foder_path = os.path.join(logs_dir_path, file_relative_folder_path)
|
file_foder_path = os.path.join(logs_dir_path, file_relative_folder_path)
|
||||||
test_file_name, _file_suffix = os.path.splitext(test_file)
|
test_file_name, _file_suffix = os.path.splitext(test_file)
|
||||||
dump_file_name = "{}.{}.json".format(test_file_name, tag_name)
|
dump_file_name = f"{test_file_name}.{file_name}"
|
||||||
|
|
||||||
dumped_json_file_abs_path = os.path.join(file_foder_path, dump_file_name)
|
dumped_json_file_abs_path = os.path.join(file_foder_path, dump_file_name)
|
||||||
return dumped_json_file_abs_path
|
return dumped_json_file_abs_path
|
||||||
@@ -651,18 +631,5 @@ def dump_logs(json_data, project_mapping, tag_name):
|
|||||||
tag_name (str): tag name, loaded/parsed/summary
|
tag_name (str): tag name, loaded/parsed/summary
|
||||||
|
|
||||||
"""
|
"""
|
||||||
json_file_abs_path = prepare_dump_json_file_abs_path(project_mapping, tag_name)
|
json_file_abs_path = prepare_log_file_abs_path(project_mapping, f"{tag_name}.json")
|
||||||
dump_json_file(json_data, json_file_abs_path)
|
dump_json_file(json_data, json_file_abs_path)
|
||||||
|
|
||||||
|
|
||||||
def get_python2_retire_msg():
|
|
||||||
retire_day = datetime(2020, 1, 1)
|
|
||||||
today = datetime.now()
|
|
||||||
left_days = (retire_day - today).days
|
|
||||||
|
|
||||||
if left_days > 0:
|
|
||||||
retire_msg = "Python 2 will retire in {} days, why not move to Python 3?".format(left_days)
|
|
||||||
else:
|
|
||||||
retire_msg = "Python 2 has been retired, you should move to Python 3."
|
|
||||||
|
|
||||||
return retire_msg
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import unittest
|
||||||
|
|
||||||
from httprunner import exceptions, loader, utils
|
from httprunner import exceptions, loader, utils
|
||||||
from tests.base import ApiServerUnittest
|
|
||||||
|
|
||||||
|
|
||||||
class TestUtils(ApiServerUnittest):
|
class TestUtils(unittest.TestCase):
|
||||||
|
|
||||||
def test_set_os_environ(self):
|
def test_set_os_environ(self):
|
||||||
self.assertNotIn("abc", os.environ)
|
self.assertNotIn("abc", os.environ)
|
||||||
@@ -187,14 +187,18 @@ class TestUtils(ApiServerUnittest):
|
|||||||
self.assertEqual(extended_variables_mapping["var1"], "val1")
|
self.assertEqual(extended_variables_mapping["var1"], "val1")
|
||||||
|
|
||||||
def test_deepcopy_dict(self):
|
def test_deepcopy_dict(self):
|
||||||
|
license_path = os.path.join(
|
||||||
|
os.path.dirname(os.path.dirname(__file__)),
|
||||||
|
"LICENSE"
|
||||||
|
)
|
||||||
data = {
|
data = {
|
||||||
'a': 1,
|
'a': 1,
|
||||||
'b': [2, 4],
|
'b': [2, 4],
|
||||||
'c': lambda x: x+1,
|
'c': lambda x: x+1,
|
||||||
'd': open('LICENSE'),
|
'd': open(license_path),
|
||||||
'f': {
|
'f': {
|
||||||
'f1': {'a1': 2},
|
'f1': {'a1': 2},
|
||||||
'f2': io.open('LICENSE', 'rb'),
|
'f2': io.open(license_path, 'rb'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new_data = utils.deepcopy_dict(data)
|
new_data = utils.deepcopy_dict(data)
|
||||||
@@ -274,26 +278,22 @@ class TestUtils(ApiServerUnittest):
|
|||||||
|
|
||||||
def test_prepare_dump_json_file_path_for_folder(self):
|
def test_prepare_dump_json_file_path_for_folder(self):
|
||||||
# hrun tests/httpbin/a.b.c/ --save-tests
|
# hrun tests/httpbin/a.b.c/ --save-tests
|
||||||
project_working_directory = os.path.join(os.getcwd(), "tests")
|
|
||||||
project_mapping = {
|
project_mapping = {
|
||||||
"PWD": project_working_directory,
|
"test_path": os.path.join("tests", "httpbin", "a.b.c")
|
||||||
"test_path": os.path.join(os.getcwd(), "tests", "httpbin", "a.b.c")
|
|
||||||
}
|
}
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
utils.prepare_dump_json_file_abs_path(project_mapping, "loaded"),
|
utils.prepare_log_file_abs_path(project_mapping, "loaded"),
|
||||||
os.path.join(project_working_directory, "logs", "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):
|
def test_prepare_dump_json_file_path_for_file(self):
|
||||||
# hrun tests/httpbin/a.b.c/rpc.yml --save-tests
|
# hrun tests/httpbin/a.b.c/rpc.yml --save-tests
|
||||||
project_working_directory = os.path.join(os.getcwd(), "tests")
|
|
||||||
project_mapping = {
|
project_mapping = {
|
||||||
"PWD": project_working_directory,
|
"test_path": os.path.join("tests", "httpbin", "a.b.c", "rpc.yml")
|
||||||
"test_path": os.path.join(os.getcwd(), "tests", "httpbin", "a.b.c", "rpc.yml")
|
|
||||||
}
|
}
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
utils.prepare_dump_json_file_abs_path(project_mapping, "loaded"),
|
utils.prepare_log_file_abs_path(project_mapping, "loaded"),
|
||||||
os.path.join(project_working_directory, "logs", "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):
|
def test_prepare_dump_json_file_path_for_passed_testcase(self):
|
||||||
@@ -302,6 +302,6 @@ class TestUtils(ApiServerUnittest):
|
|||||||
"PWD": project_working_directory
|
"PWD": project_working_directory
|
||||||
}
|
}
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
utils.prepare_dump_json_file_abs_path(project_mapping, "loaded"),
|
utils.prepare_log_file_abs_path(project_mapping, "loaded"),
|
||||||
os.path.join(project_working_directory, "logs", "tests_mapping.loaded.json")
|
os.path.join(os.getcwd(), "logs", "tests_mapping.loaded.json")
|
||||||
)
|
)
|
||||||
+18
-21
@@ -3,7 +3,9 @@
|
|||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from httprunner import exceptions, logger, parser
|
from loguru import logger
|
||||||
|
|
||||||
|
from httprunner import exceptions, parser
|
||||||
|
|
||||||
|
|
||||||
class Validator(object):
|
class Validator(object):
|
||||||
@@ -70,12 +72,12 @@ class Validator(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
script = "\n ".join(script)
|
script = "\n ".join(script)
|
||||||
code = """
|
code = f"""
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
def run_validate_script():
|
def run_validate_script():
|
||||||
{}
|
{script}
|
||||||
""".format(script)
|
"""
|
||||||
|
|
||||||
variables = {
|
variables = {
|
||||||
"status_code": self.resp_obj.status_code,
|
"status_code": self.resp_obj.status_code,
|
||||||
@@ -88,12 +90,12 @@ def run_validate_script():
|
|||||||
try:
|
try:
|
||||||
exec(code, variables)
|
exec(code, variables)
|
||||||
except SyntaxError as ex:
|
except SyntaxError as ex:
|
||||||
logger.log_warning("SyntaxError in python validate script: {}".format(ex))
|
logger.warning(f"SyntaxError in python validate script: {ex}")
|
||||||
result["check_result"] = "fail"
|
result["check_result"] = "fail"
|
||||||
result["output"] = "<br/>".join([
|
result["output"] = "<br/>".join([
|
||||||
"ErrorMessage: {}".format(ex.msg),
|
f"ErrorMessage: {ex.msg}",
|
||||||
"ErrorLine: {}".format(ex.lineno),
|
f"ErrorLine: {ex.lineno}",
|
||||||
"ErrorText: {}".format(ex.text)
|
f"ErrorText: {ex.text}"
|
||||||
])
|
])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -101,7 +103,7 @@ def run_validate_script():
|
|||||||
# run python validate script
|
# run python validate script
|
||||||
variables["run_validate_script"]()
|
variables["run_validate_script"]()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.log_warning("run python validate script failed: {}".format(ex))
|
logger.warning(f"run python validate script failed: {ex}")
|
||||||
result["check_result"] = "fail"
|
result["check_result"] = "fail"
|
||||||
|
|
||||||
_type, _value, _tb = sys.exc_info()
|
_type, _value, _tb = sys.exc_info()
|
||||||
@@ -118,8 +120,8 @@ def run_validate_script():
|
|||||||
line_no = "N/A"
|
line_no = "N/A"
|
||||||
|
|
||||||
result["output"] = "<br/>".join([
|
result["output"] = "<br/>".join([
|
||||||
"ErrorType: {}".format(_type.__name__),
|
f"ErrorType: {_type.__name__}",
|
||||||
"ErrorLine: {}".format(line_no)
|
f"ErrorLine: {line_no}"
|
||||||
])
|
])
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -131,7 +133,7 @@ def run_validate_script():
|
|||||||
if not validators:
|
if not validators:
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.log_debug("start to validate.")
|
logger.debug("start to validate.")
|
||||||
|
|
||||||
validate_pass = True
|
validate_pass = True
|
||||||
failures = []
|
failures = []
|
||||||
@@ -154,7 +156,7 @@ def run_validate_script():
|
|||||||
# validator should be LazyFunction object
|
# validator should be LazyFunction object
|
||||||
if not isinstance(validator, parser.LazyFunction):
|
if not isinstance(validator, parser.LazyFunction):
|
||||||
raise exceptions.ValidationFailure(
|
raise exceptions.ValidationFailure(
|
||||||
"validator should be parsed first: {}".format(validators))
|
f"validator should be parsed first: {validators}")
|
||||||
|
|
||||||
# evaluate validator args with context variable mapping.
|
# evaluate validator args with context variable mapping.
|
||||||
validator_args = validator.get_args()
|
validator_args = validator.get_args()
|
||||||
@@ -171,18 +173,13 @@ def run_validate_script():
|
|||||||
"expect": expect_item,
|
"expect": expect_item,
|
||||||
"expect_value": expect_value
|
"expect_value": expect_value
|
||||||
}
|
}
|
||||||
validate_msg = "\nvalidate: {} {} {}({})".format(
|
validate_msg = f"\nvalidate: {check_item} {comparator} {expect_value}({type(expect_value).__name__})"
|
||||||
check_item,
|
|
||||||
comparator,
|
|
||||||
expect_value,
|
|
||||||
type(expect_value).__name__
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validator.to_value(self.session_context.test_variables_mapping)
|
validator.to_value(self.session_context.test_variables_mapping)
|
||||||
validator_dict["check_result"] = "pass"
|
validator_dict["check_result"] = "pass"
|
||||||
validate_msg += "\t==> pass"
|
validate_msg += "\t==> pass"
|
||||||
logger.log_debug(validate_msg)
|
logger.debug(validate_msg)
|
||||||
except (AssertionError, TypeError):
|
except (AssertionError, TypeError):
|
||||||
validate_pass = False
|
validate_pass = False
|
||||||
validator_dict["check_result"] = "fail"
|
validator_dict["check_result"] = "fail"
|
||||||
@@ -194,7 +191,7 @@ def run_validate_script():
|
|||||||
expect_value,
|
expect_value,
|
||||||
type(expect_value).__name__
|
type(expect_value).__name__
|
||||||
)
|
)
|
||||||
logger.log_error(validate_msg)
|
logger.error(validate_msg)
|
||||||
failures.append(validate_msg)
|
failures.append(validate_msg)
|
||||||
|
|
||||||
self.validation_results["validate_extractor"].append(validator_dict)
|
self.validation_results["validate_extractor"].append(validator_dict)
|
||||||
|
|||||||
Generated
+256
-118
@@ -1,3 +1,17 @@
|
|||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Asyncio support for PEP-567 contextvars backport."
|
||||||
|
marker = "python_version < \"3.7\""
|
||||||
|
name = "aiocontextvars"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "0.2.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
[package.dependencies.contextvars]
|
||||||
|
python = "<3.7"
|
||||||
|
version = "2.4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Classes Without Boilerplate"
|
description = "Classes Without Boilerplate"
|
||||||
@@ -39,6 +53,7 @@ version = "7.0"
|
|||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Cross-platform colored terminal text."
|
description = "Cross-platform colored terminal text."
|
||||||
|
marker = "sys_platform == \"win32\""
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
@@ -46,36 +61,15 @@ version = "0.4.3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Log formatting with colors!"
|
description = "PEP 567 Backport"
|
||||||
name = "colorlog"
|
marker = "python_version < \"3.7\""
|
||||||
|
name = "contextvars"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
version = "4.0.2"
|
version = "2.4"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
colorama = "*"
|
immutables = ">=0.9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
category = "main"
|
|
||||||
description = "Updated configparser from Python 3.7 for Python 2.6+."
|
|
||||||
marker = "python_version < \"3\""
|
|
||||||
name = "configparser"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.6"
|
|
||||||
version = "4.0.2"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
|
|
||||||
testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2)", "pytest-flake8", "pytest-black-multipy"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
category = "main"
|
|
||||||
description = "Backports and enhancements for the contextlib module"
|
|
||||||
marker = "python_version < \"3\""
|
|
||||||
name = "contextlib2"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
version = "0.6.0.post1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "dev"
|
||||||
@@ -87,12 +81,30 @@ version = "4.5.4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4"
|
description = "A backport of the dataclasses module for Python 3.6"
|
||||||
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
|
marker = "python_version < \"3.7\""
|
||||||
name = "enum34"
|
name = "dataclasses"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
version = "1.1.6"
|
version = "0.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||||
|
name = "fastapi"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
version = "0.49.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pydantic = ">=0.32.2,<2.0.0"
|
||||||
|
starlette = "0.12.9"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["requests", "aiofiles", "jinja2", "python-multipart", "itsdangerous", "pyyaml", "graphene", "ujson", "email-validator", "uvicorn", "async-exit-stack", "async-generator"]
|
||||||
|
dev = ["pyjwt", "passlib", "autoflake", "flake8", "uvicorn", "graphene"]
|
||||||
|
doc = ["mkdocs", "mkdocs-material", "markdown-include"]
|
||||||
|
test = ["pytest (>=4.0.0)", "pytest-cov", "mypy", "black", "isort", "requests", "email-validator", "sqlalchemy", "peewee", "databases", "orjson", "async-exit-stack", "async-generator", "python-multipart", "aiofiles", "ujson"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
@@ -117,22 +129,12 @@ click = ">=2.0"
|
|||||||
itsdangerous = ">=0.21"
|
itsdangerous = ">=0.21"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "dev"
|
||||||
description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy."
|
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||||
marker = "python_version < \"3\""
|
name = "h11"
|
||||||
name = "functools32"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
version = "3.2.3-2"
|
version = "0.9.0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
category = "main"
|
|
||||||
description = "Clean single-source support for Python 3 and 2"
|
|
||||||
marker = "python_version >= \"2.7\" and python_version < \"2.8\""
|
|
||||||
name = "future"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
|
||||||
version = "0.18.2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
@@ -145,6 +147,18 @@ version = "0.3.1"
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
PyYAML = "*"
|
PyYAML = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "A collection of framework independent HTTP protocol utils."
|
||||||
|
marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\""
|
||||||
|
name = "httptools"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.1.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["Cython (0.29.14)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
@@ -153,6 +167,15 @@ optional = false
|
|||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
version = "2.8"
|
version = "2.8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Immutable Collections"
|
||||||
|
marker = "python_version < \"3.7\""
|
||||||
|
name = "immutables"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.11"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Read metadata from Python packages"
|
description = "Read metadata from Python packages"
|
||||||
@@ -165,18 +188,6 @@ version = "1.3.0"
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
zipp = ">=0.5"
|
zipp = ">=0.5"
|
||||||
|
|
||||||
[package.dependencies.configparser]
|
|
||||||
python = "<3"
|
|
||||||
version = ">=3.5"
|
|
||||||
|
|
||||||
[package.dependencies.contextlib2]
|
|
||||||
python = "<3"
|
|
||||||
version = "*"
|
|
||||||
|
|
||||||
[package.dependencies.pathlib2]
|
|
||||||
python = "<3"
|
|
||||||
version = "*"
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["sphinx", "rst.linker"]
|
docs = ["sphinx", "rst.linker"]
|
||||||
testing = ["packaging", "importlib-resources"]
|
testing = ["packaging", "importlib-resources"]
|
||||||
@@ -225,10 +236,6 @@ pyrsistent = ">=0.14.0"
|
|||||||
setuptools = "*"
|
setuptools = "*"
|
||||||
six = ">=1.11.0"
|
six = ">=1.11.0"
|
||||||
|
|
||||||
[package.dependencies.functools32]
|
|
||||||
python = "<3"
|
|
||||||
version = "*"
|
|
||||||
|
|
||||||
[package.dependencies.importlib-metadata]
|
[package.dependencies.importlib-metadata]
|
||||||
python = "<3.8"
|
python = "<3.8"
|
||||||
version = "*"
|
version = "*"
|
||||||
@@ -237,6 +244,25 @@ version = "*"
|
|||||||
format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"]
|
format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"]
|
||||||
format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"]
|
format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Python logging made (stupidly) simple"
|
||||||
|
name = "loguru"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "0.4.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = ">=0.3.4"
|
||||||
|
win32-setctime = ">=1.0.0"
|
||||||
|
|
||||||
|
[package.dependencies.aiocontextvars]
|
||||||
|
python = "<3.7"
|
||||||
|
version = ">=0.2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=4.3.20)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.3b0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Safely add untrusted strings to HTML/XML markup."
|
description = "Safely add untrusted strings to HTML/XML markup."
|
||||||
@@ -259,19 +285,21 @@ six = ">=1.0.0,<2.0.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Object-oriented filesystem paths"
|
description = "Data validation and settings management using python 3.6 type hinting"
|
||||||
marker = "python_version < \"3\""
|
name = "pydantic"
|
||||||
name = "pathlib2"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = ">=3.6"
|
||||||
version = "2.3.5"
|
version = "1.4"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
six = "*"
|
[package.dependencies.dataclasses]
|
||||||
|
python = "<3.7"
|
||||||
|
version = ">=0.6"
|
||||||
|
|
||||||
[package.dependencies.scandir]
|
[package.extras]
|
||||||
python = "<3.5"
|
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||||
version = "*"
|
email = ["email-validator (>=1.0.3)"]
|
||||||
|
typing_extensions = ["typing-extensions (>=3.7.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
@@ -321,15 +349,6 @@ version = "0.9.1"
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
requests = ">=2.0.1,<3.0.0"
|
requests = ">=2.0.1,<3.0.0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
category = "main"
|
|
||||||
description = "scandir, a better directory iterator and faster os.walk()"
|
|
||||||
marker = "python_version < \"3\""
|
|
||||||
name = "scandir"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
version = "1.10.0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Python client for Sentry (https://getsentry.com)"
|
description = "Python client for Sentry (https://getsentry.com)"
|
||||||
@@ -364,6 +383,17 @@ optional = false
|
|||||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
|
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "The little ASGI library that shines."
|
||||||
|
name = "starlette"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
version = "0.12.9"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
@@ -377,6 +407,38 @@ brotli = ["brotlipy (>=0.6.0)"]
|
|||||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "The lightning-fast ASGI server."
|
||||||
|
name = "uvicorn"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.11.3"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
click = ">=7.0.0,<8.0.0"
|
||||||
|
h11 = ">=0.8,<0.10"
|
||||||
|
httptools = ">=0.1.0,<0.2.0"
|
||||||
|
uvloop = ">=0.14.0"
|
||||||
|
websockets = ">=8.0.0,<9.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Fast implementation of asyncio event loop on top of libuv"
|
||||||
|
marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\""
|
||||||
|
name = "uvloop"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.14.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
|
||||||
|
name = "websockets"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
version = "8.0.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "dev"
|
||||||
description = "The comprehensive WSGI web application library."
|
description = "The comprehensive WSGI web application library."
|
||||||
@@ -390,6 +452,18 @@ dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-i
|
|||||||
termcolor = ["termcolor"]
|
termcolor = ["termcolor"]
|
||||||
watchdog = ["watchdog"]
|
watchdog = ["watchdog"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A small Python utility to set file creation time on Windows"
|
||||||
|
marker = "sys_platform == \"win32\""
|
||||||
|
name = "win32-setctime"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "1.0.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||||
@@ -407,10 +481,14 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
|
|||||||
testing = ["pathlib2", "contextlib2", "unittest2"]
|
testing = ["pathlib2", "contextlib2", "unittest2"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
content-hash = "843527171063a252e1b210f82037020d68f8aaed542c2d878e92d6c7e951a5f5"
|
content-hash = "57ff78f24ca37a3421d5c64007bd71eba394d6751fdbb2d0b446f523cfed9c62"
|
||||||
python-versions = "~2.7 || ^3.5"
|
python-versions = "^3.6"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
|
aiocontextvars = [
|
||||||
|
{file = "aiocontextvars-0.2.2-py2.py3-none-any.whl", hash = "sha256:885daf8261818767d8f7cbd79f9d4482d118f024b6586ef6e67980236a27bfa3"},
|
||||||
|
{file = "aiocontextvars-0.2.2.tar.gz", hash = "sha256:f027372dc48641f683c559f247bd84962becaacdc9ba711d583c3871fb5652aa"},
|
||||||
|
]
|
||||||
attrs = [
|
attrs = [
|
||||||
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
|
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
|
||||||
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
|
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
|
||||||
@@ -431,17 +509,8 @@ colorama = [
|
|||||||
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
|
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
|
||||||
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
||||||
]
|
]
|
||||||
colorlog = [
|
contextvars = [
|
||||||
{file = "colorlog-4.0.2-py2.py3-none-any.whl", hash = "sha256:450f52ea2a2b6ebb308f034ea9a9b15cea51e65650593dca1da3eb792e4e4981"},
|
{file = "contextvars-2.4.tar.gz", hash = "sha256:f38c908aaa59c14335eeea12abea5f443646216c4e29380d7bf34d2018e2c39e"},
|
||||||
{file = "colorlog-4.0.2.tar.gz", hash = "sha256:3cf31b25cbc8f86ec01fef582ef3b840950dea414084ed19ab922c8b493f9b42"},
|
|
||||||
]
|
|
||||||
configparser = [
|
|
||||||
{file = "configparser-4.0.2-py2.py3-none-any.whl", hash = "sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c"},
|
|
||||||
{file = "configparser-4.0.2.tar.gz", hash = "sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df"},
|
|
||||||
]
|
|
||||||
contextlib2 = [
|
|
||||||
{file = "contextlib2-0.6.0.post1-py2.py3-none-any.whl", hash = "sha256:3355078a159fbb44ee60ea80abd0d87b80b78c248643b49aa6d94673b413609b"},
|
|
||||||
{file = "contextlib2-0.6.0.post1.tar.gz", hash = "sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e"},
|
|
||||||
]
|
]
|
||||||
coverage = [
|
coverage = [
|
||||||
{file = "coverage-4.5.4-cp26-cp26m-macosx_10_12_x86_64.whl", hash = "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28"},
|
{file = "coverage-4.5.4-cp26-cp26m-macosx_10_12_x86_64.whl", hash = "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28"},
|
||||||
@@ -477,11 +546,13 @@ coverage = [
|
|||||||
{file = "coverage-4.5.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5"},
|
{file = "coverage-4.5.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5"},
|
||||||
{file = "coverage-4.5.4.tar.gz", hash = "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c"},
|
{file = "coverage-4.5.4.tar.gz", hash = "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c"},
|
||||||
]
|
]
|
||||||
enum34 = [
|
dataclasses = [
|
||||||
{file = "enum34-1.1.6-py2-none-any.whl", hash = "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79"},
|
{file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"},
|
||||||
{file = "enum34-1.1.6-py3-none-any.whl", hash = "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a"},
|
{file = "dataclasses-0.6.tar.gz", hash = "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"},
|
||||||
{file = "enum34-1.1.6.tar.gz", hash = "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"},
|
]
|
||||||
{file = "enum34-1.1.6.zip", hash = "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850"},
|
fastapi = [
|
||||||
|
{file = "fastapi-0.49.2-py3-none-any.whl", hash = "sha256:e3b479c61d8a02ec6c80ebbc2ee2d621a32855ffffedd55fd5f2993c6dbdcc1e"},
|
||||||
|
{file = "fastapi-0.49.2.tar.gz", hash = "sha256:68395725aac4342896b4f9aa335c7e7fb773b565df7f96e964e24bffb84dc5a3"},
|
||||||
]
|
]
|
||||||
filetype = [
|
filetype = [
|
||||||
{file = "filetype-1.0.5-py2.py3-none-any.whl", hash = "sha256:4967124d982a71700d94a08c49c4926423500e79382a92070f5ab248d44fe461"},
|
{file = "filetype-1.0.5-py2.py3-none-any.whl", hash = "sha256:4967124d982a71700d94a08c49c4926423500e79382a92070f5ab248d44fe461"},
|
||||||
@@ -491,21 +562,50 @@ flask = [
|
|||||||
{file = "Flask-0.12.4-py2.py3-none-any.whl", hash = "sha256:6c02dbaa5a9ef790d8219bdced392e2d549c10cd5a5ba4b6aa65126b2271af29"},
|
{file = "Flask-0.12.4-py2.py3-none-any.whl", hash = "sha256:6c02dbaa5a9ef790d8219bdced392e2d549c10cd5a5ba4b6aa65126b2271af29"},
|
||||||
{file = "Flask-0.12.4.tar.gz", hash = "sha256:2ea22336f6d388b4b242bc3abf8a01244a8aa3e236e7407469ef78c16ba355dd"},
|
{file = "Flask-0.12.4.tar.gz", hash = "sha256:2ea22336f6d388b4b242bc3abf8a01244a8aa3e236e7407469ef78c16ba355dd"},
|
||||||
]
|
]
|
||||||
functools32 = [
|
h11 = [
|
||||||
{file = "functools32-3.2.3-2.tar.gz", hash = "sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"},
|
{file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"},
|
||||||
{file = "functools32-3.2.3-2.zip", hash = "sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0"},
|
{file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"},
|
||||||
]
|
|
||||||
future = [
|
|
||||||
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
|
|
||||||
]
|
]
|
||||||
har2case = [
|
har2case = [
|
||||||
{file = "har2case-0.3.1-py2.py3-none-any.whl", hash = "sha256:84d3a5cc9fbb16e45372e7e880a936c59bbe8e9b66bad81927769e64f608e2af"},
|
{file = "har2case-0.3.1-py2.py3-none-any.whl", hash = "sha256:84d3a5cc9fbb16e45372e7e880a936c59bbe8e9b66bad81927769e64f608e2af"},
|
||||||
{file = "har2case-0.3.1.tar.gz", hash = "sha256:8f159ec7cba82ec4282f46af4a9dac89f65e62796521b2426d3c89c3c9fd8579"},
|
{file = "har2case-0.3.1.tar.gz", hash = "sha256:8f159ec7cba82ec4282f46af4a9dac89f65e62796521b2426d3c89c3c9fd8579"},
|
||||||
]
|
]
|
||||||
|
httptools = [
|
||||||
|
{file = "httptools-0.1.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:a2719e1d7a84bb131c4f1e0cb79705034b48de6ae486eb5297a139d6a3296dce"},
|
||||||
|
{file = "httptools-0.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:fa3cd71e31436911a44620473e873a256851e1f53dee56669dae403ba41756a4"},
|
||||||
|
{file = "httptools-0.1.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:86c6acd66765a934e8730bf0e9dfaac6fdcf2a4334212bd4a0a1c78f16475ca6"},
|
||||||
|
{file = "httptools-0.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bc3114b9edbca5a1eb7ae7db698c669eb53eb8afbbebdde116c174925260849c"},
|
||||||
|
{file = "httptools-0.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:ac0aa11e99454b6a66989aa2d44bca41d4e0f968e395a0a8f164b401fefe359a"},
|
||||||
|
{file = "httptools-0.1.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:96da81e1992be8ac2fd5597bf0283d832287e20cb3cfde8996d2b00356d4e17f"},
|
||||||
|
{file = "httptools-0.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:56b6393c6ac7abe632f2294da53f30d279130a92e8ae39d8d14ee2e1b05ad1f2"},
|
||||||
|
{file = "httptools-0.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:96eb359252aeed57ea5c7b3d79839aaa0382c9d3149f7d24dd7172b1bcecb009"},
|
||||||
|
{file = "httptools-0.1.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:fea04e126014169384dee76a153d4573d90d0cbd1d12185da089f73c78390437"},
|
||||||
|
{file = "httptools-0.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3592e854424ec94bd17dc3e0c96a64e459ec4147e6d53c0a42d0ebcef9cb9c5d"},
|
||||||
|
{file = "httptools-0.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be"},
|
||||||
|
{file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"},
|
||||||
|
]
|
||||||
idna = [
|
idna = [
|
||||||
{file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"},
|
{file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"},
|
||||||
{file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"},
|
{file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"},
|
||||||
]
|
]
|
||||||
|
immutables = [
|
||||||
|
{file = "immutables-0.11-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:bce27277a2fe91509cca69181971ab509c2ee862e8b37b09f26b64f90e8fe8fb"},
|
||||||
|
{file = "immutables-0.11-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c7eb2d15c35c73bb168c002c6ea145b65f40131e10dede54b39db0b72849b280"},
|
||||||
|
{file = "immutables-0.11-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2de2ec8dde1ca154f811776a8cbbeaea515c3b226c26036eab6484530eea28e0"},
|
||||||
|
{file = "immutables-0.11-cp35-cp35m-win32.whl", hash = "sha256:e87bd941cb4dfa35f16e1ff4b2d99a2931452dcc9cfd788dc8fe513f3d38551e"},
|
||||||
|
{file = "immutables-0.11-cp35-cp35m-win_amd64.whl", hash = "sha256:0aa055c745510238cbad2f1f709a37a1c9e30a38594de3b385e9876c48a25633"},
|
||||||
|
{file = "immutables-0.11-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:422c7d4c75c88057c625e32992248329507bca180b48cfb702b4ef608f581b50"},
|
||||||
|
{file = "immutables-0.11-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f5b93248552c9e7198558776da21c9157d3f70649905d7fdc083c2ab2fbc6088"},
|
||||||
|
{file = "immutables-0.11-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b268422a5802fbf934152b835329ac0d23b80b558eaee68034d45718edab4a11"},
|
||||||
|
{file = "immutables-0.11-cp36-cp36m-win32.whl", hash = "sha256:0f07c58122e1ce70a7165e68e18e795ac5fe94d7fee3e045ffcf6432602026df"},
|
||||||
|
{file = "immutables-0.11-cp36-cp36m-win_amd64.whl", hash = "sha256:b8fed714f1c84a3242c7184838f5e9889139a22bbdd701a182b7fdc237ca3cbb"},
|
||||||
|
{file = "immutables-0.11-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:518f20945c1f600b618fb691922c2ab43b193f04dd2d4d2823220d0202014670"},
|
||||||
|
{file = "immutables-0.11-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2c536ff2bafeeff9a7865ea10a17a50f90b80b585e31396c349e8f57b0075bd4"},
|
||||||
|
{file = "immutables-0.11-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1c2e729aab250be0de0c13fa833241a778b51390ee2650e0457d1e45b318c441"},
|
||||||
|
{file = "immutables-0.11-cp37-cp37m-win32.whl", hash = "sha256:545186faab9237c102b8bcffd36d71f0b382174c93c501e061de239753cff694"},
|
||||||
|
{file = "immutables-0.11-cp37-cp37m-win_amd64.whl", hash = "sha256:6b6d8d035e5888baad3db61dfb167476838a63afccecd927c365f228bb55754c"},
|
||||||
|
{file = "immutables-0.11.tar.gz", hash = "sha256:d6850578a0dc6530ac19113cfe4ddc13903df635212d498f176fe601a8a5a4a3"},
|
||||||
|
]
|
||||||
importlib-metadata = [
|
importlib-metadata = [
|
||||||
{file = "importlib_metadata-1.3.0-py2.py3-none-any.whl", hash = "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"},
|
{file = "importlib_metadata-1.3.0-py2.py3-none-any.whl", hash = "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"},
|
||||||
{file = "importlib_metadata-1.3.0.tar.gz", hash = "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45"},
|
{file = "importlib_metadata-1.3.0.tar.gz", hash = "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45"},
|
||||||
@@ -525,6 +625,10 @@ jsonschema = [
|
|||||||
{file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
|
{file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
|
||||||
{file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
|
{file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
|
||||||
]
|
]
|
||||||
|
loguru = [
|
||||||
|
{file = "loguru-0.4.1-py3-none-any.whl", hash = "sha256:074b3caa6748452c1e4f2b302093c94b65d5a4c5a4d7743636b4121e06437b0e"},
|
||||||
|
{file = "loguru-0.4.1.tar.gz", hash = "sha256:a6101fd435ac89ba5205a105a26a6ede9e4ddbb4408a6e167852efca47806d11"},
|
||||||
|
]
|
||||||
markupsafe = [
|
markupsafe = [
|
||||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
|
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
|
||||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
|
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
|
||||||
@@ -560,9 +664,21 @@ more-itertools = [
|
|||||||
{file = "more_itertools-5.0.0-py2-none-any.whl", hash = "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc"},
|
{file = "more_itertools-5.0.0-py2-none-any.whl", hash = "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc"},
|
||||||
{file = "more_itertools-5.0.0-py3-none-any.whl", hash = "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"},
|
{file = "more_itertools-5.0.0-py3-none-any.whl", hash = "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"},
|
||||||
]
|
]
|
||||||
pathlib2 = [
|
pydantic = [
|
||||||
{file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"},
|
{file = "pydantic-1.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:07911aab70f3bc52bb845ce1748569c5e70478ac977e106a150dd9d0465ebf04"},
|
||||||
{file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"},
|
{file = "pydantic-1.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:012c422859bac2e03ab3151ea6624fecf0e249486be7eb8c6ee69c91740c6752"},
|
||||||
|
{file = "pydantic-1.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:61d22d36808087d3184ed6ac0d91dd71c533b66addb02e4a9930e1e30833202f"},
|
||||||
|
{file = "pydantic-1.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f863456d3d4bf817f2e5248553dee3974c5dc796f48e6ddb599383570f4215ac"},
|
||||||
|
{file = "pydantic-1.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bbbed364376f4a0aebb9ea452ff7968b306499a9e74f4db69b28ff2cd4043a11"},
|
||||||
|
{file = "pydantic-1.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e27559cedbd7f59d2375bfd6eea29a330ea1a5b0589c34d6b4e0d7bec6027bbf"},
|
||||||
|
{file = "pydantic-1.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:50e4e948892a6815649ad5a9a9379ad1e5f090f17842ac206535dfaed75c6f2f"},
|
||||||
|
{file = "pydantic-1.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:8848b4eb458469739126e4c1a202d723dd092e087f8dbe3104371335f87ba5df"},
|
||||||
|
{file = "pydantic-1.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:831a0265a9e3933b3d0f04d1a81bba543bafbe4119c183ff2771871db70524ab"},
|
||||||
|
{file = "pydantic-1.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:47b8db7024ba3d46c3d4768535e1cf87b6c8cf92ccd81e76f4e1cb8ee47688b3"},
|
||||||
|
{file = "pydantic-1.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:51f11c8bbf794a68086540da099aae4a9107447c7a9d63151edbb7d50110cf21"},
|
||||||
|
{file = "pydantic-1.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6100d7862371115c40be55cc4b8d766a74b1d0dbaf99dbfe72bb4bac0faf89ed"},
|
||||||
|
{file = "pydantic-1.4-py36.py37.py38-none-any.whl", hash = "sha256:72184c1421103cca128300120f8f1185fb42a9ea73a1c9845b1c53db8c026a7d"},
|
||||||
|
{file = "pydantic-1.4.tar.gz", hash = "sha256:f17ec336e64d4583311249fb179528e9a2c27c8a2eaf590ec6ec2c6dece7cb3f"},
|
||||||
]
|
]
|
||||||
pyrsistent = [
|
pyrsistent = [
|
||||||
{file = "pyrsistent-0.15.6.tar.gz", hash = "sha256:f3b280d030afb652f79d67c5586157c5c1355c9a58dfc7940566e28d28f3df1b"},
|
{file = "pyrsistent-0.15.6.tar.gz", hash = "sha256:f3b280d030afb652f79d67c5586157c5c1355c9a58dfc7940566e28d28f3df1b"},
|
||||||
@@ -588,19 +704,6 @@ requests-toolbelt = [
|
|||||||
{file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
|
{file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
|
||||||
{file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
|
{file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
|
||||||
]
|
]
|
||||||
scandir = [
|
|
||||||
{file = "scandir-1.10.0-cp27-cp27m-win32.whl", hash = "sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188"},
|
|
||||||
{file = "scandir-1.10.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac"},
|
|
||||||
{file = "scandir-1.10.0-cp34-cp34m-win32.whl", hash = "sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f"},
|
|
||||||
{file = "scandir-1.10.0-cp34-cp34m-win_amd64.whl", hash = "sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e"},
|
|
||||||
{file = "scandir-1.10.0-cp35-cp35m-win32.whl", hash = "sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f"},
|
|
||||||
{file = "scandir-1.10.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32"},
|
|
||||||
{file = "scandir-1.10.0-cp36-cp36m-win32.whl", hash = "sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022"},
|
|
||||||
{file = "scandir-1.10.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4"},
|
|
||||||
{file = "scandir-1.10.0-cp37-cp37m-win32.whl", hash = "sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173"},
|
|
||||||
{file = "scandir-1.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d"},
|
|
||||||
{file = "scandir-1.10.0.tar.gz", hash = "sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae"},
|
|
||||||
]
|
|
||||||
sentry-sdk = [
|
sentry-sdk = [
|
||||||
{file = "sentry-sdk-0.13.5.tar.gz", hash = "sha256:c6b919623e488134a728f16326c6f0bcdab7e3f59e7f4c472a90eea4d6d8fe82"},
|
{file = "sentry-sdk-0.13.5.tar.gz", hash = "sha256:c6b919623e488134a728f16326c6f0bcdab7e3f59e7f4c472a90eea4d6d8fe82"},
|
||||||
{file = "sentry_sdk-0.13.5-py2.py3-none-any.whl", hash = "sha256:05285942901d38c7ce2498aba50d8e87b361fc603281a5902dda98f3f8c5e145"},
|
{file = "sentry_sdk-0.13.5-py2.py3-none-any.whl", hash = "sha256:05285942901d38c7ce2498aba50d8e87b361fc603281a5902dda98f3f8c5e145"},
|
||||||
@@ -609,14 +712,49 @@ six = [
|
|||||||
{file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"},
|
{file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"},
|
||||||
{file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"},
|
{file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"},
|
||||||
]
|
]
|
||||||
|
starlette = [
|
||||||
|
{file = "starlette-0.12.9.tar.gz", hash = "sha256:c2ac9a42e0e0328ad20fe444115ac5e3760c1ee2ac1ff8cdb5ec915c4a453411"},
|
||||||
|
]
|
||||||
urllib3 = [
|
urllib3 = [
|
||||||
{file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"},
|
{file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"},
|
||||||
{file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"},
|
{file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"},
|
||||||
]
|
]
|
||||||
|
uvicorn = [
|
||||||
|
{file = "uvicorn-0.11.3-py3-none-any.whl", hash = "sha256:0f58170165c4495f563d8224b2f415a0829af0412baa034d6f777904613087fd"},
|
||||||
|
{file = "uvicorn-0.11.3.tar.gz", hash = "sha256:6fdaf8e53bf1b2ddf0fe9ed06079b5348d7d1d87b3365fe2549e6de0d49e631c"},
|
||||||
|
]
|
||||||
|
uvloop = [
|
||||||
|
{file = "uvloop-0.14.0-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd"},
|
||||||
|
{file = "uvloop-0.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726"},
|
||||||
|
{file = "uvloop-0.14.0-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7"},
|
||||||
|
{file = "uvloop-0.14.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"},
|
||||||
|
{file = "uvloop-0.14.0-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891"},
|
||||||
|
{file = "uvloop-0.14.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95"},
|
||||||
|
{file = "uvloop-0.14.0-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5"},
|
||||||
|
{file = "uvloop-0.14.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09"},
|
||||||
|
{file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"},
|
||||||
|
]
|
||||||
|
websockets = [
|
||||||
|
{file = "websockets-8.0.2-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:e906128532a14b9d264a43eb48f9b3080d53a9bda819ab45bf56b8039dc606ac"},
|
||||||
|
{file = "websockets-8.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:83e63aa73331b9ca21af61df8f115fb5fbcba3f281bee650a4ad16a40cd1ef15"},
|
||||||
|
{file = "websockets-8.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e9102043a81cdc8b7c8032ff4bce39f6229e4ac39cb2010946c912eeb84e2cb6"},
|
||||||
|
{file = "websockets-8.0.2-cp36-cp36m-win32.whl", hash = "sha256:8d7a20a2f97f1e98c765651d9fb9437201a9ccc2c70e94b0270f1c5ef29667a3"},
|
||||||
|
{file = "websockets-8.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:c82e286555f839846ef4f0fdd6910769a577952e1e26aa8ee7a6f45f040e3c2b"},
|
||||||
|
{file = "websockets-8.0.2-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:73ce69217e4655783ec72ce11c151053fcbd5b837cc39de7999e19605182e28a"},
|
||||||
|
{file = "websockets-8.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8c77f7d182a6ea2a9d09c2612059f3ad859a90243e899617137ee3f6b7f2b584"},
|
||||||
|
{file = "websockets-8.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a7affaeffbc5d55681934c16bb6b8fc82bb75b175e7fd4dcca798c938bde8dda"},
|
||||||
|
{file = "websockets-8.0.2-cp37-cp37m-win32.whl", hash = "sha256:f5cb2683367e32da6a256b60929a3af9c29c212b5091cf5bace9358d03011bf5"},
|
||||||
|
{file = "websockets-8.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:049e694abe33f8a1d99969fee7bfc0ae6761f7fd5f297c58ea933b27dd6805f2"},
|
||||||
|
{file = "websockets-8.0.2.tar.gz", hash = "sha256:882a7266fa867a2ebb2c0baaa0f9159cabf131cf18c1b4270d79ad42f9208dc5"},
|
||||||
|
]
|
||||||
werkzeug = [
|
werkzeug = [
|
||||||
{file = "Werkzeug-0.16.0-py2.py3-none-any.whl", hash = "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"},
|
{file = "Werkzeug-0.16.0-py2.py3-none-any.whl", hash = "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"},
|
||||||
{file = "Werkzeug-0.16.0.tar.gz", hash = "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7"},
|
{file = "Werkzeug-0.16.0.tar.gz", hash = "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7"},
|
||||||
]
|
]
|
||||||
|
win32-setctime = [
|
||||||
|
{file = "win32_setctime-1.0.1-py3-none-any.whl", hash = "sha256:568fd636c68350bcc54755213fe01966fe0a6c90b386c0776425944a0382abef"},
|
||||||
|
{file = "win32_setctime-1.0.1.tar.gz", hash = "sha256:b47e5023ec7f0b4962950902b15bc56464a380d869f59d27dbf9ab423b23e8f9"},
|
||||||
|
]
|
||||||
zipp = [
|
zipp = [
|
||||||
{file = "zipp-0.6.0-py2.py3-none-any.whl", hash = "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"},
|
{file = "zipp-0.6.0-py2.py3-none-any.whl", hash = "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"},
|
||||||
{file = "zipp-0.6.0.tar.gz", hash = "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e"},
|
{file = "zipp-0.6.0.tar.gz", hash = "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e"},
|
||||||
|
|||||||
+6
-8
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "httprunner"
|
name = "httprunner"
|
||||||
version = "2.5.7"
|
version = "3.0.0-alpha"
|
||||||
description = "One-stop solution for HTTP(S) testing."
|
description = "One-stop solution for HTTP(S) testing."
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -20,8 +20,6 @@ classifiers = [
|
|||||||
"Operating System :: MacOS",
|
"Operating System :: MacOS",
|
||||||
"Operating System :: POSIX :: Linux",
|
"Operating System :: POSIX :: Linux",
|
||||||
"Operating System :: Microsoft :: Windows",
|
"Operating System :: Microsoft :: Windows",
|
||||||
"Programming Language :: Python :: 2.7",
|
|
||||||
"Programming Language :: Python :: 3.5",
|
|
||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
"Programming Language :: Python :: 3.8"
|
"Programming Language :: Python :: 3.8"
|
||||||
@@ -30,24 +28,24 @@ classifiers = [
|
|||||||
include = ["docs/CHANGELOG.md"]
|
include = ["docs/CHANGELOG.md"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "~2.7 || ^3.5"
|
python = "^3.6"
|
||||||
requests = "^2.22.0"
|
requests = "^2.22.0"
|
||||||
requests-toolbelt = "^0.9.1"
|
requests-toolbelt = "^0.9.1"
|
||||||
pyyaml = "^5.1.2"
|
pyyaml = "^5.1.2"
|
||||||
jinja2 = "^2.10.3"
|
jinja2 = "^2.10.3"
|
||||||
har2case = "^0.3.1"
|
har2case = "^0.3.1"
|
||||||
colorama = "^0.4.1"
|
|
||||||
colorlog = "^4.0.2"
|
|
||||||
filetype = "^1.0.5"
|
filetype = "^1.0.5"
|
||||||
jsonpath = "^0.82"
|
jsonpath = "^0.82"
|
||||||
sentry-sdk = "^0.13.5"
|
sentry-sdk = "^0.13.5"
|
||||||
future = { version = "^0.18.1", python = "~2.7" }
|
|
||||||
enum34 = { version = "^1.1.6", python = "~2.7" }
|
|
||||||
jsonschema = "^3.2.0"
|
jsonschema = "^3.2.0"
|
||||||
|
pydantic = "^1.4"
|
||||||
|
loguru = "^0.4.1"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
flask = "<1.0.0"
|
flask = "<1.0.0"
|
||||||
coverage = "^4.5.4"
|
coverage = "^4.5.4"
|
||||||
|
uvicorn = "^0.11.3"
|
||||||
|
fastapi = "^0.49.0"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
hrun = "httprunner.cli:main"
|
hrun = "httprunner.cli:main"
|
||||||
|
|||||||
@@ -470,7 +470,6 @@ class TestHttpRunner(ApiServerUnittest):
|
|||||||
self.assertEqual(summary["details"][1]["stat"]["total"], 1)
|
self.assertEqual(summary["details"][1]["stat"]["total"], 1)
|
||||||
self.assertEqual(summary["details"][2]["stat"]["total"], 1)
|
self.assertEqual(summary["details"][2]["stat"]["total"], 1)
|
||||||
|
|
||||||
|
|
||||||
def test_run_testcase_hardcode(self):
|
def test_run_testcase_hardcode(self):
|
||||||
for testcase_file_path in self.testcase_file_path_list:
|
for testcase_file_path in self.testcase_file_path_list:
|
||||||
summary = self.runner.run(testcase_file_path)
|
summary = self.runner.run(testcase_file_path)
|
||||||
@@ -479,7 +478,6 @@ class TestHttpRunner(ApiServerUnittest):
|
|||||||
self.assertEqual(summary["stat"]["teststeps"]["total"], 3)
|
self.assertEqual(summary["stat"]["teststeps"]["total"], 3)
|
||||||
self.assertEqual(summary["stat"]["teststeps"]["successes"], 3)
|
self.assertEqual(summary["stat"]["teststeps"]["successes"], 3)
|
||||||
|
|
||||||
|
|
||||||
def test_run_testcase_template_variables(self):
|
def test_run_testcase_template_variables(self):
|
||||||
testcase_file_path = os.path.join(
|
testcase_file_path = os.path.join(
|
||||||
os.getcwd(), 'tests/data/demo_testcase_variables.yml')
|
os.getcwd(), 'tests/data/demo_testcase_variables.yml')
|
||||||
|
|||||||
@@ -56,10 +56,6 @@ class TestHttpClient(ApiServerUnittest):
|
|||||||
|
|
||||||
def test_request_with_cookies(self):
|
def test_request_with_cookies(self):
|
||||||
url = "{}/api/users/1000".format(self.host)
|
url = "{}/api/users/1000".format(self.host)
|
||||||
data = {
|
|
||||||
'name': 'user1',
|
|
||||||
'password': '123456'
|
|
||||||
}
|
|
||||||
cookies = {
|
cookies = {
|
||||||
"a": "1",
|
"a": "1",
|
||||||
"b": "2"
|
"b": "2"
|
||||||
@@ -70,7 +66,6 @@ class TestHttpClient(ApiServerUnittest):
|
|||||||
|
|
||||||
def test_request_redirect(self):
|
def test_request_redirect(self):
|
||||||
url = "{}/redirect-to?url=https%3A%2F%2Fgithub.com&status_code=302".format(HTTPBIN_SERVER)
|
url = "{}/redirect-to?url=https%3A%2F%2Fgithub.com&status_code=302".format(HTTPBIN_SERVER)
|
||||||
headers = {"accept: text/html"}
|
|
||||||
cookies = {
|
cookies = {
|
||||||
"a": "1",
|
"a": "1",
|
||||||
"b": "2"
|
"b": "2"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from httprunner import exceptions, response
|
from httprunner import exceptions, response
|
||||||
from httprunner.compat import basestring, bytes
|
|
||||||
from tests.api_server import HTTPBIN_SERVER
|
from tests.api_server import HTTPBIN_SERVER
|
||||||
from tests.base import ApiServerUnittest
|
from tests.base import ApiServerUnittest
|
||||||
|
|
||||||
@@ -257,7 +256,7 @@ class TestResponse(ApiServerUnittest):
|
|||||||
]
|
]
|
||||||
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
|
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
|
||||||
|
|
||||||
self.assertIsInstance(extract_binds_dict["resp_content"], basestring)
|
self.assertIsInstance(extract_binds_dict["resp_content"], str)
|
||||||
self.assertIn("httpbin.org", extract_binds_dict["resp_content"])
|
self.assertIn("httpbin.org", extract_binds_dict["resp_content"])
|
||||||
|
|
||||||
extract_binds_list = [
|
extract_binds_list = [
|
||||||
|
|||||||
Reference in New Issue
Block a user