Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/kyua/utils/process/executor_test.cpp
48199 views
1
// Copyright 2015 The Kyua Authors.
2
// All rights reserved.
3
//
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are
6
// met:
7
//
8
// * Redistributions of source code must retain the above copyright
9
// notice, this list of conditions and the following disclaimer.
10
// * Redistributions in binary form must reproduce the above copyright
11
// notice, this list of conditions and the following disclaimer in the
12
// documentation and/or other materials provided with the distribution.
13
// * Neither the name of Google Inc. nor the names of its contributors
14
// may be used to endorse or promote products derived from this software
15
// without specific prior written permission.
16
//
17
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29
#include "utils/process/executor.ipp"
30
31
extern "C" {
32
#include <sys/types.h>
33
#include <sys/time.h>
34
#include <sys/wait.h>
35
36
#include <signal.h>
37
#include <unistd.h>
38
}
39
40
#include <cerrno>
41
#include <cstdlib>
42
#include <fstream>
43
#include <iostream>
44
#include <vector>
45
46
#include <atf-c++.hpp>
47
48
#include "utils/datetime.hpp"
49
#include "utils/defs.hpp"
50
#include "utils/env.hpp"
51
#include "utils/format/containers.ipp"
52
#include "utils/format/macros.hpp"
53
#include "utils/fs/operations.hpp"
54
#include "utils/fs/path.hpp"
55
#include "utils/optional.ipp"
56
#include "utils/passwd.hpp"
57
#include "utils/process/status.hpp"
58
#include "utils/sanity.hpp"
59
#include "utils/signals/exceptions.hpp"
60
#include "utils/stacktrace.hpp"
61
#include "utils/text/exceptions.hpp"
62
#include "utils/text/operations.ipp"
63
64
namespace datetime = utils::datetime;
65
namespace executor = utils::process::executor;
66
namespace fs = utils::fs;
67
namespace passwd = utils::passwd;
68
namespace process = utils::process;
69
namespace signals = utils::signals;
70
namespace text = utils::text;
71
72
using utils::none;
73
using utils::optional;
74
75
76
/// Large timeout for the processes we spawn.
77
///
78
/// This number is supposed to be (much) larger than the timeout of the test
79
/// cases that use it so that children processes are not killed spuriously.
80
static const datetime::delta infinite_timeout(1000000, 0);
81
82
83
static void do_exit(const int) UTILS_NORETURN;
84
85
86
/// Terminates a subprocess without invoking destructors.
87
///
88
/// This is just a simple wrapper over _exit(2) because we cannot use std::exit
89
/// on exit from a subprocess. The reason is that we do not want to invoke any
90
/// destructors as otherwise we'd clear up the global executor state by mistake.
91
/// This wouldn't be a major problem if it wasn't because doing so deletes
92
/// on-disk files and we want to leave them in place so that the parent process
93
/// can test for them!
94
///
95
/// \param exit_code Code to exit with.
96
static void
97
do_exit(const int exit_code)
98
{
99
std::cout.flush();
100
std::cerr.flush();
101
::_exit(exit_code);
102
}
103
104
105
/// Subprocess that creates a cookie file in its work directory.
106
class child_create_cookie {
107
/// Name of the cookie to create.
108
const std::string _cookie_name;
109
110
public:
111
/// Constructor.
112
///
113
/// \param cookie_name Name of the cookie to create.
114
child_create_cookie(const std::string& cookie_name) :
115
_cookie_name(cookie_name)
116
{
117
}
118
119
/// Runs the subprocess.
120
void
121
operator()(const fs::path& /* control_directory */)
122
UTILS_NORETURN
123
{
124
std::cout << "Creating cookie: " << _cookie_name << " (stdout)\n";
125
std::cerr << "Creating cookie: " << _cookie_name << " (stderr)\n";
126
atf::utils::create_file(_cookie_name, "");
127
do_exit(EXIT_SUCCESS);
128
}
129
};
130
131
132
static void child_delete_all(const fs::path&) UTILS_NORETURN;
133
134
135
/// Subprocess that deletes all files in the current directory.
136
///
137
/// This is intended to validate that the test runs in an empty directory,
138
/// separate from any control files that the executor may have created.
139
///
140
/// \param control_directory Directory where control files separate from the
141
/// work directory can be placed.
142
static void
143
child_delete_all(const fs::path& control_directory)
144
{
145
const fs::path cookie = control_directory / "exec_was_called";
146
std::ofstream control_file(cookie.c_str());
147
if (!control_file) {
148
std::cerr << "Failed to create " << cookie << '\n';
149
std::abort();
150
}
151
152
const int exit_code = ::system("rm *") == -1
153
? EXIT_FAILURE : EXIT_SUCCESS;
154
do_exit(exit_code);
155
}
156
157
158
static void child_dump_unprivileged_user(const fs::path&) UTILS_NORETURN;
159
160
161
/// Subprocess that dumps user configuration.
162
static void
163
child_dump_unprivileged_user(const fs::path& /* control_directory */)
164
{
165
const passwd::user current_user = passwd::current_user();
166
std::cout << F("UID = %s\n") % current_user.uid;
167
do_exit(EXIT_SUCCESS);
168
}
169
170
171
/// Subprocess that returns a specific exit code.
172
class child_exit {
173
/// Exit code to return.
174
int _exit_code;
175
176
public:
177
/// Constructor.
178
///
179
/// \param exit_code Exit code to return.
180
child_exit(const int exit_code) : _exit_code(exit_code)
181
{
182
}
183
184
/// Runs the subprocess.
185
void
186
operator()(const fs::path& /* control_directory */)
187
UTILS_NORETURN
188
{
189
do_exit(_exit_code);
190
}
191
};
192
193
194
static void child_pause(const fs::path&) UTILS_NORETURN;
195
196
197
/// Subprocess that just blocks.
198
static void
199
child_pause(const fs::path& /* control_directory */)
200
{
201
sigset_t mask;
202
sigemptyset(&mask);
203
for (;;) {
204
::sigsuspend(&mask);
205
}
206
std::abort();
207
}
208
209
210
static void child_print(const fs::path&) UTILS_NORETURN;
211
212
213
/// Subprocess that writes to stdout and stderr.
214
static void
215
child_print(const fs::path& /* control_directory */)
216
{
217
std::cout << "stdout: some text\n";
218
std::cerr << "stderr: some other text\n";
219
220
do_exit(EXIT_SUCCESS);
221
}
222
223
224
/// Subprocess that sleeps for a period of time before exiting.
225
class child_sleep {
226
/// Seconds to sleep for before termination.
227
int _seconds;
228
229
public:
230
/// Construtor.
231
///
232
/// \param seconds Seconds to sleep for before termination.
233
child_sleep(const int seconds) : _seconds(seconds)
234
{
235
}
236
237
/// Runs the subprocess.
238
void
239
operator()(const fs::path& /* control_directory */)
240
UTILS_NORETURN
241
{
242
::sleep(_seconds);
243
do_exit(EXIT_SUCCESS);
244
}
245
};
246
247
248
static void child_spawn_blocking_child(const fs::path&) UTILS_NORETURN;
249
250
251
/// Subprocess that spawns a subchild that gets stuck.
252
///
253
/// Used by the caller to validate that the whole process tree is terminated
254
/// when this subprocess is killed.
255
static void
256
child_spawn_blocking_child(
257
const fs::path& /* control_directory */)
258
{
259
pid_t pid = ::fork();
260
if (pid == -1) {
261
std::cerr << "Cannot fork subprocess\n";
262
do_exit(EXIT_FAILURE);
263
} else if (pid == 0) {
264
for (;;)
265
::pause();
266
} else {
267
const fs::path name = fs::path(utils::getenv("CONTROL_DIR").get()) /
268
"pid";
269
std::ofstream pidfile(name.c_str());
270
if (!pidfile) {
271
std::cerr << "Failed to create the pidfile\n";
272
do_exit(EXIT_FAILURE);
273
}
274
pidfile << pid;
275
pidfile.close();
276
do_exit(EXIT_SUCCESS);
277
}
278
}
279
280
281
static void child_validate_isolation(const fs::path&) UTILS_NORETURN;
282
283
284
/// Subprocess that checks if isolate_child() has been called.
285
static void
286
child_validate_isolation(const fs::path& /* control_directory */)
287
{
288
if (utils::getenv("HOME").get() == "fake-value") {
289
std::cerr << "HOME not reset\n";
290
do_exit(EXIT_FAILURE);
291
}
292
if (utils::getenv("LANG")) {
293
std::cerr << "LANG not unset\n";
294
do_exit(EXIT_FAILURE);
295
}
296
do_exit(EXIT_SUCCESS);
297
}
298
299
300
/// Invokes executor::spawn() with default arguments.
301
///
302
/// \param handle The executor on which to invoke spawn().
303
/// \param args Arguments to the binary.
304
/// \param timeout Maximum time the program can run for.
305
/// \param unprivileged_user If set, user to switch to when running the child
306
/// program.
307
/// \param stdout_target If not none, file to which to write the stdout of the
308
/// test case.
309
/// \param stderr_target If not none, file to which to write the stderr of the
310
/// test case.
311
///
312
/// \return The exec handle for the spawned binary.
313
template< class Hook >
314
static executor::exec_handle
315
do_spawn(executor::executor_handle& handle, Hook hook,
316
const datetime::delta& timeout = infinite_timeout,
317
const optional< passwd::user > unprivileged_user = none,
318
const optional< fs::path > stdout_target = none,
319
const optional< fs::path > stderr_target = none)
320
{
321
const executor::exec_handle exec_handle = handle.spawn< Hook >(
322
hook, timeout, unprivileged_user, stdout_target, stderr_target);
323
return exec_handle;
324
}
325
326
327
/// Checks for a specific exit status in the status of a exit_handle.
328
///
329
/// \param exit_status The expected exit status.
330
/// \param status The value of exit_handle.status().
331
///
332
/// \post Terminates the calling test case if the status does not match the
333
/// required value.
334
static void
335
require_exit(const int exit_status, const optional< process::status > status)
336
{
337
ATF_REQUIRE(status);
338
ATF_REQUIRE(status.get().exited());
339
ATF_REQUIRE_EQ(exit_status, status.get().exitstatus());
340
}
341
342
343
/// Ensures that a killed process is gone.
344
///
345
/// The way we do this is by sending an idempotent signal to the given PID
346
/// and checking if the signal was delivered. If it was, the process is
347
/// still alive; if it was not, then it is gone.
348
///
349
/// Note that this might be inaccurate for two reasons:
350
///
351
/// 1) The system may have spawned a new process with the same pid as
352
/// our subchild... but in practice, this does not happen because
353
/// most systems do not immediately reuse pid numbers. If that
354
/// happens... well, we get a false test failure.
355
///
356
/// 2) We ran so fast that even if the process was sent a signal to
357
/// die, it has not had enough time to process it yet. This is why
358
/// we retry this a few times.
359
///
360
/// \param pid PID of the process to check.
361
static void
362
ensure_dead(const pid_t pid)
363
{
364
int attempts = 30;
365
retry:
366
if (::kill(pid, SIGCONT) != -1 || errno != ESRCH) {
367
if (attempts > 0) {
368
std::cout << "Subprocess not dead yet; retrying wait\n";
369
--attempts;
370
::usleep(100000);
371
goto retry;
372
}
373
ATF_FAIL(F("The subprocess %s of our child was not killed") % pid);
374
}
375
}
376
377
378
ATF_TEST_CASE_WITHOUT_HEAD(integration__run_one);
379
ATF_TEST_CASE_BODY(integration__run_one)
380
{
381
executor::executor_handle handle = executor::setup();
382
383
const executor::exec_handle exec_handle = do_spawn(handle, child_exit(41));
384
385
executor::exit_handle exit_handle = handle.wait_any();
386
ATF_REQUIRE_EQ(exec_handle.pid(), exit_handle.original_pid());
387
require_exit(41, exit_handle.status());
388
exit_handle.cleanup();
389
390
handle.cleanup();
391
}
392
393
394
ATF_TEST_CASE_WITHOUT_HEAD(integration__run_many);
395
ATF_TEST_CASE_BODY(integration__run_many)
396
{
397
static const std::size_t num_children = 30;
398
399
executor::executor_handle handle = executor::setup();
400
401
std::size_t total_children = 0;
402
std::map< int, int > exp_exit_statuses;
403
std::map< int, datetime::timestamp > exp_start_times;
404
for (std::size_t i = 0; i < num_children; ++i) {
405
const datetime::timestamp start_time = datetime::timestamp::from_values(
406
2014, 12, 8, 9, 40, 0, i);
407
408
for (std::size_t j = 0; j < 3; j++) {
409
const std::size_t id = i * 3 + j;
410
411
datetime::set_mock_now(start_time);
412
const int pid = do_spawn(handle, child_exit(id)).pid();
413
exp_exit_statuses.insert(std::make_pair(pid, id));
414
exp_start_times.insert(std::make_pair(pid, start_time));
415
++total_children;
416
}
417
}
418
419
for (std::size_t i = 0; i < total_children; ++i) {
420
const datetime::timestamp end_time = datetime::timestamp::from_values(
421
2014, 12, 8, 9, 50, 10, i);
422
datetime::set_mock_now(end_time);
423
executor::exit_handle exit_handle = handle.wait_any();
424
const int original_pid = exit_handle.original_pid();
425
426
const int exit_status = exp_exit_statuses.find(original_pid)->second;
427
const datetime::timestamp& start_time = exp_start_times.find(
428
original_pid)->second;
429
430
require_exit(exit_status, exit_handle.status());
431
432
ATF_REQUIRE_EQ(start_time, exit_handle.start_time());
433
ATF_REQUIRE_EQ(end_time, exit_handle.end_time());
434
435
exit_handle.cleanup();
436
437
ATF_REQUIRE(!atf::utils::file_exists(
438
exit_handle.stdout_file().str()));
439
ATF_REQUIRE(!atf::utils::file_exists(
440
exit_handle.stderr_file().str()));
441
ATF_REQUIRE(!atf::utils::file_exists(
442
exit_handle.work_directory().str()));
443
}
444
445
handle.cleanup();
446
}
447
448
449
ATF_TEST_CASE_WITHOUT_HEAD(integration__parameters_and_output);
450
ATF_TEST_CASE_BODY(integration__parameters_and_output)
451
{
452
executor::executor_handle handle = executor::setup();
453
454
const executor::exec_handle exec_handle = do_spawn(handle, child_print);
455
456
executor::exit_handle exit_handle = handle.wait_any();
457
458
ATF_REQUIRE_EQ(exec_handle.pid(), exit_handle.original_pid());
459
460
require_exit(EXIT_SUCCESS, exit_handle.status());
461
462
const fs::path stdout_file = exit_handle.stdout_file();
463
ATF_REQUIRE(atf::utils::compare_file(
464
stdout_file.str(), "stdout: some text\n"));
465
const fs::path stderr_file = exit_handle.stderr_file();
466
ATF_REQUIRE(atf::utils::compare_file(
467
stderr_file.str(), "stderr: some other text\n"));
468
469
exit_handle.cleanup();
470
ATF_REQUIRE(!fs::exists(stdout_file));
471
ATF_REQUIRE(!fs::exists(stderr_file));
472
473
handle.cleanup();
474
}
475
476
477
ATF_TEST_CASE_WITHOUT_HEAD(integration__custom_output_files);
478
ATF_TEST_CASE_BODY(integration__custom_output_files)
479
{
480
executor::executor_handle handle = executor::setup();
481
482
const fs::path stdout_file("custom-stdout.txt");
483
const fs::path stderr_file("custom-stderr.txt");
484
485
const executor::exec_handle exec_handle = do_spawn(
486
handle, child_print, infinite_timeout, none,
487
utils::make_optional(stdout_file),
488
utils::make_optional(stderr_file));
489
490
executor::exit_handle exit_handle = handle.wait_any();
491
492
ATF_REQUIRE_EQ(exec_handle.pid(), exit_handle.original_pid());
493
494
require_exit(EXIT_SUCCESS, exit_handle.status());
495
496
ATF_REQUIRE_EQ(stdout_file, exit_handle.stdout_file());
497
ATF_REQUIRE_EQ(stderr_file, exit_handle.stderr_file());
498
499
exit_handle.cleanup();
500
501
handle.cleanup();
502
503
// Must compare after cleanup to ensure the files did not get deleted.
504
ATF_REQUIRE(atf::utils::compare_file(
505
stdout_file.str(), "stdout: some text\n"));
506
ATF_REQUIRE(atf::utils::compare_file(
507
stderr_file.str(), "stderr: some other text\n"));
508
}
509
510
511
ATF_TEST_CASE_WITHOUT_HEAD(integration__timestamps);
512
ATF_TEST_CASE_BODY(integration__timestamps)
513
{
514
executor::executor_handle handle = executor::setup();
515
516
const datetime::timestamp start_time = datetime::timestamp::from_values(
517
2014, 12, 8, 9, 35, 10, 1000);
518
const datetime::timestamp end_time = datetime::timestamp::from_values(
519
2014, 12, 8, 9, 35, 20, 2000);
520
521
datetime::set_mock_now(start_time);
522
do_spawn(handle, child_exit(70));
523
524
datetime::set_mock_now(end_time);
525
executor::exit_handle exit_handle = handle.wait_any();
526
527
require_exit(70, exit_handle.status());
528
529
ATF_REQUIRE_EQ(start_time, exit_handle.start_time());
530
ATF_REQUIRE_EQ(end_time, exit_handle.end_time());
531
exit_handle.cleanup();
532
533
handle.cleanup();
534
}
535
536
537
ATF_TEST_CASE_WITHOUT_HEAD(integration__files);
538
ATF_TEST_CASE_BODY(integration__files)
539
{
540
executor::executor_handle handle = executor::setup();
541
542
do_spawn(handle, child_create_cookie("cookie.12345"));
543
544
executor::exit_handle exit_handle = handle.wait_any();
545
546
ATF_REQUIRE(atf::utils::file_exists(
547
(exit_handle.work_directory() / "cookie.12345").str()));
548
549
exit_handle.cleanup();
550
551
ATF_REQUIRE(!atf::utils::file_exists(exit_handle.stdout_file().str()));
552
ATF_REQUIRE(!atf::utils::file_exists(exit_handle.stderr_file().str()));
553
ATF_REQUIRE(!atf::utils::file_exists(exit_handle.work_directory().str()));
554
555
handle.cleanup();
556
}
557
558
559
ATF_TEST_CASE_WITHOUT_HEAD(integration__followup);
560
ATF_TEST_CASE_BODY(integration__followup)
561
{
562
executor::executor_handle handle = executor::setup();
563
564
(void)handle.spawn(child_create_cookie("cookie.1"), infinite_timeout, none);
565
executor::exit_handle exit_1_handle = handle.wait_any();
566
567
(void)handle.spawn_followup(child_create_cookie("cookie.2"), exit_1_handle,
568
infinite_timeout);
569
executor::exit_handle exit_2_handle = handle.wait_any();
570
571
ATF_REQUIRE_EQ(exit_1_handle.stdout_file(), exit_2_handle.stdout_file());
572
ATF_REQUIRE_EQ(exit_1_handle.stderr_file(), exit_2_handle.stderr_file());
573
ATF_REQUIRE_EQ(exit_1_handle.control_directory(),
574
exit_2_handle.control_directory());
575
ATF_REQUIRE_EQ(exit_1_handle.work_directory(),
576
exit_2_handle.work_directory());
577
578
(void)handle.spawn_followup(child_create_cookie("cookie.3"), exit_2_handle,
579
infinite_timeout);
580
exit_2_handle.cleanup();
581
exit_1_handle.cleanup();
582
executor::exit_handle exit_3_handle = handle.wait_any();
583
584
ATF_REQUIRE_EQ(exit_1_handle.stdout_file(), exit_3_handle.stdout_file());
585
ATF_REQUIRE_EQ(exit_1_handle.stderr_file(), exit_3_handle.stderr_file());
586
ATF_REQUIRE_EQ(exit_1_handle.control_directory(),
587
exit_3_handle.control_directory());
588
ATF_REQUIRE_EQ(exit_1_handle.work_directory(),
589
exit_3_handle.work_directory());
590
591
ATF_REQUIRE(atf::utils::file_exists(
592
(exit_1_handle.work_directory() / "cookie.1").str()));
593
ATF_REQUIRE(atf::utils::file_exists(
594
(exit_1_handle.work_directory() / "cookie.2").str()));
595
ATF_REQUIRE(atf::utils::file_exists(
596
(exit_1_handle.work_directory() / "cookie.3").str()));
597
598
ATF_REQUIRE(atf::utils::compare_file(
599
exit_1_handle.stdout_file().str(),
600
"Creating cookie: cookie.1 (stdout)\n"
601
"Creating cookie: cookie.2 (stdout)\n"
602
"Creating cookie: cookie.3 (stdout)\n"));
603
604
ATF_REQUIRE(atf::utils::compare_file(
605
exit_1_handle.stderr_file().str(),
606
"Creating cookie: cookie.1 (stderr)\n"
607
"Creating cookie: cookie.2 (stderr)\n"
608
"Creating cookie: cookie.3 (stderr)\n"));
609
610
exit_3_handle.cleanup();
611
612
ATF_REQUIRE(!atf::utils::file_exists(exit_1_handle.stdout_file().str()));
613
ATF_REQUIRE(!atf::utils::file_exists(exit_1_handle.stderr_file().str()));
614
ATF_REQUIRE(!atf::utils::file_exists(exit_1_handle.work_directory().str()));
615
616
handle.cleanup();
617
}
618
619
620
ATF_TEST_CASE_WITHOUT_HEAD(integration__output_files_always_exist);
621
ATF_TEST_CASE_BODY(integration__output_files_always_exist)
622
{
623
executor::executor_handle handle = executor::setup();
624
625
// This test is racy: we specify a very short timeout for the subprocess so
626
// that we cause the subprocess to exit before it has had time to set up the
627
// output files. However, for scheduling reasons, the subprocess may
628
// actually run to completion before the timer triggers. Retry this a few
629
// times to attempt to catch a "good test".
630
for (int i = 0; i < 50; i++) {
631
const executor::exec_handle exec_handle =
632
do_spawn(handle, child_exit(0), datetime::delta(0, 100000));
633
executor::exit_handle exit_handle = handle.wait(exec_handle);
634
ATF_REQUIRE(fs::exists(exit_handle.stdout_file()));
635
ATF_REQUIRE(fs::exists(exit_handle.stderr_file()));
636
exit_handle.cleanup();
637
}
638
639
handle.cleanup();
640
}
641
642
643
ATF_TEST_CASE(integration__timeouts);
644
ATF_TEST_CASE_HEAD(integration__timeouts)
645
{
646
set_md_var("timeout", "60");
647
}
648
ATF_TEST_CASE_BODY(integration__timeouts)
649
{
650
executor::executor_handle handle = executor::setup();
651
652
const executor::exec_handle exec_handle1 =
653
do_spawn(handle, child_sleep(30), datetime::delta(2, 0));
654
const executor::exec_handle exec_handle2 =
655
do_spawn(handle, child_sleep(40), datetime::delta(5, 0));
656
const executor::exec_handle exec_handle3 =
657
do_spawn(handle, child_exit(15));
658
659
{
660
executor::exit_handle exit_handle = handle.wait_any();
661
ATF_REQUIRE_EQ(exec_handle3.pid(), exit_handle.original_pid());
662
require_exit(15, exit_handle.status());
663
exit_handle.cleanup();
664
}
665
666
{
667
executor::exit_handle exit_handle = handle.wait_any();
668
ATF_REQUIRE_EQ(exec_handle1.pid(), exit_handle.original_pid());
669
ATF_REQUIRE(!exit_handle.status());
670
const datetime::delta duration =
671
exit_handle.end_time() - exit_handle.start_time();
672
ATF_REQUIRE(duration < datetime::delta(10, 0));
673
ATF_REQUIRE(duration >= datetime::delta(2, 0));
674
exit_handle.cleanup();
675
}
676
677
{
678
executor::exit_handle exit_handle = handle.wait_any();
679
ATF_REQUIRE_EQ(exec_handle2.pid(), exit_handle.original_pid());
680
ATF_REQUIRE(!exit_handle.status());
681
const datetime::delta duration =
682
exit_handle.end_time() - exit_handle.start_time();
683
ATF_REQUIRE(duration < datetime::delta(10, 0));
684
ATF_REQUIRE(duration >= datetime::delta(4, 0));
685
exit_handle.cleanup();
686
}
687
688
handle.cleanup();
689
}
690
691
692
ATF_TEST_CASE(integration__unprivileged_user);
693
ATF_TEST_CASE_HEAD(integration__unprivileged_user)
694
{
695
set_md_var("require.config", "unprivileged-user");
696
set_md_var("require.user", "root");
697
}
698
ATF_TEST_CASE_BODY(integration__unprivileged_user)
699
{
700
executor::executor_handle handle = executor::setup();
701
702
const passwd::user unprivileged_user = passwd::find_user_by_name(
703
get_config_var("unprivileged-user"));
704
705
do_spawn(handle, child_dump_unprivileged_user,
706
infinite_timeout, utils::make_optional(unprivileged_user));
707
708
executor::exit_handle exit_handle = handle.wait_any();
709
ATF_REQUIRE(atf::utils::compare_file(
710
exit_handle.stdout_file().str(),
711
F("UID = %s\n") % unprivileged_user.uid));
712
exit_handle.cleanup();
713
714
handle.cleanup();
715
}
716
717
718
ATF_TEST_CASE_WITHOUT_HEAD(integration__auto_cleanup);
719
ATF_TEST_CASE_BODY(integration__auto_cleanup)
720
{
721
std::vector< int > pids;
722
std::vector< fs::path > paths;
723
{
724
executor::executor_handle handle = executor::setup();
725
726
pids.push_back(do_spawn(handle, child_exit(10)).pid());
727
pids.push_back(do_spawn(handle, child_exit(20)).pid());
728
729
// This invocation is never waited for below. This is intentional: we
730
// want the destructor to clean the "leaked" test automatically so that
731
// the clean up of the parent work directory also happens correctly.
732
pids.push_back(do_spawn(handle, child_pause).pid());
733
734
executor::exit_handle exit_handle1 = handle.wait_any();
735
paths.push_back(exit_handle1.stdout_file());
736
paths.push_back(exit_handle1.stderr_file());
737
paths.push_back(exit_handle1.work_directory());
738
739
executor::exit_handle exit_handle2 = handle.wait_any();
740
paths.push_back(exit_handle2.stdout_file());
741
paths.push_back(exit_handle2.stderr_file());
742
paths.push_back(exit_handle2.work_directory());
743
}
744
for (std::vector< int >::const_iterator iter = pids.begin();
745
iter != pids.end(); ++iter) {
746
ensure_dead(*iter);
747
}
748
for (std::vector< fs::path >::const_iterator iter = paths.begin();
749
iter != paths.end(); ++iter) {
750
ATF_REQUIRE(!atf::utils::file_exists((*iter).str()));
751
}
752
}
753
754
755
/// Ensures that interrupting an executor cleans things up correctly.
756
///
757
/// This test scenario is tricky. We spawn a master child process that runs the
758
/// executor code and we send a signal to it externally. The child process
759
/// spawns a bunch of tests that block indefinitely and tries to wait for their
760
/// results. When the signal is received, we expect an interrupt_error to be
761
/// raised, which in turn should clean up all test resources and exit the master
762
/// child process successfully.
763
///
764
/// \param signo Signal to deliver to the executor.
765
static void
766
do_signal_handling_test(const int signo)
767
{
768
static const char* cookie = "spawned.txt";
769
770
const pid_t pid = ::fork();
771
ATF_REQUIRE(pid != -1);
772
if (pid == 0) {
773
static const std::size_t num_children = 3;
774
775
optional< fs::path > root_work_directory;
776
try {
777
executor::executor_handle handle = executor::setup();
778
root_work_directory = handle.root_work_directory();
779
780
for (std::size_t i = 0; i < num_children; ++i) {
781
std::cout << "Spawned child number " << i << '\n';
782
do_spawn(handle, child_pause);
783
}
784
785
std::cout << "Creating " << cookie << " cookie\n";
786
atf::utils::create_file(cookie, "");
787
788
std::cout << "Waiting for subprocess termination\n";
789
for (std::size_t i = 0; i < num_children; ++i) {
790
executor::exit_handle exit_handle = handle.wait_any();
791
// We may never reach this point in the test, but if we do let's
792
// make sure the subprocess was terminated as expected.
793
if (exit_handle.status()) {
794
if (exit_handle.status().get().signaled() &&
795
exit_handle.status().get().termsig() == SIGKILL) {
796
// OK.
797
} else {
798
std::cerr << "Child exited with unexpected code: "
799
<< exit_handle.status().get();
800
std::exit(EXIT_FAILURE);
801
}
802
} else {
803
std::cerr << "Child timed out\n";
804
std::exit(EXIT_FAILURE);
805
}
806
exit_handle.cleanup();
807
}
808
std::cerr << "Terminating without reception of signal\n";
809
std::exit(EXIT_FAILURE);
810
} catch (const signals::interrupted_error& unused_error) {
811
std::cerr << "Terminating due to interrupted_error\n";
812
// We never kill ourselves until the cookie is created, so it is
813
// guaranteed that the optional root_work_directory has been
814
// initialized at this point.
815
if (atf::utils::file_exists(root_work_directory.get().str())) {
816
// Some cleanup did not happen; error out.
817
std::exit(EXIT_FAILURE);
818
} else {
819
std::exit(EXIT_SUCCESS);
820
}
821
}
822
std::abort();
823
}
824
825
std::cout << "Waiting for " << cookie << " cookie creation\n";
826
while (!atf::utils::file_exists(cookie)) {
827
// Wait for processes.
828
}
829
ATF_REQUIRE(::unlink(cookie) != -1);
830
std::cout << "Killing process\n";
831
ATF_REQUIRE(::kill(pid, signo) != -1);
832
833
int status;
834
std::cout << "Waiting for process termination\n";
835
ATF_REQUIRE(::waitpid(pid, &status, 0) != -1);
836
ATF_REQUIRE(WIFEXITED(status));
837
ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
838
}
839
840
841
ATF_TEST_CASE_WITHOUT_HEAD(integration__signal_handling);
842
ATF_TEST_CASE_BODY(integration__signal_handling)
843
{
844
// This test scenario is racy so run it multiple times to have higher
845
// chances of exposing problems.
846
const std::size_t rounds = 20;
847
848
for (std::size_t i = 0; i < rounds; ++i) {
849
std::cout << F("Testing round %s\n") % i;
850
do_signal_handling_test(SIGHUP);
851
do_signal_handling_test(SIGINT);
852
do_signal_handling_test(SIGTERM);
853
}
854
}
855
856
857
ATF_TEST_CASE_WITHOUT_HEAD(integration__isolate_child_is_called);
858
ATF_TEST_CASE_BODY(integration__isolate_child_is_called)
859
{
860
executor::executor_handle handle = executor::setup();
861
862
utils::setenv("HOME", "fake-value");
863
utils::setenv("LANG", "es_ES");
864
do_spawn(handle, child_validate_isolation);
865
866
executor::exit_handle exit_handle = handle.wait_any();
867
require_exit(EXIT_SUCCESS, exit_handle.status());
868
exit_handle.cleanup();
869
870
handle.cleanup();
871
}
872
873
874
ATF_TEST_CASE_WITHOUT_HEAD(integration__process_group_is_terminated);
875
ATF_TEST_CASE_BODY(integration__process_group_is_terminated)
876
{
877
utils::setenv("CONTROL_DIR", fs::current_path().str());
878
879
executor::executor_handle handle = executor::setup();
880
do_spawn(handle, child_spawn_blocking_child);
881
882
executor::exit_handle exit_handle = handle.wait_any();
883
require_exit(EXIT_SUCCESS, exit_handle.status());
884
exit_handle.cleanup();
885
886
handle.cleanup();
887
888
if (!fs::exists(fs::path("pid")))
889
fail("The pid file was not created");
890
891
std::ifstream pidfile("pid");
892
ATF_REQUIRE(pidfile);
893
pid_t pid;
894
pidfile >> pid;
895
pidfile.close();
896
897
ensure_dead(pid);
898
}
899
900
901
ATF_TEST_CASE_WITHOUT_HEAD(integration__prevent_clobbering_control_files);
902
ATF_TEST_CASE_BODY(integration__prevent_clobbering_control_files)
903
{
904
executor::executor_handle handle = executor::setup();
905
906
do_spawn(handle, child_delete_all);
907
908
executor::exit_handle exit_handle = handle.wait_any();
909
require_exit(EXIT_SUCCESS, exit_handle.status());
910
ATF_REQUIRE(atf::utils::file_exists(
911
(exit_handle.control_directory() / "exec_was_called").str()));
912
ATF_REQUIRE(!atf::utils::file_exists(
913
(exit_handle.work_directory() / "exec_was_called").str()));
914
exit_handle.cleanup();
915
916
handle.cleanup();
917
}
918
919
920
ATF_INIT_TEST_CASES(tcs)
921
{
922
ATF_ADD_TEST_CASE(tcs, integration__run_one);
923
ATF_ADD_TEST_CASE(tcs, integration__run_many);
924
925
ATF_ADD_TEST_CASE(tcs, integration__parameters_and_output);
926
ATF_ADD_TEST_CASE(tcs, integration__custom_output_files);
927
ATF_ADD_TEST_CASE(tcs, integration__timestamps);
928
ATF_ADD_TEST_CASE(tcs, integration__files);
929
930
ATF_ADD_TEST_CASE(tcs, integration__followup);
931
932
ATF_ADD_TEST_CASE(tcs, integration__output_files_always_exist);
933
ATF_ADD_TEST_CASE(tcs, integration__timeouts);
934
ATF_ADD_TEST_CASE(tcs, integration__unprivileged_user);
935
ATF_ADD_TEST_CASE(tcs, integration__auto_cleanup);
936
ATF_ADD_TEST_CASE(tcs, integration__signal_handling);
937
ATF_ADD_TEST_CASE(tcs, integration__isolate_child_is_called);
938
ATF_ADD_TEST_CASE(tcs, integration__process_group_is_terminated);
939
ATF_ADD_TEST_CASE(tcs, integration__prevent_clobbering_control_files);
940
}
941
942