16b3a42afSjmmv // Copyright 2012 Google Inc.
26b3a42afSjmmv // All rights reserved.
36b3a42afSjmmv //
46b3a42afSjmmv // Redistribution and use in source and binary forms, with or without
56b3a42afSjmmv // modification, are permitted provided that the following conditions are
66b3a42afSjmmv // met:
76b3a42afSjmmv //
86b3a42afSjmmv // * Redistributions of source code must retain the above copyright
96b3a42afSjmmv // notice, this list of conditions and the following disclaimer.
106b3a42afSjmmv // * Redistributions in binary form must reproduce the above copyright
116b3a42afSjmmv // notice, this list of conditions and the following disclaimer in the
126b3a42afSjmmv // documentation and/or other materials provided with the distribution.
136b3a42afSjmmv // * Neither the name of Google Inc. nor the names of its contributors
146b3a42afSjmmv // may be used to endorse or promote products derived from this software
156b3a42afSjmmv // without specific prior written permission.
166b3a42afSjmmv //
176b3a42afSjmmv // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
186b3a42afSjmmv // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
196b3a42afSjmmv // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
206b3a42afSjmmv // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
216b3a42afSjmmv // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
226b3a42afSjmmv // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
236b3a42afSjmmv // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
246b3a42afSjmmv // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
256b3a42afSjmmv // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
266b3a42afSjmmv // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
276b3a42afSjmmv // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
286b3a42afSjmmv
296b3a42afSjmmv #include "engine/testers.hpp"
306b3a42afSjmmv
316b3a42afSjmmv extern "C" {
326b3a42afSjmmv #include <dirent.h>
336b3a42afSjmmv #include <regex.h>
346b3a42afSjmmv }
356b3a42afSjmmv
366b3a42afSjmmv #include <cerrno>
376b3a42afSjmmv #include <cstring>
386b3a42afSjmmv #include <iostream>
396b3a42afSjmmv #include <map>
406b3a42afSjmmv #include <string>
416b3a42afSjmmv
426b3a42afSjmmv #include "engine/exceptions.hpp"
436b3a42afSjmmv #include "utils/env.hpp"
446b3a42afSjmmv #include "utils/format/macros.hpp"
456b3a42afSjmmv #include "utils/fs/operations.hpp"
466b3a42afSjmmv #include "utils/fs/path.hpp"
476b3a42afSjmmv #include "utils/logging/macros.hpp"
486b3a42afSjmmv #include "utils/optional.ipp"
496b3a42afSjmmv #include "utils/passwd.hpp"
506b3a42afSjmmv #include "utils/process/child.ipp"
516b3a42afSjmmv #include "utils/process/status.hpp"
526b3a42afSjmmv #include "utils/stream.hpp"
536b3a42afSjmmv
546b3a42afSjmmv namespace datetime = utils::datetime;
556b3a42afSjmmv namespace fs = utils::fs;
566b3a42afSjmmv namespace logging = utils::logging;
576b3a42afSjmmv namespace passwd = utils::passwd;
586b3a42afSjmmv namespace process = utils::process;
596b3a42afSjmmv
606b3a42afSjmmv using utils::none;
616b3a42afSjmmv using utils::optional;
626b3a42afSjmmv
636b3a42afSjmmv
646b3a42afSjmmv namespace {
656b3a42afSjmmv
666b3a42afSjmmv
676b3a42afSjmmv /// Mapping of interface names to tester binaries.
686b3a42afSjmmv typedef std::map< std::string, std::string > testers_map;
696b3a42afSjmmv
706b3a42afSjmmv
716b3a42afSjmmv /// Collection of known-good interface to tester mappings.
726b3a42afSjmmv static testers_map interfaces_to_testers;
736b3a42afSjmmv
746b3a42afSjmmv
756b3a42afSjmmv /// Drops the trailing newline in a string and replaces others with a literal.
766b3a42afSjmmv ///
776b3a42afSjmmv /// \param input The string in which to perform the replacements.
786b3a42afSjmmv ///
796b3a42afSjmmv /// \return The modified string.
806b3a42afSjmmv static std::string
replace_newlines(const std::string input)816b3a42afSjmmv replace_newlines(const std::string input)
826b3a42afSjmmv {
836b3a42afSjmmv std::string output = input;
846b3a42afSjmmv
856b3a42afSjmmv while (output.length() > 0 && output[output.length() - 1] == '\n') {
866b3a42afSjmmv output.erase(output.end() - 1);
876b3a42afSjmmv }
886b3a42afSjmmv
896b3a42afSjmmv std::string::size_type newline = output.find('\n', 0);
906b3a42afSjmmv while (newline != std::string::npos) {
916b3a42afSjmmv output.replace(newline, 1, "<<NEWLINE>>");
926b3a42afSjmmv newline = output.find('\n', newline + 1);
936b3a42afSjmmv }
946b3a42afSjmmv
956b3a42afSjmmv return output;
966b3a42afSjmmv }
976b3a42afSjmmv
986b3a42afSjmmv
996b3a42afSjmmv /// RAII pattern to invoke a release method on destruction.
1006b3a42afSjmmv ///
1016b3a42afSjmmv /// \todo The existence of this class here is a hack. We should either
1026b3a42afSjmmv /// generalize the class and use it wherever we need release on destruction
1036b3a42afSjmmv /// semantics, or we should have proper abstractions for the objects below that
1046b3a42afSjmmv /// use this class.
1056b3a42afSjmmv ///
1066b3a42afSjmmv /// \tparam Object The type of the object to be released. Not a pointer.
1076b3a42afSjmmv /// \tparam ReturnType The return type of the release method.
1086b3a42afSjmmv template< typename Object, typename ReturnType >
1096b3a42afSjmmv class object_releaser {
1106b3a42afSjmmv /// Pointer to the object being managed.
1116b3a42afSjmmv Object* _object;
1126b3a42afSjmmv
1136b3a42afSjmmv /// Release hook.
1146b3a42afSjmmv ReturnType (*_free_hook)(Object*);
1156b3a42afSjmmv
1166b3a42afSjmmv public:
1176b3a42afSjmmv /// Constructor.
1186b3a42afSjmmv ///
1196b3a42afSjmmv /// \param object Pointer to the object being managed.
1206b3a42afSjmmv /// \param free_hook Release hook.
object_releaser(Object * object,ReturnType (* free_hook)(Object *))1216b3a42afSjmmv object_releaser(Object* object, ReturnType (*free_hook)(Object*)) :
1226b3a42afSjmmv _object(object), _free_hook(free_hook)
1236b3a42afSjmmv {
1246b3a42afSjmmv }
1256b3a42afSjmmv
1266b3a42afSjmmv /// Destructor.
~object_releaser(void)1276b3a42afSjmmv ~object_releaser(void)
1286b3a42afSjmmv {
1296b3a42afSjmmv _free_hook(_object);
1306b3a42afSjmmv }
1316b3a42afSjmmv };
1326b3a42afSjmmv
1336b3a42afSjmmv
1346b3a42afSjmmv /// Finds all available testers and caches their data.
1356b3a42afSjmmv ///
1366b3a42afSjmmv /// \param [out] testers Map into which to store the list of available testers.
1376b3a42afSjmmv static void
load_testers(testers_map & testers)1386b3a42afSjmmv load_testers(testers_map& testers)
1396b3a42afSjmmv {
1406b3a42afSjmmv PRE(testers.empty());
1416b3a42afSjmmv
1426b3a42afSjmmv const fs::path raw_testersdir(utils::getenv_with_default(
1436b3a42afSjmmv "KYUA_TESTERSDIR", KYUA_TESTERSDIR));
1446b3a42afSjmmv const fs::path testersdir = raw_testersdir.is_absolute() ?
1456b3a42afSjmmv raw_testersdir : raw_testersdir.to_absolute();
1466b3a42afSjmmv
1476b3a42afSjmmv ::DIR* dir = ::opendir(testersdir.c_str());
1486b3a42afSjmmv if (dir == NULL) {
1496b3a42afSjmmv const int original_errno = errno;
1506b3a42afSjmmv LW(F("Failed to open testers dir %s: %s") % testersdir %
1516b3a42afSjmmv strerror(original_errno));
1526b3a42afSjmmv return; // No testers available in the given location.
1536b3a42afSjmmv }
1546b3a42afSjmmv const object_releaser< ::DIR, int > dir_releaser(dir, ::closedir);
1556b3a42afSjmmv
1566b3a42afSjmmv ::regex_t preg;
1576b3a42afSjmmv if (::regcomp(&preg, "^kyua-(.+)-tester$", REG_EXTENDED) != 0)
1586b3a42afSjmmv throw engine::error("Failed to compile regular expression");
1596b3a42afSjmmv const object_releaser< ::regex_t, void > preg_releaser(&preg, ::regfree);
1606b3a42afSjmmv
1616b3a42afSjmmv ::dirent* de;
1626b3a42afSjmmv while ((de = readdir(dir)) != NULL) {
1636b3a42afSjmmv ::regmatch_t matches[2];
1646b3a42afSjmmv const int ret = ::regexec(&preg, de->d_name, 2, matches, 0);
1656b3a42afSjmmv if (ret == 0) {
1666b3a42afSjmmv const std::string interface(de->d_name + matches[1].rm_so,
1676b3a42afSjmmv matches[1].rm_eo - matches[1].rm_so);
1686b3a42afSjmmv const fs::path path = testersdir / de->d_name;
1696b3a42afSjmmv LI(F("Found tester for interface %s in %s") % interface % path);
1706b3a42afSjmmv INV(path.is_absolute());
1716b3a42afSjmmv testers[interface] = path.str();
1726b3a42afSjmmv } else if (ret == REG_NOMATCH) {
1736b3a42afSjmmv // Not a tester; skip.
1746b3a42afSjmmv } else {
1756b3a42afSjmmv throw engine::error("Failed to match regular expression");
1766b3a42afSjmmv }
1776b3a42afSjmmv }
1786b3a42afSjmmv }
1796b3a42afSjmmv
1806b3a42afSjmmv
1816b3a42afSjmmv } // anonymous namespace
1826b3a42afSjmmv
1836b3a42afSjmmv
1846b3a42afSjmmv /// Returns the path to a tester binary.
1856b3a42afSjmmv ///
1866b3a42afSjmmv /// \param interface Name of the interface of the tester being looked for.
1876b3a42afSjmmv ///
1886b3a42afSjmmv /// \return Absolute path to the tester.
1896b3a42afSjmmv fs::path
tester_path(const std::string & interface)1906b3a42afSjmmv engine::tester_path(const std::string& interface)
1916b3a42afSjmmv {
1926b3a42afSjmmv if (interfaces_to_testers.empty())
1936b3a42afSjmmv load_testers(interfaces_to_testers);
1946b3a42afSjmmv
1956b3a42afSjmmv const testers_map::const_iterator iter = interfaces_to_testers.find(
1966b3a42afSjmmv interface);
1976b3a42afSjmmv if (iter == interfaces_to_testers.end())
1986b3a42afSjmmv throw engine::error("Unknown interface " + interface);
1996b3a42afSjmmv
2006b3a42afSjmmv const fs::path path((*iter).second);
2016b3a42afSjmmv INV(path.is_absolute());
2026b3a42afSjmmv return path;
2036b3a42afSjmmv }
2046b3a42afSjmmv
2056b3a42afSjmmv
2066b3a42afSjmmv /// Constructs a tester.
2076b3a42afSjmmv ///
2086b3a42afSjmmv /// \param interface Name of the interface to use.
2096b3a42afSjmmv /// \param unprivileged_user If not none, the user to switch to when running
2106b3a42afSjmmv /// the tester.
2116b3a42afSjmmv /// \param timeout If not none, the timeout to pass to the tester.
tester(const std::string & interface,const optional<passwd::user> & unprivileged_user,const optional<datetime::delta> & timeout)2126b3a42afSjmmv engine::tester::tester(const std::string& interface,
2136b3a42afSjmmv const optional< passwd::user >& unprivileged_user,
2146b3a42afSjmmv const optional< datetime::delta >& timeout) :
2156b3a42afSjmmv _interface(interface)
2166b3a42afSjmmv {
2176b3a42afSjmmv if (unprivileged_user) {
2186b3a42afSjmmv _common_args.push_back(F("-u%s") % unprivileged_user.get().uid);
2196b3a42afSjmmv _common_args.push_back(F("-g%s") % unprivileged_user.get().gid);
2206b3a42afSjmmv }
2216b3a42afSjmmv if (timeout) {
2226b3a42afSjmmv PRE(timeout.get().useconds == 0);
2236b3a42afSjmmv _common_args.push_back(F("-t%s") % timeout.get().seconds);
2246b3a42afSjmmv }
2256b3a42afSjmmv }
2266b3a42afSjmmv
2276b3a42afSjmmv
2286b3a42afSjmmv /// Destructor.
~tester(void)2296b3a42afSjmmv engine::tester::~tester(void)
2306b3a42afSjmmv {
2316b3a42afSjmmv }
2326b3a42afSjmmv
2336b3a42afSjmmv
2346b3a42afSjmmv /// Executes a list operation on a test program.
2356b3a42afSjmmv ///
2366b3a42afSjmmv /// \param program Path to the test program.
2376b3a42afSjmmv ///
2386b3a42afSjmmv /// \return The output of the tester, which represents a valid list of test
2396b3a42afSjmmv /// cases.
2406b3a42afSjmmv ///
2416b3a42afSjmmv /// \throw error If the tester returns with an unsuccessful exit code.
2426b3a42afSjmmv std::string
list(const fs::path & program) const2436b3a42afSjmmv engine::tester::list(const fs::path& program) const
2446b3a42afSjmmv {
2456b3a42afSjmmv std::vector< std::string > args = _common_args;
2466b3a42afSjmmv args.push_back("list");
2476b3a42afSjmmv args.push_back(program.str());
2486b3a42afSjmmv
2496b3a42afSjmmv const fs::path tester_path = engine::tester_path(_interface);
250*46b85cbbSlukem std::unique_ptr< process::child > child = process::child::spawn_capture(
2516b3a42afSjmmv tester_path, args);
2526b3a42afSjmmv
2536b3a42afSjmmv const std::string output = utils::read_stream(child->output());
2546b3a42afSjmmv
2556b3a42afSjmmv const process::status status = child->wait();
2566b3a42afSjmmv if (!status.exited() || status.exitstatus() != EXIT_SUCCESS)
2576b3a42afSjmmv throw engine::error("Tester did not exit cleanly: " +
2586b3a42afSjmmv replace_newlines(output));
2596b3a42afSjmmv return output;
2606b3a42afSjmmv }
2616b3a42afSjmmv
2626b3a42afSjmmv
2636b3a42afSjmmv /// Executes a test operation on a test case.
2646b3a42afSjmmv ///
2656b3a42afSjmmv /// \param program Path to the test program.
2666b3a42afSjmmv /// \param test_case_name Name of the test case to execute.
2676b3a42afSjmmv /// \param result_file Path to the file in which to leave the result of the
2686b3a42afSjmmv /// tester invocation.
2696b3a42afSjmmv /// \param stdout_file Path to the file in which to store the stdout.
2706b3a42afSjmmv /// \param stderr_file Path to the file in which to store the stderr.
2716b3a42afSjmmv /// \param vars Collection of configuration variables.
2726b3a42afSjmmv ///
2736b3a42afSjmmv /// \throw error If the tester returns with an unsuccessful exit code.
2746b3a42afSjmmv void
test(const fs::path & program,const std::string & test_case_name,const fs::path & result_file,const fs::path & stdout_file,const fs::path & stderr_file,const std::map<std::string,std::string> & vars) const2756b3a42afSjmmv engine::tester::test(const fs::path& program, const std::string& test_case_name,
2766b3a42afSjmmv const fs::path& result_file, const fs::path& stdout_file,
2776b3a42afSjmmv const fs::path& stderr_file,
2786b3a42afSjmmv const std::map< std::string, std::string >& vars) const
2796b3a42afSjmmv {
2806b3a42afSjmmv std::vector< std::string > args = _common_args;
2816b3a42afSjmmv args.push_back("test");
2826b3a42afSjmmv for (std::map< std::string, std::string >::const_iterator i = vars.begin();
2836b3a42afSjmmv i != vars.end(); ++i) {
2846b3a42afSjmmv args.push_back(F("-v%s=%s") % (*i).first % (*i).second);
2856b3a42afSjmmv }
2866b3a42afSjmmv args.push_back(program.str());
2876b3a42afSjmmv args.push_back(test_case_name);
2886b3a42afSjmmv args.push_back(result_file.str());
2896b3a42afSjmmv
2906b3a42afSjmmv const fs::path tester_path = engine::tester_path(_interface);
291*46b85cbbSlukem std::unique_ptr< process::child > child = process::child::spawn_files(
2926b3a42afSjmmv tester_path, args, stdout_file, stderr_file);
2936b3a42afSjmmv const process::status status = child->wait();
2946b3a42afSjmmv
2956b3a42afSjmmv if (status.exited()) {
2966b3a42afSjmmv if (status.exitstatus() == EXIT_SUCCESS) {
2976b3a42afSjmmv // OK; the tester exited cleanly.
2986b3a42afSjmmv } else if (status.exitstatus() == EXIT_FAILURE) {
2996b3a42afSjmmv // OK; the tester reported that the test itself failed and we have
3006b3a42afSjmmv // the result file to indicate this.
3016b3a42afSjmmv } else {
3026b3a42afSjmmv throw engine::error(F("Tester failed with code %s; this is a bug") %
3036b3a42afSjmmv status.exitstatus());
3046b3a42afSjmmv }
3056b3a42afSjmmv } else {
3066b3a42afSjmmv INV(status.signaled());
307f39f9c9bSjmmv throw engine::error(F("Tester received signal %s; this is a bug") %
308f39f9c9bSjmmv status.termsig());
3096b3a42afSjmmv }
3106b3a42afSjmmv }
311