Home | History | Annotate | Line # | Download | only in engine
      1 // Copyright 2011 Google Inc.
      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/filters.hpp"
     30 
     31 #include <algorithm>
     32 #include <stdexcept>
     33 
     34 #include "engine/test_case.hpp"
     35 #include "utils/format/macros.hpp"
     36 #include "utils/fs/exceptions.hpp"
     37 #include "utils/logging/macros.hpp"
     38 #include "utils/optional.ipp"
     39 #include "utils/sanity.hpp"
     40 
     41 namespace fs = utils::fs;
     42 
     43 using utils::none;
     44 using utils::optional;
     45 
     46 
     47 /// Constructs a filter.
     48 ///
     49 /// \param test_program_ The name of the test program or of the subdirectory to
     50 ///     match.
     51 /// \param test_case_ The name of the test case to match.
     52 engine::test_filter::test_filter(const fs::path& test_program_,
     53                                  const std::string& test_case_) :
     54     test_program(test_program_),
     55     test_case(test_case_)
     56 {
     57 }
     58 
     59 
     60 /// Parses a user-provided test filter.
     61 ///
     62 /// \param str The user-provided string representing a filter for tests.  Must
     63 ///     be of the form &lt;test_program%gt;[:&lt;test_case%gt;].
     64 ///
     65 /// \return The parsed filter.
     66 ///
     67 /// \throw std::runtime_error If the provided filter is invalid.
     68 engine::test_filter
     69 engine::test_filter::parse(const std::string& str)
     70 {
     71     if (str.empty())
     72         throw std::runtime_error("Test filter cannot be empty");
     73 
     74     const std::string::size_type pos = str.find(':');
     75     if (pos == 0)
     76         throw std::runtime_error(F("Program name component in '%s' is empty")
     77                                  % str);
     78     if (pos == str.length() - 1)
     79         throw std::runtime_error(F("Test case component in '%s' is empty")
     80                                  % str);
     81 
     82     try {
     83         const fs::path test_program_(str.substr(0, pos));
     84         if (test_program_.is_absolute())
     85             throw std::runtime_error(F("Program name '%s' must be relative "
     86                                        "to the test suite, not absolute") %
     87                                        test_program_.str());
     88         if (pos == std::string::npos) {
     89             LD(F("Parsed user filter '%s': test program '%s', no test case") %
     90                str % test_program_.str());
     91             return test_filter(test_program_, "");
     92         } else {
     93             const std::string test_case_(str.substr(pos + 1));
     94             LD(F("Parsed user filter '%s': test program '%s', test case '%s'") %
     95                str % test_program_.str() % test_case_);
     96             return test_filter(test_program_, test_case_);
     97         }
     98     } catch (const fs::error& e) {
     99         throw std::runtime_error(F("Invalid path in filter '%s': %s") % str %
    100                                  e.what());
    101     }
    102 }
    103 
    104 
    105 /// Formats a filter for user presentation.
    106 ///
    107 /// \return A user-friendly string representing the filter.  Note that this does
    108 /// not necessarily match the string the user provided: in particular, the path
    109 /// may have been internally normalized.
    110 std::string
    111 engine::test_filter::str(void) const
    112 {
    113     if (!test_case.empty())
    114         return F("%s:%s") % test_program % test_case;
    115     else
    116         return test_program.str();
    117 }
    118 
    119 
    120 /// Checks if this filter contains another.
    121 ///
    122 /// \param other The filter to compare to.
    123 ///
    124 /// \return True if this filter contains the other filter or if they are equal.
    125 bool
    126 engine::test_filter::contains(const test_filter& other) const
    127 {
    128     if (*this == other)
    129         return true;
    130     else
    131         return test_case.empty() && test_program.is_parent_of(
    132             other.test_program);
    133 }
    134 
    135 
    136 /// Checks if this filter matches a given test program name or subdirectory.
    137 ///
    138 /// \param test_program_ The test program to compare to.
    139 ///
    140 /// \return Whether the filter matches the test program.  This is a superset of
    141 /// matches_test_case.
    142 bool
    143 engine::test_filter::matches_test_program(const fs::path& test_program_) const
    144 {
    145     if (test_program == test_program_)
    146         return true;
    147     else {
    148         // Check if the filter matches a subdirectory of the test program.
    149         // The test case must be empty because we don't want foo:bar to match
    150         // foo/baz.
    151         return (test_case.empty() && test_program.is_parent_of(test_program_));
    152     }
    153 }
    154 
    155 
    156 /// Checks if this filter matches a given test case identifier.
    157 ///
    158 /// \param test_program_ The test program to compare to.
    159 /// \param test_case_ The test case to compare to.
    160 ///
    161 /// \return Whether the filter matches the test case.
    162 bool
    163 engine::test_filter::matches_test_case(const fs::path& test_program_,
    164                                        const std::string& test_case_) const
    165 {
    166     if (matches_test_program(test_program_)) {
    167         return test_case.empty() || test_case == test_case_;
    168     } else
    169         return false;
    170 }
    171 
    172 
    173 /// Less-than comparison for sorting purposes.
    174 ///
    175 /// \param other The filter to compare to.
    176 ///
    177 /// \return True if this filter sorts before the other filter.
    178 bool
    179 engine::test_filter::operator<(const test_filter& other) const
    180 {
    181     return (
    182         test_program < other.test_program ||
    183         (test_program == other.test_program && test_case < other.test_case));
    184 }
    185 
    186 
    187 /// Equality comparison.
    188 ///
    189 /// \param other The filter to compare to.
    190 ///
    191 /// \return True if this filter is equal to the other filter.
    192 bool
    193 engine::test_filter::operator==(const test_filter& other) const
    194 {
    195     return test_program == other.test_program && test_case == other.test_case;
    196 }
    197 
    198 
    199 /// Non-equality comparison.
    200 ///
    201 /// \param other The filter to compare to.
    202 ///
    203 /// \return True if this filter is different than the other filter.
    204 bool
    205 engine::test_filter::operator!=(const test_filter& other) const
    206 {
    207     return !(*this == other);
    208 }
    209 
    210 
    211 /// Constructs a new set of filters.
    212 ///
    213 /// \param filters_ The filters themselves; if empty, no filters are applied.
    214 engine::test_filters::test_filters(const std::set< test_filter >& filters_) :
    215     _filters(filters_)
    216 {
    217 }
    218 
    219 
    220 /// Checks if a given test program matches the set of filters.
    221 ///
    222 /// This is provided as an optimization only, and the results of this function
    223 /// are less specific than those of match_test_case.  Checking for the matching
    224 /// of a test program should be done before loading the list of test cases from
    225 /// a program, so as to avoid the delay in executing the test program, but
    226 /// match_test_case must still be called afterwards.
    227 ///
    228 /// \param name The test program to check against the filters.
    229 ///
    230 /// \return True if the provided identifier matches any filter.
    231 bool
    232 engine::test_filters::match_test_program(const fs::path& name) const
    233 {
    234     if (_filters.empty())
    235         return true;
    236 
    237     bool matches = false;
    238     for (std::set< test_filter >::const_iterator iter = _filters.begin();
    239          !matches && iter != _filters.end(); iter++) {
    240         matches = (*iter).matches_test_program(name);
    241     }
    242     return matches;
    243 }
    244 
    245 
    246 /// Checks if a given test case identifier matches the set of filters.
    247 ///
    248 /// \param test_program The test program to check against the filters.
    249 /// \param test_case The test case to check against the filters.
    250 ///
    251 /// \return A boolean indicating if the test case is matched by any filter and,
    252 /// if true, a string containing the filter name.  The string is empty when
    253 /// there are no filters defined.
    254 engine::test_filters::match
    255 engine::test_filters::match_test_case(const fs::path& test_program,
    256                                       const std::string& test_case) const
    257 {
    258     if (_filters.empty()) {
    259         INV(match_test_program(test_program));
    260         return match(true, none);
    261     }
    262 
    263     optional< test_filter > found = none;
    264     for (std::set< test_filter >::const_iterator iter = _filters.begin();
    265          !found && iter != _filters.end(); iter++) {
    266         if ((*iter).matches_test_case(test_program, test_case))
    267             found = *iter;
    268     }
    269     INV(!found || match_test_program(test_program));
    270     return match(static_cast< bool >(found), found);
    271 }
    272 
    273 
    274 /// Calculates the filters that have not matched any tests.
    275 ///
    276 /// \param matched The filters that did match some tests.  This must be a subset
    277 ///     of the filters held by this object.
    278 ///
    279 /// \return The set of filters that have not been used.
    280 std::set< engine::test_filter >
    281 engine::test_filters::difference(const std::set< test_filter >& matched) const
    282 {
    283     PRE(std::includes(_filters.begin(), _filters.end(),
    284                       matched.begin(), matched.end()));
    285 
    286     std::set< test_filter > filters;
    287     std::set_difference(_filters.begin(), _filters.end(),
    288                         matched.begin(), matched.end(),
    289                         std::inserter(filters, filters.begin()));
    290     return filters;
    291 }
    292 
    293 
    294 /// Checks if a collection of filters is disjoint.
    295 ///
    296 /// \param filters The filters to check.
    297 ///
    298 /// \throw std::runtime_error If the filters are not disjoint.
    299 void
    300 engine::check_disjoint_filters(const std::set< engine::test_filter >& filters)
    301 {
    302     // Yes, this is an O(n^2) algorithm.  However, we can assume that the number
    303     // of test filters (which are provided by the user on the command line) on a
    304     // particular run is in the order of tens, and thus this should not cause
    305     // any serious performance trouble.
    306     for (std::set< test_filter >::const_iterator i1 = filters.begin();
    307          i1 != filters.end(); i1++) {
    308         for (std::set< test_filter >::const_iterator i2 = filters.begin();
    309              i2 != filters.end(); i2++) {
    310             const test_filter& filter1 = *i1;
    311             const test_filter& filter2 = *i2;
    312 
    313             if (i1 != i2 && filter1.contains(filter2)) {
    314                 throw std::runtime_error(
    315                     F("Filters '%s' and '%s' are not disjoint") %
    316                     filter1.str() % filter2.str());
    317             }
    318         }
    319     }
    320 }
    321 
    322 
    323 /// Constructs a filters_state instance.
    324 ///
    325 /// \param filters_ The set of filters to track.
    326 engine::filters_state::filters_state(
    327     const std::set< engine::test_filter >& filters_) :
    328     _filters(test_filters(filters_))
    329 {
    330 }
    331 
    332 
    333 /// Checks whether these filters match the given test program.
    334 ///
    335 /// \param test_program The test program to match against.
    336 ///
    337 /// \return True if these filters match the given test program name.
    338 bool
    339 engine::filters_state::match_test_program(const fs::path& test_program) const
    340 {
    341     return _filters.match_test_program(test_program);
    342 }
    343 
    344 
    345 /// Checks whether these filters match the given test case.
    346 ///
    347 /// \param test_program The test program to match against.
    348 /// \param test_case The test case to match against.
    349 ///
    350 /// \return True if these filters match the given test case identifier.
    351 bool
    352 engine::filters_state::match_test_case(const fs::path& test_program,
    353                                        const std::string& test_case)
    354 {
    355     engine::test_filters::match match = _filters.match_test_case(
    356         test_program, test_case);
    357     if (match.first && match.second)
    358         _used_filters.insert(match.second.get());
    359     return match.first;
    360 }
    361 
    362 
    363 /// Calculates the unused filters in this set.
    364 ///
    365 /// \return Returns the set of filters that have not matched any tests.  This
    366 /// information is useful to report usage errors to the user.
    367 std::set< engine::test_filter >
    368 engine::filters_state::unused(void) const
    369 {
    370     return _filters.difference(_used_filters);
    371 }
    372