xref: /freebsd/contrib/kyua/engine/googletest_list.cpp (revision eb8f69902f7de8012a31a85db2f88a54584f81af)
1 // Copyright 2024 The Kyua Authors.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "engine/googletest_list.hpp"
30 
31 #include <fstream>
32 #include <regex>
33 #include <string>
34 #include <utility>
35 
36 #include "engine/exceptions.hpp"
37 #include "model/metadata.hpp"
38 #include "model/test_case.hpp"
39 #include "utils/config/exceptions.hpp"
40 #include "utils/format/macros.hpp"
41 
42 namespace config = utils::config;
43 namespace fs = utils::fs;
44 
45 
46 namespace {
47 
48 /// A regular expression that should match either a test suite or a test case.
49 const std::string name_expr = "([[:alpha:][:digit:]_]+[[:alpha:][:digit:]_/]*)";
50 
51 /// The separator between a test suite and a test case.
52 const std::string testsuite_testcase_separator = ".";
53 
54 }  // anonymous namespace
55 
56 
57 /// Parses the googletest list of test cases from an open stream.
58 ///
59 /// \param input The stream to read from.
60 ///
61 /// \return The collection of parsed test cases.
62 ///
63 /// \throw format_error If there is any problem in the input data.
64 model::test_cases_map
65 engine::parse_googletest_list(std::istream& input)
66 {
67     std::string line, test_suite;
68 
69     // TODO: These were moved here from the anonymous namespace because of a
70     // Doxygen bug that's reproducible with the version used by Travis CI.
71     //
72     /// A complete regular expression representing a line with a test case
73     /// definition, e,g., "  TestCase",  "  TestCase/0", or
74     /// "  TestCase/0  # GetParam() = 4".
75     const std::regex testcase_re(
76         "  " + name_expr + "([[:space:]]+# GetParam\\(\\) = .+)?");
77 
78     /// A complete regular expression representing a line with a test suite
79     /// definition, e,g., * "TestSuite.", "TestSuite/Prefix.", or
80     /// "TestSuite/Prefix.    # TypeParam = .+".
81     const std::regex testsuite_re(
82         name_expr + "\\.([[:space:]]+# TypeParam = .+)?");
83     // END TODO
84 
85     model::test_cases_map_builder test_cases_builder;
86     while (std::getline(input, line).good()) {
87         std::smatch match;
88         if (std::regex_match(line, match, testcase_re)) {
89             if (test_suite.empty()) {
90                 throw format_error("Invalid testcase definition: not preceded "
91                                    "by a test suite definition");
92             }
93             std::string test_case(match[1]);
94             test_cases_builder.add(test_suite + test_case);
95         } else if (std::regex_match(line, match, testsuite_re)) {
96             test_suite = std::string(match[1]) +
97                          testsuite_testcase_separator;
98         } else {
99             // Ignore the line; something might have used output a diagnostic
100             // message to stdout, e.g., gtest_main.
101         }
102     }
103     const model::test_cases_map test_cases = test_cases_builder.build();
104     if (test_cases.empty()) {
105         // The scheduler interface also checks for the presence of at least one
106         // test case.  However, because the atf format itself requires one test
107         // case to be always present, we check for this condition here as well.
108         throw format_error("No test cases");
109     }
110     return test_cases;
111 }
112