xref: /freebsd-src/contrib/kyua/engine/tap_parser.cpp (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
1*b0d29bc4SBrooks Davis // Copyright 2015 The Kyua Authors.
2*b0d29bc4SBrooks Davis // All rights reserved.
3*b0d29bc4SBrooks Davis //
4*b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without
5*b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are
6*b0d29bc4SBrooks Davis // met:
7*b0d29bc4SBrooks Davis //
8*b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright
9*b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer.
10*b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
11*b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
12*b0d29bc4SBrooks Davis //   documentation and/or other materials provided with the distribution.
13*b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
14*b0d29bc4SBrooks Davis //   may be used to endorse or promote products derived from this software
15*b0d29bc4SBrooks Davis //   without specific prior written permission.
16*b0d29bc4SBrooks Davis //
17*b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18*b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19*b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20*b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21*b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22*b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23*b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24*b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25*b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26*b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27*b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*b0d29bc4SBrooks Davis 
29*b0d29bc4SBrooks Davis #include "engine/tap_parser.hpp"
30*b0d29bc4SBrooks Davis 
31*b0d29bc4SBrooks Davis #include <fstream>
32*b0d29bc4SBrooks Davis 
33*b0d29bc4SBrooks Davis #include "engine/exceptions.hpp"
34*b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
35*b0d29bc4SBrooks Davis #include "utils/noncopyable.hpp"
36*b0d29bc4SBrooks Davis #include "utils/optional.ipp"
37*b0d29bc4SBrooks Davis #include "utils/sanity.hpp"
38*b0d29bc4SBrooks Davis #include "utils/text/exceptions.hpp"
39*b0d29bc4SBrooks Davis #include "utils/text/operations.ipp"
40*b0d29bc4SBrooks Davis #include "utils/text/regex.hpp"
41*b0d29bc4SBrooks Davis 
42*b0d29bc4SBrooks Davis namespace fs = utils::fs;
43*b0d29bc4SBrooks Davis namespace text = utils::text;
44*b0d29bc4SBrooks Davis 
45*b0d29bc4SBrooks Davis using utils::optional;
46*b0d29bc4SBrooks Davis 
47*b0d29bc4SBrooks Davis 
48*b0d29bc4SBrooks Davis /// TAP plan representing all tests being skipped.
49*b0d29bc4SBrooks Davis const engine::tap_plan engine::all_skipped_plan(1, 0);
50*b0d29bc4SBrooks Davis 
51*b0d29bc4SBrooks Davis 
52*b0d29bc4SBrooks Davis namespace {
53*b0d29bc4SBrooks Davis 
54*b0d29bc4SBrooks Davis 
55*b0d29bc4SBrooks Davis /// Implementation of the TAP parser.
56*b0d29bc4SBrooks Davis ///
57*b0d29bc4SBrooks Davis /// This is a class only to simplify keeping global constant values around (like
58*b0d29bc4SBrooks Davis /// prebuilt regular expressions).
59*b0d29bc4SBrooks Davis class tap_parser : utils::noncopyable {
60*b0d29bc4SBrooks Davis     /// Regular expression to match plan lines.
61*b0d29bc4SBrooks Davis     text::regex _plan_regex;
62*b0d29bc4SBrooks Davis 
63*b0d29bc4SBrooks Davis     /// Regular expression to match a TODO and extract the reason.
64*b0d29bc4SBrooks Davis     text::regex _todo_regex;
65*b0d29bc4SBrooks Davis 
66*b0d29bc4SBrooks Davis     /// Regular expression to match a SKIP and extract the reason.
67*b0d29bc4SBrooks Davis     text::regex _skip_regex;
68*b0d29bc4SBrooks Davis 
69*b0d29bc4SBrooks Davis     /// Regular expression to match a single test result.
70*b0d29bc4SBrooks Davis     text::regex _result_regex;
71*b0d29bc4SBrooks Davis 
72*b0d29bc4SBrooks Davis     /// Checks if a line contains a TAP plan and extracts its data.
73*b0d29bc4SBrooks Davis     ///
74*b0d29bc4SBrooks Davis     /// \param line The line to try to parse.
75*b0d29bc4SBrooks Davis     /// \param [in,out] out_plan Used to store the found plan, if any.  The same
76*b0d29bc4SBrooks Davis     ///     output variable should be given to all calls to this function so
77*b0d29bc4SBrooks Davis     ///     that duplicate plan entries can be discovered.
78*b0d29bc4SBrooks Davis     /// \param [out] out_all_skipped_reason Used to store the reason for all
79*b0d29bc4SBrooks Davis     ///     tests being skipped, if any.  If this is set to a non-empty value,
80*b0d29bc4SBrooks Davis     ///     then the out_plan is set to 1..0.
81*b0d29bc4SBrooks Davis     ///
82*b0d29bc4SBrooks Davis     /// \return True if the line matched a plan; false otherwise.
83*b0d29bc4SBrooks Davis     ///
84*b0d29bc4SBrooks Davis     /// \throw engine::format_error If the input is invalid.
85*b0d29bc4SBrooks Davis     /// \throw text::error If the input is invalid.
86*b0d29bc4SBrooks Davis     bool
try_parse_plan(const std::string & line,optional<engine::tap_plan> & out_plan,std::string & out_all_skipped_reason)87*b0d29bc4SBrooks Davis     try_parse_plan(const std::string& line,
88*b0d29bc4SBrooks Davis                    optional< engine::tap_plan >& out_plan,
89*b0d29bc4SBrooks Davis                    std::string& out_all_skipped_reason)
90*b0d29bc4SBrooks Davis     {
91*b0d29bc4SBrooks Davis         const text::regex_matches plan_matches = _plan_regex.match(line);
92*b0d29bc4SBrooks Davis         if (!plan_matches)
93*b0d29bc4SBrooks Davis             return false;
94*b0d29bc4SBrooks Davis         const engine::tap_plan plan(
95*b0d29bc4SBrooks Davis             text::to_type< std::size_t >(plan_matches.get(1)),
96*b0d29bc4SBrooks Davis             text::to_type< std::size_t >(plan_matches.get(2)));
97*b0d29bc4SBrooks Davis 
98*b0d29bc4SBrooks Davis         if (out_plan)
99*b0d29bc4SBrooks Davis             throw engine::format_error(
100*b0d29bc4SBrooks Davis                 F("Found duplicate plan %s..%s (saw %s..%s earlier)") %
101*b0d29bc4SBrooks Davis                 plan.first % plan.second %
102*b0d29bc4SBrooks Davis                 out_plan.get().first % out_plan.get().second);
103*b0d29bc4SBrooks Davis 
104*b0d29bc4SBrooks Davis         std::string all_skipped_reason;
105*b0d29bc4SBrooks Davis         const text::regex_matches skip_matches = _skip_regex.match(line);
106*b0d29bc4SBrooks Davis         if (skip_matches) {
107*b0d29bc4SBrooks Davis             if (plan != engine::all_skipped_plan) {
108*b0d29bc4SBrooks Davis                 throw engine::format_error(F("Skipped plan must be %s..%s") %
109*b0d29bc4SBrooks Davis                                            engine::all_skipped_plan.first %
110*b0d29bc4SBrooks Davis                                            engine::all_skipped_plan.second);
111*b0d29bc4SBrooks Davis             }
112*b0d29bc4SBrooks Davis             all_skipped_reason = skip_matches.get(2);
113*b0d29bc4SBrooks Davis             if (all_skipped_reason.empty())
114*b0d29bc4SBrooks Davis                 all_skipped_reason = "No reason specified";
115*b0d29bc4SBrooks Davis         } else {
116*b0d29bc4SBrooks Davis             if (plan.first > plan.second)
117*b0d29bc4SBrooks Davis                 throw engine::format_error(F("Found reversed plan %s..%s") %
118*b0d29bc4SBrooks Davis                                            plan.first % plan.second);
119*b0d29bc4SBrooks Davis         }
120*b0d29bc4SBrooks Davis 
121*b0d29bc4SBrooks Davis         INV(!out_plan);
122*b0d29bc4SBrooks Davis         out_plan = plan;
123*b0d29bc4SBrooks Davis         out_all_skipped_reason = all_skipped_reason;
124*b0d29bc4SBrooks Davis 
125*b0d29bc4SBrooks Davis         POST(out_plan);
126*b0d29bc4SBrooks Davis         POST(out_all_skipped_reason.empty() ||
127*b0d29bc4SBrooks Davis              out_plan.get() == engine::all_skipped_plan);
128*b0d29bc4SBrooks Davis 
129*b0d29bc4SBrooks Davis         return true;
130*b0d29bc4SBrooks Davis     }
131*b0d29bc4SBrooks Davis 
132*b0d29bc4SBrooks Davis     /// Checks if a line contains a TAP test result and extracts its data.
133*b0d29bc4SBrooks Davis     ///
134*b0d29bc4SBrooks Davis     /// \param line The line to try to parse.
135*b0d29bc4SBrooks Davis     /// \param [in,out] out_ok_count Accumulator for 'ok' results.
136*b0d29bc4SBrooks Davis     /// \param [in,out] out_not_ok_count Accumulator for 'not ok' results.
137*b0d29bc4SBrooks Davis     /// \param [out] out_bailed_out Set to true if the test bailed out.
138*b0d29bc4SBrooks Davis     ///
139*b0d29bc4SBrooks Davis     /// \return True if the line matched a result; false otherwise.
140*b0d29bc4SBrooks Davis     ///
141*b0d29bc4SBrooks Davis     /// \throw engine::format_error If the input is invalid.
142*b0d29bc4SBrooks Davis     /// \throw text::error If the input is invalid.
143*b0d29bc4SBrooks Davis     bool
try_parse_result(const std::string & line,std::size_t & out_ok_count,std::size_t & out_not_ok_count,bool & out_bailed_out)144*b0d29bc4SBrooks Davis     try_parse_result(const std::string& line, std::size_t& out_ok_count,
145*b0d29bc4SBrooks Davis                      std::size_t& out_not_ok_count, bool& out_bailed_out)
146*b0d29bc4SBrooks Davis     {
147*b0d29bc4SBrooks Davis         PRE(!out_bailed_out);
148*b0d29bc4SBrooks Davis 
149*b0d29bc4SBrooks Davis         const text::regex_matches result_matches = _result_regex.match(line);
150*b0d29bc4SBrooks Davis         if (result_matches) {
151*b0d29bc4SBrooks Davis             if (result_matches.get(1) == "ok") {
152*b0d29bc4SBrooks Davis                 ++out_ok_count;
153*b0d29bc4SBrooks Davis             } else {
154*b0d29bc4SBrooks Davis                 INV(result_matches.get(1) == "not ok");
155*b0d29bc4SBrooks Davis                 if (_todo_regex.match(line) || _skip_regex.match(line)) {
156*b0d29bc4SBrooks Davis                     ++out_ok_count;
157*b0d29bc4SBrooks Davis                 } else {
158*b0d29bc4SBrooks Davis                     ++out_not_ok_count;
159*b0d29bc4SBrooks Davis                 }
160*b0d29bc4SBrooks Davis             }
161*b0d29bc4SBrooks Davis             return true;
162*b0d29bc4SBrooks Davis         } else {
163*b0d29bc4SBrooks Davis             if (line.find("Bail out!") == 0) {
164*b0d29bc4SBrooks Davis                 out_bailed_out = true;
165*b0d29bc4SBrooks Davis                 return true;
166*b0d29bc4SBrooks Davis             } else {
167*b0d29bc4SBrooks Davis                 return false;
168*b0d29bc4SBrooks Davis             }
169*b0d29bc4SBrooks Davis         }
170*b0d29bc4SBrooks Davis     }
171*b0d29bc4SBrooks Davis 
172*b0d29bc4SBrooks Davis public:
173*b0d29bc4SBrooks Davis     /// Sets up the TAP parser state.
tap_parser(void)174*b0d29bc4SBrooks Davis     tap_parser(void) :
175*b0d29bc4SBrooks Davis         _plan_regex(text::regex::compile("^([0-9]+)\\.\\.([0-9]+)", 2)),
176*b0d29bc4SBrooks Davis         _todo_regex(text::regex::compile("TODO[ \t]*(.*)$", 2, true)),
177*b0d29bc4SBrooks Davis         _skip_regex(text::regex::compile("(SKIP|Skipped:?)[ \t]*(.*)$", 2,
178*b0d29bc4SBrooks Davis                                          true)),
179*b0d29bc4SBrooks Davis         _result_regex(text::regex::compile("^(not ok|ok)[ \t-]+[0-9]*", 1))
180*b0d29bc4SBrooks Davis     {
181*b0d29bc4SBrooks Davis     }
182*b0d29bc4SBrooks Davis 
183*b0d29bc4SBrooks Davis     /// Parses an input file containing TAP output.
184*b0d29bc4SBrooks Davis     ///
185*b0d29bc4SBrooks Davis     /// \param input The stream to read from.
186*b0d29bc4SBrooks Davis     ///
187*b0d29bc4SBrooks Davis     /// \return The results of the parsing in the form of a tap_summary object.
188*b0d29bc4SBrooks Davis     ///
189*b0d29bc4SBrooks Davis     /// \throw engine::format_error If there are any syntax errors in the input.
190*b0d29bc4SBrooks Davis     /// \throw text::error If there are any syntax errors in the input.
191*b0d29bc4SBrooks Davis     engine::tap_summary
parse(std::ifstream & input)192*b0d29bc4SBrooks Davis     parse(std::ifstream& input)
193*b0d29bc4SBrooks Davis     {
194*b0d29bc4SBrooks Davis         optional< engine::tap_plan > plan;
195*b0d29bc4SBrooks Davis         std::string all_skipped_reason;
196*b0d29bc4SBrooks Davis         bool bailed_out = false;
197*b0d29bc4SBrooks Davis         std::size_t ok_count = 0, not_ok_count = 0;
198*b0d29bc4SBrooks Davis 
199*b0d29bc4SBrooks Davis         std::string line;
200*b0d29bc4SBrooks Davis         while (!bailed_out && std::getline(input, line)) {
201*b0d29bc4SBrooks Davis             if (try_parse_result(line, ok_count, not_ok_count, bailed_out))
202*b0d29bc4SBrooks Davis                 continue;
203*b0d29bc4SBrooks Davis             (void)try_parse_plan(line, plan, all_skipped_reason);
204*b0d29bc4SBrooks Davis         }
205*b0d29bc4SBrooks Davis 
206*b0d29bc4SBrooks Davis         if (bailed_out) {
207*b0d29bc4SBrooks Davis             return engine::tap_summary::new_bailed_out();
208*b0d29bc4SBrooks Davis         } else {
209*b0d29bc4SBrooks Davis             if (!plan)
210*b0d29bc4SBrooks Davis                 throw engine::format_error(
211*b0d29bc4SBrooks Davis                     "Output did not contain any TAP plan and the program did "
212*b0d29bc4SBrooks Davis                     "not bail out");
213*b0d29bc4SBrooks Davis 
214*b0d29bc4SBrooks Davis             if (plan.get() == engine::all_skipped_plan) {
215*b0d29bc4SBrooks Davis                 return engine::tap_summary::new_all_skipped(all_skipped_reason);
216*b0d29bc4SBrooks Davis             } else {
217*b0d29bc4SBrooks Davis                 const std::size_t exp_count = plan.get().second -
218*b0d29bc4SBrooks Davis                     plan.get().first + 1;
219*b0d29bc4SBrooks Davis                 const std::size_t actual_count = ok_count + not_ok_count;
220*b0d29bc4SBrooks Davis                 if (exp_count != actual_count) {
221*b0d29bc4SBrooks Davis                     throw engine::format_error(
222*b0d29bc4SBrooks Davis                         "Reported plan differs from actual executed tests");
223*b0d29bc4SBrooks Davis                 }
224*b0d29bc4SBrooks Davis                 return engine::tap_summary::new_results(plan.get(), ok_count,
225*b0d29bc4SBrooks Davis                                                         not_ok_count);
226*b0d29bc4SBrooks Davis             }
227*b0d29bc4SBrooks Davis         }
228*b0d29bc4SBrooks Davis     }
229*b0d29bc4SBrooks Davis };
230*b0d29bc4SBrooks Davis 
231*b0d29bc4SBrooks Davis 
232*b0d29bc4SBrooks Davis }  // anonymous namespace
233*b0d29bc4SBrooks Davis 
234*b0d29bc4SBrooks Davis 
235*b0d29bc4SBrooks Davis /// Constructs a TAP summary with the results of parsing a TAP output.
236*b0d29bc4SBrooks Davis ///
237*b0d29bc4SBrooks Davis /// \param bailed_out_ Whether the test program bailed out early or not.
238*b0d29bc4SBrooks Davis /// \param plan_ The TAP plan.
239*b0d29bc4SBrooks Davis /// \param all_skipped_reason_ The reason for skipping all tests, if any.
240*b0d29bc4SBrooks Davis /// \param ok_count_ Number of 'ok' test results.
241*b0d29bc4SBrooks Davis /// \param not_ok_count_ Number of 'not ok' test results.
tap_summary(const bool bailed_out_,const tap_plan & plan_,const std::string & all_skipped_reason_,const std::size_t ok_count_,const std::size_t not_ok_count_)242*b0d29bc4SBrooks Davis engine::tap_summary::tap_summary(const bool bailed_out_,
243*b0d29bc4SBrooks Davis                                  const tap_plan& plan_,
244*b0d29bc4SBrooks Davis                                  const std::string& all_skipped_reason_,
245*b0d29bc4SBrooks Davis                                  const std::size_t ok_count_,
246*b0d29bc4SBrooks Davis                                  const std::size_t not_ok_count_) :
247*b0d29bc4SBrooks Davis     _bailed_out(bailed_out_), _plan(plan_),
248*b0d29bc4SBrooks Davis     _all_skipped_reason(all_skipped_reason_),
249*b0d29bc4SBrooks Davis     _ok_count(ok_count_), _not_ok_count(not_ok_count_)
250*b0d29bc4SBrooks Davis {
251*b0d29bc4SBrooks Davis }
252*b0d29bc4SBrooks Davis 
253*b0d29bc4SBrooks Davis 
254*b0d29bc4SBrooks Davis /// Constructs a TAP summary for a bailed out test program.
255*b0d29bc4SBrooks Davis ///
256*b0d29bc4SBrooks Davis /// \return The new tap_summary object.
257*b0d29bc4SBrooks Davis engine::tap_summary
new_bailed_out(void)258*b0d29bc4SBrooks Davis engine::tap_summary::new_bailed_out(void)
259*b0d29bc4SBrooks Davis {
260*b0d29bc4SBrooks Davis     return tap_summary(true, tap_plan(0, 0), "", 0, 0);
261*b0d29bc4SBrooks Davis }
262*b0d29bc4SBrooks Davis 
263*b0d29bc4SBrooks Davis 
264*b0d29bc4SBrooks Davis /// Constructs a TAP summary for a test program that skipped all tests.
265*b0d29bc4SBrooks Davis ///
266*b0d29bc4SBrooks Davis /// \param reason Textual reason describing why the tests were skipped.
267*b0d29bc4SBrooks Davis ///
268*b0d29bc4SBrooks Davis /// \return The new tap_summary object.
269*b0d29bc4SBrooks Davis engine::tap_summary
new_all_skipped(const std::string & reason)270*b0d29bc4SBrooks Davis engine::tap_summary::new_all_skipped(const std::string& reason)
271*b0d29bc4SBrooks Davis {
272*b0d29bc4SBrooks Davis     return tap_summary(false, all_skipped_plan, reason, 0, 0);
273*b0d29bc4SBrooks Davis }
274*b0d29bc4SBrooks Davis 
275*b0d29bc4SBrooks Davis 
276*b0d29bc4SBrooks Davis /// Constructs a TAP summary for a test program that reported results.
277*b0d29bc4SBrooks Davis ///
278*b0d29bc4SBrooks Davis /// \param plan_ The TAP plan.
279*b0d29bc4SBrooks Davis /// \param ok_count_ Total number of 'ok' results.
280*b0d29bc4SBrooks Davis /// \param not_ok_count_ Total number of 'not ok' results.
281*b0d29bc4SBrooks Davis ///
282*b0d29bc4SBrooks Davis /// \return The new tap_summary object.
283*b0d29bc4SBrooks Davis engine::tap_summary
new_results(const tap_plan & plan_,const std::size_t ok_count_,const std::size_t not_ok_count_)284*b0d29bc4SBrooks Davis engine::tap_summary::new_results(const tap_plan& plan_,
285*b0d29bc4SBrooks Davis                                  const std::size_t ok_count_,
286*b0d29bc4SBrooks Davis                                  const std::size_t not_ok_count_)
287*b0d29bc4SBrooks Davis {
288*b0d29bc4SBrooks Davis     PRE((plan_.second - plan_.first + 1) == (ok_count_ + not_ok_count_));
289*b0d29bc4SBrooks Davis     return tap_summary(false, plan_, "", ok_count_, not_ok_count_);
290*b0d29bc4SBrooks Davis }
291*b0d29bc4SBrooks Davis 
292*b0d29bc4SBrooks Davis 
293*b0d29bc4SBrooks Davis /// Checks whether the test program bailed out early or not.
294*b0d29bc4SBrooks Davis ///
295*b0d29bc4SBrooks Davis /// \return True if the test program aborted execution before completing.
296*b0d29bc4SBrooks Davis bool
bailed_out(void) const297*b0d29bc4SBrooks Davis engine::tap_summary::bailed_out(void) const
298*b0d29bc4SBrooks Davis {
299*b0d29bc4SBrooks Davis     return _bailed_out;
300*b0d29bc4SBrooks Davis }
301*b0d29bc4SBrooks Davis 
302*b0d29bc4SBrooks Davis 
303*b0d29bc4SBrooks Davis /// Gets the TAP plan of the test program.
304*b0d29bc4SBrooks Davis ///
305*b0d29bc4SBrooks Davis /// \pre bailed_out() must be false.
306*b0d29bc4SBrooks Davis ///
307*b0d29bc4SBrooks Davis /// \return The TAP plan.  If 1..0, then all_skipped_reason() will have some
308*b0d29bc4SBrooks Davis /// contents.
309*b0d29bc4SBrooks Davis const engine::tap_plan&
plan(void) const310*b0d29bc4SBrooks Davis engine::tap_summary::plan(void) const
311*b0d29bc4SBrooks Davis {
312*b0d29bc4SBrooks Davis     PRE(!_bailed_out);
313*b0d29bc4SBrooks Davis     return _plan;
314*b0d29bc4SBrooks Davis }
315*b0d29bc4SBrooks Davis 
316*b0d29bc4SBrooks Davis 
317*b0d29bc4SBrooks Davis /// Gets the reason for skipping all the tests, if any.
318*b0d29bc4SBrooks Davis ///
319*b0d29bc4SBrooks Davis /// \pre bailed_out() must be false.
320*b0d29bc4SBrooks Davis /// \pre plan() returns 1..0.
321*b0d29bc4SBrooks Davis ///
322*b0d29bc4SBrooks Davis /// \return The reason for skipping all the tests.
323*b0d29bc4SBrooks Davis const std::string&
all_skipped_reason(void) const324*b0d29bc4SBrooks Davis engine::tap_summary::all_skipped_reason(void) const
325*b0d29bc4SBrooks Davis {
326*b0d29bc4SBrooks Davis     PRE(!_bailed_out);
327*b0d29bc4SBrooks Davis     PRE(_plan == all_skipped_plan);
328*b0d29bc4SBrooks Davis     return _all_skipped_reason;
329*b0d29bc4SBrooks Davis }
330*b0d29bc4SBrooks Davis 
331*b0d29bc4SBrooks Davis 
332*b0d29bc4SBrooks Davis /// Gets the number of 'ok' test results.
333*b0d29bc4SBrooks Davis ///
334*b0d29bc4SBrooks Davis /// \pre bailed_out() must be false.
335*b0d29bc4SBrooks Davis ///
336*b0d29bc4SBrooks Davis /// \return The number of test results that reported 'ok'.
337*b0d29bc4SBrooks Davis std::size_t
ok_count(void) const338*b0d29bc4SBrooks Davis engine::tap_summary::ok_count(void) const
339*b0d29bc4SBrooks Davis {
340*b0d29bc4SBrooks Davis     PRE(!bailed_out());
341*b0d29bc4SBrooks Davis     PRE(_all_skipped_reason.empty());
342*b0d29bc4SBrooks Davis     return _ok_count;
343*b0d29bc4SBrooks Davis }
344*b0d29bc4SBrooks Davis 
345*b0d29bc4SBrooks Davis 
346*b0d29bc4SBrooks Davis /// Gets the number of 'not ok' test results.
347*b0d29bc4SBrooks Davis ///
348*b0d29bc4SBrooks Davis /// \pre bailed_out() must be false.
349*b0d29bc4SBrooks Davis ///
350*b0d29bc4SBrooks Davis /// \return The number of test results that reported 'not ok'.
351*b0d29bc4SBrooks Davis std::size_t
not_ok_count(void) const352*b0d29bc4SBrooks Davis engine::tap_summary::not_ok_count(void) const
353*b0d29bc4SBrooks Davis {
354*b0d29bc4SBrooks Davis     PRE(!_bailed_out);
355*b0d29bc4SBrooks Davis     PRE(_all_skipped_reason.empty());
356*b0d29bc4SBrooks Davis     return _not_ok_count;
357*b0d29bc4SBrooks Davis }
358*b0d29bc4SBrooks Davis 
359*b0d29bc4SBrooks Davis 
360*b0d29bc4SBrooks Davis /// Checks two tap_summary objects for equality.
361*b0d29bc4SBrooks Davis ///
362*b0d29bc4SBrooks Davis /// \param other The object to compare this one to.
363*b0d29bc4SBrooks Davis ///
364*b0d29bc4SBrooks Davis /// \return True if the two objects are equal; false otherwise.
365*b0d29bc4SBrooks Davis bool
operator ==(const tap_summary & other) const366*b0d29bc4SBrooks Davis engine::tap_summary::operator==(const tap_summary& other) const
367*b0d29bc4SBrooks Davis {
368*b0d29bc4SBrooks Davis     return (_bailed_out == other._bailed_out &&
369*b0d29bc4SBrooks Davis             _plan == other._plan &&
370*b0d29bc4SBrooks Davis             _all_skipped_reason == other._all_skipped_reason &&
371*b0d29bc4SBrooks Davis             _ok_count == other._ok_count &&
372*b0d29bc4SBrooks Davis             _not_ok_count == other._not_ok_count);
373*b0d29bc4SBrooks Davis }
374*b0d29bc4SBrooks Davis 
375*b0d29bc4SBrooks Davis 
376*b0d29bc4SBrooks Davis /// Checks two tap_summary objects for inequality.
377*b0d29bc4SBrooks Davis ///
378*b0d29bc4SBrooks Davis /// \param other The object to compare this one to.
379*b0d29bc4SBrooks Davis ///
380*b0d29bc4SBrooks Davis /// \return True if the two objects are different; false otherwise.
381*b0d29bc4SBrooks Davis bool
operator !=(const tap_summary & other) const382*b0d29bc4SBrooks Davis engine::tap_summary::operator!=(const tap_summary& other) const
383*b0d29bc4SBrooks Davis {
384*b0d29bc4SBrooks Davis     return !(*this == other);
385*b0d29bc4SBrooks Davis }
386*b0d29bc4SBrooks Davis 
387*b0d29bc4SBrooks Davis 
388*b0d29bc4SBrooks Davis /// Formats a tap_summary into a stream.
389*b0d29bc4SBrooks Davis ///
390*b0d29bc4SBrooks Davis /// \param output The stream into which to inject the object.
391*b0d29bc4SBrooks Davis /// \param summary The summary to format.
392*b0d29bc4SBrooks Davis ///
393*b0d29bc4SBrooks Davis /// \return The output stream.
394*b0d29bc4SBrooks Davis std::ostream&
operator <<(std::ostream & output,const tap_summary & summary)395*b0d29bc4SBrooks Davis engine::operator<<(std::ostream& output, const tap_summary& summary)
396*b0d29bc4SBrooks Davis {
397*b0d29bc4SBrooks Davis     output << "tap_summary{";
398*b0d29bc4SBrooks Davis     if (summary.bailed_out()) {
399*b0d29bc4SBrooks Davis         output << "bailed_out=true";
400*b0d29bc4SBrooks Davis     } else {
401*b0d29bc4SBrooks Davis         const tap_plan& plan = summary.plan();
402*b0d29bc4SBrooks Davis         output << "bailed_out=false"
403*b0d29bc4SBrooks Davis                << ", plan=" << plan.first << ".." << plan.second;
404*b0d29bc4SBrooks Davis         if (plan == all_skipped_plan) {
405*b0d29bc4SBrooks Davis             output << ", all_skipped_reason=" << summary.all_skipped_reason();
406*b0d29bc4SBrooks Davis         } else {
407*b0d29bc4SBrooks Davis             output << ", ok_count=" << summary.ok_count()
408*b0d29bc4SBrooks Davis                    << ", not_ok_count=" << summary.not_ok_count();
409*b0d29bc4SBrooks Davis         }
410*b0d29bc4SBrooks Davis     }
411*b0d29bc4SBrooks Davis     output << "}";
412*b0d29bc4SBrooks Davis     return output;
413*b0d29bc4SBrooks Davis }
414*b0d29bc4SBrooks Davis 
415*b0d29bc4SBrooks Davis 
416*b0d29bc4SBrooks Davis /// Parses an input file containing the TAP output of a test program.
417*b0d29bc4SBrooks Davis ///
418*b0d29bc4SBrooks Davis /// \param filename Path to the file to parse.
419*b0d29bc4SBrooks Davis ///
420*b0d29bc4SBrooks Davis /// \return The parsed data in the form of a tap_summary.
421*b0d29bc4SBrooks Davis ///
422*b0d29bc4SBrooks Davis /// \throw load_error If there are any problems parsing the file.  Such problems
423*b0d29bc4SBrooks Davis ///     should be considered as test program breakage.
424*b0d29bc4SBrooks Davis engine::tap_summary
parse_tap_output(const utils::fs::path & filename)425*b0d29bc4SBrooks Davis engine::parse_tap_output(const utils::fs::path& filename)
426*b0d29bc4SBrooks Davis {
427*b0d29bc4SBrooks Davis     std::ifstream input(filename.str().c_str());
428*b0d29bc4SBrooks Davis     if (!input)
429*b0d29bc4SBrooks Davis         throw engine::load_error(filename, "Failed to open TAP output file");
430*b0d29bc4SBrooks Davis 
431*b0d29bc4SBrooks Davis     try {
432*b0d29bc4SBrooks Davis         return tap_summary(tap_parser().parse(input));
433*b0d29bc4SBrooks Davis     } catch (const engine::format_error& e) {
434*b0d29bc4SBrooks Davis         throw engine::load_error(filename, e.what());
435*b0d29bc4SBrooks Davis     } catch (const text::error& e) {
436*b0d29bc4SBrooks Davis         throw engine::load_error(filename, e.what());
437*b0d29bc4SBrooks Davis     }
438*b0d29bc4SBrooks Davis }
439