| @@ -0,0 +1,193 @@ | |||||||
| 1 | +# mugen 用例生成 —— 参考手册 | ||||||
| 2 | + | ||||||
| 3 | +[SKILL.md](SKILL.md) 背后的详细规则、踩坑和模式。规范的权威来源是 mugen 仓库里的 | ||||||
| 4 | +`doc/测试用例检视规范.md`。CI 门禁对改动的 `.sh` 文件跑 **ShellCheck 0.7.2**,任何非 `info` | ||||||
| 5 | +级别的告警都会判失败。 | ||||||
| 6 | + | ||||||
| 7 | +可直接复制的骨架在 [templates/](templates/):`case_template.sh`(已通过 `scripts/verify_case.sh` | ||||||
| 8 | +的用例骨架)、`suite_common_lib_template.sh`(套件公共库)、`suite2cases_template.json`(套件 | ||||||
| 9 | +注册)。下面的说明解释这些骨架背后的规则。 | ||||||
| 10 | + | ||||||
| 11 | +## 1. 头部(精确格式) | ||||||
| 12 | + | ||||||
| 13 | +``` | ||||||
| 14 | +#!/usr/bin/bash | ||||||
| 15 | + | ||||||
| 16 | +# Copyright (c) <年份>. Huawei Technologies Co.,Ltd.ALL rights reserved. | ||||||
S | |||||||
| 17 | +# This program is licensed under Mulan PSL v2. | ||||||
| 18 | +# You can use it according to the terms and conditions of the Mulan PSL v2. | ||||||
| 19 | +# http://license.coscl.org.cn/MulanPSL2 | ||||||
| 20 | +# THIS PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||||||
| 21 | +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||||||
| 22 | +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||||||
| 23 | +# See the Mulan PSL v2 for more details. | ||||||
| 24 | + | ||||||
| 25 | +# ############################################# | ||||||
| 26 | +# @Author : <姓名> | ||||||
| 27 | +# @Contact : <邮箱> | ||||||
| 28 | +# @Date : <YYYY/MM/DD> | ||||||
| 29 | +# @License : Mulan PSL v2 | ||||||
| 30 | +# @Desc : <英文描述> | ||||||
| 31 | +# ############################################# | ||||||
| 32 | +``` | ||||||
| 33 | + | ||||||
| 34 | +- `<年份>` 必须是**当前年份**(§2.4)。 | ||||||
| 35 | +- `@Desc` 和所有注释**全英文**(§2.1)。 | ||||||
| 36 | +- 套件公共库文件的 `@Desc` 例如 `common function library for the <套件> test suite`。 | ||||||
| 37 | + | ||||||
| 38 | +## 2. source 行 | ||||||
| 39 | + | ||||||
| 40 | +- 每个用例**只有一行** `source`。 | ||||||
| 41 | +- 必须用 `${OET_PATH}` 形式:`source "${OET_PATH}"/libs/locallibs/common_lib.sh`。 | ||||||
| 42 | + (mugen 运行时把 `OET_PATH` 设为仓库根,并 `pushd` 进用例目录,所以相对路径 `../common/...` | ||||||
| 43 | + *执行*时没问题;但 ShellCheck 是从仓库根跑的,跟不进去 → `SC1091: openBinaryFile: does not | ||||||
| 44 | + exist` → CI 失败。) | ||||||
| 45 | +- 如果套件有公共库,**用例**只 source 套件库 | ||||||
| 46 | + (`source "${OET_PATH}"/testcases/<域>/<套件>/common/<套件>_common.sh`),套件库内部去 source | ||||||
| 47 | + `common_lib.sh`。每个文件一行 source。 | ||||||
| 48 | + | ||||||
| 49 | +## 3. 阶段结构 & LOG_INFO | ||||||
| 50 | + | ||||||
| 51 | +``` | ||||||
| 52 | +function pre_test() { LOG_INFO "Start environmental preparation."; ...; LOG_INFO "End of environmental preparation!"; } | ||||||
| 53 | +function run_test() { LOG_INFO "Start testing..."; ...; LOG_INFO "Finish test!"; } | ||||||
| 54 | +function post_test() { LOG_INFO "start environment cleanup."; ...; LOG_INFO "Finish environment cleanup!"; } | ||||||
| 55 | +main "$@" | ||||||
| 56 | +``` | ||||||
| 57 | + | ||||||
| 58 | +- 确无必要时可省略 `config_params`/`pre_test`/`post_test`(§2.4)。 | ||||||
| 59 | +- **正好六条 LOG_INFO**(每阶段首尾各一)。**阶段体内不许写 LOG_INFO**——上下文写到 `CHECK_RESULT` | ||||||
| 60 | + 的消息里。 | ||||||
| 61 | + | ||||||
| 62 | +## 4. CHECK_RESULT | ||||||
| 63 | + | ||||||
| 64 | +- 签名:`CHECK_RESULT <实际> <期望> <模式> "<消息>"`(4 个参数)。 | ||||||
| 65 | + - `模式 0`:`实际 == 期望` 算通过。`模式 1`:`实际 != 期望` 算通过。 | ||||||
| 66 | +- `run_test` 里每个测试命令都是一个测试点 → 后面必须跟 `CHECK_RESULT`。**不要**跑一大段命令后才 | ||||||
| 67 | + 一个 CHECK_RESULT。 | ||||||
| 68 | +- **pre_test/post_test 里绝对不能有 `CHECK_RESULT`**(§2.4)——从这两个阶段调用的 helper 里也不能有。 | ||||||
| 69 | +- 行为:`CHECK_RESULT` **累加**失败到 `exec_result` 并继续,**成功不退出**(只有 `main` 在 `run_test` | ||||||
| 70 | + 之后调用的 `CASE_RESULT` 才退出)。所以多个检查点都会跑到,放进 helper 里也安全。 | ||||||
| 71 | +- mugen 看到的退出码:`0`→成功,`255`→跳过,其它→失败。 | ||||||
| 72 | + | ||||||
| 73 | +### 检查点模式 | ||||||
| 74 | + | ||||||
| 75 | +- **单个进程启动**:捕获 `$!`、跟踪、验证存活: | ||||||
| 76 | + ```bash | ||||||
| 77 | + some_cmd & | ||||||
| 78 | + local PID | ||||||
| 79 | + PID=$! | ||||||
| 80 | + em_track_pid "$PID" # 若套件库提供;否则用本地数组跟踪 | ||||||
| 81 | + kill -0 "$PID" | ||||||
| 82 | + CHECK_RESULT $? 0 0 "<什么> 进程启动失败" | ||||||
| 83 | + ``` | ||||||
| 84 | +- **循环里批量启动**:把 PID 收进数组,验证至少一个存活(**不要用 `pgrep -f 'pattern'`**——不稳: | ||||||
| 85 | + bash 可能 exec 掉命令、丢掉关键字,且 `&` 后有竞态): | ||||||
| 86 | + ```bash | ||||||
| 87 | + local pids=() p alive=0 | ||||||
| 88 | + for _ in $(seq 1 N); do | ||||||
| 89 | + some_cmd & | ||||||
| 90 | + p=$!; pids+=("$p"); em_track_pid "$p" | ||||||
| 91 | + done | ||||||
| 92 | + for p in "${pids[@]}"; do kill -0 "$p" 2>/dev/null && { alive=1; break; }; done | ||||||
| 93 | + [ "$alive" -eq 1 ] | ||||||
| 94 | + CHECK_RESULT $? 0 0 "<什么> 进程启动失败" | ||||||
| 95 | + ``` | ||||||
| 96 | +- **声明与赋值分开**避免 SC2155:`local x` 换行 `x=$(...)`,不要 `local x=$(...)`。 | ||||||
| 97 | + | ||||||
| 98 | +## 5. SLEEP_WAIT | ||||||
| 99 | + | ||||||
| 100 | +- 用 `SLEEP_WAIT <秒>`(来自 common_lib.sh),不要裸 `sleep`。 | ||||||
| 101 | +- `SLEEP_WAIT 5` 睡 5 秒;`SLEEP_WAIT 5m` = 5 分钟;`SLEEP_WAIT 5 <cmd>` 带超时跑 cmd。 | ||||||
| 102 | + | ||||||
| 103 | +## 6. 环境不满足时的跳过 | ||||||
| 104 | + | ||||||
| 105 | +mugen 的 `main`(`libs/locallibs/common_lib.sh:119-124`)调用 `pre_test` 后**不看返回值**直接调 | ||||||
| 106 | +`run_test`。所以 `pre_test` 里的 `em_check_env || return 1` **拦不住** `run_test`。要真正跳过,在 | ||||||
| 107 | +`pre_test` 里用 `exit 255`(EXIT trap 仍会跑 `post_test` 清理;mugen 记为*跳过*): | ||||||
| 108 | + | ||||||
| 109 | +```bash | ||||||
| 110 | +function pre_test() { | ||||||
| 111 | + LOG_INFO "Start environmental preparation." | ||||||
| 112 | + <环境检查> || exit 255 # 环境不满足就跳过整个用例 | ||||||
| 113 | + ... | ||||||
| 114 | + LOG_INFO "End of environmental preparation!" | ||||||
| 115 | +} | ||||||
| 116 | +``` | ||||||
| 117 | + | ||||||
| 118 | +**不要**在 `run_test` 里再放一遍环境检查——那正是要避免的冗余模式。 | ||||||
| 119 | + | ||||||
| 120 | +## 7. 套件公共库模式 | ||||||
| 121 | + | ||||||
| 122 | +复制 [templates/suite_common_lib_template.sh](templates/suite_common_lib_template.sh),把里面的 | ||||||
| 123 | +`x_` 前缀改成你套件的前缀。真实例子见 `testcases/cli-test/elasticMem/common/em_common.sh`。 | ||||||
| 124 | + | ||||||
| 125 | +- 库内部 source `common_lib.sh`:`source "${OET_PATH}"/libs/locallibs/common_lib.sh`。 | ||||||
| 126 | +- 每个用例只 source 这个库(一行)。 | ||||||
| 127 | +- helper 按调用阶段分两类: | ||||||
| 128 | + - **校验类 helper**(在 `run_test` 调用:断言、启服务、写配置)→ 内部 `CHECK_RESULT`,让 helper | ||||||
| 129 | + 自校验,用例直接调(可传消息:`em_start_service "light start failed"`)。 | ||||||
| 130 | + - **pre/post 类 helper**(备份、停止、恢复、装包)→ `return 0/1`,**绝不 `CHECK_RESULT`**(§2.4)。 | ||||||
| 131 | +- 给"日志写文件、不走 journald"的服务做日志断言时,用**行数边界**避免跨用例污染: | ||||||
| 132 | + ```bash | ||||||
| 133 | + em_LOG_START=0 | ||||||
| 134 | + em_mark_log() { em_LOG_START=$(wc -l < "$LOG_FILE" 2>/dev/null); em_LOG_START=${em_LOG_START:-0}; } | ||||||
| 135 | + em_log_window(){ [ -f "$LOG_FILE" ] || return 0; tail -n +$((em_LOG_START + 1)) "$LOG_FILE" 2>/dev/null; } | ||||||
| 136 | + em_assert_log_contains() { | ||||||
| 137 | + local pattern=$1 msg=${2:-"expected log not found: $1"} | ||||||
| 138 | + em_log_window | grep -q -- "$pattern" 2>/dev/null | ||||||
| 139 | + CHECK_RESULT $? 0 0 "$msg" | ||||||
| 140 | + } | ||||||
| 141 | + ``` | ||||||
| 142 | + (`em_mark_log` 在产生日志的动作之前调用,例如 `em_start_service` 里 `systemctl restart` 之前。) | ||||||
| 143 | + | ||||||
| 144 | +## 8. 便携负载生成器(stress-ng 不可用时) | ||||||
| 145 | + | ||||||
| 146 | +openEuler 仓库常缺 `stress-ng`。用便携替代: | ||||||
| 147 | +- **CPU 负载**:`numactl --cpunodebind=0 --membind=0 bash -c '(( end = SECONDS + TMOUT )); while (( SECONDS < end )); do :; done' &` | ||||||
L numactl非内置 需要提前检查是否安装 ![]() ![]() | |||||||
| 148 | + (超时通过环境变量传给内层 bash;用 `(( ))` 算术不带 `$`,单引号里不会触发 SC2016)。 | ||||||
| 149 | +- **内存负载**:`numactl --cpunodebind=0 --membind=0 python3 -c '<mmap+touch+sleep>' <字节数> <超时> &` | ||||||
| 150 | + (直接用 `&` 启动并捕获 `$!`;**不要**在 `$(...)` 子 shell 里启动后台进程——它活不过子 shell 退出)。 | ||||||
| 151 | + | ||||||
| 152 | +## 9. ShellCheck 修复对照表(用代码修,绝不 `# shellcheck disable`) | ||||||
| 153 | + | ||||||
| 154 | +| 码 | 含义 | 修法 | | ||||||
| 155 | +|---|---|---| | ||||||
| 156 | +| SC1091 | source 跟不进去(相对路径) | 改用 `${OET_PATH}/...` 绝对路径 → 变 SC1090(info,允许) | | ||||||
| 157 | +| SC2086 | 变量没加引号 | 加引号:`"$VAR"` | | ||||||
| 158 | +| SC2155 | `local x=$(cmd)` 掩盖返回值 | 拆开:`local x` 换行 `x=$(cmd)` | | ||||||
| 159 | +| SC2002 | `cat file \| grep` | `grep pat file` | | ||||||
| 160 | +| SC2010/SC2012 | `ls \| grep` / `ls -d glob` | `find ... -name ...` 或 bash 数组 glob | | ||||||
| 161 | +| SC2126 | `grep \| wc -l` | `grep -c` | | ||||||
| 162 | +| SC2068 | `${arr[@]}` 没引号 | `"${arr[@]}"` | | ||||||
| 163 | +| SC2046 | `$(...)` 没引号 | 加引号 | | ||||||
| 164 | +| SC2034 | 未用变量 | 删掉;若被外部用则 `export` | | ||||||
| 165 | +| SC2016 | 单引号里有 `$` | 改用 `(( ))` 算术(无 `$`)或双引号配 `\$` | | ||||||
| 166 | +| SC2119 | 无参调用带 `$1` 的函数 | 显式传参 | | ||||||
| 167 | +| SC2154 | 引用了被 source 库里的变量 | 内联逻辑,或让 helper 通过调用方能 `kill -0`/读取的方式设置 | | ||||||
| 168 | + | ||||||
| 169 | +如果某个告警是真·误报、用代码消不掉,重新审视设计——mugen 的要求是零非 info 告警 + 零 disable 指令。 | ||||||
| 170 | + | ||||||
| 171 | +## 10. 规范要点清单(§2.4 摘要) | ||||||
| 172 | + | ||||||
| 173 | +- 注释英文;年份当前;4 空格缩进;参数/特殊符号前后一个空格。 | ||||||
| 174 | +- `grep xxx file`;`test -f file`;不留死代码/未用变量;能归并的语句归并。 | ||||||
| 175 | +- 临时文件放 `/tmp`(或在当前路径建临时目录),post_test 里清理。 | ||||||
| 176 | +- 每个用例独立(互不依赖、互不影响)。 | ||||||
| 177 | +- `DNF_INSTALL "p1 p2"`(多包);`DNF_REMOVE`(不带参,与 DNF_INSTALL 配合)。 | ||||||
| 178 | +- `cp -f`、`mv -rf`、`userdel -rf`。 | ||||||
🟡 Medium Priority 变更行:REFERENCE.md 第 178 行,
如果用户按此文档编写 post_test 清理逻辑,实际执行到该行时会导致用例失败。 建议:将 改动建议
![]() ![]() | |||||||
| 179 | +- 用框架公共变量/方法(`NODE1_*`、`DNF_INSTALL`、`SLEEP_WAIT`、`CHECK_RESULT`)。 | ||||||
| 180 | +- pre_test/post_test 只放预置/清理代码;run_test 只放测试代码;里面不能出现 `python xx` 执行命令。 | ||||||
| 181 | +- 取当前路径:`x=$(cd "$(dirname "$0")" || exit 1; pwd)`。 | ||||||
| 182 | +- 看标准错误输出:`cmd 2>&1 | grep xxx`。 | ||||||
| 183 | + | ||||||
| 184 | +## 11. 运行与校验 | ||||||
| 185 | + | ||||||
| 186 | +```bash | ||||||
| 187 | +export OET_PATH=/path/to/mugen | ||||||
| 188 | +bash mugen.sh -f <套件> -r <用例> # 单个用例 | ||||||
| 189 | +bash mugen.sh -f <套件> # 整个套件 | ||||||
| 190 | +``` | ||||||
| 191 | +结果在 `results/<套件>/{succeed,failed,skipped}/<用例>`;日志在 `logs/<套件>/<用例>/`。 | ||||||
| 192 | +退出 `0`→成功,`255`→跳过,其它→失败。声明用例完成前,**必须**跑 `scripts/verify_case.sh <文件>` | ||||||
| 193 | +(静态校验)**和**一次真实 mugen 执行。 | ||||||
| @@ -0,0 +1,75 @@ | |||||||
| 1 | +--- | ||||||
L 缺失顶层README和openai.yaml ![]() ![]() | |||||||
| 2 | +name: mugen-case-generator | ||||||
| 3 | +description: > | ||||||
L description过长 可以考虑拆分成结构块 并突出关键词 ![]() ![]() | |||||||
| 4 | + 生成和修复 openEuler mugen 测试用例(oe_test_*.sh),使其符合 doc/测试用例检视规范.md 并通过 | ||||||
| 5 | + CI 的 ShellCheck 0.7.2 门禁(只允许 info 级别告警)。覆盖:标准 Copyright/Mulan-PSL-v2 头、 | ||||||
| 6 | + 单个 ${OET_PATH} source 行、pre_test/run_test/post_test 三阶段、六条 LOG_INFO 规则、 | ||||||
| 7 | + CHECK_RESULT 规则、SLEEP_WAIT、套件公共库设计、中文注释改英文(§2.1),以及那些不直观的坑 | ||||||
| 8 | + (mugen 的 main 不会因 pre_test 失败而跳过 run_test;CHECK_RESULT 是累加不退出;进程启动要用 | ||||||
| 9 | + kill-0 验证而非 pgrep)。当用户要写/改/修 mugen 用例、建测试套,或提到 mugen、oe_test_、 | ||||||
| 10 | + pre_test/run_test/post_test、CHECK_RESULT、SLEEP_WAIT、DNF_INSTALL,或要修一个过不了 CI | ||||||
| 11 | + ShellCheck 的 mugen 用例时使用。 | ||||||
| 12 | +--- | ||||||
| 13 | + | ||||||
| 14 | +# mugen 用例生成器 | ||||||
| 15 | + | ||||||
| 16 | +让 mugen 用例既符合 `doc/测试用例检视规范.md`,又过得了 CI 的 ShellCheck 0.7.2 门禁(只有 `info` | ||||||
| 17 | +级告警允许)。可复制的骨架在 [templates/](templates/);详细规则和踩坑在 | ||||||
| 18 | +[REFERENCE.md](REFERENCE.md);用 [scripts/verify_case.sh](scripts/verify_case.sh) 做合规校验。 | ||||||
| 19 | + | ||||||
| 20 | +## 快速上手 | ||||||
| 21 | + | ||||||
| 22 | +1. 复制骨架:`cp templates/case_template.sh oe_test_<包名>_<序号>.sh`(新建套件则用 | ||||||
| 23 | + `templates/suite_common_lib_template.sh` + `templates/suite2cases_template.json`)。 | ||||||
| 24 | +2. 填掉所有 `<PLACEHOLDER>` / `true` 占位。保留那六条 `LOG_INFO`;`run_test` 里每个测试点后加 | ||||||
| 25 | + `CHECK_RESULT`。 | ||||||
| 26 | +3. 如果有中文,先翻成英文(见下)。 | ||||||
| 27 | +4. 校验:`scripts/verify_case.sh oe_test_<包名>_<序号>.sh` —— 必须 PASS。 | ||||||
| 28 | +5. 实跑:`export OET_PATH=<mugen 仓库>; bash mugen.sh -f <套件> -r <用例>`。 | ||||||
| 29 | + | ||||||
| 30 | +## 核心规则(精简版,详见 REFERENCE.md) | ||||||
| 31 | + | ||||||
| 32 | +- **头部**:Copyright(当前年份)+ Mulan PSL v2 + `#@Author/@Contact/@Date/@License/@Desc`;**注释全英文**(§2.1)。 | ||||||
| 33 | +- **只 source 一次**,写成 `${OET_PATH}/...` 形式。不要相对路径 `../common/...`,不要两行 source。 | ||||||
| 34 | +- **三阶段**:`pre_test`(预置)/`run_test`(测试)/`post_test`(清理)+ `main "$@"`。 | ||||||
| 35 | +- **正好六条 LOG_INFO**:每个阶段的开始和结束各一条。**阶段体内不许再写 LOG_INFO**——上下文放进 `CHECK_RESULT` 的消息里。 | ||||||
| 36 | +- **CHECK_RESULT** `<实际> <期望> <模式> "<消息>"`(4 个参数),跟在 `run_test` 里每个测试点后面。**pre/post 里绝对不能有**(§2.4)。 | ||||||
| 37 | +- **SLEEP_WAIT**,不要裸 `sleep`。 | ||||||
| 38 | +- **ShellCheck**:只允许 `info`(SC1090/SC1091,来自 source 行)。warning/style/error 全部为 0。用代码修,**绝不加 `# shellcheck disable`**。 | ||||||
| 39 | +- **风格**:4 空格缩进;`grep xxx file`;`test -f file`;不留死代码/未用变量;临时文件放 `/tmp` 并在 post_test 清理;`cp -f`/`mv -rf`/`userdel -rf`;用 `DNF_INSTALL`/`DNF_REMOVE`/`NODE1_*`。 | ||||||
🟡 Medium Priority 变更行: 与 REFERENCE.md 第 178 行相同的问题: 应改为 建议:将 改动建议
![]() ![]() | |||||||
| 40 | + | ||||||
| 41 | +## 中文改英文(§2.1) | ||||||
| 42 | + | ||||||
| 43 | +规范要求**所有注释和描述用英文**。如果用例(或用户给的草稿)里有中文,把**全部**中文翻成英文—— | ||||||
| 44 | +`@Desc`、行内 `#` 注释、`LOG_INFO` 消息、`CHECK_RESULT` 消息——保留语义、不改代码逻辑。你本身是 | ||||||
| 45 | +LLM,直接逐行翻译即可。 | ||||||
| 46 | + | ||||||
| 47 | +```bash | ||||||
| 48 | +grep -nP '[\x{4e00}-\x{9fff}]' <文件> # 找出所有含中文的行 | ||||||
| 49 | +# 逐行翻成英文,然后: | ||||||
| 50 | +scripts/verify_case.sh <文件> # 确认 "no CJK" | ||||||
| 51 | +``` | ||||||
| 52 | + | ||||||
| 53 | +## 校验(两步都做) | ||||||
| 54 | + | ||||||
| 55 | +```bash | ||||||
| 56 | +scripts/verify_case.sh <文件> # 静态校验:bash -n、shellcheck 仅 info、 | ||||||
| 57 | + # 单 source 行、六条 LOG_INFO、pre/post 无 | ||||||
| 58 | + # CHECK_RESULT、无中文、年份当前…… | ||||||
| 59 | +export OET_PATH=<mugen 仓库> | ||||||
| 60 | +bash mugen.sh -f <套件> -r <用例> # 真跑(0=成功,255=跳过,其它=失败) | ||||||
| 61 | +``` | ||||||
| 62 | + | ||||||
| 63 | +## 最常踩的坑(完整清单见 REFERENCE.md) | ||||||
| 64 | + | ||||||
| 65 | +- **pre_test 的环境守卫必须 `exit 255`**——mugen 的 `main` 不看 `pre_test` 返回值,照样跑 `run_test`;`return 1` 会判 *失败*,`exit 255` 才判 *跳过*(且 post_test 仍会经 EXIT trap 跑清理)。 | ||||||
| 66 | +- **`CHECK_RESULT` 是累加的,成功不退出**——可以放心放进 helper 里;多个检查点都会跑到。 | ||||||
| 67 | +- **进程启动用 `kill -0 "$PID"` 验证,不要用 pgrep**——pgrep 不稳(bash 可能 exec 掉关键字;`&` 后有竞态)。 | ||||||
| 68 | +- **写文件的服务的日志断言**:用行数边界(`wc -l` 在前,`tail -n +N` 在后)避免跨用例污染——见 [REFERENCE.md](REFERENCE.md#7-套件公共库模式)。 | ||||||
| 69 | +- **`stress-ng` 常缺失**:用便携负载(`numactl ... bash -c` / `python3 mmap`)。 | ||||||
| 70 | + | ||||||
| 71 | +## 套件公共库 | ||||||
| 72 | + | ||||||
| 73 | +多个用例共享逻辑时,用 [templates/suite_common_lib_template.sh](templates/suite_common_lib_template.sh): | ||||||
| 74 | +库内部 source `common_lib.sh`;校验类 helper 自带 `CHECK_RESULT`;pre/post 类 helper 只 `return 0/1`。 | ||||||
| 75 | +用例只 source 这个库(一行)。 | ||||||
| @@ -0,0 +1,81 @@ | |||
| 1 | +#!/usr/bin/bash | ||
| 2 | +# 校验 mugen 测试用例是否符合规范 + CI ShellCheck 门禁。 | ||
| 3 | +# 用法:verify_case.sh <用例.sh> (可设 OET_PATH 指向 mugen 仓库以提供上下文) | ||
| 4 | +# 任一检查不过则非 0 退出。 | ||
| 5 | + | ||
L 建议加上用例名称的检查
oe_test_ ![]() ![]() | |||
| 6 | +set -u | ||
| 7 | +f="${1:-}" | ||
| 8 | +[ -n "$f" ] || { echo "用法: $0 <用例.sh>"; exit 2; } | ||
| 9 | +[ -f "$f" ] || { echo "失败:文件不存在: $f"; exit 2; } | ||
| 10 | + | ||
| 11 | +fail=0 | ||
| 12 | +say() { printf '%s\n' "$*"; } | ||
| 13 | +warn() { printf ' ✗ %s\n' "$*"; fail=1; } | ||
| 14 | +ok() { printf ' ✓ %s\n' "$*"; } | ||
| 15 | + | ||
| 16 | +say "=== $f ===" | ||
| 17 | + | ||
| 18 | +# 1. bash 语法 | ||
| 19 | +if bash -n "$f"; then ok "bash -n 语法"; else warn "bash -n 语法失败"; fi | ||
| 20 | + | ||
| 21 | +# 2. ShellCheck 0.7.2 仅 info(若已安装) | ||
| 22 | +if command -v shellcheck >/dev/null 2>&1; then | ||
| 23 | + bad=$(shellcheck --format=checkstyle "$f" 2>/dev/null | grep -oE "severity='[a-z]+'" | grep -v "info" | sort -u | tr '\n' ' ') | ||
| 24 | + if [ -z "$bad" ]; then ok "shellcheck:仅 info"; else warn "shellcheck 有非 info 告警: $bad"; shellcheck "$f" 2>&1 | grep -E 'SC[0-9]' | grep -vE 'SC1091|SC1090' | head -10; fi | ||
| 25 | +else | ||
| 26 | + say " (未安装 shellcheck——跳过 lint)" | ||
| 27 | +fi | ||
| 28 | + | ||
| 29 | +# 3. 只有一行 source | ||
| 30 | +n=$(grep -c '^source ' "$f") | ||
| 31 | +[ "$n" -eq 1 ] && ok "单 source 行($n)" || warn "应只有 1 行 source,实际 $n" | ||
| 32 | + | ||
| 33 | +# 4. source 用 ${OET_PATH} | ||
| 34 | +grep -q '^source "${OET_PATH}"' "$f" && ok 'source 用 ${OET_PATH} 形式' || warn 'source 不是 ${OET_PATH}/... 形式' | ||
| 35 | + | ||
| 36 | +# 5. 正好六条 LOG_INFO | ||
| 37 | +n=$(grep -c 'LOG_INFO' "$f") | ||
| 38 | +[ "$n" -eq 6 ] && ok "六条 LOG_INFO($n)" || warn "应为 6 条 LOG_INFO(pre/run/post 各首尾),实际 $n" | ||
| 39 | + | ||
| 40 | +# 6. pre_test / post_test 里不能有 CHECK_RESULT | ||
| 41 | +pt=$(sed -n '/function pre_test()/,/function run_test()/p' "$f") | ||
| 42 | +po=$(sed -n '/function post_test()/,/^main /p' "$f") | ||
| 43 | +echo "$pt" | grep -q 'CHECK_RESULT' && warn "pre_test 里有 CHECK_RESULT(§2.4 禁止)" | ||
| 44 | +echo "$po" | grep -q 'CHECK_RESULT' && warn "post_test 里有 CHECK_RESULT(§2.4 禁止)" | ||
| 45 | +{ echo "$pt" | grep -q 'CHECK_RESULT' || echo "$po" | grep -q 'CHECK_RESULT'; } || ok "pre/post 无 CHECK_RESULT" | ||
| 46 | + | ||
| 47 | +# 7. 不能有 # shellcheck disable 指令 | ||
| 48 | +n=$(grep -c '# shellcheck disable' "$f") | ||
| 49 | +[ "$n" -eq 0 ] && ok "无 shellcheck disable 指令" || warn "发现 $n 处 '# shellcheck disable'——应改用代码修复" | ||
| 50 | + | ||
| 51 | +# 8. 注释不能有中文(§2.1 要求全英文) | ||
| 52 | +if grep -nP '[\x{4e00}-\x{9fff}]' "$f" >/dev/null 2>&1; then | ||
| 53 | + warn "存在非英文(中文)字符——注释必须英文(§2.1)" | ||
| 54 | + grep -nP '[\x{4e00}-\x{9fff}]' "$f" | head -5 | ||
| 55 | +else | ||
| 56 | + ok "无中文(注释为英文)" | ||
| 57 | +fi | ||
| 58 | + | ||
| 59 | +# 9. 不能有裸 sleep(用 SLEEP_WAIT) | ||
| 60 | +n=$(grep -cE '(^|[^_])sleep [0-9]' "$f") | ||
| 61 | +[ "$n" -eq 0 ] && ok "无裸 'sleep N'(用 SLEEP_WAIT)" || warn "发现 $n 处裸 'sleep N'——改用 SLEEP_WAIT" | ||
| 62 | + | ||
| 63 | +# 10. 头部齐全 | ||
| 64 | +head -1 "$f" | grep -q '^#!/usr/bin/bash' && ok "shebang" || warn "缺 #!/usr/bin/bash" | ||
| 65 | +grep -q 'Copyright (c)' "$f" && ok "Copyright 行" || warn "缺 Copyright 行" | ||
| 66 | +grep -q 'Mulan PSL v2' "$f" && ok "Mulan PSL v2 许可" || warn "缺 Mulan PSL v2 块" | ||
| 67 | +grep -q '#@Author\|# @Author' "$f" && ok "@Author/@Desc 头" || warn "缺 @Author/@Desc 头" | ||
| 68 | +yr=$(grep -oE 'Copyright \(c\) [0-9]{4}' "$f" | grep -oE '[0-9]{4}') | ||
| 69 | +cur=$(date +%Y) | ||
| 70 | +[ "$yr" = "$cur" ] && ok "版权年份当前($yr)" || warn "版权年份 '$yr'(应为 $cur)" | ||
| 71 | + | ||
| 72 | +# 11. 末尾 main "$@" | ||
| 73 | +grep -q '^main "\$@"' "$f" && ok 'main "$@"' || warn '缺 main "$@"' | ||
| 74 | + | ||
| 75 | +# 12. 三阶段齐全 | ||
| 76 | +for fn in pre_test run_test post_test; do | ||
| 77 | + grep -q "function $fn()" "$f" && ok "$fn 已定义" || warn "$fn 未定义" | ||
| 78 | +done | ||
| 79 | + | ||
| 80 | +say "---" | ||
| 81 | +[ "$fail" -eq 0 ] && { say "通过:$f 合规"; exit 0; } || { say "失败:$f 存在上述问题"; exit 1; } | ||
| @@ -0,0 +1,49 @@ | |||
| 1 | +#!/usr/bin/bash | ||
| 2 | +# | ||
| 3 | +# Canonical mugen test-case skeleton. Copy this file, rename to | ||
| 4 | +# oe_test_<pkg>[_<num>|_<cmd>].sh, and replace every <PLACEHOLDER> / `true` marker. | ||
| 5 | +# After editing, run: scripts/verify_case.sh <your_file> | ||
| 6 | + | ||
| 7 | +# Copyright (c) 2026. Huawei Technologies Co.,Ltd.ALL rights reserved. | ||
| 8 | +# This program is licensed under Mulan PSL v2. | ||
| 9 | +# You can use it according to the terms and conditions of the Mulan PSL v2. | ||
| 10 | +# http://license.coscl.org.cn/MulanPSL2 | ||
| 11 | +# THIS PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 12 | +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 13 | +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 14 | +# See the Mulan PSL v2 for more details. | ||
| 15 | + | ||
| 16 | +# ############################################# | ||
| 17 | +# @Author : <author name> | ||
| 18 | +# @Contact : <author email> | ||
| 19 | +# @Date : <YYYY/MM/DD> | ||
| 20 | +# @License : Mulan PSL v2 | ||
| 21 | +# @Desc : <one-line English description of what this case verifies> | ||
| 22 | +# ############################################# | ||
| 23 | + | ||
| 24 | +source "${OET_PATH}"/libs/locallibs/common_lib.sh | ||
| 25 | + | ||
| 26 | +function pre_test() { | ||
| 27 | + LOG_INFO "Start environmental preparation." | ||
| 28 | + # install packages under test: DNF_INSTALL "<pkg1> <pkg2>" | ||
| 29 | + # create any temp files under /tmp; back up files you will modify | ||
| 30 | + # if the case needs a special environment, skip cleanly: <check> || exit 255 | ||
| 31 | + LOG_INFO "End of environmental preparation!" | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +function run_test() { | ||
| 35 | + LOG_INFO "Start testing..." | ||
| 36 | + # replace: run the command/feature under test, then CHECK_RESULT every test point | ||
| 37 | + true | ||
| 38 | + CHECK_RESULT $? 0 0 "<expected outcome message>" | ||
| 39 | + # more test points, each followed by its own CHECK_RESULT ... | ||
| 40 | + LOG_INFO "Finish test!" | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +function post_test() { | ||
| 44 | + LOG_INFO "start environment cleanup." | ||
| 45 | + # restore backed-up files; DNF_REMOVE; rm temp files | ||
| 46 | + LOG_INFO "Finish environment cleanup!" | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +main "$@" | ||
| @@ -0,0 +1,7 @@ | |||
| 1 | +{ | ||
| 2 | + "path": "$OET_PATH/testcases/cli-test/<suite>", | ||
| 3 | + "cases": [ | ||
| 4 | + {"name": "oe_test_<pkg>_001"}, | ||
| 5 | + {"name": "oe_test_<pkg>_002"} | ||
| 6 | + ] | ||
| 7 | +} | ||
| @@ -0,0 +1,90 @@ | |||
| 1 | +#!/usr/bin/bash | ||
| 2 | +# | ||
| 3 | +# Suite common-library skeleton (e.g. testcases/cli-test/<suite>/common/<suite>_common.sh). | ||
| 4 | +# Use when several cases share setup/assertion helpers. Each case then sources ONLY this | ||
| 5 | +# lib (one source line): source "${OET_PATH}"/testcases/cli-test/<suite>/common/<suite>_common.sh | ||
| 6 | +# | ||
| 7 | +# This template uses the prefix "x_" — rename every x_ to your suite's prefix (e.g. em_). | ||
| 8 | +# | ||
| 9 | +# Rules: | ||
| 10 | +# - this lib sources common_lib.sh internally (so cases source only this lib, once). | ||
| 11 | +# - verification helpers (assert / start-service / write-config) call CHECK_RESULT internally. | ||
| 12 | +# - helpers called from pre_test/post_test (backup/stop/restore/require-pkg) return 0/1, NEVER CHECK_RESULT. | ||
| 13 | + | ||
| 14 | +# Copyright (c) 2026. Huawei Technologies Co.,Ltd.ALL rights reserved. | ||
| 15 | +# This program is licensed under Mulan PSL v2. | ||
| 16 | +# You can use it according to the terms and conditions of the Mulan PSL v2. | ||
| 17 | +# http://license.coscl.org.cn/MulanPSL2 | ||
| 18 | +# THIS PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 19 | +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 20 | +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 21 | +# See the Mulan PSL v2 for more details. | ||
| 22 | + | ||
| 23 | +# ############################################# | ||
| 24 | +# @Author : <author name> | ||
| 25 | +# @Contact : <author email> | ||
| 26 | +# @Date : <YYYY/MM/DD> | ||
| 27 | +# @License : Mulan PSL v2 | ||
| 28 | +# @Desc : common function library for the <suite> test suite | ||
| 29 | +# ############################################# | ||
| 30 | + | ||
| 31 | +source "${OET_PATH}"/libs/locallibs/common_lib.sh | ||
| 32 | + | ||
| 33 | +# shared constants used by the cases | ||
| 34 | +X_CONFIG="/etc/x.conf" | ||
| 35 | +X_LOG_FILE="/var/log/x/x.log" | ||
| 36 | +X_SERVICE="x" | ||
| 37 | + | ||
| 38 | +# --- helpers used from pre_test / post_test (return 0/1, no CHECK_RESULT) --- | ||
| 39 | +x_backup_config() { | ||
| 40 | + test -f "$X_CONFIG" && cp -fp "$X_CONFIG" "$X_CONFIG.mugen_bak" | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +x_restore_config() { | ||
| 44 | + test -f "$X_CONFIG.mugen_bak" && cp -fp "$X_CONFIG.mugen_bak" "$X_CONFIG" 2>/dev/null && rm -f "$X_CONFIG.mugen_bak" | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +x_stop_service() { | ||
| 48 | + systemctl stop "$X_SERVICE" 2>/dev/null | ||
| 49 | + SLEEP_WAIT 2 | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +# --- verification helpers (called from run_test; CHECK_RESULT internally) --- | ||
| 53 | + | ||
| 54 | +# Write the config; verify the write succeeded. | ||
| 55 | +x_write_config() { | ||
| 56 | + cat > "$X_CONFIG" << EOF | ||
| 57 | +# config lines here | ||
| 58 | +EOF | ||
| 59 | + CHECK_RESULT $? 0 0 "failed to write $X_SERVICE config" | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +# Start the service and verify it is active. Accepts an optional message. | ||
| 63 | +x_start_service() { | ||
| 64 | + local msg=${1:-"$X_SERVICE service start failed"} | ||
| 65 | + systemctl restart "$X_SERVICE" 2>/dev/null | ||
| 66 | + SLEEP_WAIT 3 | ||
| 67 | + systemctl is-active "$X_SERVICE" --quiet | ||
| 68 | + CHECK_RESULT $? 0 0 "$msg" | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +# Log-window assertion: bound by a line count so cases don't see each other's logs. | ||
| 72 | +# x_LOG_START is set by x_mark_log right before the action that produces the log. | ||
| 73 | +x_LOG_START=0 | ||
| 74 | +x_mark_log() { x_LOG_START=$(wc -l < "$X_LOG_FILE" 2>/dev/null); x_LOG_START=${x_LOG_START:-0}; } | ||
| 75 | +x_log_window(){ test -f "$X_LOG_FILE" || return 0; tail -n +$((x_LOG_START + 1)) "$X_LOG_FILE" 2>/dev/null; } | ||
| 76 | + | ||
| 77 | +x_assert_log_contains() { | ||
| 78 | + local pattern=$1 msg=${2:-"expected log not found: $1"} | ||
| 79 | + x_log_window | grep -q -- "$pattern" 2>/dev/null | ||
| 80 | + CHECK_RESULT $? 0 0 "$msg" | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +x_assert_not_log_contains() { | ||
| 84 | + local pattern=$1 msg=${2:-"unexpected log found: $1"} | ||
🟡 Medium Priority 变更行: 函数 但第 84 行的默认消息是 建议:将默认消息改为 ![]() ![]() | |||
| 85 | + if x_log_window | grep -q -- "$pattern" 2>/dev/null; then | ||
| 86 | + CHECK_RESULT 1 0 0 "$msg" | ||
| 87 | + else | ||
| 88 | + CHECK_RESULT 0 0 0 "$msg" | ||
| 89 | + fi | ||
| 90 | +} | ||


社区文档建议以社区来署名 # Copyright (c) <年份> openEuler Community. All rights reserved.