skip unit tests for handling child errors in pipestream with ancient boost
[libi2ncommon] / test / test_pipestream.cpp
index cf806c4..e145ab7 100644 (file)
  * Copyright 2018 by Intra2net AG
  */
 
+#include <boost/version.hpp>
+#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 <boost/test/unit_test.hpp>
@@ -52,7 +65,7 @@ BOOST_AUTO_TEST_SUITE(pipestream)
 
         BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok_result)
         {
-            ExecResult exres;
+            ExecResult exres = ExecResult ();
             const std::string result =
                     capture_exec (I2n::join_string (zero_bytes_argv, " "),
                                   exres);
@@ -87,7 +100,7 @@ BOOST_AUTO_TEST_SUITE(pipestream)
 
         BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_result)
         {
-            ExecResult exres;
+            ExecResult exres = ExecResult ();
             const std::string result = capture_exec (zero_bytes_argv, exres);
 
             BOOST_CHECK(exres.normal_exit);
@@ -96,6 +109,222 @@ BOOST_AUTO_TEST_SUITE(pipestream)
             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, false, true);
+
+            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, true, false, false);
+
+            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, true, false, true);
+
+            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, true, false, false);
+
+            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, true, false, true);
+
+            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, true, false, true);
+
+            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<std::string>
+        mk_echo_argv (const std::string &text, const bool absolute=true)
+        {
+            std::vector<std::string> 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<std::string> argv = mk_echo_argv (text);
+            const std::string result = capture_exec (argv, exres, true, false, true);
+
+            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<std::string> argv = mk_echo_argv (text, false);
+            const std::string result = capture_exec (argv, exres, true, false, true);
+
+            BOOST_CHECK(exres.normal_exit);
+            BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+            BOOST_CHECK_EQUAL(result, text);
+        }
+
+        static std::vector<std::string>
+        mk_errcho_argv (const std::string &text)
+        {
+            /*
+             * Hack cause there’s no way to make echo print to stderr without
+             * redirection.
+             */
+            std::vector<std::string> 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<std::string> argv = mk_errcho_argv (text);
+            const std::string result = capture_exec (argv, exres, false, true, true);
+
+            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<std::string> argv = mk_errcho_argv (text);
+            const std::string result = capture_exec (argv, exres, true, false, true);
+
+            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_END() /* [pipestream] */