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

randombit / botan / 5230455705

10 Jun 2023 02:30PM UTC coverage: 91.715% (-0.03%) from 91.746%
5230455705

push

github

randombit
Merge GH #3584 Change clang-format AllowShortFunctionsOnASingleLine config from All to Inline

77182 of 84154 relevant lines covered (91.72%)

11975295.43 hits per line

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

92.48
/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/version.h>
13
   #include <botan/internal/loadstor.h>
14

15
   #include <iomanip>
16
   #include <numeric>
17
   #include <time.h>
18

19
namespace Botan_Tests {
20

21
namespace {
22

23
std::tm* localtime(const time_t* timer, std::tm* buffer) {
24
   #if defined(BOTAN_BUILD_COMPILER_IS_MSVC) || defined(BOTAN_TARGET_OS_IS_MINGW) || \
25
      defined(BOTAN_TARGET_OS_IS_CYGWIN) || defined(BOTAN_TARGET_OS_IS_WINDOWS)
26
   localtime_s(buffer, timer);
27
   #else
28
   localtime_r(timer, buffer);
29
   #endif
30
   return buffer;
31
}
32

33
/// formats a given time point in ISO 8601 format (with time zone)
34
std::string format(const std::chrono::system_clock::time_point& tp) {
35
   auto seconds_since_epoch = std::chrono::system_clock::to_time_t(tp);
36

37
   std::ostringstream out;
38
   std::tm buffer{};
39
   out << std::put_time(localtime(&seconds_since_epoch, &buffer), "%FT%T%z");
40
   return out.str();
41
}
42

43
std::string format(const std::chrono::nanoseconds& dur) {
44
   const float secs = static_cast<float>(dur.count()) / 1000000000;
45

46
   std::ostringstream out;
47
   out.precision(3);
48
   out << std::fixed << secs;
49
   return out.str();
50
}
51

52
}  // namespace
53

54
XmlReporter::XmlReporter(const Test_Options& opts, std::string output_dir) :
55
      Reporter(opts), m_output_dir(std::move(output_dir)) {
56
   set_property("timestamp", format(std::chrono::system_clock::now()));
57
   auto custom_props = opts.report_properties();
58
   for(const auto& prop : custom_props) {
59
      set_property(prop.first, prop.second);
60
   }
61
}
62

63
void XmlReporter::render() const {
64
   BOTAN_STATE_CHECK(m_outfile.has_value() && m_outfile->good());
65

66
   render_preamble(m_outfile.value());
67
   render_testsuites(m_outfile.value());
68
}
69

70
std::string XmlReporter::get_unique_output_filename() const {
71
   const uint64_t ts = Botan_Tests::Test::timestamp();
72
   std::vector<uint8_t> seed(8);
73
   Botan::store_be(ts, seed.data());
74

75
   std::stringstream ss;
76
   ss << m_output_dir << "/"
77
      << "Botan-" << Botan::short_version_string() << "-tests-" << Botan::hex_encode(seed, false) << ".xml";
78

79
   return ss.str();
80
}
81

82
void XmlReporter::next_run() {
83
   if(m_outfile.has_value()) {
84
      m_outfile.reset();
85
   }
86

87
   set_property("current test run", std::to_string(current_test_run()));
88
   set_property("total test runs", std::to_string(total_test_runs()));
89
   const auto file = get_unique_output_filename();
90
   m_outfile = std::ofstream(file, std::ofstream::out | std::ofstream::trunc);
91

92
   if(!m_outfile->good()) {
93
      std::stringstream ss;
94
      ss << "Failed to open '" << file << "' for writing JUnit report.";
95
      throw Botan::System_Error(ss.str());
96
   }
97
}
98

99
// == == == == == == == == == == == == == == == == == == == == == == == == == ==
100
// XML Rendering
101
// == == == == == == == == == == == == == == == == == == == == == == == == == ==
102

103
namespace {
104

105
void replace(std::string& str, const std::string& from, const std::string& to) {
106
   if(from.empty()) {
107
      return;
108
   }
109

110
   for(size_t offset = 0, pos = 0; (pos = str.find(from, offset)) != std::string::npos; offset = pos + to.size()) {
111
      str.replace(pos, from.size(), to);
112
   }
113
}
114

115
std::string escape(std::string str) {
116
   replace(str, "&", "&amp;");
117
   replace(str, "<", "&lt;");
118
   replace(str, ">", "&gt;");
119
   replace(str, "\"", "&quot;");
120
   replace(str, "'", "&apos;");
121
   return str;
122
}
123

124
std::string format_cdata(std::string str) {
125
   // XML CDATA payloads are not evaluated, hence no special character encoding
126
   // is needed.
127
   // Though the termination sequence (i.e. ']]>') must not appear in
128
   // a CDATA payload frame. The only way to escape it is to terminate the CDATA
129
   // sequence and break the payload's termination sequence into the adjacent
130
   // CDATA frames.
131
   //
132
   //   See: https://stackoverflow.com/a/223782
133
   replace(str, "]]>", "]]]><![CDATA[]>");
134
   //            ^^^ -> ^~~~~~~~~~~~~^^
135

136
   // wrap the (escaped) payload into a CDATA frame
137
   std::ostringstream out;
138
   out << "<![CDATA[" << str << "]]>";
139
   return out.str();
140
}
141

142
}  // namespace
143

144
void XmlReporter::render_preamble(std::ostream& out) const {
145
   out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
146
}
147

148
void XmlReporter::render_properties(std::ostream& out) const {
149
   if(properties().empty()) {
150
      return;
151
   }
152

153
   out << "<properties>\n";
154
   for(const auto& prop : properties()) {
155
      out << "<property"
156
          << " name=\"" << escape(prop.first) << "\""
157
          << " value=\"" << escape(prop.second) << "\""
158
          << " />\n";
159
   }
160
   out << "</properties>\n";
161
}
162

163
void XmlReporter::render_testsuites(std::ostream& out) const {
164
   // render an empty testsuites tag even if no tests were run
165
   out << "<testsuites"
166
       << " tests=\"" << tests_run() << "\""
167
       << " failures=\"" << tests_failed() << "\""
168
       << " time=\"" << format(elapsed_time()) << "\">\n";
169

170
   // Note: In the JUnit .xsd spec, <properties> appear only in individual
171
   //       test cases. This deviation from the spec allows us to embed
172
   //       specific platform information about this particular test run.
173
   render_properties(out);
174

175
   for(const auto& suite : testsuites()) {
176
      render_testsuite(out, suite.second);
177
   }
178

179
   out << "</testsuites>\n";
180
}
181

182
void XmlReporter::render_testsuite(std::ostream& out, const Testsuite& suite) const {
183
   out << "<testsuite"
184
       << " name=\"" << escape(suite.name()) << "\""
185
       << " tests=\"" << suite.tests_run() << "\""
186
       << " failures=\"" << suite.tests_failed() << "\""
187
       << " timestamp=\"" << format(suite.timestamp()) << "\"";
188

189
   const auto elapsed = suite.elapsed_time();
190
   if(elapsed.has_value()) {
191
      out << " time=\"" << format(elapsed.value()) << "\"";
192
   }
193

194
   if(suite.results().empty()) {
195
      out << " />\n";
196
   } else {
197
      out << ">\n";
198

199
      for(const auto& result : suite.results()) {
200
         render_testcase(out, result);
201
      }
202

203
      out << "</testsuite>\n";
204
   }
205
}
206

207
void XmlReporter::render_testcase(std::ostream& out, const TestSummary& test) const {
208
   out << "<testcase"
209
       << " name=\"" << escape(test.name) << "\""
210
       << " assertions=\"" << test.assertions << "\""
211
       << " timestamp=\"" << format(test.timestamp) << "\"";
212

213
   if(test.elapsed_time.has_value()) {
214
      out << " time=\"" << format(test.elapsed_time.value()) << "\"";
215
   }
216

217
   if(test.code_location.has_value()) {
218
      out << " file=\"" << escape(test.code_location->path) << "\""
219
          << " line=\"" << test.code_location->line << "\"";
220
   }
221

222
   if(test.failures.empty() && test.notes.empty()) {
223
      out << " />\n";
224
   } else {
225
      out << ">\n";
226
      render_failures_and_stdout(out, test);
227
      out << "</testcase>\n";
228
   }
229
}
230

231
void XmlReporter::render_failures_and_stdout(std::ostream& out, const TestSummary& test) const {
232
   for(const auto& failure : test.failures) {
233
      out << "<failure>\n"
234
          << format_cdata(failure) << "\n"
235
          << "</failure>\n";
236
   }
237

238
   // xUnit format does not have a special tag for test notes, hence we
239
   // render it into the freetext 'system-out'
240
   if(!test.notes.empty()) {
241
      out << "<system-out>\n";
242
      for(const auto& note : test.notes) {
243
         out << format_cdata(note) << '\n';
244
      }
245
      out << "</system-out>\n";
246
   }
247
}
248

249
}  // namespace Botan_Tests
250

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