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

thetic / mu.tiny / 24643363089

20 Apr 2026 12:49AM UTC coverage: 98.907% (+0.03%) from 98.879%
24643363089

Pull #84

github

web-flow
Merge 8836ff557 into a2dfbd6ba
Pull Request #84: apply clang-tidy fixes

821 of 822 new or added lines in 45 files covered. (99.88%)

2 existing lines in 2 files now uncovered.

5341 of 5400 relevant lines covered (98.91%)

3300.33 hits per line

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

96.41
/src/test/JUnitOutput.cpp
1
#include "mu/tiny/test/JUnitOutput.hpp"
2

3
#include "mu/tiny/test/Failure.hpp"
4
#include "mu/tiny/test/Output.hpp"
5
#include "mu/tiny/test/Result.hpp"
6
#include "mu/tiny/test/Shell.hpp"
7

8
#include "mu/tiny/time.hpp"
9

10
#include <stdint.h>
11

12
namespace mu {
13
namespace tiny {
14
namespace test {
15

16
namespace {
17
constexpr uint_least64_t ms_per_s{ 1000 };
18

19
class TestProperty
20
{
21
public:
22
  String name;
23
  String value;
24
  TestProperty* next{ nullptr };
25
};
26

27
} // namespace
28

29
class JUnitTestCaseResultNode
30
{
31
public:
32
  JUnitTestCaseResultNode() = default;
61✔
33

34
  String name;
35
  uint_least64_t exec_time{ 0 };
36
  Failure* failure{ nullptr };
37
  bool failure_is_error{ false };
38
  bool ignored{ false };
39
  String skip_message;
40
  String file;
41
  size_t line_number{ 0 };
42
  size_t check_count{ 0 };
43
  TestProperty* properties{ nullptr };
44
  TestProperty* properties_tail{ nullptr };
45
  JUnitTestCaseResultNode* next{ nullptr };
46
};
47

48
class JUnitTestGroupResult
49
{
50
public:
51
  JUnitTestGroupResult() = default;
51✔
52

53
  size_t test_count{ 0 };
54
  size_t failure_count{ 0 };
55
  size_t error_count{ 0 };
56
  size_t skip_count{ 0 };
57
  size_t total_check_count{ 0 };
58
  uint_least64_t start_time{ 0 };
59
  uint_least64_t group_exec_time{ 0 };
60
  String group;
61
  JUnitTestCaseResultNode* head{ nullptr };
62
  JUnitTestCaseResultNode* tail{ nullptr };
63
};
64

65
class JUnitTestOutputImpl
66
{
67
public:
68
  JUnitTestGroupResult results;
69
  String current_group_xml;
70
  String accumulated_xml;
71
  String package;
72
  size_t total_test_count{ 0 };
73
  size_t total_failure_count{ 0 };
74
  size_t total_error_count{ 0 };
75
  size_t total_skip_count{ 0 };
76
  uint_least64_t total_exec_time{ 0 };
77
  String start_timestamp;
78
};
79

80
JUnitOutput::JUnitOutput()
51✔
81
  : impl_(new JUnitTestOutputImpl)
51✔
82
{
83
}
51✔
84

85
JUnitOutput::~JUnitOutput()
102✔
86
{
87
  reset_test_group_result();
51✔
88
  delete impl_;
51✔
89
}
102✔
90

91
void JUnitOutput::reset_test_group_result()
99✔
92
{
93
  impl_->results.test_count = 0;
99✔
94
  impl_->results.failure_count = 0;
99✔
95
  impl_->results.error_count = 0;
99✔
96
  impl_->results.skip_count = 0;
99✔
97
  impl_->results.group = "";
99✔
98
  JUnitTestCaseResultNode* cur = impl_->results.head;
99✔
99
  while (cur != nullptr) {
160✔
100
    JUnitTestCaseResultNode* tmp = cur->next;
61✔
101
    delete cur->failure;
61✔
102
    TestProperty* prop = cur->properties;
61✔
103
    while (prop != nullptr) {
66✔
104
      TestProperty* prop_tmp = prop->next;
5✔
105
      delete prop;
5✔
106
      prop = prop_tmp;
5✔
107
    }
108
    delete cur;
61✔
109
    cur = tmp;
61✔
110
  }
111
  impl_->results.head = nullptr;
99✔
112
  impl_->results.tail = nullptr;
99✔
113
}
99✔
114

115
void JUnitOutput::print_tests_started()
42✔
116
{
117
  impl_->accumulated_xml.clear();
42✔
118
  impl_->total_test_count = 0;
42✔
119
  impl_->total_failure_count = 0;
42✔
120
  impl_->total_error_count = 0;
42✔
121
  impl_->total_skip_count = 0;
42✔
122
  impl_->total_exec_time = 0;
42✔
123
  impl_->start_timestamp = get_time_string();
42✔
124
}
42✔
125

126
void JUnitOutput::print_current_group_started(const Shell& /*test*/) {}
48✔
127

128
void JUnitOutput::print_current_test_ended(const Result& result)
61✔
129
{
130
  impl_->results.tail->exec_time =
122✔
131
      result.get_current_test_total_execution_time();
61✔
132
  impl_->results.tail->check_count = result.get_check_count();
61✔
133
}
61✔
134

135
void JUnitOutput::print_tests_ended(const Result& /*result*/)
42✔
136
{
137
  Output::File file = fopen_(create_file_name().c_str(), "w");
42✔
138
  String header = string_from_format(
139
      "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
140
      "<testsuites tests=\"%d\" failures=\"%d\" errors=\"%d\" "
141
      "skipped=\"%d\" time=\"%d.%03d\" timestamp=\"%s\">\n",
142
      static_cast<int>(impl_->total_test_count),
42✔
143
      static_cast<int>(impl_->total_failure_count),
42✔
144
      static_cast<int>(impl_->total_error_count),
42✔
145
      static_cast<int>(impl_->total_skip_count),
42✔
146
      static_cast<int>(impl_->total_exec_time / ms_per_s),
42✔
147
      static_cast<int>(impl_->total_exec_time % ms_per_s),
42✔
148
      impl_->start_timestamp.c_str()
42✔
149
  );
42✔
150
  fputs_(header.c_str(), file);
42✔
151
  fputs_(impl_->accumulated_xml.c_str(), file);
42✔
152
  fputs_("</testsuites>\n", file);
42✔
153
  fclose_(file);
42✔
154
}
42✔
155

156
void JUnitOutput::print_current_group_ended(const Result& result)
48✔
157
{
158
  if (impl_->results.test_count == 0) {
48✔
159
    reset_test_group_result();
×
160
    return;
×
161
  }
162
  impl_->results.group_exec_time =
96✔
163
      result.get_current_group_total_execution_time();
48✔
164
  impl_->total_test_count += impl_->results.test_count;
48✔
165
  impl_->total_failure_count += impl_->results.failure_count;
48✔
166
  impl_->total_error_count += impl_->results.error_count;
48✔
167
  impl_->total_skip_count += impl_->results.skip_count;
48✔
168
  impl_->total_exec_time += impl_->results.group_exec_time;
48✔
169
  write_test_group_to_file();
48✔
170
  reset_test_group_result();
48✔
171
}
172

173
void JUnitOutput::print_current_test_started(const Shell& test)
61✔
174
{
175
  impl_->results.test_count++;
61✔
176
  impl_->results.group = test.get_group();
61✔
177
  impl_->results.start_time = get_time_in_millis();
61✔
178

179
  if (impl_->results.tail == nullptr) {
61✔
180
    impl_->results.head = impl_->results.tail = new JUnitTestCaseResultNode;
48✔
181
  } else {
182
    impl_->results.tail->next = new JUnitTestCaseResultNode;
13✔
183
    impl_->results.tail = impl_->results.tail->next;
13✔
184
  }
185
  impl_->results.tail->name = test.get_name();
61✔
186
  impl_->results.tail->file = test.get_file();
61✔
187
  impl_->results.tail->line_number = test.get_line_number();
61✔
188
  if (!test.will_run()) {
61✔
189
    impl_->results.tail->ignored = true;
1✔
190
    impl_->results.tail->skip_message = test.get_macro_name();
1✔
191
    impl_->results.skip_count++;
1✔
192
  }
193
}
61✔
194

195
String JUnitOutput::create_file_name()
42✔
196
{
197
  if (!impl_->package.empty()) {
42✔
198
    return encode_file_name(impl_->package) + ".xml";
16✔
199
  }
200
  return "mutiny.xml";
34✔
201
}
202

203
String JUnitOutput::encode_file_name(const String& file_name)
8✔
204
{
205
  // special character list based on: https://en.wikipedia.org/wiki/Filename
206
  static const char* const forbidden_characters = "/\\?%*:|\"<>";
207

208
  String result = file_name;
8✔
209
  for (const char* sym = forbidden_characters; *sym != 0; ++sym) {
88✔
210
    string_replace(result, *sym, '_');
80✔
211
  }
212
  return result;
8✔
213
}
×
214

215
void JUnitOutput::set_package_name(const String& package)
8✔
216
{
217
  if (impl_ != nullptr) {
8✔
218
    impl_->package = package;
8✔
219
  }
220
}
8✔
221

222
void JUnitOutput::write_test_suite_summary()
48✔
223
{
224
  size_t total_assertions = 0;
48✔
225
  for (JUnitTestCaseResultNode* n = impl_->results.head; n != nullptr;
109✔
226
       n = n->next) {
61✔
227
    total_assertions = n->check_count;
61✔
228
  }
229

230
  String buf = string_from_format(
231
      "<testsuite errors=\"%d\" failures=\"%d\" skipped=\"%d\" "
232
      "assertions=\"%d\" name=\"%s\" tests=\"%d\" "
233
      "time=\"%d.%03d\" timestamp=\"%s\">\n",
234
      static_cast<int>(impl_->results.error_count),
48✔
235
      static_cast<int>(impl_->results.failure_count),
48✔
236
      static_cast<int>(impl_->results.skip_count),
48✔
237
      static_cast<int>(total_assertions),
238
      impl_->results.group.c_str(),
48✔
239
      static_cast<int>(impl_->results.test_count),
48✔
240
      static_cast<int>(impl_->results.group_exec_time / ms_per_s),
48✔
241
      static_cast<int>(impl_->results.group_exec_time % ms_per_s),
48✔
242
      get_time_string()
243
  );
48✔
244
  write_to_file(buf.c_str());
48✔
245
}
48✔
246

247
String JUnitOutput::encode_xml_text(const String& textbody)
46✔
248
{
249
  String buf = textbody.c_str();
46✔
250
  string_replace(buf, "&", "&amp;");
46✔
251
  string_replace(buf, "\"", "&quot;");
46✔
252
  string_replace(buf, "<", "&lt;");
46✔
253
  string_replace(buf, ">", "&gt;");
46✔
254
  string_replace(buf, "\r", "&#13;");
46✔
255
  string_replace(buf, "\n", "&#10;");
46✔
256
  return buf;
46✔
257
}
×
258

259
void JUnitOutput::write_test_cases()
48✔
260
{
261
  JUnitTestCaseResultNode* cur = impl_->results.head;
48✔
262

263
  while (cur != nullptr) {
109✔
264
    String buf = string_from_format(
265
        "<testcase classname=\"%s%s%s\" name=\"%s\" assertions=\"%d\" "
266
        "time=\"%d.%03d\" file=\"%s\" line=\"%d\">\n",
267
        impl_->package.c_str(),
61✔
268
        impl_->package.empty() ? "" : ".",
122✔
269
        impl_->results.group.c_str(),
61✔
270
        cur->name.c_str(),
271
        static_cast<int>(cur->check_count - impl_->results.total_check_count),
61✔
272
        static_cast<int>(cur->exec_time / ms_per_s),
61✔
273
        static_cast<int>(cur->exec_time % ms_per_s),
61✔
274
        cur->file.c_str(),
275
        static_cast<int>(cur->line_number)
61✔
276
    );
61✔
277
    write_to_file(buf.c_str());
61✔
278

279
    impl_->results.total_check_count = cur->check_count;
61✔
280

281
    if (cur->properties != nullptr) {
61✔
282
      write_to_file("<properties>\n");
4✔
283
      for (TestProperty* prop = cur->properties; prop != nullptr;
9✔
284
           prop = prop->next) {
5✔
285
        String prop_buf = string_from_format(
286
            "<property name=\"%s\" value=\"%s\"/>\n",
287
            encode_xml_text(prop->name).c_str(),
5✔
288
            encode_xml_text(prop->value).c_str()
10✔
289
        );
5✔
290
        write_to_file(prop_buf.c_str());
5✔
291
      }
5✔
292
      write_to_file("</properties>\n");
4✔
293
    }
294

295
    if (cur->failure != nullptr) {
61✔
296
      if (cur->failure_is_error) {
18✔
297
        write_error(cur);
4✔
298
      } else {
299
        write_failure(cur);
14✔
300
      }
301
    } else if (cur->ignored) {
43✔
302
      if (cur->skip_message.empty()) {
4✔
303
        write_to_file("<skipped />\n");
×
304
      } else {
305
        write_to_file(string_from_format(
4✔
306
                          "<skipped message=\"%s\" />\n",
307
                          encode_xml_text(cur->skip_message).c_str()
8✔
308
        )
309
                          .c_str());
310
      }
311
    }
312

313
    write_to_file("</testcase>\n");
61✔
314
    cur = cur->next;
61✔
315
  }
61✔
316
}
48✔
317

318
void JUnitOutput::write_failure(JUnitTestCaseResultNode* node)
14✔
319
{
320
  String file = encode_xml_text(node->failure->get_file_name());
14✔
321
  String msg = encode_xml_text(node->failure->get_message());
14✔
322
  String buf = string_from_format(
323
      "<failure message=\"%s:%d: %s\" type=\"AssertionFailedError\">\n"
324
      "%s:%d: %s\n",
325
      file.c_str(),
326
      static_cast<int>(node->failure->get_failure_line_number()),
28✔
327
      msg.c_str(),
328
      file.c_str(),
329
      static_cast<int>(node->failure->get_failure_line_number()),
28✔
330
      msg.c_str()
331
  );
14✔
332
  write_to_file(buf.c_str());
14✔
333
  write_to_file("</failure>\n");
14✔
334
}
14✔
335

336
void JUnitOutput::write_error(JUnitTestCaseResultNode* node)
4✔
337
{
338
  String msg = encode_xml_text(node->failure->get_message());
4✔
339
  String buf = string_from_format(
340
      "<error message=\"%s\" type=\"UnexpectedException\">\n"
341
      "%s\n",
342
      msg.c_str(),
343
      msg.c_str()
344
  );
4✔
345
  write_to_file(buf.c_str());
4✔
346
  write_to_file("</error>\n");
4✔
347
}
4✔
348

349
void JUnitOutput::write_file_ending()
48✔
350
{
351
  write_to_file("</testsuite>\n");
48✔
352
}
48✔
353

354
void JUnitOutput::write_test_group_to_file()
48✔
355
{
356
  open_file_for_write();
48✔
357
  write_test_suite_summary();
48✔
358
  write_test_cases();
48✔
359
  write_file_ending();
48✔
360
  close_file();
48✔
361
}
48✔
362

NEW
363
void JUnitOutput::print_buffer(const char* /*buffer*/) {}
×
364

365
void JUnitOutput::print_test_property(const char* name, const char* value)
5✔
366
{
367
  if (impl_->results.tail == nullptr) {
5✔
368
    return;
×
369
  }
370
  auto* prop = new TestProperty;
5✔
371
  prop->name = name;
5✔
372
  prop->value = value;
5✔
373
  if (impl_->results.tail->properties == nullptr) {
5✔
374
    impl_->results.tail->properties = prop;
4✔
375
    impl_->results.tail->properties_tail = prop;
4✔
376
  } else {
377
    impl_->results.tail->properties_tail->next = prop;
1✔
378
    impl_->results.tail->properties_tail = prop;
1✔
379
  }
380
}
381

382
void JUnitOutput::print_skipped(const char* message)
3✔
383
{
384
  if (impl_->results.tail == nullptr) {
3✔
385
    return;
×
386
  }
387
  impl_->results.tail->ignored = true;
3✔
388
  impl_->results.tail->skip_message = message;
3✔
389
  impl_->results.skip_count++;
3✔
390
}
391

392
void JUnitOutput::print_failure(const Failure& failure)
18✔
393
{
394
  if (impl_->results.tail->failure == nullptr) {
18✔
395
    if (failure.is_error()) {
18✔
396
      impl_->results.error_count++;
4✔
397
      impl_->results.tail->failure_is_error = true;
4✔
398
    } else {
399
      impl_->results.failure_count++;
14✔
400
    }
401
    impl_->results.tail->failure = new Failure(failure);
18✔
402
  }
403
}
18✔
404

405
void JUnitOutput::open_file_for_write()
48✔
406
{
407
  impl_->current_group_xml.clear();
48✔
408
}
48✔
409

410
void JUnitOutput::write_to_file(const String& buffer)
271✔
411
{
412
  impl_->current_group_xml += buffer;
271✔
413
}
271✔
414

415
void JUnitOutput::close_file()
48✔
416
{
417
  impl_->accumulated_xml += impl_->current_group_xml;
48✔
418
}
48✔
419

420
} // namespace test
421
} // namespace tiny
422
} // namespace mu
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