@@ -19,6 +19,69 @@ namespace process = mcpp::run;
1919// change mcpp's own environment — that mutation is exactly what leaked into
2020// /bin/sh and crashed it on newer-glibc hosts. The addition is the started
2121// program's alone; this invocation's statement is what it was.
22+ TEST (RunExec, StreamCompletionAndChildWaitKeepTheirBounds) {
23+ struct Case {
24+ const char * name;
25+ const char * command;
26+ int total = 0 ;
27+ int idle = 0 ;
28+ bool stop = false ;
29+ int exit = -1 ;
30+ };
31+ const std::vector<Case> cases{
32+ {" exit" , " exit 7" , 0 , 0 , false , 7 },
33+ {" signal" , " kill -TERM $$" , 0 , 0 , false , 143 },
34+ {" already-exited-bounded" , " exit 7" , 3000 , 0 , true , 7 },
35+ {" open-stop" , " sleep 2" , 0 , 0 , true },
36+ {" both-EOF-stop" , " exec 1>&- 2>&-; sleep 2" , 0 , 0 , true },
37+ {" stdout-EOF-stop" , " exec 1>&-; sleep 2" , 0 , 0 , true },
38+ {" both-EOF-total" , " exec 1>&- 2>&-; sleep 2" , 50 },
39+ {" stdout-EOF-total" , " exec 1>&-; sleep 2" , 50 },
40+ };
41+ auto exercise = [](process::Streams streams, const Case& test) {
42+ SCOPED_TRACE (std::format (" stream={} case={}" , static_cast <int >(streams), test.name ));
43+ const auto started = std::chrono::steady_clock::now ();
44+ process::Options options;
45+ options.streams = streams;
46+ options.total = std::chrono::milliseconds (test.total );
47+ options.idle = std::chrono::milliseconds (test.idle );
48+ if (test.stop ) options.should_stop = [&] {
49+ return std::chrono::steady_clock::now () - started > std::chrono::milliseconds (50 );
50+ };
51+ const auto result = process::execute ({" /bin/sh" , " -c" , test.command }, options);
52+ if (test.exit >= 0 ) {
53+ EXPECT_EQ (result.exit_code , test.exit );
54+ EXPECT_FALSE (result.timed_out );
55+ } else {
56+ EXPECT_NE (result.exit_code , 0 );
57+ EXPECT_TRUE (result.timed_out );
58+ }
59+ EXPECT_LT (std::chrono::steady_clock::now () - started, std::chrono::seconds (1 ));
60+ };
61+ for (const auto streams : {process::Streams::inherit, process::Streams::discard,
62+ process::Streams::merged, process::Streams::output,
63+ process::Streams::output_error_shown, process::Streams::output_error_kept}) {
64+ for (const auto & test : cases) exercise (streams, test);
65+ if (streams != process::Streams::inherit && streams != process::Streams::discard)
66+ exercise (streams, {" both-EOF-idle" , " exec 1>&- 2>&-; sleep 2" , 0 , 50 });
67+ }
68+ exercise (process::Streams::output_error_kept,
69+ {" stderr-activity" , " exec 1>&-; for i in 1 2 3 4 5 6 7 8; do printf x >&2; sleep .03; done; exit 7" ,
70+ 0 , 100 , false , 7 });
71+ }
72+
73+ TEST (RunExec, StopPredicateAppliesAfterCapturedStreamsClose) {
74+ const auto started = std::chrono::steady_clock::now ();
75+ const auto result = process::execute ({" /bin/sh" , " -c" , " exec 1>&- 2>&-; sleep 2" }, {
76+ .streams = process::Streams::merged,
77+ .should_stop = [&] {
78+ return std::chrono::steady_clock::now () - started > std::chrono::milliseconds (50 );
79+ }});
80+ EXPECT_TRUE (result.timed_out );
81+ EXPECT_NE (result.exit_code , 0 );
82+ EXPECT_LT (std::chrono::steady_clock::now () - started, std::chrono::seconds (1 ));
83+ }
84+
2285TEST (RunExec, StopPredicateAppliesWithoutCapturedStreams) {
2386 for (const auto streams : {process::Streams::inherit, process::Streams::discard}) {
2487 const auto started = std::chrono::steady_clock::now ();
0 commit comments