From 0cc76cb72f4d48ea3e52e60bf7ccddb1dad01a41 Mon Sep 17 00:00:00 2001 From: Hummeltech <6109326+hummeltech@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:58:11 -0700 Subject: [PATCH] Cleaning up tests (#424) --- Makefile.am | 4 +- tests/CMakeLists.txt | 8 +- tests/{catch => }/catch_main.cpp | 25 +- tests/{catch => }/catch_test_common.cpp | 32 + tests/{catch => }/catch_test_common.hpp | 3 + tests/gen_tile_test.cpp | 4 +- tests/pstreams/pstream.hpp | 2456 +++++++++++++++++++++++ tests/render_expired_test.cpp | 180 +- tests/render_list_test.cpp | 330 +-- tests/render_old_test.cpp | 136 +- tests/render_speedtest_test.cpp | 112 +- tests/renderd_config_test.cpp | 358 +--- tests/renderd_test.cpp | 75 +- 13 files changed, 2865 insertions(+), 858 deletions(-) rename tests/{catch => }/catch_main.cpp (63%) rename tests/{catch => }/catch_test_common.cpp (85%) rename tests/{catch => }/catch_test_common.hpp (91%) create mode 100644 tests/pstreams/pstream.hpp diff --git a/Makefile.am b/Makefile.am index b8f84ce7..92a40e4f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -99,8 +99,8 @@ render_old_LDADD = $(PTHREAD_CFLAGS) $(GLIB_LIBS) $(INIPARSER_LDFLAGS) #convert_meta_SOURCES = src/dir_utils.c src/store.c src/convert_meta.c noinst_LIBRARIES = catch_main.o catch_test_common.o -catch_main_o_SOURCES = tests/catch/catch_main.cpp -catch_test_common_o_SOURCES = tests/catch/catch_test_common.cpp +catch_main_o_SOURCES = tests/catch_main.cpp +catch_test_common_o_SOURCES = tests/catch_test_common.cpp gen_tile_test_SOURCES = \ tests/gen_tile_test.cpp \ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e5721399..38625afa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1136,8 +1136,8 @@ endforeach() #----------------------------------------------------------------------------- add_library(catch_main_o OBJECT - catch/catch_main.cpp - ${PROJECT_SOURCE_DIR}/tests/catch/catch_test_common.cpp + ${PROJECT_SOURCE_DIR}/tests/catch_main.cpp + ${PROJECT_SOURCE_DIR}/tests/catch_test_common.cpp ) target_include_directories(catch_main_o PRIVATE ${GLIB_INCLUDE_DIRS}) @@ -1147,7 +1147,9 @@ target_include_directories(catch_main_o PRIVATE ${GLIB_INCLUDE_DIRS}) # #----------------------------------------------------------------------------- -add_library(catch_test_common_o OBJECT ${PROJECT_SOURCE_DIR}/tests/catch/catch_test_common.cpp) +add_library(catch_test_common_o OBJECT + ${PROJECT_SOURCE_DIR}/tests/catch_test_common.cpp +) target_include_directories(catch_test_common_o PRIVATE ${GLIB_INCLUDE_DIRS}) #----------------------------------------------------------------------------- diff --git a/tests/catch/catch_main.cpp b/tests/catch_main.cpp similarity index 63% rename from tests/catch/catch_main.cpp rename to tests/catch_main.cpp index d799da0b..5e79fb18 100644 --- a/tests/catch/catch_main.cpp +++ b/tests/catch_main.cpp @@ -17,30 +17,7 @@ #define CATCH_CONFIG_MAIN -#include "catch.hpp" +#include "catch/catch.hpp" #include "catch_test_common.hpp" int foreground = 1; - -struct CaptureListener : Catch::TestEventListenerBase { - - using TestEventListenerBase::TestEventListenerBase; - - void testCaseStarting(Catch::TestCaseInfo const &testCaseInfo) override - { - start_capture(); - } - - void testCaseEnded(Catch::TestCaseStats const &testCaseStats) override - { - bool print = false; - - if (testCaseStats.totals.assertions.failed > 0) { - print = true; - } - - end_capture(print); - } -}; - -CATCH_REGISTER_LISTENER(CaptureListener) diff --git a/tests/catch/catch_test_common.cpp b/tests/catch_test_common.cpp similarity index 85% rename from tests/catch/catch_test_common.cpp rename to tests/catch_test_common.cpp index a31b315d..f26ff1ad 100644 --- a/tests/catch/catch_test_common.cpp +++ b/tests/catch_test_common.cpp @@ -23,12 +23,44 @@ #include #include "catch_test_common.hpp" +#include "pstreams/pstream.hpp" extern int foreground; +std::string err_log_lines, out_log_lines; + captured_stdio captured_stderr; captured_stdio captured_stdout; +int run_command(std::string file, std::vector argv, std::string input) +{ + auto mode = redi::pstreams::pstdout | redi::pstreams::pstderr | redi::pstreams::pstdin; + + argv.insert(argv.begin(), file); + + redi::pstream proc(file, argv, mode); + + std::string line; + err_log_lines = ""; + out_log_lines = ""; + + if (!input.empty()) { + proc << input << redi::peof; + } + + while (std::getline(proc.err(), line)) { + err_log_lines += line; + } + + proc.clear(); + + while (std::getline(proc.out(), line)) { + out_log_lines += line; + } + + return proc.close(); +} + std::string read_stderr(int buffer_size) { char buffer[buffer_size]; diff --git a/tests/catch/catch_test_common.hpp b/tests/catch_test_common.hpp similarity index 91% rename from tests/catch/catch_test_common.hpp rename to tests/catch_test_common.hpp index 8d35c5ed..948b1c2e 100644 --- a/tests/catch/catch_test_common.hpp +++ b/tests/catch_test_common.hpp @@ -16,6 +16,7 @@ */ #include +#include #ifndef CATCH_TEST_COMMON_HPP #define CATCH_TEST_COMMON_HPP @@ -37,4 +38,6 @@ std::string get_captured_stdout(bool print = false); void start_capture(bool debug = false); std::tuple end_capture(bool print = false); +int run_command(std::string file, std::vector argv = {}, std::string input = ""); + #endif diff --git a/tests/gen_tile_test.cpp b/tests/gen_tile_test.cpp index 536b270c..93b89d8c 100644 --- a/tests/gen_tile_test.cpp +++ b/tests/gen_tile_test.cpp @@ -20,8 +20,6 @@ #include #include -#include -#include #include #include #include @@ -37,7 +35,7 @@ #include #include "catch/catch.hpp" -#include "catch/catch_test_common.hpp" +#include "catch_test_common.hpp" #include "config.h" #include "g_logger.h" #include "gen_tile.h" diff --git a/tests/pstreams/pstream.hpp b/tests/pstreams/pstream.hpp new file mode 100644 index 00000000..91ac0b3c --- /dev/null +++ b/tests/pstreams/pstream.hpp @@ -0,0 +1,2456 @@ +// PStreams - POSIX Process I/O for C++ + +// Copyright (C) 2001 - 2020 Jonathan Wakely +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// SPDX-License-Identifier: BSL-1.0 + +/** + * @file pstream.h + * @brief Declares all PStreams classes. + * @author Jonathan Wakely + * + * Defines classes redi::ipstream, redi::opstream, redi::pstream + * and redi::rpstream. + */ + +#ifndef REDI_PSTREAM_H_SEEN +#define REDI_PSTREAM_H_SEEN + +#include +#include +#include +#include +#include +#include +#include // for min() +#include // for errno +#include // for size_t, NULL +#include // for exit() +#include // for pid_t +#include // for waitpid() +#include // for ioctl() and FIONREAD +#if defined(__sun) +# include // for FIONREAD on Solaris 2.5 +#endif +#include // for pipe() fork() exec() and filedes functions +#include // for kill() +#include // for fcntl() +#if REDI_EVISCERATE_PSTREAMS +# include // for FILE, fdopen() +#endif + + +/// The library version. +#define PSTREAMS_VERSION 0x0103 // 1.0.3 + +/** + * @namespace redi + * @brief All PStreams classes are declared in namespace redi. + * + * Like the standard iostreams, PStreams is a set of class templates, + * taking a character type and traits type. As with the standard streams + * they are most likely to be used with @c char and the default + * traits type, so typedefs for this most common case are provided. + * + * The @c pstream_common class template is not intended to be used directly, + * it is used internally to provide the common functionality for the + * other stream classes. + */ +namespace redi +{ + /// Common base class providing constants and typenames. + struct pstreams + { + /// Type used to specify how to connect to the process. + typedef std::ios_base::openmode pmode; + + /// Type used to hold the arguments for a command. + typedef std::vector argv_type; + + /// Type used for file descriptors. + typedef int fd_type; + + static const pmode pstdin = std::ios_base::out; ///< Write to stdin + static const pmode pstdout = std::ios_base::in; ///< Read from stdout + static const pmode pstderr = std::ios_base::app; ///< Read from stderr + + /// Create a new process group for the child process. + static const pmode newpg = std::ios_base::trunc; + + protected: + enum { + bufsz = 32, ///< Size of pstreambuf buffers. + pbsz = 2 ///< Number of putback characters kept. + }; + +#if __cplusplus >= 201103L + template + using stringable = decltype((void)std::string(std::declval())); +#endif + }; + + /// Class template for stream buffer. + template > + class basic_pstreambuf + : public std::basic_streambuf + , public pstreams + { + public: + // Type definitions for dependent types + typedef CharT char_type; + typedef Traits traits_type; + typedef typename traits_type::int_type int_type; + typedef typename traits_type::off_type off_type; + typedef typename traits_type::pos_type pos_type; + /** @deprecated use pstreams::fd_type instead. */ + typedef fd_type fd_t; + + /// Default constructor. + basic_pstreambuf(); + + /// Constructor that initialises the buffer with @a cmd. + basic_pstreambuf(const std::string& cmd, pmode mode); + + /// Constructor that initialises the buffer with @a file and @a argv. + basic_pstreambuf( const std::string& file, + const argv_type& argv, + pmode mode ); + +#if __cplusplus >= 201103L + basic_pstreambuf(basic_pstreambuf&&) noexcept; + basic_pstreambuf& operator=(basic_pstreambuf&&) noexcept; + void swap(basic_pstreambuf&) noexcept; +#endif + + /// Destructor. + ~basic_pstreambuf(); + + /// Initialise the stream buffer with @a cmd. + basic_pstreambuf* + open(const std::string& cmd, pmode mode); + + /// Initialise the stream buffer with @a file and @a argv. + basic_pstreambuf* + open(const std::string& file, const argv_type& argv, pmode mode); + + /// Close the stream buffer and wait for the process to exit. + basic_pstreambuf* + close(); + + /// Send a signal to the process. + basic_pstreambuf* + kill(int signal = SIGTERM); + + /// Send a signal to the process' process group. + basic_pstreambuf* + killpg(int signal = SIGTERM); + + /// Close the pipe connected to the process' stdin. + void + peof(); + + /// Change active input source. + bool + read_err(bool readerr = true); + + /// Report whether the stream buffer has been initialised. + bool + is_open() const; + + /// Report whether the process has exited. + bool + exited(); + +#if REDI_EVISCERATE_PSTREAMS + /// Obtain FILE pointers for each of the process' standard streams. + std::size_t + fopen(FILE*& in, FILE*& out, FILE*& err); +#endif + + /// Return the exit status of the process. + int + status() const; + + /// Return the error number (errno) for the most recent failed operation. + int + error() const; + + protected: + /// Transfer characters to the pipe when character buffer overflows. + int_type + overflow(int_type c); + + /// Transfer characters from the pipe when the character buffer is empty. + int_type + underflow(); + + /// Make a character available to be returned by the next extraction. + int_type + pbackfail(int_type c = traits_type::eof()); + + /// Write any buffered characters to the stream. + int + sync(); + + /// Insert multiple characters into the pipe. + std::streamsize + xsputn(const char_type* s, std::streamsize n); + + /// Insert a sequence of characters into the pipe. + std::streamsize + write(const char_type* s, std::streamsize n); + + /// Extract a sequence of characters from the pipe. + std::streamsize + read(char_type* s, std::streamsize n); + + /// Report how many characters can be read from active input without blocking. + std::streamsize + showmanyc(); + + protected: + /// Enumerated type to indicate whether stdout or stderr is to be read. + enum buf_read_src { rsrc_out = 0, rsrc_err = 1 }; + + /// Initialise pipes and fork process. + pid_t + fork(pmode mode); + + /// Wait for the child process to exit. + int + wait(bool nohang = false); + + /// Return the file descriptor for the output pipe. + fd_type& + wpipe(); + + /// Return the file descriptor for the active input pipe. + fd_type& + rpipe(); + + /// Return the file descriptor for the specified input pipe. + fd_type& + rpipe(buf_read_src which); + + void + create_buffers(pmode mode); + + void + destroy_buffers(pmode mode); + + /// Writes buffered characters to the process' stdin pipe. + bool + empty_buffer(); + + bool + fill_buffer(bool non_blocking = false); + + /// Return the active input buffer. + char_type* + rbuffer(); + + buf_read_src + switch_read_buffer(buf_read_src); + + private: +#if __cplusplus >= 201103L + using basic_streambuf = std::basic_streambuf; +#else + basic_pstreambuf(const basic_pstreambuf&); + basic_pstreambuf& operator=(const basic_pstreambuf&); +#endif + + void + init_rbuffers(); + + pid_t ppid_; // pid of process + fd_type wpipe_; // pipe used to write to process' stdin + fd_type rpipe_[2]; // two pipes to read from, stdout and stderr + char_type* wbuffer_; + char_type* rbuffer_[2]; + char_type* rbufstate_[3]; + /// Index into rpipe_[] to indicate active source for read operations. + buf_read_src rsrc_; + int status_; // hold exit status of child process + int error_; // hold errno if fork() or exec() fails + }; + + /// Class template for common base class. + template > + class pstream_common + : virtual public std::basic_ios + , virtual public pstreams + { + protected: + typedef basic_pstreambuf streambuf_type; + typedef std::basic_ios ios_type; + + typedef pstreams::pmode pmode; + typedef pstreams::argv_type argv_type; + + /// Default constructor. + pstream_common(); + + /// Constructor that initialises the stream by starting a process. + pstream_common(const std::string& cmd, pmode mode); + + /// Constructor that initialises the stream by starting a process. + pstream_common(const std::string& file, const argv_type& argv, pmode mode); + + /// Pure virtual destructor. + virtual + ~pstream_common() = 0; + +#if __cplusplus >= 201103L + pstream_common(pstream_common&& rhs) noexcept + : command_(std::move(rhs.command_)) + , buf_(std::move(rhs.buf_)) + { + /* derived class is responsible for ios_type::move(rhs) happening */ + } + + pstream_common& + operator=(pstream_common&& rhs) noexcept + { + command_ = std::move(rhs.command_); + buf_ = std::move(rhs.buf_); + return *this; + } + + void + swap(pstream_common& rhs) noexcept + { + /* derived class is responsible for ios_type::swap(rhs) happening */ + command_.swap(rhs.command_); + buf_.swap(rhs.buf_); + } +#endif // C++11 + + /// Start a process. + void + do_open(const std::string& cmd, pmode mode); + + /// Start a process. + void + do_open(const std::string& file, const argv_type& argv, pmode mode); + + public: + /// Close the pipe, returning the program's exit status, as + /// pclose(3) does. + int + close(); + + /// Report whether the stream's buffer has been initialised. + bool + is_open() const; + + /// Return the command used to initialise the stream. + const std::string& + command() const; + + /// Return a pointer to the stream buffer. + streambuf_type* + rdbuf() const; + +#if REDI_EVISCERATE_PSTREAMS + /// Obtain FILE pointers for each of the process' standard streams. + std::size_t + fopen(FILE*& in, FILE*& out, FILE*& err); +#endif + + protected: + std::string command_; ///< The command used to start the process. + streambuf_type buf_; ///< The stream buffer. + }; + + + /** + * @class basic_ipstream + * @brief Class template for Input PStreams. + * + * Reading from an ipstream reads the command's standard output and/or + * standard error (depending on how the ipstream is opened) + * and the command's standard input is the same as that of the process + * that created the object, unless altered by the command itself. + */ + + template > + class basic_ipstream + : public std::basic_istream + , public pstream_common + , virtual public pstreams + { + typedef std::basic_istream istream_type; + typedef pstream_common pbase_type; + + using pbase_type::buf_; // declare name in this scope + + // Ensure a basic_ipstream will read from at least one pipe + pmode readable(pmode mode) + { + if (!(mode & (pstdout|pstderr))) + mode |= pstdout; + return mode; + } + + public: + /// Type used to specify how to connect to the process. + typedef typename pbase_type::pmode pmode; + + /// Type used to hold the arguments for a command. + typedef typename pbase_type::argv_type argv_type; + + /// Default constructor, creates an uninitialised stream. + basic_ipstream() + : istream_type(NULL), pbase_type() + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + explicit + basic_ipstream(const std::string& cmd, pmode mode = pstdout) + : istream_type(NULL), pbase_type(cmd, readable(mode)) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + basic_ipstream( const std::string& file, + const argv_type& argv, + pmode mode = pstdout ) + : istream_type(NULL), pbase_type(file, argv, readable(mode)) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling + * @c do_open(argv[0],argv,mode|pstdout) + * + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + explicit + basic_ipstream(const argv_type& argv, pmode mode = pstdout) + : istream_type(NULL), pbase_type(argv.at(0), argv, readable(mode)) + { } + +#if __cplusplus >= 201103L + template> + explicit + basic_ipstream(std::initializer_list args, pmode mode = pstdout) + : basic_ipstream(argv_type(args.begin(), args.end()), mode) + { } + + basic_ipstream(basic_ipstream&& rhs) + : istream_type(std::move(rhs)) + , pbase_type(std::move(rhs)) + { istream_type::set_rdbuf(std::addressof(pbase_type::buf_)); } + + basic_ipstream& + operator=(basic_ipstream&& rhs) + { + istream_type::operator=(std::move(rhs)); + pbase_type::operator=(std::move(rhs)); + return *this; + } + + void + swap(basic_ipstream& rhs) + { + istream_type::swap(rhs); + pbase_type::swap(rhs); + } +#endif // C++11 + + /** + * @brief Destructor. + * + * Closes the stream and waits for the child to exit. + */ + ~basic_ipstream() + { } + + /** + * @brief Start a process. + * + * Calls do_open( @a cmd , @a mode|pstdout ). + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + void + open(const std::string& cmd, pmode mode = pstdout) + { + this->do_open(cmd, readable(mode)); + } + + /** + * @brief Start a process. + * + * Calls do_open( @a file , @a argv , @a mode|pstdout ). + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + void + open( const std::string& file, + const argv_type& argv, + pmode mode = pstdout ) + { + this->do_open(file, argv, readable(mode)); + } + + /** + * @brief Set streambuf to read from process' @c stdout. + * @return @c *this + */ + basic_ipstream& + out() + { + this->buf_.read_err(false); + return *this; + } + + /** + * @brief Set streambuf to read from process' @c stderr. + * @return @c *this + */ + basic_ipstream& + err() + { + this->buf_.read_err(true); + return *this; + } + }; + + + /** + * @class basic_opstream + * @brief Class template for Output PStreams. + * + * Writing to an open opstream writes to the standard input of the command; + * the command's standard output is the same as that of the process that + * created the pstream object, unless altered by the command itself. + */ + + template > + class basic_opstream + : public std::basic_ostream + , public pstream_common + , virtual public pstreams + { + typedef std::basic_ostream ostream_type; + typedef pstream_common pbase_type; + + using pbase_type::buf_; // declare name in this scope + + public: + /// Type used to specify how to connect to the process. + typedef typename pbase_type::pmode pmode; + + /// Type used to hold the arguments for a command. + typedef typename pbase_type::argv_type argv_type; + + /// Default constructor, creates an uninitialised stream. + basic_opstream() + : ostream_type(NULL), pbase_type() + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + explicit + basic_opstream(const std::string& cmd, pmode mode = pstdin) + : ostream_type(NULL), pbase_type(cmd, mode|pstdin) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + basic_opstream( const std::string& file, + const argv_type& argv, + pmode mode = pstdin ) + : ostream_type(NULL), pbase_type(file, argv, mode|pstdin) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling + * @c do_open(argv[0],argv,mode|pstdin) + * + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + explicit + basic_opstream(const argv_type& argv, pmode mode = pstdin) + : ostream_type(NULL), pbase_type(argv.at(0), argv, mode|pstdin) + { } + +#if __cplusplus >= 201103L + /** + * @brief Constructor that initialises the stream by starting a process. + * + * @param args a list of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + template> + explicit + basic_opstream(std::initializer_list args, pmode mode = pstdin) + : basic_opstream(argv_type(args.begin(), args.end()), mode) + { } + + basic_opstream(basic_opstream&& rhs) + : ostream_type(std::move(rhs)) + , pbase_type(std::move(rhs)) + { ostream_type::set_rdbuf(std::addressof(pbase_type::buf_)); } + + basic_opstream& + operator=(basic_opstream&& rhs) + { + ostream_type::operator=(std::move(rhs)); + pbase_type::operator=(std::move(rhs)); + return *this; + } + + void + swap(basic_opstream& rhs) + { + ostream_type::swap(rhs); + pbase_type::swap(rhs); + } +#endif // C++11 + + /** + * @brief Destructor + * + * Closes the stream and waits for the child to exit. + */ + ~basic_opstream() { } + + /** + * @brief Start a process. + * + * Calls do_open( @a cmd , @a mode|pstdin ). + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + void + open(const std::string& cmd, pmode mode = pstdin) + { + this->do_open(cmd, mode|pstdin); + } + + /** + * @brief Start a process. + * + * Calls do_open( @a file , @a argv , @a mode|pstdin ). + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + void + open( const std::string& file, + const argv_type& argv, + pmode mode = pstdin) + { + this->do_open(file, argv, mode|pstdin); + } + }; + + + /** + * @class basic_pstream + * @brief Class template for Bidirectional PStreams. + * + * Writing to a pstream opened with @c pmode @c pstdin writes to the + * standard input of the command. + * Reading from a pstream opened with @c pmode @c pstdout and/or @c pstderr + * reads the command's standard output and/or standard error. + * Any of the process' @c stdin, @c stdout or @c stderr that is not + * connected to the pstream (as specified by the @c pmode) + * will be the same as the process that created the pstream object, + * unless altered by the command itself. + */ + template > + class basic_pstream + : public std::basic_iostream + , public pstream_common + , virtual public pstreams + { + typedef std::basic_iostream iostream_type; + typedef pstream_common pbase_type; + + using pbase_type::buf_; // declare name in this scope + + public: + /// Type used to specify how to connect to the process. + typedef typename pbase_type::pmode pmode; + + /// Type used to hold the arguments for a command. + typedef typename pbase_type::argv_type argv_type; + + /// Default constructor, creates an uninitialised stream. + basic_pstream() + : iostream_type(NULL), pbase_type() + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + explicit + basic_pstream(const std::string& cmd, pmode mode = pstdout|pstdin) + : iostream_type(NULL), pbase_type(cmd, mode) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + basic_pstream( const std::string& file, + const argv_type& argv, + pmode mode = pstdout|pstdin ) + : iostream_type(NULL), pbase_type(file, argv, mode) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling + * @c do_open(argv[0],argv,mode) + * + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + explicit + basic_pstream(const argv_type& argv, pmode mode = pstdout|pstdin) + : iostream_type(NULL), pbase_type(argv.at(0), argv, mode) + { } + +#if __cplusplus >= 201103L + /** + * @brief Constructor that initialises the stream by starting a process. + * + * @param l a list of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + template> + explicit + basic_pstream(std::initializer_list l, pmode mode = pstdout|pstdin) + : basic_pstream(argv_type(l.begin(), l.end()), mode) + { } + + basic_pstream(basic_pstream&& rhs) + : iostream_type(std::move(rhs)) + , pbase_type(std::move(rhs)) + { iostream_type::set_rdbuf(std::addressof(pbase_type::buf_)); } + + basic_pstream& + operator=(basic_pstream&& rhs) + { + iostream_type::operator=(std::move(rhs)); + pbase_type::operator=(std::move(rhs)); + return *this; + } + + void + swap(basic_pstream& rhs) + { + iostream_type::swap(rhs); + pbase_type::swap(rhs); + } +#endif // C++11 + + /** + * @brief Destructor + * + * Closes the stream and waits for the child to exit. + */ + ~basic_pstream() { } + + /** + * @brief Start a process. + * + * Calls do_open( @a cnd , @a mode ). + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + void + open(const std::string& cmd, pmode mode = pstdout|pstdin) + { + this->do_open(cmd, mode); + } + + /** + * @brief Start a process. + * + * Calls do_open( @a file , @a argv , @a mode ). + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + void + open( const std::string& file, + const argv_type& argv, + pmode mode = pstdout|pstdin ) + { + this->do_open(file, argv, mode); + } + + /** + * @brief Set streambuf to read from process' @c stdout. + * @return @c *this + */ + basic_pstream& + out() + { + this->buf_.read_err(false); + return *this; + } + + /** + * @brief Set streambuf to read from process' @c stderr. + * @return @c *this + */ + basic_pstream& + err() + { + this->buf_.read_err(true); + return *this; + } + }; + + + /** + * @class basic_rpstream + * @brief Class template for Restricted PStreams. + * + * Writing to an rpstream opened with @c pmode @c pstdin writes to the + * standard input of the command. + * It is not possible to read directly from an rpstream object, to use + * an rpstream as in istream you must call either basic_rpstream::out() + * or basic_rpstream::err(). This is to prevent accidental reads from + * the wrong input source. If the rpstream was not opened with @c pmode + * @c pstderr then the class cannot read the process' @c stderr, and + * basic_rpstream::err() will return an istream that reads from the + * process' @c stdout, and vice versa. + * Reading from an rpstream opened with @c pmode @c pstdout and/or + * @c pstderr reads the command's standard output and/or standard error. + * Any of the process' @c stdin, @c stdout or @c stderr that is not + * connected to the pstream (as specified by the @c pmode) + * will be the same as the process that created the pstream object, + * unless altered by the command itself. + */ + + template > + class basic_rpstream + : public std::basic_ostream + , private std::basic_istream + , private pstream_common + , virtual public pstreams + { + typedef std::basic_ostream ostream_type; + typedef std::basic_istream istream_type; + typedef pstream_common pbase_type; + + using pbase_type::buf_; // declare name in this scope + + public: + /// Type used to specify how to connect to the process. + typedef typename pbase_type::pmode pmode; + + /// Type used to hold the arguments for a command. + typedef typename pbase_type::argv_type argv_type; + + /// Default constructor, creates an uninitialised stream. + basic_rpstream() + : ostream_type(NULL), istream_type(NULL), pbase_type() + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + explicit + basic_rpstream(const std::string& cmd, pmode mode = pstdout|pstdin) + : ostream_type(NULL) , istream_type(NULL) , pbase_type(cmd, mode) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling do_open() with the supplied + * arguments. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + basic_rpstream( const std::string& file, + const argv_type& argv, + pmode mode = pstdout|pstdin ) + : ostream_type(NULL), istream_type(NULL), pbase_type(file, argv, mode) + { } + + /** + * @brief Constructor that initialises the stream by starting a process. + * + * Initialises the stream buffer by calling + * @c do_open(argv[0],argv,mode) + * + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + explicit + basic_rpstream(const argv_type& argv, pmode mode = pstdout|pstdin) + : ostream_type(NULL), istream_type(NULL) + , pbase_type(argv.at(0), argv, mode) + { } + +#if __cplusplus >= 201103L + /** + * @brief Constructor that initialises the stream by starting a process. + * + * @param l a list of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + template> + explicit + basic_rpstream(std::initializer_list l, pmode mode = pstdout|pstdin) + : basic_rpstream(argv_type(l.begin(), l.end()), mode) + { } + + // TODO: figure out how to move istream and ostream bases separately, + // but so the virtual basic_ios base is only modified once. +#if 0 + basic_rpstream(basic_rpstream&& rhs) + : iostream_type(std::move(rhs)) + , pbase_type(std::move(rhs)) + { iostream_type::set_rdbuf(std::addressof(pbase_type::buf_)); } + + basic_rpstream& + operator=(basic_rpstream&& rhs) + { + iostream_type::operator=(std::move(rhs)); + pbase_type::operator=(std::move(rhs)); + return *this; + } + + void + swap(basic_rpstream& rhs) + { + iostream_type::swap(rhs); + pbase_type::swap(rhs); + } +#endif +#endif // C++11 + + /// Destructor + ~basic_rpstream() { } + + /** + * @brief Start a process. + * + * Calls do_open( @a cmd , @a mode ). + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + void + open(const std::string& cmd, pmode mode = pstdout|pstdin) + { + this->do_open(cmd, mode); + } + + /** + * @brief Start a process. + * + * Calls do_open( @a file , @a argv , @a mode ). + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + void + open( const std::string& file, + const argv_type& argv, + pmode mode = pstdout|pstdin ) + { + this->do_open(file, argv, mode); + } + + /** + * @brief Obtain a reference to the istream that reads + * the process' @c stdout. + * @return @c *this + */ + istream_type& + out() + { + this->buf_.read_err(false); + return *this; + } + + /** + * @brief Obtain a reference to the istream that reads + * the process' @c stderr. + * @return @c *this + */ + istream_type& + err() + { + this->buf_.read_err(true); + return *this; + } + }; + + + /// Type definition for common template specialisation. + typedef basic_pstreambuf pstreambuf; + /// Type definition for common template specialisation. + typedef basic_ipstream ipstream; + /// Type definition for common template specialisation. + typedef basic_opstream opstream; + /// Type definition for common template specialisation. + typedef basic_pstream pstream; + /// Type definition for common template specialisation. + typedef basic_rpstream rpstream; + + + /** + * When inserted into an output pstream the manipulator calls + * basic_pstreambuf::peof() to close the output pipe, + * causing the child process to receive the end-of-file indicator + * on subsequent reads from its @c stdin stream. + * + * @brief Manipulator to close the pipe connected to the process' stdin. + * @param s An output PStream class. + * @return The stream object the manipulator was invoked on. + * @see basic_pstreambuf::peof() + * @relates basic_opstream basic_pstream basic_rpstream + */ + template + inline std::basic_ostream& + peof(std::basic_ostream& s) + { + typedef basic_pstreambuf pstreambuf_type; + if (pstreambuf_type* p = dynamic_cast(s.rdbuf())) + p->peof(); + return s; + } + + + /* + * member definitions for pstreambuf + */ + + + /** + * @class basic_pstreambuf + * Provides underlying streambuf functionality for the PStreams classes. + */ + + /** Creates an uninitialised stream buffer. */ + template + inline + basic_pstreambuf::basic_pstreambuf() + : ppid_(-1) // initialise to -1 to indicate no process run yet. + , wpipe_(-1) + , wbuffer_() + , rbuffer_() + , rbufstate_() + , rsrc_(rsrc_out) + , status_(-1) + , error_(0) + { + rpipe_[rsrc_out] = rpipe_[rsrc_err] = -1; + } + + /** + * Initialises the stream buffer by calling open() with the supplied + * arguments. + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see open() + */ + template + inline + basic_pstreambuf::basic_pstreambuf(const std::string& cmd, pmode mode) + : ppid_(-1) // initialise to -1 to indicate no process run yet. + , wpipe_(-1) + , wbuffer_() + , rbuffer_() + , rbufstate_() + , rsrc_(rsrc_out) + , status_(-1) + , error_(0) + { + rpipe_[rsrc_out] = rpipe_[rsrc_err] = -1; + open(cmd, mode); + } + + /** + * Initialises the stream buffer by calling open() with the supplied + * arguments. + * + * @param file a string containing the name of a program to execute. + * @param argv a vector of argument strings passsed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see open() + */ + template + inline + basic_pstreambuf::basic_pstreambuf( const std::string& file, + const argv_type& argv, + pmode mode ) + : ppid_(-1) // initialise to -1 to indicate no process run yet. + , wpipe_(-1) + , wbuffer_() + , rbuffer_() + , rbufstate_() + , rsrc_(rsrc_out) + , status_(-1) + , error_(0) + { + rpipe_[rsrc_out] = rpipe_[rsrc_err] = -1; + open(file, argv, mode); + } + + /** + * Closes the stream by calling close(). + * @see close() + */ + template + inline + basic_pstreambuf::~basic_pstreambuf() + { + close(); + } + +#if __cplusplus >= 201103L + /** + * Move constructor. + */ + template + inline + basic_pstreambuf::basic_pstreambuf( basic_pstreambuf&& rhs ) noexcept + : basic_streambuf(static_cast(rhs)) + , ppid_(rhs.ppid_) + , wpipe_(rhs.wpipe_) + , rpipe_{rhs.rpipe_[0], rhs.rpipe_[1]} + , wbuffer_(rhs.wbuffer_) + , rbuffer_{rhs.rbuffer_[0], rhs.rbuffer_[1]} + , rbufstate_{rhs.rbufstate_[0], rhs.rbufstate_[1], rhs.rbufstate_[2]} + , rsrc_(rhs.rsrc_) + , status_(rhs.status_) + , error_(rhs.error_) + { + rhs.ppid_ = -1; + rhs.wpipe_ = -1; + rhs.rpipe_[0] = rhs.rpipe_[1] = -1; + rhs.wbuffer_ = nullptr; + rhs.rbuffer_[0] = rhs.rbuffer_[1] = nullptr; + rhs.rbufstate_[0] = rhs.rbufstate_[1] = rhs.rbufstate_[2] = nullptr; + rhs.rsrc_ = rsrc_out; + rhs.status_ = -1; + rhs.error_ = 0; + rhs.setg(nullptr, nullptr, nullptr); + rhs.setp(nullptr, nullptr); + } + + template + inline basic_pstreambuf& + basic_pstreambuf::operator=( basic_pstreambuf&& rhs ) noexcept + { + close(); + basic_streambuf::operator=(static_cast(rhs)); + swap(rhs); + return *this; + } + + template + inline void + basic_pstreambuf::swap( basic_pstreambuf& rhs ) noexcept + { + basic_streambuf::swap(static_cast(rhs)); + std::swap(ppid_, rhs.ppid_); + std::swap(wpipe_, rhs.wpipe_); + std::swap(rpipe_, rhs.rpipe_); + std::swap(wbuffer_, rhs.wbuffer_); + std::swap(rbuffer_, rhs.rbuffer_); + std::swap(rbufstate_, rhs.rbufstate_); + std::swap(rsrc_, rhs.rsrc_); + std::swap(status_, rhs.status_); + std::swap(error_, rhs.error_); + } +#endif // C++11 + + /** + * Starts a new process by passing @a command to the shell (/bin/sh) + * and opens pipes to the process with the specified @a mode. + * + * If @a mode contains @c pstdout the initial read source will be + * the child process' stdout, otherwise if @a mode contains @c pstderr + * the initial read source will be the child's stderr. + * + * Will duplicate the actions of the shell in searching for an + * executable file if the specified file name does not contain a slash (/) + * character. + * + * @warning + * There is no way to tell whether the shell command succeeded, this + * function will always succeed unless resource limits (such as + * memory usage, or number of processes or open files) are exceeded. + * This means is_open() will return true even if @a command cannot + * be executed. + * Use pstreambuf::open(const std::string&, const argv_type&, pmode) + * if you need to know whether the command failed to execute. + * + * @param command a string containing a shell command. + * @param mode a bitwise OR of one or more of @c out, @c in, @c err. + * @return NULL if the shell could not be started or the + * pipes could not be opened, @c this otherwise. + * @see execl(3) + */ + template + basic_pstreambuf* + basic_pstreambuf::open(const std::string& command, pmode mode) + { + const char * shell_path = "/bin/sh"; +#if 0 + const std::string argv[] = { "sh", "-c", command }; + return this->open(shell_path, argv_type(argv, argv+3), mode); +#else + basic_pstreambuf* ret = NULL; + + if (!is_open()) + { + switch(fork(mode)) + { + case 0 : + // this is the new process, exec command + ::execl(shell_path, "sh", "-c", command.c_str(), (char*)NULL); + + // can only reach this point if exec() failed + + // parent can get exit code from waitpid() + ::_exit(errno); + // using std::exit() would make static dtors run twice + + case -1 : + // couldn't fork, error already handled in pstreambuf::fork() + break; + + default : + // this is the parent process + // activate buffers + create_buffers(mode); + ret = this; + } + } + return ret; +#endif + } + + /** + * @brief Helper function to close a file descriptor. + * + * Inspects @a fd and calls close(3) if it has a non-negative value. + * + * @param fd a file descriptor. + * @relates basic_pstreambuf + */ + inline void + close_fd(pstreams::fd_type& fd) + { + if (fd >= 0 && ::close(fd) == 0) + fd = -1; + } + + /** + * @brief Helper function to close an array of file descriptors. + * + * Calls @c close_fd() on each member of the array. + * The length of the array is determined automatically by + * template argument deduction to avoid errors. + * + * @param fds an array of file descriptors. + * @relates basic_pstreambuf + */ + template + inline void + close_fd_array(pstreams::fd_type (&fds)[N]) + { + for (std::size_t i = 0; i < N; ++i) + close_fd(fds[i]); + } + + /** + * Starts a new process by executing @a file with the arguments in + * @a argv and opens pipes to the process with the specified @a mode. + * + * By convention @c argv[0] should be the file name of the file being + * executed. + * + * If @a mode contains @c pstdout the initial read source will be + * the child process' stdout, otherwise if @a mode contains @c pstderr + * the initial read source will be the child's stderr. + * + * Will duplicate the actions of the shell in searching for an + * executable file if the specified file name does not contain a slash (/) + * character. + * + * Iff @a file is successfully executed then is_open() will return true. + * Otherwise, pstreambuf::error() can be used to obtain the value of + * @c errno that was set by execvp(3) in the child process. + * + * The exit status of the new process will be returned by + * pstreambuf::status() after pstreambuf::exited() returns true. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode a bitwise OR of one or more of @c out, @c in and @c err. + * @return NULL if a pipe could not be opened or if the program could + * not be executed, @c this otherwise. + * @see execvp(3) + */ + template + basic_pstreambuf* + basic_pstreambuf::open( const std::string& file, + const argv_type& argv, + pmode mode ) + { + basic_pstreambuf* ret = NULL; + + if (!is_open()) + { + // constants for read/write ends of pipe + enum { RD, WR }; + + // open another pipe and set close-on-exec + fd_type ck_exec[] = { -1, -1 }; + if (-1 == ::pipe(ck_exec) + || -1 == ::fcntl(ck_exec[RD], F_SETFD, FD_CLOEXEC) + || -1 == ::fcntl(ck_exec[WR], F_SETFD, FD_CLOEXEC)) + { + error_ = errno; + close_fd_array(ck_exec); + } + else + { + switch(fork(mode)) + { + case 0 : + // this is the new process, exec command + { + char** arg_v = new char*[argv.size()+1]; + for (std::size_t i = 0; i < argv.size(); ++i) + { + const std::string& src = argv[i]; + char*& dest = arg_v[i]; + dest = new char[src.size()+1]; + dest[ src.copy(dest, src.size()) ] = '\0'; + } + arg_v[argv.size()] = NULL; + + ::execvp(file.c_str(), arg_v); + + // can only reach this point if exec() failed + + // parent can get error code from ck_exec pipe + error_ = errno; + + while (::write(ck_exec[WR], &error_, sizeof(error_)) == -1 + && errno == EINTR) + { } + + ::close(ck_exec[WR]); + ::close(ck_exec[RD]); + + ::_exit(error_); + // using std::exit() would make static dtors run twice + } + + case -1 : + // couldn't fork, error already handled in pstreambuf::fork() + close_fd_array(ck_exec); + break; + + default : + // this is the parent process + + // check child called exec() successfully + ::close(ck_exec[WR]); + switch (::read(ck_exec[RD], &error_, sizeof(error_))) + { + case 0: + // activate buffers + create_buffers(mode); + ret = this; + break; + case -1: + error_ = errno; + break; + default: + // error_ contains error code from child + // call wait() to clean up and set ppid_ to 0 + this->wait(); + break; + } + ::close(ck_exec[RD]); + } + } + } + return ret; + } + + /** + * Creates pipes as specified by @a mode and calls @c fork() to create + * a new process. If the fork is successful the parent process stores + * the child's PID and the opened pipes and the child process replaces + * its standard streams with the opened pipes. + * + * If an error occurs the error code will be set to one of the possible + * errors for @c pipe() or @c fork(). + * See your system's documentation for these error codes. + * + * @param mode an OR of pmodes specifying which of the child's + * standard streams to connect to. + * @return On success the PID of the child is returned in the parent's + * context and zero is returned in the child's context. + * On error -1 is returned and the error code is set appropriately. + */ + template + pid_t + basic_pstreambuf::fork(pmode mode) + { + pid_t pid = -1; + + // Three pairs of file descriptors, for pipes connected to the + // process' stdin, stdout and stderr + // (stored in a single array so close_fd_array() can close all at once) + fd_type fd[] = { -1, -1, -1, -1, -1, -1 }; + fd_type* const pin = fd; + fd_type* const pout = fd+2; + fd_type* const perr = fd+4; + + // constants for read/write ends of pipe + enum { RD, WR }; + + // N.B. + // For the pstreambuf pin is an output stream and + // pout and perr are input streams. + + if (!error_ && mode&pstdin && ::pipe(pin)) + error_ = errno; + + if (!error_ && mode&pstdout && ::pipe(pout)) + error_ = errno; + + if (!error_ && mode&pstderr && ::pipe(perr)) + error_ = errno; + + if (!error_) + { + pid = ::fork(); + switch (pid) + { + case 0 : + { + // this is the new process + + // for each open pipe close one end and redirect the + // respective standard stream to the other end + + if (*pin >= 0) + { + ::close(pin[WR]); + ::dup2(pin[RD], STDIN_FILENO); + ::close(pin[RD]); + } + if (*pout >= 0) + { + ::close(pout[RD]); + ::dup2(pout[WR], STDOUT_FILENO); + ::close(pout[WR]); + } + if (*perr >= 0) + { + ::close(perr[RD]); + ::dup2(perr[WR], STDERR_FILENO); + ::close(perr[WR]); + } + +#ifdef _POSIX_JOB_CONTROL + if (mode&newpg) + ::setpgid(0, 0); // Change to a new process group +#endif + + break; + } + case -1 : + { + // couldn't fork for some reason + error_ = errno; + // close any open pipes + close_fd_array(fd); + break; + } + default : + { + // this is the parent process, store process' pid + ppid_ = pid; + + // store one end of open pipes and close other end + if (*pin >= 0) + { + wpipe_ = pin[WR]; + ::close(pin[RD]); + } + if (*pout >= 0) + { + rpipe_[rsrc_out] = pout[RD]; + ::close(pout[WR]); + } + if (*perr >= 0) + { + rpipe_[rsrc_err] = perr[RD]; + ::close(perr[WR]); + } + } + } + } + else + { + // close any pipes we opened before failure + close_fd_array(fd); + } + return pid; + } + + /** + * Closes all pipes and calls wait() to wait for the process to finish. + * If an error occurs the error code will be set to one of the possible + * errors for @c waitpid(). + * See your system's documentation for these errors. + * + * @return @c this on successful close or @c NULL if there is no + * process to close or if an error occurs. + */ + template + basic_pstreambuf* + basic_pstreambuf::close() + { + const bool running = is_open(); + + basic_pstreambuf::sync(); // might call wait() and reap the child process + + // rather than trying to work out whether or not we need to clean up + // just do it anyway, all cleanup functions are safe to call twice. + + destroy_buffers(pstdin|pstdout|pstderr); + + // close pipes before wait() so child gets EOF/SIGPIPE + close_fd(wpipe_); + close_fd_array(rpipe_); + + do + { + error_ = 0; + } while (wait() == -1 && error() == EINTR); + + return running ? this : NULL; + } + + /** + * Used to be called on construction to initialise the arrays for reading. + * No longer used. + */ + template +#if __cplusplus >= 201402L && __has_cpp_attribute(deprecated) + [[deprecated]] +#elif __GNUC__ + __attribute__((deprecated)) +#endif + inline void + basic_pstreambuf::init_rbuffers() + { + rpipe_[rsrc_out] = rpipe_[rsrc_err] = -1; + rbuffer_[rsrc_out] = rbuffer_[rsrc_err] = NULL; + rbufstate_[0] = rbufstate_[1] = rbufstate_[2] = NULL; + } + + template + void + basic_pstreambuf::create_buffers(pmode mode) + { + if (mode & pstdin) + { + delete[] wbuffer_; + wbuffer_ = new char_type[bufsz]; + this->setp(wbuffer_, wbuffer_ + bufsz); + } + if (mode & pstdout) + { + delete[] rbuffer_[rsrc_out]; + rbuffer_[rsrc_out] = new char_type[bufsz]; + rsrc_ = rsrc_out; + this->setg(rbuffer_[rsrc_out] + pbsz, rbuffer_[rsrc_out] + pbsz, + rbuffer_[rsrc_out] + pbsz); + } + if (mode & pstderr) + { + delete[] rbuffer_[rsrc_err]; + rbuffer_[rsrc_err] = new char_type[bufsz]; + if (!(mode & pstdout)) + { + rsrc_ = rsrc_err; + this->setg(rbuffer_[rsrc_err] + pbsz, rbuffer_[rsrc_err] + pbsz, + rbuffer_[rsrc_err] + pbsz); + } + } + } + + template + void + basic_pstreambuf::destroy_buffers(pmode mode) + { + if (mode & pstdin) + { + this->setp(NULL, NULL); + delete[] wbuffer_; + wbuffer_ = NULL; + } + if (mode & pstdout) + { + if (rsrc_ == rsrc_out) + this->setg(NULL, NULL, NULL); + delete[] rbuffer_[rsrc_out]; + rbuffer_[rsrc_out] = NULL; + } + if (mode & pstderr) + { + if (rsrc_ == rsrc_err) + this->setg(NULL, NULL, NULL); + delete[] rbuffer_[rsrc_err]; + rbuffer_[rsrc_err] = NULL; + } + } + + template + typename basic_pstreambuf::buf_read_src + basic_pstreambuf::switch_read_buffer(buf_read_src src) + { + if (rsrc_ != src) + { + char_type* tmpbufstate[] = {this->eback(), this->gptr(), this->egptr()}; + this->setg(rbufstate_[0], rbufstate_[1], rbufstate_[2]); + for (std::size_t i = 0; i < 3; ++i) + rbufstate_[i] = tmpbufstate[i]; + rsrc_ = src; + } + return rsrc_; + } + + /** + * Suspends execution and waits for the associated process to exit, or + * until a signal is delivered whose action is to terminate the current + * process or to call a signal handling function. If the process has + * already exited (i.e. it is a "zombie" process) then wait() returns + * immediately. Waiting for the child process causes all its system + * resources to be freed. + * + * error() will return EINTR if wait() is interrupted by a signal. + * + * @param nohang true to return immediately if the process has not exited. + * @return 1 if the process has exited and wait() has not yet been called. + * 0 if @a nohang is true and the process has not exited yet. + * -1 if no process has been started or if an error occurs, + * in which case the error can be found using error(). + */ + template + int + basic_pstreambuf::wait(bool nohang) + { + int child_exited = -1; + if (is_open()) + { + int exit_status; + switch(::waitpid(ppid_, &exit_status, nohang ? WNOHANG : 0)) + { + case 0 : + // nohang was true and process has not exited + child_exited = 0; + break; + case -1 : + error_ = errno; + break; + default : + // process has exited + ppid_ = 0; + status_ = exit_status; + child_exited = 1; + // Close wpipe, would get SIGPIPE if we used it. + destroy_buffers(pstdin); + close_fd(wpipe_); + // Must free read buffers and pipes on destruction + // or next call to open()/close() + break; + } + } + return child_exited; + } + + /** + * Sends the specified signal to the process. A signal can be used to + * terminate a child process that would not exit otherwise. + * + * If an error occurs the error code will be set to one of the possible + * errors for @c kill(). See your system's documentation for these errors. + * + * @param signal A signal to send to the child process. + * @return @c this or @c NULL if @c kill() fails. + */ + template + inline basic_pstreambuf* + basic_pstreambuf::kill(int signal) + { + basic_pstreambuf* ret = NULL; + if (is_open()) + { + if (::kill(ppid_, signal)) + error_ = errno; + else + { +#if 0 + // TODO call exited() to check for exit and clean up? leave to user? + if (signal==SIGTERM || signal==SIGKILL) + this->exited(); +#endif + ret = this; + } + } + return ret; + } + + /** + * Sends the specified signal to the process group of the child process. + * A signal can be used to terminate a child process that would not exit + * otherwise, or to kill the process and its own children. + * + * If an error occurs the error code will be set to one of the possible + * errors for @c getpgid() or @c kill(). See your system's documentation + * for these errors. If the child is in the current process group then + * NULL will be returned and the error code set to EPERM. + * + * @param signal A signal to send to the child process. + * @return @c this on success or @c NULL on failure. + */ + template + inline basic_pstreambuf* + basic_pstreambuf::killpg(int signal) + { + basic_pstreambuf* ret = NULL; +#ifdef _POSIX_JOB_CONTROL + if (is_open()) + { + pid_t pgid = ::getpgid(ppid_); + if (pgid == -1) + error_ = errno; + else if (pgid == ::getpgrp()) + error_ = EPERM; // Don't commit suicide + else if (::killpg(pgid, signal)) + error_ = errno; + else + ret = this; + } +#else + error_ = ENOTSUP; +#endif + return ret; + } + + /** + * This function can call pstreambuf::wait() and so may change the + * object's state if the child process has already exited. + * + * @return True if the associated process has exited, false otherwise. + * @see basic_pstreambuf::wait() + */ + template + inline bool + basic_pstreambuf::exited() + { + return ppid_ == 0 || wait(true)==1; + } + + + /** + * @return The exit status of the child process, or -1 if wait() + * has not yet been called to wait for the child to exit. + * @see basic_pstreambuf::wait() + */ + template + inline int + basic_pstreambuf::status() const + { + return status_; + } + + /** + * @return The error code of the most recently failed operation, or zero. + */ + template + inline int + basic_pstreambuf::error() const + { + return error_; + } + + /** + * Closes the output pipe, causing the child process to receive the + * end-of-file indicator on subsequent reads from its @c stdin stream. + */ + template + inline void + basic_pstreambuf::peof() + { + sync(); + destroy_buffers(pstdin); + close_fd(wpipe_); + } + + /** + * Unlike pstreambuf::exited(), this function will not call wait() and + * so will not change the object's state. This means that once a child + * process is executed successfully this function will continue to + * return true even after the process exits (until wait() is called.) + * + * @return true if a previous call to open() succeeded and wait() has + * not been called and determined that the process has exited, + * false otherwise. + */ + template + inline bool + basic_pstreambuf::is_open() const + { + return ppid_ > 0; + } + + /** + * Toggle the stream used for reading. If @a readerr is @c true then the + * process' @c stderr output will be used for subsequent extractions, if + * @a readerr is false the the process' stdout will be used. + * @param readerr @c true to read @c stderr, @c false to read @c stdout. + * @return @c true if the requested stream is open and will be used for + * subsequent extractions, @c false otherwise. + */ + template + inline bool + basic_pstreambuf::read_err(bool readerr) + { + buf_read_src src = readerr ? rsrc_err : rsrc_out; + if (rpipe_[src]>=0) + { + switch_read_buffer(src); + return true; + } + return false; + } + + /** + * Called when the internal character buffer is not present or is full, + * to transfer the buffer contents to the pipe. + * + * @param c a character to be written to the pipe. + * @return @c traits_type::eof() if an error occurs, otherwise if @a c + * is not equal to @c traits_type::eof() it will be buffered and + * a value other than @c traits_type::eof() returned to indicate + * success. + */ + template + typename basic_pstreambuf::int_type + basic_pstreambuf::overflow(int_type c) + { + if (!empty_buffer()) + return traits_type::eof(); + else if (!traits_type::eq_int_type(c, traits_type::eof())) + return this->sputc(c); + else + return traits_type::not_eof(c); + } + + + template + int + basic_pstreambuf::sync() + { + return !exited() && empty_buffer() ? 0 : -1; + } + + /** + * @param s character buffer. + * @param n buffer length. + * @return the number of characters written. + */ + template + std::streamsize + basic_pstreambuf::xsputn(const char_type* s, std::streamsize n) + { + std::streamsize done = 0; + while (done < n) + { + if (std::streamsize nbuf = this->epptr() - this->pptr()) + { + nbuf = std::min(nbuf, n - done); + traits_type::copy(this->pptr(), s + done, nbuf); + this->pbump(nbuf); + done += nbuf; + } + else if (!empty_buffer()) + break; + } + return done; + } + + /** + * @return true if the buffer was emptied, false otherwise. + */ + template + bool + basic_pstreambuf::empty_buffer() + { + const std::streamsize count = this->pptr() - this->pbase(); + if (count > 0) + { + const std::streamsize written = this->write(this->wbuffer_, count); + if (written > 0) + { + if (const std::streamsize unwritten = count - written) + traits_type::move(this->pbase(), this->pbase()+written, unwritten); + this->pbump(-written); + return true; + } + } + return false; + } + + /** + * Called when the internal character buffer is is empty, to re-fill it + * from the pipe. + * + * @return The first available character in the buffer, + * or @c traits_type::eof() in case of failure. + */ + template + typename basic_pstreambuf::int_type + basic_pstreambuf::underflow() + { + if (this->gptr() < this->egptr() || fill_buffer()) + return traits_type::to_int_type(*this->gptr()); + else + return traits_type::eof(); + } + + /** + * Attempts to make @a c available as the next character to be read by + * @c sgetc(). + * + * @param c a character to make available for extraction. + * @return @a c if the character can be made available, + * @c traits_type::eof() otherwise. + */ + template + typename basic_pstreambuf::int_type + basic_pstreambuf::pbackfail(int_type c) + { + if (this->gptr() != this->eback()) + { + this->gbump(-1); + if (!traits_type::eq_int_type(c, traits_type::eof())) + *this->gptr() = traits_type::to_char_type(c); + return traits_type::not_eof(c); + } + else + return traits_type::eof(); + } + + template + std::streamsize + basic_pstreambuf::showmanyc() + { + int avail = 0; + if (sizeof(char_type) == 1) + avail = fill_buffer(true) ? this->egptr() - this->gptr() : -1; +#ifdef FIONREAD + else + { + if (::ioctl(rpipe(), FIONREAD, &avail) == -1) + avail = -1; + else if (avail) + avail /= sizeof(char_type); + } +#endif + return std::streamsize(avail); + } + + /** + * @return true if the buffer was filled, false otherwise. + */ + template + bool + basic_pstreambuf::fill_buffer(bool non_blocking) + { + const std::streamsize pb1 = this->gptr() - this->eback(); + const std::streamsize pb2 = pbsz; + const std::streamsize npb = std::min(pb1, pb2); + + char_type* const rbuf = rbuffer(); + + if (npb) + traits_type::move(rbuf + pbsz - npb, this->gptr() - npb, npb); + + std::streamsize rc = -1; + + if (non_blocking) + { + const int flags = ::fcntl(rpipe(), F_GETFL); + if (flags != -1) + { + const bool blocking = !(flags & O_NONBLOCK); + if (blocking) + ::fcntl(rpipe(), F_SETFL, flags | O_NONBLOCK); // set non-blocking + + error_ = 0; + rc = read(rbuf + pbsz, bufsz - pbsz); + + if (rc == -1 && error_ == EAGAIN) // nothing available + rc = 0; + else if (rc == 0) // EOF + rc = -1; + + if (blocking) + ::fcntl(rpipe(), F_SETFL, flags); // restore + } + } + else + rc = read(rbuf + pbsz, bufsz - pbsz); + + if (rc > 0 || (rc == 0 && non_blocking)) + { + this->setg( rbuf + pbsz - npb, + rbuf + pbsz, + rbuf + pbsz + rc ); + return true; + } + else + { + this->setg(NULL, NULL, NULL); + return false; + } + } + + /** + * Writes up to @a n characters to the pipe from the buffer @a s. + * + * @param s character buffer. + * @param n buffer length. + * @return the number of characters written. + */ + template + inline std::streamsize + basic_pstreambuf::write(const char_type* s, std::streamsize n) + { + std::streamsize nwritten = 0; + if (wpipe() >= 0) + { + nwritten = ::write(wpipe(), s, n * sizeof(char_type)); + if (nwritten == -1) + error_ = errno; + else + nwritten /= sizeof(char_type); + } + return nwritten; + } + + /** + * Reads up to @a n characters from the pipe to the buffer @a s. + * + * @param s character buffer. + * @param n buffer length. + * @return the number of characters read. + */ + template + inline std::streamsize + basic_pstreambuf::read(char_type* s, std::streamsize n) + { + std::streamsize nread = 0; + if (rpipe() >= 0) + { + nread = ::read(rpipe(), s, n * sizeof(char_type)); + if (nread == -1) + error_ = errno; + else + nread /= sizeof(char_type); + } + return nread; + } + + /** @return a reference to the output file descriptor */ + template + inline pstreams::fd_type& + basic_pstreambuf::wpipe() + { + return wpipe_; + } + + /** @return a reference to the active input file descriptor */ + template + inline pstreams::fd_type& + basic_pstreambuf::rpipe() + { + return rpipe_[rsrc_]; + } + + /** @return a reference to the specified input file descriptor */ + template + inline pstreams::fd_type& + basic_pstreambuf::rpipe(buf_read_src which) + { + return rpipe_[which]; + } + + /** @return a pointer to the start of the active input buffer area. */ + template + inline typename basic_pstreambuf::char_type* + basic_pstreambuf::rbuffer() + { + return rbuffer_[rsrc_]; + } + + + /* + * member definitions for pstream_common + */ + + /** + * @class pstream_common + * Abstract Base Class providing common functionality for basic_ipstream, + * basic_opstream and basic_pstream. + * pstream_common manages the basic_pstreambuf stream buffer that is used + * by the derived classes to initialise an iostream class. + */ + + /** Creates an uninitialised stream. */ + template + inline + pstream_common::pstream_common() + : std::basic_ios(NULL) + , command_() + , buf_() + { + this->std::basic_ios::rdbuf(&buf_); + } + + /** + * Initialises the stream buffer by calling + * do_open( @a command , @a mode ) + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, pmode) + */ + template + inline + pstream_common::pstream_common(const std::string& cmd, pmode mode) + : std::basic_ios(NULL) + , command_(cmd) + , buf_() + { + this->std::basic_ios::rdbuf(&buf_); + do_open(cmd, mode); + } + + /** + * Initialises the stream buffer by calling + * do_open( @a file , @a argv , @a mode ) + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see do_open(const std::string&, const argv_type&, pmode) + */ + template + inline + pstream_common::pstream_common( const std::string& file, + const argv_type& argv, + pmode mode ) + : std::basic_ios(NULL) + , command_(file) + , buf_() + { + this->std::basic_ios::rdbuf(&buf_); + do_open(file, argv, mode); + } + + /** + * This is a pure virtual function to make @c pstream_common abstract. + * Because it is the destructor it will be called by derived classes + * and so must be defined. It is also protected, to discourage use of + * the PStreams classes through pointers or references to the base class. + * + * @sa If defining a pure virtual seems odd you should read + * http://www.gotw.ca/gotw/031.htm (and the rest of the site as well!) + */ + template + inline + pstream_common::~pstream_common() + { + } + + /** + * Calls rdbuf()->open( @a command , @a mode ) + * and sets @c failbit on error. + * + * @param cmd a string containing a shell command. + * @param mode the I/O mode to use when opening the pipe. + * @see basic_pstreambuf::open(const std::string&, pmode) + */ + template + inline void + pstream_common::do_open(const std::string& cmd, pmode mode) + { + if (!buf_.open((command_=cmd), mode)) + this->setstate(std::ios_base::failbit); + } + + /** + * Calls rdbuf()->open( @a file, @a argv, @a mode ) + * and sets @c failbit on error. + * + * @param file a string containing the pathname of a program to execute. + * @param argv a vector of argument strings passed to the new program. + * @param mode the I/O mode to use when opening the pipe. + * @see basic_pstreambuf::open(const std::string&, const argv_type&, pmode) + */ + template + inline void + pstream_common::do_open( const std::string& file, + const argv_type& argv, + pmode mode ) + { + if (!buf_.open((command_=file), argv, mode)) + this->setstate(std::ios_base::failbit); + } + + /** Calls rdbuf->close() and sets @c failbit on error. Returns + * process's exit status, as pclose(3) does. */ + template + inline int + pstream_common::close() + { + if (!buf_.close()) + this->setstate(std::ios_base::failbit); + return buf_.status(); + } + + /** + * @return rdbuf()->is_open(). + * @see basic_pstreambuf::is_open() + */ + template + inline bool + pstream_common::is_open() const + { + return buf_.is_open(); + } + + /** @return a string containing the command used to initialise the stream. */ + template + inline const std::string& + pstream_common::command() const + { + return command_; + } + + /** @return a pointer to the private stream buffer member. */ + // TODO document behaviour if buffer replaced. + template + inline typename pstream_common::streambuf_type* + pstream_common::rdbuf() const + { + return const_cast(&buf_); + } + + +#if REDI_EVISCERATE_PSTREAMS + /** + * @def REDI_EVISCERATE_PSTREAMS + * If this macro has a non-zero value then certain internals of the + * @c basic_pstreambuf template class are exposed. In general this is + * a Bad Thing, as the internal implementation is largely undocumented + * and may be subject to change at any time, so this feature is only + * provided because it might make PStreams useful in situations where + * it is necessary to do Bad Things. + */ + + /** + * @warning This function exposes the internals of the stream buffer and + * should be used with caution. It is the caller's responsibility + * to flush streams etc. in order to clear any buffered data. + * The POSIX.1 function fdopen(3) is used to obtain the + * @c FILE pointers from the streambuf's private file descriptor + * members so consult your system's documentation for + * fdopen(3). + * + * @param in A FILE* that will refer to the process' stdin. + * @param out A FILE* that will refer to the process' stdout. + * @param err A FILE* that will refer to the process' stderr. + * @return An OR of zero or more of @c pstdin, @c pstdout, @c pstderr. + * + * For each open stream shared with the child process a @c FILE* is + * obtained and assigned to the corresponding parameter. For closed + * streams @c NULL is assigned to the parameter. + * The return value can be tested to see which parameters should be + * @c !NULL by masking with the corresponding @c pmode value. + * + * @see fdopen(3) + */ + template + std::size_t + basic_pstreambuf::fopen(FILE*& in, FILE*& out, FILE*& err) + { + in = out = err = NULL; + std::size_t open_files = 0; + if (wpipe() > -1) + { + if ((in = ::fdopen(wpipe(), "w"))) + { + open_files |= pstdin; + } + } + if (rpipe(rsrc_out) > -1) + { + if ((out = ::fdopen(rpipe(rsrc_out), "r"))) + { + open_files |= pstdout; + } + } + if (rpipe(rsrc_err) > -1) + { + if ((err = ::fdopen(rpipe(rsrc_err), "r"))) + { + open_files |= pstderr; + } + } + return open_files; + } + + /** + * @warning This function exposes the internals of the stream buffer and + * should be used with caution. + * + * @param in A FILE* that will refer to the process' stdin. + * @param out A FILE* that will refer to the process' stdout. + * @param err A FILE* that will refer to the process' stderr. + * @return A bitwise-or of zero or more of @c pstdin, @c pstdout, @c pstderr. + * @see basic_pstreambuf::fopen() + */ + template + inline std::size_t + pstream_common::fopen(FILE*& fin, FILE*& fout, FILE*& ferr) + { + return buf_.fopen(fin, fout, ferr); + } + +#endif // REDI_EVISCERATE_PSTREAMS + + +} // namespace redi + +/** + * @mainpage PStreams Reference + * @htmlinclude mainpage.html + */ + +#endif // REDI_PSTREAM_H_SEEN + +// vim: ts=2 sw=2 expandtab + diff --git a/tests/render_expired_test.cpp b/tests/render_expired_test.cpp index c513bd11..a1a03cb2 100644 --- a/tests/render_expired_test.cpp +++ b/tests/render_expired_test.cpp @@ -19,7 +19,7 @@ #include #include "catch/catch.hpp" -#include "catch/catch_test_common.hpp" +#include "catch_test_common.hpp" #include "config.h" #include "render_config.h" @@ -36,198 +36,124 @@ #endif std::string test_binary = (std::string)PROJECT_BINARY_DIR + "/" + "render_expired"; -int found; -std::string err_log_lines; +extern std::string err_log_lines, out_log_lines; TEST_CASE("render_expired common", "common testing") { SECTION("invalid long option", "should return 1") { - std::string option = "--doesnotexist"; - std::string command = test_binary + " " + option; + std::vector argv = {"--doesnotexist"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } SECTION("invalid short options", "should return 1") { - std::string option = "-oesnotexist"; - std::string command = test_binary + " " + option; + std::vector argv = {"-oesnotexist"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } SECTION("--help", "should return 0") { - std::string option = "--help"; - std::string command = test_binary + " " + option; + std::vector argv = {"--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } SECTION("--version", "should show version number") { - std::string option = "--version"; - std::string command = test_binary + " " + option; - - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - std::string output; - char buffer[sizeof(VERSION)]; - fgets(buffer, sizeof(buffer), pipe); - output += buffer; - REQUIRE(output == VERSION); - int status = pclose(pipe); + std::vector argv = {"--version"}; + + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); + REQUIRE(out_log_lines == VERSION); } SECTION("--config invalid file", "should return 1") { std::string renderd_conf = "/path/is/invalid"; - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Config file '" + renderd_conf + "' does not exist, please specify a valid file"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Config file '" + renderd_conf + "' does not exist, please specify a valid file")); } } TEST_CASE("render_expired specific", "specific testing") { + std::string renderd_conf = (std::string)RENDERD_CONF; SECTION("--config with invalid --map", "should return 1") { - std::string renderd_conf = (std::string)RENDERD_CONF; - std::string option = "--config " + renderd_conf + " --map invalid"; - std::string command = test_binary + " " + option; + std::string map = "invalid"; + std::vector argv = {"--config", renderd_conf, "--map", map}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Map section 'invalid' does not exist in config file '" + renderd_conf + "'."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Map section '" + map + "' does not exist in config file '" + renderd_conf + "'.")); } SECTION("--config with valid --map and --tile-dir with invalid path", "should return 1") { - std::string renderd_conf = (std::string)RENDERD_CONF; - std::string option = "--config " + renderd_conf + " --map example-map --tile-dir /path/is/invalid"; - std::string command = test_binary + " " + option; + std::string map = "example-map"; + std::string tile_dir = GENERATE("invalid", "/path/is/invalid"); + std::vector argv = {"--config", renderd_conf, "--map", map, "--tile-dir", tile_dir}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("init_storage_backend: Failed to stat /path/is/invalid with error: No such file or directory"); - REQUIRE(found > -1); - found = err_log_lines.find("Failed to initialise storage backend /path/is/invalid"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Failed to initialise storage backend " + tile_dir)); } SECTION("--config with valid --map, --verbose and bad input lines", "should return 0") { - std::string renderd_conf = (std::string)RENDERD_CONF; - std::string option = "--config " + renderd_conf + " --map example-map --tile-dir " + P_tmpdir + " --verbose"; - std::string command = test_binary + " " + option; - - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "w"); - fputs("z/x/y\n", pipe); - fputs("x y z\n", pipe); - int status = pclose(pipe); - REQUIRE(WEXITSTATUS(status) == 0); + std::string map = "example-map"; + std::string tile_dir = P_tmpdir; + std::vector argv = {"--config", renderd_conf, "--map", map, "--tile-dir", tile_dir, "--verbose"}; + std::string input = "z/x/y\nx y z\n"; - err_log_lines = read_stderr(); - found = err_log_lines.find("bad line 0: z/x/y"); - REQUIRE(found > -1); - found = err_log_lines.find("bad line 0: x y z"); - REQUIRE(found > -1); + int status = run_command(test_binary, argv, input); + REQUIRE(WEXITSTATUS(status) == 0); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("bad line 0: z/x/y")); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("bad line 0: x y z")); } SECTION("--tile-dir with invalid option", "should return 1") { - std::string option = "--tile-dir invalid"; - std::string command = test_binary + " " + option; + std::string tile_dir = "invalid"; + std::vector argv = {"--tile-dir", tile_dir}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("init_storage_backend: No valid storage backend found for options: invalid"); - REQUIRE(found > -1); - found = err_log_lines.find("Failed to initialise storage backend invalid"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("init_storage_backend: No valid storage backend found for options: " + tile_dir)); } SECTION("--tile-dir with invalid path", "should return 1") { - std::string option = "--tile-dir /path/is/invalid"; - std::string command = test_binary + " " + option; + std::string tile_dir = "/path/is/invalid"; + std::vector argv = {"--tile-dir", tile_dir}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("init_storage_backend: Failed to stat /path/is/invalid with error: No such file or directory"); - REQUIRE(found > -1); - found = err_log_lines.find("Failed to initialise storage backend /path/is/invalid"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("init_storage_backend: Failed to stat " + tile_dir + " with error: No such file or directory")); } SECTION("--num-threads subceeds minimum of 1", "should return 1") { - std::string option = "--num-threads 0"; - std::string command = test_binary + " " + option; + std::vector argv = {"--num-threads", "0"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Invalid number of threads, must be >= 1 (0 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Invalid number of threads, must be >= 1 (0 was provided)")); } SECTION("--min-zoom/--max-zoom exceeds maximum of MAX_ZOOM", "should return 1") { - std::string option = GENERATE("--max-zoom", "--min-zoom"); - std::string command = test_binary + " " + option + " " + std::to_string(MAX_ZOOM + 1); + std::vector argv = {GENERATE("--max-zoom", "--min-zoom"), std::to_string(MAX_ZOOM + 1)}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("zoom, must be <= 20 (21 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("zoom, must be <= 20 (21 was provided)")); } SECTION("--min-zoom exceeds --max-zoom", "should return 1") { - std::string option = "--max-zoom 1 --min-zoom 2"; - std::string command = test_binary + " " + option; + std::vector argv = {"--max-zoom", "1", "--min-zoom", "2"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified min zoom (2) is larger than max zoom (1)."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified min zoom (2) is larger than max zoom (1).")); } } @@ -236,11 +162,9 @@ TEST_CASE("render_expired min/max int generator", "min/max int generator testing std::string option = GENERATE("--delete-from", "--max-load", "--max-zoom", "--min-zoom", "--num-threads", "--touch-from"); SECTION(option + " option is positive with --help", "should return 0") { - std::string command = test_binary + " " + option + " 1 --help"; + std::vector argv = {option, "1", "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } } diff --git a/tests/render_list_test.cpp b/tests/render_list_test.cpp index 2e17324f..581e6f7b 100644 --- a/tests/render_list_test.cpp +++ b/tests/render_list_test.cpp @@ -19,7 +19,7 @@ #include #include "catch/catch.hpp" -#include "catch/catch_test_common.hpp" +#include "catch_test_common.hpp" #include "config.h" #include "render_config.h" @@ -36,319 +36,205 @@ #endif std::string test_binary = (std::string)PROJECT_BINARY_DIR + "/" + "render_list"; -int found; -std::string err_log_lines; +extern std::string err_log_lines, out_log_lines; TEST_CASE("render_list common", "common testing") { SECTION("invalid long option", "should return 1") { - std::string option = "--doesnotexist"; - std::string command = test_binary + " " + option; + std::vector argv = {"--doesnotexist"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } SECTION("invalid short options", "should return 1") { - std::string option = "-doesnotexist"; - std::string command = test_binary + " " + option; + std::vector argv = {"-doesnotexist"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } + SECTION("--help", "should return 0") { + std::vector argv = {"--help"}; + + int status = run_command(test_binary, argv); + REQUIRE(WEXITSTATUS(status) == 0); + } + SECTION("--version", "should show version number") { - std::string option = "--version"; - std::string command = test_binary + " " + option; - - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - std::string output; - char buffer[sizeof(VERSION)]; - fgets(buffer, sizeof(buffer), pipe); - output += buffer; - REQUIRE(output == VERSION); - int status = pclose(pipe); + std::vector argv = {"--version"}; + + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); + REQUIRE(out_log_lines == VERSION); } SECTION("--config invalid file", "should return 1") { std::string renderd_conf = "/path/is/invalid"; - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Config file '" + renderd_conf + "' does not exist, please specify a valid file"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Config file '" + renderd_conf + "' does not exist, please specify a valid file")); } } TEST_CASE("render_list specific", "specific testing") { + std::string renderd_conf = (std::string)RENDERD_CONF; + SECTION("--config with invalid --map", "should return 1") { - std::string renderd_conf = (std::string)RENDERD_CONF; - std::string option = "--config " + renderd_conf + " --map invalid"; - std::string command = test_binary + " " + option; + std::string map = "invalid"; + std::vector argv = {"--config", renderd_conf, "--map", map}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Map section 'invalid' does not exist in config file '" + renderd_conf + "'."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Map section '" + map + "' does not exist in config file '" + renderd_conf + "'.")); } SECTION("--config with valid --map and --tile-dir with invalid path", "should return 1") { - std::string renderd_conf = (std::string)RENDERD_CONF; - std::string option = "--config " + renderd_conf + " --map example-map --tile-dir /path/is/invalid"; - std::string command = test_binary + " " + option; + std::string map = "example-map"; + std::string tile_dir = "/path/is/invalid"; + std::vector argv = {"--config", renderd_conf, "--map", map, "--tile-dir", tile_dir}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("init_storage_backend: Failed to stat /path/is/invalid with error: No such file or directory"); - REQUIRE(found > -1); - found = err_log_lines.find("Failed to initialise storage backend /path/is/invalid"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("init_storage_backend: Failed to stat " + tile_dir + " with error: No such file or directory")); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Failed to initialise storage backend " + tile_dir)); } SECTION("--config with valid --map, --verbose and bad input lines", "should return 0") { - std::string renderd_conf = (std::string)RENDERD_CONF; - std::string option = "--config " + renderd_conf + " --map example-map --tile-dir " + P_tmpdir + " --verbose"; - std::string command = test_binary + " " + option; - - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "w"); - fputs("z/x/y\n", pipe); - fputs("x y z\n", pipe); - int status = pclose(pipe); - REQUIRE(WEXITSTATUS(status) == 0); + std::string map = "example-map"; + std::string tile_dir = P_tmpdir; + std::vector argv = {"--config", renderd_conf, "--map", map, "--tile-dir", tile_dir, "--verbose"}; + std::string input = "z/x/y\nx y z\n"; - err_log_lines = read_stderr(); - found = err_log_lines.find("bad line 0: z/x/y"); - REQUIRE(found > -1); - found = err_log_lines.find("bad line 0: x y z"); - REQUIRE(found > -1); + int status = run_command(test_binary, argv, input); + REQUIRE(WEXITSTATUS(status) == 0); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("bad line 0: z/x/y")); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("bad line 0: x y z")); } SECTION("--config with valid --map, --verbose and invalid zoom input lines", "should return 0") { - std::string renderd_conf = (std::string)RENDERD_CONF; - std::string option = "--config " + renderd_conf + " --map example-map --tile-dir " + P_tmpdir + " --verbose"; - std::string command = test_binary + " " + option; - - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "w"); - fputs("0 0 -100\n", pipe); - fputs("0 0 100\n", pipe); - int status = pclose(pipe); - REQUIRE(WEXITSTATUS(status) == 0); + std::string map = "example-map"; + std::string tile_dir = P_tmpdir; + std::vector argv = {"--config", renderd_conf, "--map", map, "--tile-dir", tile_dir, "--verbose"}; + std::string input = "0 0 -100\n0 0 100\n"; - err_log_lines = read_stderr(); - found = err_log_lines.find("Ignoring tile, zoom -100 outside valid range (0..20)"); - REQUIRE(found > -1); - found = err_log_lines.find("Ignoring tile, zoom 100 outside valid range (0..20)"); - REQUIRE(found > -1); + int status = run_command(test_binary, argv, input); + REQUIRE(WEXITSTATUS(status) == 0); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Ignoring tile, zoom -100 outside valid range (0..20)")); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Ignoring tile, zoom 100 outside valid range (0..20)")); } SECTION("--tile-dir with invalid option", "should return 1") { - std::string option = "--tile-dir invalid"; - std::string command = test_binary + " " + option; + std::string tile_dir = "invalid"; + std::vector argv = {"--tile-dir", tile_dir}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("init_storage_backend: No valid storage backend found for options: invalid"); - REQUIRE(found > -1); - found = err_log_lines.find("Failed to initialise storage backend invalid"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("init_storage_backend: No valid storage backend found for options: " + tile_dir)); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Failed to initialise storage backend " + tile_dir)); } SECTION("--tile-dir with invalid path", "should return 1") { - std::string option = "--tile-dir /path/is/invalid"; - std::string command = test_binary + " " + option; + std::string tile_dir = "/path/is/invalid"; + std::vector argv = {"--tile-dir", tile_dir}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("init_storage_backend: Failed to stat /path/is/invalid with error: No such file or directory"); - REQUIRE(found > -1); - found = err_log_lines.find("Failed to initialise storage backend /path/is/invalid"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("init_storage_backend: Failed to stat " + tile_dir + " with error: No such file or directory")); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Failed to initialise storage backend " + tile_dir)); } SECTION("--num-threads subceeds minimum of 1", "should return 1") { - std::string option = "--num-threads 0"; - std::string command = test_binary + " " + option; + std::vector argv = {"--num-threads", "0"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Invalid number of threads, must be >= 1 (0 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Invalid number of threads, must be >= 1 (0 was provided)")); } SECTION("--min-zoom/--max-zoom exceeds maximum of MAX_ZOOM", "should return 1") { - std::string option = GENERATE("--max-zoom", "--min-zoom"); - std::string command = test_binary + " " + option + " " + std::to_string(MAX_ZOOM + 1); + std::vector argv = {GENERATE("--max-zoom", "--min-zoom"), std::to_string(MAX_ZOOM + 1)}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("zoom, must be <= 20 (21 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("zoom, must be <= 20 (21 was provided)")); } SECTION("--min-zoom exceeds --max-zoom", "should return 1") { - std::string option = "--max-zoom 1 --min-zoom 2"; - std::string command = test_binary + " " + option; + std::vector argv = {"--max-zoom", "1", "--min-zoom", "2"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified min zoom (2) is larger than max zoom (1)."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified min zoom (2) is larger than max zoom (1).")); } SECTION("--all --min-zoom not equal to --max-zoom with X/Y options", "should return 1") { - std::string option = "--all --max-x 1 --max-zoom 10 --max-y 1 --min-zoom 9"; - std::string command = test_binary + " " + option; + std::vector argv = {"--all", "--max-x", "1", "--max-zoom", "10", "--max-y", "1", "--min-zoom", "9"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("min-zoom must be equal to max-zoom when using min-x, max-x, min-y, or max-y options"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("min-zoom must be equal to max-zoom when using min-x, max-x, min-y, or max-y options")); } SECTION("--all --max-x/y options exceed maximum (2^zoom-1)", "should return 1") { - std::string option = "--all --max-x 2 --max-y 2 --max-zoom 1 --min-zoom 1"; - std::string command = test_binary + " " + option; + std::vector argv = {"--all", "--max-x", "2", "--max-y", "2", "--max-zoom", "1", "--min-zoom", "1"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Invalid range, x and y values must be <= 1 (2^zoom-1)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Invalid range, x and y values must be <= 1 (2^zoom-1)")); } SECTION("--all --min-x/y options exceed maximum (2^zoom-1)", "should return 1") { - std::string option = "--all --max-zoom 1 --min-x 2 --min-y 2 --min-zoom 1"; - std::string command = test_binary + " " + option; + std::vector argv = {"--all", "--max-zoom", "1", "--min-x", "2", "--min-y", "2", "--min-zoom", "1"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Invalid range, x and y values must be <= 1 (2^zoom-1)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Invalid range, x and y values must be <= 1 (2^zoom-1)")); } SECTION("--all --min-x exceeds --max-x", "should return 1") { - std::string option = "--all --max-x 1 --max-zoom 1 --min-x 2 --min-zoom 1"; - std::string command = test_binary + " " + option; + std::vector argv = {"--all", "--max-x", "1", "--max-zoom", "1", "--min-x", "2", "--min-zoom", "1"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified min-x (2) is larger than max-x (1)."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified min-x (2) is larger than max-x (1).")); } SECTION("--all --min-y exceeds --max-y", "should return 1") { - std::string option = "--all --max-y 1 --max-zoom 1 --min-y 2 --min-zoom 1"; - std::string command = test_binary + " " + option; + std::vector argv = {"--all", "--max-y", "1", "--max-zoom", "1", "--min-y", "2", "--min-zoom", "1"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified min-y (2) is larger than max-y (1)."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified min-y (2) is larger than max-y (1).")); } SECTION("--all --max-lat --max-lon --min-lat --min-lon with --min-x/y and/or --max-x/y", "should return 1") { - std::string suboption = GENERATE("--min-x 0", "--min-y 0", "--max-x 0", "--max-y 0", "--min-x 0 --min-y 0 --max-x 0 --max-y 0"); - std::string option = "--all --max-lat 0 --max-lon 0 --min-lat 0 --min-lon 0 " + suboption; - std::string command = test_binary + " " + option; + std::vector suboptions = GENERATE(std::vector({"--min-x", "0"}), std::vector({"--min-y", "0"}), std::vector({"--max-x", "0"}), std::vector({"--max-y", "0"}), std::vector({"--min-x", "0", "--min-y", "0", "--max-x", "0", "--max-y", "0"})); + std::vector argv = {"--all", "--max-lat", "0", "--max-lon", "0", "--min-lat", "0", "--min-lon", "0"}; + argv.insert(argv.end(), suboptions.begin(), suboptions.end()); - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("min-lat, min-lon, max-lat & max-lon cannot be used together with min-x, max-x, min-y, or max-y"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("min-lat, min-lon, max-lat & max-lon cannot be used together with min-x, max-x, min-y, or max-y")); } SECTION("--all --min-lat exceeds --max-lat", "should return 1") { - std::string option = "--all --max-lat 0 --max-lon 0 --min-lat 1 --min-lon 0"; - std::string command = test_binary + " " + option; + std::vector argv = {"--all", "--max-lat", "0", "--max-lon", "0", "--min-lat", "1", "--min-lon", "0"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified min-lat (1.000000) is larger than max-lat (0.000000)."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified min-lat (1.000000) is larger than max-lat (0.000000).")); } SECTION("--all --min-lon exceeds --max-lon", "should return 1") { - std::string option = "--all --max-lat 0 --max-lon 0 --min-lat 0 --min-lon 1"; - std::string command = test_binary + " " + option; + std::vector argv = {"--all", "--max-lat", "0", "--max-lon", "0", "--min-lat", "0", "--min-lon", "1"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified min-lon (1.000000) is larger than max-lon (0.000000)."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified min-lon (1.000000) is larger than max-lon (0.000000).")); } } @@ -357,11 +243,9 @@ TEST_CASE("render_list min/max int generator", "min/max int generator testing") std::string option = GENERATE("--max-load", "--max-x", "--max-y", "--max-zoom", "--min-x", "--min-y", "--min-zoom", "--num-threads"); SECTION(option + " option is positive with --help", "should return 0") { - std::string command = test_binary + " " + option + " 1 --help"; + std::vector argv = {option, "1", "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } } @@ -373,20 +257,16 @@ TEST_CASE("render_list min/max lat generator", "min/max double generator testing double max = 85.051100; SECTION(option + " option is positive with --help", "should return 0") { - std::string command = test_binary + " " + option + " " + std::to_string(max) + " --help"; + std::vector argv = {option, std::to_string(max), "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } SECTION(option + " option is negative with --help", "should return 0") { - std::string command = test_binary + " " + option + " " + std::to_string(min) + " --help"; + std::vector argv = {option, std::to_string(min), "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } } @@ -398,20 +278,16 @@ TEST_CASE("render_list min/max lon generator", "min/max double generator testing double max = 180.000000; SECTION(option + " option is positive with --help", "should return 0") { - std::string command = test_binary + " " + option + " " + std::to_string(max) + " --help"; + std::vector argv = {option, std::to_string(max), "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } SECTION(option + " option is negative with --help", "should return 0") { - std::string command = test_binary + " " + option + " " + std::to_string(min) + " --help"; + std::vector argv = {option, std::to_string(min), "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } } diff --git a/tests/render_old_test.cpp b/tests/render_old_test.cpp index 9e82bae2..f9074014 100644 --- a/tests/render_old_test.cpp +++ b/tests/render_old_test.cpp @@ -15,11 +15,10 @@ * along with this program; If not, see http://www.gnu.org/licenses/. */ -#include #include #include "catch/catch.hpp" -#include "catch/catch_test_common.hpp" +#include "catch_test_common.hpp" #include "config.h" #include "render_config.h" @@ -36,148 +35,107 @@ #endif std::string test_binary = (std::string)PROJECT_BINARY_DIR + "/" + "render_old"; -int found; -std::string err_log_lines; +extern std::string err_log_lines, out_log_lines; TEST_CASE("render_old common", "common testing") { SECTION("invalid long option", "should return 1") { - std::string option = "--doesnotexist"; - std::string command = test_binary + " " + option; + std::vector argv = {"--doesnotexist"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } SECTION("invalid short options", "should return 1") { - std::string option = "-doesnotexist"; - std::string command = test_binary + " " + option; + std::vector argv = {"-doesnotexist"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } + SECTION("--help", "should return 0") { + std::vector argv = {"--help"}; + + int status = run_command(test_binary, argv); + REQUIRE(WEXITSTATUS(status) == 0); + } + SECTION("--version", "should show version number") { - std::string option = "--version"; - std::string command = test_binary + " " + option; - - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - std::string output; - char buffer[sizeof(VERSION)]; - fgets(buffer, sizeof(buffer), pipe); - output += buffer; - REQUIRE(output == VERSION); - int status = pclose(pipe); + std::vector argv = {"--version"}; + + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); + REQUIRE(out_log_lines == VERSION); } SECTION("--config invalid file", "should return 1") { std::string renderd_conf = "/path/is/invalid"; - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Config file '" + renderd_conf + "' does not exist, please specify a valid file"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Config file '" + renderd_conf + "' does not exist, please specify a valid file")); } } TEST_CASE("render_old specific", "specific testing") { + std::string renderd_conf = (std::string)RENDERD_CONF; + SECTION("--config with invalid --map", "should return 1") { - std::string renderd_conf = (std::string)RENDERD_CONF; - std::string option = "--config " + renderd_conf + " --map invalid"; - std::string command = test_binary + " " + option; + std::string map = "invalid"; + std::vector argv = {"--config", renderd_conf, "--map", map}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Map section 'invalid' does not exist in config file '" + renderd_conf + "'."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Map section '" + map + "' does not exist in config file '" + renderd_conf + "'.")); } SECTION("--num-threads subceeds minimum of 1", "should return 1") { - std::string option = "--num-threads 0"; - std::string command = test_binary + " " + option; + std::vector argv = {"--num-threads", "0"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Invalid number of threads, must be >= 1 (0 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Invalid number of threads, must be >= 1 (0 was provided)")); } SECTION("--min-zoom/--max-zoom exceeds maximum of MAX_ZOOM", "should return 1") { - std::string option = GENERATE("--max-zoom", "--min-zoom"); - std::string command = test_binary + " " + option + " " + std::to_string(MAX_ZOOM + 1); + std::vector argv = {GENERATE("--max-zoom", "--min-zoom"), std::to_string(MAX_ZOOM + 1)}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("zoom, must be <= 20 (21 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("zoom, must be <= 20 (21 was provided)")); } SECTION("--min-zoom exceeds --max-zoom", "should return 1") { - std::string option = "--max-zoom 1 --min-zoom 2"; - std::string command = test_binary + " " + option; + std::vector argv = {"--max-zoom", "1", "--min-zoom", "2"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified min zoom (2) is larger than max zoom (1)."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified min zoom (2) is larger than max zoom (1).")); } SECTION("--timestamp is not an integer", "should return 1") { - std::string option = "--timestamp invalid"; - std::string command = test_binary + " " + option; + std::string timestamp = "invalid"; + std::vector argv = {"--timestamp", timestamp}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } SECTION("--timestamp is a date with --help", "should return 0") { - std::string option = GENERATE("--timestamp 01/01/01", "--timestamp 01/01/1901"); - std::string command = test_binary + " " + option + " --help"; + std::string timestamp = GENERATE("01/01/69", "01/01/101"); + std::vector argv = {"--timestamp", timestamp, "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } SECTION("--timestamp is an integer with --help", "should return 0") { - std::string option = "--timestamp 111 --help"; - std::string command = test_binary + " " + option; + std::string timestamp = "111"; + std::vector argv = {"--timestamp", timestamp, "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } } @@ -187,11 +145,9 @@ TEST_CASE("render_old min/max int generator", "min/max int generator testing") std::string option = GENERATE("--max-load", "--max-zoom", "--min-zoom", "--num-threads"); SECTION(option + " option is positive with --help", "should return 0") { - std::string command = test_binary + " " + option + " 1 --help"; + std::vector argv = {option, "1", "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } } diff --git a/tests/render_speedtest_test.cpp b/tests/render_speedtest_test.cpp index fed6b2a8..2414dbe3 100644 --- a/tests/render_speedtest_test.cpp +++ b/tests/render_speedtest_test.cpp @@ -15,11 +15,10 @@ * along with this program; If not, see http://www.gnu.org/licenses/. */ -#include #include #include "catch/catch.hpp" -#include "catch/catch_test_common.hpp" +#include "catch_test_common.hpp" #include "config.h" #include "render_config.h" @@ -36,119 +35,84 @@ #endif std::string test_binary = (std::string)PROJECT_BINARY_DIR + "/" + "render_speedtest"; -int found; -std::string err_log_lines; +extern std::string err_log_lines, out_log_lines; TEST_CASE("render_speedtest common", "common testing") { SECTION("invalid long option", "should return 1") { - std::string option = "--doesnotexist"; - std::string command = test_binary + " " + option; + std::vector argv = {"--doesnotexist"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } SECTION("invalid short options", "should return 1") { - std::string option = "-doesnotexist"; - std::string command = test_binary + " " + option; + std::vector argv = {"-doesnotexist"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } + SECTION("--help", "should return 0") { + std::vector argv = {"--help"}; + + int status = run_command(test_binary, argv); + REQUIRE(WEXITSTATUS(status) == 0); + } + SECTION("--version", "should show version number") { - std::string option = "--version"; - std::string command = test_binary + " " + option; - - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - std::string output; - char buffer[sizeof(VERSION)]; - fgets(buffer, sizeof(buffer), pipe); - output += buffer; - REQUIRE(output == VERSION); - int status = pclose(pipe); + std::vector argv = {"--version"}; + + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); + REQUIRE(out_log_lines == VERSION); } SECTION("--config invalid file", "should return 1") { std::string renderd_conf = "/path/is/invalid"; - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Config file '" + renderd_conf + "' does not exist, please specify a valid file"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Config file '" + renderd_conf + "' does not exist, please specify a valid file")); } } TEST_CASE("render_speedtest specific", "specific testing") { + std::string renderd_conf = (std::string)RENDERD_CONF; + SECTION("--config with invalid --map", "should return 1") { - std::string renderd_conf = (std::string)RENDERD_CONF; - std::string option = "--config " + renderd_conf + " --map invalid"; - std::string command = test_binary + " " + option; + std::string map = "invalid"; + std::vector argv = {"--config", renderd_conf, "--map", map}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Map section 'invalid' does not exist in config file '" + renderd_conf + "'."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Map section '" + map + "' does not exist in config file '" + renderd_conf + "'.")); } SECTION("--num-threads subceeds minimum of 1", "should return 1") { - std::string option = "--num-threads 0"; - std::string command = test_binary + " " + option; + std::vector argv = {"--num-threads", "0"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Invalid number of threads, must be >= 1 (0 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Invalid number of threads, must be >= 1 (0 was provided)")); } SECTION("--min-zoom/--max-zoom exceeds maximum of MAX_ZOOM", "should return 1") { - std::string option = GENERATE("--max-zoom", "--min-zoom"); - std::string command = test_binary + " " + option + " " + std::to_string(MAX_ZOOM + 1); + std::vector argv = {GENERATE("--max-zoom", "--min-zoom"), std::to_string(MAX_ZOOM + 1)}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("zoom, must be <= 20 (21 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("zoom, must be <= 20 (21 was provided)")); } SECTION("--min-zoom exceeds --max-zoom", "should return 1") { - std::string option = "--max-zoom 1 --min-zoom 2"; - std::string command = test_binary + " " + option; + std::vector argv = {"--max-zoom", "1", "--min-zoom", "2"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified min zoom (2) is larger than max zoom (1)."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified min zoom (2) is larger than max zoom (1).")); } } @@ -157,11 +121,9 @@ TEST_CASE("render_speedtest min/max int generator", "min/max int generator testi std::string option = GENERATE("--max-zoom", "--min-zoom", "--num-threads"); SECTION(option + " option is positive with --help", "should return 0") { - std::string command = test_binary + " " + option + " 1 --help"; + std::vector argv = {option, "1", "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } } diff --git a/tests/renderd_config_test.cpp b/tests/renderd_config_test.cpp index dd848fc8..777f587b 100644 --- a/tests/renderd_config_test.cpp +++ b/tests/renderd_config_test.cpp @@ -16,12 +16,11 @@ */ #include -#include #include #include #include "catch/catch.hpp" -#include "catch/catch_test_common.hpp" +#include "catch_test_common.hpp" #include "render_config.h" #include "renderd.h" @@ -35,61 +34,42 @@ // Only render_list uses all functions in renderd_config.c std::string test_binary = (std::string)PROJECT_BINARY_DIR + "/" + "render_list"; -int found; -std::string err_log_lines; +extern std::string err_log_lines; TEST_CASE("renderd_config min/max int", "min/max int generator testing") { std::string option = GENERATE("--max-load", "--max-x", "--max-y", "--max-zoom", "--min-x", "--min-y", "--min-zoom", "--num-threads"); SECTION(option + " option is positive with --help", "should return 0") { - std::string command = test_binary + " " + option + " 1 --help"; + std::vector argv = {option, "1", "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } SECTION(option + " option is negative", "should return 1") { - std::string command = test_binary + " " + option + " -1"; + std::vector argv = {option, "-1"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("must be >="); - REQUIRE(found > -1); - found = err_log_lines.find("(-1 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("must be >=")); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("(-1 was provided)")); } SECTION(option + " option is float", "should return 1") { - std::string command = test_binary + " " + option + " 1.23456789"; + std::vector argv = {option, "1.23456789"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("must be an integer (1.23456789 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("must be an integer (1.23456789 was provided)")); } SECTION(option + " option is not an integer", "should return 1") { - std::string command = test_binary + " " + option + " invalid"; + std::vector argv = {option, "invalid"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("must be an integer (invalid was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("must be an integer (invalid was provided)")); } } @@ -100,68 +80,47 @@ TEST_CASE("renderd_config min/max double lat generator", "min/max double generat double max = 85.051100; SECTION(option + " option is too large", "should return 1") { - std::string command = test_binary + " " + option + " 85.05111"; + std::vector argv = {option, std::to_string(max + .1)}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("must be <= 85.051100 (85.05111 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("must be <= 85.051100 (85.151100 was provided)")); } SECTION(option + " option is too small", "should return 1") { - std::string command = test_binary + " " + option + " -85.05111"; + std::vector argv = {option, std::to_string(min - .1)}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("must be >= -85.051100 (-85.05111 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("must be >= -85.051100 (-85.151100 was provided)")); } SECTION(option + " option is not a double", "should return 1") { - std::string command = test_binary + " " + option + " invalid"; + std::vector argv = {option, "invalid"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("must be a double (invalid was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("must be a double (invalid was provided)")); } SECTION(option + " option is positive with --help", "should return 0") { - std::string command = test_binary + " " + option + " 85.0511 --help"; + std::vector argv = {option, std::to_string(max), "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } SECTION(option + " option is negative with --help", "should return 0") { - std::string command = test_binary + " " + option + " -85.0511 --help"; + std::vector argv = {option, std::to_string(min), "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } SECTION(option + " option is double with --help", "should return 0") { - std::string command = test_binary + " " + option + " 1.23456789 --help"; + std::vector argv = {option, "1.23456789", "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } } @@ -173,68 +132,47 @@ TEST_CASE("renderd_config min/max double lon generator", "min/max double generat double max = 180.000000; SECTION(option + " option is too large", "should return 1") { - std::string command = test_binary + " " + option + " 180.1"; + std::vector argv = {option, std::to_string(max + .1)}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("must be <= 180.000000 (180.1 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("must be <= 180.000000 (180.100000 was provided)")); } SECTION(option + " option is too small", "should return 1") { - std::string command = test_binary + " " + option + " -180.1"; + std::vector argv = {option, std::to_string(min - .1)}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("must be >= -180.000000 (-180.1 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("must be >= -180.000000 (-180.100000 was provided)")); } SECTION(option + " option is not a double", "should return 1") { - std::string command = test_binary + " " + option + " invalid"; + std::vector argv = {option, "invalid"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("must be a double (invalid was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("must be a double (invalid was provided)")); } SECTION(option + " option is positive with --help", "should return 0") { - std::string command = test_binary + " " + option + " 180 --help"; + std::vector argv = {option, std::to_string(max), "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } SECTION(option + " option is negative with --help", "should return 0") { - std::string command = test_binary + " " + option + " -180 --help"; + std::vector argv = {option, std::to_string(min), "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } SECTION(option + " option is double with --help", "should return 0") { - std::string command = test_binary + " " + option + " 1.23456789 --help"; + std::vector argv = {option, "1.23456789", "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } } @@ -246,23 +184,19 @@ TEST_CASE("renderd_config config parser", "specific testing") std::ofstream renderd_conf_file; renderd_conf_file.open(renderd_conf); renderd_conf_file << "[mapnik]\n[renderd]\n"; + for (int i = 0; i <= XMLCONFIGS_MAX; i++) { renderd_conf_file << "[map" + std::to_string(i) + "]\n"; } + renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Can't handle more than " + std::to_string(XMLCONFIGS_MAX) + " map config sections"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Can't handle more than " + std::to_string(XMLCONFIGS_MAX) + " map config sections")); } SECTION("renderd.conf without map sections", "should return 1") { @@ -272,18 +206,12 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[mapnik]\n[renderd]\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("No map config sections were found in file: " + renderd_conf); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("No map config sections were found in file: " + renderd_conf)); } SECTION("renderd.conf without mapnik section", "should return 1") { @@ -293,39 +221,29 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[map]\n[renderd]\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("No mapnik config section was found in file: " + renderd_conf); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("No mapnik config section was found in file: " + renderd_conf)); } SECTION("renderd.conf with invalid renderd sections", "should return 7") { + std::string renderd_conf_renderd_section_name = "renderdinvalid"; + std::string renderd_conf = std::tmpnam(nullptr); std::ofstream renderd_conf_file; renderd_conf_file.open(renderd_conf); - renderd_conf_file << "[mapnik]\n[map]\n[renderdinvalid]\n"; + renderd_conf_file << "[mapnik]\n[map]\n[" + renderd_conf_renderd_section_name + "]\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Invalid renderd section name: renderdinvalid"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Invalid renderd section name: " + renderd_conf_renderd_section_name)); } SECTION("renderd.conf with too many renderd sections", "should return 7") { @@ -333,23 +251,19 @@ TEST_CASE("renderd_config config parser", "specific testing") std::ofstream renderd_conf_file; renderd_conf_file.open(renderd_conf); renderd_conf_file << "[mapnik]\n[map]\n"; + for (int i = 0; i <= MAX_SLAVES; i++) { renderd_conf_file << "[renderd" + std::to_string(i) + "]\n"; } + renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Can't handle more than " + std::to_string(MAX_SLAVES) + " renderd config sections"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Can't handle more than " + std::to_string(MAX_SLAVES) + " renderd config sections")); } SECTION("renderd.conf without renderd sections", "should return 1") { @@ -359,18 +273,12 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[map]\n[mapnik]\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("No renderd config sections were found in file: " + renderd_conf); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("No renderd config sections were found in file: " + renderd_conf)); } SECTION("renderd.conf map section scale too small", "should return 7") { @@ -381,18 +289,12 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[map]\nscale=0.0\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified scale factor (0.000000) is too small, must be greater than or equal to 0.100000."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified scale factor (0.000000) is too small, must be greater than or equal to 0.100000.")); } SECTION("renderd.conf map section scale too large", "should return 7") { @@ -403,18 +305,12 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[map]\nscale=8.1\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified scale factor (8.100000) is too large, must be less than or equal to 8.000000."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified scale factor (8.100000) is too large, must be less than or equal to 8.000000.")); } SECTION("renderd.conf map section maxzoom too small", "should return 7") { @@ -425,18 +321,12 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[map]\nmaxzoom=-1\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified max zoom (-1) is too small, must be greater than or equal to 0."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified max zoom (-1) is too small, must be greater than or equal to 0.")); } SECTION("renderd.conf map section maxzoom too large", "should return 7") { @@ -447,18 +337,12 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[map]\nmaxzoom=" << MAX_ZOOM + 1 << "\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified max zoom (" + std::to_string(MAX_ZOOM + 1) + ") is too large, must be less than or equal to " + std::to_string(MAX_ZOOM) + "."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified max zoom (" + std::to_string(MAX_ZOOM + 1) + ") is too large, must be less than or equal to " + std::to_string(MAX_ZOOM) + ".")); } SECTION("renderd.conf map section minzoom too small", "should return 7") { @@ -469,18 +353,12 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[map]\nminzoom=-1\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified min zoom (-1) is too small, must be greater than or equal to 0."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified min zoom (-1) is too small, must be greater than or equal to 0.")); } SECTION("renderd.conf map section minzoom too large", "should return 7") { @@ -491,62 +369,48 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[map]\nminzoom=" << MAX_ZOOM + 1 << "\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified min zoom (" + std::to_string(MAX_ZOOM + 1) + ") is larger than max zoom (" + std::to_string(MAX_ZOOM) + ")."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified min zoom (" + std::to_string(MAX_ZOOM + 1) + ") is larger than max zoom (" + std::to_string(MAX_ZOOM) + ").")); } SECTION("renderd.conf map section type has too few parts", "should return 7") { + std::string renderd_conf_map_type = "a"; + std::string renderd_conf = std::tmpnam(nullptr); std::ofstream renderd_conf_file; renderd_conf_file.open(renderd_conf); renderd_conf_file << "[mapnik]\n[renderd]\n"; - renderd_conf_file << "[map]\ntype=a\n"; + renderd_conf_file << "[map]\ntype=" + renderd_conf_map_type + "\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified type (a) has too few parts, there must be at least 2, e.g., 'png image/png'."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified type (" + renderd_conf_map_type + ") has too few parts, there must be at least 2, e.g., 'png image/png'.")); } SECTION("renderd.conf map section type has too many parts", "should return 7") { + std::string renderd_conf_map_type = "a b c d"; + std::string renderd_conf = std::tmpnam(nullptr); std::ofstream renderd_conf_file; renderd_conf_file.open(renderd_conf); renderd_conf_file << "[mapnik]\n[renderd]\n"; - renderd_conf_file << "[map]\ntype=a b c d\n"; + renderd_conf_file << "[map]\ntype=" + renderd_conf_map_type + "\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified type (a b c d) has too many parts, there must be no more than 3, e.g., 'png image/png png256'."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified type (" + renderd_conf_map_type + ") has too many parts, there must be no more than 3, e.g., 'png image/png png256'.")); } SECTION("renderd.conf renderd section socketname is too long", "should return 7") { @@ -560,18 +424,12 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[renderd]\nsocketname=" << renderd_socketname << "\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Specified socketname (" + renderd_socketname + ") exceeds maximum allowed length of " + std::to_string(renderd_socketname_maxlen) + "."); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Specified socketname (" + renderd_socketname + ") exceeds maximum allowed length of " + std::to_string(renderd_socketname_maxlen) + ".")); } SECTION("renderd.conf duplicate renderd section names", "should return 7") { @@ -582,17 +440,11 @@ TEST_CASE("renderd_config config parser", "specific testing") renderd_conf_file << "[renderd0]\n[renderd]\n"; renderd_conf_file.close(); - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " " + option; + std::vector argv = {"--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); std::remove(renderd_conf.c_str()); REQUIRE(WEXITSTATUS(status) == 7); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Duplicate renderd config section names for section 0: renderd0 & renderd"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Duplicate renderd config section names for section 0: renderd0 & renderd")); } } diff --git a/tests/renderd_test.cpp b/tests/renderd_test.cpp index 81c910aa..54850a40 100644 --- a/tests/renderd_test.cpp +++ b/tests/renderd_test.cpp @@ -15,11 +15,10 @@ * along with this program; If not, see http://www.gnu.org/licenses/. */ -#include #include #include "catch/catch.hpp" -#include "catch/catch_test_common.hpp" +#include "catch_test_common.hpp" #include "config.h" #ifdef __FreeBSD__ @@ -31,69 +30,46 @@ #endif std::string test_binary = (std::string)PROJECT_BINARY_DIR + "/" + "renderd"; -int found; -std::string err_log_lines; +extern std::string err_log_lines, out_log_lines; TEST_CASE("renderd common", "common testing") { SECTION("invalid long option", "should return 1") { - std::string option = "--doesnotexist"; - std::string command = test_binary + " " + option; + std::vector argv = {"--doesnotexist"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } SECTION("invalid short options", "should return 1") { - std::string option = "-doesnotexist"; - std::string command = test_binary + " " + option; + std::vector argv = {"-doesnotexist"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); } SECTION("--help", "should return 0") { - std::string option = "--help"; - std::string command = test_binary + " " + option; + std::vector argv = {"--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } SECTION("--version", "should show version number") { - std::string option = "--version"; - std::string command = test_binary + " " + option; - - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - std::string output; - char buffer[sizeof(VERSION)]; - fgets(buffer, sizeof(buffer), pipe); - output += buffer; - REQUIRE(output == VERSION); - int status = pclose(pipe); + std::vector argv = {"--version"}; + + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); + REQUIRE(out_log_lines == VERSION); } SECTION("--config invalid file", "should return 1") { std::string renderd_conf = "/path/is/invalid"; - std::string option = "--config " + renderd_conf; - std::string command = test_binary + " --foreground " + option; + std::vector argv = {"--foreground", "--config", renderd_conf}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("Config file '" + renderd_conf + "' does not exist, please specify a valid file"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("Config file '" + renderd_conf + "' does not exist, please specify a valid file")); } } @@ -101,25 +77,18 @@ TEST_CASE("renderd specific", "specific testing") { std::string option = "--slave"; - SECTION("--slave is positive with --help", "should return 0") { - std::string command = test_binary + " --foreground " + option + " 1 --help"; + SECTION(option + " is positive with --help", "should return 0") { + std::vector argv = {"--foreground", option, "1", "--help"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 0); } - SECTION("--slave subceeds minimum of 0", "should return 1") { - std::string command = test_binary + " --foreground " + option + " -1"; + SECTION(option + " subceeds minimum of 0", "should return 1") { + std::vector argv = {"--foreground", option, "-1"}; - // flawfinder: ignore - FILE *pipe = popen(command.c_str(), "r"); - int status = pclose(pipe); + int status = run_command(test_binary, argv); REQUIRE(WEXITSTATUS(status) == 1); - - err_log_lines = read_stderr(); - found = err_log_lines.find("must be >= 0 (-1 was provided)"); - REQUIRE(found > -1); + REQUIRE_THAT(err_log_lines, Catch::Matchers::Contains("must be >= 0 (-1 was provided)")); } }