aicortexpy 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aicortexpy/__init__.py +3 -0
- aicortexpy/__main__.py +5 -0
- aicortexpy/acp_client.py +559 -0
- aicortexpy/acp_conn.py +853 -0
- aicortexpy/acp_errors.py +107 -0
- aicortexpy/agent_env.py +88 -0
- aicortexpy/artifact_ws.py +233 -0
- aicortexpy/artifacts.py +67 -0
- aicortexpy/attachments.py +85 -0
- aicortexpy/auto_commit.py +106 -0
- aicortexpy/capabilities.py +92 -0
- aicortexpy/chat_acp_pool.py +321 -0
- aicortexpy/cli.py +133 -0
- aicortexpy/client.py +438 -0
- aicortexpy/codex_config.py +98 -0
- aicortexpy/config.py +298 -0
- aicortexpy/daemon.py +677 -0
- aicortexpy/data/default_skills/agent-memory/SKILL.md +67 -0
- aicortexpy/data/default_skills/aicorkit-atlassian/SKILL.md +144 -0
- aicortexpy/data/default_skills/aicorkit-graph/SKILL.md +145 -0
- aicortexpy/data/default_skills/aicorkit-outlook/SKILL.md +87 -0
- aicortexpy/data/default_skills/aicorkit-wxmsg/SKILL.md +96 -0
- aicortexpy/data/default_skills/aicortex-basics/SKILL.md +10 -0
- aicortexpy/data/default_skills/aicortex-bridge-cdp/SKILL.md +118 -0
- aicortexpy/data/default_skills/aicortex-cli-ops/SKILL.md +100 -0
- aicortexpy/data/default_skills/aicortex-graph/SKILL.md +30 -0
- aicortexpy/data/default_skills/graphify/SKILL.md +97 -0
- aicortexpy/data/default_skills/question-form/SKILL.md +162 -0
- aicortexpy/data/default_skills/test-report-generator/SKILL.md +179 -0
- aicortexpy/data/default_skills/test-report-generator/scripts/generate_cases.py +146 -0
- aicortexpy/data/default_skills/test-report-generator/scripts/generate_report.py +275 -0
- aicortexpy/design_seed.py +59 -0
- aicortexpy/diskusage.py +209 -0
- aicortexpy/execenv.py +264 -0
- aicortexpy/gc.py +286 -0
- aicortexpy/handoff_bundle.py +65 -0
- aicortexpy/health.py +178 -0
- aicortexpy/local_skills.py +157 -0
- aicortexpy/models.py +385 -0
- aicortexpy/openclaw_config.py +153 -0
- aicortexpy/pinned_workdir.py +60 -0
- aicortexpy/platform_info.py +61 -0
- aicortexpy/poisoned.py +44 -0
- aicortexpy/process.py +168 -0
- aicortexpy/procutil.py +61 -0
- aicortexpy/prompt.py +235 -0
- aicortexpy/repo.py +462 -0
- aicortexpy/runner.py +969 -0
- aicortexpy/runtime_config.py +540 -0
- aicortexpy/sandbox.py +264 -0
- aicortexpy/stream.py +422 -0
- aicortexpy/terminal.py +147 -0
- aicortexpy/tunnel.py +82 -0
- aicortexpy/wakeup.py +222 -0
- aicortexpy-0.1.0.dist-info/METADATA +106 -0
- aicortexpy-0.1.0.dist-info/RECORD +58 -0
- aicortexpy-0.1.0.dist-info/WHEEL +5 -0
- aicortexpy-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# @AI_GENERATED
|
|
2
|
+
"""
|
|
3
|
+
生成 Excel 测试用例清单(.xlsx),按模块分 Sheet。
|
|
4
|
+
|
|
5
|
+
依赖: pip install openpyxl
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
python generate_cases.py --output "测试用例.xlsx"
|
|
9
|
+
|
|
10
|
+
复用方式:
|
|
11
|
+
from generate_cases import build_workbook
|
|
12
|
+
build_workbook(modules, "用例.xlsx")
|
|
13
|
+
|
|
14
|
+
modules 结构: {"Sheet名(模块)": [用例dict, ...], ...}
|
|
15
|
+
用例 dict 字段:
|
|
16
|
+
id 用例编号(多步骤用例仅首行填,续行用 "" 表示延续)
|
|
17
|
+
name 用例名称
|
|
18
|
+
module 所属模块(路径式,如 /需求/模块/子模块/场景)
|
|
19
|
+
precondition 前置条件(可空)
|
|
20
|
+
remark 备注(可空)
|
|
21
|
+
step 步骤描述
|
|
22
|
+
expected 预期结果
|
|
23
|
+
level 用例等级 P0/P1/P2/P3
|
|
24
|
+
owner 责任人(ID)(可选)
|
|
25
|
+
"""
|
|
26
|
+
import argparse
|
|
27
|
+
|
|
28
|
+
import openpyxl
|
|
29
|
+
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
|
|
30
|
+
from openpyxl.utils import get_column_letter
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
HEADERS = ["ID", "用例名称", "所属模块", "前置条件", "备注",
|
|
34
|
+
"步骤描述", "预期结果", "用例等级", "责任人(ID)"]
|
|
35
|
+
FIELDS = ["id", "name", "module", "precondition", "remark",
|
|
36
|
+
"step", "expected", "level", "owner"]
|
|
37
|
+
# 列宽(字符数)
|
|
38
|
+
WIDTHS = [10, 24, 34, 20, 16, 40, 40, 10, 14]
|
|
39
|
+
|
|
40
|
+
HEADER_FILL = PatternFill("solid", fgColor="4472C4")
|
|
41
|
+
HEADER_FONT = Font(bold=True, color="FFFFFF", size=11)
|
|
42
|
+
BODY_FONT = Font(size=10)
|
|
43
|
+
THIN = Side(style="thin", color="BFBFBF")
|
|
44
|
+
BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
|
|
45
|
+
WRAP_TOP = Alignment(wrap_text=True, vertical="top")
|
|
46
|
+
HEADER_ALIGN = Alignment(horizontal="center", vertical="center")
|
|
47
|
+
|
|
48
|
+
# Excel Sheet 名非法字符与长度限制
|
|
49
|
+
_INVALID = set(r'[]:*?/\\')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _safe_sheet_name(name, used):
|
|
53
|
+
name = "".join("_" if c in _INVALID else c for c in str(name))[:31] or "Sheet"
|
|
54
|
+
base, i = name, 1
|
|
55
|
+
while name in used:
|
|
56
|
+
suffix = f"_{i}"
|
|
57
|
+
name = base[:31 - len(suffix)] + suffix
|
|
58
|
+
i += 1
|
|
59
|
+
used.add(name)
|
|
60
|
+
return name
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _write_sheet(ws, cases):
|
|
64
|
+
# 表头
|
|
65
|
+
for col, title in enumerate(HEADERS, start=1):
|
|
66
|
+
c = ws.cell(row=1, column=col, value=title)
|
|
67
|
+
c.fill = HEADER_FILL
|
|
68
|
+
c.font = HEADER_FONT
|
|
69
|
+
c.alignment = HEADER_ALIGN
|
|
70
|
+
c.border = BORDER
|
|
71
|
+
# 正文
|
|
72
|
+
for r, case in enumerate(cases, start=2):
|
|
73
|
+
for col, field in enumerate(FIELDS, start=1):
|
|
74
|
+
val = case.get(field, "")
|
|
75
|
+
c = ws.cell(row=r, column=col, value=val)
|
|
76
|
+
c.font = BODY_FONT
|
|
77
|
+
c.alignment = WRAP_TOP
|
|
78
|
+
c.border = BORDER
|
|
79
|
+
# 列宽
|
|
80
|
+
for col, w in enumerate(WIDTHS, start=1):
|
|
81
|
+
ws.column_dimensions[get_column_letter(col)].width = w
|
|
82
|
+
# 冻结表头
|
|
83
|
+
ws.freeze_panes = "A2"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def build_workbook(modules, output_path):
|
|
87
|
+
wb = openpyxl.Workbook()
|
|
88
|
+
wb.remove(wb.active)
|
|
89
|
+
used = set()
|
|
90
|
+
for sheet_name, cases in modules.items():
|
|
91
|
+
ws = wb.create_sheet(_safe_sheet_name(sheet_name, used))
|
|
92
|
+
_write_sheet(ws, cases)
|
|
93
|
+
if not wb.sheetnames: # 防止空工作簿
|
|
94
|
+
wb.create_sheet("用例")
|
|
95
|
+
wb.save(output_path)
|
|
96
|
+
return output_path
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ----------------------------- 示例数据 -----------------------------
|
|
100
|
+
SAMPLE_MODULES = {
|
|
101
|
+
"三方登录注册和绑定": [
|
|
102
|
+
{
|
|
103
|
+
"id": 122946, "name": "取消绑定",
|
|
104
|
+
"module": "/需求/三方登录/APP端/账号绑定/功能",
|
|
105
|
+
"precondition": "", "remark": "",
|
|
106
|
+
"step": "用户进入绑定页面,选择绑定第三方账号,进入第三方账号后点击取消",
|
|
107
|
+
"expected": "返回到 app 绑定页", "level": "P1", "owner": "",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"id": 122944, "name": "重复绑定",
|
|
111
|
+
"module": "/需求/三方登录/APP端/账号绑定/功能",
|
|
112
|
+
"precondition": "", "remark": "",
|
|
113
|
+
"step": "用一个新的未绑定第三方账号去绑定已绑定的 app 账号",
|
|
114
|
+
"expected": "绑定失败并给出相应提示", "level": "P1", "owner": "",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
"电价设置": [
|
|
118
|
+
# 多步骤用例示例:首行填 ID,续行 ID/名称/模块/前置留空
|
|
119
|
+
{
|
|
120
|
+
"id": 122737, "name": "设置全时段电价为 1.99",
|
|
121
|
+
"module": "/需求/电价设置/按日期设置电价",
|
|
122
|
+
"precondition": "", "remark": "",
|
|
123
|
+
"step": "点击电价设置入口", "expected": "进入电价设置页",
|
|
124
|
+
"level": "P1", "owner": "",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "", "name": "", "module": "", "precondition": "", "remark": "",
|
|
128
|
+
"step": "保持默认时间,只设置电价点击保存", "expected": "弹出相应提示",
|
|
129
|
+
"level": "", "owner": "",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"id": "", "name": "", "module": "", "precondition": "", "remark": "",
|
|
133
|
+
"step": "点击确认", "expected": "电价保存成功",
|
|
134
|
+
"level": "", "owner": "",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
parser = argparse.ArgumentParser(description="生成 Excel 测试用例清单")
|
|
142
|
+
parser.add_argument("--output", default="测试用例.xlsx", help="输出文件路径")
|
|
143
|
+
args = parser.parse_args()
|
|
144
|
+
path = build_workbook(SAMPLE_MODULES, args.output)
|
|
145
|
+
print(f"已生成测试用例清单: {path}")
|
|
146
|
+
# @AI_GENERATED: end
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# @AI_GENERATED
|
|
2
|
+
"""
|
|
3
|
+
生成 Word 测试报告(.docx)。
|
|
4
|
+
|
|
5
|
+
依赖: pip install python-docx
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
python generate_report.py --output "功能测试报告.docx"
|
|
9
|
+
|
|
10
|
+
复用方式:
|
|
11
|
+
from generate_report import build_report
|
|
12
|
+
build_report(data, "报告.docx")
|
|
13
|
+
|
|
14
|
+
data 结构见文件底部 SAMPLE_DATA。AGENT 应将真实数据填入同样结构后调用 build_report。
|
|
15
|
+
关键结论字段(缺陷统计、修复率、结论)必须来自真实数据,不得编造。
|
|
16
|
+
"""
|
|
17
|
+
import argparse
|
|
18
|
+
|
|
19
|
+
from docx import Document
|
|
20
|
+
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
21
|
+
from docx.enum.table import WD_TABLE_ALIGNMENT
|
|
22
|
+
from docx.shared import Pt, RGBColor
|
|
23
|
+
from docx.oxml.ns import qn
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ----------------------------- 样式辅助 -----------------------------
|
|
27
|
+
def _set_cell_bg(cell, hex_color):
|
|
28
|
+
"""设置单元格背景色。"""
|
|
29
|
+
tc_pr = cell._tc.get_or_add_tcPr()
|
|
30
|
+
shd = tc_pr.makeelement(qn("w:shd"), {
|
|
31
|
+
qn("w:val"): "clear",
|
|
32
|
+
qn("w:color"): "auto",
|
|
33
|
+
qn("w:fill"): hex_color,
|
|
34
|
+
})
|
|
35
|
+
tc_pr.append(shd)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _add_table(doc, headers, rows, header_bg="4472C4", header_color=(255, 255, 255)):
|
|
39
|
+
"""通用建表:首行表头加背景与加粗。rows 为二维列表。"""
|
|
40
|
+
table = doc.add_table(rows=1, cols=len(headers))
|
|
41
|
+
table.style = "Table Grid"
|
|
42
|
+
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
43
|
+
|
|
44
|
+
hdr = table.rows[0].cells
|
|
45
|
+
for i, text in enumerate(headers):
|
|
46
|
+
hdr[i].text = ""
|
|
47
|
+
p = hdr[i].paragraphs[0]
|
|
48
|
+
run = p.add_run(str(text))
|
|
49
|
+
run.bold = True
|
|
50
|
+
run.font.color.rgb = RGBColor(*header_color)
|
|
51
|
+
_set_cell_bg(hdr[i], header_bg)
|
|
52
|
+
|
|
53
|
+
for row in rows:
|
|
54
|
+
cells = table.add_row().cells
|
|
55
|
+
for i, val in enumerate(row):
|
|
56
|
+
cells[i].text = "" if val is None else str(val)
|
|
57
|
+
doc.add_paragraph()
|
|
58
|
+
return table
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _add_centered(doc, text, size=None, bold=False, space_after=6):
|
|
62
|
+
p = doc.add_paragraph()
|
|
63
|
+
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
64
|
+
run = p.add_run(text)
|
|
65
|
+
run.bold = bold
|
|
66
|
+
if size:
|
|
67
|
+
run.font.size = Pt(size)
|
|
68
|
+
p.paragraph_format.space_after = Pt(space_after)
|
|
69
|
+
return p
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ----------------------------- 报告构建 -----------------------------
|
|
73
|
+
def build_report(data, output_path):
|
|
74
|
+
doc = Document()
|
|
75
|
+
|
|
76
|
+
# ---------- 封面 ----------
|
|
77
|
+
for _ in range(6):
|
|
78
|
+
doc.add_paragraph()
|
|
79
|
+
_add_centered(doc, data["system_name"], size=26, bold=True, space_after=12)
|
|
80
|
+
_add_centered(doc, data.get("stage", ""), size=18, bold=True, space_after=12)
|
|
81
|
+
_add_centered(doc, "测试报告", size=22, bold=True, space_after=48)
|
|
82
|
+
for _ in range(6):
|
|
83
|
+
doc.add_paragraph()
|
|
84
|
+
_add_centered(doc, data.get("org", ""), size=14)
|
|
85
|
+
_add_centered(doc, data.get("date", ""), size=14)
|
|
86
|
+
doc.add_page_break()
|
|
87
|
+
|
|
88
|
+
# ---------- 文档修订信息 ----------
|
|
89
|
+
doc.add_heading("文档修订信息", level=1)
|
|
90
|
+
_add_table(
|
|
91
|
+
doc,
|
|
92
|
+
["版本", "修改内容描述", "修改人", "日期", "审批人"],
|
|
93
|
+
data.get("revisions", []),
|
|
94
|
+
)
|
|
95
|
+
doc.add_page_break()
|
|
96
|
+
|
|
97
|
+
# ---------- 目录 ----------
|
|
98
|
+
doc.add_heading("目录", level=1)
|
|
99
|
+
toc = doc.add_paragraph()
|
|
100
|
+
run = toc.add_run()
|
|
101
|
+
fld = run._r.makeelement(qn("w:fldSimple"), {qn("w:instr"): r"TOC \o \"1-3\" \h \z \u"})
|
|
102
|
+
placeholder = fld.makeelement(qn("w:r"), {})
|
|
103
|
+
t = placeholder.makeelement(qn("w:t"), {})
|
|
104
|
+
t.text = "(打开文档后右键“更新域”生成目录)"
|
|
105
|
+
placeholder.append(t)
|
|
106
|
+
fld.append(placeholder)
|
|
107
|
+
run._r.append(fld)
|
|
108
|
+
doc.add_page_break()
|
|
109
|
+
|
|
110
|
+
# ---------- 1. 概述 ----------
|
|
111
|
+
doc.add_heading("1. 概述", level=1)
|
|
112
|
+
doc.add_heading("1.1 测试目的", level=2)
|
|
113
|
+
doc.add_paragraph(data["overview"]["purpose"])
|
|
114
|
+
|
|
115
|
+
doc.add_heading("1.2 测试范围", level=2)
|
|
116
|
+
_add_table(
|
|
117
|
+
doc,
|
|
118
|
+
["#", "模块", "核心功能"],
|
|
119
|
+
[[i + 1, m["module"], m["features"]] for i, m in enumerate(data["scope"])],
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
doc.add_heading("1.3 参考资料", level=2)
|
|
123
|
+
for ref in data["overview"].get("references", []):
|
|
124
|
+
doc.add_paragraph(ref, style="List Bullet")
|
|
125
|
+
|
|
126
|
+
# ---------- 2. 测试内容 ----------
|
|
127
|
+
doc.add_heading("2. 测试内容", level=1)
|
|
128
|
+
|
|
129
|
+
doc.add_heading("2.1 测试版本", level=2)
|
|
130
|
+
_add_table(
|
|
131
|
+
doc,
|
|
132
|
+
["软件名称及组织", "软件版本"],
|
|
133
|
+
[[v["name"], v["version"]] for v in data["versions"]],
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
doc.add_heading("2.2 测试环境", level=2)
|
|
137
|
+
doc.add_paragraph("环境地址:")
|
|
138
|
+
_add_table(
|
|
139
|
+
doc,
|
|
140
|
+
["环境", "地址"],
|
|
141
|
+
[[e["env"], e["addr"]] for e in data["environment"].get("envs", [])],
|
|
142
|
+
)
|
|
143
|
+
doc.add_paragraph("移动端手机型号以及系统:")
|
|
144
|
+
_add_table(
|
|
145
|
+
doc,
|
|
146
|
+
["系统", "型号", "版本"],
|
|
147
|
+
[[d["os"], d["model"], d["version"]] for d in data["environment"].get("mobile", [])],
|
|
148
|
+
)
|
|
149
|
+
doc.add_paragraph("Web端浏览器版本:")
|
|
150
|
+
_add_table(
|
|
151
|
+
doc,
|
|
152
|
+
["浏览器", "版本"],
|
|
153
|
+
[[b["browser"], b["version"]] for b in data["environment"].get("browsers", [])],
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
doc.add_heading("2.3 测试执行", level=2)
|
|
157
|
+
doc.add_heading("2.3.1 功能测试及结果", level=3)
|
|
158
|
+
doc.add_paragraph("核心测试点结果如下:")
|
|
159
|
+
_add_table(
|
|
160
|
+
doc,
|
|
161
|
+
["#", "模块", "核心功能", "测试结果"],
|
|
162
|
+
[[i + 1, r["module"], r["features"], r["result"]]
|
|
163
|
+
for i, r in enumerate(data["function_results"])],
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
doc.add_heading("2.3.2 集成测试及结果", level=3)
|
|
167
|
+
doc.add_paragraph("核心测试点结果如下:")
|
|
168
|
+
_add_table(
|
|
169
|
+
doc,
|
|
170
|
+
["ID", "所属模块", "核心测试点", "测试结果"],
|
|
171
|
+
[[r["id"], r["module"], r["point"], r["result"]]
|
|
172
|
+
for r in data.get("integration_results", [])],
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
doc.add_heading("2.3.3 详细测试用例", level=3)
|
|
176
|
+
doc.add_paragraph(
|
|
177
|
+
data.get("detail_cases_note",
|
|
178
|
+
"详细测试用例见配套附件 Excel 用例清单。")
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# ---------- 3. 测试结论 ----------
|
|
182
|
+
doc.add_heading("3. 测试结论", level=1)
|
|
183
|
+
doc.add_heading("3.1 测试结果", level=2)
|
|
184
|
+
conclusion = data["conclusion"]
|
|
185
|
+
doc.add_paragraph(conclusion["verdict"])
|
|
186
|
+
for para in conclusion.get("summary", []):
|
|
187
|
+
doc.add_paragraph(para)
|
|
188
|
+
doc.add_paragraph("过程缺陷分析如下:")
|
|
189
|
+
d = conclusion["defects"]
|
|
190
|
+
_add_table(
|
|
191
|
+
doc,
|
|
192
|
+
["问题总数", "严重问题数", "中等问题数", "较轻问题",
|
|
193
|
+
"未解决问题数", "问题解决率", "严重问题比率"],
|
|
194
|
+
[[d["total"], d["critical"], d["medium"], d["minor"],
|
|
195
|
+
d["unresolved"], d["resolve_rate"], d["critical_rate"]]],
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
doc.add_heading("3.2 遗留问题列表", level=2)
|
|
199
|
+
_add_table(
|
|
200
|
+
doc,
|
|
201
|
+
["模块", "问题描述", "说明"],
|
|
202
|
+
[[i["module"], i["desc"], i["note"]] for i in data.get("open_issues", [])],
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
doc.save(output_path)
|
|
206
|
+
return output_path
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ----------------------------- 示例数据 -----------------------------
|
|
210
|
+
SAMPLE_DATA = {
|
|
211
|
+
"system_name": "示例平台",
|
|
212
|
+
"stage": "P2阶段",
|
|
213
|
+
"org": "示例科技有限公司",
|
|
214
|
+
"date": "2024年07月08日",
|
|
215
|
+
"revisions": [
|
|
216
|
+
["V1.0", "初始编制", "张三", "2024/7/08", "李四"],
|
|
217
|
+
],
|
|
218
|
+
"overview": {
|
|
219
|
+
"purpose": "本次报告为示例平台 P2 阶段功能测试报告。测试目的在于验证测试环境"
|
|
220
|
+
"移动端及 web 端部分新增功能以及原有功能的正常使用。",
|
|
221
|
+
"references": ["产品规格说明书.doc", "测试计划.doc"],
|
|
222
|
+
},
|
|
223
|
+
"scope": [
|
|
224
|
+
{"module": "三方登录和绑定", "features": "绑定三方账号 / 登录 / 解绑"},
|
|
225
|
+
{"module": "消息中心", "features": "公告接收 / 已读消息"},
|
|
226
|
+
],
|
|
227
|
+
"versions": [
|
|
228
|
+
{"name": "前端", "version": "tag_1.0.8_2024070401"},
|
|
229
|
+
{"name": "后端", "version": "tag_1.2.8_240708"},
|
|
230
|
+
],
|
|
231
|
+
"environment": {
|
|
232
|
+
"envs": [
|
|
233
|
+
{"env": "功能测试环境", "addr": "https://example.com/#/login"},
|
|
234
|
+
],
|
|
235
|
+
"mobile": [
|
|
236
|
+
{"os": "iOS", "model": "iPhone 14 Pro", "version": "17.5.1"},
|
|
237
|
+
{"os": "Android", "model": "Galaxy A53 5G", "version": "Android 13"},
|
|
238
|
+
],
|
|
239
|
+
"browsers": [
|
|
240
|
+
{"browser": "Chrome", "version": "126.0.6478.127(64 位)"},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
"function_results": [
|
|
244
|
+
{"module": "三方登录和绑定", "features": "绑定 / 登录 / 解绑", "result": "Pass"},
|
|
245
|
+
{"module": "消息中心", "features": "公告接收 / 已读", "result": "Pass"},
|
|
246
|
+
],
|
|
247
|
+
"integration_results": [
|
|
248
|
+
{"id": 1, "module": "公司管理", "point": "创建公司", "result": "Pass"},
|
|
249
|
+
],
|
|
250
|
+
"detail_cases_note": "详细测试用例见配套附件《示例-测试用例.xlsx》。",
|
|
251
|
+
"conclusion": {
|
|
252
|
+
"verdict": "测试通过。",
|
|
253
|
+
"summary": [
|
|
254
|
+
"本次执行了一轮功能测试、一轮集成测试和两轮回归测试,"
|
|
255
|
+
"共执行新增用例约 730 条,回归用例约 300 条,发现缺陷 110 个。",
|
|
256
|
+
"所有严重及中等缺陷均已修复,整体修复率 98%,达到上线标准。",
|
|
257
|
+
],
|
|
258
|
+
"defects": {
|
|
259
|
+
"total": 110, "critical": 8, "medium": 65, "minor": 37,
|
|
260
|
+
"unresolved": 2, "resolve_rate": "98%", "critical_rate": "7%",
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
"open_issues": [
|
|
264
|
+
{"module": "建站管理", "desc": "欧洲地址建站时区显示错误", "note": "下一版本修复"},
|
|
265
|
+
],
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
if __name__ == "__main__":
|
|
270
|
+
parser = argparse.ArgumentParser(description="生成 Word 测试报告")
|
|
271
|
+
parser.add_argument("--output", default="功能测试报告.docx", help="输出文件路径")
|
|
272
|
+
args = parser.parse_args()
|
|
273
|
+
path = build_report(SAMPLE_DATA, args.output)
|
|
274
|
+
print(f"已生成测试报告: {path}")
|
|
275
|
+
# @AI_GENERATED: end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Seed design example bundles into task workdirs (Go design.SeedExample*)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import shutil
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
log = logging.getLogger("aicortexpy.design_seed")
|
|
11
|
+
|
|
12
|
+
MARKER = ".aicortex-design-example"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def seed_example_workdir(
|
|
16
|
+
work_dir: Path,
|
|
17
|
+
example_id: str,
|
|
18
|
+
bundles: list[dict[str, Any]],
|
|
19
|
+
*,
|
|
20
|
+
design_studio: bool = False,
|
|
21
|
+
) -> None:
|
|
22
|
+
example_id = (example_id or "").strip()
|
|
23
|
+
if not example_id:
|
|
24
|
+
return
|
|
25
|
+
target = work_dir / "design" if design_studio else work_dir
|
|
26
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
marker = target / MARKER
|
|
28
|
+
if marker.is_file() and marker.read_text(encoding="utf-8").strip() == example_id:
|
|
29
|
+
log.debug("design example already seeded id=%s", example_id)
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
for b in bundles:
|
|
33
|
+
if not isinstance(b, dict):
|
|
34
|
+
continue
|
|
35
|
+
src = Path(str(b.get("src") or ""))
|
|
36
|
+
dest_rel = str(b.get("dest") or "").lstrip("/\\")
|
|
37
|
+
if not src.is_file() and not src.is_dir():
|
|
38
|
+
log.warning("design seed skip missing src=%s", src)
|
|
39
|
+
continue
|
|
40
|
+
if not dest_rel or ".." in dest_rel.split("/"):
|
|
41
|
+
log.warning("design seed skip unsafe dest=%s", dest_rel)
|
|
42
|
+
continue
|
|
43
|
+
dest = target / dest_rel
|
|
44
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
try:
|
|
46
|
+
if src.is_dir():
|
|
47
|
+
if dest.exists():
|
|
48
|
+
shutil.rmtree(dest)
|
|
49
|
+
shutil.copytree(src, dest)
|
|
50
|
+
else:
|
|
51
|
+
shutil.copy2(src, dest)
|
|
52
|
+
except OSError as e:
|
|
53
|
+
log.warning("design seed copy failed %s -> %s: %s", src, dest, e)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
marker.write_text(example_id, encoding="utf-8")
|
|
57
|
+
except OSError as e:
|
|
58
|
+
log.warning("design seed marker failed: %s", e)
|
|
59
|
+
log.info("design example seeded id=%s dest=%s", example_id, target)
|
aicortexpy/diskusage.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Read-only workspace disk usage scan (parity with Go diskusage.go)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import asdict, dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
DEFAULT_ARTIFACT_PATTERNS = ("node_modules", ".next", ".turbo", "dist", "build", "__pycache__")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class TaskDiskUsage:
|
|
16
|
+
workspace_id: str
|
|
17
|
+
workspace_short: str
|
|
18
|
+
task_short: str
|
|
19
|
+
path: str
|
|
20
|
+
kind: str
|
|
21
|
+
parent_status: str
|
|
22
|
+
age_seconds: int
|
|
23
|
+
size_bytes: int
|
|
24
|
+
artifact_size_bytes: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class WorkspaceDiskUsage:
|
|
29
|
+
workspace_id: str
|
|
30
|
+
workspace_short: str
|
|
31
|
+
task_count: int
|
|
32
|
+
size_bytes: int
|
|
33
|
+
artifact_size_bytes: int
|
|
34
|
+
artifact_ratio: float
|
|
35
|
+
oldest_age_seconds: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class DiskUsageReport:
|
|
40
|
+
workspaces_root: str
|
|
41
|
+
generated_at: str
|
|
42
|
+
artifact_patterns: list[str]
|
|
43
|
+
tasks: list[TaskDiskUsage] = field(default_factory=list)
|
|
44
|
+
workspaces: list[WorkspaceDiskUsage] = field(default_factory=list)
|
|
45
|
+
total_task_count: int = 0
|
|
46
|
+
total_workspace_count: int = 0
|
|
47
|
+
total_size_bytes: int = 0
|
|
48
|
+
total_artifact_size_bytes: int = 0
|
|
49
|
+
total_artifact_ratio: float = 0.0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def scan_disk_usage(
|
|
53
|
+
workspaces_root: Path | str,
|
|
54
|
+
artifact_patterns: list[str] | None = None,
|
|
55
|
+
) -> DiskUsageReport:
|
|
56
|
+
root = Path(workspaces_root)
|
|
57
|
+
patterns = _pattern_set(artifact_patterns or list(DEFAULT_ARTIFACT_PATTERNS))
|
|
58
|
+
report = DiskUsageReport(
|
|
59
|
+
workspaces_root=str(root),
|
|
60
|
+
generated_at=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
61
|
+
artifact_patterns=sorted(patterns),
|
|
62
|
+
)
|
|
63
|
+
if not root.is_dir():
|
|
64
|
+
return report
|
|
65
|
+
|
|
66
|
+
ws_agg: dict[str, WorkspaceDiskUsage] = {}
|
|
67
|
+
now = time.time()
|
|
68
|
+
|
|
69
|
+
for ws_entry in root.iterdir():
|
|
70
|
+
if not ws_entry.is_dir() or ws_entry.name == ".repos":
|
|
71
|
+
continue
|
|
72
|
+
ws_id = ws_entry.name
|
|
73
|
+
for task_entry in ws_entry.iterdir():
|
|
74
|
+
if not task_entry.is_dir():
|
|
75
|
+
continue
|
|
76
|
+
# Skip pinned projects tree at workspace/projects/
|
|
77
|
+
if task_entry.name == "projects":
|
|
78
|
+
continue
|
|
79
|
+
usage = _task_usage(task_entry, ws_id, patterns, now)
|
|
80
|
+
report.tasks.append(usage)
|
|
81
|
+
report.total_size_bytes += usage.size_bytes
|
|
82
|
+
report.total_artifact_size_bytes += usage.artifact_size_bytes
|
|
83
|
+
agg = ws_agg.get(ws_id)
|
|
84
|
+
if agg is None:
|
|
85
|
+
agg = WorkspaceDiskUsage(
|
|
86
|
+
workspace_id=ws_id,
|
|
87
|
+
workspace_short=ws_id[:8],
|
|
88
|
+
task_count=0,
|
|
89
|
+
size_bytes=0,
|
|
90
|
+
artifact_size_bytes=0,
|
|
91
|
+
artifact_ratio=0.0,
|
|
92
|
+
oldest_age_seconds=usage.age_seconds,
|
|
93
|
+
)
|
|
94
|
+
ws_agg[ws_id] = agg
|
|
95
|
+
agg.task_count += 1
|
|
96
|
+
agg.size_bytes += usage.size_bytes
|
|
97
|
+
agg.artifact_size_bytes += usage.artifact_size_bytes
|
|
98
|
+
if usage.age_seconds > agg.oldest_age_seconds:
|
|
99
|
+
agg.oldest_age_seconds = usage.age_seconds
|
|
100
|
+
|
|
101
|
+
workspaces = list(ws_agg.values())
|
|
102
|
+
for ws in workspaces:
|
|
103
|
+
ws.artifact_ratio = (
|
|
104
|
+
(ws.artifact_size_bytes / ws.size_bytes) if ws.size_bytes else 0.0
|
|
105
|
+
)
|
|
106
|
+
workspaces.sort(key=lambda w: w.size_bytes, reverse=True)
|
|
107
|
+
report.workspaces = workspaces
|
|
108
|
+
report.total_task_count = len(report.tasks)
|
|
109
|
+
report.total_workspace_count = len(workspaces)
|
|
110
|
+
report.total_artifact_ratio = (
|
|
111
|
+
(report.total_artifact_size_bytes / report.total_size_bytes)
|
|
112
|
+
if report.total_size_bytes
|
|
113
|
+
else 0.0
|
|
114
|
+
)
|
|
115
|
+
report.tasks.sort(key=lambda t: t.size_bytes, reverse=True)
|
|
116
|
+
return report
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def report_to_dict(report: DiskUsageReport) -> dict:
|
|
120
|
+
return asdict(report)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def format_report_table(report: DiskUsageReport, *, top: int = 20) -> str:
|
|
124
|
+
lines = [
|
|
125
|
+
f"workspaces_root: {report.workspaces_root}",
|
|
126
|
+
f"total: tasks={report.total_task_count} workspaces={report.total_workspace_count} "
|
|
127
|
+
f"size={_fmt_bytes(report.total_size_bytes)} "
|
|
128
|
+
f"artifacts={_fmt_bytes(report.total_artifact_size_bytes)} "
|
|
129
|
+
f"({report.total_artifact_ratio:.0%})",
|
|
130
|
+
"",
|
|
131
|
+
f"{'SIZE':>10} {'ART':>10} {'AGE':>8} PATH",
|
|
132
|
+
]
|
|
133
|
+
for t in report.tasks[: max(1, top)]:
|
|
134
|
+
lines.append(
|
|
135
|
+
f"{_fmt_bytes(t.size_bytes):>10} {_fmt_bytes(t.artifact_size_bytes):>10} "
|
|
136
|
+
f"{_fmt_age(t.age_seconds):>8} {t.path}"
|
|
137
|
+
)
|
|
138
|
+
return "\n".join(lines)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _pattern_set(patterns: list[str]) -> set[str]:
|
|
142
|
+
out: set[str] = set()
|
|
143
|
+
for p in patterns:
|
|
144
|
+
name = Path(str(p)).name.strip()
|
|
145
|
+
if name and "/" not in name and "\\" not in name and name not in (".", ".."):
|
|
146
|
+
out.add(name)
|
|
147
|
+
return out
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _task_usage(task_dir: Path, ws_id: str, patterns: set[str], now: float) -> TaskDiskUsage:
|
|
151
|
+
size = 0
|
|
152
|
+
art = 0
|
|
153
|
+
for path in task_dir.rglob("*"):
|
|
154
|
+
try:
|
|
155
|
+
if path.is_symlink() or not path.is_file():
|
|
156
|
+
continue
|
|
157
|
+
if ".git" in path.parts:
|
|
158
|
+
continue
|
|
159
|
+
n = path.stat().st_size
|
|
160
|
+
except OSError:
|
|
161
|
+
continue
|
|
162
|
+
size += n
|
|
163
|
+
if any(part in patterns for part in path.parts):
|
|
164
|
+
art += n
|
|
165
|
+
kind = "unknown"
|
|
166
|
+
parent_status = ""
|
|
167
|
+
meta = task_dir / ".gc_meta.json"
|
|
168
|
+
if meta.is_file():
|
|
169
|
+
try:
|
|
170
|
+
data = json.loads(meta.read_text(encoding="utf-8"))
|
|
171
|
+
kind = str(data.get("kind") or kind)
|
|
172
|
+
parent_status = str(data.get("parent_status") or "")
|
|
173
|
+
except (OSError, json.JSONDecodeError):
|
|
174
|
+
pass
|
|
175
|
+
try:
|
|
176
|
+
mtime = task_dir.stat().st_mtime
|
|
177
|
+
except OSError:
|
|
178
|
+
mtime = now
|
|
179
|
+
age = max(0, int(now - mtime))
|
|
180
|
+
return TaskDiskUsage(
|
|
181
|
+
workspace_id=ws_id,
|
|
182
|
+
workspace_short=ws_id[:8],
|
|
183
|
+
task_short=task_dir.name[:8],
|
|
184
|
+
path=str(task_dir),
|
|
185
|
+
kind=kind,
|
|
186
|
+
parent_status=parent_status,
|
|
187
|
+
age_seconds=age,
|
|
188
|
+
size_bytes=size,
|
|
189
|
+
artifact_size_bytes=art,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _fmt_bytes(n: int) -> str:
|
|
194
|
+
x = float(n)
|
|
195
|
+
for unit in ("B", "KB", "MB", "GB", "TB"):
|
|
196
|
+
if abs(x) < 1024 or unit == "TB":
|
|
197
|
+
if unit == "B":
|
|
198
|
+
return f"{int(x)}{unit}"
|
|
199
|
+
return f"{x:.1f}{unit}"
|
|
200
|
+
x /= 1024
|
|
201
|
+
return f"{n}B"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _fmt_age(seconds: int) -> str:
|
|
205
|
+
if seconds < 3600:
|
|
206
|
+
return f"{seconds // 60}m"
|
|
207
|
+
if seconds < 86400:
|
|
208
|
+
return f"{seconds // 3600}h"
|
|
209
|
+
return f"{seconds // 86400}d"
|