Skip to content

Commit 5dd2bb1

Browse files
committed
better support for single test runs, better match catch2 output in TestResult, basic assertion tracking
1 parent 0de0abb commit 5dd2bb1

File tree

9 files changed

+127
-64
lines changed

9 files changed

+127
-64
lines changed

example/always_passes.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
#include <iostream>
33

44
namespace demo {
5-
template <typename T= void>
6-
auto zoinks() -> T {
5+
auto zoinks() {
76
ASSERT(false, "oh no");
87
}
98

@@ -12,8 +11,4 @@ auto zoinks() -> T {
1211
std::cerr << "bar\n";
1312
zoinks();
1413
}
15-
16-
[[ = rsl::test, = rsl::expect_failure ]] void always_fails() {
17-
ASSERT(false, "oh no");
18-
}
1914
} // namespace demo

include/rsl/testing/assert.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,21 @@
33
#include <vector>
44
#include <string_view>
55

6-
namespace rsl::testing::_testing_impl {
6+
namespace rsl::testing {
77
struct AssertionInfo {
88
std::string_view raw;
99
std::string_view expanded;
1010
bool success;
1111
};
12-
12+
namespace _testing_impl {
1313
struct AssertionTracker {
1414
std::vector<AssertionInfo> assertions;
1515
std::string test_name;
1616
};
1717

1818
AssertionTracker& assertion_counter();
19-
} // namespace rsl::testing::_testing_impl
19+
} // namespace _testing_impl
20+
} // namespace rsl::testing
2021

2122
#define LIBASSERT_ASSERT_MAIN_BODY(expr, \
2223
name, \
@@ -38,5 +39,5 @@ AssertionTracker& assertion_counter();
3839
libassert_params LIBASSERT_VA_ARGS(__VA_ARGS__) \
3940
pretty_function_arg); \
4041
}
41-
42+
#define LIBASSERT_BREAK_ON_FAIL
4243
#include <libassert/assert.hpp>

include/rsl/testing/test.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,15 @@ struct TestNamespace {
105105
bool operator==(iterator const& other) const;
106106
};
107107

108+
[[nodiscard]] bool is_empty() const { return tests.empty() && children.empty(); }
108109
[[nodiscard]] iterator begin() const { return iterator{*this}; }
109110
[[nodiscard]] static iterator end() { return {}; }
110111
void insert(const Test& test, size_t i = 0);
111112

112113
[[nodiscard]] std::size_t count() const;
113114
bool run(Reporter* reporter);
115+
116+
void filter(std::span<std::string const> parts);
114117
};
115118

