Skip to content

Commit 059d97b

Browse files
test: the skeleton's tests in mtest 0.3.0's form; --case reaches the cases a listed case declares
Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent d8410c6 commit 059d97b

10 files changed

Lines changed: 94 additions & 34 deletions

‎CHANGELOG.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
variables and reports its cases: the record names the protocol and each
1515
case's result and failures, a case a crash left open failed, and a failing
1616
program's report names its failed cases with their places. `--case PATTERN`
17-
runs the matching cases, `--cases` lists them, `--seed N` states the seed. A
17+
runs the matching cases -- a name below a listed case selects the case that
18+
case declares, which is reported when it runs (P3.4), and a pattern below it
19+
runs the listed case whole --, `--cases` lists them, `--seed N` states the seed. A
1820
program that implements no protocol is one case and receives none of the
1921
variables. `--no-run` records each built test with its program and the files
2022
it is compiled from; `--print-launch` states the protocol; the feature
2123
`test-protocol` names all of it (e2e 1035).
22-
- `mcpp new` scaffolds tests with mtest: the skeleton's test is a file of
23-
mtest's cases with no `main`, with the dev-dependency `mcpplibs.mtest`, and
24+
- `mcpp new` scaffolds tests with mtest 0.3.0: the skeleton's test is a file of
25+
mtest's cases with no `main` (`UTest smoke{"...", [] { ... }}`: an object is a
26+
case, its type its kind), with the dev-dependency `mcpplibs.mtest`, and
2427
`--template lib` scaffolds a library -- a module, a partition it exports and
2528
the partition's implementation, named after the package -- with its test
2629
(docs/01; e2e 1037).

