• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 20579846577

29 Dec 2025 06:24PM UTC coverage: 90.415% (+0.2%) from 90.243%
20579846577

push

github

web-flow
Merge pull request #5167 from randombit/jack/src-size-reductions

Changes to reduce unnecessary inclusions

101523 of 112285 relevant lines covered (90.42%)

12817276.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.55
/src/tests/runner/test_xml_reporter.cpp
1
/*
2
* (C) 2022 Jack Lloyd
3
* (C) 2022 René Meusel, Rohde & Schwarz Cybersecurity
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "test_xml_reporter.h"
9

10
#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
11

12
   #include <botan/build.h>
13
   #include <botan/version.h>
14
   #include <botan/internal/loadstor.h>
15
   #include <botan/internal/target_info.h>
16

17
   #if defined(BOTAN_HAS_OS_UTILS)
18
      #include <botan/internal/os_utils.h>
19
   #endif
20

21
   #include <sstream>
22

23
namespace Botan_Tests {
24

25
namespace {
26

27
std::string full_compiler_version_string() {
1✔
28
   #if defined(__VERSION__)
29
   return __VERSION__;
1✔
30

31
   #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC)
32
   // See https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros
33
   //    If the version number of the Microsoft C/C++ compiler is 15.00.20706.01,
34
   //    the _MSC_FULL_VER macro evaluates to 150020706.
35
   constexpr int major = _MSC_FULL_VER / 10000000;
36
   constexpr int minor = (_MSC_FULL_VER % 10000000) / 100000;
37
   constexpr int patch = _MSC_FULL_VER % 100000;
38
   constexpr int build = _MSC_BUILD;
39

40
   std::ostringstream oss;
41

42
   oss << std::setfill('0') << std::setw(2) << major << "." << std::setw(2) << minor << "." << std::setw(5) << patch
43
       << "." << std::setw(2) << build << "\n";
44

45
   return oss.str();
46
   #else
47
   return "unknown";
48
   #endif
49
}
50

51
std::string full_compiler_name_string() {
1✔
52
   #if defined(BOTAN_BUILD_COMPILER_IS_XCODE)
53
   return "xcode";
54
   #elif defined(BOTAN_BUILD_COMPILER_IS_CLANG)
55
   return "clang";
56
   #elif defined(BOTAN_BUILD_COMPILER_IS_CLANGCL)
57
   return "clangcl";
58
   #elif defined(BOTAN_BUILD_COMPILER_IS_GCC)
59
   return "gcc";
1✔
60
   #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC)
61
   return "Microsoft Visual C++";
62
   #else
63
   return "unknown";
64
   #endif
65
}
66

67
/// formats a given time point in ISO 8601 format (with time zone)
68
std::string format(const std::chrono::system_clock::time_point& tp) {
2,921✔
69
   auto seconds_since_epoch = std::chrono::system_clock::to_time_t(tp);
2,921✔
70
   #if defined(BOTAN_HAS_OS_UTILS)
71
   return Botan::OS::format_time(seconds_since_epoch, "%FT%T%z");
5,842✔
72
   #else
73
   return std::to_string(seconds_since_epoch);
74
   #endif
75
}
76

77
std::string format(const std::chrono::nanoseconds& dur) {
1,739✔
78
   const double secs = static_cast<double>(dur.count()) / 1000000000.0;
1,739✔
79

80
   std::ostringstream out;
1,739✔
81
   out.precision(3);
1,739✔
82
   out << std::fixed << secs;
1,739✔
83
   return out.str();
3,478✔
84
}
1,739✔
85

86
std::map<std::string, std::string> parse_report_properties(const std::vector<std::string>& report_properties) {
1✔
87
   std::map<std::string, std::string> result;
1✔
88

89
   for(const auto& prop : report_properties) {
3✔
90
      const auto colon = prop.find(':');
2✔
91
      // props without a colon separator or without a name are not allowed
92
      if(colon == std::string::npos || colon == 0) {
2✔
93
         throw Test_Error("--report-properties should be of the form <key>:<value>,<key>:<value>,...");
×
94
      }
95

96
      result.insert_or_assign(prop.substr(0, colon), prop.substr(colon + 1, prop.size() - colon - 1));
4✔
97
   }
98

99
   return result;
1✔
100
}
×
101

102
}  // namespace
103

104
XmlReporter::XmlReporter(const Test_Options& opts, std::string output_dir) :
1✔
105
      Reporter(opts), m_output_dir(std::move(output_dir)) {
1✔
106
   set_property("architecture", BOTAN_TARGET_ARCH);
2✔
107
   set_property("compiler", full_compiler_name_string());
2✔
108
   set_property("compiler_version", full_compiler_version_string());
2✔
109
   set_property("timestamp", format(std::chrono::system_clock::now()));
2✔
110
   auto custom_props = parse_report_properties(opts.report_properties());
1✔
111
   for(const auto& prop : custom_props) {
3✔
112
      set_property(prop.first, prop.second);
2✔
113
   }
114
}
1✔
115

116
void XmlReporter::render() const {
1✔
117
   BOTAN_STATE_CHECK(m_outfile.has_value() && m_outfile->good());
1✔
118

119
   render_preamble(m_outfile.value());
1✔
120
   render_testsuites(m_outfile.value());
1✔
121
}
1✔
122

123
std::string XmlReporter::get_unique_output_filename() const {
1✔
124
   const uint64_t ts = Botan_Tests::Test::timestamp();
1✔
125
   std::vector<uint8_t> seed(8);
1✔
126
   Botan::store_be(ts, seed.data());
1✔
127

128
   std::stringstream ss;
1✔
129
   ss << m_output_dir << "/"
1✔
130
      << "Botan-" << Botan::short_version_string() << "-tests-" << Botan::hex_encode(seed, false) << ".xml";
3✔
131

132
   return ss.str();
1✔
133
}
2✔
134

135
void XmlReporter::next_run() {
1✔
136
   if(m_outfile.has_value()) {
1✔
137
      m_outfile.reset();
×
138
   }
139

140
   set_property("current test run", std::to_string(current_test_run()));
2✔
141
   set_property("total test runs", std::to_string(total_test_runs()));
2✔
142
   const auto file = get_unique_output_filename();
1✔
143
   m_outfile = std::ofstream(file, std::ofstream::out | std::ofstream::trunc);
1✔
144

145
   if(!m_outfile->good()) {
1✔
146
      std::stringstream ss;
×
147
      ss << "Failed to open '" << file << "' for writing JUnit report.";
×
148
      throw Botan::System_Error(ss.str());
×
149
   }
×
150
}
1✔
151

152
// == == == == == == == == == == == == == == == == == == == == == == == == == ==
153
// XML Rendering
154
// == == == == == == == == == == == == == == == == == == == == == == == == == ==
155

156
namespace {
157

158
void replace(std::string& str, const std::string& from, const std::string& to) {
30,016✔
159
   if(from.empty()) {
30,016✔
160
      return;
161
   }
162

163
   for(size_t offset = 0, pos = 0; (pos = str.find(from, offset)) != std::string::npos; offset = pos + to.size()) {
30,075✔
164
      str.replace(pos, from.size(), to);
59✔
165
   }
166
}
167

168
std::string escape(std::string str) {
5,450✔
169
   replace(str, "&", "&amp;");
10,900✔
170
   replace(str, "<", "&lt;");
10,900✔
171
   replace(str, ">", "&gt;");
10,900✔
172
   replace(str, "\"", "&quot;");
10,900✔
173
   replace(str, "'", "&apos;");
10,900✔
174
   return str;
5,450✔
175
}
176

177
std::string format_cdata(std::string str) {
2,766✔
178
   // XML CDATA payloads are not evaluated, hence no special character encoding
179
   // is needed.
180
   // Though the termination sequence (i.e. ']]>') must not appear in
181
   // a CDATA payload frame. The only way to escape it is to terminate the CDATA
182
   // sequence and break the payload's termination sequence into the adjacent
183
   // CDATA frames.
184
   //
185
   //   See: https://stackoverflow.com/a/223782
186
   replace(str, "]]>", "]]]><![CDATA[]>");
5,532✔
187
   //            ^^^ -> ^~~~~~~~~~~~~^^
188

189
   // wrap the (escaped) payload into a CDATA frame
190
   std::ostringstream out;
2,766✔
191
   out << "<![CDATA[" << str << "]]>";
2,766✔
192
   return out.str();
5,532✔
193
}
2,766✔
194

195
}  // namespace
196

197
void XmlReporter::render_preamble(std::ostream& out) const {
1✔
198
   out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1✔
199
}
×
200

201
void XmlReporter::render_properties(std::ostream& out) const {
1✔
202
   if(properties().empty()) {
1✔
203
      return;
204
   }
205

206
   out << "<properties>\n";
1✔
207
   for(const auto& prop : properties()) {
12✔
208
      out << "<property"
11✔
209
          << " name=\"" << escape(prop.first) << "\""
22✔
210
          << " value=\"" << escape(prop.second) << "\""
22✔
211
          << " />\n";
33✔
212
   }
213
   out << "</properties>\n";
1✔
214
}
215

216
void XmlReporter::render_testsuites(std::ostream& out) const {
1✔
217
   // render an empty testsuites tag even if no tests were run
218
   out << "<testsuites"
1✔
219
       << " tests=\"" << tests_run() << "\""
1✔
220
       << " failures=\"" << tests_failed() << "\""
1✔
221
       << " time=\"" << format(elapsed_time()) << "\">\n";
2✔
222

223
   // Note: In the JUnit .xsd spec, <properties> appear only in individual
224
   //       test cases. This deviation from the spec allows us to embed
225
   //       specific platform information about this particular test run.
226
   render_properties(out);
1✔
227

228
   for(const auto& suite : testsuites()) {
413✔
229
      render_testsuite(out, suite.second);
412✔
230
   }
231

232
   out << "</testsuites>\n";
1✔
233
}
1✔
234

235
void XmlReporter::render_testsuite(std::ostream& out, const Testsuite& suite) const {
412✔
236
   out << "<testsuite"
412✔
237
       << " name=\"" << escape(suite.name()) << "\""
824✔
238
       << " tests=\"" << suite.tests_run() << "\""
824✔
239
       << " failures=\"" << suite.tests_failed() << "\""
412✔
240
       << " timestamp=\"" << format(suite.timestamp()) << "\"";
1,236✔
241

242
   const auto elapsed = suite.elapsed_time();
412✔
243
   if(elapsed.has_value()) {
412✔
244
      out << " time=\"" << format(elapsed.value()) << "\"";
810✔
245
   }
246

247
   if(suite.results().empty()) {
412✔
248
      out << " />\n";
×
249
   } else {
250
      out << ">\n";
412✔
251

252
      for(const auto& result : suite.results()) {
2,920✔
253
         render_testcase(out, result);
2,508✔
254
      }
255

256
      out << "</testsuite>\n";
412✔
257
   }
258
}
412✔
259

260
void XmlReporter::render_testcase(std::ostream& out, const TestSummary& test) const {
2,508✔
261
   out << "<testcase"
2,508✔
262
       << " name=\"" << escape(test.name()) << "\""
5,016✔
263
       << " assertions=\"" << test.assertions() << "\""
5,016✔
264
       << " timestamp=\"" << format(test.timestamp()) << "\"";
7,524✔
265

266
   if(test.elapsed_time().has_value()) {
2,508✔
267
      out << " time=\"" << format(test.elapsed_time().value()) << "\"";
4,404✔
268
   }
269

270
   if(test.code_location().has_value()) {
2,508✔
271
      out << " file=\"" << escape(test.code_location()->path) << "\""
5,016✔
272
          << " line=\"" << test.code_location()->line << "\"";
5,016✔
273
   }
274

275
   if(test.passed() && test.notes().empty()) {
2,508✔
276
      out << " />\n";
2,382✔
277
   } else {
278
      out << ">\n";
126✔
279
      render_failures_and_stdout(out, test);
126✔
280
      out << "</testcase>\n";
126✔
281
   }
282
}
2,508✔
283

284
void XmlReporter::render_failures_and_stdout(std::ostream& out, const TestSummary& test) const {
126✔
285
   for(const auto& failure : test.failures()) {
126✔
286
      out << "<failure>\n"
×
287
          << format_cdata(failure) << "\n"
×
288
          << "</failure>\n";
×
289
   }
290

291
   // xUnit format does not have a special tag for test notes, hence we
292
   // render it into the freetext 'system-out'
293
   if(!test.notes().empty()) {
126✔
294
      out << "<system-out>\n";
126✔
295
      for(const auto& note : test.notes()) {
2,892✔
296
         out << format_cdata(note) << '\n';
8,298✔
297
      }
298
      out << "</system-out>\n";
126✔
299
   }
300
}
126✔
301

302
}  // namespace Botan_Tests
303

304
#endif  // defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc