974f1a452b188706affe638b5142754ab2d8d318
[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, " "), exres);
85
86     BOOST_CHECK(exres.normal_exit);
87     BOOST_CHECK_EQUAL(exres.return_code, 0);
88     BOOST_CHECK(!exres.terminated_by_signal);
89     BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
90 }
91
92 BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok)
93 {
94     const std::string result = capture_exec (zero_bytes_argv);
95
96     BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
97 }
98
99 BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_strvec)
100 {
101     std::vector<std::string> argvec;
102     const char *const *argp = zero_bytes_argv;
103     const char *       cur  = NULL;
104
105     while ((cur = *argp++) != NULL) {
106         argvec.push_back (std::string (cur));
107     }
108
109     const std::string result = capture_exec (argvec);
110
111     BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
112 }
113
114 BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_result)
115 {
116     ExecResult exres = ExecResult ();
117     const std::string result = capture_exec (zero_bytes_argv, exres);
118
119     BOOST_CHECK(exres.normal_exit);
120     BOOST_CHECK_EQUAL(exres.return_code, 0);
121     BOOST_CHECK(!exres.terminated_by_signal);
122     BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
123 }
124
125 const char *const bad_command [] = { "/does_not_exist", NULL };
126
127 # ifndef NO_CHILD_FAIL_TESTS
128 BOOST_AUTO_TEST_CASE(abspath_bad_shell_fail)
129 {
130     assert (access(bad_command [0], X_OK) != 0);
131
132     ExecResult exres = ExecResult ();
133     /*
134      * Note that the next line will make the unit test spew a message
135      * to stderr which cannot be prevented due to the limitations of
136      * popen(3).
137      */
138     const std::string result =
139         capture_exec (I2n::join_string (bad_command, " "));
140
141     BOOST_CHECK(!exres.normal_exit);
142     BOOST_CHECK_EQUAL(result.size (), 0);
143 }
144 # endif /* [!NO_CHILD_FAIL_TESTS] */
145
146 # ifndef NO_CHILD_FAIL_TESTS
147 BOOST_AUTO_TEST_CASE(abspath_bad_noshell_fail)
148 {
149     assert (access(bad_command [0], X_OK) != 0);
150
151     ExecResult exres = ExecResult ();
152     const std::string result = capture_exec (bad_command, exres);
153
154     BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
155     BOOST_CHECK(!exres.terminated_by_signal);
156     BOOST_CHECK_EQUAL(result.size (), 0);
157     BOOST_CHECK_EQUAL(exres.error_message,
158                       "child failed to exec(): "
159                       "error 2 (No such file or directory)");
160 }
161 # endif /* [!NO_CHILD_FAIL_TESTS] */
162
163 # ifndef NO_CHILD_FAIL_TESTS
164 BOOST_AUTO_TEST_CASE(abspath_bad_noshell_stderr)
165 {
166     assert (access(bad_command [0], X_OK) != 0);
167
168     ExecResult exres = ExecResult ();
169     const std::string result = capture_exec (bad_command, exres,
170                                              capture_flag::collect_err);
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, capture_flag::collect_out);
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,
217                       capture_flag::collect_out | capture_flag::search_path);
218
219     BOOST_CHECK(exres.normal_exit);
220     BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
221     BOOST_CHECK_EQUAL(result.size (), 0);
222 }
223
224 # ifndef NO_CHILD_FAIL_TESTS
225 BOOST_AUTO_TEST_CASE(relpath_true_noshell_fail)
226 {
227     ExecResult exres = ExecResult ();
228     const std::string result =
229         capture_exec (true_argv_rel, exres, capture_flag::collect_out);
230
231     BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
232     /* no return code check since we couldn't exit */
233     BOOST_CHECK_EQUAL(result.size (), 0);
234     BOOST_CHECK_EQUAL(exres.error_message,
235                       "child failed to exec(): "
236                       "error 2 (No such file or directory)");
237 }
238 # endif /* [!NO_CHILD_FAIL_TESTS] */
239
240 BOOST_AUTO_TEST_CASE(abspath_true_noshell_ok)
241 {
242     ExecResult exres = ExecResult ();
243     const std::string result =
244         capture_exec (true_argv_abs, exres,
245                       capture_flag::collect_out | capture_flag::search_path);
246
247     BOOST_CHECK(exres.normal_exit);
248     BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
249     BOOST_CHECK_EQUAL(result.size (), 0);
250 }
251
252 # ifndef NO_CHILD_FAIL_TESTS
253 BOOST_AUTO_TEST_CASE(relpath_false_noshell_fail)
254 {
255     ExecResult exres = ExecResult ();
256     const std::string result =
257         capture_exec (false_argv_rel, exres,
258                       capture_flag::collect_out | capture_flag::search_path);
259
260     BOOST_CHECK(exres.normal_exit);
261     /* no return code check since we couldn't exit */
262     BOOST_CHECK_EQUAL(result.size (), 0);
263 }
264 # endif /* [!NO_CHILD_FAIL_TESTS] */
265
266 const char *const echo_abs = "/bin/echo";
267 const char *const echo_rel = "echo";
268
269 static std::vector<std::string>
270 mk_echo_argv (const std::string &text, const bool absolute=true)
271 {
272     std::vector<std::string> ret;
273
274     ret.push_back (absolute ? echo_abs : echo_rel);
275     ret.push_back ("-n");
276     ret.push_back (text);
277
278     return ret;
279 }
280
281 BOOST_AUTO_TEST_CASE(abspath_echo_noshell_capture_ok)
282 {
283     ExecResult exres = ExecResult ();
284     const std::string text = "The significant owl hoots in the night.";
285     const std::vector<std::string> argv = mk_echo_argv (text);
286     const std::string result = capture_exec (argv, exres,
287                                              capture_flag::collect_out
288                                              | capture_flag::search_path);
289
290     BOOST_CHECK(exres.normal_exit);
291     BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
292     BOOST_CHECK_EQUAL(result, text);
293 }
294
295 BOOST_AUTO_TEST_CASE(relpath_echo_noshell_capture_ok)
296 {
297     ExecResult exres = ExecResult ();
298     const std::string text = "Yet many grey lords go sadly to the masterless men.";
299     const std::vector<std::string> argv = mk_echo_argv (text, false);
300     const std::string result = capture_exec (argv, exres,
301                                              capture_flag::collect_out
302                                              | capture_flag::search_path);
303
304     BOOST_CHECK(exres.normal_exit);
305     BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
306     BOOST_CHECK_EQUAL(result, text);
307 }
308
309 static std::vector<std::string>
310 mk_errcho_argv (const std::string &text)
311 {
312     /*
313      * Hack cause there’s no way to make echo print to stderr without
314      * redirection.
315      */
316     std::vector<std::string> ret;
317
318     ret.push_back ("/bin/sh");
319     ret.push_back ("-c");
320     ret.push_back (std::string ("1>&- 1>&2 echo -n '") + text + "'"); /* brr */
321
322     return ret;
323 }
324
325 BOOST_AUTO_TEST_CASE(sh_errcho_capture_ok)
326 {
327     ExecResult exres = ExecResult ();
328     const std::string text = "Hooray, hooray for the spinster’s sister’s daughter.";
329     const std::vector<std::string> argv = mk_errcho_argv (text);
330     const std::string result = capture_exec (argv, exres,
331                                                capture_flag::collect_err
332                                              | capture_flag::search_path);
333
334     BOOST_CHECK(exres.normal_exit);
335     BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
336     BOOST_CHECK_EQUAL(result, text);
337 }
338
339 BOOST_AUTO_TEST_CASE(sh_errcho_stdout_empty_ok)
340 {
341     ExecResult exres = ExecResult ();
342     const std::string text = "To the axeman, all supplicants are the same height.";
343     const std::vector<std::string> argv = mk_errcho_argv (text);
344     const std::string result = capture_exec (argv, exres,
345                                                capture_flag::collect_out
346                                              | capture_flag::search_path);
347
348     BOOST_CHECK(exres.normal_exit);
349     BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
350     BOOST_CHECK_EQUAL(result.size (), 0);
351 }
352
353 BOOST_AUTO_TEST_SUITE_END() /* [pipestream->read] */
354
355 BOOST_AUTO_TEST_SUITE(env)
356
357 static const char *const env_argv_abs [] = { "/usr/bin/env", NULL };
358
359 #define I2N_EXTRA_ENVIRON I2N_EXTRA_ENV "=Yet verily, the rose is within the thorn."
360 static char *i2n_extra_environ [] =
361     { const_cast<char*> (I2N_EXTRA_ENVIRON) , NULL };
362
363 BOOST_AUTO_TEST_CASE(env_passthrough)
364 {
365     ExecResult exres = ExecResult ();
366     environ = i2n_extra_environ;
367
368     const std::string result =
369         capture_exec (env_argv_abs, exres,
370                         capture_flag::collect_out
371                       | capture_flag::collect_err
372                       | capture_flag::env_passthru);
373
374     BOOST_CHECK(exres.normal_exit);
375     BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
376     BOOST_CHECK_EQUAL(result, (std::string)(I2N_EXTRA_ENVIRON "\n"));
377 }
378
379 BOOST_AUTO_TEST_CASE(env_nil)
380 {
381     ExecResult exres = ExecResult ();
382     environ = i2n_extra_environ;
383
384     const std::string result =
385         capture_exec (env_argv_abs, exres,
386                         capture_flag::collect_out
387                       | capture_flag::collect_err);
388
389     BOOST_CHECK(exres.normal_exit);
390     BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
391     BOOST_CHECK_EQUAL(result, std::string());
392 }
393
394 BOOST_AUTO_TEST_SUITE_END() /* [pipestream->env] */
395
396 BOOST_AUTO_TEST_SUITE_END() /* [pipestream] */
397