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 "cli/common.hpp" 30 31 #include <algorithm> 32 33 #include "engine/filters.hpp" 34 #include "engine/test_case.hpp" 35 #include "engine/test_program.hpp" 36 #include "engine/test_result.hpp" 37 #include "utils/cmdline/exceptions.hpp" 38 #include "utils/cmdline/parser.ipp" 39 #include "utils/datetime.hpp" 40 #include "utils/env.hpp" 41 #include "utils/format/macros.hpp" 42 #include "utils/logging/macros.hpp" 43 #include "utils/fs/exceptions.hpp" 44 #include "utils/fs/operations.hpp" 45 #include "utils/fs/path.hpp" 46 #include "utils/optional.ipp" 47 48 namespace cmdline = utils::cmdline; 49 namespace datetime = utils::datetime; 50 namespace fs = utils::fs; 51 52 using utils::none; 53 using utils::optional; 54 55 56 /// Standard definition of the option to specify the build root. 57 const cmdline::path_option cli::build_root_option( 58 "build-root", 59 "Path to the built test programs, if different from the location of the " 60 "Kyuafile scripts", 61 "path"); 62 63 64 /// Standard definition of the option to specify a Kyuafile. 65 const cmdline::path_option cli::kyuafile_option( 66 'k', "kyuafile", 67 "Path to the test suite definition", 68 "file", "Kyuafile"); 69 70 71 /// Standard definition of the option to specify the store. 72 const cmdline::path_option cli::store_option( 73 's', "store", 74 "Path to the store database", 75 "file", "~/.kyua/store.db"); 76 77 78 /// Gets the path to the build root, if any. 79 /// 80 /// This is just syntactic sugar to simplify quierying the 'build_root_option'. 81 /// 82 /// \param cmdline The parsed command line. 83 /// 84 /// \return The path to the build root, if specified; none otherwise. 85 optional< fs::path > 86 cli::build_root_path(const cmdline::parsed_cmdline& cmdline) 87 { 88 optional< fs::path > build_root; 89 if (cmdline.has_option(build_root_option.long_name())) 90 build_root = cmdline.get_option< cmdline::path_option >( 91 build_root_option.long_name()); 92 return build_root; 93 } 94 95 96 /// Gets the value of the HOME environment variable with path validation. 97 /// 98 /// \return The value of the HOME environment variable if it is a valid path; 99 /// none if it is not defined or if it contains an invalid path. 100 optional< fs::path > 101 cli::get_home(void) 102 { 103 const optional< std::string > home = utils::getenv("HOME"); 104 if (home) { 105 try { 106 return utils::make_optional(fs::path(home.get())); 107 } catch (const fs::error& e) { 108 LW(F("Invalid value '%s' in HOME environment variable: %s") % 109 home.get() % e.what()); 110 return none; 111 } 112 } else 113 return none; 114 } 115 116 117 /// Gets the path to the Kyuafile to be loaded. 118 /// 119 /// This is just syntactic sugar to simplify quierying the 'kyuafile_option'. 120 /// 121 /// \param cmdline The parsed command line. 122 /// 123 /// \return The path to the Kyuafile to be loaded. 124 fs::path 125 cli::kyuafile_path(const cmdline::parsed_cmdline& cmdline) 126 { 127 return cmdline.get_option< cmdline::path_option >( 128 kyuafile_option.long_name()); 129 } 130 131 132 /// Gets the path to the store to be used. 133 /// 134 /// This has the side-effect of creating the directory in which to store the 135 /// database if and only if the path to the database matches the default value. 136 /// When the user does not specify an override for the location of the database, 137 /// he should not care about the directory existing. Any of this is not a big 138 /// deal though, because logs are also stored within ~/.kyua and thus we will 139 /// most likely end up creating the directory anyway. 140 /// 141 /// \param cmdline The parsed command line. 142 /// 143 /// \return The path to the store to be used. 144 /// 145 /// \throw fs::error If the creation of the directory fails. 146 fs::path 147 cli::store_path(const cmdline::parsed_cmdline& cmdline) 148 { 149 fs::path store = cmdline.get_option< cmdline::path_option >( 150 store_option.long_name()); 151 if (store == fs::path(store_option.default_value())) { 152 const optional< fs::path > home = cli::get_home(); 153 if (home) { 154 store = home.get() / ".kyua/store.db"; 155 fs::mkdir_p(store.branch_path(), 0777); 156 } else { 157 store = fs::path("kyua-store.db"); 158 LW("HOME not defined; creating store database in current " 159 "directory"); 160 } 161 } 162 LI(F("Store database set to: %s") % store); 163 return store; 164 } 165 166 167 /// Parses a set of command-line arguments to construct test filters. 168 /// 169 /// \param args The command-line arguments representing test filters. 170 /// 171 /// \throw cmdline:error If any of the arguments is invalid, or if they 172 /// represent a non-disjoint collection of filters. 173 std::set< engine::test_filter > 174 cli::parse_filters(const cmdline::args_vector& args) 175 { 176 std::set< engine::test_filter > filters; 177 178 try { 179 for (cmdline::args_vector::const_iterator iter = args.begin(); 180 iter != args.end(); iter++) { 181 const engine::test_filter filter(engine::test_filter::parse(*iter)); 182 if (filters.find(filter) != filters.end()) 183 throw cmdline::error(F("Duplicate filter '%s'") % filter.str()); 184 filters.insert(filter); 185 } 186 check_disjoint_filters(filters); 187 } catch (const std::runtime_error& e) { 188 throw cmdline::error(e.what()); 189 } 190 191 return filters; 192 } 193 194 195 /// Reports the filters that have not matched any tests as errors. 196 /// 197 /// \param unused The collection of unused filters to report. 198 /// \param ui The user interface object through which errors are to be reported. 199 /// 200 /// \return True if there are any unused filters. The caller should report this 201 /// as an error to the user by means of a non-successful exit code. 202 bool 203 cli::report_unused_filters(const std::set< engine::test_filter >& unused, 204 cmdline::ui* ui) 205 { 206 for (std::set< engine::test_filter >::const_iterator iter = unused.begin(); 207 iter != unused.end(); iter++) { 208 cmdline::print_warning(ui, F("No test cases matched by the filter '%s'") 209 % (*iter).str()); 210 } 211 212 return !unused.empty(); 213 } 214 215 216 /// Formats a time delta for user presentation. 217 /// 218 /// \param delta The time delta to format. 219 /// 220 /// \return A user-friendly representation of the time delta. 221 std::string 222 cli::format_delta(const datetime::delta& delta) 223 { 224 return F("%.3ss") % (delta.seconds + (delta.useconds / 1000000.0)); 225 } 226 227 228 /// Formats a test case result for user presentation. 229 /// 230 /// \param result The result to format. 231 /// 232 /// \return A user-friendly representation of the result. 233 std::string 234 cli::format_result(const engine::test_result& result) 235 { 236 std::string text; 237 238 using engine::test_result; 239 switch (result.type()) { 240 case test_result::broken: text = "broken"; break; 241 case test_result::expected_failure: text = "expected_failure"; break; 242 case test_result::failed: text = "failed"; break; 243 case test_result::passed: text = "passed"; break; 244 case test_result::skipped: text = "skipped"; break; 245 } 246 INV(!text.empty()); 247 248 if (!result.reason().empty()) 249 text += ": " + result.reason(); 250 251 return text; 252 } 253 254 255 /// Formats the identifier of a test case for user presentation. 256 /// 257 /// \param test_case The test case whose identifier to format. 258 /// 259 /// \return A string representing the test case uniquely within a test suite. 260 std::string 261 cli::format_test_case_id(const engine::test_case& test_case) 262 { 263 return F("%s:%s") % test_case.container_test_program().relative_path() % 264 test_case.name(); 265 } 266 267 268 /// Formats a filter using the same syntax of a test case. 269 /// 270 /// \param test_filter The filter to format. 271 /// 272 /// \return A string representing the test filter. 273 std::string 274 cli::format_test_case_id(const engine::test_filter& test_filter) 275 { 276 return F("%s:%s") % test_filter.test_program % test_filter.test_case; 277 } 278