add option to forward environment to pipestream
[libi2ncommon] / test / test_pipestream.cpp
1 /*
2  *  The software in this package is distributed under the GNU General
3  *  Public License version 2 (with a special exception described below).
4  *
5  *  A copy of GNU General Public License (GPL) is included in this distribution,
6  *  in the file COPYING.GPL.
7  *
8  *  As a special exception, if other files instantiate templates or use macros
9  *  or inline functions from this file, or you compile this file and link it
10  *  with other works to produce a work based on this file, this file
11  *  does not by itself cause the resulting work to be covered
12  *  by the GNU General Public License.
13  *
14  *  However the source code for this file must still be made available
15  *  in accordance with section (3) of the GNU General Public License.
16  *
17  *  This exception does not invalidate any other reasons why a work based
18  *  on this file might be covered by the GNU General Public License.
19  *
20  * @file
21  *
22  * unit tests for the module "pipestream"
23  *
24  * Copyright 2018 by Intra2net AG
25  */
26
27 #include <boost/version.hpp>
28 #if BOOST_VERSION > /* guessed */ 104400
29 /*
30  * Boost overeagerly terminates a unit test when a child exits non-zero
31  * without offering a means of disabling this behavior locally. All we
32  * have is below macro which isn’t even available on older versions of
33  * the unittest runner.
34  */
35 #   define BOOST_TEST_IGNORE_NON_ZERO_CHILD_CODE
36 #else
37 /* Boost too old; skip test that validate error handling. */
38 #   define NO_CHILD_FAIL_TESTS
39 #endif
40
41 #define BOOST_TEST_DYN_LINK
42 #include <boost/test/unit_test.hpp>
43
44 #include "stringfunc.hxx"
45 #include "pipestream.hxx"
46
47 #define TO_CHARP_TOK(x) #x
48 #define TO_CHARP(x) TO_CHARP_TOK(x)
49
50 #define I2N_EXTRA_ENV "I2N_EXTRA_ENV"
51
52 struct Test_Pipestream_Fixture
53 {
54     char **saved_environ;
55
56     Test_Pipestream_Fixture (void) : saved_environ (environ) { }
57
58     ~Test_Pipestream_Fixture (void) {
59         environ = this->saved_environ;
60         (void)unsetenv (I2N_EXTRA_ENV);
61     }
62 };
63
64 BOOST_FIXTURE_TEST_SUITE(pipestream, Test_Pipestream_Fixture)
65
66     BOOST_AUTO_TEST_SUITE(read)
67
68         # define ENOUGH_ZEROS 42
69         const char *const zero_bytes_argv [] =
70                 { "/usr/bin/head", "-c", TO_CHARP(ENOUGH_ZEROS), "/dev/zero", NULL };
71
72         BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok)
73         {
74             const std::string result =
75                     capture_exec (I2n::join_string (zero_bytes_argv, " "));
76
77             BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
78         }
79
80         BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok_result)
81         {
82             ExecResult exres = ExecResult ();
83             const std::string result =
84                     capture_exec (I2n::join_string (zero_bytes_argv, " "),
85                                   exres);
86
87             BOOST_CHECK(exres.normal_exit);
88             BOOST_CHECK_EQUAL(exres.return_code, 0);
89             BOOST_CHECK(!exres.terminated_by_signal);
90             BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
91         }
92
93         BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok)
94         {
95             const std::string result = capture_exec (zero_bytes_argv);
96
97             BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
98         }
99
100         BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_strvec)
101         {
102             std::vector<std::string> argvec;
103             const char *const *argp = zero_bytes_argv;
104             const char *       cur  = NULL;
105
106             while ((cur = *argp++) != NULL) {
107                 argvec.push_back (std::string (cur));
108             }
109
110             const std::string result = capture_exec (argvec);
111
112             BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
113         }
114
115         BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_result)
116         {
117             ExecResult exres = ExecResult ();
118             const std::string result = capture_exec (zero_bytes_argv, exres);
119
120             BOOST_CHECK(exres.normal_exit);
121             BOOST_CHECK_EQUAL(exres.return_code, 0);
122             BOOST_CHECK(!exres.terminated_by_signal);
123             BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
124         }
125
126         const char *const bad_command [] = { "/does_not_exist", NULL };
127
128 # ifndef NO_CHILD_FAIL_TESTS
129         BOOST_AUTO_TEST_CASE(abspath_bad_shell_fail)
130         {
131             assert (access(bad_command [0], X_OK) != 0);
132
133             ExecResult exres = ExecResult ();
134             /*
135              * Note that the next line will make the unit test spew a message
136              * to stderr which cannot be prevented due to the limitations of
137              * popen(3).
138              */
139             const std::string result =
140                     capture_exec (I2n::join_string (bad_command, " "));
141
142             BOOST_CHECK(!exres.normal_exit);
143             BOOST_CHECK_EQUAL(result.size (), 0);
144         }
145 # endif /* [!NO_CHILD_FAIL_TESTS] */
146
147 # ifndef NO_CHILD_FAIL_TESTS
148         BOOST_AUTO_TEST_CASE(abspath_bad_noshell_fail)
149         {
150             assert (access(bad_command [0], X_OK) != 0);
151
152             ExecResult exres = ExecResult ();
153             const std::string result = capture_exec (bad_command, exres);
154
155             BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
156             BOOST_CHECK(!exres.terminated_by_signal);
157             BOOST_CHECK_EQUAL(result.size (), 0);
158             BOOST_CHECK_EQUAL(exres.error_message,
159                               "child failed to exec(): "
160                               "error 2 (No such file or directory)");
161         }
162 # endif /* [!NO_CHILD_FAIL_TESTS] */
163
164 # ifndef NO_CHILD_FAIL_TESTS
165         BOOST_AUTO_TEST_CASE(abspath_bad_noshell_stderr)
166         {
167             assert (access(bad_command [0], X_OK) != 0);
168
169             ExecResult exres = ExecResult ();
170             const std::string result = capture_exec (bad_command, exres, false, true);
171
172             BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
173             BOOST_CHECK(!exres.terminated_by_signal);
174             BOOST_CHECK_EQUAL(result.size (), 0);
175             BOOST_CHECK_EQUAL(exres.error_message,
176                               "child failed to exec(): "
177                               "error 2 (No such file or directory)");
178         }
179 # endif /* [!NO_CHILD_FAIL_TESTS] */
180
181         const char *const false_argv_abs [] = { "/bin/false", NULL };
182         const char *const true_argv_abs  [] = { "/bin/true" , NULL };
183         const char *const false_argv_rel [] = { "false"     , NULL };
184         const char *const true_argv_rel  [] = { "true"      , NULL };
185
186 # ifndef NO_CHILD_FAIL_TESTS
187         BOOST_AUTO_TEST_CASE(abspath_false_noshell_fail_exit)
188         {
189             ExecResult exres = ExecResult ();
190             const std::string result =
191                 capture_exec (false_argv_abs, exres, true, false, false);
192
193             BOOST_CHECK(exres.normal_exit);
194             BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
195             BOOST_CHECK_EQUAL(result.size (), 0);
196         }
197 # endif /* [!NO_CHILD_FAIL_TESTS] */
198
199 # ifndef NO_CHILD_FAIL_TESTS
200         BOOST_AUTO_TEST_CASE(abspath_false_shell_fail_exit)
201         {
202             ExecResult exres = ExecResult ();
203             const std::string result =
204                     capture_exec (std::string (false_argv_abs [0]), exres);
205
206             BOOST_CHECK(exres.normal_exit);
207             BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
208             BOOST_CHECK_EQUAL(result.size (), 0);
209         }
210 # endif /* [!NO_CHILD_FAIL_TESTS] */
211
212         BOOST_AUTO_TEST_CASE(relpath_true_noshell_ok)
213         {
214             ExecResult exres = ExecResult ();
215             const std::string result =
216                 capture_exec (true_argv_rel, exres, true, false, true);
217
218             BOOST_CHECK(exres.normal_exit);
219             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
220             BOOST_CHECK_EQUAL(result.size (), 0);
221         }
222
223 # ifndef NO_CHILD_FAIL_TESTS
224         BOOST_AUTO_TEST_CASE(relpath_true_noshell_fail)
225         {
226             ExecResult exres = ExecResult ();
227             const std::string result =
228                 capture_exec (true_argv_rel, exres, true, false, false);
229
230             BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
231             /* no return code check since we couldn't exit */
232             BOOST_CHECK_EQUAL(result.size (), 0);
233             BOOST_CHECK_EQUAL(exres.error_message,
234                               "child failed to exec(): "
235                               "error 2 (No such file or directory)");
236         }
237 # endif /* [!NO_CHILD_FAIL_TESTS] */
238
239         BOOST_AUTO_TEST_CASE(abspath_true_noshell_ok)
240         {
241             ExecResult exres = ExecResult ();
242             const std::string result =
243                 capture_exec (true_argv_abs, exres, true, false, true);
244
245             BOOST_CHECK(exres.normal_exit);
246             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
247             BOOST_CHECK_EQUAL(result.size (), 0);
248         }
249
250 # ifndef NO_CHILD_FAIL_TESTS
251         BOOST_AUTO_TEST_CASE(relpath_false_noshell_fail)
252         {
253             ExecResult exres = ExecResult ();
254             const std::string result =
255                 capture_exec (false_argv_rel, exres, true, false, true);
256
257             BOOST_CHECK(exres.normal_exit);
258             /* no return code check since we couldn't exit */
259             BOOST_CHECK_EQUAL(result.size (), 0);
260         }
261 # endif /* [!NO_CHILD_FAIL_TESTS] */
262
263         const char *const echo_abs = "/bin/echo";
264         const char *const echo_rel = "echo";
265
266         static std::vector<std::string>
267         mk_echo_argv (const std::string &text, const bool absolute=true)
268         {
269             std::vector<std::string> ret;
270
271             ret.push_back (absolute ? echo_abs : echo_rel);
272             ret.push_back ("-n");
273             ret.push_back (text);
274
275             return ret;
276         }
277
278         BOOST_AUTO_TEST_CASE(abspath_echo_noshell_capture_ok)
279         {
280             ExecResult exres = ExecResult ();
281             const std::string text = "The significant owl hoots in the night.";
282             const std::vector<std::string> argv = mk_echo_argv (text);
283             const std::string result = capture_exec (argv, exres, true, false, true);
284
285             BOOST_CHECK(exres.normal_exit);
286             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
287             BOOST_CHECK_EQUAL(result, text);
288         }
289
290         BOOST_AUTO_TEST_CASE(relpath_echo_noshell_capture_ok)
291         {
292             ExecResult exres = ExecResult ();
293             const std::string text = "Yet many grey lords go sadly to the masterless men.";
294             const std::vector<std::string> argv = mk_echo_argv (text, false);
295             const std::string result = capture_exec (argv, exres, true, false, true);
296
297             BOOST_CHECK(exres.normal_exit);
298             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
299             BOOST_CHECK_EQUAL(result, text);
300         }
301
302         static std::vector<std::string>
303         mk_errcho_argv (const std::string &text)
304         {
305             /*
306              * Hack cause there’s no way to make echo print to stderr without
307              * redirection.
308              */
309             std::vector<std::string> ret;
310
311             ret.push_back ("/bin/sh");
312             ret.push_back ("-c");
313             ret.push_back (std::string ("1>&- 1>&2 echo -n '") + text + "'"); /* brr */
314
315             return ret;
316         }
317
318         BOOST_AUTO_TEST_CASE(sh_errcho_capture_ok)
319         {
320             ExecResult exres = ExecResult ();
321             const std::string text = "Hooray, hooray for the spinster’s sister’s daughter.";
322             const std::vector<std::string> argv = mk_errcho_argv (text);
323             const std::string result = capture_exec (argv, exres, false, true, true);
324
325             BOOST_CHECK(exres.normal_exit);
326             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
327             BOOST_CHECK_EQUAL(result, text);
328         }
329
330         BOOST_AUTO_TEST_CASE(sh_errcho_stdout_empty_ok)
331         {
332             ExecResult exres = ExecResult ();
333             const std::string text = "To the axeman, all supplicants are the same height.";
334             const std::vector<std::string> argv = mk_errcho_argv (text);
335             const std::string result = capture_exec (argv, exres, true, false, true);
336
337             BOOST_CHECK(exres.normal_exit);
338             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
339             BOOST_CHECK_EQUAL(result.size (), 0);
340         }
341
342     BOOST_AUTO_TEST_SUITE_END() /* [pipestream->read] */
343
344     BOOST_AUTO_TEST_SUITE(env)
345
346         static const char *const env_argv_abs [] = { "/usr/bin/env", NULL };
347
348         #define I2N_EXTRA_ENVIRON I2N_EXTRA_ENV "=Yet verily, the rose is within the thorn."
349         static char *i2n_extra_environ [] =
350             { const_cast<char*> (I2N_EXTRA_ENVIRON)
351             , NULL
352             };
353
354         BOOST_AUTO_TEST_CASE(env_passthrough)
355         {
356             ExecResult exres = ExecResult ();
357             environ = i2n_extra_environ;
358
359             const std::string result =
360                 capture_exec (env_argv_abs, exres, true, true, false, true);
361
362             BOOST_CHECK(exres.normal_exit);
363             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
364             BOOST_CHECK_EQUAL(result, (std::string)(I2N_EXTRA_ENVIRON "\n"));
365         }
366
367         BOOST_AUTO_TEST_CASE(env_nil)
368         {
369             ExecResult exres = ExecResult ();
370             environ = i2n_extra_environ;
371
372             const std::string result =
373                 capture_exec (env_argv_abs, exres, true, true, false, false);
374
375             BOOST_CHECK(exres.normal_exit);
376             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
377             BOOST_CHECK_EQUAL(result, std::string());
378         }
379
380     BOOST_AUTO_TEST_SUITE_END() /* [pipestream->env] */
381
382 BOOST_AUTO_TEST_SUITE_END() /* [pipestream] */
383