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

thetic / mu.tiny / 24642562173

20 Apr 2026 12:12AM UTC coverage: 98.89% (+0.01%) from 98.879%
24642562173

Pull #84

github

web-flow
Merge 37778513a into a2dfbd6ba
Pull Request #84: apply clang-tidy fixes

625 of 626 new or added lines in 45 files covered. (99.84%)

2 existing lines in 2 files now uncovered.

5254 of 5313 relevant lines covered (98.89%)

3352.89 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 <cstdint>
11

12
#include <stdint.h>
13

14
namespace mu {
15
namespace tiny {
16
namespace test {
17

18
namespace {
19
constexpr uint_least64_t ms_per_s{ 1000 };
20

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

29
} // namespace
30

31
class JUnitTestCaseResultNode
32
{
33
public:
34
  JUnitTestCaseResultNode() = default;
61✔
35

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

50
class JUnitTestGroupResult
51
{
52
public:
53
  JUnitTestGroupResult() = default;
51✔
54

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

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

82
JUnitOutput::JUnitOutput()
51✔
83
  : impl_(new JUnitTestOutputImpl)
51✔
84
{
85
}
51✔
86

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

281
    impl_->results.total_check_count = cur->check_count;
61✔
282

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

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

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

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

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

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

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

NEW
365
void JUnitOutput::print_buffer(const char* /*buffer*/) {}
×
366

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

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

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

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

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

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

422
} // namespace test
423
} // namespace tiny
424
} // 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