init the project
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# YAML/JSON 差异对比工具
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
def compute_diff(doc_a: dict | str, doc_b: dict | str) -> dict:
|
||||
"""Compute diff between two dicts or YAML strings."""
|
||||
if isinstance(doc_a, str):
|
||||
try:
|
||||
doc_a = yaml.safe_load(doc_a) or {}
|
||||
except yaml.YAMLError:
|
||||
doc_a = {}
|
||||
if isinstance(doc_b, str):
|
||||
try:
|
||||
doc_b = yaml.safe_load(doc_b) or {}
|
||||
except yaml.YAMLError:
|
||||
doc_b = {}
|
||||
|
||||
added, removed, modified = _recursive_diff(doc_a, doc_b, "")
|
||||
return {
|
||||
"added": added,
|
||||
"removed": removed,
|
||||
"modified": modified,
|
||||
}
|
||||
|
||||
|
||||
def _recursive_diff(a: dict | list, b: dict | list, path: str) -> tuple[list, list, list]:
|
||||
added, removed, modified = [], [], []
|
||||
|
||||
if isinstance(a, dict) and isinstance(b, dict):
|
||||
all_keys = set(a.keys()) | set(b.keys())
|
||||
for key in sorted(all_keys):
|
||||
new_path = f"{path}.{key}" if path else key
|
||||
if key not in a:
|
||||
added.append({"path": new_path, "value": b[key]})
|
||||
elif key not in b:
|
||||
removed.append({"path": new_path, "value": a[key]})
|
||||
elif a[key] != b[key]:
|
||||
if isinstance(a[key], (dict, list)) and isinstance(b[key], (dict, list)):
|
||||
a_sub, r_sub, m_sub = _recursive_diff(a[key], b[key], new_path)
|
||||
added.extend(a_sub)
|
||||
removed.extend(r_sub)
|
||||
modified.extend(m_sub)
|
||||
else:
|
||||
modified.append({
|
||||
"path": new_path,
|
||||
"old_value": a[key],
|
||||
"new_value": b[key],
|
||||
})
|
||||
|
||||
elif isinstance(a, list) and isinstance(b, list):
|
||||
for i in range(max(len(a), len(b))):
|
||||
new_path = f"{path}[{i}]"
|
||||
if i >= len(a):
|
||||
added.append({"path": new_path, "value": b[i]})
|
||||
elif i >= len(b):
|
||||
removed.append({"path": new_path, "value": a[i]})
|
||||
elif a[i] != b[i]:
|
||||
if isinstance(a[i], (dict, list)) and isinstance(b[i], (dict, list)):
|
||||
a_sub, r_sub, m_sub = _recursive_diff(a[i], b[i], new_path)
|
||||
added.extend(a_sub)
|
||||
removed.extend(r_sub)
|
||||
modified.extend(m_sub)
|
||||
else:
|
||||
modified.append({
|
||||
"path": new_path,
|
||||
"old_value": a[i],
|
||||
"new_value": b[i],
|
||||
})
|
||||
|
||||
return added, removed, modified
|
||||
@@ -0,0 +1,29 @@
|
||||
# 导出格式化工具:将测试用例转换为 YAML/CSV/XMind 格式
|
||||
|
||||
import yaml
|
||||
import csv
|
||||
import io
|
||||
from typing import Any
|
||||
|
||||
|
||||
def to_yaml(cases: list[dict]) -> str:
|
||||
"""将用例集序列化为 YAML 字符串"""
|
||||
# TODO: Phase 4 按实际 IR schema 调整输出结构
|
||||
return yaml.dump(cases, allow_unicode=True, sort_keys=False)
|
||||
|
||||
|
||||
def to_csv(cases: list[dict]) -> str:
|
||||
"""将用例集序列化为 CSV 字符串"""
|
||||
if not cases:
|
||||
return ""
|
||||
output = io.StringIO()
|
||||
writer = csv.DictWriter(output, fieldnames=cases[0].keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(cases)
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
def to_xmind(cases: list[dict]) -> bytes:
|
||||
"""将用例集导出为 XMind 文件(占位,Phase 4 实现)"""
|
||||
# TODO: Phase 4 使用 xmind 库生成 .xmind 文件
|
||||
return b""
|
||||
@@ -0,0 +1,28 @@
|
||||
# 结构化日志工具
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from server.config import settings
|
||||
|
||||
|
||||
def setup_logger(name: str = "testflow") -> logging.Logger:
|
||||
logger = logging.getLogger(name)
|
||||
|
||||
if logger.handlers:
|
||||
return logger
|
||||
|
||||
logger.setLevel(logging.DEBUG if settings.DEBUG else logging.INFO)
|
||||
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setFormatter(
|
||||
logging.Formatter(
|
||||
"[%(asctime)s] %(levelname)s [%(name)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
)
|
||||
logger.addHandler(handler)
|
||||
return logger
|
||||
|
||||
|
||||
logger = setup_logger()
|
||||
Reference in New Issue
Block a user