From 959456533fdbd29310cd9cba2d29efcbd33f2391 Mon Sep 17 00:00:00 2001 From: "sgjesse@google.com" Date: Tue, 13 Aug 2013 14:12:31 +0000 Subject: [PATCH] Add Process.runSync for running processes synchronously BUG=http://dartbug.com/1707 R=whesse@google.com Review URL: https://codereview.chromium.org//21816002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@26052 260f80e4-7a28-3924-810f-c04153c831b5 --- runtime/bin/eventhandler_win.cc | 102 +++++----- runtime/bin/eventhandler_win.h | 43 ++-- runtime/bin/io_buffer.cc | 1 + runtime/bin/io_natives.cc | 1 + runtime/bin/process.cc | 42 ++++ runtime/bin/process.h | 137 +++++++++++++ runtime/bin/process_linux.cc | 121 ++++++++++++ runtime/bin/process_macos.cc | 124 ++++++++++++ runtime/bin/process_patch.dart | 79 ++++++++ runtime/bin/process_win.cc | 195 +++++++++++++++++++ sdk/lib/_internal/lib/io_patch.dart | 12 ++ sdk/lib/io/process.dart | 20 ++ tests/standalone/io/process_sync_script.dart | 24 +++ tests/standalone/io/process_sync_test.dart | 63 ++++++ 14 files changed, 894 insertions(+), 70 deletions(-) create mode 100644 tests/standalone/io/process_sync_script.dart create mode 100644 tests/standalone/io/process_sync_test.dart diff --git a/runtime/bin/eventhandler_win.cc b/runtime/bin/eventhandler_win.cc index f6e91c43692..872a815ebeb 100644 --- a/runtime/bin/eventhandler_win.cc +++ b/runtime/bin/eventhandler_win.cc @@ -26,51 +26,54 @@ namespace dart { namespace bin { static const int kBufferSize = 64 * 1024; -static const int kStdioBufferSize = 16 * 1024; +static const int kStdOverlappedBufferSize = 16 * 1024; static const int kInfinityTimeout = -1; static const int kTimeoutId = -1; static const int kShutdownId = -2; -IOBuffer* IOBuffer::AllocateBuffer(int buffer_size, Operation operation) { - IOBuffer* buffer = new(buffer_size) IOBuffer(buffer_size, operation); +OverlappedBuffer* OverlappedBuffer::AllocateBuffer(int buffer_size, + Operation operation) { + OverlappedBuffer* buffer = + new(buffer_size) OverlappedBuffer(buffer_size, operation); return buffer; } -IOBuffer* IOBuffer::AllocateAcceptBuffer(int buffer_size) { - IOBuffer* buffer = AllocateBuffer(buffer_size, kAccept); +OverlappedBuffer* OverlappedBuffer::AllocateAcceptBuffer(int buffer_size) { + OverlappedBuffer* buffer = AllocateBuffer(buffer_size, kAccept); return buffer; } -IOBuffer* IOBuffer::AllocateReadBuffer(int buffer_size) { +OverlappedBuffer* OverlappedBuffer::AllocateReadBuffer(int buffer_size) { return AllocateBuffer(buffer_size, kRead); } -IOBuffer* IOBuffer::AllocateWriteBuffer(int buffer_size) { +OverlappedBuffer* OverlappedBuffer::AllocateWriteBuffer(int buffer_size) { return AllocateBuffer(buffer_size, kWrite); } -IOBuffer* IOBuffer::AllocateDisconnectBuffer() { +OverlappedBuffer* OverlappedBuffer::AllocateDisconnectBuffer() { return AllocateBuffer(0, kDisconnect); } -void IOBuffer::DisposeBuffer(IOBuffer* buffer) { +void OverlappedBuffer::DisposeBuffer(OverlappedBuffer* buffer) { delete buffer; } -IOBuffer* IOBuffer::GetFromOverlapped(OVERLAPPED* overlapped) { - IOBuffer* buffer = CONTAINING_RECORD(overlapped, IOBuffer, overlapped_); +OverlappedBuffer* OverlappedBuffer::GetFromOverlapped(OVERLAPPED* overlapped) { + OverlappedBuffer* buffer = + CONTAINING_RECORD(overlapped, OverlappedBuffer, overlapped_); return buffer; } -int IOBuffer::Read(void* buffer, int num_bytes) { +int OverlappedBuffer::Read(void* buffer, int num_bytes) { if (num_bytes > GetRemainingLength()) { num_bytes = GetRemainingLength(); } @@ -80,7 +83,7 @@ int IOBuffer::Read(void* buffer, int num_bytes) { } -int IOBuffer::Write(const void* buffer, int num_bytes) { +int OverlappedBuffer::Write(const void* buffer, int num_bytes) { ASSERT(num_bytes == buflen_); memcpy(GetBufferStart(), buffer, num_bytes); data_length_ = num_bytes; @@ -88,7 +91,7 @@ int IOBuffer::Write(const void* buffer, int num_bytes) { } -int IOBuffer::GetRemainingLength() { +int OverlappedBuffer::GetRemainingLength() { ASSERT(operation_ == kRead); return data_length_ - index_; } @@ -183,7 +186,7 @@ bool Handle::HasPendingWrite() { } -void Handle::ReadComplete(IOBuffer* buffer) { +void Handle::ReadComplete(OverlappedBuffer* buffer) { ScopedLock lock(this); // Currently only one outstanding read at the time. ASSERT(pending_read_ == buffer); @@ -191,17 +194,17 @@ void Handle::ReadComplete(IOBuffer* buffer) { if (!IsClosing() && !buffer->IsEmpty()) { data_ready_ = pending_read_; } else { - IOBuffer::DisposeBuffer(buffer); + OverlappedBuffer::DisposeBuffer(buffer); } pending_read_ = NULL; } -void Handle::WriteComplete(IOBuffer* buffer) { +void Handle::WriteComplete(OverlappedBuffer* buffer) { ScopedLock lock(this); // Currently only one outstanding write at the time. ASSERT(pending_write_ == buffer); - IOBuffer::DisposeBuffer(buffer); + OverlappedBuffer::DisposeBuffer(buffer); pending_write_ = NULL; } @@ -215,11 +218,11 @@ static unsigned int __stdcall ReadFileThread(void* args) { void Handle::ReadSyncCompleteAsync() { ASSERT(pending_read_ != NULL); - ASSERT(pending_read_->GetBufferSize() >= kStdioBufferSize); + ASSERT(pending_read_->GetBufferSize() >= kStdOverlappedBufferSize); DWORD buffer_size = pending_read_->GetBufferSize(); if (GetFileType(handle_) == FILE_TYPE_CHAR) { - buffer_size = kStdioBufferSize; + buffer_size = kStdOverlappedBufferSize; } DWORD bytes_read = 0; BOOL ok = ReadFile(handle_, @@ -248,7 +251,7 @@ bool Handle::IssueRead() { ScopedLock lock(this); ASSERT(type_ != kListenSocket); ASSERT(pending_read_ == NULL); - IOBuffer* buffer = IOBuffer::AllocateReadBuffer(kBufferSize); + OverlappedBuffer* buffer = OverlappedBuffer::AllocateReadBuffer(kBufferSize); if (SupportsOverlappedIO()) { ASSERT(completion_port_ != INVALID_HANDLE_VALUE); @@ -262,7 +265,7 @@ bool Handle::IssueRead() { pending_read_ = buffer; return true; } - IOBuffer::DisposeBuffer(buffer); + OverlappedBuffer::DisposeBuffer(buffer); HandleIssueError(); return false; } else { @@ -284,9 +287,9 @@ bool Handle::IssueWrite() { ASSERT(type_ != kListenSocket); ASSERT(completion_port_ != INVALID_HANDLE_VALUE); ASSERT(pending_write_ != NULL); - ASSERT(pending_write_->operation() == IOBuffer::kWrite); + ASSERT(pending_write_->operation() == OverlappedBuffer::kWrite); - IOBuffer* buffer = pending_write_; + OverlappedBuffer* buffer = pending_write_; BOOL ok = WriteFile(handle_, buffer->GetBufferStart(), buffer->GetBufferSize(), @@ -297,7 +300,7 @@ bool Handle::IssueWrite() { pending_write_ = buffer; return true; } - IOBuffer::DisposeBuffer(buffer); + OverlappedBuffer::DisposeBuffer(buffer); HandleIssueError(); return false; } @@ -383,8 +386,8 @@ bool ListenSocket::IssueAccept() { static const int kAcceptExAddressAdditionalBytes = 16; static const int kAcceptExAddressStorageSize = sizeof(SOCKADDR_STORAGE) + kAcceptExAddressAdditionalBytes; - IOBuffer* buffer = - IOBuffer::AllocateAcceptBuffer(2 * kAcceptExAddressStorageSize); + OverlappedBuffer* buffer = + OverlappedBuffer::AllocateAcceptBuffer(2 * kAcceptExAddressStorageSize); DWORD received; BOOL ok; ok = AcceptEx_(socket(), @@ -399,7 +402,7 @@ bool ListenSocket::IssueAccept() { if (WSAGetLastError() != WSA_IO_PENDING) { Log::PrintErr("AcceptEx failed: %d\n", WSAGetLastError()); closesocket(buffer->client()); - IOBuffer::DisposeBuffer(buffer); + OverlappedBuffer::DisposeBuffer(buffer); return false; } } @@ -410,7 +413,8 @@ bool ListenSocket::IssueAccept() { } -void ListenSocket::AcceptComplete(IOBuffer* buffer, HANDLE completion_port) { +void ListenSocket::AcceptComplete(OverlappedBuffer* buffer, + HANDLE completion_port) { ScopedLock lock(this); if (!IsClosing()) { // Update the accepted socket to support the full range of API calls. @@ -441,7 +445,7 @@ void ListenSocket::AcceptComplete(IOBuffer* buffer, HANDLE completion_port) { } pending_accept_count_--; - IOBuffer::DisposeBuffer(buffer); + OverlappedBuffer::DisposeBuffer(buffer); } @@ -508,7 +512,7 @@ int Handle::Read(void* buffer, int num_bytes) { if (data_ready_ == NULL) return 0; num_bytes = data_ready_->Read(buffer, num_bytes); if (data_ready_->IsEmpty()) { - IOBuffer::DisposeBuffer(data_ready_); + OverlappedBuffer::DisposeBuffer(data_ready_); data_ready_ = NULL; } return num_bytes; @@ -521,7 +525,7 @@ int Handle::Write(const void* buffer, int num_bytes) { if (pending_write_ != NULL) return 0; if (completion_port_ == INVALID_HANDLE_VALUE) return 0; if (num_bytes > kBufferSize) num_bytes = kBufferSize; - pending_write_ = IOBuffer::AllocateWriteBuffer(num_bytes); + pending_write_ = OverlappedBuffer::AllocateWriteBuffer(num_bytes); pending_write_->Write(buffer, num_bytes); if (!IssueWrite()) return -1; return num_bytes; @@ -587,7 +591,7 @@ bool ClientSocket::IssueRead() { ASSERT(completion_port_ != INVALID_HANDLE_VALUE); ASSERT(pending_read_ == NULL); - IOBuffer* buffer = IOBuffer::AllocateReadBuffer(1024); + OverlappedBuffer* buffer = OverlappedBuffer::AllocateReadBuffer(1024); DWORD flags; flags = 0; @@ -602,7 +606,7 @@ bool ClientSocket::IssueRead() { pending_read_ = buffer; return true; } - IOBuffer::DisposeBuffer(buffer); + OverlappedBuffer::DisposeBuffer(buffer); pending_read_ = NULL; HandleIssueError(); return false; @@ -613,7 +617,7 @@ bool ClientSocket::IssueWrite() { ScopedLock lock(this); ASSERT(completion_port_ != INVALID_HANDLE_VALUE); ASSERT(pending_write_ != NULL); - ASSERT(pending_write_->operation() == IOBuffer::kWrite); + ASSERT(pending_write_->operation() == OverlappedBuffer::kWrite); int rc = WSASend(socket(), pending_write_->GetWASBUF(), @@ -625,7 +629,7 @@ bool ClientSocket::IssueWrite() { if (rc == NO_ERROR || WSAGetLastError() == WSA_IO_PENDING) { return true; } - IOBuffer::DisposeBuffer(pending_write_); + OverlappedBuffer::DisposeBuffer(pending_write_); pending_write_ = NULL; HandleIssueError(); return false; @@ -634,7 +638,7 @@ bool ClientSocket::IssueWrite() { void ClientSocket::IssueDisconnect() { Dart_Port p = port(); - IOBuffer* buffer = IOBuffer::AllocateDisconnectBuffer(); + OverlappedBuffer* buffer = OverlappedBuffer::AllocateDisconnectBuffer(); BOOL ok = DisconnectEx_( socket(), buffer->GetCleanOverlapped(), TF_REUSE_SOCKET, 0); if (!ok && WSAGetLastError() != WSA_IO_PENDING) { @@ -644,11 +648,11 @@ void ClientSocket::IssueDisconnect() { } -void ClientSocket::DisconnectComplete(IOBuffer* buffer) { - IOBuffer::DisposeBuffer(buffer); +void ClientSocket::DisconnectComplete(OverlappedBuffer* buffer) { + OverlappedBuffer::DisposeBuffer(buffer); closesocket(socket()); if (data_ready_ != NULL) { - IOBuffer::DisposeBuffer(data_ready_); + OverlappedBuffer::DisposeBuffer(data_ready_); } // When disconnect is complete get rid of the object. delete this; @@ -776,7 +780,7 @@ void EventHandlerImplementation::HandleInterrupt(InterruptMessage* msg) { void EventHandlerImplementation::HandleAccept(ListenSocket* listen_socket, - IOBuffer* buffer) { + OverlappedBuffer* buffer) { listen_socket->AcceptComplete(buffer, completion_port_); if (!listen_socket->IsClosing()) { @@ -810,7 +814,7 @@ void EventHandlerImplementation::HandleError(Handle* handle) { void EventHandlerImplementation::HandleRead(Handle* handle, int bytes, - IOBuffer* buffer) { + OverlappedBuffer* buffer) { buffer->set_data_length(bytes); handle->ReadComplete(buffer); if (bytes > 0) { @@ -835,7 +839,7 @@ void EventHandlerImplementation::HandleRead(Handle* handle, void EventHandlerImplementation::HandleWrite(Handle* handle, int bytes, - IOBuffer* buffer) { + OverlappedBuffer* buffer) { handle->WriteComplete(buffer); if (bytes > 0) { @@ -858,7 +862,7 @@ void EventHandlerImplementation::HandleWrite(Handle* handle, void EventHandlerImplementation::HandleDisconnect( ClientSocket* client_socket, int bytes, - IOBuffer* buffer) { + OverlappedBuffer* buffer) { client_socket->DisconnectComplete(buffer); } @@ -872,24 +876,24 @@ void EventHandlerImplementation::HandleTimeout() { void EventHandlerImplementation::HandleIOCompletion(DWORD bytes, ULONG_PTR key, OVERLAPPED* overlapped) { - IOBuffer* buffer = IOBuffer::GetFromOverlapped(overlapped); + OverlappedBuffer* buffer = OverlappedBuffer::GetFromOverlapped(overlapped); switch (buffer->operation()) { - case IOBuffer::kAccept: { + case OverlappedBuffer::kAccept: { ListenSocket* listen_socket = reinterpret_cast(key); HandleAccept(listen_socket, buffer); break; } - case IOBuffer::kRead: { + case OverlappedBuffer::kRead: { Handle* handle = reinterpret_cast(key); HandleRead(handle, bytes, buffer); break; } - case IOBuffer::kWrite: { + case OverlappedBuffer::kWrite: { Handle* handle = reinterpret_cast(key); HandleWrite(handle, bytes, buffer); break; } - case IOBuffer::kDisconnect: { + case OverlappedBuffer::kDisconnect: { ClientSocket* client_socket = reinterpret_cast(key); HandleDisconnect(client_socket, bytes, buffer); break; diff --git a/runtime/bin/eventhandler_win.h b/runtime/bin/eventhandler_win.h index 280175cbbdd..1c622008538 100644 --- a/runtime/bin/eventhandler_win.h +++ b/runtime/bin/eventhandler_win.h @@ -34,21 +34,21 @@ struct InterruptMessage { }; -// An IOBuffer encapsulates the OVERLAPPED structure and the +// An OverlappedBuffer encapsulates the OVERLAPPED structure and the // associated data buffer. For accept it also contains the pre-created // socket for the client. -class IOBuffer { +class OverlappedBuffer { public: enum Operation { kAccept, kRead, kWrite, kDisconnect }; - static IOBuffer* AllocateAcceptBuffer(int buffer_size); - static IOBuffer* AllocateReadBuffer(int buffer_size); - static IOBuffer* AllocateWriteBuffer(int buffer_size); - static IOBuffer* AllocateDisconnectBuffer(); - static void DisposeBuffer(IOBuffer* buffer); + static OverlappedBuffer* AllocateAcceptBuffer(int buffer_size); + static OverlappedBuffer* AllocateReadBuffer(int buffer_size); + static OverlappedBuffer* AllocateWriteBuffer(int buffer_size); + static OverlappedBuffer* AllocateDisconnectBuffer(); + static void DisposeBuffer(OverlappedBuffer* buffer); // Find the IO buffer from the OVERLAPPED address. - static IOBuffer* GetFromOverlapped(OVERLAPPED* overlapped); + static OverlappedBuffer* GetFromOverlapped(OVERLAPPED* overlapped); // Read data from a buffer which has been received. It will read up // to num_bytes bytes of data returning the actual number of bytes @@ -88,7 +88,7 @@ class IOBuffer { void set_data_length(int data_length) { data_length_ = data_length; } private: - IOBuffer(int buffer_size, Operation operation) + OverlappedBuffer(int buffer_size, Operation operation) : operation_(operation), buflen_(buffer_size) { memset(GetBufferStart(), 0, GetBufferSize()); index_ = 0; @@ -106,7 +106,8 @@ class IOBuffer { free(buffer); } - static IOBuffer* AllocateBuffer(int buffer_size, Operation operation); + static OverlappedBuffer* AllocateBuffer(int buffer_size, + Operation operation); OVERLAPPED overlapped_; // OVERLAPPED structure for overlapped IO. SOCKET client_; // Used for AcceptEx client socket. @@ -157,8 +158,8 @@ class Handle { virtual bool IssueWrite(); bool HasPendingRead(); bool HasPendingWrite(); - void ReadComplete(IOBuffer* buffer); - void WriteComplete(IOBuffer* buffer); + void ReadComplete(OverlappedBuffer* buffer); + void WriteComplete(OverlappedBuffer* buffer); bool IsClosing() { return (flags_ & (1 << kClosing)) != 0; } bool IsClosedRead() { return (flags_ & (1 << kCloseRead)) != 0; } @@ -230,9 +231,9 @@ class Handle { HANDLE completion_port_; EventHandlerImplementation* event_handler_; - IOBuffer* data_ready_; // IO buffer for data ready to be read. - IOBuffer* pending_read_; // IO buffer for pending read. - IOBuffer* pending_write_; // IO buffer for pending write + OverlappedBuffer* data_ready_; // Buffer for data ready to be read. + OverlappedBuffer* pending_read_; // Buffer for pending read. + OverlappedBuffer* pending_write_; // Buffer for pending write DWORD last_error_; @@ -291,7 +292,7 @@ class ListenSocket : public SocketHandle { // Internal interface used by the event handler. bool HasPendingAccept() { return pending_accept_count_ > 0; } bool IssueAccept(); - void AcceptComplete(IOBuffer* buffer, HANDLE completion_port); + void AcceptComplete(OverlappedBuffer* buffer, HANDLE completion_port); virtual void EnsureInitialized( EventHandlerImplementation* event_handler); @@ -342,7 +343,7 @@ class ClientSocket : public SocketHandle { virtual bool IssueRead(); virtual bool IssueWrite(); void IssueDisconnect(); - void DisconnectComplete(IOBuffer* buffer); + void DisconnectComplete(OverlappedBuffer* buffer); virtual void EnsureInitialized( EventHandlerImplementation* event_handler); @@ -375,14 +376,14 @@ class EventHandlerImplementation { int64_t GetTimeout(); void HandleInterrupt(InterruptMessage* msg); void HandleTimeout(); - void HandleAccept(ListenSocket* listen_socket, IOBuffer* buffer); + void HandleAccept(ListenSocket* listen_socket, OverlappedBuffer* buffer); void HandleClosed(Handle* handle); void HandleError(Handle* handle); - void HandleRead(Handle* handle, int bytes, IOBuffer* buffer); - void HandleWrite(Handle* handle, int bytes, IOBuffer* buffer); + void HandleRead(Handle* handle, int bytes, OverlappedBuffer* buffer); + void HandleWrite(Handle* handle, int bytes, OverlappedBuffer* buffer); void HandleDisconnect(ClientSocket* client_socket, int bytes, - IOBuffer* buffer); + OverlappedBuffer* buffer); void HandleIOCompletion(DWORD bytes, ULONG_PTR key, OVERLAPPED* overlapped); HANDLE completion_port() { return completion_port_; } diff --git a/runtime/bin/io_buffer.cc b/runtime/bin/io_buffer.cc index dbc10476f37..ef08b4f2045 100644 --- a/runtime/bin/io_buffer.cc +++ b/runtime/bin/io_buffer.cc @@ -24,6 +24,7 @@ Dart_Handle IOBuffer::Allocate(intptr_t size, uint8_t **buffer) { return result; } + uint8_t* IOBuffer::Allocate(intptr_t size) { return new uint8_t[size]; } diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc index 8fe11218ddb..a5c71709747 100644 --- a/runtime/bin/io_natives.cc +++ b/runtime/bin/io_natives.cc @@ -37,6 +37,7 @@ namespace bin { V(Platform_Environment, 0) \ V(Platform_GetVersion, 0) \ V(Process_Start, 10) \ + V(Process_Wait, 5) \ V(Process_Kill, 3) \ V(Process_SetExitCode, 1) \ V(Process_Exit, 1) \ diff --git a/runtime/bin/process.cc b/runtime/bin/process.cc index 21750f1ea9c..0820f842209 100644 --- a/runtime/bin/process.cc +++ b/runtime/bin/process.cc @@ -159,6 +159,48 @@ void FUNCTION_NAME(Process_Start)(Dart_NativeArguments args) { } +void FUNCTION_NAME(Process_Wait)(Dart_NativeArguments args) { + Dart_Handle process = Dart_GetNativeArgument(args, 0); + Dart_Handle stdin_handle = Dart_GetNativeArgument(args, 1); + Dart_Handle stdout_handle = Dart_GetNativeArgument(args, 2); + Dart_Handle stderr_handle = Dart_GetNativeArgument(args, 3); + Dart_Handle exit_handle = Dart_GetNativeArgument(args, 4); + intptr_t process_stdin; + intptr_t process_stdout; + intptr_t process_stderr; + intptr_t exit_event; + Socket::GetSocketIdNativeField(stdin_handle, &process_stdin); + Socket::GetSocketIdNativeField(stdout_handle, &process_stdout); + Socket::GetSocketIdNativeField(stderr_handle, &process_stderr); + Socket::GetSocketIdNativeField(exit_handle, &exit_event); + ProcessResult result; + intptr_t pid; + Process::GetProcessIdNativeField(process, &pid); + if (Process::Wait(pid, + process_stdin, + process_stdout, + process_stderr, + exit_event, + &result)) { + Dart_Handle out = result.stdout_data(); + if (Dart_IsError(out)) Dart_PropagateError(out); + Dart_Handle err = result.stderr_data(); + if (Dart_IsError(err)) Dart_PropagateError(err); + Dart_Handle list = Dart_NewList(4); + Dart_ListSetAt(list, 0, Dart_NewInteger(pid)); + Dart_ListSetAt(list, 1, Dart_NewInteger(result.exit_code())); + Dart_ListSetAt(list, 2, out); + Dart_ListSetAt(list, 3, err); + Dart_SetReturnValue(args, list); + } else { + Dart_Handle error = DartUtils::NewDartOSError(); + Process::Kill(pid, 9); + if (Dart_IsError(error)) Dart_PropagateError(error); + Dart_ThrowException(error); + } +} + + void FUNCTION_NAME(Process_Kill)(Dart_NativeArguments args) { Dart_Handle process = Dart_GetNativeArgument(args, 1); intptr_t pid = -1; diff --git a/runtime/bin/process.h b/runtime/bin/process.h index 8e82f394aa7..02a9ecc8453 100644 --- a/runtime/bin/process.h +++ b/runtime/bin/process.h @@ -6,13 +6,41 @@ #define BIN_PROCESS_H_ #include "bin/builtin.h" +#include "bin/io_buffer.h" #include "bin/thread.h" #include "platform/globals.h" +#include "platform/utils.h" namespace dart { namespace bin { +class ProcessResult { + public: + ProcessResult() : exit_code_(0) {} + + void set_stdout_data(Dart_Handle stdout_data) { + stdout_data_ = stdout_data; + } + void set_stderr_data(Dart_Handle stderr_data) { + stderr_data_ = stderr_data; + } + + void set_exit_code(intptr_t exit_code) { exit_code_ = exit_code; } + + Dart_Handle stdout_data() { return stdout_data_; } + Dart_Handle stderr_data() { return stderr_data_; } + intptr_t exit_code() { return exit_code_; } + + private: + Dart_Handle stdout_data_; + Dart_Handle stderr_data_; + intptr_t exit_code_; + + DISALLOW_ALLOCATION(); +}; + + class Process { public: // Start a new process providing access to stdin, stdout, stderr and @@ -30,6 +58,13 @@ class Process { intptr_t* exit_handler, char** os_error_message); + static bool Wait(intptr_t id, + intptr_t in, + intptr_t out, + intptr_t err, + intptr_t exit_handler, + ProcessResult* result); + // Kill a process with a given pid. static bool Kill(intptr_t id, int signal); @@ -62,6 +97,108 @@ class Process { DISALLOW_IMPLICIT_CONSTRUCTORS(Process); }; + +// Utility class for collecting the output when running a process +// synchronously by using Process::Wait. This class is sub-classed in +// the platform specific files to implement reading into the buffers +// allocated. +class BufferListBase { + protected: + static const intptr_t kBufferSize = 16 * 1024; + + class BufferListNode { + public: + explicit BufferListNode(intptr_t size) { + data_ = new uint8_t[size]; + if (data_ == NULL) FATAL("Allocation failed"); + next_ = NULL; + } + + ~BufferListNode() { + delete[] data_; + } + + uint8_t* data_; + BufferListNode* next_; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(BufferListNode); + }; + + public: + BufferListBase() : head_(NULL), tail_(NULL), data_size_(0), free_size_(0) {} + ~BufferListBase() { + ASSERT(head_ == NULL); + ASSERT(tail_ == NULL); + } + + // Returns the collected data as a Uint8List. If an error occours an + // error handle is returned. + Dart_Handle GetData() { + uint8_t* buffer; + intptr_t buffer_position = 0; + Dart_Handle result = IOBuffer::Allocate(data_size_, &buffer); + if (Dart_IsError(result)) { + Free(); + return result; + } + for (BufferListNode* current = head_; + current != NULL; + current = current->next_) { + intptr_t to_copy = dart::Utils::Minimum(data_size_, kBufferSize); + memmove(buffer + buffer_position, current->data_, to_copy); + buffer_position += to_copy; + data_size_ -= to_copy; + } + ASSERT(data_size_ == 0); + Free(); + return result; + } + + protected: + void Allocate() { + ASSERT(free_size_ == 0); + BufferListNode* node = new BufferListNode(kBufferSize); + if (head_ == NULL) { + head_ = node; + tail_ = node; + } else { + ASSERT(tail_->next_ == NULL); + tail_->next_ = node; + tail_ = node; + } + free_size_ = kBufferSize; + } + + void Free() { + BufferListNode* current = head_; + while (current != NULL) { + BufferListNode* tmp = current; + current = current->next_; + delete tmp; + } + head_ = NULL; + tail_ = NULL; + data_size_ = 0; + free_size_ = 0; + } + + // Returns the address of the first byte in the free space. + uint8_t* FreeSpaceAddress() { + return tail_->data_ + (kBufferSize - free_size_); + } + + // Linked list for data collected. + BufferListNode* head_; + BufferListNode* tail_; + + // Number of bytes of data collected in the linked list. + intptr_t data_size_; + + // Number of free bytes in the last node in the list. + intptr_t free_size_; +}; + } // namespace bin } // namespace dart diff --git a/runtime/bin/process_linux.cc b/runtime/bin/process_linux.cc index 6102e704e6c..595861f2c17 100644 --- a/runtime/bin/process_linux.cc +++ b/runtime/bin/process_linux.cc @@ -21,6 +21,7 @@ #include "bin/log.h" #include "bin/thread.h" + extern char **environ; @@ -568,6 +569,126 @@ int Process::Start(const char* path, } +class BufferList: public BufferListBase { + public: + bool Read(int fd, intptr_t available) { + // Read all available bytes. + while (available > 0) { + if (free_size_ == 0) Allocate(); + ASSERT(free_size_ > 0); + ASSERT(free_size_ <= kBufferSize); + intptr_t block_size = dart::Utils::Minimum(free_size_, available); + intptr_t bytes = TEMP_FAILURE_RETRY(read( + fd, + reinterpret_cast(FreeSpaceAddress()), + block_size)); + if (bytes < 0) return false; + data_size_ += bytes; + free_size_ -= bytes; + available -= bytes; + } + return true; + } +}; + + +static bool CloseProcessBuffers(struct pollfd fds[3]) { + int e = errno; + VOID_TEMP_FAILURE_RETRY(close(fds[0].fd)); + VOID_TEMP_FAILURE_RETRY(close(fds[1].fd)); + VOID_TEMP_FAILURE_RETRY(close(fds[2].fd)); + errno = e; + return false; +} + + +bool Process::Wait(intptr_t pid, + intptr_t in, + intptr_t out, + intptr_t err, + intptr_t exit_event, + ProcessResult* result) { + // Close input to the process right away. + VOID_TEMP_FAILURE_RETRY(close(in)); + + // There is no return from this function using Dart_PropagateError + // as memory used by the buffer lists is freed through their + // destructors. + BufferList out_data; + BufferList err_data; + union { + uint8_t bytes[8]; + int32_t ints[2]; + } exit_code_data; + + struct pollfd fds[3]; + fds[0].fd = out; + fds[1].fd = err; + fds[2].fd = exit_event; + + for (int i = 0; i < 3; i++) { + fds[i].events = POLLIN; + } + + int alive = 3; + while (alive > 0) { + // Blocking call waiting for events from the child process. + if (TEMP_FAILURE_RETRY(poll(fds, alive, -1)) <= 0) { + return CloseProcessBuffers(fds); + } + + // Process incoming data. + for (int i = 0; i < alive; i++) { + if (fds[i].revents & POLLIN) { + intptr_t avail = FDUtils::AvailableBytes(fds[i].fd); + if (fds[i].fd == out) { + if (!out_data.Read(out, avail)) { + return CloseProcessBuffers(fds); + } + } else if (fds[i].fd == err) { + if (!err_data.Read(err, avail)) { + return CloseProcessBuffers(fds); + } + } else if (fds[i].fd == exit_event) { + if (avail == 8) { + intptr_t b = TEMP_FAILURE_RETRY(read(exit_event, + exit_code_data.bytes, 8)); + if (b != 8) { + return CloseProcessBuffers(fds); + } + } + } else { + UNREACHABLE(); + } + } + } + + // Process closed. + for (int i = 0; i < alive; i++) { + if (fds[i].revents & POLLHUP) { + VOID_TEMP_FAILURE_RETRY(close(fds[i].fd)); + alive--; + if (i < alive) { + fds[i] = fds[alive]; + } + } + } + } + + // All handles closed and all data read. + result->set_stdout_data(out_data.GetData()); + result->set_stderr_data(err_data.GetData()); + + // Calculate the exit code. + intptr_t exit_code = exit_code_data.ints[0]; + intptr_t negative = exit_code_data.ints[1]; + if (negative) exit_code = -exit_code; + result->set_exit_code(exit_code); + + return true; +} + + bool Process::Kill(intptr_t id, int signal) { return (TEMP_FAILURE_RETRY(kill(id, signal)) != -1); } diff --git a/runtime/bin/process_macos.cc b/runtime/bin/process_macos.cc index c87523c3919..1d08cacf8ce 100644 --- a/runtime/bin/process_macos.cc +++ b/runtime/bin/process_macos.cc @@ -567,6 +567,130 @@ int Process::Start(const char* path, } +class BufferList: public BufferListBase { + public: + bool Read(int fd, intptr_t available) { + // Read all available bytes. + while (available > 0) { + if (free_size_ == 0) Allocate(); + ASSERT(free_size_ > 0); + ASSERT(free_size_ <= kBufferSize); + intptr_t block_size = dart::Utils::Minimum(free_size_, available); + intptr_t bytes = TEMP_FAILURE_RETRY(read( + fd, + reinterpret_cast(FreeSpaceAddress()), + block_size)); + if (bytes < 0) return false; + data_size_ += bytes; + free_size_ -= bytes; + available -= bytes; + } + return true; + } +}; + + +static bool CloseProcessBuffers(struct pollfd fds[3]) { + int e = errno; + VOID_TEMP_FAILURE_RETRY(close(fds[0].fd)); + VOID_TEMP_FAILURE_RETRY(close(fds[1].fd)); + VOID_TEMP_FAILURE_RETRY(close(fds[2].fd)); + errno = e; + return false; +} + + +bool Process::Wait(intptr_t pid, + intptr_t in, + intptr_t out, + intptr_t err, + intptr_t exit_event, + ProcessResult* result) { + // Close input to the process right away. + VOID_TEMP_FAILURE_RETRY(close(in)); + + // There is no return from this function using Dart_PropagateError + // as memory used by the buffer lists is freed through their + // destructors. + BufferList out_data; + BufferList err_data; + union { + uint8_t bytes[8]; + int32_t ints[2]; + } exit_code_data; + + struct pollfd fds[3]; + fds[0].fd = out; + fds[1].fd = err; + fds[2].fd = exit_event; + + for (int i = 0; i < 3; i++) { + fds[i].events = POLLIN; + } + + int alive = 3; + while (alive > 0) { + // Blocking call waiting for events from the child process. + if (TEMP_FAILURE_RETRY(poll(fds, alive, -1)) <= 0) { + return CloseProcessBuffers(fds); + } + + // Process incoming data. + for (int i = 0; i < alive; i++) { + if (fds[i].revents & POLLIN) { + intptr_t avail = FDUtils::AvailableBytes(fds[i].fd); + // On Mac OS POLLIN can be set with zero available + // bytes. POLLHUP is most likely also set in this case. + if (avail > 0) { + if (fds[i].fd == out) { + if (!out_data.Read(out, avail)) { + return CloseProcessBuffers(fds); + } + } else if (fds[i].fd == err) { + if (!err_data.Read(err, avail)) { + return CloseProcessBuffers(fds); + } + } else if (fds[i].fd == exit_event) { + if (avail == 8) { + intptr_t b = TEMP_FAILURE_RETRY(read(fds[i].fd, + exit_code_data.bytes, 8)); + if (b != 8) { + return CloseProcessBuffers(fds); + } + } + } else { + UNREACHABLE(); + } + } + } + } + + // Process closed. + for (int i = 0; i < alive; i++) { + if (fds[i].revents & POLLHUP) { + VOID_TEMP_FAILURE_RETRY(close(fds[i].fd)); + alive--; + if (i < alive) { + fds[i] = fds[alive]; + } + } + } + } + + // All handles closed and all data read. + result->set_stdout_data(out_data.GetData()); + result->set_stderr_data(err_data.GetData()); + + // Calculate the exit code. + intptr_t exit_code = exit_code_data.ints[0]; + intptr_t negative = exit_code_data.ints[1]; + if (negative) exit_code = -exit_code; + result->set_exit_code(exit_code); + + return true; +} + + bool Process::Kill(intptr_t id, int signal) { return (TEMP_FAILURE_RETRY(kill(id, signal)) != -1); } diff --git a/runtime/bin/process_patch.dart b/runtime/bin/process_patch.dart index 6d69139ab28..62489ca6520 100644 --- a/runtime/bin/process_patch.dart +++ b/runtime/bin/process_patch.dart @@ -49,6 +49,25 @@ patch class Process { stdoutEncoding, stderrEncoding); } + + /* patch */ static ProcessResult runSync( + String executable, + List arguments, + {String workingDirectory, + Map environment, + bool includeParentEnvironment: true, + bool runInShell: false, + Encoding stdoutEncoding: Encoding.SYSTEM, + Encoding stderrEncoding: Encoding.SYSTEM}) { + return _runNonInteractiveProcessSync(executable, + arguments, + workingDirectory, + environment, + includeParentEnvironment, + runInShell, + stdoutEncoding, + stderrEncoding); + } } @@ -278,6 +297,43 @@ class _ProcessImpl extends NativeFieldWrapperClass1 implements Process { return completer.future; } + ProcessResult _runAndWait(Encoding stdoutEncoding, + Encoding stderrEncoding) { + var status = new _ProcessStartStatus(); + bool success = _startNative(_path, + _arguments, + _workingDirectory, + _environment, + _stdin._sink._nativeSocket, + _stdout._stream._nativeSocket, + _stderr._stream._nativeSocket, + _exitHandler._nativeSocket, + status); + if (!success) { + throw new ProcessException(_path, + _arguments, + status._errorMessage, + status._errorCode); + } + + var result = _wait( + _stdin._sink._nativeSocket, + _stdout._stream._nativeSocket, + _stderr._stream._nativeSocket, + _exitHandler._nativeSocket); + + getOutput(output, encoding) { + if (stderrEncoding == null) return output; + return _decodeString(output, encoding); + } + + return new _ProcessResult( + result[0], + result[1], + getOutput(result[2], stdoutEncoding), + getOutput(result[3], stderrEncoding)); + } + bool _startNative(String path, List arguments, String workingDirectory, @@ -288,6 +344,11 @@ class _ProcessImpl extends NativeFieldWrapperClass1 implements Process { _NativeSocket exitHandler, _ProcessStartStatus status) native "Process_Start"; + _wait(_NativeSocket stdin, + _NativeSocket stdout, + _NativeSocket stderr, + _NativeSocket exitHandler) native "Process_Wait"; + Stream> get stdout { return _stdout; } @@ -383,6 +444,24 @@ Future _runNonInteractiveProcess(String path, }); } +ProcessResult _runNonInteractiveProcessSync( + String executable, + List arguments, + String workingDirectory, + Map environment, + bool includeParentEnvironment, + bool runInShell, + Encoding stdoutEncoding, + Encoding stderrEncoding) { + var process = new _ProcessImpl(executable, + arguments, + workingDirectory, + environment, + includeParentEnvironment, + runInShell); + return process._runAndWait(stdoutEncoding, stderrEncoding); +} + class _ProcessResult implements ProcessResult { const _ProcessResult(int this.pid, diff --git a/runtime/bin/process_win.cc b/runtime/bin/process_win.cc index f6c809a75c5..d3efd4cc5f6 100644 --- a/runtime/bin/process_win.cc +++ b/runtime/bin/process_win.cc @@ -642,6 +642,201 @@ int Process::Start(const char* path, } +class BufferList: public BufferListBase { + public: + BufferList() : read_pending_(true) { } + + // Indicate that data has been read into the buffer provided to + // overlapped read. + void DataIsRead(intptr_t size) { + ASSERT(read_pending_ == true); + data_size_ += size; + free_size_ -= size; + ASSERT(free_size_ >= 0); + read_pending_ = false; + } + + // The access to the read buffer for overlapped read. + void GetReadBuffer(uint8_t** buffer, intptr_t* size) { + ASSERT(!read_pending_); + if (free_size_ == 0) Allocate(); + ASSERT(free_size_ > 0); + ASSERT(free_size_ <= kBufferSize); + *buffer = FreeSpaceAddress(); + *size = free_size_; + read_pending_ = true; + } + + intptr_t GetDataSize() { + return data_size_; + } + + uint8_t* GetFirstDataBuffer() { + ASSERT(head_ != NULL); + ASSERT(head_ == tail_); + ASSERT(data_size_ <= kBufferSize); + return head_->data_; + } + + void FreeDataBuffer() { + Free(); + } + + private: + bool read_pending_; +}; + + +class OverlappedHandle { + public: + void Init(HANDLE handle, HANDLE event) { + handle_ = handle; + event_ = event; + ClearOverlapped(); + } + + bool HasEvent(HANDLE event) { + return event_ == event; + } + + bool Read() { + // Get the data read as a result of a completed overlapped operation. + if (overlapped_.InternalHigh > 0) { + buffer_.DataIsRead(overlapped_.InternalHigh); + } else { + buffer_.DataIsRead(0); + } + + // Keep reading until error or pending operation. + while (true) { + ClearOverlapped(); + uint8_t* buffer; + intptr_t buffer_size; + buffer_.GetReadBuffer(&buffer, &buffer_size); + BOOL ok = ReadFile(handle_, buffer, buffer_size, NULL, &overlapped_); + if (!ok) return GetLastError() == ERROR_IO_PENDING; + buffer_.DataIsRead(overlapped_.InternalHigh); + } + } + + Dart_Handle GetData() { + return buffer_.GetData(); + } + + intptr_t GetDataSize() { + return buffer_.GetDataSize(); + } + + uint8_t* GetFirstDataBuffer() { + return buffer_.GetFirstDataBuffer(); + } + + void FreeDataBuffer() { + return buffer_.FreeDataBuffer(); + } + + void Close() { + CloseHandle(handle_); + CloseHandle(event_); + handle_ = INVALID_HANDLE_VALUE; + overlapped_.hEvent = INVALID_HANDLE_VALUE; + } + + private: + void ClearOverlapped() { + memset(&overlapped_, 0, sizeof(overlapped_)); + overlapped_.hEvent = event_; + } + + OVERLAPPED overlapped_; + HANDLE handle_; + HANDLE event_; + BufferList buffer_; + + DISALLOW_ALLOCATION(); +}; + + +bool Process::Wait(intptr_t pid, + intptr_t in, + intptr_t out, + intptr_t err, + intptr_t exit_event, + ProcessResult* result) { + // Close input to the process right away. + reinterpret_cast(in)->Close(); + + // All pipes created to the sub-process support overlapped IO. + FileHandle* stdout_handle = reinterpret_cast(out); + ASSERT(stdout_handle->SupportsOverlappedIO()); + FileHandle* stderr_handle = reinterpret_cast(err); + ASSERT(stderr_handle->SupportsOverlappedIO()); + FileHandle* exit_handle = reinterpret_cast(exit_event); + ASSERT(exit_handle->SupportsOverlappedIO()); + + // Create three events for overlapped IO. These are created as already + // signalled to ensure they have read called at least once. + static const int kHandles = 3; + HANDLE events[kHandles]; + for (int i = 0; i < kHandles; i++) { + events[i] = CreateEvent(NULL, FALSE, TRUE, NULL); + } + + // Setup the structure for handling overlapped IO. + OverlappedHandle oh[kHandles]; + oh[0].Init(stdout_handle->handle(), events[0]); + oh[1].Init(stderr_handle->handle(), events[1]); + oh[2].Init(exit_handle->handle(), events[2]); + + // Continue until all handles are closed. + int alive = kHandles; + while (alive > 0) { + // Blocking call waiting for events from the child process. + DWORD wait_result = WaitForMultipleObjects(alive, events, FALSE, INFINITE); + + // Find the handle signalled. + int index = wait_result - WAIT_OBJECT_0; + for (int i = 0; i < kHandles; i++) { + if (oh[i].HasEvent(events[index])) { + bool ok = oh[i].Read(); + if (!ok) { + if (GetLastError() == ERROR_BROKEN_PIPE) { + oh[i].Close(); + alive--; + if (index < alive) { + events[index] = events[alive]; + } + } else if (err != ERROR_IO_PENDING) { + DWORD e = GetLastError(); + oh[0].Close(); + oh[1].Close(); + oh[2].Close(); + SetLastError(e); + return false; + } + } + break; + } + } + } + + // All handles closed and all data read. + result->set_stdout_data(oh[0].GetData()); + result->set_stderr_data(oh[1].GetData()); + + // Calculate the exit code. + ASSERT(oh[2].GetDataSize() == 8); + uint32_t exit[2]; + memcpy(&exit, oh[2].GetFirstDataBuffer(), sizeof(exit)); + oh[2].FreeDataBuffer(); + intptr_t exit_code = exit[0]; + intptr_t negative = exit[1]; + if (negative) exit_code = -exit_code; + result->set_exit_code(exit_code); + return true; +} + + bool Process::Kill(intptr_t id, int signal) { USE(signal); // signal is not used on windows. HANDLE process_handle; diff --git a/sdk/lib/_internal/lib/io_patch.dart b/sdk/lib/_internal/lib/io_patch.dart index 9f3943b1cac..6aed3a2c6c5 100644 --- a/sdk/lib/_internal/lib/io_patch.dart +++ b/sdk/lib/_internal/lib/io_patch.dart @@ -210,6 +210,18 @@ patch class Process { Encoding stderrEncoding: Encoding.SYSTEM}) { throw new UnsupportedError("Process.run"); } + + patch static ProcessResult runSync( + String executable, + List arguments, + {String workingDirectory, + Map environment, + bool includeParentEnvironment: true, + bool runInShell: false, + Encoding stdoutEncoding: Encoding.SYSTEM, + Encoding stderrEncoding: Encoding.SYSTEM}) { + throw new UnsupportedError("Process.runSync"); + } } patch class InternetAddress { diff --git a/sdk/lib/io/process.dart b/sdk/lib/io/process.dart index 78c13a8a090..0f89a300af5 100644 --- a/sdk/lib/io/process.dart +++ b/sdk/lib/io/process.dart @@ -147,6 +147,26 @@ abstract class Process { Encoding stdoutEncoding: Encoding.SYSTEM, Encoding stderrEncoding: Encoding.SYSTEM}); + + /** + * Starts a process and runs it to completion. This is a synchronous + * call and will block until the child process terminates. + * + * The arguments are the same as for `Process.run`. + * + * Returns a `ProcessResult` with the result of running the process, + * i.e., exit code, standard out and standard in. + */ + external static ProcessResult runSync( + String executable, + List arguments, + {String workingDirectory, + Map environment, + bool includeParentEnvironment: true, + bool runInShell: false, + Encoding stdoutEncoding: Encoding.SYSTEM, + Encoding stderrEncoding: Encoding.SYSTEM}); + /** * Returns the standard output stream of the process as a [:Stream:]. * diff --git a/tests/standalone/io/process_sync_script.dart b/tests/standalone/io/process_sync_script.dart new file mode 100644 index 00000000000..d8aab9c7b28 --- /dev/null +++ b/tests/standalone/io/process_sync_script.dart @@ -0,0 +1,24 @@ +// Copyright (c) 2013, 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. +// +// Utility script to generate some output on stdout and stderr. + +import "dart:math"; +import "dart:io"; + +main() { + var options = new Options(); + var blockCount = int.parse(options.arguments[0]); + var stdoutBlockSize = int.parse(options.arguments[1]); + var stderrBlockSize = int.parse(options.arguments[2]); + var stdoutBlock = + new String.fromCharCodes(new List.filled(stdoutBlockSize, 65)); + var stderrBlock = + new String.fromCharCodes(new List.filled(stderrBlockSize, 66)); + for (int i = 0; i < blockCount; i++) { + stdout.write(stdoutBlock); + stderr.write(stderrBlock); + } + exit(int.parse(options.arguments[3])); +} diff --git a/tests/standalone/io/process_sync_test.dart b/tests/standalone/io/process_sync_test.dart new file mode 100644 index 00000000000..04bc59f11cf --- /dev/null +++ b/tests/standalone/io/process_sync_test.dart @@ -0,0 +1,63 @@ +// Copyright (c) 2013, 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 "package:expect/expect.dart"; +import 'package:path/path.dart'; +import "dart:io"; + +test(int blockCount, + int stdoutBlockSize, + int stderrBlockSize, + int exitCode, + [int nonWindowsExitCode]) { + // Get the Dart script file that generates output. + var scriptFile = new File(join(dirname(Platform.script), + "process_sync_script.dart")); + var args = [scriptFile.path, + blockCount.toString(), + stdoutBlockSize.toString(), + stderrBlockSize.toString(), + exitCode.toString()]; + ProcessResult syncResult = Process.runSync(Platform.executable, args); + Expect.equals(blockCount * stdoutBlockSize, syncResult.stdout.length); + Expect.equals(blockCount * stderrBlockSize, syncResult.stderr.length); + if (Platform.isWindows) { + Expect.equals(exitCode, syncResult.exitCode); + } else { + if (nonWindowsExitCode == null) { + Expect.equals(exitCode, syncResult.exitCode); + } else { + Expect.equals(nonWindowsExitCode, syncResult.exitCode); + } + } + Process.run(Platform.executable, args).then((asyncResult) { + Expect.equals(syncResult.stdout, asyncResult.stdout); + Expect.equals(syncResult.stderr, asyncResult.stderr); + Expect.equals(syncResult.exitCode, asyncResult.exitCode); + }); +} + +main() { + test(10, 10, 10, 0); + test(10, 100, 10, 0); + test(10, 10, 100, 0); + test(100, 1, 10, 0); + test(100, 10, 1, 0); + test(100, 1, 1, 0); + test(1, 100000, 100000, 0); + + // The buffer size used in process.h. + var kBufferSize = 16 * 1024; + test(1, kBufferSize, kBufferSize, 0); + test(1, kBufferSize - 1, kBufferSize + 1, 0); + test(kBufferSize - 1, 1, 1, 0); + test(kBufferSize, 1, 1, 0); + test(kBufferSize + 1, 1, 1, 0); + + test(10, 10, 10, 1); + test(10, 10, 10, 255); + test(10, 10, 10, -1, 255); + test(10, 10, 10, -255, 1); +} +