116119
struct TestRoot : TestNamespace {

include/rsl/testing/test_case.hpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
#pragma once
22
#include <functional>
3+
#include <string>
4+
#include "assert.hpp"
35

46
namespace rsl::testing {
57

68
struct TestResult {
79
class Test const* test;
810
std::string name;
9-
1011
bool passed;
11-
std::string error;
12+
double duration_ms;
1213

14+
std::string failure;
15+
std::string exception;
1316
std::string stdout;
1417
std::string stderr;
15-
double duration_ms;
18+
19+
std::vector<AssertionInfo> assertions;
1620
};
1721

1822
struct TestCase {

src/main/main.cpp

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,71 @@
1-
#include "rsl/testing/output.hpp"
2-
#include "rsl/testing/test.hpp"
3-
#define RSLTEST_SKIP
41
#include <memory>
5-
#include <rsl/test>
2+
#include <ranges>
63
#include <string_view>
74
#include <string>
85

96
#include <rsl/config>
7+
#include <rsl/testing/output.hpp>
8+
#include <rsl/testing/test.hpp>
109
#include <rsl/testing/util.hpp>
1110
#include <rsl/testing/_testing_impl/factory.hpp>
1211
#include "output.hpp"
1312

14-
class[[= rsl::cli::description("rsl::test (in Catch2 v3.8.1 compatibility mode)")]] TestConfig : public rsl::cli {
13+
std::string_view base_name(std::string_view name) {
14+
auto lt = name.find('<');
15+
auto paren = name.find('(');
16+
auto pos = std::min(lt, paren);
17+
return pos == std::string_view::npos ? name : name.substr(0, pos);
18+
}
19+
20+
std::vector<std::string_view> split_filter_path(std::string_view filter) {
21+
if (filter.starts_with("::")) {
22+
filter.remove_prefix(2);
23+
}
24+
25+
std::vector<std::string_view> parts;
26+
27+
while (true) {
28+
auto next = filter.find("::");
29+
auto templ = filter.find('<');
30+
31+
if (templ != std::string_view::npos && (next == std::string_view::npos || templ < next)) {
32+
parts.push_back(filter);
33+
break;
34+
}
35+
36+
if (next == std::string_view::npos) {
37+
parts.push_back(filter);
38+
break;
39+
}
40+
41+
parts.push_back(filter.substr(0, next));
42+
filter.remove_prefix(next + 2);
43+
}
44+
45+
return parts;
46+
}
47+
48+
void filter_test_tree(rsl::testing::TestRoot& root,
49+
std::string_view filter,
50+
std::vector<std::string> subfilters) {
51+
if (filter.empty() || filter == "[.],*") {
52+
return;
53+
}
54+
55+
auto parts = split_filter_path(filter);
56+
57+
for (auto part : std::ranges::reverse_view(parts)) {
58+
subfilters.insert(subfilters.begin(), std::string(part));
59+
}
60+
if (!subfilters.empty()) {
61+
// cut off template arguments and parameters
62+
subfilters.back() = base_name(subfilters.back());
63+
}
64+
root.filter(subfilters);
65+
}
66+
67+
class[[= rsl::cli::description("rsl::test (in Catch2 v3.8.1 compatibility mode)")]] TestConfig
68+
: public rsl::cli {
1569
rsl::testing::TestRoot tree;
1670
std::vector<std::string> sections;
1771
std::unique_ptr<rsl::testing::Output> _output;
@@ -38,35 +92,7 @@ class[[= rsl::cli::description("rsl::test (in Catch2 v3.8.1 compatibility mode)"
3892
, _output(new rsl::testing::ConsoleOutput()) {}
3993

4094
void apply_filter() {
41-
if (filter.empty() || filter == "[.],*") {
42-
return;
43-
}
44-
std::string_view filter_view = filter;
45-
std::vector<std::string> names;
46-
std::size_t idx = 0;
47-
while ((idx = filter_view.find(',')) != filter_view.npos) {
48-
names.emplace_back(filter_view.substr(0, idx));
49-
filter_view.remove_prefix(idx + 1);
50-
}
51-
if (!filter_view.empty()) {
52-
names.emplace_back(filter_view);
53-
}
54-
55-
rsl::testing::TestRoot new_tree;
56-
// rebuild the test tree with filters applied
57-
for (auto&& test : tree) {
58-
auto full_name = rsl::join_str(test.full_name, "::");
59-
60-
for (auto const& name : names) {
61-
if (name == full_name || name == test.name || name == test.full_name[0]) {
62-
new_tree.insert(test);
63-
}
64-
}
65-
// if (!std::regex_search(full_name, std::regex{filter})) {
66-
// continue;
67-
// }
68-
}
69-
tree = new_tree;
95+
filter_test_tree(tree, filter, sections);
7096
}
7197

7298
static void print_tests(rsl::testing::TestNamespace const& current, std::size_t indent = 0) {

src/main/reporters/catch2xml.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <numeric>
12
#include <print>
23
#include <string>
34
#include <optional>
@@ -101,7 +102,13 @@ struct TestCase {
101102
void update_results() {
102103
for (auto& section : sections) {
103104
section.update_results();
105+
106+
// TODO this is no good, we need to count skips in sections
107+
result.skips += unsigned(section.results.skipped);
104108
}
109+
result.success = std::ranges::none_of(sections, [](Section const& section) {
110+
return section.results.failures > section.results.expectedFailures;
111+
});
105112
}
106113
};
107114

@@ -194,7 +201,7 @@ class[[= rename("xml")]] Catch2XmlReporter : public Reporter::Registrar<Catch2Xm
194201
if (result.passed) {
195202
++section->results.successes;
196203
} else {
197-
section->failure = {.value = result.error};
204+
section->failure = {.value = result.failure};
198205
++section->results.failures;
199206
}
200207
section->results.durationInSeconds += result.duration_ms / 1000.;

src/main/reporters/terminal.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class [[=rename("plain")]] ConsoleReporter : public Reporter::Registrar<ConsoleR
2626
reset,
2727
result.name,
2828
result.duration_ms);
29-
std::print("{}ERROR{}: {}\n", color[1], reset, result.error);
29+
std::print("{}ERROR{}: {}\n", color[1], reset, result.failure);
3030
std::print("==== {}stdout{} ====\n{}\n", color[1], reset, result.stdout);
3131
std::print("==== {}stderr{} ====\n{}\n", color[1], reset, result.stderr);
3232
}

src/main/reporters/xml.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class [[=rename("junit")]] JUnitXmlReporter : public Reporter::Registrar<JUnitXm
2323
void after_test(TestResult const& result) override {
2424
auto node = testcase{.name=std::string(result.name), .time=result.duration_ms / 1000.};
2525
if (!result.passed) {
26-
node.failure = result.error + "\n";
26+
node.failure = result.failure + "\n";
2727
}
2828
suite.tests.push_back(node);
2929
}

src/test.cpp

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <algorithm>
2+
#include <ranges>
23
#include <print>
34
#include <chrono>
45

@@ -26,20 +27,21 @@ void cleanup_frames(cpptrace::stacktrace& trace, std::string_view test_name) {
2627

2728
void failure_handler(libassert::assertion_info const& info) {
2829
// libassert::enable_virtual_terminal_processing_if_needed(); // for terminal colors on windows
29-
constexpr bool Colorize = true;
30+
constexpr bool Colorize = false;
3031
auto width = libassert::terminal_width(libassert::stderr_fileno);
3132
const auto& scheme = Colorize ? libassert::get_color_scheme() : libassert::color_scheme::blank;
3233
std::string message = std::string(info.action()) + " at " + info.location() + ":";
3334
if (info.message) {
3435
message += " " + *info.message;
3536
}
3637
message += "\n";
37-
message += info.statement(scheme) + info.print_binary_diagnostics(width, scheme) +
38-
info.print_extra_diagnostics(width, scheme);// + info.print_stacktrace(width, scheme);
38+
message +=
39+
info.statement(scheme) + info.print_binary_diagnostics(width, scheme) +
40+
info.print_extra_diagnostics(width, scheme); // + info.print_stacktrace(width, scheme);
3941

4042
auto trace = info.get_stacktrace();
41-
cleanup_frames(trace, rsl::testing::_testing_impl::assertion_counter().test_name);
42-
message += trace.to_string(true);
43+
cleanup_frames(trace, rsl::testing::_testing_impl::assertion_counter().test_name);
44+
message += trace.to_string(Colorize);
4345
throw rsl::testing::assertion_failure(message);
4446
}
4547

@@ -82,15 +84,15 @@ bool TestNamespace::run(Reporter* reporter) {
8284

8385
std::vector<TestResult> results;
8486
for (auto const& test_run : test.get_tests()) {
85-
auto& tracker = _testing_impl::assertion_counter();
87+
auto& tracker = _testing_impl::assertion_counter();
8688
tracker.assertions = {};
87-
tracker.test_name = join_str(test.full_name, "::");
89+
tracker.test_name = join_str(test.full_name, "::");
8890

8991
reporter->before_test(test_run);
9092
auto result = test_run.run();
9193
reporter->after_test(result);
9294

93-
std::println("assertion count: {}", tracker.assertions.size());
95+
result.assertions = tracker.assertions;
9496
results.push_back(result);
9597
}
9698

@@ -116,16 +118,16 @@ TestResult TestCase::run() const {
116118
ret.duration_ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
117119
return ret;
118120
} catch (assertion_failure const& failure) {
119-
ret.error += failure.what();
121+
ret.failure += failure.what();
120122
} catch (std::exception const& exc) { //
121-
ret.error += exc.what();
123+
ret.exception += exc.what();
122124
} catch (std::string const& msg) { //
123-
ret.error += msg;
125+
ret.failure += msg;
124126
} catch (std::string_view msg) { //
125-
ret.error += msg;
127+
ret.failure += msg;
126128
} catch (char const* msg) { //
127-
ret.error += msg;
128-
} catch (...) { ret.error += "unknown exception thrown"; }
129+
ret.failure += msg;
130+
} catch (...) { ret.exception += "unknown exception thrown"; }
129131

130132
ret.passed = test->expect_failure;
131133
return ret;
@@ -197,6 +199,31 @@ std::size_t TestNamespace::count() const {
197199
return total;
198200
}
199201

202+
void TestNamespace::filter(std::span<std::string const> parts) {
203+
if (parts.empty()) {
204+
return;
205+
}
206+
207+
std::string_view current = parts.front();
208+
std::span<std::string const> next = parts.subspan(1);
209+
210+
auto it = std::ranges::find_if(children, [&](TestNamespace& ns) { return ns.name == current; });
211+
212+
if (it != children.end()) {
213+
tests.clear();
214+
it->filter(next);
215+
if (it->children.empty() && it->tests.empty()) {
216+
children.clear();
217+
} else {
218+
children = {*it};
219+
}
220+
return;
221+
} else {
222+
std::erase_if(tests, [&](const Test& t) { return t.name != current; });
223+
children.clear();
224+
}
225+
}
226+
200227
TestRoot get_tests() {
201228
TestRoot root;
202229
for (auto test_def : rsl::testing::_testing_impl::registry()) {

0 commit comments

Comments
 (0)