Skip to content

Commit

Permalink
Codedb ffi io (#3619)
Browse files Browse the repository at this point in the history
* add basic support for additional IO functions

* add back dup2 function definition

* add convenience methods for errno handling

* add O_CLOEXEC to list for File IO

* add a few more functions like ftruncate to the list

* hack away and rejigger things to get IO booting using mostly FFI calls

* make some fixes to pass more specs

* passes most read specs

* add spec for negative IO#pos when 'unget'ing at start of stream

* more fixes to pass ungetc/ungetbyte/pos/read specs; all pass

* fix #getc to handle encodings properly; passes chars_spec

* fix read_to_separator to handle skipping properly

* specs for requesting a negative-sized pointer which causes SEGV

* return NULL for a MemoryPointer.new with a negative size

* add basic support for additional IO functions

* add back dup2 function definition

* add convenience methods for errno handling

* add O_CLOEXEC to list for File IO

* add a few more functions like ftruncate to the list

* hack away and rejigger things to get IO booting using mostly FFI calls

* make some fixes to pass more specs

* passes most read specs

* add spec for negative IO#pos when 'unget'ing at start of stream

* more fixes to pass ungetc/ungetbyte/pos/read specs; all pass

* fix #getc to handle encodings properly; passes chars_spec

* fix read_to_separator to handle skipping properly

* specs for requesting a negative-sized pointer which causes SEGV

* return NULL for a MemoryPointer.new with a negative size

* raise IOError on malloc failure; force returned strings to binary

* force strings to binary for handling #gets with separators and limits; remove debug prints

* remove debug prints again - ps i hate git

* cleanup var use to make specs happy

* fix initialize specs

* remove some commented out and dead code

* switch from C++-based #seek to FFI seek

* redefine STDIN/STDOUT/STDERR to use new IO obj

* switch to using FFI-bound #write command

* include sync as part of fields to move to new IO obj

* mark rubinius as non-compliant for buffering writes; impl detail anyway

* to swap in new IO we need it to happen early enough in the load order before STDIN/etc are used

* need to redefine STDIN/etc and /etc when we swap in new IO object

* begin refactoring IO internals to make support and expansion simpler hopefully

* simplify by taking advantage of existing class method

* add a convenience method for testing call failure

* add #pipe function

* continue refactoring to support pipes and reopening FDs

* baby steps toward making #dup work

* use #descriptor accessor instead of using ivar directly

* make #ensure_ methods public; buffered read should return nil when EOF

* fix error message to use proper local var

* fix #new_pipe so handle encodings correctly

* set encodings for 'left hand side' pipes

* fix #dup specs and add a debugging method

* fix #eof specs

* fix #getbyte spec

* fix several IO#reopen_path bugs particularly improper Errno usage

* use a FFI convenience method for checking call failure

* add spec to test EOF behavior with IO#reopen

* fix IO#reopen to handle EOF correctly

* fix some eof specs with just one to go

* fix setting EOF when seeking

* make any seek on a Pipe raise Errno::ESPIPE

* fix buffer issues when reading with separators and limits; fix sysread

* Multiple fixes...
1. Fixed the #seek inheritance hierarchy mostly by no longer
conflating #seek and #sysseek as the same thing.
2. Rename PipeFileDescriptor to FIFOFileDescriptor since it
can also be used for special character files.
3. Fix #gets so the peek ahead for multi-byte characters is
properly respected; refill the buffer if we don't have enough
bytes for peek ahead.
4. Fix important error in #new_open_fd which was using the wrong
flag on an FD. When using fcntl on an FD, it is important to use
the FD_* flags. Was using O_CLOEXEC instead of FD_CLOEXEC and as
a result all child processes were hanging when their read pipe
was closed.
5. Handle EPIPE in #write correctly. Also raise an error if we
get an error instead of ignoring it.

* provide initial but broken C++ to Ruby conversion for IO.select

* Fixed FDSet to link.

This is a complex part of Rubinius and FFI where we need some primitive
code to interact with libc macros.

* converted until/end into begin/end until which fixed 4 specs

* add logic to load and detect posix_fadvise constants and enable feature detection

* add with_feature detection to skip these specs on platforms without posix_fadvise

* convert usage of posix_fadvise from C++ to Ruby code

* add select function signature; may need to change when we learn more about implementation

* continue fixup of IO.select; handle coercion and choosing max FD

* generate and save a string defining the struct timeval as a FFI struct

* add gettimeofday function for FFI access

* add FDSet::to_set to return FDSET for use by select

* finish fleshing out select support code; unfortunately SEGVs

* fix function signature for posix function #select

* fix returning pointer to descriptor_set

* pull FD_SETSIZE directly from the headers

* fix discovery of highest numbered FD; select still SEGVs

* return pointer to fd_set as a FFI pointer so we can use it from Ruby

* pass FFI Pointer to select; fixes SEGV but still does not work correctly

* fix SEGV by *correctly* getting the address of the data member

* fix #collect_set_fds; kind of works now

* Passes all but one select spec which is related to thread sleeping

Fixed +timeout+ handling so that a nil timeout is respected.
Prior to this we were allocating timeval structs every time and
it turns out that an empty struct and a nil value are not
equivalent in behavior.

Took the opportunity to refactor and DRY up some code that
validated IO.select arguments.

* modify this ugly hack a little to put struct in proper namespace

* add helper methods for raising EAGAIN and EAGAINWaitWritable

* disable the C++ version of #readpartial

* fix readpartial and read_nonblock specs; add new behavior for read_nonblock

* disable the C++ code for write_nonblock

* write the code to support write_nonblock and clean up some style issues

* comment out all primitives that are no longer in use

* stub out a call to #pos for 2 specs

* when reopening a FD flush buffer and seek to other FDs location; fix typo

* Support File#truncate and File#ftruncate correctly

Turns out that #truncate needed to be a class method, so I moved
it to a class method of FileDescriptor.

Also added support for creating a DirectoryFileDescriptor. It
merely inherits from FileDescriptor now. We will see if it needs
any new behavior or if default behavior needs to be curtailed.

* fix bug where kernel/common/io.rb was not set to nil after #foreach call

* fix bug in spec where incorrect args were passed to test #gets with limit

Tricky one to find. The original spec used #gets(1) to read one
char. Unfortunately, this arg format ends up setting a separator
to $/ so the logic ends up calling
EachReader#read_to_separator_with_limit instead of
EachReader#read_to_limit. By passing nil, as in #gets(nil, 1),
then we force the code paths to call read_to_limit.

* fix handling of multi-byte chars when reading with char limits

This fixes several specs shared by gets, foreach, and readline.
The EachReader class needs a good refactoring at this point
but for now I just want to commit working code.

* refactor IO::EachReader, dry it up, and improve code docs

* rescue ESPIPE from seek in #reopen when reopening a Pipe FD

* fix spec to properly handle the expected returns from #readlines

* move stdin/out/err redefinition under Rubinius::IOUtility namespace to avoid collisions

* fix indentation

* remove superfluous todo comments and dead/commented-out code

* fix indentation

* verify that non-blocking writes DO NOT BLOCK when a system buffer is full

* when a write returns EAGAIN or EINTR test to see if in non-blocking mode

Also took the opportunity to refactor fcntl F_GETFL and F_SETFL

* fix misuage of F_GETFL with F_GETFD

* first attempt at getting C-API updated to use Ruby obj over C++ obj

* fix several CAPI specs by correctly getting descriptor

* rename set_blocking method to clear_nonblock which makes more sense to me

* toggle the current thread sleep state when potentially blocking on read or select

* modify exception message to match MRI test expectations

* handle case where user calls IO.allocate and close

* allow multiple successive IO#close calls without error

* second attempt at fixing io=IO.allocate; io.close

* third try is the charm for IO.allocate; io.close

* multiple calls of close, close_read, and close_write should no longer raise IOError

* no longer raise IOError on multiple calls to close, close_read, or close_write

* refactor descriptor testing to make it cleaner

* remove duplicate check of ::read return value

* verify #read raises IOError when read interrupted by another thread closing socket

* always verify file is open after a read operation

* spec limit 0 behavior for each_lines for MRI 2.3 compatibility

* support raising ArgumentError given limit 0 for each_lines

* begin work on supporting Socket

* use rb_funcall properly; fixes SEGVs

* fix typo in private method name

* define method to raise EAGAINWaitWritable

* raise EAGAINWaitWritable when nonblocking write would block

* rescue exceptions when #find_type fails in @enclosing_module

This is really weird. The code calls #find_type in the scope
of the enclosing_module, but there could be a conflict with
that method name, numbers of args it takes, its purpose, etc.
So a failure here should be rescued and allow the followup
code to call #find_type on the FFI class.

This was discovered when the ffi-io branch failed to build
a native extension. The mkmf.rb gem was doing some logging
during the build which caused some code in IO::Select to
call IO::Select.class_eval on code to instantiate the
Timeval_t struct class. Creating a struct calls #find_type
but it was calling it from the scope of mkmf.rb which had
its own (completely different!) method named #find_type
defined. It threw an ArgumentError because the arity didn't
match, but it lead me to this logic error and fix.

* only allow seeking on 'file' file descriptors and not on fifo or other

During a call to IO.reopen a file descriptor was dup2'ed. Its
ftype changed from "file" to "fifo" and as we know a fifo cannot
seek. So we were blowing up when trying to set the offset ivar.
By only allowing seek on "file" FDs, we'll avoid the bug.

* remove spec for an IO implementation detail that no longer holds

As far as I can tell, there is no #buffer_empty? public method on
the IO class. We had one to test this behavior. Gone!

* remove unused and unnecessary public method

* remove temp spec file that was accidently committed earlier"

* emulate blocking read behavior using #read_nonblock

We had a failing spec where a thread blocked on read was
supposed to raise IOError when *another thread* closed
the IO. However, it didn't work. Turns out that the
low-level system read function blocks forever so the
closed IO/file descriptor didn't raise as expected.

The original C code got around this OS behavior by setting
special flags and sending signals to the blocked thread
to wake it up. This mechanism was only exposed to the
bytecode VM and wasn't available to the Ruby runtime so
I had to go this other direction. It's less than ideal
but if the long-term plan is to utilize libuv for
Rubinius IO then this is a good enough fix.

* change IO.setup to set fd accessor; fixes socket setup

I don't really like this solution very much. Ideally the
Socket classes would set @fd directly in their #initialize
methods via a call to FileDescriptor.choose_type(fd). Having
a +fd+ accessor on the IO class was intended only for
debugging purposes.

Let's go with this patch for now. If the larger refactoring
of the IO class to use a private IO::FileDescriptor class
is accepted then we can go back and change the Socket methods
to conform better and remove this accessor.

* replace calls to C++ IO obj with Ruby IO obj

* remove dead and commented-out code as part of cleanup

* spec out #ttyname behavior in Ruby

* add #ttyname function to FFI

* convert IO#ttyname from C++ to Ruby

* use Regexp to handle forward slash escaping in spec

* re-enable partial support for IO finalizer and autoclose

The IO finalizer is tricky since it also has to detect possible
modifications from C API calls. There will likely need to be
some rework done on the C API code itself to make this easier
to manage from the Ruby side since we are trying to get as much
as possible out of C/C++.

* A couple fixes for finding constants.

* Fixed accessing Stat in IO::FileDescriptor.

* make sure proper encoding set when using #read_nonblock

* first try to add finalizer support for C-extensions

* improve variable name

* if we fclose a FD then do not call close on it too

* use Ruby-based IO functions instead of C++ object

* remove unnecessary IO calls

* remove test file for dead C code

* remove dead C code for IO classes

* remove dead code

* make socket C funcs call ruby code from C; first try

* Fixed defining GC stubs for IO class.

* use calling frame when given else look it up

* Cleanup from stw branch merge.

* Revert "use Ruby-based IO functions instead of C++ object"

This reverts commit ca75509.

Re-introduced IO::open_with_cloexec to clean up this.

* Removed test_io.hpp incorrectly merged.

* Fixed IO::FileDescriptor#finalizer definition.

* Switched fork/exec lock to SpinLock.

Since a SpinLock is a simple integer on which CAS operations are performed,
there is no way to go afoul of 'ownership' during fork(). This appears to
solve a spordic issue where the child was not able to reset the fork_exec_lock_
inherited from the parent process.

* Properly assign rio_stream GC root.

* Use built-in finalizer protocol for IO-related objects.

* use files instead of pipes to test finalizers

* move fd instance alloc to IO.setup

* fix method signature; remove debug print

* call method on self via send

Before the code was looking up 'self' by getting the current
call frame and pulling self from it. This was returning a
different object than expected. So now we just call #send
against the current object; this works consistently.

* cast unsigned vals back to signed to fix compiler warnings

* modify order of operations via parens to satisfy compiler

* hack to prevent closing rio twice

* Removed .tap generating structs.

* Removed double finalization bandaid.

We need to ensure this is fixed now.

* Re-added Rubinius define_finalizer extension.

* Fixed finalizer spec to clean up temporary.

* ignore NotImplementedError for #fadvise on platforms that do not support that call

* bump ruby compatibility to 2.3.0

* rename spec file so ci run matches 2.3.0 ruby compat

* verify that infinitely looping subprocesses can be force closed

* remove special case EPIPE error handling so all write errors bubble up

* clarify intent of spec

* make sure we test for new transcoding options

* prepare to handle passing more encoding options in IO.open/File.open

* stub in an attempt at hooking up newline transcoders

* stub in attempt at accessing transcoders

* fix regex so it matches newline transcoders

* Revert "stub in attempt at accessing transcoders"

This reverts commit 6455dc7.

* Revert "stub in an attempt at hooking up newline transcoders"

This reverts commit 39d255f.

* Revert "make sure we test for new transcoding options"

This reverts commit 679a247.

* fix encoding_options handling to pass specs

* Guard calling fclose() on NULL rio-f.
chuckremes authored and brixen committed Jul 11, 2016
1 parent f03286d commit a8f23c1
Showing 34 changed files with 2,234 additions and 1,973 deletions.
27 changes: 26 additions & 1 deletion core/errno.rb
Original file line number Diff line number Diff line change
@@ -9,9 +9,34 @@ module Errno
# Unlike rb_sys_fail(), handle does not raise an exception if errno is 0.

def self.handle(additional = nil)
err = FFI::Platform::POSIX.errno
err = errno
return if err == 0

raise SystemCallError.new(additional, err)
end

def self.errno
FFI.errno
end

def self.eql?(code)
FFI.errno == code
end

def self.raise_waitreadable(message=nil)
raise IO::EAGAINWaitReadable, message
end

def self.raise_waitwritable(message=nil)
raise IO::EAGAINWaitWritable, message
end

def self.raise_eagain(message=nil)
raise_errno(message, Errno::EAGAIN::Errno)
end

def self.raise_errno(message, errno)
raise SystemCallError.new(message, errno)
end
private :raise_errno
end
6 changes: 5 additions & 1 deletion core/ffi.rb
Original file line number Diff line number Diff line change
@@ -72,6 +72,10 @@ def errno
FFI::Platform::POSIX.errno
end

# Convenience method for determining if a function call succeeded or failed
def call_failed?(return_code)
return_code == -1
end
end

# Represents a C enum.
@@ -318,7 +322,7 @@ def self.layout(*spec)
element_size = type_size = f.size
else
if @enclosing_module
type_code = @enclosing_module.find_type(f)
type_code = @enclosing_module.find_type(f) rescue nil
end

type_code ||= FFI.find_type(f)
13 changes: 7 additions & 6 deletions core/file.rb
Original file line number Diff line number Diff line change
@@ -250,7 +250,7 @@ def self.chown(owner, group, *paths)

def chmod(mode)
mode = Rubinius::Type.coerce_to(mode, Integer, :to_int)
n = POSIX.fchmod @descriptor, clamp_short(mode)
n = POSIX.fchmod descriptor, clamp_short(mode)
Errno.handle if n == -1
n
end
@@ -268,7 +268,7 @@ def chown(owner, group)
group = -1
end

n = POSIX.fchown @descriptor, owner, group
n = POSIX.fchown descriptor, owner, group
Errno.handle if n == -1
n
end
@@ -1018,7 +1018,7 @@ def self.truncate(path, length)

length = Rubinius::Type.coerce_to length, Integer, :to_int

prim_truncate(path, length)
FileDescriptor.truncate(path, length)
end

##
@@ -1189,6 +1189,7 @@ def initialize(path_or_fd, mode=undefined, perm=undefined, options=undefined)
Errno.handle path if fd < 0

@path = path

super(fd, mode, options)
end
end
@@ -1230,7 +1231,7 @@ def reopen(other, mode = 'r+')
def flock(const)
const = Rubinius::Type.coerce_to const, Integer, :to_int

result = POSIX.flock @descriptor, const
result = POSIX.flock descriptor, const

return false if result == -1
result
@@ -1241,7 +1242,7 @@ def lstat
end

def stat
Stat.fstat @descriptor
Stat.fstat descriptor
end

alias_method :to_path, :path
@@ -1254,7 +1255,7 @@ def truncate(length)

flush
reset_buffering
prim_ftruncate(length)
@fd.ftruncate(length)
end

def inspect
2,032 changes: 1,443 additions & 589 deletions core/io.rb

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions core/pointer.rb
Original file line number Diff line number Diff line change
@@ -315,6 +315,8 @@ def self.new(type, count=nil, clear=true)
total = size
end

return NULL if total < 0

ptr = malloc total
ptr.total = total
ptr.type_size = size
2 changes: 1 addition & 1 deletion core/stat.rb
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ def self.stat(path)
def self.fstat(fd)
stat = allocate
result = Rubinius.privately { stat.fsetup fd }
Errno.handle "file descriptor #{descriptor}" unless result == 0
Errno.handle "file descriptor #{fd}" unless result == 0
stat
end

76 changes: 71 additions & 5 deletions core/zed.rb
Original file line number Diff line number Diff line change
@@ -595,15 +595,23 @@ module FFI

# Converts an unsigned short
add_typedef TYPE_USHORT, :ushort
add_typedef TYPE_USHORT, :mode_t
add_typedef TYPE_USHORT, :nlink_t

# Converts an int
add_typedef TYPE_INT, :int
add_typedef TYPE_INT, :dev_t
add_typedef TYPE_INT, :blksize_t
add_typedef TYPE_INT, :time_t

# Converts an unsigned int
add_typedef TYPE_UINT, :uint
add_typedef TYPE_UINT, :uid_t
add_typedef TYPE_UINT, :gid_t

# Converts a long
add_typedef TYPE_LONG, :long
add_typedef TYPE_LONG, :ssize_t

# Converts an unsigned long
add_typedef TYPE_ULONG, :ulong
@@ -613,9 +621,12 @@ module FFI

# Converts a long long
add_typedef TYPE_LL, :long_long
add_typedef TYPE_LL, :blkcnt_t
add_typedef TYPE_LL, :off_t

# Converts an unsigned long long
add_typedef TYPE_ULL, :ulong_long
add_typedef TYPE_ULL, :ino64_t

# Converts a float
add_typedef TYPE_FLOAT, :float
@@ -855,14 +866,32 @@ module POSIX
attach_function :chroot, [:string], :int

# File/IO
attach_function :fcntl, [:int, :int, :long], :int
attach_function :ioctl, [:int, :ulong, :long], :int
attach_function :fsync, [:int], :int
attach_function :dup, [:int], :int
attach_function :dup2, [:int, :int], :int
attach_function :fcntl, [:int, :int, :long], :int
attach_function :ioctl, [:int, :ulong, :long], :int
attach_function :fsync, [:int], :int
attach_function :dup, [:int], :int
attach_function :dup2, [:int, :int], :int
attach_function :open, [:string, :int, :mode_t], :int
attach_function :close, [:int], :int
attach_function :lseek, [:int, :off_t, :int], :off_t
attach_function :read, [:int, :pointer, :size_t], :ssize_t
attach_function :ftruncate, [:int, :off_t], :int
attach_function :truncate, [:string, :off_t], :int
attach_function :write, [:int, :pointer, :size_t], :ssize_t
attach_function :select, [:int, :pointer, :pointer, :pointer, :pointer], :int

# Other I/O
attach_function :pipe, [:pointer], :int
attach_function :mmap, [:pointer, :size_t, :int, :int, :int, :off_t], :pointer
attach_function :msync, [:pointer, :size_t, :int], :int
attach_function :munmap, [:pointer, :size_t], :int
attach_function :getpagesize, [], :int
attach_function :shutdown, [:int, :int], :int
attach_function :posix_fadvise, [:int, :off_t, :off_t, :int], :int

# inspecting
attach_function :isatty, [:int], :int
attach_function :ttyname, [:int], :string

# locking
attach_function :flock, [:int, :int], :int
@@ -920,6 +949,9 @@ module POSIX
attach_function :major, 'ffi_major', [:dev_t], :dev_t
attach_function :minor, 'ffi_minor', [:dev_t], :dev_t

# time
attach_function :gettimeofday, [:pointer, :pointer], :int

#--
# Internal class for accessing timevals
#++
@@ -1074,10 +1106,13 @@ module Constants

# O_ACCMODE is /undocumented/ for fcntl() on some platforms
ACCMODE = Rubinius::Config['rbx.platform.fcntl.O_ACCMODE']
O_ACCMODE = Rubinius::Config['rbx.platform.fcntl.O_ACCMODE']

F_GETFD = Rubinius::Config['rbx.platform.fcntl.F_GETFD']
F_SETFD = Rubinius::Config['rbx.platform.fcntl.F_SETFD']
FD_CLOEXEC = Rubinius::Config['rbx.platform.fcntl.FD_CLOEXEC']
O_CLOEXEC = Rubinius::Config['rbx.platform.file.O_CLOEXEC']
O_NONBLOCK = Rubinius::Config['rbx.platform.file.O_NONBLOCK']

RDONLY = Rubinius::Config['rbx.platform.file.O_RDONLY']
WRONLY = Rubinius::Config['rbx.platform.file.O_WRONLY']
@@ -1159,6 +1194,28 @@ class IO
SEEK_SET = Rubinius::Config['rbx.platform.io.SEEK_SET']
SEEK_CUR = Rubinius::Config['rbx.platform.io.SEEK_CUR']
SEEK_END = Rubinius::Config['rbx.platform.io.SEEK_END']

# Not available on all platforms, so these constants may be nil
POSIX_FADV_NORMAL = Rubinius::Config['rbx.platform.advise.POSIX_FADV_NORMAL']
POSIX_FADV_SEQUENTIAL = Rubinius::Config['rbx.platform.advise.POSIX_FADV_SEQUENTIAL']
POSIX_FADV_RANDOM = Rubinius::Config['rbx.platform.advise.POSIX_FADV_RANDOM']
POSIX_FADV_WILLNEED = Rubinius::Config['rbx.platform.advise.POSIX_FADV_WILLNEED']
POSIX_FADV_DONTNEED = Rubinius::Config['rbx.platform.advise.POSIX_FADV_DONTNEED']
POSIX_FADV_NOREUSE = Rubinius::Config['rbx.platform.advise.POSIX_FADV_NOREUSE']

class FileDescriptor
@@max_descriptors = Rubinius::AtomicReference.new(2)

include File::Constants

O_RDONLY = Rubinius::Config['rbx.platform.file.O_RDONLY']
O_WRONLY = Rubinius::Config['rbx.platform.file.O_WRONLY']
O_RDWR = Rubinius::Config['rbx.platform.file.O_RDWR']
end

class Select
FD_SETSIZE = Rubinius::Config['rbx.platform.select.FD_SETSIZE']
end
end

class NilClass
@@ -1599,6 +1656,15 @@ class Rlimit < FFI::Struct
Rubinius::Globals.set_hook(:$?) { Thread.current[:$?] }
end


STDIN = Rubinius::IOUtility.redefine_io(0, :read_only)
STDOUT = Rubinius::IOUtility.redefine_io(1, :write_only)
STDERR = Rubinius::IOUtility.redefine_io(2, :write_only)

Rubinius::Globals.set!(:$stdin, STDIN)
Rubinius::Globals.set!(:$stdout, STDOUT)
Rubinius::Globals.set!(:$stderr, STDERR)

module Rubinius
begin
is_tty = STDIN.tty?
8 changes: 8 additions & 0 deletions library/ffi/generators/structures.rb
Original file line number Diff line number Diff line change
@@ -159,6 +159,14 @@ def write_config(io)
@fields.each { |field| io.puts field.to_config(@name) }
end

def write_class(io)
layout = generate_layout.gsub(/\n/, '').squeeze(' ')
struct_name = @struct_name.split(' ').last.capitalize + "_t"
struct_class = "class #{struct_name} < FFI::Struct; #{layout}; end"

io.puts "rbx.platform.#{@name}.class = #{struct_class}"
end

def generate_layout
buf = ""

1,024 changes: 80 additions & 944 deletions machine/builtin/io.cpp

Large diffs are not rendered by default.

196 changes: 54 additions & 142 deletions machine/builtin/io.hpp
Original file line number Diff line number Diff line change
@@ -15,191 +15,103 @@ namespace rubinius {
class Encoding;

class IO : public Object {
static int max_descriptors_;

public:
const static object_type type = IOType;

attr_accessor(descriptor, Fixnum);
attr_accessor(path, String);
attr_accessor(ibuffer, Object);
attr_accessor(mode, Fixnum);
attr_accessor(eof, Object);
attr_accessor(lineno, Fixnum);
attr_accessor(sync, Object);
attr_accessor(external, Encoding);
attr_accessor(internal, Encoding);
attr_accessor(autoclose, Object);
public:
/* interface */

static void bootstrap(STATE);
static void initialize(STATE, IO* obj) {
obj->descriptor(nil<Fixnum>());
obj->path(nil<String>());
obj->ibuffer(nil<Object>());
obj->mode(nil<Fixnum>());
obj->eof(cFalse);
obj->lineno(Fixnum::from(0));
obj->sync(nil<Object>());
obj->external(nil<Encoding>());
obj->internal(nil<Encoding>());
obj->autoclose(nil<Object>());
}
static void initialize(STATE, IO* obj) { }

static IO* create(STATE, int fd);
static native_int open_with_cloexec(STATE, const char* path, int mode, int permissions);

static int max_descriptors() {
return max_descriptors_;
}

native_int to_fd();
void set_mode(STATE);
void force_read_only(STATE);
void force_write_only(STATE);
static void finalize(STATE, IO* io);
native_int descriptor(STATE);
void ensure_open(STATE);

/* Class primitives */

// Rubinius.primitive :io_allocate
static IO* allocate(STATE, Object* self);

// Rubinius.primitive :io_connect_pipe
static Object* connect_pipe(STATE, IO* lhs, IO* rhs);

// Rubinius.primitive :io_open
static Fixnum* open(STATE, String* path, Fixnum* mode, Fixnum* perm);

static native_int open_with_cloexec(STATE, const char* path, int mode, int permissions);
static void new_open_fd(STATE, native_int fd);
static void update_max_fd(STATE, native_int fd);

/**
* Perform select() on descriptors.
*
* @todo Replace with an evented version when redoing events. --rue
*/
// Rubinius.primitive :io_select
static Object* select(STATE, Object* readables, Object* writables, Object* errorables, Object* timeout);

// Rubinius.primitive :io_fnmatch
static Object* fnmatch(STATE, String* pattern, String* path, Fixnum* flags);

/* Instance primitives */

// Rubinius.primitive :io_ensure_open
Object* ensure_open(STATE);

/**
* Directly read up to number of bytes from descriptor.
*
* Returns cNil at EOF.
*/
// Rubinius.primitive :io_sysread
Object* sysread(STATE, Fixnum* number_of_bytes);

// Rubinius.primitive :io_read_if_available
Object* read_if_available(STATE, Fixnum* number_of_bytes);

// Rubinius.primitive :io_socket_read
Object* socket_read(STATE, Fixnum* bytes, Fixnum* flags, Fixnum* type);

// Rubinius.primitive :io_seek
Integer* seek(STATE, Integer* amount, Fixnum* whence);
// Rubinius.primitive :io_send_io
Object* send_io(STATE, IO* io);

// Rubinius.primitive :io_truncate
static Integer* truncate(STATE, String* name, Fixnum* off);
// Rubinius.primitive :io_recv_fd
Object* recv_fd(STATE);

// Rubinius.primitive :io_ftruncate
Integer* ftruncate(STATE, Fixnum* off);
class Info : public TypeInfo {
public:
Info(object_type type) : TypeInfo(type) { }
void auto_mark(Object* obj, memory::ObjectMark& mark) { }
void set_field(STATE, Object* target, size_t index, Object* val) { }
Object* get_field(STATE, Object* target, size_t index) { return cNil; }
void populate_slot_locations() { }
};

// Rubinius.primitive :io_write
Object* write(STATE, String* buf);
};

// Rubinius.primitive :io_reopen
Object* reopen(STATE, IO* other);
class FDSet : public Object {
public:
const static object_type type = FDSetType;

// Rubinius.primitive :io_reopen_path
Object* reopen_path(STATE, String* other, Fixnum * mode);
private:
uint8_t descriptor_set[sizeof(fd_set)];

// Rubinius.primitive :io_close
Object* close(STATE);
public:

// Rubinius.primitive :io_send_io
Object* send_io(STATE, IO* io);
static void bootstrap(STATE);

// Rubinius.primitive :io_recv_fd
Object* recv_fd(STATE);
static FDSet* create(STATE);

/**
* Shutdown a full-duplex descriptor's read and/or write stream.
*
* Careful with this, it applies to full-duplex only.
* It also shuts the stream *in all processes*, not
* just the current one.
*/
// Rubinius.primitive :io_shutdown
Object* shutdown(STATE, Fixnum* how);
// Rubinius.primitive :fdset_allocate
static FDSet* allocate(STATE, Object* self);

// Rubinius.primitive :io_query
Object* query(STATE, Symbol* op);
// Rubinius.primitive :fdset_zero
Object* zero(STATE);

// Rubinius.primitive :io_write_nonblock
Object* write_nonblock(STATE, String* buf);
// Rubinius.primitive :fdset_is_set
Object* is_set(STATE, Fixnum* descriptor);

// Rubinius.primitive :io_advise
Object* advise(STATE, Symbol* advice_name, Integer* offset, Integer* len);
// Rubinius.primitive :fdset_set
Object* set(STATE, Fixnum* descriptor);

void set_nonblock(STATE);
// Rubinius.primitive :fdset_to_set
Object* to_set(STATE);

class Info : public TypeInfo {
public:
BASIC_TYPEINFO(TypeInfo)
Info(object_type type) : TypeInfo(type) { }
void auto_mark(Object* obj, memory::ObjectMark& mark) { }
void set_field(STATE, Object* target, size_t index, Object* val) { }
Object* get_field(STATE, Object* target, size_t index) { return cNil; }
void populate_slot_locations() { }
};

};

#define IOBUFFER_SIZE 32768U

class IOBuffer : public Object {
class RIOStream : public Object {
public:
const static size_t fields = 7;
const static object_type type = IOBufferType;

attr_accessor(storage, ByteArray);
attr_accessor(total, Fixnum);
attr_accessor(used, Fixnum);
attr_accessor(start, Fixnum);
attr_accessor(eof, Object);
attr_accessor(write_synced, Object);

static void initialize(STATE, IOBuffer* obj) {
obj->storage(nil<ByteArray>());
obj->total(Fixnum::from(0));
obj->used(Fixnum::from(0));
obj->start(Fixnum::from(0));
obj->eof(cFalse);
obj->write_synced(cTrue);
}

static IOBuffer* create(STATE, size_t bytes = IOBUFFER_SIZE);
// Rubinius.primitive :iobuffer_allocate
static IOBuffer* allocate(STATE);

// Rubinius.primitive :iobuffer_unshift
Object* unshift(STATE, String* str, Fixnum* start_pos);

// Rubinius.primitive :iobuffer_fill
Object* fill(STATE, IO* io);

void reset(STATE);
String* drain(STATE);
char* byte_address();
size_t left();
char* at_unused();
void read_bytes(STATE, size_t bytes);
const static object_type type = RIOStreamType;

static void bootstrap(STATE);

// Rubinius.primitive :rio_close
static Object* close(STATE, Object* io, Object* allow_exception);

class Info : public TypeInfo {
public:
BASIC_TYPEINFO(TypeInfo)
Info(object_type type) : TypeInfo(type) { }
void auto_mark(Object* obj, memory::ObjectMark& mark) { }
void set_field(STATE, Object* target, size_t index, Object* val) { }
Object* get_field(STATE, Object* target, size_t index) { return cNil; }
void populate_slot_locations() { }
};
};

13 changes: 8 additions & 5 deletions machine/builtin/system.cpp
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
#include "builtin/location.hpp"
#include "builtin/lookup_table.hpp"
#include "builtin/method_table.hpp"
#include "builtin/native_method.hpp"
#include "builtin/thread.hpp"
#include "builtin/tuple.hpp"
#include "builtin/string.hpp"
@@ -346,7 +347,9 @@ namespace rubinius {
}

if(CBOOL(table->has_key(state, state->symbol("close_others")))) {
int max = IO::max_descriptors();
Class* fd_class = (Class*) G(io)->get_const(state, "FileDescriptor");
Fixnum* max_fd = (Fixnum*)fd_class->send(state, state->symbol("max_fd"));
int max = max_fd->to_native();
int flags;

for(int fd = STDERR_FILENO + 1; fd < max; fd++) {
@@ -362,11 +365,11 @@ namespace rubinius {
native_int size = assign->size();
for(native_int i = 0; i < size; i += 4) {
int from = as<Fixnum>(assign->get(state, i))->to_native();
int mode = as<Fixnum>(assign->get(state, i + 2))->to_native();
int perm = as<Fixnum>(assign->get(state, i + 3))->to_native();
const char* name = as<String>(assign->get(state, i + 1))->c_str_null_safe(state);
int to = IO::open_with_cloexec(state,
as<String>(assign->get(state, i + 1))->c_str(state),
as<Fixnum>(assign->get(state, i + 2))->to_native(),
as<Fixnum>(assign->get(state, i + 3))->to_native());

int to = IO::open_with_cloexec(state, name, mode, perm);
redirect_file_descriptor(from, to);
}
}
56 changes: 39 additions & 17 deletions machine/capi/io.cpp
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
#include "builtin/array.hpp"
#include "builtin/fixnum.hpp"
#include "builtin/io.hpp"
#include "builtin/string.hpp"
#include "builtin/thread.hpp"
#include "memory.hpp"
#include "primitives.hpp"
@@ -59,12 +60,21 @@ namespace rubinius {

RIO* Handle::as_rio(NativeMethodEnvironment* env) {
IO* io_obj = c_as<IO>(object());
ID id_descriptor = rb_intern("descriptor");
ID id_mode = rb_intern("mode");
VALUE jobj = env->get_handle(io_obj);

if(type_ != cRIO) {
env->shared().capi_ds_lock().lock();

if(type_ != cRIO) {
int fd = (int)io_obj->descriptor()->to_native();
native_int fd = -1;
VALUE fileno = rb_funcall(jobj, id_descriptor, 0);
Fixnum* tmp_fd = try_as<Fixnum>(env->get_object(fileno));

if(tmp_fd) {
fd = tmp_fd->to_native();
}

env->shared().capi_ds_lock().unlock();

@@ -74,7 +84,7 @@ namespace rubinius {
rb_raise(rb_eIOError, "%s (%d)", err, errno);
}

FILE* f = fdopen(fd, flags_modestr(io_obj->mode()->to_native()));
FILE* f = fdopen(fd, flags_modestr(try_as<Fixnum>(env->get_object(rb_funcall(jobj, id_mode, 0)))->to_native()));

if(!f) {
char buf[RBX_STRERROR_BUFSIZE];
@@ -118,8 +128,15 @@ namespace rubinius {

if(rio->finalize) rio->finalize(rio, true);

bool ok = (fclose(rio->f) == 0);
rio->f = NULL;
bool ok = true;

/* This field is publicly accessible in the C-API structure so we need
* to guard against the possibility that it is NULL;
*/
if(rio->f) {
ok = (fclose(rio->f) == 0);
rio->f = NULL;
}

return ok;
}
@@ -167,8 +184,7 @@ extern "C" {
int rb_io_fd(VALUE io_handle) {
NativeMethodEnvironment* env = NativeMethodEnvironment::get();

IO* io = c_as<IO>(env->get_object(io_handle));
return io->descriptor()->to_native();
return c_as<Fixnum>(env->get_object(rb_funcall(io_handle, rb_intern("fileno"), 0)))->to_native();
}

long rb_io_fread(char* ptr, int len, FILE* f) {
@@ -353,10 +369,9 @@ extern "C" {

void rb_io_set_nonblock(rb_io_t* iot) {
VALUE io_handle = iot->handle;
NativeMethodEnvironment* env = NativeMethodEnvironment::get();
VALUE fd_ivar = rb_ivar_get(io_handle, rb_intern("fd"));

IO* io = c_as<IO>(env->get_object(io_handle));
io->set_nonblock(env->state());
rb_funcall(fd_ivar, rb_intern("set_nonblock"), 0);
}

VALUE rb_io_check_io(VALUE io) {
@@ -371,18 +386,16 @@ extern "C" {
void rb_io_check_closed(rb_io_t* iot) {
VALUE io_handle = iot->handle;
NativeMethodEnvironment* env = NativeMethodEnvironment::get();
IO* io = c_as<IO>(env->get_object(io_handle));

if(io->descriptor()->to_native() == -1) {
if(c_as<Fixnum>(env->get_object(rb_funcall(io_handle, rb_intern("fileno"), 0)))->to_native() == -1) {
rb_raise(rb_eIOError, "closed stream");
}
}

void rb_io_check_readable(rb_io_t* iot) {
VALUE io_handle = iot->handle;
NativeMethodEnvironment* env = NativeMethodEnvironment::get();
IO* io = c_as<IO>(env->get_object(io_handle));
int io_mode = io->mode()->to_native() & O_ACCMODE;
int io_mode = c_as<Fixnum>(env->get_object(rb_funcall(io_handle, rb_intern("mode"), 0)))->to_native() & O_ACCMODE;
if(!(O_RDONLY == io_mode || O_RDWR == io_mode)) {
rb_raise(rb_eIOError, "not opened for reading");
}
@@ -391,21 +404,30 @@ extern "C" {
void rb_io_check_writable(rb_io_t* iot) {
VALUE io_handle = iot->handle;
NativeMethodEnvironment* env = NativeMethodEnvironment::get();
IO* io = c_as<IO>(env->get_object(io_handle));
int io_mode = io->mode()->to_native() & O_ACCMODE;
int io_mode = c_as<Fixnum>(env->get_object(rb_funcall(io_handle, rb_intern("mode"), 0)))->to_native() & O_ACCMODE;
if(!(O_WRONLY == io_mode || O_RDWR == io_mode)) {
rb_raise(rb_eIOError, "not opened for writing");
}
}

void rb_update_max_fd(int fd) {
NativeMethodEnvironment* env = NativeMethodEnvironment::get();
IO::update_max_fd(env->state(), fd);
State *state = env->state();
Object* fd_object = G(io)->get_const(env->state(), "FileDescriptor");
VALUE fd_class = env->get_handle(fd_object);
VALUE descriptor = env->get_handle(Fixnum::from(fd));

rb_funcall(fd_class, rb_intern("update_max_fd"), 1, descriptor);
}

void rb_fd_fix_cloexec(int fd) {
NativeMethodEnvironment* env = NativeMethodEnvironment::get();
IO::new_open_fd(env->state(), fd);
State *state = env->state();
Object* fd_object = G(io)->get_const(env->state(), "FileDescriptor");
VALUE fd_class = env->get_handle(fd_object);
VALUE descriptor = env->get_handle(Fixnum::from(fd));

rb_funcall(fd_class, rb_intern("new_open_fd"), 1, descriptor);
}

int rb_cloexec_open(const char *pathname, int flags, int mode) {
2 changes: 1 addition & 1 deletion machine/codegen/transcoders_extract.rb
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ def read(name)
File.open definitions, "wb" do |f|
re = /^static\s*const\s*rb_transcoder\s*\n
rb_\w+\s*=\s*\{\s*\n
\s*"([^"]+)",\s*"([^"]+)"/mx
\s*"([^"]*)",\s*"([^"]+)"/mx

Dir["#{dir}/*.c"].sort.each do |name|
f.puts " // #{name}"
7 changes: 5 additions & 2 deletions machine/globals.hpp
Original file line number Diff line number Diff line change
@@ -56,7 +56,8 @@ namespace rubinius {
memory::TypedRoot<Class*> floatpoint, nmc, list, list_node;
memory::TypedRoot<Class*> channel, thread, thread_state, constantscope;
memory::TypedRoot<Class*> constant_table, lookup_table;
memory::TypedRoot<Class*> iseq, executable, native_function, iobuffer;
memory::TypedRoot<Class*> iseq, executable, native_function;
memory::TypedRoot<Class*> select, fdset, rio_stream;
memory::TypedRoot<Class*> included_module;

/* the primary symbol table */
@@ -174,7 +175,9 @@ namespace rubinius {
iseq(&roots),
executable(&roots),
native_function(&roots),
iobuffer(&roots),
select(&roots),
fdset(&roots),
rio_stream(&roots),
included_module(&roots),
sym_method_missing(&roots),
sym_respond_to_missing(&roots),
13 changes: 5 additions & 8 deletions machine/ontology.cpp
Original file line number Diff line number Diff line change
@@ -260,6 +260,8 @@ namespace rubinius {
Randomizer::bootstrap(state);
Encoding::bootstrap(state);
FSEvent::bootstrap(state);
FDSet::bootstrap(state);
RIOStream::bootstrap(state);
Logger::bootstrap(state);
JIT::bootstrap(state);
CodeDB::bootstrap(state);
@@ -335,18 +337,13 @@ namespace rubinius {
}

void VM::initialize_platform_data(STATE) {
// HACK test hooking up IO
/* Hook up stub IO class so we can begin bootstrapping. STDIN/OUT/ERR will be
* replaced in core/zed.rb with the pure Ruby IO objects.
*/
IO* in_io = IO::create(state, STDIN_FILENO);
IO* out_io = IO::create(state, STDOUT_FILENO);
IO* err_io = IO::create(state, STDERR_FILENO);

out_io->sync(state, cTrue);
err_io->sync(state, cTrue);

in_io->force_read_only(state);
out_io->force_write_only(state);
err_io->force_write_only(state);

G(object)->set_const(state, "STDIN", in_io);
G(object)->set_const(state, "STDOUT", out_io);
G(object)->set_const(state, "STDERR", err_io);
8 changes: 4 additions & 4 deletions machine/test/test_bignum.hpp
Original file line number Diff line number Diff line change
@@ -648,7 +648,7 @@ class TestBignum : public CxxTest::TestSuite, public VMTest {
fix = neg_one->left_shift(state, width_minus1);

TS_ASSERT(kind_of<Fixnum>(fix));
TS_ASSERT_EQUALS((0UL - 1L) << (FIXNUM_WIDTH-1), fix->to_native());
TS_ASSERT_EQUALS((native_int)((0UL - 1L) << (FIXNUM_WIDTH-1)), fix->to_native());

Integer* max_plus1 = one->left_shift(state, width);

@@ -676,7 +676,7 @@ class TestBignum : public CxxTest::TestSuite, public VMTest {
fix = neg_one->right_shift(state, neg_width_minus1);

TS_ASSERT(kind_of<Fixnum>(fix));
TS_ASSERT_EQUALS((0UL - 1L) << (FIXNUM_WIDTH-1), fix->to_native());
TS_ASSERT_EQUALS((native_int)((0UL - 1L) << (FIXNUM_WIDTH-1)), fix->to_native());

Integer* max_plus1 = one->right_shift(state, neg_width);

@@ -704,7 +704,7 @@ class TestBignum : public CxxTest::TestSuite, public VMTest {
fix = as<Integer>(neg_two->pow(state, width_minus1));

TS_ASSERT(kind_of<Fixnum>(fix));
TS_ASSERT_EQUALS((0UL - 1L) << (FIXNUM_WIDTH-1), fix->to_native());
TS_ASSERT_EQUALS((native_int)((0UL - 1L) << (FIXNUM_WIDTH-1)), fix->to_native());

Integer* max_plus1 = as<Integer>(two->pow(state, width));

@@ -719,7 +719,7 @@ class TestBignum : public CxxTest::TestSuite, public VMTest {
big = as<Integer>(neg_two->pow(state, Fixnum::from(FIXNUM_WIDTH+1)));

TS_ASSERT(kind_of<Bignum>(big));
TS_ASSERT_EQUALS((0ULL - 1LL) << (FIXNUM_WIDTH+1), as<Bignum>(big)->to_long_long());
TS_ASSERT_EQUALS((long long)((0ULL - 1LL) << (FIXNUM_WIDTH+1)), as<Bignum>(big)->to_long_long());
}

void test_equal() {
137 changes: 0 additions & 137 deletions machine/test/test_io.hpp

This file was deleted.

34 changes: 32 additions & 2 deletions rakelib/platform.rake
Original file line number Diff line number Diff line change
@@ -59,12 +59,15 @@ file 'runtime/platform.conf' => deps do |task|
s.field :d_name, :char_array
end.write_config(f)

Rubinius::FFI::Generators::Structures.new 'timeval' do |s|
struct = Rubinius::FFI::Generators::Structures.new 'timeval' do |s|
s.include "sys/time.h"
s.name 'struct timeval'
s.field :tv_sec, :time_t
s.field :tv_usec, :suseconds_t
end.write_config(f)
end

struct.write_config(f)
struct.write_class(f)

Rubinius::FFI::Generators::Structures.new 'sockaddr_in' do |s|
if BUILD_CONFIG[:windows]
@@ -236,6 +239,7 @@ file 'runtime/platform.conf' => deps do |task|
O_APPEND
O_NONBLOCK
O_SYNC
O_CLOEXEC
S_IRUSR
S_IWUSR
S_IXUSR
@@ -273,6 +277,32 @@ file 'runtime/platform.conf' => deps do |task|

io_constants.each { |c| cg.const c }
end.write_constants(f)

Rubinius::FFI::Generators::Constants.new 'rbx.platform.select' do |cg|
cg.include 'sys/select.h'

select_constants = %w[
FD_SETSIZE
]

select_constants.each { |c| cg.const c }
end.write_constants(f)

# Not available on all platforms. Try to load these constants anyway.
Rubinius::FFI::Generators::Constants.new 'rbx.platform.advise' do |cg|
cg.include 'fcntl.h'

advise_constants = %w[
POSIX_FADV_NORMAL
POSIX_FADV_SEQUENTIAL
POSIX_FADV_RANDOM
POSIX_FADV_WILLNEED
POSIX_FADV_DONTNEED
POSIX_FADV_NOREUSE
]

advise_constants.each { |c| cg.const c }
end.write_constants(f)

# Only constants needed by core are added here
Rubinius::FFI::Generators::Constants.new 'rbx.platform.fcntl' do |cg|
8 changes: 8 additions & 0 deletions spec/core/ffi/memorypointer/new_spec.rb
Original file line number Diff line number Diff line change
@@ -3,4 +3,12 @@

describe "FFI::MemoryPointer.new" do
it "needs to be reviewed for spec completeness"

it "returns null when the byte size is negative" do
FFI::MemoryPointer.new(-1).should == FFI::Pointer::NULL
end

it "returns null when the count is negative" do
FFI::MemoryPointer.new(:int, -3).should == FFI::Pointer::NULL
end
end
13 changes: 0 additions & 13 deletions spec/core/io/buffer_spec.rb
Original file line number Diff line number Diff line change
@@ -24,17 +24,4 @@
@io.read(4)
@io.read(4).should == @contents.slice(8, 2)
end

it "doesn't confuse IO.select" do
read, write = @read, @write

write << "data"

IO.select([read],nil,nil,3).should == [[read],[],[]]

read.getc
read.buffer_empty?.should be_false

IO.select([read],nil,nil,3).should == [[read],[],[]]
end
end
150 changes: 77 additions & 73 deletions spec/ruby/core/io/advise_spec.rb
Original file line number Diff line number Diff line change
@@ -2,79 +2,83 @@
require File.expand_path('../../../spec_helper', __FILE__)
require File.expand_path('../fixtures/classes', __FILE__)

describe "IO#advise" do
before :each do
@kcode, $KCODE = $KCODE, "utf-8"
@io = IOSpecs.io_fixture "lines.txt"
end

after :each do
@io.close unless @io.closed?
$KCODE = @kcode
end

it "raises a TypeError if advise is not a Symbol" do
lambda {
@io.advise("normal")
}.should raise_error(TypeError)
end

it "raises a TypeError if offsert cannot be coerced to an Integer" do
lambda {
@io.advise(:normal, "wat")
}.should raise_error(TypeError)
end

it "raises a TypeError if len cannot be coerced to an Integer" do
lambda {
@io.advise(:normal, 0, "wat")
}.should raise_error(TypeError)
end

it "raises a RangeError if offset is too big" do
lambda {
@io.advise(:normal, 10 ** 32)
}.should raise_error(RangeError)
end

it "raises a RangeError if len is too big" do
lambda {
@io.advise(:normal, 0, 10 ** 32)
}.should raise_error(RangeError)
end
with_feature :posix_fadvise do

it "raises a NotImplementedError if advise is not recognized" do
lambda{
@io.advise(:foo)
}.should raise_error(NotImplementedError)
describe "IO#advise" do
before :each do
@kcode, $KCODE = $KCODE, "utf-8"
@io = IOSpecs.io_fixture "lines.txt"
end

after :each do
@io.close unless @io.closed?
$KCODE = @kcode
end

it "raises a TypeError if advise is not a Symbol" do
lambda {
@io.advise("normal")
}.should raise_error(TypeError)
end

it "raises a TypeError if offsert cannot be coerced to an Integer" do
lambda {
@io.advise(:normal, "wat")
}.should raise_error(TypeError)
end

it "raises a TypeError if len cannot be coerced to an Integer" do
lambda {
@io.advise(:normal, 0, "wat")
}.should raise_error(TypeError)
end

it "raises a RangeError if offset is too big" do
lambda {
@io.advise(:normal, 10 ** 32)
}.should raise_error(RangeError)
end

it "raises a RangeError if len is too big" do
lambda {
@io.advise(:normal, 0, 10 ** 32)
}.should raise_error(RangeError)
end

it "raises a NotImplementedError if advise is not recognized" do
lambda{
@io.advise(:foo)
}.should raise_error(NotImplementedError)
end

it "supports the normal advice type" do
@io.advise(:normal).should be_nil
end

it "supports the sequential advice type" do
@io.advise(:sequential).should be_nil
end

it "supports the random advice type" do
@io.advise(:random).should be_nil
end

it "supports the dontneed advice type" do
@io.advise(:dontneed).should be_nil
end

it "supports the noreuse advice type" do
@io.advise(:noreuse).should be_nil
end

it "supports the willneed advice type" do
@io.advise(:willneed).should be_nil
end

it "raises an IOError if the stream is closed" do
@io.close
lambda { @io.advise(:normal) }.should raise_error(IOError)
end
end

it "supports the normal advice type" do
@io.advise(:normal).should be_nil
end

it "supports the sequential advice type" do
@io.advise(:sequential).should be_nil
end

it "supports the random advice type" do
@io.advise(:random).should be_nil
end

it "supports the dontneed advice type" do
@io.advise(:dontneed).should be_nil
end

it "supports the noreuse advice type" do
@io.advise(:noreuse).should be_nil
end

it "supports the willneed advice type" do
@io.advise(:willneed).should be_nil
end

it "raises an IOError if the stream is closed" do
@io.close
lambda { @io.advise(:normal) }.should raise_error(IOError)
end
end
end
14 changes: 14 additions & 0 deletions spec/ruby/core/io/each_line_spec.rb
Original file line number Diff line number Diff line change
@@ -9,3 +9,17 @@
describe "IO#each_line" do
it_behaves_like :io_each_default_separator, :each_line
end

describe "IO#each_line" do
before :each do
@io = IOSpecs.io_fixture "lines.txt"
end

after :each do
@io.close
end

it "raises ArgumentError when given a limit of 0" do
lambda { @io.each_line(0) }.should raise_error(ArgumentError)
end
end
4 changes: 2 additions & 2 deletions spec/ruby/core/io/gets_spec.rb
Original file line number Diff line number Diff line change
@@ -228,11 +228,11 @@
end

it "reads limit bytes and extra bytes when limit is reached not at character boundary" do
[@io.gets(1), @io.gets(1)].should == ["朝", "日"]
[@io.gets(nil, 1), @io.gets(nil, 1)].should == ["朝", "日"]
end

it "read limit bytes and extra bytes with maximum of 16" do
@io.gets(7).should == "朝日\xE3" + "\x81\xE3" * 8
@io.gets(nil, 7).should == "朝日\xE3" + "\x81\xE3" * 8
end

after :each do
7 changes: 7 additions & 0 deletions spec/ruby/core/io/popen_spec.rb
Original file line number Diff line number Diff line change
@@ -21,6 +21,13 @@
lambda { @io.write('foo') }.should raise_error(IOError)
end

it "sees an infinitely looping subprocess exit when read pipe is closed" do
io = IO.popen "#{RUBY_EXE} -e 'r = loop{puts \"y\"; 0} rescue 1; exit r'", 'r'
io.close

$?.exitstatus.should_not == 0
end

platform_is_not :windows do
before :each do
@fname = tmp("IO_popen_spec")
21 changes: 21 additions & 0 deletions spec/ruby/core/io/pos_spec.rb
Original file line number Diff line number Diff line change
@@ -6,6 +6,27 @@
it_behaves_like :io_pos, :pos
end

describe "IO#pos" do
before :each do
@fname = tmp('pos-text.txt')
File.open @fname, 'w' do |f| f.write "123" end
end

after :each do
rm_r @fname
end

it "allows a negative value when calling #ungetc at beginning of stream" do
File.open @fname do |f|
f.pos.should == 0
f.ungetbyte(97)
f.pos.should == -1
f.getbyte
f.pos.should == 0
end
end
end

describe "IO#pos=" do
it_behaves_like :io_set_pos, :pos=
end
18 changes: 17 additions & 1 deletion spec/ruby/core/io/read_spec.rb
Original file line number Diff line number Diff line change
@@ -278,6 +278,23 @@
it "raises IOError on closed stream" do
lambda { IOSpecs.closed_io.read }.should raise_error(IOError)
end

it "raises IOError when stream is closed by another thread" do
r, w = IO.pipe
t = Thread.new do
begin
r.read(1)
rescue => e
e
end
end

sleep(0.1) until t.stop?
r.close
t.join
t.value.should be_kind_of(IOError)
w.close
end
end

platform_is :windows do
@@ -544,7 +561,6 @@

describe "IO#read with large data" do
before :each do
# TODO: what is the significance of this mystery math?
@data_size = 8096 * 2 + 1024
@data = "*" * @data_size

4 changes: 2 additions & 2 deletions spec/ruby/core/io/readlines_spec.rb
Original file line number Diff line number Diff line change
@@ -111,9 +111,9 @@
it "gets data from a fork when passed -" do
lines = IO.readlines("|-")

if lines # parent
if !lines.empty? # parent, #readlines always returns an array
lines.should == ["hello\n", "from a fork\n"]
else
elsif lines.empty?
puts "hello"
puts "from a fork"
exit!
30 changes: 30 additions & 0 deletions spec/ruby/core/io/reopen_spec.rb
Original file line number Diff line number Diff line change
@@ -21,12 +21,14 @@
it "calls #to_io to convert an object" do
obj = mock("io")
obj.should_receive(:to_io).and_return(@other_io)
obj.stub!(:pos).and_return(0)
@io.reopen obj
end

it "changes the class of the instance to the class of the object returned by #to_io" do
obj = mock("io")
obj.should_receive(:to_io).and_return(@other_io)
obj.stub!(:pos).and_return(0)
@io.reopen(obj).should be_an_instance_of(File)
end

@@ -191,6 +193,34 @@
end
end

describe "IO#reopen with an IO at EOF" do
before :each do
@name = tmp("io_reopen.txt")
touch(@name) { |f| f.puts "a line" }
@other_name = tmp("io_reopen_other.txt")
touch(@other_name) do |f|
f.puts "Line 1"
f.puts "Line 2"
end

@io = new_io @name, "r"
@other_io = new_io @other_name, "r"
@io.read
end

after :each do
@io.close unless @io.closed?
@other_io.close unless @other_io.closed?
rm_r @name, @other_name
end

it "resets the EOF status to false" do
@io.eof?.should be_true
@io.reopen @other_io
@io.eof?.should be_false
end
end

describe "IO#reopen with an IO" do
before :each do
@name = tmp("io_reopen.txt")
9 changes: 9 additions & 0 deletions spec/ruby/core/io/shared/write.rb
Original file line number Diff line number Diff line change
@@ -69,4 +69,13 @@
lambda { IOSpecs.closed_io.send(@method, "hello") }.should raise_error(IOError)
end

it "does not block when descriptor is set to nonblocking mode" do
r, w = IO.pipe
flags = Rubinius::FFI::Platform::POSIX.fcntl(w.fileno, IO::F_GETFL, 0)
Rubinius::FFI::Platform::POSIX.fcntl(w.fileno, IO::F_SETFL, flags | IO::O_NONBLOCK)

written = w.send(@method, 'a' * 1_000_000) # pick a number that will exceed buffer
written.should > 0
end

end
237 changes: 237 additions & 0 deletions spec/ruby/core/io/shared/z_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
#describe :io_readlines, :shared => true do
# it "raises TypeError if the first parameter is nil" do
# lambda { IO.send(@method, nil, &@object) }.should raise_error(TypeError)
# end
#
# it "raises an Errno::ENOENT if the file does not exist" do
# name = tmp("nonexistent.txt")
# lambda { IO.send(@method, name, &@object) }.should raise_error(Errno::ENOENT)
# end
#
# it "yields a single string with entire content when the separator is nil" do
# result = IO.send(@method, @name, nil, &@object)
# (result ? result : ScratchPad.recorded).should == [IO.read(@name)]
# end
#
# it "yields a sequence of paragraphs when the separator is an empty string" do
# result = IO.send(@method, @name, "", &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_empty_separator
# end
#end

#describe :io_readlines_options_18, :shared => true do
# it "does not change $_" do
# $_ = "test"
# IO.send(@method, @name, &@object)
# $_.should == "test"
# end
#
# describe "when passed name" do
# it "calls #to_str to convert the name" do
# name = mock("io readlines name")
# name.should_receive(:to_str).and_return(@name)
# result = IO.send(@method, name, &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines
# end
# end
#
# describe "when passed name, separator" do
# it "calls #to_str to convert the name" do
# name = mock("io readlines name")
# name.should_receive(:to_str).and_return(@name)
# result = IO.send(@method, name, &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines
# end
#
# it "calls #to_str to convert the separator" do
# sep = mock("io readlines separator")
# sep.should_receive(:to_str).at_least(1).and_return(" ")
# result = IO.send(@method, @name, sep, &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator
# end
# end
#end

describe :io_readlines_options_19, :shared => true do
before :each do
@filename = tmp("io readlines options")
end

after :each do
rm_r @filename
end

# describe "when passed name" do
# it "calls #to_path to convert the name" do
# name = mock("io name to_path")
# name.should_receive(:to_path).and_return(@name)
# IO.send(@method, name, &@object)
# end
#
# it "defaults to $/ as the separator" do
# result = IO.send(@method, @name, &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines
# end
# end

describe "when passed name, object" do
# it "calls #to_str to convert the object to a separator" do
# sep = mock("io readlines separator")
# sep.should_receive(:to_str).at_least(1).and_return(" ")
# result = IO.send(@method, @name, sep, &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator
# end

describe "when the object is a Fixnum" do
before :each do
@sep = $/
end

after :each do
$/ = @sep
end

# it "defaults to $/ as the separator" do
# $/ = " "
# result = IO.send(@method, @name, 10, &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
# end

it "uses the object as a limit if it is a Fixnum" do
result = IO.send(@method, @name, 10, &@object)
(result ? result : ScratchPad.recorded).should == IOSpecs.lines_limit
end
end

# describe "when the object is a String" do
# it "uses the value as the separator" do
# result = IO.send(@method, @name, " ", &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator
# end
#
# it "accepts non-ASCII data as separator" do
# result = IO.send(@method, @name, "\303\250".force_encoding("utf-8"), &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_arbitrary_separator
# end
# end
#
# describe "when the object is a Hash" do
# it "uses the value as the options hash" do
# result = IO.send(@method, @name, :mode => "r", &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines
# end
# end
end

# describe "when passed name, object, object" do
# describe "when the first object is a Fixnum" do
# it "uses the second object as an options Hash" do
# lambda do
# IO.send(@method, @filename, 10, :mode => "w", &@object)
# end.should raise_error(IOError)
# end
#
# it "calls #to_hash to convert the second object to a Hash" do
# options = mock("io readlines options Hash")
# options.should_receive(:to_hash).and_return({ :mode => "w" })
# lambda do
# IO.send(@method, @filename, 10, options, &@object)
# end.should raise_error(IOError)
# end
# end
#
# describe "when the first object is a String" do
# it "uses the second object as a limit if it is a Fixnum" do
# result = IO.send(@method, @name, " ", 10, &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
# end
#
# it "calls #to_int to convert the second object" do
# limit = mock("io readlines limit")
# limit.should_receive(:to_int).at_least(1).and_return(10)
# result = IO.send(@method, @name, " ", limit, &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
# end
#
# it "uses the second object as an options Hash" do
# lambda do
# IO.send(@method, @filename, " ", :mode => "w", &@object)
# end.should raise_error(IOError)
# end
#
# it "calls #to_hash to convert the second object to a Hash" do
# options = mock("io readlines options Hash")
# options.should_receive(:to_hash).and_return({ :mode => "w" })
# lambda do
# IO.send(@method, @filename, " ", options, &@object)
# end.should raise_error(IOError)
# end
# end
#
# describe "when the first object is not a String or Fixnum" do
# it "calls #to_str to convert the object to a String" do
# sep = mock("io readlines separator")
# sep.should_receive(:to_str).at_least(1).and_return(" ")
# result = IO.send(@method, @name, sep, 10, :mode => "r", &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
# end
#
# it "uses the second object as a limit if it is a Fixnum" do
# result = IO.send(@method, @name, " ", 10, :mode => "r", &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
# end
#
# it "calls #to_int to convert the second object" do
# limit = mock("io readlines limit")
# limit.should_receive(:to_int).at_least(1).and_return(10)
# result = IO.send(@method, @name, " ", limit, &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
# end
#
# it "uses the second object as an options Hash" do
# lambda do
# IO.send(@method, @filename, " ", :mode => "w", &@object)
# end.should raise_error(IOError)
# end
#
# it "calls #to_hash to convert the second object to a Hash" do
# options = mock("io readlines options Hash")
# options.should_receive(:to_hash).and_return({ :mode => "w" })
# lambda do
# IO.send(@method, @filename, " ", options, &@object)
# end.should raise_error(IOError)
# end
# end
# end

# describe "when passed name, separator, limit, options" do
# it "calls #to_path to convert the name object" do
# name = mock("io name to_path")
# name.should_receive(:to_path).and_return(@name)
# result = IO.send(@method, name, " ", 10, :mode => "r", &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
# end
#
# it "calls #to_str to convert the separator object" do
# sep = mock("io readlines separator")
# sep.should_receive(:to_str).at_least(1).and_return(" ")
# result = IO.send(@method, @name, sep, 10, :mode => "r", &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
# end
#
# it "calls #to_int to convert the limit argument" do
# limit = mock("io readlines limit")
# limit.should_receive(:to_int).at_least(1).and_return(10)
# result = IO.send(@method, @name, " ", limit, :mode => "r", &@object)
# (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
# end
#
# it "calls #to_hash to convert the options object" do
# options = mock("io readlines options Hash")
# options.should_receive(:to_hash).and_return({ :mode => "w" })
# lambda do
# IO.send(@method, @filename, " ", 10, options, &@object)
# end.should raise_error(IOError)
# end
# end
end
7 changes: 7 additions & 0 deletions spec/ruby/core/io/tty_spec.rb
Original file line number Diff line number Diff line change
@@ -4,3 +4,10 @@
describe "IO#tty?" do
it_behaves_like :io_tty, :tty?
end

describe "IO#ttyname" do
it "returns the name of the STDOUT tty" do
io = $stdout
io.ttyname.should =~ Regexp.new('/dev/')
end
end
12 changes: 0 additions & 12 deletions spec/ruby/core/io/ungetc_spec.rb
Original file line number Diff line number Diff line change
@@ -81,18 +81,6 @@
@io.pos.should == pos - 1
end

# TODO: file MRI bug
# Another specified behavior that MRI doesn't follow:
# "Has no effect with unbuffered reads (such as IO#sysread)."
#
#it "has no effect with unbuffered reads" do
# length = File.size(@io_name)
# content = @io.sysread(length)
# @io.rewind
# @io.ungetc(100)
# @io.sysread(length).should == content
#end

it "makes subsequent unbuffered operations to raise IOError" do
@io.getc
@io.ungetc(100)
4 changes: 1 addition & 3 deletions spec/ruby/core/io/write_spec.rb
Original file line number Diff line number Diff line change
@@ -20,9 +20,7 @@
rm_r @filename
end

# TODO: impl detail? discuss this with matz. This spec is useless. - rdavis
# I agree. I've marked it not compliant on macruby, as we don't buffer input. -pthomson
not_compliant_on :macruby do
not_compliant_on :macruby, :rubinius do
it "writes all of the string's bytes but buffers them" do
written = @file.write("abcde")
written.should == 5
13 changes: 11 additions & 2 deletions spec/ruby/core/objectspace/define_finalizer_spec.rb
Original file line number Diff line number Diff line change
@@ -61,6 +61,15 @@ def handler.call(obj) end

# see [ruby-core:24095]
with_feature :fork do
before :each do
@fname = tmp("finalizer_test.txt")
@contents = "finalized"
end

after :each do
rm_r @fname
end

it "calls finalizer on process termination" do
rd, wr = IO.pipe

@@ -85,15 +94,15 @@ def handler.call(obj) end
pid = Kernel::fork do
rd.close
obj = "Test"
handler = Proc.new { wr.write "finalized"; wr.close }
handler = Proc.new { wr.write(@contents); wr.close }
ObjectSpace.define_finalizer(obj, handler)
exit 0
end

wr.close
Process.waitpid pid

rd.read.should == "finalized"
rd.read.should == @contents
rd.close
end

0 comments on commit a8f23c1

Please sign in to comment.