1 1.1 jmmv // Copyright 2012 Google Inc. 2 1.1 jmmv // All rights reserved. 3 1.1 jmmv // 4 1.1 jmmv // Redistribution and use in source and binary forms, with or without 5 1.1 jmmv // modification, are permitted provided that the following conditions are 6 1.1 jmmv // met: 7 1.1 jmmv // 8 1.1 jmmv // * Redistributions of source code must retain the above copyright 9 1.1 jmmv // notice, this list of conditions and the following disclaimer. 10 1.1 jmmv // * Redistributions in binary form must reproduce the above copyright 11 1.1 jmmv // notice, this list of conditions and the following disclaimer in the 12 1.1 jmmv // documentation and/or other materials provided with the distribution. 13 1.1 jmmv // * Neither the name of Google Inc. nor the names of its contributors 14 1.1 jmmv // may be used to endorse or promote products derived from this software 15 1.1 jmmv // without specific prior written permission. 16 1.1 jmmv // 17 1.1 jmmv // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 1.1 jmmv // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 1.1 jmmv // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 1.1 jmmv // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 1.1 jmmv // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 1.1 jmmv // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 1.1 jmmv // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 1.1 jmmv // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 1.1 jmmv // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 1.1 jmmv // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 1.1 jmmv // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 1.1 jmmv 29 1.1 jmmv #include "engine/testers.hpp" 30 1.1 jmmv 31 1.1 jmmv extern "C" { 32 1.1 jmmv #include <dirent.h> 33 1.1 jmmv #include <regex.h> 34 1.1 jmmv } 35 1.1 jmmv 36 1.1 jmmv #include <cerrno> 37 1.1 jmmv #include <cstring> 38 1.1 jmmv #include <iostream> 39 1.1 jmmv #include <map> 40 1.1 jmmv #include <string> 41 1.1 jmmv 42 1.1 jmmv #include "engine/exceptions.hpp" 43 1.1 jmmv #include "utils/env.hpp" 44 1.1 jmmv #include "utils/format/macros.hpp" 45 1.1 jmmv #include "utils/fs/operations.hpp" 46 1.1 jmmv #include "utils/fs/path.hpp" 47 1.1 jmmv #include "utils/logging/macros.hpp" 48 1.1 jmmv #include "utils/optional.ipp" 49 1.1 jmmv #include "utils/passwd.hpp" 50 1.1 jmmv #include "utils/process/child.ipp" 51 1.1 jmmv #include "utils/process/status.hpp" 52 1.1 jmmv #include "utils/stream.hpp" 53 1.1 jmmv 54 1.1 jmmv namespace datetime = utils::datetime; 55 1.1 jmmv namespace fs = utils::fs; 56 1.1 jmmv namespace logging = utils::logging; 57 1.1 jmmv namespace passwd = utils::passwd; 58 1.1 jmmv namespace process = utils::process; 59 1.1 jmmv 60 1.1 jmmv using utils::none; 61 1.1 jmmv using utils::optional; 62 1.1 jmmv 63 1.1 jmmv 64 1.1 jmmv namespace { 65 1.1 jmmv 66 1.1 jmmv 67 1.1 jmmv /// Mapping of interface names to tester binaries. 68 1.1 jmmv typedef std::map< std::string, std::string > testers_map; 69 1.1 jmmv 70 1.1 jmmv 71 1.1 jmmv /// Collection of known-good interface to tester mappings. 72 1.1 jmmv static testers_map interfaces_to_testers; 73 1.1 jmmv 74 1.1 jmmv 75 1.1 jmmv /// Drops the trailing newline in a string and replaces others with a literal. 76 1.1 jmmv /// 77 1.1 jmmv /// \param input The string in which to perform the replacements. 78 1.1 jmmv /// 79 1.1 jmmv /// \return The modified string. 80 1.1 jmmv static std::string 81 1.1 jmmv replace_newlines(const std::string input) 82 1.1 jmmv { 83 1.1 jmmv std::string output = input; 84 1.1 jmmv 85 1.1 jmmv while (output.length() > 0 && output[output.length() - 1] == '\n') { 86 1.1 jmmv output.erase(output.end() - 1); 87 1.1 jmmv } 88 1.1 jmmv 89 1.1 jmmv std::string::size_type newline = output.find('\n', 0); 90 1.1 jmmv while (newline != std::string::npos) { 91 1.1 jmmv output.replace(newline, 1, "<<NEWLINE>>"); 92 1.1 jmmv newline = output.find('\n', newline + 1); 93 1.1 jmmv } 94 1.1 jmmv 95 1.1 jmmv return output; 96 1.1 jmmv } 97 1.1 jmmv 98 1.1 jmmv 99 1.1 jmmv /// RAII pattern to invoke a release method on destruction. 100 1.1 jmmv /// 101 1.1 jmmv /// \todo The existence of this class here is a hack. We should either 102 1.1 jmmv /// generalize the class and use it wherever we need release on destruction 103 1.1 jmmv /// semantics, or we should have proper abstractions for the objects below that 104 1.1 jmmv /// use this class. 105 1.1 jmmv /// 106 1.1 jmmv /// \tparam Object The type of the object to be released. Not a pointer. 107 1.1 jmmv /// \tparam ReturnType The return type of the release method. 108 1.1 jmmv template< typename Object, typename ReturnType > 109 1.1 jmmv class object_releaser { 110 1.1 jmmv /// Pointer to the object being managed. 111 1.1 jmmv Object* _object; 112 1.1 jmmv 113 1.1 jmmv /// Release hook. 114 1.1 jmmv ReturnType (*_free_hook)(Object*); 115 1.1 jmmv 116 1.1 jmmv public: 117 1.1 jmmv /// Constructor. 118 1.1 jmmv /// 119 1.1 jmmv /// \param object Pointer to the object being managed. 120 1.1 jmmv /// \param free_hook Release hook. 121 1.1 jmmv object_releaser(Object* object, ReturnType (*free_hook)(Object*)) : 122 1.1 jmmv _object(object), _free_hook(free_hook) 123 1.1 jmmv { 124 1.1 jmmv } 125 1.1 jmmv 126 1.1 jmmv /// Destructor. 127 1.1 jmmv ~object_releaser(void) 128 1.1 jmmv { 129 1.1 jmmv _free_hook(_object); 130 1.1 jmmv } 131 1.1 jmmv }; 132 1.1 jmmv 133 1.1 jmmv 134 1.1 jmmv /// Finds all available testers and caches their data. 135 1.1 jmmv /// 136 1.1 jmmv /// \param [out] testers Map into which to store the list of available testers. 137 1.1 jmmv static void 138 1.1 jmmv load_testers(testers_map& testers) 139 1.1 jmmv { 140 1.1 jmmv PRE(testers.empty()); 141 1.1 jmmv 142 1.1 jmmv const fs::path raw_testersdir(utils::getenv_with_default( 143 1.1 jmmv "KYUA_TESTERSDIR", KYUA_TESTERSDIR)); 144 1.1 jmmv const fs::path testersdir = raw_testersdir.is_absolute() ? 145 1.1 jmmv raw_testersdir : raw_testersdir.to_absolute(); 146 1.1 jmmv 147 1.1 jmmv ::DIR* dir = ::opendir(testersdir.c_str()); 148 1.1 jmmv if (dir == NULL) { 149 1.1 jmmv const int original_errno = errno; 150 1.1 jmmv LW(F("Failed to open testers dir %s: %s") % testersdir % 151 1.1 jmmv strerror(original_errno)); 152 1.1 jmmv return; // No testers available in the given location. 153 1.1 jmmv } 154 1.1 jmmv const object_releaser< ::DIR, int > dir_releaser(dir, ::closedir); 155 1.1 jmmv 156 1.1 jmmv ::regex_t preg; 157 1.1 jmmv if (::regcomp(&preg, "^kyua-(.+)-tester$", REG_EXTENDED) != 0) 158 1.1 jmmv throw engine::error("Failed to compile regular expression"); 159 1.1 jmmv const object_releaser< ::regex_t, void > preg_releaser(&preg, ::regfree); 160 1.1 jmmv 161 1.1 jmmv ::dirent* de; 162 1.1 jmmv while ((de = readdir(dir)) != NULL) { 163 1.1 jmmv ::regmatch_t matches[2]; 164 1.1 jmmv const int ret = ::regexec(&preg, de->d_name, 2, matches, 0); 165 1.1 jmmv if (ret == 0) { 166 1.1 jmmv const std::string interface(de->d_name + matches[1].rm_so, 167 1.1 jmmv matches[1].rm_eo - matches[1].rm_so); 168 1.1 jmmv const fs::path path = testersdir / de->d_name; 169 1.1 jmmv LI(F("Found tester for interface %s in %s") % interface % path); 170 1.1 jmmv INV(path.is_absolute()); 171 1.1 jmmv testers[interface] = path.str(); 172 1.1 jmmv } else if (ret == REG_NOMATCH) { 173 1.1 jmmv // Not a tester; skip. 174 1.1 jmmv } else { 175 1.1 jmmv throw engine::error("Failed to match regular expression"); 176 1.1 jmmv } 177 1.1 jmmv } 178 1.1 jmmv } 179 1.1 jmmv 180 1.1 jmmv 181 1.1 jmmv } // anonymous namespace 182 1.1 jmmv 183 1.1 jmmv 184 1.1 jmmv /// Returns the path to a tester binary. 185 1.1 jmmv /// 186 1.1 jmmv /// \param interface Name of the interface of the tester being looked for. 187 1.1 jmmv /// 188 1.1 jmmv /// \return Absolute path to the tester. 189 1.1 jmmv fs::path 190 1.1 jmmv engine::tester_path(const std::string& interface) 191 1.1 jmmv { 192 1.1 jmmv if (interfaces_to_testers.empty()) 193 1.1 jmmv load_testers(interfaces_to_testers); 194 1.1 jmmv 195 1.1 jmmv const testers_map::const_iterator iter = interfaces_to_testers.find( 196 1.1 jmmv interface); 197 1.1 jmmv if (iter == interfaces_to_testers.end()) 198 1.1 jmmv throw engine::error("Unknown interface " + interface); 199 1.1 jmmv 200 1.1 jmmv const fs::path path((*iter).second); 201 1.1 jmmv INV(path.is_absolute()); 202 1.1 jmmv return path; 203 1.1 jmmv } 204 1.1 jmmv 205 1.1 jmmv 206 1.1 jmmv /// Constructs a tester. 207 1.1 jmmv /// 208 1.1 jmmv /// \param interface Name of the interface to use. 209 1.1 jmmv /// \param unprivileged_user If not none, the user to switch to when running 210 1.1 jmmv /// the tester. 211 1.1 jmmv /// \param timeout If not none, the timeout to pass to the tester. 212 1.1 jmmv engine::tester::tester(const std::string& interface, 213 1.1 jmmv const optional< passwd::user >& unprivileged_user, 214 1.1 jmmv const optional< datetime::delta >& timeout) : 215 1.1 jmmv _interface(interface) 216 1.1 jmmv { 217 1.1 jmmv if (unprivileged_user) { 218 1.1 jmmv _common_args.push_back(F("-u%s") % unprivileged_user.get().uid); 219 1.1 jmmv _common_args.push_back(F("-g%s") % unprivileged_user.get().gid); 220 1.1 jmmv } 221 1.1 jmmv if (timeout) { 222 1.1 jmmv PRE(timeout.get().useconds == 0); 223 1.1 jmmv _common_args.push_back(F("-t%s") % timeout.get().seconds); 224 1.1 jmmv } 225 1.1 jmmv } 226 1.1 jmmv 227 1.1 jmmv 228 1.1 jmmv /// Destructor. 229 1.1 jmmv engine::tester::~tester(void) 230 1.1 jmmv { 231 1.1 jmmv } 232 1.1 jmmv 233 1.1 jmmv 234 1.1 jmmv /// Executes a list operation on a test program. 235 1.1 jmmv /// 236 1.1 jmmv /// \param program Path to the test program. 237 1.1 jmmv /// 238 1.1 jmmv /// \return The output of the tester, which represents a valid list of test 239 1.1 jmmv /// cases. 240 1.1 jmmv /// 241 1.1 jmmv /// \throw error If the tester returns with an unsuccessful exit code. 242 1.1 jmmv std::string 243 1.1 jmmv engine::tester::list(const fs::path& program) const 244 1.1 jmmv { 245 1.1 jmmv std::vector< std::string > args = _common_args; 246 1.1 jmmv args.push_back("list"); 247 1.1 jmmv args.push_back(program.str()); 248 1.1 jmmv 249 1.1 jmmv const fs::path tester_path = engine::tester_path(_interface); 250 1.2 lukem std::unique_ptr< process::child > child = process::child::spawn_capture( 251 1.1 jmmv tester_path, args); 252 1.1 jmmv 253 1.1 jmmv const std::string output = utils::read_stream(child->output()); 254 1.1 jmmv 255 1.1 jmmv const process::status status = child->wait(); 256 1.1 jmmv if (!status.exited() || status.exitstatus() != EXIT_SUCCESS) 257 1.1 jmmv throw engine::error("Tester did not exit cleanly: " + 258 1.1 jmmv replace_newlines(output)); 259 1.1 jmmv return output; 260 1.1 jmmv } 261 1.1 jmmv 262 1.1 jmmv 263 1.1 jmmv /// Executes a test operation on a test case. 264 1.1 jmmv /// 265 1.1 jmmv /// \param program Path to the test program. 266 1.1 jmmv /// \param test_case_name Name of the test case to execute. 267 1.1 jmmv /// \param result_file Path to the file in which to leave the result of the 268 1.1 jmmv /// tester invocation. 269 1.1 jmmv /// \param stdout_file Path to the file in which to store the stdout. 270 1.1 jmmv /// \param stderr_file Path to the file in which to store the stderr. 271 1.1 jmmv /// \param vars Collection of configuration variables. 272 1.1 jmmv /// 273 1.1 jmmv /// \throw error If the tester returns with an unsuccessful exit code. 274 1.1 jmmv void 275 1.1 jmmv engine::tester::test(const fs::path& program, const std::string& test_case_name, 276 1.1 jmmv const fs::path& result_file, const fs::path& stdout_file, 277 1.1 jmmv const fs::path& stderr_file, 278 1.1 jmmv const std::map< std::string, std::string >& vars) const 279 1.1 jmmv { 280 1.1 jmmv std::vector< std::string > args = _common_args; 281 1.1 jmmv args.push_back("test"); 282 1.1 jmmv for (std::map< std::string, std::string >::const_iterator i = vars.begin(); 283 1.1 jmmv i != vars.end(); ++i) { 284 1.1 jmmv args.push_back(F("-v%s=%s") % (*i).first % (*i).second); 285 1.1 jmmv } 286 1.1 jmmv args.push_back(program.str()); 287 1.1 jmmv args.push_back(test_case_name); 288 1.1 jmmv args.push_back(result_file.str()); 289 1.1 jmmv 290 1.1 jmmv const fs::path tester_path = engine::tester_path(_interface); 291 1.2 lukem std::unique_ptr< process::child > child = process::child::spawn_files( 292 1.1 jmmv tester_path, args, stdout_file, stderr_file); 293 1.1 jmmv const process::status status = child->wait(); 294 1.1 jmmv 295 1.1 jmmv if (status.exited()) { 296 1.1 jmmv if (status.exitstatus() == EXIT_SUCCESS) { 297 1.1 jmmv // OK; the tester exited cleanly. 298 1.1 jmmv } else if (status.exitstatus() == EXIT_FAILURE) { 299 1.1 jmmv // OK; the tester reported that the test itself failed and we have 300 1.1 jmmv // the result file to indicate this. 301 1.1 jmmv } else { 302 1.1 jmmv throw engine::error(F("Tester failed with code %s; this is a bug") % 303 1.1 jmmv status.exitstatus()); 304 1.1 jmmv } 305 1.1 jmmv } else { 306 1.1 jmmv INV(status.signaled()); 307 1.2 lukem throw engine::error(F("Tester received signal %s; this is a bug") % 308 1.2 lukem status.termsig()); 309 1.1 jmmv } 310 1.1 jmmv } 311