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