Skip to content

Commit 97e9cd6

Browse files
committed
Add junit output support
1 parent 8f31d7c commit 97e9cd6

File tree

6 files changed

+51
-59
lines changed

6 files changed

+51
-59
lines changed

.clang-tidy

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,9 @@ Checks: >
3636
-readability-identifier-length,
3737
-readability-magic-numbers
3838
WarningsAsErrors: false # should be true once we get there
39-
#HeaderFileExtensions: ['h','hh','hpp','hxx'] # enable iff clang-tidy v17+
40-
#ImplementationFileExtensions: ['c','cc','cpp','cxx'] # enable iff clang-tidy v17+ (stops from touching .S assembly files)
39+
HeaderFileExtensions: ['h','hh','hpp','hxx'] # enable iff clang-tidy v17+
40+
ImplementationFileExtensions: ['c','cc','cpp','cxx'] # enable iff clang-tidy v17+ (stops from touching .S assembly files)
4141
HeaderFilterRegex: ".*"
42-
AnalyzeTemporaryDtors: false
4342
ExtraArgs: ['-Wno-unknown-argument', '-Wno-unknown-warning-option', '-W']
4443
FormatStyle: file
4544
CheckOptions:

include/argparse.hpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#pragma once
22

33
#include <argparse/argparse.hpp>
4+
#include <fstream>
5+
#include <string>
46
#include <string_view>
57

68
#include "formatters/junit_xml.hpp"
@@ -21,11 +23,6 @@ inline std::string file_name(std::string_view path) {
2123
return std::string{file};
2224
}
2325

