OTest2
A C++ testing framework
reporterjunit.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2020 Ondrej Starek
3  *
4  * This file is part of OTest2.
5  *
6  * OTest2 is free software: you can redistribute it and/or modify it under
7  * the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License,
9  * or (at your option) any later version.
10  *
11  * OTest2 is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14  * License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with OTest2. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <reporterjunit.h>
21 
22 #include <assert.h>
23 #include <chrono>
24 #include <iomanip>
25 #include <pugixml.hpp>
26 #include <sstream>
27 #include <string>
28 #include <ctime>
29 #include <time.h>
30 #include <vector>
31 
32 #include <assertbufferstr.h>
33 #include <context.h>
34 #include <parameters.h>
35 #include <timesource.h>
36 #include <utils.h>
37 
38 namespace OTest2 {
39 
40 namespace {
41 
42 std::string formatTimestamp(
43  const TimeSource::time_point& time_) {
44  std::time_t epoche_(std::chrono::system_clock::to_time_t(time_));
45  struct tm broken_;
46  localtime_r(&epoche_, &broken_);
47  std::ostringstream oss_;
48  oss_ << std::setfill('0') << std::dec << std::internal
49  << std::setw(4) << (broken_.tm_year + 1900)
50  << '-'
51  << std::setw(2) << (broken_.tm_mon + 1)
52  << '-'
53  << std::setw(2) << broken_.tm_mday
54  << 'T'
55  << std::setw(2) << broken_.tm_hour
56  << ':'
57  << std::setw(2) << broken_.tm_min
58  << ':'
59  << std::setw(2) << broken_.tm_sec;
60  return oss_.str();
61 }
62 
63 std::string formatDuration(
64  const TimeSource::time_point& start_,
65  const TimeSource::time_point& end_) {
66  std::chrono::duration<double> duration_(end_ - start_);
67  std::ostringstream oss_;
68  oss_ << std::setprecision(3) << std::fixed << duration_.count();
69  return oss_.str();
70 }
71 
72 } /* -- namespace */
73 
74 struct ReporterJUnit::Impl : public AssertBufferListener {
75  std::string filename;
76  bool hide_location;
77 
78  /* -- XML structure */
79  ::pugi::xml_document doc;
80 
81  /* -- name of the test (usually from the command line) */
82  std::string testname;
83 
84  /* -- current active object */
85  struct Record {
86  ::pugi::xml_node node;
88  int case_count;
89  bool first_failure;
90  int case_failures;
91  bool first_error;
92  int case_errors;
93  };
94  std::vector<Record> node_stack;
95 
96  std::ostringstream message;
97  AssertBufferStrPtr assert_buffer;
98 
99  /* -- avoid copying */
100  Impl(
101  const Impl&) = delete;
102  Impl& operator = (
103  const Impl&) = delete;
104 
105  explicit Impl(
106  const std::string& file_,
107  bool hide_location_);
108  virtual ~Impl();
109 
110  void cumulateStatistics(
111  Record& target_,
112  const Record& source_);
113  void fillRunningTime(
114  const Context& context_,
115  Record& record_);
116  void fillStatistics(
117  const Context& context_,
118  Record& record_);
119  void fillTimestamp(
120  const Context& context_,
121  Record& record_);
122  void fillName(
123  const Context& context_,
124  Record& record_,
125  const std::string& name_);
126  void appendMessage(
127  const Context& context_,
128  const std::string& message_);
129  void commitMessage(
130  const Context& context_);
131 
132  /* -- listener of the assertion buffer */
133  virtual void assertionOpeningMessage(
134  const Context& context_,
135  const AssertBufferAssertData& data_,
136  const std::string& message_) override;
137  virtual void assertionAdditionalMessage(
138  const Context& context_,
139  const AssertBufferAssertData& data_,
140  const std::string& message_) override;
141  virtual void assertionClose(
142  const Context& context_,
143  const AssertBufferAssertData& data_) override;
144  virtual void errorOpeningMessage(
145  const Context& context_,
146  const std::string& message_) override;
147  virtual void errorAdditionalMessage(
148  const Context& context_,
149  const std::string& message_) override;
150  virtual void errorClose(
151  const Context& context_) override;
152 };
153 
154 ReporterJUnit::Impl::Impl(
155  const std::string& file_,
156  bool hide_location_) :
157  filename(file_),
158  hide_location(hide_location_),
159  testname(),
160  node_stack(),
161  assert_buffer(std::make_shared<AssertBufferStr>(this)) {
162 
163 }
164 
165 ReporterJUnit::Impl::~Impl() {
166 
167 }
168 
169 void ReporterJUnit::Impl::cumulateStatistics(
170  Record& target_,
171  const Record& source_) {
172  target_.case_count += source_.case_count;
173  target_.case_failures += source_.case_failures;
174  target_.case_errors += source_.case_errors;
175 }
176 
177 void ReporterJUnit::Impl::fillRunningTime(
178  const Context& context_,
179  Record& record_) {
180  /* -- time of run of the suite (in seconds) */
181  auto end_time_(context_.time_source->now());
182  auto time_(record_.node.append_attribute("time"));
183  time_ = formatDuration(record_.start, end_time_).c_str();
184 }
185 
186 void ReporterJUnit::Impl::fillStatistics(
187  const Context& context_,
188  Record& record_) {
189  /* -- number of tests */
190  auto tests_(record_.node.append_attribute("tests"));
191  tests_ = record_.case_count;
192 
193  /* -- number of failed tests */
194  auto failures_(record_.node.append_attribute("failures"));
195  failures_ = record_.case_failures;
196 
197  /* -- number of error tests */
198  auto errors_(record_.node.append_attribute("errors"));
199  errors_ = record_.case_errors;
200 }
201 
202 void ReporterJUnit::Impl::fillTimestamp(
203  const Context& context_,
204  Record& record_) {
205  /* -- timestamp of run */
206  auto timestamp_(record_.node.append_attribute("timestamp"));
207  timestamp_ = formatTimestamp(record_.start).c_str();
208 }
209 
210 void ReporterJUnit::Impl::fillName(
211  const Context& context_,
212  Record& record_,
213  const std::string& name_) {
214  /* -- name of the test */
215  auto name_node_(record_.node.append_attribute("name"));
216  name_node_ = name_.c_str();
217 }
218 
219 void ReporterJUnit::Impl::appendMessage(
220  const Context& context_,
221  const std::string& message_) {
222  message << "\n" << message_;
223 }
224 
225 void ReporterJUnit::Impl::commitMessage(
226  const Context& context_) {
227  auto& top_(node_stack.back());
228  auto node_(top_.node.last_child());
229  auto msg_(node_.append_attribute("message"));
230  msg_ = message.str().c_str();
231  message.str("");
232 }
233 
234 void ReporterJUnit::Impl::assertionOpeningMessage(
235  const Context& context_,
236  const AssertBufferAssertData& data_,
237  const std::string& message_) {
238  /* -- make the failure record */
239  if(!data_.condition) {
240  auto& top_(node_stack.back());
241  auto failure_(top_.node.append_child("failure"));
242  if(!hide_location) {
243  auto line_attr_(failure_.append_attribute("line"));
244  line_attr_ = data_.line;
245  auto file_attr_(failure_.append_attribute("file"));
246  file_attr_ = data_.file.c_str();
247  }
248  message << message_;
249  }
250 }
251 
252 void ReporterJUnit::Impl::assertionAdditionalMessage(
253  const Context& context_,
254  const AssertBufferAssertData& data_,
255  const std::string& message_) {
256  appendMessage(context_, message_);
257 }
258 
259 void ReporterJUnit::Impl::assertionClose(
260  const Context& context_,
261  const AssertBufferAssertData& data_) {
262  commitMessage(context_);
263 }
264 
265 void ReporterJUnit::Impl::errorOpeningMessage(
266  const Context& context_,
267  const std::string& message_) {
268  auto& top_(node_stack.back());
269  auto error_(top_.node.append_child("error"));
270  auto msg_(error_.append_attribute("message"));
271  message << message_;
272 }
273 
274 void ReporterJUnit::Impl::errorAdditionalMessage(
275  const Context& context_,
276  const std::string& message_) {
277  appendMessage(context_, message_);
278 }
279 
280 void ReporterJUnit::Impl::errorClose(
281  const Context& context_) {
282  commitMessage(context_);
283 }
284 
286  const std::string& file_,
287  bool hide_location_) :
288  pimpl(new Impl(file_, hide_location_)) {
289 
290 }
291 
293  odelete(pimpl);
294 }
295 
297  const Context& context_,
298  const std::string& name_,
299  const Parameters& params_) {
300  pimpl->testname = params_.mixWithName(name_);
301 
302  /* -- create root node and root test suite (CircleCI ignores standalone
303  * test cases) */
304  auto root_(pimpl->doc.append_child("testsuites"));
305  pimpl->node_stack.push_back({
306  root_.append_child("testsuite"),
307  context_.time_source->now(),
308  0,
309  true,
310  0,
311  true,
312  0,
313  });
314  pimpl->fillTimestamp(context_, pimpl->node_stack.back());
315 }
316 
318  const Context& context_,
319  const std::string& name_,
320  const Parameters& params_) {
321  /* -- create new node */
322  pimpl->node_stack.push_back({
323  pimpl->node_stack.back().node.append_child("testsuite"),
324  context_.time_source->now(),
325  0,
326  true,
327  0,
328  true,
329  0,
330  });
331  pimpl->fillName(context_, pimpl->node_stack.back(), params_.mixWithName(name_));
332  pimpl->fillTimestamp(context_, pimpl->node_stack.back());
333 }
334 
336  const Context& context_,
337  const std::string& name_,
338  const Parameters& params_) {
339  /* -- create new node */
340  pimpl->node_stack.push_back({
341  pimpl->node_stack.back().node.append_child("testcase"),
342  context_.time_source->now(),
343  1,
344  true,
345  0,
346  true,
347  0,
348  });
349  pimpl->fillName(context_, pimpl->node_stack.back(), params_.mixWithName(name_));
350 }
351 
353  const Context& context_,
354  const std::string& name_) {
355  /* -- nothing to do - the states are not supported by the JUnit report */
356 }
357 
359  const Context& context_,
360  bool condition_,
361  const std::string& file_,
362  int lineno_) {
363  if(!condition_) {
364  /* -- update statistics */
365  auto& top_(pimpl->node_stack.back());
366  if(top_.first_failure) {
367  top_.first_failure = false;
368  ++top_.case_failures;
369  }
370  }
371 
372  pimpl->assert_buffer->openAssertion({condition_, file_, lineno_});
373  return pimpl->assert_buffer;
374 }
375 
377  const Context& context_) {
378  /* -- update statistics */
379  auto& top_(pimpl->node_stack.back());
380  if(top_.first_error) {
381  top_.first_error = false;
382  ++top_.case_errors;
383  }
384 
385  pimpl->assert_buffer->openError();
386  return pimpl->assert_buffer;
387 }
388 
390  const Context& context_,
391  const std::string& name_,
392  bool result_) {
393 
394 }
395 
397  const Context& context_,
398  const std::string& name_,
399  const Parameters& params_,
400  bool result_) {
401  auto top_(pimpl->node_stack.back());
402 
403  /* -- fill test case attributes */
404  pimpl->fillRunningTime(context_, top_);
405 
406  /* -- pop the object and cumulate statistics with the parent */
407  pimpl->node_stack.pop_back();
408  pimpl->cumulateStatistics(pimpl->node_stack.back(), top_);
409 }
410 
412  const Context& context_,
413  const std::string& name_,
414  const Parameters& params_,
415  bool result_) {
416  auto top_(pimpl->node_stack.back());
417 
418  /* -- fill suite attributes */
419  pimpl->fillRunningTime(context_, top_);
420  pimpl->fillStatistics(context_, top_);
421 
422  /* -- pop the object and cumulate statistics with the parent */
423  pimpl->node_stack.pop_back();
424  pimpl->cumulateStatistics(pimpl->node_stack.back(), top_);
425 }
426 
428  const Context& context_,
429  const std::string& name_,
430  const Parameters& params_,
431  bool result_) {
432  auto top_(pimpl->node_stack.back());
433 
434  /* -- fill suite attributes */
435  pimpl->fillRunningTime(context_, top_);
436  pimpl->fillStatistics(context_, top_);
437 
438  /* -- remove the root test suite */
439  pimpl->node_stack.pop_back();
440 
441  /* -- save the XML report */
442  pimpl->doc.save_file(pimpl->filename.c_str());
443 }
444 
445 } /* -- namespace OTest2 */
OTest2::Context::time_source
TimeSource *const time_source
Definition: context.h:43
OTest2::ReporterJUnit::enterSuite
virtual void enterSuite(const Context &context_, const std::string &name_, const Parameters &params_) override
Enter a suite.
Definition: reporterjunit.cpp:317
OTest2::ReporterJUnit::~ReporterJUnit
virtual ~ReporterJUnit()
Dtor.
Definition: reporterjunit.cpp:292
OTest2::AssertBufferListener::assertionClose
virtual void assertionClose(const Context &context_, const AssertBufferAssertData &data_)=0
Closing of the assertion.
OTest2::ReporterJUnit::enterError
virtual AssertBufferPtr enterError(const Context &context_) override
Enter an error report.
Definition: reporterjunit.cpp:376
OTest2::ReporterJUnit::operator=
ReporterJUnit & operator=(const ReporterJUnit &)=delete
OTest2::AssertBufferStrPtr
std::shared_ptr< AssertBufferStr > AssertBufferStrPtr
Definition: assertbufferstrptr.h:27
OTest2::ReporterJUnit::leaveTest
virtual void leaveTest(const Context &context_, const std::string &name_, const Parameters &params_, bool result_) override
Leave entire test.
Definition: reporterjunit.cpp:427
utils.h
OTest2::Parameters::mixWithName
std::string mixWithName(const std::string &name_) const
Create one string mixed with a name of a testing object.
Definition: parameters.cpp:80
OTest2::Parameters
Generic parameters of a run of an testing object.
Definition: parameters.h:30
OTest2::ReporterJUnit::enterTest
virtual void enterTest(const Context &context_, const std::string &name_, const Parameters &params_) override
Enter entire test.
Definition: reporterjunit.cpp:296
OTest2
Definition: assertbean.h:25
OTest2::AssertBufferListener::assertionAdditionalMessage
virtual void assertionAdditionalMessage(const Context &context_, const AssertBufferAssertData &data_, const std::string &message_)=0
Additional assertion message.
OTest2::AssertBufferListener::errorClose
virtual void errorClose(const Context &context_)=0
Closing of the internal error.
OTest2::ReporterJUnit::enterCase
virtual void enterCase(const Context &context_, const std::string &name_, const Parameters &params_) override
Enter a case.
Definition: reporterjunit.cpp:335
parameters.h
OTest2::AssertBufferListener::errorAdditionalMessage
virtual void errorAdditionalMessage(const Context &context_, const std::string &message_)=0
Additional error message.
OTest2::ReporterJUnit::ReporterJUnit
ReporterJUnit(const std::string &file_, bool hide_location_)
Ctor.
Definition: reporterjunit.cpp:285
OTest2::ReporterJUnit::leaveSuite
virtual void leaveSuite(const Context &context_, const std::string &name_, const Parameters &params_, bool result_) override
Leave a suite.
Definition: reporterjunit.cpp:411
OTest2::TimeSource::now
virtual time_point now()=0
Get current time.
OTest2::AssertBufferListener::assertionOpeningMessage
virtual void assertionOpeningMessage(const Context &context_, const AssertBufferAssertData &data_, const std::string &message_)=0
First composed message of an assertion.
context.h
OTest2::Context
OTest2 runtime context.
Definition: context.h:38
OTest2::ReporterJUnit::leaveCase
virtual void leaveCase(const Context &context_, const std::string &name_, const Parameters &params_, bool result_) override
Leave a case.
Definition: reporterjunit.cpp:396
timesource.h
reporterjunit.h
OTest2::AssertBufferPtr
std::shared_ptr< AssertBuffer > AssertBufferPtr
Definition: assertbufferptr.h:27
OTest2::ReporterJUnit::enterAssert
virtual AssertBufferPtr enterAssert(const Context &context_, bool condition_, const std::string &file_, int lineno_) override
Enter an assertion.
Definition: reporterjunit.cpp:358
OTest2::ReporterJUnit::enterState
virtual void enterState(const Context &context_, const std::string &name_) override
Enter a state.
Definition: reporterjunit.cpp:352
OTest2::odelete
void odelete(T_ *&object_)
Delete a pointer and set it invalid.
Definition: utils.h:34
assertbufferstr.h
OTest2::AssertBufferListener::errorOpeningMessage
virtual void errorOpeningMessage(const Context &context_, const std::string &message_)=0
First composed message of an internal error.
OTest2::TimeSource::time_point
std::chrono::system_clock::time_point time_point
Definition: timesource.h:32
OTest2::ReporterJUnit::leaveState
virtual void leaveState(const Context &context_, const std::string &name_, bool result_) override
Leave a state.
Definition: reporterjunit.cpp:389