/* * The software in this package is distributed under the GNU General * Public License version 2 (with a special exception described below). * * A copy of GNU General Public License (GPL) is included in this distribution, * in the file COPYING.GPL. * * As a special exception, if other files instantiate templates or use macros * or inline functions from this file, or you compile this file and link it * with other works to produce a work based on this file, this file * does not by itself cause the resulting work to be covered * by the GNU General Public License. * * However the source code for this file must still be made available * in accordance with section (3) of the GNU General Public License. * * This exception does not invalidate any other reasons why a work based * on this file might be covered by the GNU General Public License. * * @file * * unit tests for the module "pipestream" * * Copyright 2018 by Intra2net AG */ #include #if BOOST_VERSION > /* guessed */ 104400 /* * Boost overeagerly terminates a unit test when a child exits non-zero * without offering a means of disabling this behavior locally. All we * have is below macro which isn’t even available on older versions of * the unittest runner. */ # define BOOST_TEST_IGNORE_NON_ZERO_CHILD_CODE #else /* Boost too old; skip test that validate error handling. */ # define NO_CHILD_FAIL_TESTS #endif #define BOOST_TEST_DYN_LINK #include #include "stringfunc.hxx" #include "pipestream.hxx" #define TO_CHARP_TOK(x) #x #define TO_CHARP(x) TO_CHARP_TOK(x) #define I2N_EXTRA_ENV "I2N_EXTRA_ENV" struct Test_Pipestream_Fixture { char **saved_environ; Test_Pipestream_Fixture (void) : saved_environ (environ) { } ~Test_Pipestream_Fixture (void) { environ = this->saved_environ; (void)unsetenv (I2N_EXTRA_ENV); } }; BOOST_FIXTURE_TEST_SUITE(pipestream, Test_Pipestream_Fixture) BOOST_AUTO_TEST_SUITE(read) # define ENOUGH_ZEROS 42 const char *const zero_bytes_argv [] = { "/usr/bin/head", "-c", TO_CHARP(ENOUGH_ZEROS), "/dev/zero", NULL }; BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok) { const std::string result = capture_exec (I2n::join_string (zero_bytes_argv, " ")); BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS); } BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok_result) { ExecResult exres = ExecResult (); const std::string result = capture_exec (I2n::join_string (zero_bytes_argv, " "), exres); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, 0); BOOST_CHECK(!exres.terminated_by_signal); BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS); } BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok) { const std::string result = capture_exec (zero_bytes_argv); BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS); } BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_strvec) { std::vector argvec; const char *const *argp = zero_bytes_argv; const char * cur = NULL; while ((cur = *argp++) != NULL) { argvec.push_back (std::string (cur)); } const std::string result = capture_exec (argvec); BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS); } BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_result) { ExecResult exres = ExecResult (); const std::string result = capture_exec (zero_bytes_argv, exres); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, 0); BOOST_CHECK(!exres.terminated_by_signal); BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS); } const char *const bad_command [] = { "/does_not_exist", NULL }; # ifndef NO_CHILD_FAIL_TESTS BOOST_AUTO_TEST_CASE(abspath_bad_shell_fail) { assert (access(bad_command [0], X_OK) != 0); ExecResult exres = ExecResult (); /* * Note that the next line will make the unit test spew a message * to stderr which cannot be prevented due to the limitations of * popen(3). */ const std::string result = capture_exec (I2n::join_string (bad_command, " ")); BOOST_CHECK(!exres.normal_exit); BOOST_CHECK_EQUAL(result.size (), 0); } # endif /* [!NO_CHILD_FAIL_TESTS] */ # ifndef NO_CHILD_FAIL_TESTS BOOST_AUTO_TEST_CASE(abspath_bad_noshell_fail) { assert (access(bad_command [0], X_OK) != 0); ExecResult exres = ExecResult (); const std::string result = capture_exec (bad_command, exres); BOOST_CHECK(!exres.normal_exit); /* failed to exec() */ BOOST_CHECK(!exres.terminated_by_signal); BOOST_CHECK_EQUAL(result.size (), 0); BOOST_CHECK_EQUAL(exres.error_message, "child failed to exec(): " "error 2 (No such file or directory)"); } # endif /* [!NO_CHILD_FAIL_TESTS] */ # ifndef NO_CHILD_FAIL_TESTS BOOST_AUTO_TEST_CASE(abspath_bad_noshell_stderr) { assert (access(bad_command [0], X_OK) != 0); ExecResult exres = ExecResult (); const std::string result = capture_exec (bad_command, exres, capture_flag::collect_err); BOOST_CHECK(!exres.normal_exit); /* failed to exec() */ BOOST_CHECK(!exres.terminated_by_signal); BOOST_CHECK_EQUAL(result.size (), 0); BOOST_CHECK_EQUAL(exres.error_message, "child failed to exec(): " "error 2 (No such file or directory)"); } # endif /* [!NO_CHILD_FAIL_TESTS] */ const char *const false_argv_abs [] = { "/bin/false", NULL }; const char *const true_argv_abs [] = { "/bin/true" , NULL }; const char *const false_argv_rel [] = { "false" , NULL }; const char *const true_argv_rel [] = { "true" , NULL }; # ifndef NO_CHILD_FAIL_TESTS BOOST_AUTO_TEST_CASE(abspath_false_noshell_fail_exit) { ExecResult exres = ExecResult (); const std::string result = capture_exec (false_argv_abs, exres, capture_flag::collect_out); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE); BOOST_CHECK_EQUAL(result.size (), 0); } # endif /* [!NO_CHILD_FAIL_TESTS] */ # ifndef NO_CHILD_FAIL_TESTS BOOST_AUTO_TEST_CASE(abspath_false_shell_fail_exit) { ExecResult exres = ExecResult (); const std::string result = capture_exec (std::string (false_argv_abs [0]), exres); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE); BOOST_CHECK_EQUAL(result.size (), 0); } # endif /* [!NO_CHILD_FAIL_TESTS] */ BOOST_AUTO_TEST_CASE(relpath_true_noshell_ok) { ExecResult exres = ExecResult (); const std::string result = capture_exec (true_argv_rel, exres, capture_flag::collect_out | capture_flag::search_path); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS); BOOST_CHECK_EQUAL(result.size (), 0); } # ifndef NO_CHILD_FAIL_TESTS BOOST_AUTO_TEST_CASE(relpath_true_noshell_fail) { ExecResult exres = ExecResult (); const std::string result = capture_exec (true_argv_rel, exres, capture_flag::collect_out); BOOST_CHECK(!exres.normal_exit); /* failed to exec() */ /* no return code check since we couldn't exit */ BOOST_CHECK_EQUAL(result.size (), 0); BOOST_CHECK_EQUAL(exres.error_message, "child failed to exec(): " "error 2 (No such file or directory)"); } # endif /* [!NO_CHILD_FAIL_TESTS] */ BOOST_AUTO_TEST_CASE(abspath_true_noshell_ok) { ExecResult exres = ExecResult (); const std::string result = capture_exec (true_argv_abs, exres, capture_flag::collect_out | capture_flag::search_path); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS); BOOST_CHECK_EQUAL(result.size (), 0); } # ifndef NO_CHILD_FAIL_TESTS BOOST_AUTO_TEST_CASE(relpath_false_noshell_fail) { ExecResult exres = ExecResult (); const std::string result = capture_exec (false_argv_rel, exres, capture_flag::collect_out | capture_flag::search_path); BOOST_CHECK(exres.normal_exit); /* no return code check since we couldn't exit */ BOOST_CHECK_EQUAL(result.size (), 0); } # endif /* [!NO_CHILD_FAIL_TESTS] */ const char *const echo_abs = "/bin/echo"; const char *const echo_rel = "echo"; static std::vector mk_echo_argv (const std::string &text, const bool absolute=true) { std::vector ret; ret.push_back (absolute ? echo_abs : echo_rel); ret.push_back ("-n"); ret.push_back (text); return ret; } BOOST_AUTO_TEST_CASE(abspath_echo_noshell_capture_ok) { ExecResult exres = ExecResult (); const std::string text = "The significant owl hoots in the night."; const std::vector argv = mk_echo_argv (text); const std::string result = capture_exec (argv, exres, capture_flag::collect_out | capture_flag::search_path); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS); BOOST_CHECK_EQUAL(result, text); } BOOST_AUTO_TEST_CASE(relpath_echo_noshell_capture_ok) { ExecResult exres = ExecResult (); const std::string text = "Yet many grey lords go sadly to the masterless men."; const std::vector argv = mk_echo_argv (text, false); const std::string result = capture_exec (argv, exres, capture_flag::collect_out | capture_flag::search_path); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS); BOOST_CHECK_EQUAL(result, text); } static std::vector mk_errcho_argv (const std::string &text) { /* * Hack cause there’s no way to make echo print to stderr without * redirection. */ std::vector ret; ret.push_back ("/bin/sh"); ret.push_back ("-c"); ret.push_back (std::string ("1>&- 1>&2 echo -n '") + text + "'"); /* brr */ return ret; } BOOST_AUTO_TEST_CASE(sh_errcho_capture_ok) { ExecResult exres = ExecResult (); const std::string text = "Hooray, hooray for the spinster’s sister’s daughter."; const std::vector argv = mk_errcho_argv (text); const std::string result = capture_exec (argv, exres, capture_flag::collect_err | capture_flag::search_path); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS); BOOST_CHECK_EQUAL(result, text); } BOOST_AUTO_TEST_CASE(sh_errcho_stdout_empty_ok) { ExecResult exres = ExecResult (); const std::string text = "To the axeman, all supplicants are the same height."; const std::vector argv = mk_errcho_argv (text); const std::string result = capture_exec (argv, exres, capture_flag::collect_out | capture_flag::search_path); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS); BOOST_CHECK_EQUAL(result.size (), 0); } BOOST_AUTO_TEST_SUITE_END() /* [pipestream->read] */ BOOST_AUTO_TEST_SUITE(env) static const char *const env_argv_abs [] = { "/usr/bin/env", NULL }; #define I2N_EXTRA_ENVIRON I2N_EXTRA_ENV "=Yet verily, the rose is within the thorn." static char *i2n_extra_environ [] = { const_cast (I2N_EXTRA_ENVIRON) , NULL }; BOOST_AUTO_TEST_CASE(env_passthrough) { ExecResult exres = ExecResult (); environ = i2n_extra_environ; const std::string result = capture_exec (env_argv_abs, exres, capture_flag::collect_out | capture_flag::collect_err | capture_flag::env_passthru); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS); BOOST_CHECK_EQUAL(result, (std::string)(I2N_EXTRA_ENVIRON "\n")); } BOOST_AUTO_TEST_CASE(env_nil) { ExecResult exres = ExecResult (); environ = i2n_extra_environ; const std::string result = capture_exec (env_argv_abs, exres, capture_flag::collect_out | capture_flag::collect_err); BOOST_CHECK(exres.normal_exit); BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS); BOOST_CHECK_EQUAL(result, std::string()); } BOOST_AUTO_TEST_SUITE_END() /* [pipestream->env] */ BOOST_AUTO_TEST_SUITE_END() /* [pipestream] */