pass pipestream flags as bitset
[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,
171                                                      capture_flag::collect_err);
172
173             BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
174             BOOST_CHECK(!exres.terminated_by_signal);
175             BOOST_CHECK_EQUAL(result.size (), 0);
176             BOOST_CHECK_EQUAL(exres.error_message,
177                               "child failed to exec(): "
178                               "error 2 (No such file or directory)");
179         }
180 # endif /* [!NO_CHILD_FAIL_TESTS] */
181
182         const char *const false_argv_abs [] = { "/bin/false", NULL };
183         const char *const true_argv_abs  [] = { "/bin/true" , NULL };
184         const char *const false_argv_rel [] = { "false"     , NULL };
185         const char *const true_argv_rel  [] = { "true"      , NULL };
186
187 # ifndef NO_CHILD_FAIL_TESTS
188         BOOST_AUTO_TEST_CASE(abspath_false_noshell_fail_exit)
189         {
190             ExecResult exres = ExecResult ();
191             const std::string result =
192                 capture_exec (false_argv_abs, exres, capture_flag::collect_out);
193
194             BOOST_CHECK(exres.normal_exit);
195             BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
196             BOOST_CHECK_EQUAL(result.size (), 0);
197         }
198 # endif /* [!NO_CHILD_FAIL_TESTS] */
199
200 # ifndef NO_CHILD_FAIL_TESTS
201         BOOST_AUTO_TEST_CASE(abspath_false_shell_fail_exit)
202         {
203             ExecResult exres = ExecResult ();
204             const std::string result =
205                     capture_exec (std::string (false_argv_abs [0]), exres);
206
207             BOOST_CHECK(exres.normal_exit);
208             BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
209             BOOST_CHECK_EQUAL(result.size (), 0);
210         }
211 # endif /* [!NO_CHILD_FAIL_TESTS] */
212
213         BOOST_AUTO_TEST_CASE(relpath_true_noshell_ok)
214         {
215             ExecResult exres = ExecResult ();
216             const std::string result =
217                 capture_exec (true_argv_rel, exres,
218                               capture_flag::collect_out | capture_flag::search_path);
219
220             BOOST_CHECK(exres.normal_exit);
221             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
222             BOOST_CHECK_EQUAL(result.size (), 0);
223         }
224
225 # ifndef NO_CHILD_FAIL_TESTS
226         BOOST_AUTO_TEST_CASE(relpath_true_noshell_fail)
227         {
228             ExecResult exres = ExecResult ();
229             const std::string result =
230                 capture_exec (true_argv_rel, exres, capture_flag::collect_out);
231
232             BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
233             /* no return code check since we couldn't exit */
234             BOOST_CHECK_EQUAL(result.size (), 0);
235             BOOST_CHECK_EQUAL(exres.error_message,
236                               "child failed to exec(): "
237                               "error 2 (No such file or directory)");
238         }
239 # endif /* [!NO_CHILD_FAIL_TESTS] */
240
241         BOOST_AUTO_TEST_CASE(abspath_true_noshell_ok)
242         {
243             ExecResult exres = ExecResult ();
244             const std::string result =
245                 capture_exec (true_argv_abs, exres,
246                               capture_flag::collect_out | capture_flag::search_path);
247
248             BOOST_CHECK(exres.normal_exit);
249             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
250             BOOST_CHECK_EQUAL(result.size (), 0);
251         }
252
253 # ifndef NO_CHILD_FAIL_TESTS
254         BOOST_AUTO_TEST_CASE(relpath_false_noshell_fail)
255         {
256             ExecResult exres = ExecResult ();
257             const std::string result =
258                 capture_exec (false_argv_rel, exres,
259                               capture_flag::collect_out | capture_flag::search_path);
260
261             BOOST_CHECK(exres.normal_exit);
262             /* no return code check since we couldn't exit */
263             BOOST_CHECK_EQUAL(result.size (), 0);
264         }
265 # endif /* [!NO_CHILD_FAIL_TESTS] */
266
267         const char *const echo_abs = "/bin/echo";
268         const char *const echo_rel = "echo";
269
270         static std::vector<std::string>
271         mk_echo_argv (const std::string &text, const bool absolute=true)
272         {
273             std::vector<std::string> ret;
274
275             ret.push_back (absolute ? echo_abs : echo_rel);
276             ret.push_back ("-n");
277             ret.push_back (text);
278
279             return ret;
280         }
281
282         BOOST_AUTO_TEST_CASE(abspath_echo_noshell_capture_ok)
283         {
284             ExecResult exres = ExecResult ();
285             const std::string text = "The significant owl hoots in the night.";
286             const std::vector<std::string> argv = mk_echo_argv (text);
287             const std::string result = capture_exec (argv, exres,
288                                                        capture_flag::collect_out
289                                                      | capture_flag::search_path);
290
291             BOOST_CHECK(exres.normal_exit);
292             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
293             BOOST_CHECK_EQUAL(result, text);
294         }
295
296         BOOST_AUTO_TEST_CASE(relpath_echo_noshell_capture_ok)
297         {
298             ExecResult exres = ExecResult ();
299             const std::string text = "Yet many grey lords go sadly to the masterless men.";
300             const std::vector<std::string> argv = mk_echo_argv (text, false);
301             const std::string result = capture_exec (argv, exres,
302                                                        capture_flag::collect_out
303                                                      | capture_flag::search_path);
304
305             BOOST_CHECK(exres.normal_exit);
306             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
307             BOOST_CHECK_EQUAL(result, text);
308         }
309
310         static std::vector<std::string>
311         mk_errcho_argv (const std::string &text)
312         {
313             /*
314              * Hack cause there’s no way to make echo print to stderr without
315              * redirection.
316              */
317             std::vector<std::string> ret;
318
319             ret.push_back ("/bin/sh");
320             ret.push_back ("-c");
321             ret.push_back (std::string ("1>&- 1>&2 echo -n '") + text + "'"); /* brr */
322
323             return ret;
324         }
325
326         BOOST_AUTO_TEST_CASE(sh_errcho_capture_ok)
327         {
328             ExecResult exres = ExecResult ();
329             const std::string text = "Hooray, hooray for the spinster’s sister’s daughter.";
330             const std::vector<std::string> argv = mk_errcho_argv (text);
331             const std::string result = capture_exec (argv, exres,
332                                                        capture_flag::collect_err
333                                                      | capture_flag::search_path);
334
335             BOOST_CHECK(exres.normal_exit);
336             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
337             BOOST_CHECK_EQUAL(result, text);
338         }
339
340         BOOST_AUTO_TEST_CASE(sh_errcho_stdout_empty_ok)
341         {
342             ExecResult exres = ExecResult ();
343             const std::string text = "To the axeman, all supplicants are the same height.";
344             const std::vector<std::string> argv = mk_errcho_argv (text);
345             const std::string result = capture_exec (argv, exres,
346                                                        capture_flag::collect_out
347                                                      | capture_flag::search_path);
348
349             BOOST_CHECK(exres.normal_exit);
350             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
351             BOOST_CHECK_EQUAL(result.size (), 0);
352         }
353
354     BOOST_AUTO_TEST_SUITE_END() /* [pipestream->read] */
355
356     BOOST_AUTO_TEST_SUITE(env)
357
358         static const char *const env_argv_abs [] = { "/usr/bin/env", NULL };
359
360         #define I2N_EXTRA_ENVIRON I2N_EXTRA_ENV "=Yet verily, the rose is within the thorn."
361         static char *i2n_extra_environ [] =
362             { const_cast<char*> (I2N_EXTRA_ENVIRON)
363             , NULL
364             };
365
366         BOOST_AUTO_TEST_CASE(env_passthrough)
367         {
368             ExecResult exres = ExecResult ();
369             environ = i2n_extra_environ;
370
371             const std::string result =
372                 capture_exec (env_argv_abs, exres,
373                                 capture_flag::collect_out
374                               | capture_flag::collect_err
375                               | capture_flag::env_passthru);
376
377             BOOST_CHECK(exres.normal_exit);
378             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
379             BOOST_CHECK_EQUAL(result, (std::string)(I2N_EXTRA_ENVIRON "\n"));
380         }
381
382         BOOST_AUTO_TEST_CASE(env_nil)
383         {
384             ExecResult exres = ExecResult ();
385             environ = i2n_extra_environ;
386
387             const std::string result =
388                 capture_exec (env_argv_abs, exres,
389                                 capture_flag::collect_out
390                               | capture_flag::collect_err);
391
392             BOOST_CHECK(exres.normal_exit);
393             BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
394             BOOST_CHECK_EQUAL(result, std::string());
395         }
396
397     BOOST_AUTO_TEST_SUITE_END() /* [pipestream->env] */
398
399 BOOST_AUTO_TEST_SUITE_END() /* [pipestream] */
400