Skip to content

Commit eb1722f

Browse files
committed
fix up coverage output
1 parent 62f6c2d commit eb1722f

File tree

6 files changed

+264
-223
lines changed

6 files changed

+264
-223
lines changed

include/rsl/testing/result.hpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
2-
#include <functional>
2+
#include <cstddef>
33
#include <string>
4+
45
#include "assert.hpp"
56

67
namespace rsl::testing {
@@ -11,6 +12,16 @@ enum class TestOutcome: uint8_t {
1112
SKIP
1213
};
1314

15+
struct LineCoverage {
16+
std::size_t line = 0;
17+
std::size_t count = 0;
18+
};
19+
20+
struct FileCoverage {
21+
std::string filename;
22+
std::vector<LineCoverage> coverage;
23+
};
24+
1425
struct Result {
1526
class Test const* test;
1627
std::string name;
@@ -24,6 +35,7 @@ struct Result {
2435
std::string stderr;
2536

2637
std::vector<AssertionInfo> assertions;
38+
std::vector<FileCoverage> coverage;
2739
};
2840

2941
struct TestResult {

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
target_sources(rsltest PUBLIC
22
capture.cpp
33
test.cpp
4+
runner.cpp
45
)
56

67
add_subdirectory(main)

src/coverage/runner.cpp

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@
33

44
#include "hooks.hpp"
55
#include "coverage.hpp"
6-
6+
namespace rsl::coverage {
77
namespace {
88
void reset_counters() {
9-
using rsl::coverage::counters;
10-
using rsl::coverage::guard_count;
11-
129
if (guard_count == 0 || counters == nullptr) {
1310
return;
1411
}
@@ -18,7 +15,22 @@ void reset_counters() {
1815
}
1916
}
2017

18+
auto filter_traces() {
19+
std::unordered_map<std::uintptr_t, std::uint64_t> reached;
20+
for (std::size_t idx = 0; idx < guard_count; ++idx) {
21+
if (counters[idx] != 0) {
22+
reached[pc_table[idx].pc] = counters[idx];
23+
}
24+
}
25+
std::vector<uintptr_t> snapshot = auto{pc_tracker()};
26+
for (auto pc : snapshot) {
27+
reached[pc]++;
28+
}
29+
return reached;
30+
}
31+
2132
} // namespace
33+
} // namespace rsl::coverage
2234

2335
extern "C" __attribute__((no_sanitize("coverage"))) void _rsl_test_run_with_coverage(
2436
void (*fnc)(void const*),
@@ -31,47 +43,30 @@ extern "C" __attribute__((no_sanitize("coverage"))) void _rsl_test_run_with_cove
3143
//? data races on counters are acceptable
3244
//? => coverage counters are only approximate
3345
using namespace rsl::coverage;
34-
3546
std::println("running with coverage");
36-
reset_counters();
37-
__sancov_should_track = 1;
3847

3948
auto finalize = [&] {
49+
__sancov_should_track = 0;
50+
auto reached = rsl::coverage::filter_traces();
51+
// set output
52+
*output = (CoverageReport*)malloc(sizeof(CoverageReport) * reached.size());
4053

41-
};
42-
43-
// try {
44-
fnc(test);
45-
// } catch (...) {
46-
// finalize();
47-
// throw;
48-
// }
49-
// finalize();
50-
__sancov_should_track = 0;
51-
std::unordered_map<std::uintptr_t, std::uint64_t> reached;
52-
for (std::size_t idx = 0; idx < guard_count; ++idx) {
53-
if (counters[idx] != 0) {
54-
reached[pc_table[idx].pc] = counters[idx];
54+
std::size_t idx = 0;
55+
for (auto const& [pc, count] : reached) {
56+
(*output)[idx] = {pc, count};
57+
++idx;
5558
}
56-
}
57-
std::vector<uintptr_t> snapshot = auto{pc_tracker()};
58-
for (auto pc : snapshot) {
59-
reached[pc]++;
60-
}
6159

62-
// set output
63-
*output = (CoverageReport*)malloc(sizeof(CoverageReport) * reached.size());
64-
65-
std::size_t idx = 0;
66-
for (auto const&[pc, count] : reached) {
67-
// char PcDescr[1024];
68-
// __sanitizer_symbolize_pc(pc + 4, "%s:%l:%c", PcDescr, sizeof(PcDescr));
69-
// char PcDescr2[1024];
70-
// __sanitizer_symbolize_global(pc + 4, "%s:%l:0", PcDescr2, sizeof(PcDescr2));
71-
// std::println("{} - {}", PcDescr, PcDescr);
72-
(*output)[idx] = {pc, count};
73-
++idx;
74-
}
60+
*output_size = reached.size();
61+
};
7562

76-
*output_size = reached.size();
63+
rsl::coverage::reset_counters();
64+
__sancov_should_track = 1;
65+
try {
66+
fnc(test);
67+
} catch (...) {
68+
finalize();
69+
throw;
70+
}
71+
finalize();
7772
}

src/main/reporters/terminal.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class [[=rename("plain")]] ConsoleReporter : public Reporter::Registrar<ConsoleR
3030
std::print("==== {}stdout{} ====\n{}\n", color[1], reset, result.stdout);
3131
std::print("==== {}stderr{} ====\n{}\n", color[1], reset, result.stderr);
3232
}
33+
for (auto const& [file, coverage] : result.coverage) {
34+
std::println("Reached {} lines in file {}", coverage.size(), file);
35+
}
3336
}
3437
void after_run(std::span<Result> results) override {
3538
auto passed = std::ranges::count_if(results, [](auto& r) { return r.outcome == TestOutcome::PASS; });

src/runner.cpp

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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

Comments
 (0)