Skip to content

Commit 7aee9c3

Browse files
committed
feat(solutions): 参考答案自己成为一个 mcpp 工程,mcpp test -p solutions 直接跑
在此之前 solutions/ 只是一堆躺在磁盘上的 .cpp:不是工作区成员,mcpp 从头到 尾不认识它。想验证答案对不对,唯一的办法是把 52 个文件覆盖到 src/*/tests/ 上、跑一遍、再 git 还原 —— 一套带副作用、要求工作树干净、只有 CI 脚本会用 的流程。学习者或贡献者想确认「答案是好的」,没有一条命令可以跑。 现在 solutions/ 是一个正经工程: solutions/mcpp.toml name=solutions, c++23, 依赖 d2x solutions/tests/<std>/... 52 份答案,就是它的测试 于是: mcpp test -p solutions # 52 passed; 0 failed mcpp test -p solutions cpp11 # 按子串筛 零副作用、不碰练习目录、任何人随手可跑。布局与练习一一对应,测试名 (intro/hello-mcpp、cpp11/00-auto-and-decltype/0) 直接对回练习文件。 配置刻意与练习工程一致(c++23 + d2x),否则「答案能过」推不出「答案放进 练习里也能过」。 覆盖-还原那套 e2e 保留 —— 它验的是另一件事:答案放到练习的位置上也成立, 且练习在未完成时确实不通过。两条互补:新的这条挂了说明答案本身是错的, 旧的那条挂了说明答案与练习对不上。 - e2e.sh 新增 solutions_selftest(),并核对测试条数 == 答案文件数:测试发现 是按 tests/**/*.cpp 自动扫的,布局一改就可能一个都没扫到,而「0 个测试」 在 mcpp 眼里同样是 "test result ok" - CI 每档平台新增独立一段 `mcpp test -p solutions`,排在覆盖-还原之前, 失败定位最短 - e2e.sh / checker-e2e.sh / 文件名核对 job 的路径映射跟随改为 solutions/tests/
1 parent eb5ce5e commit 7aee9c3

57 files changed

Lines changed: 78 additions & 6 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/dslings-ref-ci.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,15 @@ jobs:
181181
fi
182182
echo "OK: 无 MSVC STL 时回落到 winlibs MinGW (x86_64-windows-gnu)"
183183
184+
# 参考答案自检 —— 单独一段,因为它是最便宜也最直白的一条:solutions/ 自己
185+
# 就是一个 mcpp 工程,一条命令、零副作用,红了就说明答案本身编不过或跑不过,
186+
# 跟练习目录、跟 d2x 都没关系。放在覆盖-还原那套之前,失败定位最短。
187+
- name: mcpp test -p solutions (reference answers must be all green)
188+
run: mcpp test -p solutions
189+
184190
# 第一层:`mcpp test` —— pristine 全部不通过 + 覆盖答案后全部通过
191+
# (e2e.sh 内部还会再跑一遍上面那条自检并核对测试条数,防止布局改动后
192+
# tests/**/*.cpp 一个都没扫到却依然 "test result ok")
185193
- name: mcpp test (pristine fails, solutions all pass)
186194
run: bash d2x/buildtools/tests/e2e.sh ${{ matrix.langs }}
187195

@@ -209,8 +217,8 @@ jobs:
209217
rel="${f#src/}"
210218
std="${rel%%/*}"
211219
rest="${rel#*/tests/}"
212-
if [ ! -f "solutions/$std/$rest" ]; then
213-
echo "::warning::missing solutions/$std/$rest (paired with $f)"
220+
if [ ! -f "solutions/tests/$std/$rest" ]; then
221+
echo "::warning::missing solutions/tests/$std/$rest (paired with $f)"
214222
missing=$((missing+1))
215223
fi
216224
done

