1+ #include < string>
2+ #include < ranges>
3+ #include < vector>
4+ #include < functional>
5+ #include < chrono>
6+ #include < print>
7+
8+ #include < rsl/source_location>
9+ #include < rsl/testing/assert.hpp>
10+ #include < rsl/testing/test.hpp>
11+ #include < rsl/testing/result.hpp>
12+ #include < rsl/testing/output.hpp>
13+ #include < rsl/testing/util.hpp>
14+
15+ #include < cpptrace/basic.hpp>
16+ #include < cpptrace/utils.hpp>
17+
18+ #include " capture.hpp"
19+ #include " coverage/coverage.hpp"
20+
21+ namespace {
22+ void cleanup_frames (cpptrace::stacktrace& trace, std::string_view test_name) {
23+ std::vector<cpptrace::stacktrace_frame> frames;
24+ for (auto const & frame : trace.frames | std::views::drop (1 )) {
25+ frames.push_back (frame);
26+ if (cpptrace::prune_symbol (frame.symbol ) == test_name) {
27+ break ;
28+ }
29+ }
30+ trace.frames = frames;
31+ }
32+
33+ void failure_handler (libassert::assertion_info const & info) {
34+ // libassert::enable_virtual_terminal_processing_if_needed(); // for terminal colors on windows
35+ constexpr bool Colorize = false ;
36+ auto width = libassert::terminal_width (libassert::stderr_fileno);
37+ const auto & scheme = Colorize ? libassert::get_color_scheme () : libassert::color_scheme::blank;
38+ std::string message = std::string (info.action ()) + " at " + info.location () + " :" ;
39+ if (info.message ) {
40+ message += " " + *info.message ;
41+ }
42+ message += " \n " ;
43+ message +=
44+ info.statement (scheme) + info.print_binary_diagnostics (width, scheme) +
45+ info.print_extra_diagnostics (width, scheme); // + info.print_stacktrace(width, scheme);
46+
47+ auto trace = info.get_stacktrace ();
48+ cleanup_frames (trace, rsl::testing::_testing_impl::assertion_counter ().test_name );
49+ message += trace.to_string (Colorize);
50+ throw rsl::testing::assertion_failure (
51+ message,
52+ rsl::source_location (info.file_name , info.function , info.line ));
53+ }
54+
55+ void print_tests (rsl::testing::TestNamespace const & current, std::size_t indent = 0 ) {
56+ auto current_indent = std::string (indent * 2 , ' ' );
57+ for (auto const & ns : current.children ) {
58+ std::println (" {}{}" , current_indent, ns.name );
59+ print_tests (ns, indent + 1 );
60+ }
61+
62+ for (auto const & test : current.tests ) {
63+ std::println (" {}- {}" , current_indent, test.name );
64+ for (auto const & run : test.get_tests ()) {
65+ std::println (" {}- {}" , std::string ((indent + 1 ) * 2 , ' ' ), run.name );
66+ }
67+ }
68+ }
69+ } // namespace
70+
71+ namespace rsl ::testing {
72+ void Reporter::list_tests (TestNamespace const & tests) {
73+ print_tests (tests);
74+ }
75+
76+ bool TestRoot::run (Reporter* reporter) {
77+ libassert::set_failure_handler (failure_handler);
78+ std::println (" failure handler set" );
79+ reporter->before_run (*this );
80+ bool status = TestNamespace::run (reporter);
81+ libassert::set_failure_handler (libassert::default_failure_handler);
82+ // TODO after_run
83+ reporter->after_run ({});
84+ return status;
85+ }
86+
87+ bool TestNamespace::run (Reporter* reporter) {
88+ if (!name.empty ()) {
89+ reporter->enter_namespace (name);
90+ }
91+ bool status = true ;
92+ for (auto & ns : children) {
93+ status &= ns.run (reporter);
94+ }
95+
96+ for (auto & test : tests) {
97+ auto runs = test.get_tests ();
98+ reporter->before_test_group (test);
99+ std::vector<Result> results;
100+ if (!test.skip ()) {
101+ std::vector<Result> results;
102+ for (auto const & test_run : test.get_tests ()) {
103+ auto & tracker = _testing_impl::assertion_counter ();
104+ tracker.assertions = {};
105+ tracker.test_name = join_str (test.full_name , " ::" );
106+
107+ reporter->before_test (test_run);
108+ auto result = test_run.run ();
109+ reporter->after_test (result);
110+
111+ result.assertions = tracker.assertions ;
112+ results.push_back (result);
113+ }
114+ } else {
115+ reporter->before_test (TestCase{&test, +[] {}, std::string (test.name )});
116+
117+ // TODO stringify skipped tests properly
118+ auto result = Result{&test, std::string (test.name ) + " (...)" , TestOutcome::SKIP};
119+ reporter->after_test (result);
120+ results.push_back (result);
121+ }
122+
123+ reporter->after_test_group (results);
124+ }
125+ if (!name.empty ()) {
126+ reporter->exit_namespace (name);
127+ }
128+ return status;
129+ }
130+
131+ namespace {
132+ void run_test (void const * test) {
133+ (*static_cast <std::function<void ()> const *>(test))();
134+ }
135+
136+ auto resolve_pc (std::uintptr_t pc) {
137+ auto raw_trace = cpptrace::raw_trace{{pc}};
138+ auto trace = raw_trace.resolve ();
139+ return trace.frames [0 ];
140+ }
141+
142+ auto filter_coverage (rsl::coverage::CoverageReport* data, std::size_t size) {
143+ std::unordered_map<std::string, std::vector<LineCoverage>> coverage;
144+
145+ for (std::size_t idx = 0 ; idx < size; ++idx) {
146+ auto resolved = resolve_pc (data[idx].pc );
147+ if (resolved.filename .empty () || (int )resolved.line .value () < 0 ) {
148+ continue ;
149+ }
150+ if (resolved.filename .contains (" /../include/c++/" )) {
151+ continue ;
152+ }
153+ coverage[resolved.filename ].push_back ({resolved.line .value (), data[idx].hits });
154+ }
155+
156+ std::vector<FileCoverage> result;
157+ for (auto const & [name, cov] : coverage) {
158+ result.emplace_back (name, cov);
159+ }
160+ return result;
161+ }
162+ } // namespace
163+
164+ Result TestCase::run () const {
165+ auto ret = Result{.test = test, .name = name};
166+ try {
167+ // Capture _out(stdout, ret.stdout);
168+ // Capture _err(stderr, ret.stderr);
169+
170+ auto t0 = std::chrono::steady_clock::now ();
171+ if (_rsl_test_run_with_coverage != nullptr ) {
172+ // rsltest_cov was linked in -> run with coverage
173+ rsl::coverage::CoverageReport* reports = nullptr ;
174+ std::size_t report_count = 0 ;
175+ auto finalize = [&] {
176+ ret.coverage = filter_coverage (reports, report_count);
177+ free (reports);
178+ };
179+ try {
180+ _rsl_test_run_with_coverage (run_test,
181+ static_cast <void const *>(&fnc),
182+ &reports,
183+ &report_count);
184+ finalize ();
185+ } catch (...) {
186+ finalize ();
187+ throw ;
188+ }
189+ } else {
190+ fnc ();
191+ }
192+ auto t1 = std::chrono::steady_clock::now ();
193+
194+ ret.outcome = TestOutcome (!test->expect_failure );
195+ ret.duration_ms = std::chrono::duration<double , std::milli>(t1 - t0).count ();
196+ return ret;
197+ } catch (assertion_failure const & failure) {
198+ ret.failure = failure;
199+ } catch (std::exception const & exc) { //
200+ ret.exception += exc.what ();
201+ } catch (std::string const & msg) { //
202+ ret.exception += msg;
203+ } catch (std::string_view msg) { //
204+ ret.exception += msg;
205+ } catch (char const * msg) { //
206+ ret.exception += msg;
207+ } catch (...) { ret.exception += " unknown exception thrown" ; }
208+
209+ ret.outcome = TestOutcome (test->expect_failure );
210+ return ret;
211+ }
212+ } // namespace rsl::testing
0 commit comments