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

thetic / mutiny / 24009085345

05 Apr 2026 07:46PM UTC coverage: 98.971%. Remained the same
24009085345

Pull #28

github

web-flow
Merge f94b1b372 into 9ac355afb
Pull Request #28: Fix duplicate CI runs on branches with open PRs

5576 of 5634 relevant lines covered (98.97%)

3588.02 hits per line

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

96.98
/src/test/JUnitOutput.cpp
1
#include "mutiny/test/JUnitOutput.hpp"
2

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

8
#include "mutiny/time.hpp"
9

10
#include <stdint.h>
11

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

16
namespace {
17

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

26
} // namespace
27

28
class JUnitTestCaseResultNode
29
{
30
public:
31
  JUnitTestCaseResultNode() = default;
59✔
32

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

46
class JUnitTestGroupResult
47
{
48
public:
49
  JUnitTestGroupResult() = default;
42✔
50

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

62
class JUnitTestOutputImpl
63
{
64
public:
65
  JUnitTestGroupResult results;
66
  Output::File file;
67
  String package;
68
  String std_output;
69
};
70

71
JUnitOutput::JUnitOutput()
42✔
72
  : impl_(new JUnitTestOutputImpl)
42✔
73
{
74
}
42✔
75

76
JUnitOutput::~JUnitOutput()
84✔
77
{
78
  reset_test_group_result();
42✔
79
  delete impl_;
42✔
80
}
84✔
81

82
void JUnitOutput::reset_test_group_result()
88✔
83
{
84
  impl_->results.test_count = 0;
88✔
85
  impl_->results.failure_count = 0;
88✔
86
  impl_->results.error_count = 0;
88✔
87
  impl_->results.group = "";
88✔
88
  JUnitTestCaseResultNode* cur = impl_->results.head;
88✔
89
  while (cur) {
147✔
90
    JUnitTestCaseResultNode* tmp = cur->next;
59✔
91
    delete cur->failure;
59✔
92
    TestProperty* prop = cur->properties;
59✔
93
    while (prop) {
64✔
94
      TestProperty* prop_tmp = prop->next;
5✔
95
      delete prop;
5✔
96
      prop = prop_tmp;
5✔
97
    }
98
    delete cur;
59✔
99
    cur = tmp;
59✔
100
  }
101
  impl_->results.head = nullptr;
88✔
102
  impl_->results.tail = nullptr;
88✔
103
}
88✔
104

105
void JUnitOutput::print_tests_started() {}
41✔
106

107
void JUnitOutput::print_current_group_started(const Shell& /*test*/) {}
46✔
108

109
void JUnitOutput::print_current_test_ended(const Result& result)
59✔
110
{
111
  impl_->results.tail->exec_time =
118✔
112
      result.get_current_test_total_execution_time();
59✔
113
  impl_->results.tail->check_count = result.get_check_count();
59✔
114
}
59✔
115

116
void JUnitOutput::print_tests_ended(const Result& /*result*/) {}
41✔
117

118
void JUnitOutput::print_current_group_ended(const Result& result)
46✔
119
{
120
  impl_->results.group_exec_time =
92✔
121
      result.get_current_group_total_execution_time();
46✔
122
  write_test_group_to_file();
46✔
123
  reset_test_group_result();
46✔
124
}
46✔
125

126
void JUnitOutput::print_current_test_started(const Shell& test)
59✔
127
{
128
  impl_->results.test_count++;
59✔
129
  impl_->results.group = test.get_group();
59✔
130
  impl_->results.start_time = get_time_in_millis();
59✔
131

132
  if (impl_->results.tail == nullptr) {
59✔
133
    impl_->results.head = impl_->results.tail = new JUnitTestCaseResultNode;
46✔
134
  } else {
135
    impl_->results.tail->next = new JUnitTestCaseResultNode;
13✔
136
    impl_->results.tail = impl_->results.tail->next;
13✔
137
  }
138
  impl_->results.tail->name = test.get_name();
59✔
139
  impl_->results.tail->file = test.get_file();
59✔
140
  impl_->results.tail->line_number = test.get_line_number();
59✔
141
  if (!test.will_run()) {
59✔
142
    impl_->results.tail->ignored = true;
1✔
143
  }
144
}
59✔
145

146
String JUnitOutput::create_file_name(const String& group)
47✔
147
{
148
  String file_name = "mutiny_";
47✔
149
  if (!impl_->package.empty()) {
47✔
150
    file_name += impl_->package;
5✔
151
    file_name += "_";
5✔
152
  }
153
  file_name += group;
47✔
154
  return encode_file_name(file_name) + ".xml";
141✔
155
}
47✔
156

157
String JUnitOutput::encode_file_name(const String& file_name)
47✔
158
{
159
  // special character list based on: https://en.wikipedia.org/wiki/Filename
160
  static const char* const forbidden_characters = "/\\?%*:|\"<>";
161

162
  String result = file_name;
47✔
163
  for (const char* sym = forbidden_characters; *sym; ++sym) {
517✔
164
    string_replace(result, *sym, '_');
470✔
165
  }
166
  return result;
47✔
167
}
×
168

169
void JUnitOutput::set_package_name(const String& package)
5✔
170
{
171
  if (impl_ != nullptr) {
5✔
172
    impl_->package = package;
5✔
173
  }
174
}
5✔
175

176
void JUnitOutput::write_xml_header()
46✔
177
{
178
  write_to_file("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
46✔
179
}
46✔
180

181
void JUnitOutput::write_test_suite_summary()
46✔
182
{
183
  String buf = string_from_format(
184
      "<testsuite errors=\"%d\" failures=\"%d\" "
185
      "name=\"%s\" tests=\"%d\" time=\"%d.%03d\" timestamp=\"%s\">\n",
186
      static_cast<int>(impl_->results.error_count),
46✔
187
      static_cast<int>(impl_->results.failure_count),
46✔
188
      impl_->results.group.c_str(),
46✔
189
      static_cast<int>(impl_->results.test_count),
46✔
190
      static_cast<int>(impl_->results.group_exec_time / 1000),
46✔
191
      static_cast<int>(impl_->results.group_exec_time % 1000),
46✔
192
      get_time_string()
193
  );
46✔
194
  write_to_file(buf.c_str());
46✔
195
}
46✔
196

197
void JUnitOutput::write_properties()
46✔
198
{
199
  write_to_file("<properties>\n");
46✔
200
  write_to_file("</properties>\n");
46✔
201
}
46✔
202

203
String JUnitOutput::encode_xml_text(const String& textbody)
73✔
204
{
205
  String buf = textbody.c_str();
73✔
206
  string_replace(buf, "&", "&amp;");
73✔
207
  string_replace(buf, "\"", "&quot;");
73✔
208
  string_replace(buf, "<", "&lt;");
73✔
209
  string_replace(buf, ">", "&gt;");
73✔
210
  string_replace(buf, "\r", "&#13;");
73✔
211
  string_replace(buf, "\n", "&#10;");
73✔
212
  return buf;
73✔
213
}
×
214

215
void JUnitOutput::write_test_cases()
46✔
216
{
217
  JUnitTestCaseResultNode* cur = impl_->results.head;
46✔
218

219
  while (cur) {
105✔
220
    String buf = string_from_format(
221
        "<testcase classname=\"%s%s%s\" name=\"%s\" assertions=\"%d\" "
222
        "time=\"%d.%03d\" file=\"%s\" line=\"%d\">\n",
223
        impl_->package.c_str(),
59✔
224
        impl_->package.empty() ? "" : ".",
118✔
225
        impl_->results.group.c_str(),
59✔
226
        cur->name.c_str(),
227
        static_cast<int>(cur->check_count - impl_->results.total_check_count),
59✔
228
        static_cast<int>(cur->exec_time / 1000),
59✔
229
        static_cast<int>(cur->exec_time % 1000),
59✔
230
        cur->file.c_str(),
231
        static_cast<int>(cur->line_number)
59✔
232
    );
59✔
233
    write_to_file(buf.c_str());
59✔
234

235
    impl_->results.total_check_count = cur->check_count;
59✔
236

237
    if (cur->properties) {
59✔
238
      write_to_file("<properties>\n");
4✔
239
      for (TestProperty* prop = cur->properties; prop; prop = prop->next) {
9✔
240
        String prop_buf = string_from_format(
241
            "<property name=\"%s\" value=\"%s\"/>\n",
242
            encode_xml_text(prop->name).c_str(),
5✔
243
            encode_xml_text(prop->value).c_str()
10✔
244
        );
5✔
245
        write_to_file(prop_buf.c_str());
5✔
246
      }
5✔
247
      write_to_file("</properties>\n");
4✔
248
    }
249

250
    if (cur->failure) {
59✔
251
      if (cur->failure_is_error)
17✔
252
        write_error(cur);
4✔
253
      else
254
        write_failure(cur);
13✔
255
    } else if (cur->ignored) {
42✔
256
      write_to_file("<skipped />\n");
1✔
257
    }
258
    write_to_file("</testcase>\n");
59✔
259
    cur = cur->next;
59✔
260
  }
59✔
261
}
46✔
262

263
void JUnitOutput::write_failure(JUnitTestCaseResultNode* node)
13✔
264
{
265
  String msg = encode_xml_text(node->failure->get_message());
13✔
266
  String buf = string_from_format(
267
      "<failure message=\"%s:%d: %s\" type=\"AssertionFailedError\">\n"
268
      "%s:%d: %s\n",
269
      node->failure->get_file_name().c_str(),
26✔
270
      static_cast<int>(node->failure->get_failure_line_number()),
26✔
271
      msg.c_str(),
272
      node->failure->get_file_name().c_str(),
26✔
273
      static_cast<int>(node->failure->get_failure_line_number()),
26✔
274
      msg.c_str()
275
  );
13✔
276
  write_to_file(buf.c_str());
13✔
277
  write_to_file("</failure>\n");
13✔
278
}
13✔
279

280
void JUnitOutput::write_error(JUnitTestCaseResultNode* node)
4✔
281
{
282
  String msg = encode_xml_text(node->failure->get_message());
4✔
283
  String buf = string_from_format(
284
      "<error message=\"%s\" type=\"UnexpectedException\">\n"
285
      "%s\n",
286
      msg.c_str(),
287
      msg.c_str()
288
  );
4✔
289
  write_to_file(buf.c_str());
4✔
290
  write_to_file("</error>\n");
4✔
291
}
4✔
292

293
void JUnitOutput::write_file_ending()
46✔
294
{
295
  write_to_file("<system-out>");
46✔
296
  write_to_file(encode_xml_text(impl_->std_output));
46✔
297
  write_to_file("</system-out>\n");
46✔
298
  write_to_file("<system-err></system-err>\n");
46✔
299
  write_to_file("</testsuite>\n");
46✔
300
}
46✔
301

302
void JUnitOutput::write_test_group_to_file()
46✔
303
{
304
  open_file_for_write(create_file_name(impl_->results.group));
46✔
305
  write_xml_header();
46✔
306
  write_test_suite_summary();
46✔
307
  write_properties();
46✔
308
  write_test_cases();
46✔
309
  write_file_ending();
46✔
310
  close_file();
46✔
311
}
46✔
312

313
void JUnitOutput::print_buffer(const char*) {}
×
314

315
void JUnitOutput::print(const char* output)
2✔
316
{
317
  impl_->std_output += output;
2✔
318
}
2✔
319

320
void JUnitOutput::print(long) {}
×
321

322
void JUnitOutput::print(size_t) {}
×
323

324
void JUnitOutput::print_test_property(const char* name, const char* value)
5✔
325
{
326
  if (impl_->results.tail == nullptr)
5✔
327
    return;
×
328
  auto* prop = new TestProperty;
5✔
329
  prop->name = name;
5✔
330
  prop->value = value;
5✔
331
  if (impl_->results.tail->properties == nullptr) {
5✔
332
    impl_->results.tail->properties = prop;
4✔
333
    impl_->results.tail->properties_tail = prop;
4✔
334
  } else {
335
    impl_->results.tail->properties_tail->next = prop;
1✔
336
    impl_->results.tail->properties_tail = prop;
1✔
337
  }
338
}
339

340
void JUnitOutput::print_failure(const Failure& failure)
17✔
341
{
342
  if (impl_->results.tail->failure == nullptr) {
17✔
343
    if (failure.is_error()) {
17✔
344
      impl_->results.error_count++;
4✔
345
      impl_->results.tail->failure_is_error = true;
4✔
346
    } else {
347
      impl_->results.failure_count++;
13✔
348
    }
349
    impl_->results.tail->failure = new Failure(failure);
17✔
350
  }
351
}
17✔
352

353
void JUnitOutput::open_file_for_write(const String& file_name)
46✔
354
{
355
  impl_->file = fopen_(file_name.c_str(), "w");
46✔
356
}
46✔
357

358
void JUnitOutput::write_to_file(const String& buffer)
580✔
359
{
360
  fputs_(buffer.c_str(), impl_->file);
580✔
361
}
580✔
362

363
void JUnitOutput::close_file()
46✔
364
{
365
  fclose_(impl_->file);
46✔
366
}
46✔
367

368
} // namespace test
369
} // namespace tiny
370
} // 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