d2x/buildtools/tests/checker-e2e.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ trap restore EXIT
5555
# —— 覆盖参考答案(zh/en 共用同一份 solutions)——
5656
n_expected=0
5757
while IFS= read -r sol; do
58-
rel="${sol#solutions/}" # intro/hello-mcpp.cpp | cpp11/00-x/0.cpp
58+
rel="${sol#solutions/tests/}" # intro/hello-mcpp.cpp | cpp11/00-x/0.cpp
5959
std="${rel%%/*}"; rest="${rel#*/}"
6060
dst="${PREFIX}${std}/tests/${rest}"
6161
if [[ ! -f "$dst" ]]; then
@@ -64,7 +64,7 @@ while IFS= read -r sol; do
6464
fi
6565
cp "$sol" "$dst"
6666
n_expected=$((n_expected + 1))
67-
done < <(find solutions -name '*.cpp' | sort)
67+
done < <(find solutions/tests -name '*.cpp' | sort)
6868

6969
if [[ "$n_expected" -eq 0 ]]; then
7070
echo "CHECKER-E2E($LANG_SEL) FAIL: 一份参考答案都没找到"

d2x/buildtools/tests/e2e.sh

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
# 1) 每个练习未完成时不通过 (pristine 全量 mcpp test 必须 0 passed)
55
# 2) 每份参考答案放进去后通过 (overlay 后全量 mcpp test 必须全绿)
66
#
7+
# 外加一条独立的答案自检:solutions/ 自己是一个 mcpp 工程,
8+
# `mcpp test -p solutions` 直接编译运行全部答案,零副作用(见 solutions_selftest)。
9+
#
710
# 这是 rustlings `cargo dev check --require-solutions` 的等价物。
811
#
912
# 两道防线(均源自历史缺陷,背景见 .agents/docs 参考文档):
@@ -54,7 +57,7 @@ run_lang() {
5457
# —— 覆盖参考答案 (zh/en 共用同一份 solutions) ——
5558
local n_overlaid=0 sol rel std rest dst
5659
while IFS= read -r sol; do
57-
rel="${sol#solutions/}" # intro/hello-mcpp.cpp | cpp11/00-x/0.cpp
60+
rel="${sol#solutions/tests/}" # intro/hello-mcpp.cpp | cpp11/00-x/0.cpp
5861
std="${rel%%/*}"; rest="${rel#*/}"
5962
dst="${prefix}${std}/tests/${rest}"
6063
if [[ ! -f "$dst" ]]; then
@@ -64,7 +67,7 @@ run_lang() {
6467
fi
6568
cp "$sol" "$dst"
6669
n_overlaid=$((n_overlaid + 1))
67-
done < <(find solutions -name '*.cpp' | sort)
70+
done < <(find solutions/tests -name '*.cpp' | sort)
6871

6972
# —— 断言 2 + 防线 2: overlay 后全绿,且 passed 总数与答案数一致 ——
7073
local total_passed=0 ok=1
@@ -92,6 +95,39 @@ run_lang() {
9295
echo "E2E($label) solutions: $total_passed/$n_overlaid 参考答案全部通过 ✓"
9396
}
9497

98+
# 参考答案自检:solutions/ 自己就是一个 mcpp 工程,直接编译运行,不碰练习目录。
99+
#
100+
# 这是最便宜也最直白的一条 —— 一条命令、零副作用、谁都能在本地复现:
101+
# mcpp test -p solutions
102+
# 底下 run_lang 的覆盖-还原流程验的是另一件事(答案「放进练习的位置」也成立,
103+
# 且练习在未完成时确实不通过),两者互补:这条挂了说明答案本身就是错的,那条
104+
# 挂了说明答案与练习对不上。
105+
solutions_selftest() {
106+
local n_files out passed
107+
n_files=$(find solutions/tests -name '*.cpp' | wc -l | tr -d ' ')
108+
if [[ "$n_files" -eq 0 ]]; then
109+
echo "E2E(solutions) FAIL: solutions/tests 下一份答案都没有"
110+
return 1
111+
fi
112+
113+
out=$(mcpp test -p solutions 2>&1)
114+
if ! echo "$out" | grep -q "test result ok"; then
115+
echo "E2E(solutions) FAIL: mcpp test -p solutions 未全绿:"
116+
echo "$out" | grep -E "FAIL|failures" -A10 | head -20
117+
return 1
118+
fi
119+
120+
passed=$(echo "$out" | grep -oE '[0-9]+ passed' | grep -oE '^[0-9]+' | tail -1)
121+
passed="${passed:-0}"
122+
# 防空转:测试发现是按 tests/**/*.cpp 自动扫的,布局一改就可能一个都没扫到,
123+
# 而「0 个测试」在 mcpp 眼里同样是 "test result ok"。
124+
if [[ "$passed" -ne "$n_files" ]]; then
125+
echo "E2E(solutions) FAIL: solutions/tests 下有 $n_files 份答案,但只跑了 $passed 个测试"
126+
return 1
127+
fi
128+
echo "E2E(solutions) 自检: mcpp test -p solutions —— $passed/$n_files 全部通过 ✓"
129+
}
130+
95131
provider_smoke() {
96132
# Provider 协议冒烟:枚举数量、check 的关键事件
97133
local n
@@ -110,6 +146,7 @@ provider_smoke() {
110146
}
111147

112148
rc=0
149+
solutions_selftest || rc=1
113150
provider_smoke || rc=1
114151
if [[ "$MODE" == "zh" || "$MODE" == "all" ]]; then run_lang "src/" zh || rc=1; fi
115152
if [[ "$MODE" == "en" || "$MODE" == "all" ]]; then run_lang "src/en/" en || rc=1; fi

mcpp.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
# d2mcpp 根 workspace。练习即测试:每个 C++ 标准对应 src/ 下的一个 mcpp 工程,
22
# 练习即该工程的 tests/,mcpp test 的测试报告即学习进度;d2x checker 复用同一条链路。
33
# d2x/ 目录包含练习库(import d2x)与 Provider(buildtools/)。
4+
#
5+
# solutions/ 同理,是参考答案自己的工程:`mcpp test -p solutions` 直接编译运行
6+
# 全部答案,应当全绿。它和练习工程的关系是「同一份代码的两种状态」——
7+
# src/ 下是挖空的,solutions/ 下是填好的,两边配置一致才可比。
48
[workspace]
59
members = [
610
"d2x",
711
"src/intro", "src/cpp11", "src/cpp14",
812
"src/en/intro", "src/en/cpp11", "src/en/cpp14",
13+
"solutions",
914
"d2x/buildtools",
1015
]

solutions/mcpp.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# 参考答案工程。每份答案就是这里的一个测试:
2+
# mcpp test -p solutions 全部参考答案(应当全绿)
3+
# mcpp test -p solutions <子串> 只跑匹配的答案
4+
#
5+
# 为什么答案自己也是一个 mcpp 工程,而不只是一堆躺着的 .cpp:
6+
# 答案要能被验证,而验证的唯一可信方式就是真的编译并运行它。做成工程之后
7+
# `mcpp test -p solutions` 就是一条谁都能跑的命令 —— CI 跑它,贡献者提 PR 前
8+
# 也跑它,不需要先把文件覆盖到练习目录里再 git 还原那一套。
9+
#
10+
# 布局与练习一一对应,tests/ 下的相对路径就是两边的对应关系:
11+
# solutions/tests/<std>/<章节>/<序号>.cpp ←→ src[/en]/<std>/tests/<章节>/<序号>.cpp
12+
# 所以测试名(intro/hello-mcpp、cpp11/00-auto-and-decltype/0)一眼就能对回练习。
13+
#
14+
# 与练习工程共用同一套配置(c++23 + d2x),否则「答案能过」证明不了「答案放进
15+
# 练习里也能过」—— 两边的编译条件必须是同一个。
16+
[package]
17+
name = "solutions"
18+
version = "0.1.0"
19+
standard = "c++23"
20+
21+
[dependencies]
22+
d2x = { path = "../d2x" }
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)