108334c51SBrooks Davis // Copyright 2015 The Kyua Authors.
208334c51SBrooks Davis // All rights reserved.
308334c51SBrooks Davis //
408334c51SBrooks Davis // Redistribution and use in source and binary forms, with or without
508334c51SBrooks Davis // modification, are permitted provided that the following conditions are
608334c51SBrooks Davis // met:
708334c51SBrooks Davis //
808334c51SBrooks Davis // * Redistributions of source code must retain the above copyright
908334c51SBrooks Davis // notice, this list of conditions and the following disclaimer.
1008334c51SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
1108334c51SBrooks Davis // notice, this list of conditions and the following disclaimer in the
1208334c51SBrooks Davis // documentation and/or other materials provided with the distribution.
1308334c51SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
1408334c51SBrooks Davis // may be used to endorse or promote products derived from this software
1508334c51SBrooks Davis // without specific prior written permission.
1608334c51SBrooks Davis //
1708334c51SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1808334c51SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1908334c51SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2008334c51SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2108334c51SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2208334c51SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2308334c51SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2408334c51SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2508334c51SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2608334c51SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2708334c51SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2808334c51SBrooks Davis
2908334c51SBrooks Davis #include "engine/tap_parser.hpp"
3008334c51SBrooks Davis
3108334c51SBrooks Davis #include <fstream>
3208334c51SBrooks Davis
3308334c51SBrooks Davis #include "engine/exceptions.hpp"
3408334c51SBrooks Davis #include "utils/format/macros.hpp"
3508334c51SBrooks Davis #include "utils/noncopyable.hpp"
3608334c51SBrooks Davis #include "utils/optional.ipp"
3708334c51SBrooks Davis #include "utils/sanity.hpp"
3808334c51SBrooks Davis #include "utils/text/exceptions.hpp"
3908334c51SBrooks Davis #include "utils/text/operations.ipp"
4008334c51SBrooks Davis #include "utils/text/regex.hpp"
4108334c51SBrooks Davis
4208334c51SBrooks Davis namespace fs = utils::fs;
4308334c51SBrooks Davis namespace text = utils::text;
4408334c51SBrooks Davis
4508334c51SBrooks Davis using utils::optional;
4608334c51SBrooks Davis
4708334c51SBrooks Davis
4808334c51SBrooks Davis /// TAP plan representing all tests being skipped.
4908334c51SBrooks Davis const engine::tap_plan engine::all_skipped_plan(1, 0);
5008334c51SBrooks Davis
5108334c51SBrooks Davis
5208334c51SBrooks Davis namespace {
5308334c51SBrooks Davis
5408334c51SBrooks Davis
5508334c51SBrooks Davis /// Implementation of the TAP parser.
5608334c51SBrooks Davis ///
5708334c51SBrooks Davis /// This is a class only to simplify keeping global constant values around (like
5808334c51SBrooks Davis /// prebuilt regular expressions).
5908334c51SBrooks Davis class tap_parser : utils::noncopyable {
6008334c51SBrooks Davis /// Regular expression to match plan lines.
6108334c51SBrooks Davis text::regex _plan_regex;
6208334c51SBrooks Davis
6308334c51SBrooks Davis /// Regular expression to match a TODO and extract the reason.
6408334c51SBrooks Davis text::regex _todo_regex;
6508334c51SBrooks Davis
6608334c51SBrooks Davis /// Regular expression to match a SKIP and extract the reason.
6708334c51SBrooks Davis text::regex _skip_regex;
6808334c51SBrooks Davis
6908334c51SBrooks Davis /// Regular expression to match a single test result.
7008334c51SBrooks Davis text::regex _result_regex;
7108334c51SBrooks Davis
7208334c51SBrooks Davis /// Checks if a line contains a TAP plan and extracts its data.
7308334c51SBrooks Davis ///
7408334c51SBrooks Davis /// \param line The line to try to parse.
7508334c51SBrooks Davis /// \param [in,out] out_plan Used to store the found plan, if any. The same
7608334c51SBrooks Davis /// output variable should be given to all calls to this function so
7708334c51SBrooks Davis /// that duplicate plan entries can be discovered.
7808334c51SBrooks Davis /// \param [out] out_all_skipped_reason Used to store the reason for all
7908334c51SBrooks Davis /// tests being skipped, if any. If this is set to a non-empty value,
8008334c51SBrooks Davis /// then the out_plan is set to 1..0.
8108334c51SBrooks Davis ///
8208334c51SBrooks Davis /// \return True if the line matched a plan; false otherwise.
8308334c51SBrooks Davis ///
8408334c51SBrooks Davis /// \throw engine::format_error If the input is invalid.
8508334c51SBrooks Davis /// \throw text::error If the input is invalid.
8608334c51SBrooks Davis bool
try_parse_plan(const std::string & line,optional<engine::tap_plan> & out_plan,std::string & out_all_skipped_reason)8708334c51SBrooks Davis try_parse_plan(const std::string& line,
8808334c51SBrooks Davis optional< engine::tap_plan >& out_plan,
8908334c51SBrooks Davis std::string& out_all_skipped_reason)
9008334c51SBrooks Davis {
9108334c51SBrooks Davis const text::regex_matches plan_matches = _plan_regex.match(line);
9208334c51SBrooks Davis if (!plan_matches)
9308334c51SBrooks Davis return false;
9408334c51SBrooks Davis const engine::tap_plan plan(
9508334c51SBrooks Davis text::to_type< std::size_t >(plan_matches.get(1)),
9608334c51SBrooks Davis text::to_type< std::size_t >(plan_matches.get(2)));
9708334c51SBrooks Davis
9808334c51SBrooks Davis if (out_plan)
9908334c51SBrooks Davis throw engine::format_error(
10008334c51SBrooks Davis F("Found duplicate plan %s..%s (saw %s..%s earlier)") %
10108334c51SBrooks Davis plan.first % plan.second %
10208334c51SBrooks Davis out_plan.get().first % out_plan.get().second);
10308334c51SBrooks Davis
10408334c51SBrooks Davis std::string all_skipped_reason;
10508334c51SBrooks Davis const text::regex_matches skip_matches = _skip_regex.match(line);
10608334c51SBrooks Davis if (skip_matches) {
10708334c51SBrooks Davis if (plan != engine::all_skipped_plan) {
10808334c51SBrooks Davis throw engine::format_error(F("Skipped plan must be %s..%s") %
10908334c51SBrooks Davis engine::all_skipped_plan.first %
11008334c51SBrooks Davis engine::all_skipped_plan.second);
11108334c51SBrooks Davis }
11208334c51SBrooks Davis all_skipped_reason = skip_matches.get(2);
11308334c51SBrooks Davis if (all_skipped_reason.empty())
11408334c51SBrooks Davis all_skipped_reason = "No reason specified";
11508334c51SBrooks Davis } else {
11608334c51SBrooks Davis if (plan.first > plan.second)
11708334c51SBrooks Davis throw engine::format_error(F("Found reversed plan %s..%s") %
11808334c51SBrooks Davis plan.first % plan.second);
11908334c51SBrooks Davis }
12008334c51SBrooks Davis
12108334c51SBrooks Davis INV(!out_plan);
12208334c51SBrooks Davis out_plan = plan;
12308334c51SBrooks Davis out_all_skipped_reason = all_skipped_reason;
12408334c51SBrooks Davis
12508334c51SBrooks Davis POST(out_plan);
12608334c51SBrooks Davis POST(out_all_skipped_reason.empty() ||
12708334c51SBrooks Davis out_plan.get() == engine::all_skipped_plan);
12808334c51SBrooks Davis
12908334c51SBrooks Davis return true;
13008334c51SBrooks Davis }
13108334c51SBrooks Davis
13208334c51SBrooks Davis /// Checks if a line contains a TAP test result and extracts its data.
13308334c51SBrooks Davis ///
13408334c51SBrooks Davis /// \param line The line to try to parse.
13508334c51SBrooks Davis /// \param [in,out] out_ok_count Accumulator for 'ok' results.
13608334c51SBrooks Davis /// \param [in,out] out_not_ok_count Accumulator for 'not ok' results.
13708334c51SBrooks Davis /// \param [out] out_bailed_out Set to true if the test bailed out.
13808334c51SBrooks Davis ///
13908334c51SBrooks Davis /// \return True if the line matched a result; false otherwise.
14008334c51SBrooks Davis ///
14108334c51SBrooks Davis /// \throw engine::format_error If the input is invalid.
14208334c51SBrooks Davis /// \throw text::error If the input is invalid.
14308334c51SBrooks 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)14408334c51SBrooks Davis try_parse_result(const std::string& line, std::size_t& out_ok_count,
14508334c51SBrooks Davis std::size_t& out_not_ok_count, bool& out_bailed_out)
14608334c51SBrooks Davis {
14708334c51SBrooks Davis PRE(!out_bailed_out);
14808334c51SBrooks Davis
14908334c51SBrooks Davis const text::regex_matches result_matches = _result_regex.match(line);
15008334c51SBrooks Davis if (result_matches) {
15108334c51SBrooks Davis if (result_matches.get(1) == "ok") {
15208334c51SBrooks Davis ++out_ok_count;
15308334c51SBrooks Davis } else {
15408334c51SBrooks Davis INV(result_matches.get(1) == "not ok");
15508334c51SBrooks Davis if (_todo_regex.match(line) || _skip_regex.match(line)) {
15608334c51SBrooks Davis ++out_ok_count;
15708334c51SBrooks Davis } else {
15808334c51SBrooks Davis ++out_not_ok_count;
15908334c51SBrooks Davis }
16008334c51SBrooks Davis }
16108334c51SBrooks Davis return true;
16208334c51SBrooks Davis } else {
16308334c51SBrooks Davis if (line.find("Bail out!") == 0) {
16408334c51SBrooks Davis out_bailed_out = true;
16508334c51SBrooks Davis return true;
16608334c51SBrooks Davis } else {
16708334c51SBrooks Davis return false;
16808334c51SBrooks Davis }
16908334c51SBrooks Davis }
17008334c51SBrooks Davis }
17108334c51SBrooks Davis
17208334c51SBrooks Davis public:
17308334c51SBrooks Davis /// Sets up the TAP parser state.
tap_parser(void)17408334c51SBrooks Davis tap_parser(void) :
17508334c51SBrooks Davis _plan_regex(text::regex::compile("^([0-9]+)\\.\\.([0-9]+)", 2)),
17608334c51SBrooks Davis _todo_regex(text::regex::compile("TODO[ \t]*(.*)$", 2, true)),
17708334c51SBrooks Davis _skip_regex(text::regex::compile("(SKIP|Skipped:?)[ \t]*(.*)$", 2,
17808334c51SBrooks Davis true)),
17908334c51SBrooks Davis _result_regex(text::regex::compile("^(not ok|ok)[ \t-]+[0-9]*", 1))
18008334c51SBrooks Davis {
18108334c51SBrooks Davis }
18208334c51SBrooks Davis
18308334c51SBrooks Davis /// Parses an input file containing TAP output.
18408334c51SBrooks Davis ///
18508334c51SBrooks Davis /// \param input The stream to read from.
18608334c51SBrooks Davis ///
18708334c51SBrooks Davis /// \return The results of the parsing in the form of a tap_summary object.
18808334c51SBrooks Davis ///
18908334c51SBrooks Davis /// \throw engine::format_error If there are any syntax errors in the input.
19008334c51SBrooks Davis /// \throw text::error If there are any syntax errors in the input.
19108334c51SBrooks Davis engine::tap_summary
parse(std::ifstream & input)19208334c51SBrooks Davis parse(std::ifstream& input)
19308334c51SBrooks Davis {
19408334c51SBrooks Davis optional< engine::tap_plan > plan;
19508334c51SBrooks Davis std::string all_skipped_reason;
19608334c51SBrooks Davis bool bailed_out = false;
19708334c51SBrooks Davis std::size_t ok_count = 0, not_ok_count = 0;
19808334c51SBrooks Davis
19908334c51SBrooks Davis std::string line;
20008334c51SBrooks Davis while (!bailed_out && std::getline(input, line)) {
20108334c51SBrooks Davis if (try_parse_result(line, ok_count, not_ok_count, bailed_out))
20208334c51SBrooks Davis continue;
20308334c51SBrooks Davis (void)try_parse_plan(line, plan, all_skipped_reason);
20408334c51SBrooks Davis }
20508334c51SBrooks Davis
20608334c51SBrooks Davis if (bailed_out) {
20708334c51SBrooks Davis return engine::tap_summary::new_bailed_out();
20808334c51SBrooks Davis } else {
20908334c51SBrooks Davis if (!plan)
21008334c51SBrooks Davis throw engine::format_error(
21108334c51SBrooks Davis "Output did not contain any TAP plan and the program did "
21208334c51SBrooks Davis "not bail out");
21308334c51SBrooks Davis
21408334c51SBrooks Davis if (plan.get() == engine::all_skipped_plan) {
21508334c51SBrooks Davis return engine::tap_summary::new_all_skipped(all_skipped_reason);
21608334c51SBrooks Davis } else {
21708334c51SBrooks Davis const std::size_t exp_count = plan.get().second -
21808334c51SBrooks Davis plan.get().first + 1;
21908334c51SBrooks Davis const std::size_t actual_count = ok_count + not_ok_count;
22008334c51SBrooks Davis if (exp_count != actual_count) {
22108334c51SBrooks Davis throw engine::format_error(
22208334c51SBrooks Davis "Reported plan differs from actual executed tests");
22308334c51SBrooks Davis }
22408334c51SBrooks Davis return engine::tap_summary::new_results(plan.get(), ok_count,
22508334c51SBrooks Davis not_ok_count);
22608334c51SBrooks Davis }
22708334c51SBrooks Davis }
22808334c51SBrooks Davis }
22908334c51SBrooks Davis };
23008334c51SBrooks Davis
23108334c51SBrooks Davis
23208334c51SBrooks Davis } // anonymous namespace
23308334c51SBrooks Davis
23408334c51SBrooks Davis
23508334c51SBrooks Davis /// Constructs a TAP summary with the results of parsing a TAP output.
23608334c51SBrooks Davis ///
23708334c51SBrooks Davis /// \param bailed_out_ Whether the test program bailed out early or not.
23808334c51SBrooks Davis /// \param plan_ The TAP plan.
23908334c51SBrooks Davis /// \param all_skipped_reason_ The reason for skipping all tests, if any.
24008334c51SBrooks Davis /// \param ok_count_ Number of 'ok' test results.
24108334c51SBrooks 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_)24208334c51SBrooks Davis engine::tap_summary::tap_summary(const bool bailed_out_,
24308334c51SBrooks Davis const tap_plan& plan_,
24408334c51SBrooks Davis const std::string& all_skipped_reason_,
24508334c51SBrooks Davis const std::size_t ok_count_,
24608334c51SBrooks Davis const std::size_t not_ok_count_) :
24708334c51SBrooks Davis _bailed_out(bailed_out_), _plan(plan_),
24808334c51SBrooks Davis _all_skipped_reason(all_skipped_reason_),
24908334c51SBrooks Davis _ok_count(ok_count_), _not_ok_count(not_ok_count_)
25008334c51SBrooks Davis {
25108334c51SBrooks Davis }
25208334c51SBrooks Davis
25308334c51SBrooks Davis
25408334c51SBrooks Davis /// Constructs a TAP summary for a bailed out test program.
25508334c51SBrooks Davis ///
25608334c51SBrooks Davis /// \return The new tap_summary object.
25708334c51SBrooks Davis engine::tap_summary
new_bailed_out(void)25808334c51SBrooks Davis engine::tap_summary::new_bailed_out(void)
25908334c51SBrooks Davis {
26008334c51SBrooks Davis return tap_summary(true, tap_plan(0, 0), "", 0, 0);
26108334c51SBrooks Davis }
26208334c51SBrooks Davis
26308334c51SBrooks Davis
26408334c51SBrooks Davis /// Constructs a TAP summary for a test program that skipped all tests.
26508334c51SBrooks Davis ///
26608334c51SBrooks Davis /// \param reason Textual reason describing why the tests were skipped.
26708334c51SBrooks Davis ///
26808334c51SBrooks Davis /// \return The new tap_summary object.
26908334c51SBrooks Davis engine::tap_summary
new_all_skipped(const std::string & reason)27008334c51SBrooks Davis engine::tap_summary::new_all_skipped(const std::string& reason)
27108334c51SBrooks Davis {
27208334c51SBrooks Davis return tap_summary(false, all_skipped_plan, reason, 0, 0);
27308334c51SBrooks Davis }
27408334c51SBrooks Davis
27508334c51SBrooks Davis
27608334c51SBrooks Davis /// Constructs a TAP summary for a test program that reported results.
27708334c51SBrooks Davis ///
27808334c51SBrooks Davis /// \param plan_ The TAP plan.
27908334c51SBrooks Davis /// \param ok_count_ Total number of 'ok' results.
28008334c51SBrooks Davis /// \param not_ok_count_ Total number of 'not ok' results.
28108334c51SBrooks Davis ///
28208334c51SBrooks Davis /// \return The new tap_summary object.
28308334c51SBrooks Davis engine::tap_summary
new_results(const tap_plan & plan_,const std::size_t ok_count_,const std::size_t not_ok_count_)28408334c51SBrooks Davis engine::tap_summary::new_results(const tap_plan& plan_,
28508334c51SBrooks Davis const std::size_t ok_count_,
28608334c51SBrooks Davis const std::size_t not_ok_count_)
28708334c51SBrooks Davis {
28808334c51SBrooks Davis PRE((plan_.second - plan_.first + 1) == (ok_count_ + not_ok_count_));
28908334c51SBrooks Davis return tap_summary(false, plan_, "", ok_count_, not_ok_count_);
29008334c51SBrooks Davis }
29108334c51SBrooks Davis
29208334c51SBrooks Davis
29308334c51SBrooks Davis /// Checks whether the test program bailed out early or not.
29408334c51SBrooks Davis ///
29508334c51SBrooks Davis /// \return True if the test program aborted execution before completing.
29608334c51SBrooks Davis bool
bailed_out(void) const29708334c51SBrooks Davis engine::tap_summary::bailed_out(void) const
29808334c51SBrooks Davis {
29908334c51SBrooks Davis return _bailed_out;
30008334c51SBrooks Davis }
30108334c51SBrooks Davis
30208334c51SBrooks Davis
30308334c51SBrooks Davis /// Gets the TAP plan of the test program.
30408334c51SBrooks Davis ///
30508334c51SBrooks Davis /// \pre bailed_out() must be false.
30608334c51SBrooks Davis ///
30708334c51SBrooks Davis /// \return The TAP plan. If 1..0, then all_skipped_reason() will have some
30808334c51SBrooks Davis /// contents.
30908334c51SBrooks Davis const engine::tap_plan&
plan(void) const31008334c51SBrooks Davis engine::tap_summary::plan(void) const
31108334c51SBrooks Davis {
31208334c51SBrooks Davis PRE(!_bailed_out);
31308334c51SBrooks Davis return _plan;
31408334c51SBrooks Davis }
31508334c51SBrooks Davis
31608334c51SBrooks Davis
31708334c51SBrooks Davis /// Gets the reason for skipping all the tests, if any.
31808334c51SBrooks Davis ///
31908334c51SBrooks Davis /// \pre bailed_out() must be false.
32008334c51SBrooks Davis /// \pre plan() returns 1..0.
32108334c51SBrooks Davis ///
32208334c51SBrooks Davis /// \return The reason for skipping all the tests.
32308334c51SBrooks Davis const std::string&
all_skipped_reason(void) const32408334c51SBrooks Davis engine::tap_summary::all_skipped_reason(void) const
32508334c51SBrooks Davis {
32608334c51SBrooks Davis PRE(!_bailed_out);
32708334c51SBrooks Davis PRE(_plan == all_skipped_plan);
32808334c51SBrooks Davis return _all_skipped_reason;
32908334c51SBrooks Davis }
33008334c51SBrooks Davis
33108334c51SBrooks Davis
33208334c51SBrooks Davis /// Gets the number of 'ok' test results.
33308334c51SBrooks Davis ///
33408334c51SBrooks Davis /// \pre bailed_out() must be false.
33508334c51SBrooks Davis ///
33608334c51SBrooks Davis /// \return The number of test results that reported 'ok'.
33708334c51SBrooks Davis std::size_t
ok_count(void) const33808334c51SBrooks Davis engine::tap_summary::ok_count(void) const
33908334c51SBrooks Davis {
34008334c51SBrooks Davis PRE(!bailed_out());
34108334c51SBrooks Davis PRE(_all_skipped_reason.empty());
34208334c51SBrooks Davis return _ok_count;
34308334c51SBrooks Davis }
34408334c51SBrooks Davis
34508334c51SBrooks Davis
34608334c51SBrooks Davis /// Gets the number of 'not ok' test results.
34708334c51SBrooks Davis ///
34808334c51SBrooks Davis /// \pre bailed_out() must be false.
34908334c51SBrooks Davis ///
35008334c51SBrooks Davis /// \return The number of test results that reported 'not ok'.
35108334c51SBrooks Davis std::size_t
not_ok_count(void) const35208334c51SBrooks Davis engine::tap_summary::not_ok_count(void) const
35308334c51SBrooks Davis {
35408334c51SBrooks Davis PRE(!_bailed_out);
35508334c51SBrooks Davis PRE(_all_skipped_reason.empty());
35608334c51SBrooks Davis return _not_ok_count;
35708334c51SBrooks Davis }
35808334c51SBrooks Davis
35908334c51SBrooks Davis
36008334c51SBrooks Davis /// Checks two tap_summary objects for equality.
36108334c51SBrooks Davis ///
36208334c51SBrooks Davis /// \param other The object to compare this one to.
36308334c51SBrooks Davis ///
36408334c51SBrooks Davis /// \return True if the two objects are equal; false otherwise.
36508334c51SBrooks Davis bool
operator ==(const tap_summary & other) const36608334c51SBrooks Davis engine::tap_summary::operator==(const tap_summary& other) const
36708334c51SBrooks Davis {
36808334c51SBrooks Davis return (_bailed_out == other._bailed_out &&
36908334c51SBrooks Davis _plan == other._plan &&
37008334c51SBrooks Davis _all_skipped_reason == other._all_skipped_reason &&
37108334c51SBrooks Davis _ok_count == other._ok_count &&
37208334c51SBrooks Davis _not_ok_count == other._not_ok_count);
37308334c51SBrooks Davis }
37408334c51SBrooks Davis
37508334c51SBrooks Davis
37608334c51SBrooks Davis /// Checks two tap_summary objects for inequality.
37708334c51SBrooks Davis ///
37808334c51SBrooks Davis /// \param other The object to compare this one to.
37908334c51SBrooks Davis ///
38008334c51SBrooks Davis /// \return True if the two objects are different; false otherwise.
38108334c51SBrooks Davis bool
operator !=(const tap_summary & other) const38208334c51SBrooks Davis engine::tap_summary::operator!=(const tap_summary& other) const
38308334c51SBrooks Davis {
38408334c51SBrooks Davis return !(*this == other);
38508334c51SBrooks Davis }
38608334c51SBrooks Davis
38708334c51SBrooks Davis
38808334c51SBrooks Davis /// Formats a tap_summary into a stream.
38908334c51SBrooks Davis ///
39008334c51SBrooks Davis /// \param output The stream into which to inject the object.
39108334c51SBrooks Davis /// \param summary The summary to format.
39208334c51SBrooks Davis ///
39308334c51SBrooks Davis /// \return The output stream.
39408334c51SBrooks Davis std::ostream&
operator <<(std::ostream & output,const tap_summary & summary)39508334c51SBrooks Davis engine::operator<<(std::ostream& output, const tap_summary& summary)
39608334c51SBrooks Davis {
39708334c51SBrooks Davis output << "tap_summary{";
39808334c51SBrooks Davis if (summary.bailed_out()) {
39908334c51SBrooks Davis output << "bailed_out=true";
40008334c51SBrooks Davis } else {
40108334c51SBrooks Davis const tap_plan& plan = summary.plan();
40208334c51SBrooks Davis output << "bailed_out=false"
40308334c51SBrooks Davis << ", plan=" << plan.first << ".." << plan.second;
40408334c51SBrooks Davis if (plan == all_skipped_plan) {
40508334c51SBrooks Davis output << ", all_skipped_reason=" << summary.all_skipped_reason();
40608334c51SBrooks Davis } else {
40708334c51SBrooks Davis output << ", ok_count=" << summary.ok_count()
40808334c51SBrooks Davis << ", not_ok_count=" << summary.not_ok_count();
40908334c51SBrooks Davis }
41008334c51SBrooks Davis }
41108334c51SBrooks Davis output << "}";
41208334c51SBrooks Davis return output;
41308334c51SBrooks Davis }
41408334c51SBrooks Davis
41508334c51SBrooks Davis
41608334c51SBrooks Davis /// Parses an input file containing the TAP output of a test program.
41708334c51SBrooks Davis ///
41808334c51SBrooks Davis /// \param filename Path to the file to parse.
41908334c51SBrooks Davis ///
42008334c51SBrooks Davis /// \return The parsed data in the form of a tap_summary.
42108334c51SBrooks Davis ///
42208334c51SBrooks Davis /// \throw load_error If there are any problems parsing the file. Such problems
42308334c51SBrooks Davis /// should be considered as test program breakage.
42408334c51SBrooks Davis engine::tap_summary
parse_tap_output(const utils::fs::path & filename)42508334c51SBrooks Davis engine::parse_tap_output(const utils::fs::path& filename)
42608334c51SBrooks Davis {
42708334c51SBrooks Davis std::ifstream input(filename.str().c_str());
42808334c51SBrooks Davis if (!input)
42908334c51SBrooks Davis throw engine::load_error(filename, "Failed to open TAP output file");
43008334c51SBrooks Davis
43108334c51SBrooks Davis try {
43208334c51SBrooks Davis return tap_summary(tap_parser().parse(input));
43308334c51SBrooks Davis } catch (const engine::format_error& e) {
43408334c51SBrooks Davis throw engine::load_error(filename, e.what());
43508334c51SBrooks Davis } catch (const text::error& e) {
43608334c51SBrooks Davis throw engine::load_error(filename, e.what());
43708334c51SBrooks Davis }
43808334c51SBrooks Davis }
439