Skip to content

Commit 4e67194

Browse files
committed
Add runtime exception handling, formatter improvements
1 parent 7f2c35a commit 4e67194

21 files changed

+286
-161
lines changed

examples/sample/example_spec.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
#include <list>
55
#include "cppspec.hpp"
66

7+
// clang-format off
78
describe bool_spec("Some Tests", $ {
89
context("true is", _ {
910
it("true", _ {
10-
expect(static_cast<bool>(1)).to_equal(true);
11+
expect(1).to_equal(true);
1112
});
1213

1314
it("true", _ {
@@ -27,6 +28,10 @@ describe bool_spec("Some Tests", $ {
2728
it("plus 1 equals 5", _ {
2829
expect(i+1).to_equal(5);
2930
});
31+
32+
it("equals 6", _ {
33+
expect(i).to_equal(6);
34+
});
3035
});
3136

3237
explain("0 is", _ {

include/argparse.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ inline std::string file_name(std::string_view path) {
2424
}
2525

2626
inline Runner parse(int argc, char** const argv) {
27-
argparse::ArgumentParser program{file_name(argv[0])};
27+
std::filesystem::path executable_path = argv[0];
28+
std::string executable_name = executable_path.filename().string();
29+
argparse::ArgumentParser program{executable_name};
2830

2931
program.add_argument("-f", "--format")
3032
.default_value(std::string{"p"})

include/expectations/expectation.hpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <exception>
44
#include <regex>
5+
#include <source_location>
56
#include <string>
67
#include <vector>
78

@@ -50,10 +51,11 @@ class Expectation {
5051
protected:
5152
bool is_positive_ = true;
5253
// Have we been negated?
53-
bool ignore_failure_ = false;
54+
bool ignore_ = false;
5455

5556
public:
5657
Expectation() = default;
58+
explicit Expectation(std::source_location location) : location(location) {}
5759

5860
/**
5961
* @brief Create an Expectation using a value.
@@ -68,12 +70,12 @@ class Expectation {
6870
// virtual const A &get_target() const & { return target; }
6971
virtual A& get_target() & = 0;
7072

71-
ItBase* get_it() const { return it; }
72-
std::source_location get_location() const { return location; }
73+
[[nodiscard]] ItBase* get_it() const { return it; }
74+
[[nodiscard]] std::source_location get_location() const { return location; }
7375

7476
/** @brief Get whether the expectation is normal or negated. */
75-
[[nodiscard]] constexpr bool sign() const { return is_positive_; }
76-
[[nodiscard]] constexpr bool ignore_failure() const { return ignore_failure_; }
77+
[[nodiscard]] constexpr bool positive() const { return is_positive_; }
78+
[[nodiscard]] constexpr bool ignored() const { return ignore_; }
7779

7880
/********* Modifiers *********/
7981

@@ -404,6 +406,8 @@ class ExpectationValue : public Expectation<A> {
404406
* @return The constructed ExpectationValue.
405407
*/
406408
ExpectationValue(ItBase& it, A value, std::source_location location) : Expectation<A>(it, location), value(value) {}
409+
explicit ExpectationValue(A value, std::source_location location = std::source_location::current())
410+
: Expectation<A>(location), value(value) {}
407411

408412
/**
409413
* @brief Create an Expectation using an initializer list.
@@ -425,7 +429,7 @@ class ExpectationValue : public Expectation<A> {
425429
}
426430

427431
ExpectationValue& ignore() override {
428-
this->ignore_failure_ = true;
432+
this->ignore_ = true;
429433
return *this;
430434
}
431435
};
@@ -484,7 +488,7 @@ class ExpectationFunc : public Expectation<decltype(std::declval<F>()())> {
484488
}
485489

486490
ExpectationFunc& ignore() override {
487-
this->ignore_failure_ = true;
491+
this->ignore_ = true;
488492
return *this;
489493
}
490494

include/expectations/handler.hpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#pragma once
1111

12+
#include <exception>
1213
#include <string>
1314

1415
#include "result.hpp"
@@ -39,9 +40,17 @@ struct NegativeExpectationHandler {
3940
*/
4041
template <class Matcher>
4142
Result PositiveExpectationHandler::handle_matcher(Matcher& matcher) {
42-
// TODO: handle expectation failure here
43-
return !matcher.match() ? Result::failure_with(matcher.get_location(), matcher.failure_message())
44-
: Result::success(matcher.get_location());
43+
bool matched = false;
44+
try {
45+
matched = matcher.match();
46+
} catch (std::exception& e) {
47+
return Result::error_with(matcher.get_location(), e.what());
48+
} catch (...) {
49+
return Result::error_with(matcher.get_location(), "Unknown exception thrown during matcher execution.");
50+
}
51+
52+
return !matched ? Result::failure_with(matcher.get_location(), matcher.failure_message())
53+
: Result::success(matcher.get_location());
4554
}
4655

4756
/**
@@ -54,9 +63,16 @@ Result PositiveExpectationHandler::handle_matcher(Matcher& matcher) {
5463
*/
5564
template <class Matcher>
5665
Result NegativeExpectationHandler::handle_matcher(Matcher& matcher) {
57-
// TODO: handle expectation failure here
58-
return !matcher.negated_match() ? Result::failure_with(matcher.get_location(), matcher.failure_message_when_negated())
59-
: Result::success(matcher.get_location());
66+
bool matched = false;
67+
try {
68+
matched = matcher.negated_match();
69+
} catch (std::exception& e) {
70+
return Result::error_with(matcher.get_location(), e.what());
71+
} catch (...) {
72+
return Result::error_with(matcher.get_location(), "Unhandled exception thrown during matcher execution.");
73+
}
74+
return !matched ? Result::failure_with(matcher.get_location(), matcher.failure_message_when_negated())
75+
: Result::success(matcher.get_location());
6076
}
6177

6278
} // namespace CppSpec

include/formatters/formatters_base.hpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "description.hpp"
88
#include "it_base.hpp"
99
#include "runnable.hpp"
10+
#include "term_colors.hpp"
1011

1112
extern "C" {
1213
#ifdef _WIN32
@@ -69,6 +70,31 @@ class BaseFormatter {
6970

7071
int get_and_increment_test_counter() { return test_counter++; }
7172
void reset_test_counter() { test_counter = 1; }
73+
74+
const char* set_color(const char* color) {
75+
if (!color_output) {
76+
return ""; // No color output
77+
}
78+
return color;
79+
}
80+
81+
const char* status_color(Result::Status status) {
82+
if (!color_output) {
83+
return ""; // No color output
84+
}
85+
switch (status) {
86+
case Result::Status::Success:
87+
return GREEN;
88+
case Result::Status::Failure:
89+
return RED;
90+
case Result::Status::Error:
91+
return MAGENTA;
92+
case Result::Status::Skipped:
93+
return YELLOW;
94+
}
95+
}
96+
97+
const char* reset_color() { return color_output ? RESET : ""; }
7298
};
7399

74100
inline BaseFormatter& BaseFormatter::set_color_output(bool value) {

include/formatters/progress.hpp

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ namespace CppSpec::Formatters {
1313
// The TAP format makes things a little tricky
1414
class Progress : public BaseFormatter {
1515
std::list<std::string> baked_failure_messages;
16-
std::list<std::string> raw_failure_messages;
1716

1817
std::string prep_failure_helper(const ItBase& it);
1918

@@ -25,6 +24,20 @@ class Progress : public BaseFormatter {
2524

2625
void format_failure_messages();
2726
void prep_failure(const ItBase& it);
27+
28+
static char status_char(Result::Status status) {
29+
switch (status) {
30+
case Result::Status::Success:
31+
return '.';
32+
case Result::Status::Failure:
33+
return 'F';
34+
case Result::Status::Error:
35+
return 'E';
36+
case Result::Status::Skipped:
37+
return 'S';
38+
}
39+
return '.'; // Default to success if status is unknown
40+
}
2841
};
2942

3043
/** @brief An assistant function for prep_failure to reduce complexity */
@@ -63,43 +76,33 @@ inline std::string Progress::prep_failure_helper(const ItBase& it) {
6376
}
6477

6578
inline void Progress::prep_failure(const ItBase& it) {
66-
std::ostringstream string_builder; // oss is used as the local string builder
67-
if (color_output) {
68-
string_builder << RED; // if we're doing color, make it red
69-
}
79+
std::list<std::string> raw_failure_messages; // raw failure messages
80+
std::ranges::transform(it.get_results(), std::back_inserter(raw_failure_messages),
81+
[](const Result& result) { return result.get_message(); });
82+
83+
std::ostringstream string_builder; // oss is used as the local string builder
84+
string_builder << set_color(RED); // if we're doing color, make it red
7085
string_builder << "Test number " << test_counter << " failed:"; // Tell us what test # failed
71-
if (color_output) {
72-
string_builder << RESET; // if we're doing color, reset the terminal
73-
}
86+
string_builder << reset_color(); // reset the color
7487
string_builder << prep_failure_helper(it);
75-
if (color_output) {
76-
string_builder << RED;
77-
}
78-
string_builder << Util::join(raw_failure_messages, "\n");
79-
if (color_output) {
80-
string_builder << RESET;
81-
}
88+
string_builder << set_color(RED);
89+
string_builder << Util::join_endl(raw_failure_messages);
90+
string_builder << reset_color(); // reset the color
91+
string_builder << std::endl;
92+
8293
raw_failure_messages.clear();
8394
baked_failure_messages.push_back(string_builder.str());
8495
}
8596

8697
inline void Progress::format(const ItBase& it) {
87-
if (it.get_result().status()) {
88-
if (color_output) {
89-
out_stream << GREEN;
90-
}
91-
out_stream << ".";
92-
} else {
93-
if (color_output) {
94-
out_stream << RED;
95-
}
96-
out_stream << "F";
98+
out_stream << status_color(it.get_result().status());
99+
out_stream << status_char(it.get_result().status());
100+
out_stream << reset_color();
101+
out_stream << std::flush;
102+
103+
if (it.get_result().status() == Result::Status::Failure) {
97104
prep_failure(it);
98105
}
99-
if (color_output) {
100-
out_stream << RESET;
101-
}
102-
out_stream << std::flush;
103106
get_and_increment_test_counter();
104107
}
105108

include/formatters/tap.hpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,23 @@ inline std::string TAP::result_to_yaml(const Result& result) {
2525
return {};
2626
}
2727

28+
auto message = result.get_message();
29+
2830
std::ostringstream oss;
2931
oss << " " << "---" << std::endl;
30-
oss << " " << "message: " << "'" << result.get_message() << "'" << std::endl;
32+
if (message.contains("\n")) {
33+
oss << " " << "message: |" << std::endl;
34+
std::string indented_message = message; // split on newlines and indent
35+
std::string::size_type pos = 0;
36+
while ((pos = indented_message.find('\n', pos)) != std::string::npos) {
37+
indented_message.replace(pos, 1, "\n ");
38+
pos += 2; // Skip over the newline and the space we just added
39+
}
40+
oss << " " << " " << indented_message << std::endl;
41+
} else {
42+
oss << " " << "message: " << '"' << result.get_message() << '"' << std::endl;
43+
}
44+
3145
oss << " " << "severity: failure" << std::endl;
3246
oss << " " << "at:" << std::endl;
3347
oss << " " << " " << "file: " << result.get_location().file_name() << std::endl;
@@ -88,13 +102,9 @@ inline void TAP::format(const ItBase& it) {
88102

89103
std::string description = Util::join(descriptions, " ");
90104

91-
if (color_output) {
92-
buffer << (it.get_result().status() ? GREEN : RED);
93-
}
94-
buffer << (it.get_result().status() ? "ok" : "not ok");
95-
if (color_output) {
96-
buffer << RESET;
97-
}
105+
buffer << status_color(it.get_result().status());
106+
buffer << (it.get_result().is_success() ? "ok" : "not ok");
107+
buffer << reset_color();
98108
buffer << " " << get_and_increment_test_counter() << " - " << description << std::endl;
99109
buffer << result_to_yaml(it.get_result());
100110
}

include/formatters/term_colors.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/** @file */
22
#pragma once
33

4+
namespace CppSpec {
5+
46
// the following are Unix/BASH ONLY terminal color codes.
57
constexpr auto RESET("\033[0m");
68
constexpr auto BLACK("\033[30m"); /* Black */
@@ -19,3 +21,5 @@ constexpr auto BOLDBLUE("\033[1m\033[34m"); /* Bold Blue */
1921
constexpr auto BOLDMAGENTA("\033[1m\033[35m"); /* Bold Magenta */
2022
constexpr auto BOLDCYAN("\033[1m\033[36m"); /* Bold Cyan */
2123
constexpr auto BOLDWHITE("\033[1m\033[37m"); /* Bold White */
24+
25+
} // namespace CppSpec

include/formatters/verbose.hpp

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,18 @@ inline void Verbose::format(const Description& description) {
3131
}
3232

3333
inline void Verbose::format(const ItBase& it) {
34-
if (color_output) {
35-
out_stream << (it.get_result().status() ? GREEN : RED);
36-
}
34+
out_stream << status_color(it.get_result().status());
3735
out_stream << it.padding() << it.get_description() << std::endl;
38-
if (color_output) {
39-
out_stream << RESET;
40-
}
36+
out_stream << reset_color();
4137

4238
// Print any failures if we've got them
4339
// 'it' having a bad status necessarily
4440
// implies that there are failure messages
4541
for (const Result& result : it.get_results()) {
4642
if (result.is_failure()) {
47-
if (color_output) {
48-
out_stream << RED; // make them red
49-
}
43+
out_stream << set_color(RED);
5044
out_stream << result.get_message() << std::endl;
51-
if (color_output) {
52-
out_stream << RESET;
53-
}
45+
out_stream << reset_color();
5446
}
5547
}
5648

0 commit comments

Comments
 (0)