do not indent boost unittest structurals
[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 65
a44b0eb1 66BOOST_AUTO_TEST_SUITE(read)
3ed2cc9b 67
a44b0eb1
PG
68# define ENOUGH_ZEROS 42
69const char *const zero_bytes_argv [] =
70 { "/usr/bin/head", "-c", TO_CHARP(ENOUGH_ZEROS), "/dev/zero", NULL };
3ed2cc9b 71
a44b0eb1
PG
72BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok)
73{
74 const std::string result =
75 capture_exec (I2n::join_string (zero_bytes_argv, " "));
3ed2cc9b 76
a44b0eb1
PG
77 BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
78}
3ed2cc9b 79
a44b0eb1
PG
80BOOST_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);
c2c29997 85
a44b0eb1
PG
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}
c2c29997 91
a44b0eb1
PG
92BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok)
93{
94 const std::string result = capture_exec (zero_bytes_argv);
3ed2cc9b 95
a44b0eb1
PG
96 BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
97}
3ed2cc9b 98
a44b0eb1
PG
99BOOST_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;
c2c29997 104
a44b0eb1
PG
105 while ((cur = *argp++) != NULL) {
106 argvec.push_back (std::string (cur));
107 }
c2c29997 108
a44b0eb1 109 const std::string result = capture_exec (argvec);
c2c29997 110
a44b0eb1
PG
111 BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
112}
c2c29997 113
a44b0eb1
PG
114BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_result)
115{
116 ExecResult exres = ExecResult ();
117 const std::string result = capture_exec (zero_bytes_argv, exres);
c2c29997 118
a44b0eb1
PG
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}
c2c29997 124
a44b0eb1 125const char *const bad_command [] = { "/does_not_exist", NULL };
cc917897 126
0c68bd0f 127# ifndef NO_CHILD_FAIL_TESTS
a44b0eb1
PG
128BOOST_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}
0c68bd0f 144# endif /* [!NO_CHILD_FAIL_TESTS] */
cc917897 145
0c68bd0f 146# ifndef NO_CHILD_FAIL_TESTS
a44b0eb1
PG
147BOOST_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}
0c68bd0f 161# endif /* [!NO_CHILD_FAIL_TESTS] */
cc917897 162
0c68bd0f 163# ifndef NO_CHILD_FAIL_TESTS
a44b0eb1
PG
164BOOST_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}
0c68bd0f 179# endif /* [!NO_CHILD_FAIL_TESTS] */
cc917897 180
a44b0eb1
PG
181const char *const false_argv_abs [] = { "/bin/false", NULL };
182const char *const true_argv_abs [] = { "/bin/true" , NULL };
183const char *const false_argv_rel [] = { "false" , NULL };
184const char *const true_argv_rel [] = { "true" , NULL };
7f58145b 185
0c68bd0f 186# ifndef NO_CHILD_FAIL_TESTS
a44b0eb1
PG
187BOOST_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}
0c68bd0f 197# endif /* [!NO_CHILD_FAIL_TESTS] */
7f58145b 198
0c68bd0f 199# ifndef NO_CHILD_FAIL_TESTS
a44b0eb1
PG
200BOOST_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}
0c68bd0f 210# endif /* [!NO_CHILD_FAIL_TESTS] */
7f58145b 211
a44b0eb1
PG
212BOOST_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);
ad4490f1 218
a44b0eb1
PG
219 BOOST_CHECK(exres.normal_exit);
220 BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
221 BOOST_CHECK_EQUAL(result.size (), 0);
222}
ad4490f1 223
0c68bd0f 224# ifndef NO_CHILD_FAIL_TESTS
a44b0eb1
PG
225BOOST_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}
0c68bd0f 238# endif /* [!NO_CHILD_FAIL_TESTS] */
ad4490f1 239
a44b0eb1
PG
240BOOST_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);
ad4490f1 246
a44b0eb1
PG
247 BOOST_CHECK(exres.normal_exit);
248 BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
249 BOOST_CHECK_EQUAL(result.size (), 0);
250}
ad4490f1 251
0c68bd0f 252# ifndef NO_CHILD_FAIL_TESTS
a44b0eb1
PG
253BOOST_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}
0c68bd0f 264# endif /* [!NO_CHILD_FAIL_TESTS] */
ad4490f1 265
a44b0eb1
PG
266const char *const echo_abs = "/bin/echo";
267const char *const echo_rel = "echo";
268
269static std::vector<std::string>
270mk_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
281BOOST_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
295BOOST_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
309static std::vector<std::string>
310mk_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
325BOOST_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
339BOOST_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
353BOOST_AUTO_TEST_SUITE_END() /* [pipestream->read] */
354
355BOOST_AUTO_TEST_SUITE(env)
356
357static 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."
360static char *i2n_extra_environ [] =
361 { const_cast<char*> (I2N_EXTRA_ENVIRON) , NULL };
362
363BOOST_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
379BOOST_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
394BOOST_AUTO_TEST_SUITE_END() /* [pipestream->env] */
cdc166d2 395
3ed2cc9b
PG
396BOOST_AUTO_TEST_SUITE_END() /* [pipestream] */
397