24-
struct RuntimeOpts {
25-
bool verbose = false;
26-
std::shared_ptr<Formatters::BaseFormatter> formatter = nullptr;
27-
};
28-
2926
inline Runner parse(int argc, char** const argv) {
3027
argparse::ArgumentParser program{file_name(argv[0])};
3128

@@ -35,7 +32,8 @@ inline Runner parse(int argc, char** const argv) {
3532
.required()
3633
.help("set the output format");
3734

38-
program.add_argument("--verbose").help("increase output verbosity").default_value(false).implicit_value(true);
35+
program.add_argument("--output-junit").help("output JUnit XML to the specified file").default_value(std::string{});
36+
program.add_argument("--verbose").help("increase output verbosity").flag();
3937

4038
try {
4139
program.parse_args(argc, argv);
@@ -45,22 +43,28 @@ inline Runner parse(int argc, char** const argv) {
4543
std::exit(1);
4644
}
4745

48-
RuntimeOpts opts;
49-
5046
auto format_string = program.get<std::string>("--format");
47+
std::shared_ptr<Formatters::BaseFormatter> formatter;
5148
if (format_string == "d" || format_string == "detail" || program["--verbose"] == true) {
52-
opts.formatter = std::make_unique<Formatters::Verbose>();
49+
formatter = std::make_shared<Formatters::Verbose>();
5350
} else if (format_string == "p" || format_string == "progress") {
54-
opts.formatter = std::make_unique<Formatters::Progress>();
51+
formatter = std::make_shared<Formatters::Progress>();
5552
} else if (format_string == "t" || format_string == "tap") {
56-
opts.formatter = std::make_unique<Formatters::TAP>();
53+
formatter = std::make_shared<Formatters::TAP>();
5754
} else if (format_string == "j" || format_string == "junit") {
58-
opts.formatter = std::make_unique<Formatters::JUnitXML>();
55+
formatter = std::make_shared<Formatters::JUnitXML>();
5956
} else {
6057
std::cerr << "Unrecognized format type" << std::endl;
6158
std::exit(-1);
6259
}
6360

64-
return Runner{opts.formatter};
61+
auto junit_output_filepath = program.get<std::string>("--output-junit");
62+
if (!junit_output_filepath.empty()) {
63+
auto* file_stream = new std::ofstream(junit_output_filepath);
64+
auto junit_output = std::make_shared<Formatters::JUnitXML>(*file_stream, false);
65+
return Runner{formatter, junit_output};
66+
}
67+
68+
return Runner{formatter};
6569
}
6670
} // namespace CppSpec

include/formatters/formatters_base.hpp

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,54 +33,44 @@ class BaseFormatter {
3333
protected:
3434
std::ostream& out_stream;
3535
int test_counter = 1;
36-
bool multiple = false;
3736
bool color_output;
3837

3938
public:
4039
explicit BaseFormatter(std::ostream& out_stream = std::cout, bool color = is_terminal())
4140
: out_stream(out_stream), color_output(color) {}
4241
BaseFormatter(const BaseFormatter&) = default;
4342
BaseFormatter(const BaseFormatter& copy, std::ostream& out_stream)
44-
: out_stream(out_stream),
45-
test_counter(copy.test_counter),
46-
multiple(copy.multiple),
47-
color_output(copy.color_output) {}
43+
: out_stream(out_stream), test_counter(copy.test_counter), color_output(copy.color_output) {}
4844

4945
virtual ~BaseFormatter() = default;
5046

5147
void format(Runnable& runnable) {
52-
if (Description* description = dynamic_cast<Description*>(&runnable)) {
48+
if (auto* description = dynamic_cast<Description*>(&runnable)) {
5349
format(*description);
54-
} else if (ItBase* it = dynamic_cast<ItBase*>(&runnable)) {
50+
} else if (auto* it = dynamic_cast<ItBase*>(&runnable)) {
5551
format(*it);
5652
}
5753
format_children(runnable);
5854
}
5955

6056
void format_children(Runnable& runnable) {
6157
for (auto& child : runnable.get_children()) {
62-
if (Runnable* runnable = dynamic_cast<Runnable*>(child.get())) {
58+
if (auto* runnable = dynamic_cast<Runnable*>(child.get())) {
6359
this->format(*runnable);
6460
}
6561
}
6662
}
6763

68-
virtual void format(Description& description) {}
69-
virtual void format(ItBase& it) {}
64+
virtual void format(Description& /* description */) {}
65+
virtual void format(ItBase& /* it */) {}
7066
virtual void cleanup() {}
7167

72-
BaseFormatter& set_multiple(bool value);
7368
BaseFormatter& set_color_output(bool value);
7469

7570
int get_and_increment_test_counter() { return test_counter++; }
7671
void reset_test_counter() { test_counter = 1; }
7772
};
7873

79-
inline BaseFormatter& BaseFormatter::set_multiple(bool multiple) {
80-
this->multiple = multiple;
81-
return *this;
82-
}
83-
8474
inline BaseFormatter& BaseFormatter::set_color_output(bool value) {
8575
this->color_output = value;
8676
return *this;

include/formatters/junit_xml.hpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
#include <algorithm>
33
#include <atomic>
44
#include <chrono>
5+
#include <filesystem>
56
#include <numeric>
67
#include <string>
7-
#include <string_view>
88

99
#include "formatters_base.hpp"
1010
#include "it_base.hpp"
1111

1212
namespace CppSpec::Formatters {
1313
// JUnit XML header
14-
constexpr static std::string_view junit_xml_header = R"(<?xml version="1.0" encoding="UTF-8"?>)";
14+
constexpr static auto junit_xml_header = R"(<?xml version="1.0" encoding="UTF-8"?>)";
1515

1616
struct XMLSerializable {
1717
virtual ~XMLSerializable() = default;
@@ -26,8 +26,8 @@ struct Result {
2626
std::string type;
2727
std::string text;
2828

29-
Result(const std::string& message, const std::string& type, const std::string& text, Status status = Status::Failure)
30-
: status(status), message(message), type(type), text(text) {}
29+
Result(std::string message, std::string type, std::string text, Status status = Status::Failure)
30+
: status(status), message(std::move(message)), type(std::move(type)), text(std::move(text)) {}
3131

3232
[[nodiscard]] std::string status_string() const {
3333
switch (status) {
@@ -91,7 +91,7 @@ struct TestSuite {
9191
size_t tests,
9292
size_t failures,
9393
std::chrono::time_point<std::chrono::system_clock> timestamp)
94-
: id(get_next_id()), name(name), time(time), timestamp(timestamp), tests(tests), failures(failures) {}
94+
: id(get_next_id()), name(std::move(name)), time(time), timestamp(timestamp), tests(tests), failures(failures) {}
9595

9696
[[nodiscard]] std::string to_xml() const {
9797
auto timestamp_str =
@@ -119,7 +119,7 @@ struct TestSuites {
119119
std::string name;
120120
size_t tests = 0;
121121
size_t failures = 0;
122-
std::chrono::duration<double> time;
122+
std::chrono::duration<double> time{};
123123
std::chrono::time_point<std::chrono::system_clock> timestamp;
124124

125125
std::list<TestSuite> suites;
@@ -165,6 +165,10 @@ class JUnitXML : public BaseFormatter {
165165
}
166166

167167
void format(Description& description) override {
168+
if (test_suites.name.empty()) {
169+
std::filesystem::path file_path = description.get_location().file_name();
170+
test_suites.name = file_path.stem().string();
171+
}
168172
if (description.has_parent()) {
169173
return;
170174
}
@@ -177,7 +181,7 @@ class JUnitXML : public BaseFormatter {
177181
std::forward_list<std::string> descriptions;
178182

179183
descriptions.push_front(it.get_description());
180-
for (auto parent = it.get_parent_as<Description>(); parent->has_parent();
184+
for (auto* parent = it.get_parent_as<Description>(); parent->has_parent();
181185
parent = parent->get_parent_as<Description>()) {
182186
descriptions.push_front(parent->get_description());
183187
}
@@ -198,8 +202,8 @@ class JUnitXML : public BaseFormatter {
198202
if (result.is_success()) {
199203
continue;
200204
}
201-
std::string junit_message = result.get_location_string() + ": " + result.get_message();
202-
test_case.results.emplace_back("Match failure.", result.get_type(), result.get_message());
205+
test_case.results.emplace_back(result.get_location_string() + ": Match failure.", result.get_type(),
206+
result.get_message());
203207
}
204208

205209
test_suites.suites.back().cases.push_back(test_case);

include/formatters/verbose.hpp

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

4-
#include <list>
5-
#include <string>
6-
74
#include "formatters_base.hpp"
85
#include "it_base.hpp"
96
#include "term_colors.hpp"
@@ -20,7 +17,7 @@ class Verbose : public BaseFormatter {
2017
explicit Verbose(const BaseFormatter& base) : BaseFormatter(base) {}
2118

2219
void format(Description& description) override;
23-
void format(ItBase& description) override;
20+
void format(ItBase& it) override;
2421
};
2522

2623
inline void Verbose::format(Description& description) {

include/runner.hpp

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
/** @file */
22
#pragma once
3-
#include <initializer_list>
4-
#pragma once
53

64
#include <list>
75
#include <utility>
@@ -16,13 +14,15 @@ namespace CppSpec {
1614
* @brief A collection of Descriptions that are run in sequence
1715
*/
1816
class Runner {
19-
std::list<Description*> specs{};
20-
std::shared_ptr<Formatters::BaseFormatter> formatter;
17+
std::list<Description*> specs;
18+
std::list<std::shared_ptr<Formatters::BaseFormatter>> formatters;
2119

2220
public:
23-
explicit Runner(std::shared_ptr<Formatters::BaseFormatter> formatter) : formatter{std::move(formatter)} {};
21+
template <typename... Formatters>
22+
explicit Runner(Formatters&&... formatters) : formatters{std::forward<Formatters>(formatters)...} {}
2423

25-
~Runner() = default;
24+
explicit Runner(std::list<std::shared_ptr<Formatters::BaseFormatter>>&& formatters)
25+
: formatters{std::move(formatters)} {}
2626

2727
/**
2828
* @brief Add a Description object
@@ -45,19 +45,17 @@ class Runner {
4545
bool success = true;
4646
for (Description* spec : specs) {
4747
spec->timed_run();
48-
formatter->format(static_cast<Runnable&>(*spec));
4948
success &= spec->get_result().status();
5049
}
50+
for (auto& formatter : formatters) {
51+
for (Description* spec : specs) {
52+
formatter->format(static_cast<Runnable&>(*spec));
53+
}
54+
}
5155
return success ? Result::success(location) : Result::failure(location);
5256
}
5357

54-
Result exec() {
55-
if (specs.size() > 1) {
56-
formatter->set_multiple(true);
57-
}
58-
Result result = run();
59-
return result;
60-
}
58+
Result exec() { return run(); }
6159
};
6260

6361
} // namespace CppSpec

0 commit comments

Comments
 (0)