mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
refactor: remove unused code
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
"""
|
||||
HttpRunner report
|
||||
|
||||
- summarize: aggregate test stat data to summary
|
||||
- stringify: stringify summary, in order to dump json file and generate html report.
|
||||
- html: render html report
|
||||
"""
|
||||
|
||||
from httprunner.report.summarize import get_platform, aggregate_stat, get_summary
|
||||
from httprunner.report.stringify import stringify_summary
|
||||
from httprunner.report.html import HtmlTestResult, gen_html_report
|
||||
|
||||
__all__ = [
|
||||
"get_platform",
|
||||
"aggregate_stat",
|
||||
"get_summary",
|
||||
"stringify_summary",
|
||||
"HtmlTestResult",
|
||||
"gen_html_report",
|
||||
]
|
||||
@@ -1,12 +0,0 @@
|
||||
"""
|
||||
HttpRunner html report
|
||||
|
||||
- result: define resultclass for unittest TextTestRunner
|
||||
- gen_report: render html report with jinja2 template
|
||||
|
||||
"""
|
||||
|
||||
from httprunner.report.html.result import HtmlTestResult
|
||||
from httprunner.report.html.gen_report import gen_html_report
|
||||
|
||||
__all__ = ["HtmlTestResult", "gen_html_report"]
|
||||
@@ -1,64 +0,0 @@
|
||||
import io
|
||||
import os
|
||||
|
||||
from jinja2 import Template
|
||||
from loguru import logger
|
||||
|
||||
from httprunner.exceptions import SummaryEmpty
|
||||
from httprunner.schema import TestSuiteSummary
|
||||
|
||||
|
||||
def gen_html_report(
|
||||
testsuite_summary: TestSuiteSummary,
|
||||
report_template=None,
|
||||
report_dir=None,
|
||||
report_file=None,
|
||||
):
|
||||
""" render html report with specified report name and template
|
||||
|
||||
Args:
|
||||
testsuite_summary (dict): testsuite result summary data
|
||||
report_template (str): specify html report template path, template should be in Jinja2 format.
|
||||
report_dir (str): specify html report save directory
|
||||
report_file (str): specify html report file path, this has higher priority than specifying report dir.
|
||||
|
||||
"""
|
||||
if testsuite_summary.stat.total == 0:
|
||||
logger.error(f"test result testsuite_summary is empty ! {testsuite_summary}")
|
||||
raise SummaryEmpty
|
||||
|
||||
if not report_template:
|
||||
report_template = os.path.join(
|
||||
os.path.abspath(os.path.dirname(__file__)), "template.html"
|
||||
)
|
||||
logger.debug("No html report template specified, use default.")
|
||||
else:
|
||||
logger.info(f"render with html report template: {report_template}")
|
||||
|
||||
logger.info("Start to render Html report ...")
|
||||
|
||||
if report_file:
|
||||
report_dir = os.path.dirname(report_file)
|
||||
report_file_name = os.path.basename(report_file)
|
||||
else:
|
||||
report_dir = report_dir or os.path.join(os.getcwd(), "reports")
|
||||
# fix #826: Windows does not support file name include ":"
|
||||
report_file_name = "{}.html".format(
|
||||
testsuite_summary.time.start_at_iso_format.replace(":", "").replace("-", "")
|
||||
)
|
||||
|
||||
if not os.path.isdir(report_dir):
|
||||
os.makedirs(report_dir)
|
||||
|
||||
report_path = os.path.join(report_dir, report_file_name)
|
||||
with io.open(report_template, "r", encoding="utf-8") as fp_r:
|
||||
template_content = fp_r.read()
|
||||
with io.open(report_path, "w", encoding="utf-8") as fp_w:
|
||||
rendered_content = Template(
|
||||
template_content, extensions=["jinja2.ext.loopcontrols"]
|
||||
).render(testsuite_summary.dict())
|
||||
fp_w.write(rendered_content)
|
||||
|
||||
logger.info(f"Generated Html report: {report_path}")
|
||||
|
||||
return report_path
|
||||
@@ -1,65 +0,0 @@
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class HtmlTestResult(unittest.TextTestResult):
|
||||
""" A html result class that can generate formatted html results, used by TextTestRunner.
|
||||
Each testcase is corresponding to one HtmlTestResult instance
|
||||
"""
|
||||
|
||||
def __init__(self, stream, descriptions, verbosity):
|
||||
super(HtmlTestResult, self).__init__(stream, descriptions, verbosity)
|
||||
self.name = ""
|
||||
self.status = ""
|
||||
self.attachment = ""
|
||||
self.step_datas = None
|
||||
|
||||
def _record_test(self, test, status, attachment=""):
|
||||
self.name = test.shortDescription()
|
||||
self.status = status
|
||||
self.attachment = attachment
|
||||
self.step_datas = test.step_datas
|
||||
|
||||
def startTestRun(self):
|
||||
self.start_at = time.time()
|
||||
|
||||
def startTest(self, test):
|
||||
""" add start test time """
|
||||
super(HtmlTestResult, self).startTest(test)
|
||||
logger.info(test.shortDescription())
|
||||
|
||||
def addSuccess(self, test):
|
||||
super(HtmlTestResult, self).addSuccess(test)
|
||||
self._record_test(test, "success")
|
||||
print("")
|
||||
|
||||
def addError(self, test, err):
|
||||
super(HtmlTestResult, self).addError(test, err)
|
||||
self._record_test(test, "error", self._exc_info_to_string(err, test))
|
||||
print("")
|
||||
|
||||
def addFailure(self, test, err):
|
||||
super(HtmlTestResult, self).addFailure(test, err)
|
||||
self._record_test(test, "failure", self._exc_info_to_string(err, test))
|
||||
print("")
|
||||
|
||||
def addSkip(self, test, reason):
|
||||
super(HtmlTestResult, self).addSkip(test, reason)
|
||||
self._record_test(test, "skipped", reason)
|
||||
print("")
|
||||
|
||||
def addExpectedFailure(self, test, err):
|
||||
super(HtmlTestResult, self).addExpectedFailure(test, err)
|
||||
self._record_test(test, "ExpectedFailure", self._exc_info_to_string(err, test))
|
||||
print("")
|
||||
|
||||
def addUnexpectedSuccess(self, test):
|
||||
super(HtmlTestResult, self).addUnexpectedSuccess(test)
|
||||
self._record_test(test, "UnexpectedSuccess")
|
||||
print("")
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
return time.time() - self.start_at
|
||||
@@ -1,350 +0,0 @@
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{html_report_name}} - TestReport</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #f2f2f2;
|
||||
color: #333;
|
||||
margin: 0 auto;
|
||||
width: 960px;
|
||||
}
|
||||
#summary {
|
||||
width: 960px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#summary th {
|
||||
background-color: skyblue;
|
||||
padding: 5px 12px;
|
||||
}
|
||||
#summary td {
|
||||
background-color: lightblue;
|
||||
text-align: center;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.details {
|
||||
width: 960px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.details th {
|
||||
background-color: skyblue;
|
||||
padding: 5px 12px;
|
||||
}
|
||||
.details tr .passed {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
.details tr .failed {
|
||||
background-color: red;
|
||||
}
|
||||
.details tr .unchecked {
|
||||
background-color: gray;
|
||||
}
|
||||
.details td {
|
||||
background-color: lightblue;
|
||||
padding: 5px 12px;
|
||||
}
|
||||
.details .detail {
|
||||
background-color: lightgrey;
|
||||
font-size: smaller;
|
||||
padding: 5px 10px;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
.details .success {
|
||||
background-color: greenyellow;
|
||||
}
|
||||
.details .error {
|
||||
background-color: red;
|
||||
}
|
||||
.details .failure {
|
||||
background-color: salmon;
|
||||
}
|
||||
.details .skipped {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 1em;
|
||||
padding: 6px;
|
||||
width: 4em;
|
||||
text-align: center;
|
||||
background-color: #06d85f;
|
||||
border-radius: 20px/50px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
a.button{
|
||||
color: gray;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.button:hover {
|
||||
background: #2cffbd;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
transition: opacity 500ms;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
line-height: 25px;
|
||||
}
|
||||
.overlay:target {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.popup {
|
||||
margin: 70px auto;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
width: 50%;
|
||||
position: relative;
|
||||
transition: all 3s ease-in-out;
|
||||
}
|
||||
|
||||
.popup h2 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
font-family: Tahoma, Arial, sans-serif;
|
||||
}
|
||||
.popup .close {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 30px;
|
||||
transition: all 200ms;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
.popup .close:hover {
|
||||
color: #06d85f;
|
||||
}
|
||||
.popup .content {
|
||||
max-height: 80%;
|
||||
overflow: auto;
|
||||
text-align: left;
|
||||
}
|
||||
.popup .separator {
|
||||
color:royalblue
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.box {
|
||||
width: 70%;
|
||||
}
|
||||
.popup {
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Test Report: {{html_report_name}}</h1>
|
||||
|
||||
<h2>Summary</h2>
|
||||
<table id="summary">
|
||||
<tr>
|
||||
<th>START AT</th>
|
||||
<td colspan="4">{{time.start_at_iso_format}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>DURATION</th>
|
||||
<td colspan="4">{{ '%0.3f'| format(time.duration|float) }} seconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>PLATFORM</th>
|
||||
<td>HttpRunner {{ platform.httprunner_version }} </td>
|
||||
<td>{{ platform.python_version }} </td>
|
||||
<td colspan="2">{{ platform.platform }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>STAT</th>
|
||||
<th colspan="2">SUCCESS</th>
|
||||
<th colspan="2">FAIL</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>total (details) =></td>
|
||||
<td colspan="2">{{stat.success}}</td>
|
||||
<td colspan="2">{{stat.fail}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Details</h2>
|
||||
|
||||
{% for testcase_summary in testcases %}
|
||||
{% set testcase_index = loop.index %}
|
||||
<h3>{{testcase_summary.name}}</h3>
|
||||
<table id="testcase_{{testcase_index}}" class="details">
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th colspan="2">Name</th>
|
||||
<th>Response Time</th>
|
||||
<th>Detail</th>
|
||||
</tr>
|
||||
|
||||
{% set testcase = testcase_summary %}
|
||||
{% set step_datas = testcase_summary.step_datas %}
|
||||
{% for session_data in step_datas %}
|
||||
{% set step_index = "{}_{}".format(testcase_index, loop.index) %}
|
||||
<tr id="step_{{step_index}}">
|
||||
<th class="{{session_data.status}}" style="width:5em;">{{session_data.status}}</th>
|
||||
<td colspan="2">{{session_data.name}}</td>
|
||||
<td style="text-align:center;width:6em;">{{ session_data.stat.response_time_ms }} ms</td>
|
||||
<td class="detail">
|
||||
<a class="button" href="#popup_log_{{step_index}}">log-{{loop.index}}</a>
|
||||
<div id="popup_log_{{step_index}}" class="overlay">
|
||||
<div class="popup">
|
||||
<h2>Request and Response data</h2>
|
||||
<a class="close" href="#step_{{step_index}}">×</a>
|
||||
|
||||
<div class="content">
|
||||
<h3>Name: {{ session_data.name }}</h3>
|
||||
|
||||
{% for req_resp in session_data.req_resps %}
|
||||
|
||||
{% if loop.index > 1 %}
|
||||
<div class="separator">==================================== redirect to ====================================</div>
|
||||
{% endif %}
|
||||
|
||||
<h3>Request:</h3>
|
||||
<div style="overflow: auto">
|
||||
<table>
|
||||
{% for key, value in req_resp.request.items() %}
|
||||
<tr>
|
||||
<th>{{key}}</th>
|
||||
<td>
|
||||
{% if key in ["headers", "body"] %}
|
||||
<pre>{{ value | e }}</pre>
|
||||
{% else %}
|
||||
{{value}}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>Response:</h3>
|
||||
<div style="overflow: auto">
|
||||
<table>
|
||||
{% for key, value in req_resp.response.items() %}
|
||||
<tr>
|
||||
<th>{{key}}</th>
|
||||
<td>
|
||||
{% if key == "headers" %}
|
||||
<pre>{{ value | e }}</pre>
|
||||
{% elif key == "body" %}
|
||||
{% if "image" in req_resp.response.content_type %}
|
||||
<img src="{{ req_resp.response.content }}" />
|
||||
{% else %}
|
||||
<pre>{{ value | e }}</pre>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<h3>Validators:</h3>
|
||||
<div style="overflow: auto">
|
||||
{% set validate_extractors = session_data.validators.validate_extractor %}
|
||||
{% if validate_extractors %}
|
||||
<table>
|
||||
<tr>
|
||||
<th>check</th>
|
||||
<th>comparator</th>
|
||||
<th>expect value</th>
|
||||
<th>actual value</th>
|
||||
</tr>
|
||||
{% for validator in validate_extractors %}
|
||||
<tr>
|
||||
{% if validator.check_result == "pass" %}
|
||||
<td class="passed">
|
||||
{% elif validator.check_result == "fail" %}
|
||||
<td class="failed">
|
||||
{% elif validator.check_result == "unchecked" %}
|
||||
<td class="unchecked">
|
||||
{% endif %}
|
||||
{{validator.check | e}}
|
||||
</td>
|
||||
<td>{{validator.comparator}}</td>
|
||||
<td>{{validator.expect | e}}</td>
|
||||
<td>{{validator.check_value | e}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% set validate_script = session_data.validators.validate_script %}
|
||||
{% if validate_script %}
|
||||
<table>
|
||||
<tr>
|
||||
<th>validate script</th><th>output</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><pre>{{validate_script.validate_script | safe}}</pre></td>
|
||||
{% if validate_script.check_result == "pass" %}
|
||||
<td class="passed">
|
||||
{% elif validate_script.check_result == "fail" %}
|
||||
<td class="failed">
|
||||
{% endif %}
|
||||
{{validate_script.output}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h3>Statistics:</h3>
|
||||
<div style="overflow: auto">
|
||||
<table>
|
||||
<tr>
|
||||
<th>content_size(bytes)</th>
|
||||
<td>{{ session_data.stat.content_size }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>response_time(ms)</th>
|
||||
<td>{{ session_data.stat.response_time_ms }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>elapsed(ms)</th>
|
||||
<td>{{ session_data.stat.elapsed_ms }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if session_data.status == "failed" %}
|
||||
<a class="button" href="#popup_attachment_{{step_index}}">traceback</a>
|
||||
<div id="popup_attachment_{{step_index}}" class="overlay">
|
||||
<div class="popup">
|
||||
<h2>Traceback Message</h2>
|
||||
<a class="close" href="#step_{{step_index}}">×</a>
|
||||
<div class="content"><pre>{{ testcase.attachment | e }}</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endfor %}
|
||||
</body>
|
||||
@@ -1,45 +0,0 @@
|
||||
import json
|
||||
import platform
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
|
||||
from httprunner import __version__
|
||||
|
||||
|
||||
def prepare_event_kwargs(event_name, params):
|
||||
""" prepare report event kwargs"""
|
||||
|
||||
kwargs = {
|
||||
"headers": {"content-type": "application/json"},
|
||||
"json": {
|
||||
"user": {"user_unique_id": str(uuid.getnode())},
|
||||
"header": {
|
||||
"app_id": 173519,
|
||||
"os_name": platform.system(),
|
||||
"os_version": platform.release(),
|
||||
"app_version": __version__, # HttpRunner version
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"event": event_name,
|
||||
"params": json.dumps(params),
|
||||
"time": int(time.time()),
|
||||
}
|
||||
],
|
||||
"verbose": 1,
|
||||
},
|
||||
}
|
||||
return kwargs
|
||||
|
||||
|
||||
def report_event(event_name, success=True):
|
||||
params = {"success": 1 if success else 0}
|
||||
kwargs = prepare_event_kwargs(event_name, params)
|
||||
resp = requests.post("http://mcs.snssdk.com/v1/json", **kwargs)
|
||||
print("resp---", resp.json())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
report_event("loader")
|
||||
@@ -1,170 +0,0 @@
|
||||
import json
|
||||
from base64 import b64encode
|
||||
from collections import Iterable
|
||||
from typing import List
|
||||
|
||||
from jinja2 import escape
|
||||
from requests.cookies import RequestsCookieJar
|
||||
|
||||
from httprunner.schema import TestSuiteSummary, SessionData
|
||||
|
||||
|
||||
def dumps_json(value):
|
||||
""" dumps json value to indented string
|
||||
|
||||
Args:
|
||||
value (dict): raw json data
|
||||
|
||||
Returns:
|
||||
str: indented json dump string
|
||||
|
||||
"""
|
||||
return json.dumps(value, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
def detect_encoding(value):
|
||||
try:
|
||||
return json.detect_encoding(value)
|
||||
except AttributeError:
|
||||
return "utf-8"
|
||||
|
||||
|
||||
def __stringify_request(request_data):
|
||||
""" stringfy HTTP request data
|
||||
|
||||
Args:
|
||||
request_data (dict): HTTP request data in dict.
|
||||
|
||||
{
|
||||
"url": "http://127.0.0.1:5000/api/get-token",
|
||||
"method": "POST",
|
||||
"headers": {
|
||||
"User-Agent": "python-requests/2.20.0",
|
||||
"Accept-Encoding": "gzip, deflate",
|
||||
"Accept": "*/*",
|
||||
"Connection": "keep-alive",
|
||||
"user_agent": "iOS/10.3",
|
||||
"device_sn": "TESTCASE_CREATE_XXX",
|
||||
"os_platform": "ios",
|
||||
"app_version": "2.8.6",
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": "52"
|
||||
},
|
||||
"body": b'{"sign": "cb9d60acd09080ea66c8e63a1c78c6459ea00168"}',
|
||||
"verify": false
|
||||
}
|
||||
|
||||
"""
|
||||
for key, value in request_data.items():
|
||||
|
||||
if isinstance(value, (list, dict)):
|
||||
value = dumps_json(value)
|
||||
|
||||
elif isinstance(value, bytes):
|
||||
try:
|
||||
encoding = detect_encoding(value)
|
||||
value = value.decode(encoding)
|
||||
if key == "body":
|
||||
try:
|
||||
# request body is in json format
|
||||
value = json.loads(value)
|
||||
value = dumps_json(value)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
value = escape(value)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
|
||||
elif not isinstance(value, (str, bytes, int, float, Iterable)):
|
||||
# class instance, e.g. MultipartEncoder()
|
||||
value = repr(value)
|
||||
|
||||
elif isinstance(value, RequestsCookieJar):
|
||||
value = value.get_dict()
|
||||
|
||||
request_data[key] = value
|
||||
|
||||
|
||||
def __stringify_response(response_data):
|
||||
""" stringfy HTTP response data
|
||||
|
||||
Args:
|
||||
response_data (dict):
|
||||
|
||||
{
|
||||
"status_code": 404,
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": "30",
|
||||
"Server": "Werkzeug/0.14.1 Python/3.7.0",
|
||||
"Date": "Tue, 27 Nov 2018 06:19:27 GMT"
|
||||
},
|
||||
"encoding": "None",
|
||||
"content_type": "application/json",
|
||||
"ok": false,
|
||||
"url": "http://127.0.0.1:5000/api/users/9001",
|
||||
"reason": "NOT FOUND",
|
||||
"cookies": {},
|
||||
"body": {
|
||||
"success": false,
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
for key, value in response_data.items():
|
||||
|
||||
if isinstance(value, (list, dict)):
|
||||
value = dumps_json(value)
|
||||
|
||||
elif isinstance(value, bytes):
|
||||
try:
|
||||
encoding = response_data.get("encoding")
|
||||
if not encoding or encoding == "None":
|
||||
encoding = detect_encoding(value)
|
||||
|
||||
if key == "body" and "image" in response_data["content_type"]:
|
||||
# display image
|
||||
value = "data:{};base64,{}".format(
|
||||
response_data["content_type"], b64encode(value).decode(encoding)
|
||||
)
|
||||
else:
|
||||
value = escape(value.decode(encoding))
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
|
||||
elif not isinstance(value, (str, bytes, int, float, Iterable)):
|
||||
# class instance, e.g. MultipartEncoder()
|
||||
value = repr(value)
|
||||
|
||||
elif isinstance(value, RequestsCookieJar):
|
||||
value = value.get_dict()
|
||||
|
||||
response_data[key] = value
|
||||
|
||||
|
||||
def stringify_summary(testsuite_summary: TestSuiteSummary):
|
||||
""" stringify summary, in order to dump json file and generate html report.
|
||||
"""
|
||||
for index, testcase_summary in enumerate(testsuite_summary.testcases):
|
||||
|
||||
if not testcase_summary.name:
|
||||
testcase_summary.name = f"testcase {index}"
|
||||
|
||||
step_datas = testcase_summary.step_datas
|
||||
for step_data in step_datas:
|
||||
if isinstance(step_data.data, list):
|
||||
# List[SessionData]
|
||||
session_data_list: List[SessionData] = step_data.data
|
||||
for session_data in session_data_list:
|
||||
req_resp_list = session_data.req_resps
|
||||
for req_resp in req_resp_list:
|
||||
__stringify_request(req_resp.request)
|
||||
__stringify_response(req_resp.response)
|
||||
else:
|
||||
# SessionData
|
||||
session_data: SessionData = step_data.data
|
||||
req_resp_list = session_data.req_resps
|
||||
for req_resp in req_resp_list:
|
||||
__stringify_request(req_resp.request)
|
||||
__stringify_response(req_resp.response)
|
||||
@@ -1,76 +0,0 @@
|
||||
import platform
|
||||
from datetime import datetime
|
||||
|
||||
from httprunner import __version__
|
||||
from httprunner.report.html.result import HtmlTestResult
|
||||
from httprunner.schema import TestCaseSummary, TestCaseTime, TestCaseInOut
|
||||
|
||||
|
||||
def get_platform():
|
||||
return {
|
||||
"httprunner_version": __version__,
|
||||
"python_version": "{} {}".format(
|
||||
platform.python_implementation(), platform.python_version()
|
||||
),
|
||||
"platform": platform.platform(),
|
||||
}
|
||||
|
||||
|
||||
def aggregate_stat(origin_stat, new_stat):
|
||||
""" aggregate new_stat to origin_stat.
|
||||
|
||||
Args:
|
||||
origin_stat (dict): origin stat dict, will be updated with new_stat dict.
|
||||
new_stat (dict): new stat dict.
|
||||
|
||||
"""
|
||||
for key in new_stat:
|
||||
if key not in origin_stat:
|
||||
origin_stat[key] = new_stat[key]
|
||||
elif key == "start_at":
|
||||
# start datetime
|
||||
origin_stat["start_at"] = min(origin_stat["start_at"], new_stat["start_at"])
|
||||
elif key == "duration":
|
||||
# duration = max_end_time - min_start_time
|
||||
max_end_time = max(
|
||||
origin_stat["start_at"] + origin_stat["duration"],
|
||||
new_stat["start_at"] + new_stat["duration"],
|
||||
)
|
||||
min_start_time = min(origin_stat["start_at"], new_stat["start_at"])
|
||||
origin_stat["duration"] = max_end_time - min_start_time
|
||||
else:
|
||||
origin_stat[key] += new_stat[key]
|
||||
|
||||
|
||||
def get_summary(result: HtmlTestResult) -> TestCaseSummary:
|
||||
""" get summary from test result
|
||||
|
||||
Args:
|
||||
result (instance): HtmlTestResult() instance
|
||||
|
||||
Returns:
|
||||
dict: summary extracted from result.
|
||||
|
||||
{
|
||||
"success": True,
|
||||
"stat": {},
|
||||
"time": {},
|
||||
"record": {}
|
||||
}
|
||||
|
||||
"""
|
||||
start_at_timestamp = result.start_at
|
||||
start_at_iso_format = datetime.utcfromtimestamp(start_at_timestamp).isoformat()
|
||||
return TestCaseSummary(
|
||||
success=result.wasSuccessful(),
|
||||
time=TestCaseTime(
|
||||
start_at=result.start_at,
|
||||
start_at_iso_format=start_at_iso_format,
|
||||
duration=result.duration,
|
||||
),
|
||||
name=result.name,
|
||||
status=result.status,
|
||||
attachment=result.attachment,
|
||||
in_out=TestCaseInOut(),
|
||||
step_datas=result.step_datas,
|
||||
)
|
||||
@@ -174,7 +174,7 @@ class HttpRunner(object):
|
||||
|
||||
self.start_at = time.time()
|
||||
self.step_datas: List[StepData] = []
|
||||
self.session_variables.clear()
|
||||
self.session_variables = {}
|
||||
for step in self.teststeps:
|
||||
# update with config variables
|
||||
step.variables.update(self.config.variables)
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
# encoding: utf-8
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import io
|
||||
import itertools
|
||||
import json
|
||||
import os.path
|
||||
from typing import Union
|
||||
import platform
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from httprunner import __version__
|
||||
from httprunner import exceptions
|
||||
|
||||
|
||||
@@ -83,42 +79,6 @@ def lower_dict_keys(origin_dict):
|
||||
return {key.lower(): value for key, value in origin_dict.items()}
|
||||
|
||||
|
||||
def deepcopy_dict(data):
|
||||
""" deepcopy dict data, ignore file object (_io.BufferedReader)
|
||||
|
||||
Args:
|
||||
data (dict): dict data structure
|
||||
{
|
||||
'a': 1,
|
||||
'b': [2, 4],
|
||||
'c': lambda x: x+1,
|
||||
'd': open('LICENSE'),
|
||||
'f': {
|
||||
'f1': {'a1': 2},
|
||||
'f2': io.open('LICENSE', 'rb'),
|
||||
}
|
||||
}
|
||||
|
||||
Returns:
|
||||
dict: deep copied dict data, with file object unchanged.
|
||||
|
||||
"""
|
||||
try:
|
||||
return copy.deepcopy(data)
|
||||
except TypeError:
|
||||
copied_data = {}
|
||||
for key, value in data.items():
|
||||
if isinstance(value, dict):
|
||||
copied_data[key] = deepcopy_dict(value)
|
||||
else:
|
||||
try:
|
||||
copied_data[key] = copy.deepcopy(value)
|
||||
except TypeError:
|
||||
copied_data[key] = value
|
||||
|
||||
return copied_data
|
||||
|
||||
|
||||
def print_info(info_mapping):
|
||||
""" print info in mapping.
|
||||
|
||||
@@ -164,47 +124,6 @@ def print_info(info_mapping):
|
||||
logger.info(content)
|
||||
|
||||
|
||||
def gen_cartesian_product(*args):
|
||||
""" generate cartesian product for lists
|
||||
|
||||
Args:
|
||||
args (list of list): lists to be generated with cartesian product
|
||||
|
||||
Returns:
|
||||
list: cartesian product in list
|
||||
|
||||
Examples:
|
||||
|
||||
>>> arg1 = [{"a": 1}, {"a": 2}]
|
||||
>>> arg2 = [{"x": 111, "y": 112}, {"x": 121, "y": 122}]
|
||||
>>> args = [arg1, arg2]
|
||||
>>> gen_cartesian_product(*args)
|
||||
>>> # same as below
|
||||
>>> gen_cartesian_product(arg1, arg2)
|
||||
[
|
||||
{'a': 1, 'x': 111, 'y': 112},
|
||||
{'a': 1, 'x': 121, 'y': 122},
|
||||
{'a': 2, 'x': 111, 'y': 112},
|
||||
{'a': 2, 'x': 121, 'y': 122}
|
||||
]
|
||||
|
||||
"""
|
||||
if not args:
|
||||
return []
|
||||
elif len(args) == 1:
|
||||
return args[0]
|
||||
|
||||
product_list = []
|
||||
for product_item_tuple in itertools.product(*args):
|
||||
product_item_dict = {}
|
||||
for item in product_item_tuple:
|
||||
product_item_dict.update(item)
|
||||
|
||||
product_list.append(product_item_dict)
|
||||
|
||||
return product_list
|
||||
|
||||
|
||||
def omit_long_data(body, omit_len=512):
|
||||
""" omit too long str/bytes
|
||||
"""
|
||||
@@ -224,64 +143,11 @@ def omit_long_data(body, omit_len=512):
|
||||
return omitted_body + appendix_str
|
||||
|
||||
|
||||
def dump_json_file(json_data: Union[dict, list], json_file_abs_path: str) -> None:
|
||||
""" dump json data to file
|
||||
"""
|
||||
|
||||
class PythonObjectEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
try:
|
||||
return super().default(self, obj)
|
||||
except TypeError:
|
||||
return str(obj)
|
||||
|
||||
file_foder_path = os.path.dirname(json_file_abs_path)
|
||||
if not os.path.isdir(file_foder_path):
|
||||
os.makedirs(file_foder_path)
|
||||
|
||||
try:
|
||||
with io.open(json_file_abs_path, "w", encoding="utf-8") as outfile:
|
||||
json.dump(
|
||||
json_data,
|
||||
outfile,
|
||||
indent=4,
|
||||
separators=(",", ":"),
|
||||
ensure_ascii=False,
|
||||
cls=PythonObjectEncoder,
|
||||
)
|
||||
|
||||
msg = f"dump file: {json_file_abs_path}"
|
||||
logger.info(msg)
|
||||
|
||||
except TypeError as ex:
|
||||
msg = f"Failed to dump json file: {json_file_abs_path}\nReason: {ex}"
|
||||
logger.error(msg)
|
||||
|
||||
|
||||
def prepare_log_file_abs_path(test_path: str, file_name: str) -> str:
|
||||
""" prepare dump json file absolute path.
|
||||
"""
|
||||
current_working_dir = os.getcwd()
|
||||
|
||||
if not test_path:
|
||||
# running passed in testcase/testsuite data structure
|
||||
dump_file_name = f"tests_mapping.{file_name}"
|
||||
dumped_json_file_abs_path = os.path.join(
|
||||
current_working_dir, "logs", dump_file_name
|
||||
)
|
||||
return dumped_json_file_abs_path
|
||||
|
||||
# both test_path and pwd_dir_path are absolute path
|
||||
logs_dir_path = os.path.join(current_working_dir, "logs")
|
||||
|
||||
if os.path.isdir(test_path):
|
||||
file_foder_path = os.path.join(logs_dir_path, test_path)
|
||||
dump_file_name = f"all.{file_name}"
|
||||
else:
|
||||
file_relative_folder_path, test_file = os.path.split(test_path)
|
||||
file_foder_path = os.path.join(logs_dir_path, file_relative_folder_path)
|
||||
test_file_name, _file_suffix = os.path.splitext(test_file)
|
||||
dump_file_name = f"{test_file_name}.{file_name}"
|
||||
|
||||
dumped_json_file_abs_path = os.path.join(file_foder_path, dump_file_name)
|
||||
return dumped_json_file_abs_path
|
||||
def get_platform():
|
||||
return {
|
||||
"httprunner_version": __version__,
|
||||
"python_version": "{} {}".format(
|
||||
platform.python_implementation(), platform.python_version()
|
||||
),
|
||||
"platform": platform.platform(),
|
||||
}
|
||||
|
||||
@@ -86,75 +86,6 @@ class TestUtils(unittest.TestCase):
|
||||
new_request_dict = utils.lower_dict_keys(request_dict)
|
||||
self.assertEqual(None, request_dict)
|
||||
|
||||
def test_deepcopy_dict(self):
|
||||
license_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(__file__)), "LICENSE"
|
||||
)
|
||||
data = {
|
||||
"a": 1,
|
||||
"b": [2, 4],
|
||||
"c": lambda x: x + 1,
|
||||
"d": open(license_path),
|
||||
"f": {"f1": {"a1": 2}, "f2": io.open(license_path, "rb"),},
|
||||
}
|
||||
new_data = utils.deepcopy_dict(data)
|
||||
data["a"] = 0
|
||||
self.assertEqual(new_data["a"], 1)
|
||||
data["f"]["f1"] = 123
|
||||
self.assertEqual(new_data["f"]["f1"], {"a1": 2})
|
||||
self.assertNotEqual(id(new_data["b"]), id(data["b"]))
|
||||
self.assertEqual(id(new_data["c"]), id(data["c"]))
|
||||
# self.assertEqual(id(new_data["d"]), id(data["d"]))
|
||||
|
||||
def test_cartesian_product_one(self):
|
||||
parameters_content_list = [[{"a": 1}, {"a": 2}]]
|
||||
product_list = utils.gen_cartesian_product(*parameters_content_list)
|
||||
self.assertEqual(product_list, [{"a": 1}, {"a": 2}])
|
||||
|
||||
def test_cartesian_product_multiple(self):
|
||||
parameters_content_list = [
|
||||
[{"a": 1}, {"a": 2}],
|
||||
[{"x": 111, "y": 112}, {"x": 121, "y": 122}],
|
||||
]
|
||||
product_list = utils.gen_cartesian_product(*parameters_content_list)
|
||||
self.assertEqual(
|
||||
product_list,
|
||||
[
|
||||
{"a": 1, "x": 111, "y": 112},
|
||||
{"a": 1, "x": 121, "y": 122},
|
||||
{"a": 2, "x": 111, "y": 112},
|
||||
{"a": 2, "x": 121, "y": 122},
|
||||
],
|
||||
)
|
||||
|
||||
def test_cartesian_product_empty(self):
|
||||
parameters_content_list = []
|
||||
product_list = utils.gen_cartesian_product(*parameters_content_list)
|
||||
self.assertEqual(product_list, [])
|
||||
|
||||
def test_print_info(self):
|
||||
info_mapping = {"a": 1, "t": (1, 2), "b": {"b1": 123}, "c": None, "d": [4, 5]}
|
||||
utils.print_info(info_mapping)
|
||||
|
||||
def test_prepare_dump_json_file_path_for_folder(self):
|
||||
# hrun tests/httpbin/a.b.c/ --save-tests
|
||||
test_path = os.path.join("tests", "httpbin", "a.b.c")
|
||||
self.assertEqual(
|
||||
utils.prepare_log_file_abs_path(test_path, "loaded.json"),
|
||||
os.path.join(os.getcwd(), "logs", "tests/httpbin/a.b.c/all.loaded.json"),
|
||||
)
|
||||
|
||||
def test_prepare_dump_json_file_path_for_file(self):
|
||||
# hrun tests/httpbin/a.b.c/rpc.yml --save-tests
|
||||
test_path = os.path.join("tests", "httpbin", "a.b.c", "rpc.yml")
|
||||
self.assertEqual(
|
||||
utils.prepare_log_file_abs_path(test_path, "loaded.json"),
|
||||
os.path.join(os.getcwd(), "logs", "tests/httpbin/a.b.c/rpc.loaded.json"),
|
||||
)
|
||||
|
||||
def test_prepare_dump_json_file_path_for_passed_testcase(self):
|
||||
test_path = ""
|
||||
self.assertEqual(
|
||||
utils.prepare_log_file_abs_path(test_path, "loaded.json"),
|
||||
os.path.join(os.getcwd(), "logs", "tests_mapping.loaded.json"),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user