pass pipestream flags as bitset
[libi2ncommon] / test / test_pipestream.cpp
CommitLineData
3ed2cc9b
PG
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
0c68bd0f
PG
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
3ed2cc9b
PG
40
41#define BOOST_TEST_DYN_LINK
42#include <boost/test/unit_test.hpp>
3ed2cc9b
PG
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
cdc166d2
PG
50#define I2N_EXTRA_ENV "I2N_EXTRA_ENV"
51
52struct 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
64BOOST_FIXTURE_TEST_SUITE(pipestream, Test_Pipestream_Fixture)
3ed2cc9b
PG
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
c2c29997
PG
80 BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok_result)
81 {
ad4490f1 82 ExecResult exres = ExecResult ();
c2c29997
PG
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
3ed2cc9b
PG
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
c2c29997
PG
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 {
ad4490f1 117 ExecResult exres = ExecResult ();
c2c29997
PG
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
cc917897
PG
126 const char *const bad_command [] = { "/does_not_exist", NULL };
127
0c68bd0f 128# ifndef NO_CHILD_FAIL_TESTS
7f58145b 129 BOOST_AUTO_TEST_CASE(abspath_bad_shell_fail)
cc917897
PG
130 {
131 assert (access(bad_command [0], X_OK) != 0);
132
ad4490f1
PG
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 */
cc917897
PG
139 const std::string result =
140 capture_exec (I2n::join_string (bad_command, " "));
141
ff5191e6 142 BOOST_CHECK(!exres.normal_exit);
cc917897
PG
143 BOOST_CHECK_EQUAL(result.size (), 0);
144 }
0c68bd0f 145# endif /* [!NO_CHILD_FAIL_TESTS] */
cc917897 146
0c68bd0f 147# ifndef NO_CHILD_FAIL_TESTS
7f58145b 148 BOOST_AUTO_TEST_CASE(abspath_bad_noshell_fail)
cc917897
PG
149 {
150 assert (access(bad_command [0], X_OK) != 0);
151
ad4490f1 152 ExecResult exres = ExecResult ();
cc917897
PG
153 const std::string result = capture_exec (bad_command, exres);
154
08199c66 155 BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
cc917897
PG
156 BOOST_CHECK(!exres.terminated_by_signal);
157 BOOST_CHECK_EQUAL(result.size (), 0);
08199c66
PG
158 BOOST_CHECK_EQUAL(exres.error_message,
159 "child failed to exec(): "
160 "error 2 (No such file or directory)");
cc917897 161 }
0c68bd0f 162# endif /* [!NO_CHILD_FAIL_TESTS] */
cc917897 163
0c68bd0f 164# ifndef NO_CHILD_FAIL_TESTS
7f58145b 165 BOOST_AUTO_TEST_CASE(abspath_bad_noshell_stderr)
cc917897
PG
166 {
167 assert (access(bad_command [0], X_OK) != 0);
168
ad4490f1 169 ExecResult exres = ExecResult ();
825c519f
PG
170 const std::string result = capture_exec (bad_command, exres,
171 capture_flag::collect_err);
cc917897 172
08199c66 173 BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
ad4490f1
PG
174 BOOST_CHECK(!exres.terminated_by_signal);
175 BOOST_CHECK_EQUAL(result.size (), 0);
08199c66
PG
176 BOOST_CHECK_EQUAL(exres.error_message,
177 "child failed to exec(): "
178 "error 2 (No such file or directory)");
cc917897 179 }
0c68bd0f 180# endif /* [!NO_CHILD_FAIL_TESTS] */
cc917897 181
ad4490f1
PG
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 };
7f58145b 186
0c68bd0f 187# ifndef NO_CHILD_FAIL_TESTS
7f58145b
PG
188 BOOST_AUTO_TEST_CASE(abspath_false_noshell_fail_exit)
189 {
ad4490f1
PG
190 ExecResult exres = ExecResult ();
191 const std::string result =
825c519f 192 capture_exec (false_argv_abs, exres, capture_flag::collect_out);
7f58145b
PG
193
194 BOOST_CHECK(exres.normal_exit);
195 BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
196 BOOST_CHECK_EQUAL(result.size (), 0);
197 }
0c68bd0f 198# endif /* [!NO_CHILD_FAIL_TESTS] */
7f58145b 199
0c68bd0f 200# ifndef NO_CHILD_FAIL_TESTS
7f58145b
PG
201 BOOST_AUTO_TEST_CASE(abspath_false_shell_fail_exit)
202 {
ad4490f1 203 ExecResult exres = ExecResult ();
7f58145b 204 const std::string result =
ad4490f1 205 capture_exec (std::string (false_argv_abs [0]), exres);
7f58145b
PG
206
207 BOOST_CHECK(exres.normal_exit);
208 BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
209 BOOST_CHECK_EQUAL(result.size (), 0);
210 }
0c68bd0f 211# endif /* [!NO_CHILD_FAIL_TESTS] */
7f58145b 212
ad4490f1
PG
213 BOOST_AUTO_TEST_CASE(relpath_true_noshell_ok)
214 {
215 ExecResult exres = ExecResult ();
216 const std::string result =
825c519f
PG
217 capture_exec (true_argv_rel, exres,
218 capture_flag::collect_out | capture_flag::search_path);
ad4490f1
PG
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
0c68bd0f 225# ifndef NO_CHILD_FAIL_TESTS
ad4490f1
PG
226 BOOST_AUTO_TEST_CASE(relpath_true_noshell_fail)
227 {
228 ExecResult exres = ExecResult ();
229 const std::string result =
825c519f 230 capture_exec (true_argv_rel, exres, capture_flag::collect_out);
ad4490f1 231
08199c66
PG
232 BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
233 /* no return code check since we couldn't exit */
ad4490f1 234 BOOST_CHECK_EQUAL(result.size (), 0);
08199c66
PG
235 BOOST_CHECK_EQUAL(exres.error_message,
236 "child failed to exec(): "
237 "error 2 (No such file or directory)");
ad4490f1 238 }
0c68bd0f 239# endif /* [!NO_CHILD_FAIL_TESTS] */
ad4490f1
PG
240
241 BOOST_AUTO_TEST_CASE(abspath_true_noshell_ok)
242 {
243 ExecResult exres = ExecResult ();
244 const std::string result =
825c519f
PG
245 capture_exec (true_argv_abs, exres,
246 capture_flag::collect_out | capture_flag::search_path);
ad4490f1
PG
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
0c68bd0f 253# ifndef NO_CHILD_FAIL_TESTS
ad4490f1
PG
254 BOOST_AUTO_TEST_CASE(relpath_false_noshell_fail)
255 {
256 ExecResult exres = ExecResult ();
257 const std::string result =
825c519f
PG
258 capture_exec (false_argv_rel, exres,
259 capture_flag::collect_out | capture_flag::search_path);
ad4490f1
PG
260
261 BOOST_CHECK(exres.normal_exit);
08199c66 262 /* no return code check since we couldn't exit */
ad4490f1
PG
263 BOOST_CHECK_EQUAL(result.size (), 0);
264 }
0c68bd0f 265# endif /* [!NO_CHILD_FAIL_TESTS] */
ad4490f1 266
ff5191e6
PG
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);
825c519f
PG
287 const std::string result = capture_exec (argv, exres,
288 capture_flag::collect_out
289 | capture_flag::search_path);
ff5191e6
PG
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);
825c519f
PG
301 const std::string result = capture_exec (argv, exres,
302 capture_flag::collect_out
303 | capture_flag::search_path);
ff5191e6
PG
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);
825c519f
PG
331 const std::string result = capture_exec (argv, exres,
332 capture_flag::collect_err
333 | capture_flag::search_path);
ff5191e6
PG
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);
825c519f
PG
345 const std::string result = capture_exec (argv, exres,
346 capture_flag::collect_out
347 | capture_flag::search_path);
ff5191e6
PG
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
3ed2cc9b
PG
354 BOOST_AUTO_TEST_SUITE_END() /* [pipestream->read] */
355
cdc166d2
PG
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 =
825c519f
PG
372 capture_exec (env_argv_abs, exres,
373 capture_flag::collect_out
374 | capture_flag::collect_err
375 | capture_flag::env_passthru);
cdc166d2
PG
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 =
825c519f
PG
388 capture_exec (env_argv_abs, exres,
389 capture_flag::collect_out
390 | capture_flag::collect_err);
cdc166d2
PG
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
3ed2cc9b
PG
399BOOST_AUTO_TEST_SUITE_END() /* [pipestream] */
400