[dart:io] Adds ProcessStartMode.INHERIT_STDIO

Adds a ProcessStartMode in which the child process inherits the stdio
handles from the parent.

Change-Id: Ibe7b8ae08caccaed827ae0a911a3cced7803cb6b
Reviewed-on: https://dart-review.googlesource.com/36362
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Zach Anderson <zra@google.com>
This commit is contained in:
Zachary Anderson 2018-01-23 22:50:06 +00:00 committed by commit-bot@chromium.org
parent 5de9c20fb0
commit 6523896c6f
10 changed files with 223 additions and 105 deletions

View file

@ -71,6 +71,14 @@ static char** ExtractCStringList(Dart_Handle strings,
return string_args;
}
bool Process::ModeIsAttached(ProcessStartMode mode) {
return (mode == kNormal) || (mode == kInheritStdio);
}
bool Process::ModeHasStdio(ProcessStartMode mode) {
return (mode == kNormal) || (mode == kDetachedWithStdio);
}
void Process::ClearAllSignalHandlers() {
for (intptr_t i = 1; i <= kLastSignal; i++) {
ClearSignalHandler(i, ILLEGAL_PORT);
@ -145,7 +153,7 @@ void FUNCTION_NAME(Process_Start)(Dart_NativeArguments args) {
}
}
int64_t mode =
DartUtils::GetInt64ValueCheckRange(Dart_GetNativeArgument(args, 6), 0, 2);
DartUtils::GetInt64ValueCheckRange(Dart_GetNativeArgument(args, 6), 0, 3);
Dart_Handle stdin_handle = Dart_GetNativeArgument(args, 7);
Dart_Handle stdout_handle = Dart_GetNativeArgument(args, 8);
Dart_Handle stderr_handle = Dart_GetNativeArgument(args, 9);
@ -159,7 +167,7 @@ void FUNCTION_NAME(Process_Start)(Dart_NativeArguments args) {
static_cast<ProcessStartMode>(mode), &process_stdout, &process_stdin,
&process_stderr, &pid, &exit_event, &os_error_message);
if (error_code == 0) {
if (mode != kDetached) {
if (Process::ModeHasStdio(static_cast<ProcessStartMode>(mode))) {
Socket::SetSocketIdNativeField(stdin_handle, process_stdin,
Socket::kFinalizerNormal);
Socket::SetSocketIdNativeField(stdout_handle, process_stdout,
@ -167,7 +175,7 @@ void FUNCTION_NAME(Process_Start)(Dart_NativeArguments args) {
Socket::SetSocketIdNativeField(stderr_handle, process_stderr,
Socket::kFinalizerNormal);
}
if (mode == kNormal) {
if (Process::ModeIsAttached(static_cast<ProcessStartMode>(mode))) {
Socket::SetSocketIdNativeField(exit_handle, exit_event,
Socket::kFinalizerNormal);
}

View file

@ -80,8 +80,9 @@ enum ProcessSignals {
// To be kept in sync with ProcessStartMode consts in sdk/lib/io/process.dart.
enum ProcessStartMode {
kNormal = 0,
kDetached = 1,
kDetachedWithStdio = 2,
kInheritStdio = 1,
kDetached = 2,
kDetachedWithStdio = 3,
};
class Process {
@ -153,6 +154,9 @@ class Process {
static int64_t MaxRSS();
static void GetRSSInformation(int64_t* max_rss, int64_t* current_rss);
static bool ModeIsAttached(ProcessStartMode mode);
static bool ModeHasStdio(ProcessStartMode mode);
private:
static int global_exit_code_;
static Mutex* global_exit_code_mutex_;

View file

@ -319,7 +319,7 @@ class ProcessStarter {
ExitCodeHandler::ProcessStarted();
// Register the child process if not detached.
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
err = RegisterProcess(pid);
if (err != 0) {
return err;
@ -339,7 +339,7 @@ class ProcessStarter {
// Read the result of executing the child process.
VOID_TEMP_FAILURE_RETRY(close(exec_control_[1]));
exec_control_[1] = -1;
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
err = ReadExecResult();
} else {
err = ReadDetachedExecResult(&pid);
@ -349,7 +349,7 @@ class ProcessStarter {
// Return error code if any failures.
if (err != 0) {
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
// Since exec() failed, we're not interested in the exit code.
// We close the reading side of the exit code pipe here.
// GetProcessExitCodes will get a broken pipe error when it
@ -362,7 +362,7 @@ class ProcessStarter {
return err;
}
if (mode_ != kDetached) {
if (Process::ModeHasStdio(mode_)) {
// Connect stdio, stdout and stderr.
FDUtils::SetNonBlocking(read_in_[0]);
*in_ = read_in_[0];
@ -405,7 +405,7 @@ class ProcessStarter {
}
// For detached processes the pipe to connect stderr and stdin are not used.
if (mode_ != kDetached) {
if (Process::ModeHasStdio(mode_)) {
result = TEMP_FAILURE_RETRY(pipe2(read_err_, O_CLOEXEC));
if (result < 0) {
return CleanupAndReturnError();
@ -428,7 +428,7 @@ class ProcessStarter {
perror("Failed receiving notification message");
exit(1);
}
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
ExecProcess();
} else {
ExecDetachedProcess();
@ -469,16 +469,20 @@ class ProcessStarter {
}
void ExecProcess() {
if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) {
ReportChildError();
}
if (mode_ == kNormal) {
if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) {
ReportChildError();
if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) {
ReportChildError();
}
} else {
ASSERT(mode_ == kInheritStdio);
}
if (working_directory_ != NULL &&

View file

@ -319,7 +319,7 @@ class ProcessStarter {
ExitCodeHandler::ProcessStarted();
// Register the child process if not detached.
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
err = RegisterProcess(pid);
if (err != 0) {
return err;
@ -339,7 +339,7 @@ class ProcessStarter {
// Read the result of executing the child process.
VOID_TEMP_FAILURE_RETRY(close(exec_control_[1]));
exec_control_[1] = -1;
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
err = ReadExecResult();
} else {
err = ReadDetachedExecResult(&pid);
@ -349,7 +349,7 @@ class ProcessStarter {
// Return error code if any failures.
if (err != 0) {
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
// Since exec() failed, we're not interested in the exit code.
// We close the reading side of the exit code pipe here.
// GetProcessExitCodes will get a broken pipe error when it
@ -362,7 +362,7 @@ class ProcessStarter {
return err;
}
if (mode_ != kDetached) {
if (Process::ModeHasStdio(mode_)) {
// Connect stdio, stdout and stderr.
FDUtils::SetNonBlocking(read_in_[0]);
*in_ = read_in_[0];
@ -405,7 +405,7 @@ class ProcessStarter {
}
// For detached processes the pipe to connect stderr and stdin are not used.
if (mode_ != kDetached) {
if (Process::ModeHasStdio(mode_)) {
result = TEMP_FAILURE_RETRY(pipe2(read_err_, O_CLOEXEC));
if (result < 0) {
return CleanupAndReturnError();
@ -428,7 +428,7 @@ class ProcessStarter {
perror("Failed receiving notification message");
exit(1);
}
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
ExecProcess();
} else {
ExecDetachedProcess();
@ -469,16 +469,20 @@ class ProcessStarter {
}
void ExecProcess() {
if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) {
ReportChildError();
}
if (mode_ == kNormal) {
if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) {
ReportChildError();
if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) {
ReportChildError();
}
} else {
ASSERT(mode_ == kInheritStdio);
}
if (working_directory_ != NULL &&

View file

@ -313,7 +313,7 @@ class ProcessStarter {
ExitCodeHandler::ProcessStarted();
// Register the child process if not detached.
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
err = RegisterProcess(pid);
if (err != 0) {
return err;
@ -333,7 +333,7 @@ class ProcessStarter {
// Read the result of executing the child process.
VOID_TEMP_FAILURE_RETRY(close(exec_control_[1]));
exec_control_[1] = -1;
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
err = ReadExecResult();
} else {
err = ReadDetachedExecResult(&pid);
@ -343,7 +343,7 @@ class ProcessStarter {
// Return error code if any failures.
if (err != 0) {
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
// Since exec() failed, we're not interested in the exit code.
// We close the reading side of the exit code pipe here.
// GetProcessExitCodes will get a broken pipe error when it
@ -356,7 +356,7 @@ class ProcessStarter {
return err;
}
if (mode_ != kDetached) {
if (Process::ModeHasStdio(mode_)) {
// Connect stdio, stdout and stderr.
FDUtils::SetNonBlocking(read_in_[0]);
*in_ = read_in_[0];
@ -403,7 +403,7 @@ class ProcessStarter {
FDUtils::SetCloseOnExec(read_in_[1]);
// For detached processes the pipe to connect stderr and stdin are not used.
if (mode_ != kDetached) {
if (Process::ModeHasStdio(mode_)) {
result = TEMP_FAILURE_RETRY(pipe(read_err_));
if (result < 0) {
return CleanupAndReturnError();
@ -430,7 +430,7 @@ class ProcessStarter {
perror("Failed receiving notification message");
exit(1);
}
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
ExecProcess();
} else {
ExecDetachedProcess();
@ -438,16 +438,20 @@ class ProcessStarter {
}
void ExecProcess() {
if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) {
ReportChildError();
}
if (mode_ == kNormal) {
if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) {
ReportChildError();
if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) {
ReportChildError();
}
} else {
ASSERT(mode_ == kInheritStdio);
}
if (working_directory_ != NULL &&

View file

@ -281,7 +281,7 @@ class _ProcessImpl extends _ProcessImplNativeWrapper implements Process {
}
_mode = mode;
if (mode != ProcessStartMode.DETACHED) {
if (_modeHasStdio(mode)) {
// stdin going to process.
_stdin = new _StdSink(new _Socket._writePipe().._owner = this);
// stdout coming from process.
@ -289,7 +289,7 @@ class _ProcessImpl extends _ProcessImplNativeWrapper implements Process {
// stderr coming from process.
_stderr = new _StdStream(new _Socket._readPipe().._owner = this);
}
if (mode == ProcessStartMode.NORMAL) {
if (_modeIsAttached(mode)) {
_exitHandler = new _Socket._readPipe();
}
_ended = false;
@ -303,6 +303,16 @@ class _ProcessImpl extends _ProcessImplNativeWrapper implements Process {
_NativeSocket get _stderrNativeSocket =>
(_stderr._stream as _Socket)._nativeSocket;
static bool _modeIsAttached(ProcessStartMode mode) {
return (mode == ProcessStartMode.NORMAL) ||
(mode == ProcessStartMode.INHERIT_STDIO);
}
static bool _modeHasStdio(ProcessStartMode mode) {
return (mode == ProcessStartMode.NORMAL) ||
(mode == ProcessStartMode.DETACHED_WITH_STDIO);
}
static String _getShellCommand() {
if (Platform.isWindows) {
return 'cmd.exe';
@ -390,7 +400,7 @@ class _ProcessImpl extends _ProcessImplNativeWrapper implements Process {
Future<Process> _start() {
var completer = new Completer<Process>();
if (_mode == ProcessStartMode.NORMAL) {
if (_modeIsAttached(_mode)) {
_exitCode = new Completer<int>();
}
// TODO(ager): Make the actual process starting really async instead of
@ -404,10 +414,10 @@ class _ProcessImpl extends _ProcessImplNativeWrapper implements Process {
_workingDirectory,
_environment,
_mode.index,
_mode == ProcessStartMode.DETACHED ? null : _stdinNativeSocket,
_mode == ProcessStartMode.DETACHED ? null : _stdoutNativeSocket,
_mode == ProcessStartMode.DETACHED ? null : _stderrNativeSocket,
_mode != ProcessStartMode.NORMAL ? null : _exitHandler._nativeSocket,
_modeHasStdio(_mode) ? _stdinNativeSocket : null,
_modeHasStdio(_mode) ? _stdoutNativeSocket : null,
_modeHasStdio(_mode) ? _stderrNativeSocket : null,
_modeIsAttached(_mode) ? _exitHandler._nativeSocket : null,
status);
if (!success) {
completer.completeError(new ProcessException(
@ -420,7 +430,7 @@ class _ProcessImpl extends _ProcessImplNativeWrapper implements Process {
// Setup an exit handler to handle internal cleanup and possible
// callback when a process terminates.
if (_mode == ProcessStartMode.NORMAL) {
if (_modeIsAttached(_mode)) {
int exitDataRead = 0;
final int EXIT_DATA_SIZE = 8;
List<int> exitDataBuffer = new List<int>(EXIT_DATA_SIZE);
@ -436,7 +446,9 @@ class _ProcessImpl extends _ProcessImplNativeWrapper implements Process {
_ended = true;
_exitCode.complete(exitCode(exitDataBuffer));
// Kill stdin, helping hand if the user forgot to do it.
(_stdin._sink as _Socket).destroy();
if (_modeHasStdio(_mode)) {
(_stdin._sink as _Socket).destroy();
}
_resourceInfo.stopped();
}

View file

@ -504,39 +504,41 @@ class ProcessStarter {
STARTUPINFOEXW startup_info;
ZeroMemory(&startup_info, sizeof(startup_info));
startup_info.StartupInfo.cb = sizeof(startup_info);
startup_info.StartupInfo.hStdInput = stdin_handles_[kReadHandle];
startup_info.StartupInfo.hStdOutput = stdout_handles_[kWriteHandle];
startup_info.StartupInfo.hStdError = stderr_handles_[kWriteHandle];
startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
if (mode_ != kInheritStdio) {
startup_info.StartupInfo.hStdInput = stdin_handles_[kReadHandle];
startup_info.StartupInfo.hStdOutput = stdout_handles_[kWriteHandle];
startup_info.StartupInfo.hStdError = stderr_handles_[kWriteHandle];
startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
bool supports_proc_thread_attr_lists = EnsureInitialized();
if (supports_proc_thread_attr_lists) {
// Setup the handles to inherit. We only want to inherit the three handles
// for stdin, stdout and stderr.
SIZE_T size = 0;
// The call to determine the size of an attribute list always fails with
// ERROR_INSUFFICIENT_BUFFER and that error should be ignored.
if (!init_proc_thread_attr_list(NULL, 1, 0, &size) &&
(GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
return CleanupAndReturnError();
bool supports_proc_thread_attr_lists = EnsureInitialized();
if (supports_proc_thread_attr_lists) {
// Setup the handles to inherit. We only want to inherit the three
// handles for stdin, stdout and stderr.
SIZE_T size = 0;
// The call to determine the size of an attribute list always fails with
// ERROR_INSUFFICIENT_BUFFER and that error should be ignored.
if (!init_proc_thread_attr_list(NULL, 1, 0, &size) &&
(GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
return CleanupAndReturnError();
}
attribute_list_ = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(
Dart_ScopeAllocate(size));
ZeroMemory(attribute_list_, size);
if (!init_proc_thread_attr_list(attribute_list_, 1, 0, &size)) {
return CleanupAndReturnError();
}
static const int kNumInheritedHandles = 3;
HANDLE inherited_handles[kNumInheritedHandles] = {
stdin_handles_[kReadHandle], stdout_handles_[kWriteHandle],
stderr_handles_[kWriteHandle]};
if (!update_proc_thread_attr(
attribute_list_, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
inherited_handles, kNumInheritedHandles * sizeof(HANDLE), NULL,
NULL)) {
return CleanupAndReturnError();
}
startup_info.lpAttributeList = attribute_list_;
}
attribute_list_ = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(
Dart_ScopeAllocate(size));
ZeroMemory(attribute_list_, size);
if (!init_proc_thread_attr_list(attribute_list_, 1, 0, &size)) {
return CleanupAndReturnError();
}
static const int kNumInheritedHandles = 3;
HANDLE inherited_handles[kNumInheritedHandles] = {
stdin_handles_[kReadHandle], stdout_handles_[kWriteHandle],
stderr_handles_[kWriteHandle]};
if (!update_proc_thread_attr(
attribute_list_, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
inherited_handles, kNumInheritedHandles * sizeof(HANDLE), NULL,
NULL)) {
return CleanupAndReturnError();
}
startup_info.lpAttributeList = attribute_list_;
}
PROCESS_INFORMATION process_info;
@ -545,7 +547,7 @@ class ProcessStarter {
// Create process.
DWORD creation_flags =
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT;
if (mode_ != kNormal) {
if (!Process::ModeIsAttached(mode_)) {
creation_flags |= DETACHED_PROCESS;
}
BOOL result = CreateProcessW(
@ -558,26 +560,33 @@ class ProcessStarter {
reinterpret_cast<STARTUPINFOW*>(&startup_info), &process_info);
if (result == 0) {
Log::PrintErr("CreateProcessW failed %d\n", GetLastError());
return CleanupAndReturnError();
}
CloseHandle(stdin_handles_[kReadHandle]);
CloseHandle(stdout_handles_[kWriteHandle]);
CloseHandle(stderr_handles_[kWriteHandle]);
if (mode_ == kNormal) {
if (mode_ != kInheritStdio) {
CloseHandle(stdin_handles_[kReadHandle]);
CloseHandle(stdout_handles_[kWriteHandle]);
CloseHandle(stderr_handles_[kWriteHandle]);
}
if (Process::ModeIsAttached(mode_)) {
ProcessInfoList::AddProcess(process_info.dwProcessId,
process_info.hProcess,
exit_handles_[kWriteHandle]);
}
if (mode_ != kDetached) {
// Connect the three stdio streams.
FileHandle* stdin_handle = new FileHandle(stdin_handles_[kWriteHandle]);
FileHandle* stdout_handle = new FileHandle(stdout_handles_[kReadHandle]);
FileHandle* stderr_handle = new FileHandle(stderr_handles_[kReadHandle]);
*in_ = reinterpret_cast<intptr_t>(stdout_handle);
*out_ = reinterpret_cast<intptr_t>(stdin_handle);
*err_ = reinterpret_cast<intptr_t>(stderr_handle);
if (mode_ == kNormal) {
if (Process::ModeHasStdio(mode_)) {
FileHandle* stdin_handle = new FileHandle(stdin_handles_[kWriteHandle]);
FileHandle* stdout_handle =
new FileHandle(stdout_handles_[kReadHandle]);
FileHandle* stderr_handle =
new FileHandle(stderr_handles_[kReadHandle]);
*in_ = reinterpret_cast<intptr_t>(stdout_handle);
*out_ = reinterpret_cast<intptr_t>(stdin_handle);
*err_ = reinterpret_cast<intptr_t>(stderr_handle);
}
if (Process::ModeIsAttached(mode_)) {
FileHandle* exit_handle = new FileHandle(exit_handles_[kReadHandle]);
*exit_handler_ = reinterpret_cast<intptr_t>(exit_handle);
}
@ -603,13 +612,15 @@ class ProcessStarter {
if (mode_ != kDetached) {
// Open pipes for stdin, stdout, stderr and for communicating the exit
// code.
if (!CreateProcessPipe(stdin_handles_, pipe_names[0], kInheritRead) ||
!CreateProcessPipe(stdout_handles_, pipe_names[1], kInheritWrite) ||
!CreateProcessPipe(stderr_handles_, pipe_names[2], kInheritWrite)) {
return CleanupAndReturnError();
if (Process::ModeHasStdio(mode_)) {
if (!CreateProcessPipe(stdin_handles_, pipe_names[0], kInheritRead) ||
!CreateProcessPipe(stdout_handles_, pipe_names[1], kInheritWrite) ||
!CreateProcessPipe(stderr_handles_, pipe_names[2], kInheritWrite)) {
return CleanupAndReturnError();
}
}
// Only open exit code pipe for non detached processes.
if (mode_ == kNormal) {
if (Process::ModeIsAttached(mode_)) {
if (!CreateProcessPipe(exit_handles_, pipe_names[3], kInheritNone)) {
return CleanupAndReturnError();
}

View file

@ -141,12 +141,15 @@ enum ProcessStartMode {
/// Normal child process.
NORMAL,
/// Stdio handles are inherited by the child process.
INHERIT_STDIO,
/// Detached child process with no open communication channel.
DETACHED,
/// Detached child process with stdin, stdout and stderr still open
/// for communication with the child.
DETACHED_WITH_STDIO
DETACHED_WITH_STDIO,
}
/**

View file

@ -0,0 +1,29 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import "package:async_helper/async_helper.dart";
import "process_test_util.dart";
void main(List<String> args) {
String arg = args[0];
if (arg == "--child") {
print(args[1]);
return;
}
asyncStart();
var script =
Platform.script.resolve('process_inherit_stdio_script.dart').toFilePath();
var future = Process.start(Platform.executable, [script, "--child", "foo"],
mode: ProcessStartMode.INHERIT_STDIO);
future.then((process) {
process.exitCode.then((c) {
asyncEnd();
});
});
}

View file

@ -0,0 +1,39 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// OtherResources=process_inherit_stdio_script.dart
// Process test program to test 'inherit stdio' processes.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import "package:async_helper/async_helper.dart";
import "package:expect/expect.dart";
import "process_test_util.dart";
main() {
asyncStart();
// process_inherit_stdio_script.dart spawns a process in INHERIT_STDIO mode
// that prints to its stdout. Since that child process inherits the stdout
// of the process spawned here, we should see it.
var script =
Platform.script.resolve('process_inherit_stdio_script.dart').toFilePath();
var future = Process.start(Platform.executable, [script, "foo"]);
Completer<String> s = new Completer();
future.then((process) {
StringBuffer buf = new StringBuffer();
process.stdout.transform(UTF8.decoder).listen((data) {
buf.write(data);
}, onDone: () {
s.complete(buf.toString());
});
});
s.future.then((String result) {
Expect.isTrue(result.contains("foo"));
asyncEnd();
});
}