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

randombit / botan / 5682765062

27 Jul 2023 01:06PM UTC coverage: 91.685% (-0.01%) from 91.698%
5682765062

push

github

web-flow
Merge pull request #3642 from randombit/pl/extend_xml_report

Add compiler and architecture information to XML report

78283 of 85383 relevant lines covered (91.68%)

12328084.89 hits per line

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

92.86
/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

16
   #include <iomanip>
17
   #include <numeric>
18
   #include <sstream>
19
   #include <time.h>
20

21
namespace Botan_Tests {
22

23
namespace {
24

25
std::string full_compiler_version_string() {
1✔
26
   #if defined(BOTAN_BUILD_COMPILER_IS_CLANG) || defined(BOTAN_BUILD_COMPILER_IS_GCC)
27
   return __VERSION__;
1✔
28
   #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC)
29
   // See https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros
30
   //    If the version number of the Microsoft C/C++ compiler is 15.00.20706.01,
31
   //    the _MSC_FULL_VER macro evaluates to 150020706.
32
   constexpr int major = _MSC_FULL_VER / 10000000;
33
   constexpr int minor = (_MSC_FULL_VER % 10000000) / 100000;
34
   constexpr int patch = _MSC_FULL_VER % 100000;
35
   constexpr int build = _MSC_BUILD;
36

37
   std::ostringstream oss;
38

39
   oss << std::setfill('0') << std::setw(2) << major << "." << std::setw(2) << minor << "." << std::setw(5) << patch
40
       << "." << std::setw(2) << build << std::endl;
41

42
   return oss.str();
43
   #else
44
   return "unknown";
45
   #endif
46
}
47

48
std::string full_compiler_name_string() {
1✔
49
   #if defined(BOTAN_BUILD_COMPILER_IS_CLANG)
50
   return "clang";
51
   #elif defined(BOTAN_BUILD_COMPILER_IS_GCC)
52
   return "gcc";
1✔
53
   #elif defined(BOTAN_BUILD_COMPILER_IS_MSVC)
54
   return "Microsoft Visual C++";
55
   #else
56
   return "unknown";
57
   #endif
58
}
59

60
std::tm* localtime(const time_t* timer, std::tm* buffer) {
2,000✔
61
   #if defined(BOTAN_BUILD_COMPILER_IS_MSVC) || defined(BOTAN_TARGET_OS_IS_MINGW) || \
62
      defined(BOTAN_TARGET_OS_IS_CYGWIN) || defined(BOTAN_TARGET_OS_IS_WINDOWS)
63
   localtime_s(buffer, timer);
64
   #else
65
   localtime_r(timer, buffer);
2,000✔
66
   #endif
67
   return buffer;
2,000✔
68
}
69

70
/// formats a given time point in ISO 8601 format (with time zone)
71
std::string format(const std::chrono::system_clock::time_point& tp) {
2,000✔
72
   auto seconds_since_epoch = std::chrono::system_clock::to_time_t(tp);
2,000✔
73

74
   std::ostringstream out;
2,000✔
75
   std::tm buffer{};
2,000✔
76
   out << std::put_time(localtime(&seconds_since_epoch, &buffer), "%FT%T%z");
2,000✔
77
   return out.str();
4,000✔
78
}
2,000✔
79

80
std::string format(const std::chrono::nanoseconds& dur) {
1,213✔
81
   const float secs = static_cast<float>(dur.count()) / 1000000000;
1,213✔
82

83
   std::ostringstream out;
1,213✔
84
   out.precision(3);
1,213✔
85
   out << std::fixed << secs;
1,213✔
86
   return out.str();
2,426✔
87
}
1,213✔
88

89
}  // namespace
90

91
XmlReporter::XmlReporter(const Test_Options& opts, std::string output_dir) :
1✔
92
      Reporter(opts), m_output_dir(std::move(output_dir)) {
2✔
93
   set_property("architecture", BOTAN_TARGET_ARCH);
2✔
94
   set_property("compiler", full_compiler_name_string());
2✔
95
   set_property("compiler_version", full_compiler_version_string());
2✔
96
   set_property("timestamp", format(std::chrono::system_clock::now()));
2✔
97
   auto custom_props = opts.report_properties();
1✔
98
   for(const auto& prop : custom_props) {
3✔
99
      set_property(prop.first, prop.second);
2✔
100
   }
101
}
1✔
102

103
void XmlReporter::render() const {
1✔
104
   BOTAN_STATE_CHECK(m_outfile.has_value() && m_outfile->good());
1✔
105

106
   render_preamble(m_outfile.value());
1✔
107
   render_testsuites(m_outfile.value());
1✔
108
}
1✔
109

110
std::string XmlReporter::get_unique_output_filename() const {
1✔
111
   const uint64_t ts = Botan_Tests::Test::timestamp();
1✔
112
   std::vector<uint8_t> seed(8);
1✔
113
   Botan::store_be(ts, seed.data());
1✔
114

115
   std::stringstream ss;
1✔
116
   ss << m_output_dir << "/"
1✔
117
      << "Botan-" << Botan::short_version_string() << "-tests-" << Botan::hex_encode(seed, false) << ".xml";
3✔
118

119
   return ss.str();
1✔
120
}
2✔
121

122
void XmlReporter::next_run() {
1✔
123
   if(m_outfile.has_value()) {
1✔
124
      m_outfile.reset();
×
125
   }
126

127
   set_property("current test run", std::to_string(current_test_run()));
2✔
128
   set_property("total test runs", std::to_string(total_test_runs()));
2✔
129
   const auto file = get_unique_output_filename();
1✔
130
   m_outfile = std::ofstream(file, std::ofstream::out | std::ofstream::trunc);
1✔
131

132
   if(!m_outfile->good()) {
1✔
133
      std::stringstream ss;
×
134
      ss << "Failed to open '" << file << "' for writing JUnit report.";
×
135
      throw Botan::System_Error(ss.str());
×
136
   }
×
137
}
1✔
138

139
// == == == == == == == == == == == == == == == == == == == == == == == == == ==
140
// XML Rendering
141
// == == == == == == == == == == == == == == == == == == == == == == == == == ==
142

143
namespace {
144

145
void replace(std::string& str, const std::string& from, const std::string& to) {
21,177✔
146
   if(from.empty()) {
21,177✔
147
      return;
148
   }
149

150
   for(size_t offset = 0, pos = 0; (pos = str.find(from, offset)) != std::string::npos; offset = pos + to.size()) {
21,197✔
151
      str.replace(pos, from.size(), to);
20✔
152
   }
153
}
154

155
std::string escape(std::string str) {
3,700✔
156
   replace(str, "&", "&amp;");
11,100✔
157
   replace(str, "<", "&lt;");
7,400✔
158
   replace(str, ">", "&gt;");
7,400✔
159
   replace(str, "\"", "&quot;");
7,400✔
160
   replace(str, "'", "&apos;");
7,400✔
161
   return str;
7,400✔
162
}
163

164
std::string format_cdata(std::string str) {
2,677✔
165
   // XML CDATA payloads are not evaluated, hence no special character encoding
166
   // is needed.
167
   // Though the termination sequence (i.e. ']]>') must not appear in
168
   // a CDATA payload frame. The only way to escape it is to terminate the CDATA
169
   // sequence and break the payload's termination sequence into the adjacent
170
   // CDATA frames.
171
   //
172
   //   See: https://stackoverflow.com/a/223782
173
   replace(str, "]]>", "]]]><![CDATA[]>");
5,354✔
174
   //            ^^^ -> ^~~~~~~~~~~~~^^
175

176
   // wrap the (escaped) payload into a CDATA frame
177
   std::ostringstream out;
2,677✔
178
   out << "<![CDATA[" << str << "]]>";
2,677✔
179
   return out.str();
5,354✔
180
}
2,677✔
181

182
}  // namespace
183

184
void XmlReporter::render_preamble(std::ostream& out) const {
1✔
185
   out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1✔
186
}
×
187

188
void XmlReporter::render_properties(std::ostream& out) const {
1✔
189
   if(properties().empty()) {
1✔
190
      return;
191
   }
192

193
   out << "<properties>\n";
1✔
194
   for(const auto& prop : properties()) {
12✔
195
      out << "<property"
11✔
196
          << " name=\"" << escape(prop.first) << "\""
22✔
197
          << " value=\"" << escape(prop.second) << "\""
22✔
198
          << " />\n";
33✔
199
   }
200
   out << "</properties>\n";
1✔
201
}
202

203
void XmlReporter::render_testsuites(std::ostream& out) const {
1✔
204
   // render an empty testsuites tag even if no tests were run
205
   out << "<testsuites"
1✔
206
       << " tests=\"" << tests_run() << "\""
1✔
207
       << " failures=\"" << tests_failed() << "\""
1✔
208
       << " time=\"" << format(elapsed_time()) << "\">\n";
2✔
209

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

215
   for(const auto& suite : testsuites()) {
321✔
216
      render_testsuite(out, suite.second);
320✔
217
   }
218

219
   out << "</testsuites>\n";
1✔
220
}
1✔
221

222
void XmlReporter::render_testsuite(std::ostream& out, const Testsuite& suite) const {
320✔
223
   out << "<testsuite"
320✔
224
       << " name=\"" << escape(suite.name()) << "\""
640✔
225
       << " tests=\"" << suite.tests_run() << "\""
640✔
226
       << " failures=\"" << suite.tests_failed() << "\""
320✔
227
       << " timestamp=\"" << format(suite.timestamp()) << "\"";
960✔
228

229
   const auto elapsed = suite.elapsed_time();
320✔
230
   if(elapsed.has_value()) {
320✔
231
      out << " time=\"" << format(elapsed.value()) << "\"";
648✔
232
   }
233

234
   if(suite.results().empty()) {
320✔
235
      out << " />\n";
×
236
   } else {
237
      out << ">\n";
320✔
238

239
      for(const auto& result : suite.results()) {
1,999✔
240
         render_testcase(out, result);
1,679✔
241
      }
242

243
      out << "</testsuite>\n";
320✔
244
   }
245
}
320✔
246

247
void XmlReporter::render_testcase(std::ostream& out, const TestSummary& test) const {
1,679✔
248
   out << "<testcase"
1,679✔
249
       << " name=\"" << escape(test.name) << "\""
3,358✔
250
       << " assertions=\"" << test.assertions << "\""
3,358✔
251
       << " timestamp=\"" << format(test.timestamp) << "\"";
5,037✔
252

253
   if(test.elapsed_time.has_value()) {
1,679✔
254
      out << " time=\"" << format(test.elapsed_time.value()) << "\"";
1,992✔
255
   }
256

257
   if(test.code_location.has_value()) {
1,679✔
258
      out << " file=\"" << escape(test.code_location->path) << "\""
5,037✔
259
          << " line=\"" << test.code_location->line << "\"";
3,358✔
260
   }
261

262
   if(test.failures.empty() && test.notes.empty()) {
1,679✔
263
      out << " />\n";
1,562✔
264
   } else {
265
      out << ">\n";
117✔
266
      render_failures_and_stdout(out, test);
117✔
267
      out << "</testcase>\n";
117✔
268
   }
269
}
1,679✔
270

271
void XmlReporter::render_failures_and_stdout(std::ostream& out, const TestSummary& test) const {
117✔
272
   for(const auto& failure : test.failures) {
117✔
273
      out << "<failure>\n"
×
274
          << format_cdata(failure) << "\n"
×
275
          << "</failure>\n";
×
276
   }
277

278
   // xUnit format does not have a special tag for test notes, hence we
279
   // render it into the freetext 'system-out'
280
   if(!test.notes.empty()) {
117✔
281
      out << "<system-out>\n";
117✔
282
      for(const auto& note : test.notes) {
2,794✔
283
         out << format_cdata(note) << '\n';
10,708✔
284
      }
285
      out << "</system-out>\n";
117✔
286
   }
287
}
117✔
288

289
}  // namespace Botan_Tests
290

291
#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

© 2025 Coveralls, Inc