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

randombit / botan / 5079590438

25 May 2023 12:28PM UTC coverage: 92.228% (+0.5%) from 91.723%
5079590438

Pull #3502

github

Pull Request #3502: Apply clang-format to the codebase

75589 of 81959 relevant lines covered (92.23%)

12139530.51 hits per line

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

93.13
/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) {
1,955✔
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);
1,955✔
29
   #endif
30
   return buffer;
1,955✔
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) {
1,955✔
35
   auto seconds_since_epoch = std::chrono::system_clock::to_time_t(tp);
1,955✔
36

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

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

46
   std::ostringstream out;
1,188✔
47
   out.precision(3);
1,188✔
48
   out << std::fixed << secs;
1,188✔
49
   return out.str();
2,376✔
50
}
1,188✔
51

52
}
53

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

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

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

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

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

79
   return ss.str();
1✔
80
}
2✔
81

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

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

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

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

103
namespace {
104

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

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

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

124
std::string format_cdata(std::string str) {
2,677✔
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[]>");
5,354✔
134
   //            ^^^ -> ^~~~~~~~~~~~~^^
135

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

142
}
143

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

146
void XmlReporter::render_properties(std::ostream& out) const {
1✔
147
   if(properties().empty()) {
1✔
148
      return;
149
   }
150

151
   out << "<properties>\n";
1✔
152
   for(const auto& prop : properties()) {
9✔
153
      out << "<property"
8✔
154
          << " name=\"" << escape(prop.first) << "\""
16✔
155
          << " value=\"" << escape(prop.second) << "\""
16✔
156
          << " />\n";
24✔
157
   }
158
   out << "</properties>\n";
1✔
159
}
160

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

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

173
   for(const auto& suite : testsuites()) {
312✔
174
      render_testsuite(out, suite.second);
311✔
175
   }
176

177
   out << "</testsuites>\n";
1✔
178
}
1✔
179

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

187
   const auto elapsed = suite.elapsed_time();
311✔
188
   if(elapsed.has_value()) {
311✔
189
      out << " time=\"" << format(elapsed.value()) << "\"";
627✔
190
   }
191

192
   if(suite.results().empty()) {
311✔
193
      out << " />\n";
×
194
   } else {
195
      out << ">\n";
311✔
196

197
      for(const auto& result : suite.results()) {
1,954✔
198
         render_testcase(out, result);
1,643✔
199
      }
200

201
      out << "</testsuite>\n";
311✔
202
   }
203
}
311✔
204

205
void XmlReporter::render_testcase(std::ostream& out, const TestSummary& test) const {
1,643✔
206
   out << "<testcase"
1,643✔
207
       << " name=\"" << escape(test.name) << "\""
3,286✔
208
       << " assertions=\"" << test.assertions << "\""
3,286✔
209
       << " timestamp=\"" << format(test.timestamp) << "\"";
4,929✔
210

211
   if(test.elapsed_time.has_value()) {
1,643✔
212
      out << " time=\"" << format(test.elapsed_time.value()) << "\"";
1,956✔
213
   }
214

215
   if(test.code_location.has_value()) {
1,643✔
216
      out << " file=\"" << escape(test.code_location->path) << "\""
4,929✔
217
          << " line=\"" << test.code_location->line << "\"";
3,286✔
218
   }
219

220
   if(test.failures.empty() && test.notes.empty()) {
1,643✔
221
      out << " />\n";
1,526✔
222
   } else {
223
      out << ">\n";
117✔
224
      render_failures_and_stdout(out, test);
117✔
225
      out << "</testcase>\n";
117✔
226
   }
227
}
1,643✔
228

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

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

247
}
248

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