pipeline-app/deploy/smoke/run_smoke.py

141 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""run_smoke.py — 增量部署冒泡测试执行器。
用法(在应用根目录,凭据走环境变量或参数,不落库):
SMOKE_USER=admin SMOKE_PASS=*** ./py3/bin/python deploy/smoke/run_smoke.py
./py3/bin/python deploy/smoke/run_smoke.py --base http://127.0.0.1:9090 --user admin --pass ***
./py3/bin/python deploy/smoke/run_smoke.py --https https://doit.opencomputing.cn # 走外网域名
退出码0=全部通过1=有用例失败。增量部署第3步验证必跑。
"""
import json
import os
import sys
import ssl
import urllib.request
import urllib.error
import urllib.parse
HERE = os.path.dirname(os.path.abspath(__file__))
CASES = os.path.join(HERE, "cases.json")
def req(opener, base, case, session_cookie, verify_ssl):
url = base.rstrip("/") + case["url"]
method = case.get("method", "GET").upper()
body = case.get("body")
data = None
headers = {}
if body is not None:
# 变量替换
b = {}
for k, v in body.items():
if isinstance(v, str):
v = v.replace("${SMOKE_USER}", os.environ.get("SMOKE_USER", ""))
v = v.replace("${SMOKE_PASS}", os.environ.get("SMOKE_PASS", ""))
b[k] = v
data = json.dumps(b).encode("utf-8")
headers["Content-Type"] = "application/json"
if case.get("auth") == "login" and session_cookie:
headers["Cookie"] = session_cookie
r = urllib.request.Request(url, data=data, method=method, headers=headers)
ctx = None
if url.startswith("https"):
ctx = ssl.create_default_context()
if not verify_ssl:
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
resp = opener.open(r, timeout=20, context=ctx) if ctx else opener.open(r, timeout=20)
status = resp.getcode()
text = resp.read().decode("utf-8", errors="replace")
return status, text, resp.headers, None
except urllib.error.HTTPError as e:
return e.code, (e.read() or b"").decode("utf-8", errors="replace"), e.headers, None
except Exception as e:
return 0, "", None, str(e)
def main():
base = "http://127.0.0.1:9090"
verify_ssl = True
args = sys.argv[1:]
if "--https" in args:
base = args[args.index("--https") + 1]
verify_ssl = False # 自签/内网域名常见
elif "--base" in args:
base = args[args.index("--base") + 1]
if "--user" in args:
os.environ["SMOKE_USER"] = args[args.index("--user") + 1]
if "--pass" in args:
os.environ["SMOKE_PASS"] = args[args.index("--pass") + 1]
cfg = json.load(open(CASES))
cases = cfg["cases"]
import http.cookiejar
cj = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
# 第一步:登录拿会话
session_cookie = None
login_case = None
for c in cases:
if c.get("auth") == "none" and c.get("method", "GET") == "POST" and "login" in c["url"]:
login_case = c
break
if login_case and os.environ.get("SMOKE_PASS"):
status, text, headers, err = req(opener, base, login_case, None, verify_ssl)
# 手动解析 Set-Cookiecookiejar 在 http:// 下会拒绝带 Secure 属性的 cookie
names = []
if headers is not None:
for hv in headers.get_all("Set-Cookie", []):
pair = hv.split(";", 1)[0].strip()
if "=" in pair:
names.append(pair)
session_cookie = "; ".join(names) if names else None
if status != 200 or not session_cookie:
print("[LOGIN-FAIL] 登录用例返回 %s cookie=%s,后续需登录的用例将跳过: %s"
% (status, bool(session_cookie), (err or text[:100])))
passed, failed, skipped = [], [], []
for c in cases:
cid, name = c["id"], c["name"]
if c.get("auth") == "login" and not session_cookie:
skipped.append(cid)
print(" SKIP %-4s %s(无登录态)" % (cid, name))
continue
status, text, headers, err = req(opener, base, c, session_cookie, verify_ssl)
exp = c.get("expect", {})
ok = True
reason = []
if err:
ok = False
reason.append("请求异常:" + err[:80])
if "status" in exp and status != exp["status"]:
ok = False
reason.append("状态 %s%s" % (status, exp["status"]))
if "contains" in exp and exp["contains"] not in text:
ok = False
reason.append("缺内容:%s" % exp["contains"][:20])
if "not_contains" in exp and exp["not_contains"] in text:
ok = False
reason.append("含禁止内容:%s" % exp["not_contains"][:20])
if ok:
passed.append(cid)
print(" PASS %-4s %s" % (cid, name))
else:
failed.append(cid)
print(" FAIL %-4s %s [%s]" % (cid, name, "; ".join(reason)))
print("\n══ 冒泡结果: %d 通过 / %d 失败 / %d 跳过(共 %d)══"
% (len(passed), len(failed), len(skipped), len(cases)))
sys.exit(0 if not failed else 1)
if __name__ == "__main__":
main()