‎docs/01-getting-started.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,15 @@ int main() {
102102
```
103103

104104
The test is a file of [mtest](https://github.com/mcpp-community/mtest)'s cases
105-
with no `main`, and the manifest names it a dev-dependency:
105+
with no `main`, and the manifest names it a dev-dependency. Each object is one
106+
case; its type is its kind (`UTest`, `E2E`, `CTS`, `Bench`), and the attributes
107+
after its name state how it runs (resources, time limit, isolation, retries):
106108

107109
```cpp
108110
import mcpplibs.mtest;
109111
using namespace mcpplibs::mtest;
110112

111-
suite smoke{"smoke", [] {
112-
"the project builds and its test runs"_test = [] { expect(eq(1 + 1, 2)); };
113-
}};
113+
UTest smoke{"the project builds and its test runs", [] { expect(eq(1 + 1, 2)); }};
114114
```
115115
116116
### Creating a library

‎docs/08-testing.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,12 @@ mcpp test --seed 42 # the seed of anything random in the cases
327327
- A failing program's report names each failed case with the place of its
328328
first failure: `case fs/read failed at tests/fs.cpp:12: ...`.
329329
- A pattern of `--case` matches with `*` and `?`; a name without either
330-
matches itself and the cases below it (`fs` matches `fs/read`). Under
331-
`--case`, a program with no matching case, and every program that implements
332-
no protocol, does not run.
330+
matches itself and the cases below it (`fs` matches `fs/read`). A case that
331+
a listed case declares is reported when it runs and need not be listed
332+
(protocol P3.4; mtest's `UTest fs{"fs", [] { "read"_test = ...; }}` lists
333+
`fs`): a name below a listed case selects that one case, and a pattern below
334+
it runs the listed case whole. Under `--case`, a program with no matching
335+
case, and every program that implements no protocol, does not run.
333336
- A program that implements no protocol is one case, judged by its exit status,
334337
and receives none of the variables, even when mcpp's own environment has
335338
them.

‎docs/zh/01-getting-started.md‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,14 @@ int main() {
100100
```
101101

102102
测试是一个不定义 `main` 的 [mtest](https://github.com/mcpp-community/mtest) 用例文件,manifest 把 mtest
103-
列为 dev-dependency:
103+
列为 dev-dependency。每个对象是一个用例:类型即类别(`UTest`、`E2E`、`CTS`、`Bench`),名字之后的属性说明它如何
104+
运行(资源、时限、隔离、重试):
104105

105106
```cpp
106107
import mcpplibs.mtest;
107108
using namespace mcpplibs::mtest;
108109

109-
suite smoke{"smoke", [] {
110-
"the project builds and its test runs"_test = [] { expect(eq(1 + 1, 2)); };
111-
}};
110+
UTest smoke{"the project builds and its test runs", [] { expect(eq(1 + 1, 2)); }};
112111
```
113112
114113
### 创建库

‎docs/zh/08-testing.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ mcpp test --seed 42 # 用例中随机性的种子
276276
原因是程序的退出码。
277277
- 失败程序的报告列出每个失败的用例及其第一处失败的位置:`case fs/read failed at tests/fs.cpp:12: ...`。
278278
- `--case` 的模式以 `*` 与 `?` 匹配;不含二者的名字匹配它自身及其下的用例(`fs` 匹配 `fs/read`)。
279+
已列举的用例所声明的用例在运行时报告,可以不被列举(协议 P3.4;mtest 的 `UTest fs{"fs", [] { "read"_test = ...; }}`
280+
只列举 `fs`):已列举用例之下的名字选中该用例,之下的模式则完整运行该已列举用例。
279281
使用 `--case` 时,没有匹配用例的程序与所有未实现协议的程序不运行。
280282
- 未实现协议的程序是一个用例,以退出码判定,并且收不到任何协议变量,即使 mcpp 自身的环境中有它们。
281283

‎src/build/execute.cppm‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5162,11 +5162,8 @@ static int test_run_member(TestBuild& tb, const TestOptions& testOpts,
51625162
continue;
51635163
}
51645164
std::string lines;
5165-
for (const auto& c : report.listed)
5166-
if (std::ranges::any_of(testOpts.cases, [&](const std::string& p) {
5167-
return mcpp::build::test_protocol::matches(c.id, p);
5168-
}))
5169-
lines += c.id + "\n";
5165+
for (const auto& id : mcpp::build::test_protocol::selection(report.listed, testOpts.cases))
5166+
lines += id + "\n";
51705167
if (lines.empty()) { ++notSelected; continue; }
51715168
r.select = r.protocolDir / "select.txt";
51725169
std::ofstream(*r.select, std::ios::binary) << lines;

‎src/build/test_protocol.cppm‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ inline constexpr int kVersion = 1;
1818

1919
// The framework mcpp scaffolds tests with and suggests where a package has
2020
// none (docs/01, docs/08): mtest, by this release, from its repository.
21-
inline constexpr std::string_view kMtestRelease = "0.2.0";
21+
inline constexpr std::string_view kMtestRelease = "0.3.0";
2222
inline constexpr std::string_view kMtestRepo = "https://github.com/mcpp-community/mtest";
2323

2424
// The protocol's variables, removed from the environment a program inherits
@@ -98,6 +98,14 @@ variables(std::string_view mode, const std::filesystem::path& report,
9898
// it equals or the identifiers it is a `/`-prefix of (`fs` matches `fs/read`).
9999
bool matches(std::string_view id, std::string_view pattern);
100100

101+
// The identifiers `mcpp test --case` passes a program (P4.1), from its listing:
102+
// each listed case a pattern matches; for a pattern under a listed case -- a
103+
// case that case declares, which the program reports when it runs and need not
104+
// list (P3.4) -- the pattern itself when it names one case, else the listed
105+
// case, all of whose cases then run.
106+
std::vector<std::string> selection(const std::vector<Case>& listed,
107+
const std::vector<std::string>& patterns);
108+
101109
// The JSON of a report in a `mcpp test --message-format json` record
102110
// (docs/50): `cases` and `missing`.
103111
nlohmann::json cases_json(const Report& r);
@@ -278,6 +286,26 @@ bool matches(std::string_view id, std::string_view pattern) {
278286
&& id[pattern.size()] == '/');
279287
}
280288

289+
std::vector<std::string> selection(const std::vector<Case>& listed,
290+
const std::vector<std::string>& patterns) {
291+
std::vector<std::string> out;
292+
auto add = [&](const std::string& id) {
293+
if (std::ranges::find(out, id) == out.end()) out.push_back(id);
294+
};
295+
for (const auto& p : patterns) {
296+
bool found = false;
297+
for (const auto& c : listed)
298+
if (matches(c.id, p)) { add(c.id); found = true; }
299+
if (found) continue;
300+
const auto under = std::ranges::find_if(listed, [&](const Case& c) {
301+
return p.size() > c.id.size() && p.starts_with(c.id) && p[c.id.size()] == '/';
302+
});
303+
if (under == listed.end()) continue;
304+
add(p.find_first_of("*?") == std::string::npos ? p : under->id);
305+
}
306+
return out;
307+
}
308+
281309
nlohmann::json cases_json(const Report& r) {
282310
nlohmann::json cases = nlohmann::json::array();
283311
for (const auto& c : r.cases) {

‎src/scaffold/create.cppm‎

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -488,28 +488,24 @@ std::string hello(std::string_view name) {
488488
"cannot create scaffold test directory: {}", ec.message()));
489489
return 1;
490490
}
491-
constexpr std::string_view smoke = R"(// A test of {{project.name}}: each `_test` is a case `mcpp test` lists, selects and reports.
492-
// More tests are more files under tests/; a file defines no `main`.
491+
constexpr std::string_view smoke = R"(// A test of {{project.name}}: each object is a case `mcpp test` lists, selects and reports.
492+
// More tests are more objects, or more files under tests/; a file defines no `main`.
493493
import mcpplibs.mtest;
494494
495495
using namespace mcpplibs::mtest;
496496
497-
suite smoke{"smoke", [] {
498-
"the project builds and its test runs"_test = [] { expect(eq(1 + 1, 2)); };
499-
}};
497+
UTest smoke{"the project builds and its test runs", [] { expect(eq(1 + 1, 2)); }};
500498
)";
501-
constexpr std::string_view greeting = R"(// The cases of {{project.module}}: each `_test` is one `mcpp test` lists, selects and reports.
502-
// More tests are more files under tests/; a file defines no `main`.
499+
constexpr std::string_view greeting = R"(// The cases of {{project.module}}: each object is one `mcpp test` lists, selects and reports.
500+
// More tests are more objects, or more files under tests/; a file defines no `main`.
503501
import std;
504502
import mcpplibs.mtest;
505503
import {{project.module}};
506504
507505
using namespace mcpplibs::mtest;
508506
509-
suite greeting{"hello", [] {
510-
"greets by name"_test = [] {
511-
expect(eq({{project.cxxNamespace}}::hello("mcpp"), std::string("Hello, mcpp!")));
512-
};
507+
UTest greeting{"hello greets by name", [] {
508+
expect(eq({{project.cxxNamespace}}::hello("mcpp"), std::string("Hello, mcpp!")));
513509
}};
514510
)";
515511
mcpp::scaffold::RenderVars vars{

‎tests/e2e/1038_test_programs_of_a_directory_and_the_tests_a_change_affects.sh‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
# E. Under a shipping profile, `mcpp build` and `mcpp test` are one configuration: the test
1818
# compiles none of the code under test again, which is trimmed as the build trims it, and a
1919
# test program's own file keeps its path (`__FILE__` finds the test's directory).
20+
# F. A case a listed case declares (protocol P3.4) is selected by its name alone; a pattern below
21+
# the listed case runs it whole.
2022
set -e
2123
TMP=$(mktemp -d)
2224
trap 'rm -rf "$TMP"' EXIT
@@ -34,13 +36,13 @@ cat > tests/unit/a.cpp <<'CPP'
3436
import mcpplibs.mtest;
3537
import app.calc;
3638
using namespace mcpplibs::mtest;
37-
suite a{"a", [] { "doubles"_test = [] { expect(eq(twice(2), 4)); }; }};
39+
UTest a{"doubles", [] { expect(eq(twice(2), 4)); }};
3840
CPP
3941
cat > tests/unit/b.cpp <<'CPP'
4042
import std;
4143
import mcpplibs.mtest;
4244
using namespace mcpplibs::mtest;
43-
suite b{"b", [] { "reads its data"_test = [] { expect(eq(std::string_view("tests/data/f.txt").size(), 16u)); }; }};
45+
UTest b{"reads its data", [] { expect(eq(std::string_view("tests/data/f.txt").size(), 16u)); }};
4446
CPP
4547
printf 'import std;\nint main() { return 0; }\n' > tests/e2e/plain.cpp
4648
printf 'data\n' > tests/data/f.txt
@@ -51,7 +53,7 @@ printf '\n[test]\nprograms = "directory"\n' >> mcpp.toml
5153
grep -q 'unit \.\.\. ok (.*2 cases)' a.log || fail "A: unit is not one program of two cases" a.log
5254
grep -q '^e2e \.\.\. ok' a.log || fail "A: the directory of a file with main" a.log
5355
"$MCPP" test --cases > a-cases.log 2>&1 || fail "A: --cases" a-cases.log
54-
grep -q 'a/doubles' a-cases.log && grep -q 'b/reads its data' a-cases.log || fail "A: the program's cases" a-cases.log
56+
grep -q 'doubles' a-cases.log && grep -q 'reads its data' a-cases.log || fail "A: the program's cases" a-cases.log
5557
# B: a file of plain main beside the library's marker.
5658
grep -q 'e2e: one case, its exit status' a-cases.log || fail "B: a program of plain main is listed as one case" a-cases.log
5759
printf 'int main() { return 0; }\n' > tests/e2e/second.cpp
@@ -113,4 +115,23 @@ set -- target/*/*/build.ninja
113115
grep -q "library rel@0.1.0/src/where.cppm" e-test.log || fail "E: the code under test is not trimmed as built" e-test.log
114116
[ "$(grep -cE 'obj/where\.m\.(o|obj) ' "$(dirname "$1")/.ninja_log")" = 1 ] || fail "E: the test compiled the library again" "$(dirname "$1")/.ninja_log"
115117

118+
# F
119+
cd "$TMP"
120+
"$MCPP" new grp > /dev/null 2>&1
121+
cd grp
122+
cat > tests/test_smoke.cpp <<'CPP'
123+
import mcpplibs.mtest;
124+
using namespace mcpplibs::mtest;
125+
UTest g{"g", [] {
126+
"one"_test = [] { expect(true); };
127+
"two"_test = [] { expect(true); };
128+
}};
129+
CPP
130+
"$MCPP" test --cases > f-cases.log 2>&1 || fail "F: --cases" f-cases.log
131+
grep -q 'test_smoke: 1 case' f-cases.log || fail "F: the object is the one listed case" f-cases.log
132+
"$MCPP" test --case 'g/one' > f1.log 2>&1 || fail "F: --case g/one" f1.log
133+
grep -q 'test_smoke \.\.\. ok (.*1 case)' f1.log || fail "F: a declared case is not selected alone" f1.log
134+
"$MCPP" test --case 'g/t*' > f2.log 2>&1 || fail "F: --case g/t*" f2.log
135+
grep -q 'test_smoke \.\.\. ok (.*3 cases)' f2.log || fail "F: a pattern below the listed case does not run it whole" f2.log
136+
116137
echo "PASS: 1038 test programs of a directory, and the tests a change affects"

‎tests/unit/test_test_protocol.cpp‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ TEST(TestProtocol, APatternMatchesIdentifiers) {
8787
EXPECT_EQ(tp::matches(row.id, row.pattern), row.matches) << row.id << " ~ " << row.pattern;
8888
}
8989

90+
TEST(TestProtocol, ASelectionReachesTheCasesAListedCaseDeclares) {
91+
// P3.4: `fields` is listed; the cases its body declares are reported when it runs.
92+
const std::vector<tp::Case> listed = {{"fields"}, {"empty"}};
93+
using V = std::vector<std::string>;
94+
EXPECT_EQ(tp::selection(listed, {"fields"}), V{"fields"});
95+
EXPECT_EQ(tp::selection(listed, {"fields/one"}), V{"fields/one"}); // passed as it is
96+
EXPECT_EQ(tp::selection(listed, {"fields/o*"}), V{"fields"}); // the listed case, all of it
97+
EXPECT_EQ(tp::selection(listed, {"e*", "fields/one", "fields"}), (V{"empty", "fields/one", "fields"}));
98+
EXPECT_EQ(tp::selection(listed, {"other/one"}), V{});
99+
}
100+
90101
TEST(TestProtocol, TheVariablesOfARun) {
91102
auto env = tp::variables("run", "/r", std::filesystem::path("/s"), "/t", 7);
92103
std::map<std::string, std::string> m(env.begin(), env.end());

0 commit comments

Comments
 (0)