summaryrefslogtreecommitdiff
path: root/spec/ruby/shared
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/shared')
-rw-r--r--spec/ruby/shared/basicobject/method_missing.rb124
-rw-r--r--spec/ruby/shared/basicobject/send.rb128
-rw-r--r--spec/ruby/shared/enumerable/minmax.rb24
-rw-r--r--spec/ruby/shared/file/blockdev.rb9
-rw-r--r--spec/ruby/shared/file/chardev.rb9
-rw-r--r--spec/ruby/shared/file/directory.rb66
-rw-r--r--spec/ruby/shared/file/executable.rb83
-rw-r--r--spec/ruby/shared/file/executable_real.rb81
-rw-r--r--spec/ruby/shared/file/exist.rb19
-rw-r--r--spec/ruby/shared/file/file.rb45
-rw-r--r--spec/ruby/shared/file/grpowned.rb39
-rw-r--r--spec/ruby/shared/file/identical.rb51
-rw-r--r--spec/ruby/shared/file/owned.rb3
-rw-r--r--spec/ruby/shared/file/pipe.rb3
-rw-r--r--spec/ruby/shared/file/readable.rb49
-rw-r--r--spec/ruby/shared/file/readable_real.rb39
-rw-r--r--spec/ruby/shared/file/setgid.rb2
-rw-r--r--spec/ruby/shared/file/setuid.rb2
-rw-r--r--spec/ruby/shared/file/size.rb124
-rw-r--r--spec/ruby/shared/file/socket.rb33
-rw-r--r--spec/ruby/shared/file/sticky.rb29
-rw-r--r--spec/ruby/shared/file/symlink.rb46
-rw-r--r--spec/ruby/shared/file/world_readable.rb49
-rw-r--r--spec/ruby/shared/file/world_writable.rb49
-rw-r--r--spec/ruby/shared/file/writable.rb44
-rw-r--r--spec/ruby/shared/file/writable_real.rb49
-rw-r--r--spec/ruby/shared/file/zero.rb68
-rw-r--r--spec/ruby/shared/hash/key_error.rb23
-rw-r--r--spec/ruby/shared/io/putc.rb57
-rw-r--r--spec/ruby/shared/kernel/at_exit.rb76
-rw-r--r--spec/ruby/shared/kernel/complex.rb133
-rw-r--r--spec/ruby/shared/kernel/equal.rb54
-rw-r--r--spec/ruby/shared/kernel/fixtures/END.rb3
-rw-r--r--spec/ruby/shared/kernel/fixtures/at_exit.rb3
-rw-r--r--spec/ruby/shared/kernel/object_id.rb100
-rw-r--r--spec/ruby/shared/kernel/raise.rb398
-rw-r--r--spec/ruby/shared/process/abort.rb36
-rw-r--r--spec/ruby/shared/process/exit.rb126
-rw-r--r--spec/ruby/shared/process/fork.rb90
-rw-r--r--spec/ruby/shared/queue/clear.rb12
-rw-r--r--spec/ruby/shared/queue/close.rb14
-rw-r--r--spec/ruby/shared/queue/closed.rb12
-rw-r--r--spec/ruby/shared/queue/deque.rb164
-rw-r--r--spec/ruby/shared/queue/empty.rb12
-rw-r--r--spec/ruby/shared/queue/enque.rb18
-rw-r--r--spec/ruby/shared/queue/freeze.rb18
-rw-r--r--spec/ruby/shared/queue/length.rb9
-rw-r--r--spec/ruby/shared/queue/num_waiting.rb16
-rw-r--r--spec/ruby/shared/sizedqueue/enque.rb129
-rw-r--r--spec/ruby/shared/sizedqueue/max.rb47
-rw-r--r--spec/ruby/shared/sizedqueue/new.rb23
-rw-r--r--spec/ruby/shared/sizedqueue/num_waiting.rb12
-rw-r--r--spec/ruby/shared/string/end_with.rb61
-rw-r--r--spec/ruby/shared/string/start_with.rb84
-rw-r--r--spec/ruby/shared/string/times.rb58
-rw-r--r--spec/ruby/shared/time/strftime_for_date.rb273
-rw-r--r--spec/ruby/shared/time/strftime_for_time.rb181
-rw-r--r--spec/ruby/shared/time/yday.rb18
-rw-r--r--spec/ruby/shared/types/rb_num2dbl_fails.rb17
59 files changed, 3544 insertions, 0 deletions
diff --git a/spec/ruby/shared/basicobject/method_missing.rb b/spec/ruby/shared/basicobject/method_missing.rb
new file mode 100644
index 0000000000..4871603dce
--- /dev/null
+++ b/spec/ruby/shared/basicobject/method_missing.rb
@@ -0,0 +1,124 @@
+require_relative '../../spec_helper'
+require_relative '../../fixtures/basicobject/method_missing'
+
+describe :method_missing_defined_module, shared: true do
+ describe "for a Module with #method_missing defined" do
+ it "is not called when a defined method is called" do
+ @object.method_public.should == :module_public_method
+ end
+
+ it "is called when a not defined method is called" do
+ @object.not_defined_method.should == :module_method_missing
+ end
+
+ it "is called when a protected method is called" do
+ @object.method_protected.should == :module_method_missing
+ end
+
+ it "is called when a private method is called" do
+ @object.method_private.should == :module_method_missing
+ end
+ end
+end
+
+describe :method_missing_module, shared: true do
+ describe "for a Module" do
+ it "raises a NoMethodError when an undefined method is called" do
+ -> { @object.no_such_method }.should raise_error(NoMethodError)
+ end
+
+ it "raises a NoMethodError when a protected method is called" do
+ -> { @object.method_protected }.should raise_error(NoMethodError)
+ end
+
+ it "raises a NoMethodError when a private method is called" do
+ -> { @object.method_private }.should raise_error(NoMethodError)
+ end
+ end
+end
+
+describe :method_missing_defined_class, shared: true do
+ describe "for a Class with #method_missing defined" do
+ it "is not called when a defined method is called" do
+ @object.method_public.should == :class_public_method
+ end
+
+ it "is called when an undefined method is called" do
+ @object.no_such_method.should == :class_method_missing
+ end
+
+ it "is called when an protected method is called" do
+ @object.method_protected.should == :class_method_missing
+ end
+
+ it "is called when an private method is called" do
+ @object.method_private.should == :class_method_missing
+ end
+ end
+end
+
+describe :method_missing_class, shared: true do
+ describe "for a Class" do
+ it "raises a NoMethodError when an undefined method is called" do
+ -> { @object.no_such_method }.should raise_error(NoMethodError)
+ end
+
+ it "raises a NoMethodError when a protected method is called" do
+ -> { @object.method_protected }.should raise_error(NoMethodError)
+ end
+
+ it "raises a NoMethodError when a private method is called" do
+ -> { @object.method_private }.should raise_error(NoMethodError)
+ end
+ end
+end
+
+describe :method_missing_defined_instance, shared: true do
+ describe "for an instance with #method_missing defined" do
+ before :each do
+ @instance = @object.new
+ end
+
+ it "is not called when a defined method is called" do
+ @instance.method_public.should == :instance_public_method
+ end
+
+ it "is called when an undefined method is called" do
+ @instance.no_such_method.should == :instance_method_missing
+ end
+
+ it "is called when an protected method is called" do
+ @instance.method_protected.should == :instance_method_missing
+ end
+
+ it "is called when an private method is called" do
+ @instance.method_private.should == :instance_method_missing
+ end
+ end
+end
+
+describe :method_missing_instance, shared: true do
+ describe "for an instance" do
+ it "raises a NoMethodError when an undefined method is called" do
+ -> { @object.new.no_such_method }.should raise_error(NoMethodError)
+ end
+
+ it "raises a NoMethodError when a protected method is called" do
+ -> { @object.new.method_protected }.should raise_error(NoMethodError)
+ end
+
+ it "raises a NoMethodError when a private method is called" do
+ -> { @object.new.method_private }.should raise_error(NoMethodError)
+ end
+
+ it 'sets the receiver of the raised NoMethodError' do
+ obj = @object.new
+
+ begin
+ obj.method_private
+ rescue NoMethodError => error
+ (error.receiver == obj).should == true
+ end
+ end
+ end
+end
diff --git a/spec/ruby/shared/basicobject/send.rb b/spec/ruby/shared/basicobject/send.rb
new file mode 100644
index 0000000000..625aaa2917
--- /dev/null
+++ b/spec/ruby/shared/basicobject/send.rb
@@ -0,0 +1,128 @@
+module SendSpecs
+end
+
+describe :basicobject_send, shared: true do
+ it "invokes the named method" do
+ class SendSpecs::Foo
+ def bar
+ 'done'
+ end
+ end
+ SendSpecs::Foo.new.send(@method, :bar).should == 'done'
+ end
+
+ it "accepts a String method name" do
+ class SendSpecs::Foo
+ def bar
+ 'done'
+ end
+ end
+ SendSpecs::Foo.new.send(@method, 'bar').should == 'done'
+ end
+
+ it "invokes a class method if called on a class" do
+ class SendSpecs::Foo
+ def self.bar
+ 'done'
+ end
+ end
+ SendSpecs::Foo.send(@method, :bar).should == 'done'
+ end
+
+ it "raises a TypeError if the method name is not a string or symbol" do
+ -> { SendSpecs.send(@method, nil) }.should raise_error(TypeError, /not a symbol nor a string/)
+ -> { SendSpecs.send(@method, 42) }.should raise_error(TypeError, /not a symbol nor a string/)
+ -> { SendSpecs.send(@method, 3.14) }.should raise_error(TypeError, /not a symbol nor a string/)
+ -> { SendSpecs.send(@method, true) }.should raise_error(TypeError, /not a symbol nor a string/)
+ end
+
+ it "raises a NameError if the corresponding method can't be found" do
+ class SendSpecs::Foo
+ def bar
+ 'done'
+ end
+ end
+ -> { SendSpecs::Foo.new.send(@method, :syegsywhwua) }.should raise_error(NameError)
+ end
+
+ it "raises a NameError if the corresponding singleton method can't be found" do
+ class SendSpecs::Foo
+ def self.bar
+ 'done'
+ end
+ end
+ -> { SendSpecs::Foo.send(@method, :baz) }.should raise_error(NameError)
+ end
+
+ it "raises an ArgumentError if no arguments are given" do
+ class SendSpecs::Foo; end
+ -> { SendSpecs::Foo.new.send @method }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if called with more arguments than available parameters" do
+ class SendSpecs::Foo
+ def bar; end
+ end
+
+ -> { SendSpecs::Foo.new.send(@method, :bar, :arg) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError if called with fewer arguments than required parameters" do
+ class SendSpecs::Foo
+ def foo(arg); end
+ end
+
+ -> { SendSpecs::Foo.new.send(@method, :foo) }.should raise_error(ArgumentError)
+ end
+
+ it "succeeds if passed an arbitrary number of arguments as a splat parameter" do
+ class SendSpecs::Foo
+ def baz(*args) args end
+ end
+
+ begin
+ SendSpecs::Foo.new.send(@method, :baz).should == []
+ SendSpecs::Foo.new.send(@method, :baz, :quux).should == [:quux]
+ SendSpecs::Foo.new.send(@method, :baz, :quux, :foo).should == [:quux, :foo]
+ rescue
+ fail
+ end
+ end
+
+ it "succeeds when passing 1 or more arguments as a required and a splat parameter" do
+ class SendSpecs::Foo
+ def baz(first, *rest) [first, *rest] end
+ end
+
+ SendSpecs::Foo.new.send(@method, :baz, :quux).should == [:quux]
+ SendSpecs::Foo.new.send(@method, :baz, :quux, :foo).should == [:quux, :foo]
+ end
+
+ it "succeeds when passing 0 arguments to a method with one parameter with a default" do
+ class SendSpecs::Foo
+ def foo(first = true) first end
+ end
+
+ begin
+ SendSpecs::Foo.new.send(@method, :foo).should == true
+ SendSpecs::Foo.new.send(@method, :foo, :arg).should == :arg
+ rescue
+ fail
+ end
+ end
+
+ it "has a negative arity" do
+ method(@method).arity.should < 0
+ end
+
+ it "invokes module methods with super correctly" do
+ m1 = Module.new { def foo(ary); ary << :m1; end; }
+ m2 = Module.new { def foo(ary = []); super(ary); ary << :m2; end; }
+ c2 = Class.new do
+ include m1
+ include m2
+ end
+
+ c2.new.send(@method, :foo, *[[]]).should == %i[m1 m2]
+ end
+end
diff --git a/spec/ruby/shared/enumerable/minmax.rb b/spec/ruby/shared/enumerable/minmax.rb
new file mode 100644
index 0000000000..8af2626d2a
--- /dev/null
+++ b/spec/ruby/shared/enumerable/minmax.rb
@@ -0,0 +1,24 @@
+describe :enumerable_minmax, shared: true do
+ it "min should return the minimum element" do
+ @enum.minmax.should == [4, 10]
+ @strs.minmax.should == ["1010", "60"]
+ end
+
+ it "returns the minimum when using a block rule" do
+ @enum.minmax {|a,b| b <=> a }.should == [10, 4]
+ @strs.minmax {|a,b| a.length <=> b.length }.should == ["2", "55555"]
+ end
+
+ it "returns [nil, nil] for an empty Enumerable" do
+ @empty_enum.minmax.should == [nil, nil]
+ end
+
+ it "raises a NoMethodError for elements without #<=>" do
+ -> { @incomparable_enum.minmax }.should raise_error(NoMethodError)
+ end
+
+ it "raises an ArgumentError when elements are incompatible" do
+ -> { @incompatible_enum.minmax }.should raise_error(ArgumentError)
+ -> { @enum.minmax{ |a, b| nil } }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/shared/file/blockdev.rb b/spec/ruby/shared/file/blockdev.rb
new file mode 100644
index 0000000000..b0b3ea040a
--- /dev/null
+++ b/spec/ruby/shared/file/blockdev.rb
@@ -0,0 +1,9 @@
+describe :file_blockdev, shared: true do
+ it "returns true/false depending if the named file is a block device" do
+ @object.send(@method, tmp("")).should == false
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(tmp(""))).should == false
+ end
+end
diff --git a/spec/ruby/shared/file/chardev.rb b/spec/ruby/shared/file/chardev.rb
new file mode 100644
index 0000000000..8a7a89fd05
--- /dev/null
+++ b/spec/ruby/shared/file/chardev.rb
@@ -0,0 +1,9 @@
+describe :file_chardev, shared: true do
+ it "returns true/false depending if the named file is a char device" do
+ @object.send(@method, tmp("")).should == false
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(tmp(""))).should == false
+ end
+end
diff --git a/spec/ruby/shared/file/directory.rb b/spec/ruby/shared/file/directory.rb
new file mode 100644
index 0000000000..8ba933a601
--- /dev/null
+++ b/spec/ruby/shared/file/directory.rb
@@ -0,0 +1,66 @@
+describe :file_directory, shared: true do
+ before :each do
+ @dir = tmp("file_directory")
+ @file = tmp("file_directory.txt")
+
+ mkdir_p @dir
+ touch @file
+ end
+
+ after :each do
+ rm_r @dir, @file
+ end
+
+ it "returns true if the argument is a directory" do
+ @object.send(@method, @dir).should be_true
+ end
+
+ it "returns false if the argument is not a directory" do
+ @object.send(@method, @file).should be_false
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(@dir)).should be_true
+ end
+
+ it "raises a TypeError when passed an Integer" do
+ -> { @object.send(@method, 1) }.should raise_error(TypeError)
+ -> { @object.send(@method, bignum_value) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { @object.send(@method, nil) }.should raise_error(TypeError)
+ end
+end
+
+describe :file_directory_io, shared: true do
+ before :each do
+ @dir = tmp("file_directory_io")
+ @file = tmp("file_directory_io.txt")
+
+ mkdir_p @dir
+ touch @file
+ end
+
+ after :each do
+ rm_r @dir, @file
+ end
+
+ it "returns false if the argument is an IO that's not a directory" do
+ @object.send(@method, STDIN).should be_false
+ end
+
+ platform_is_not :windows do
+ it "returns true if the argument is an IO that is a directory" do
+ File.open(@dir, "r") do |f|
+ @object.send(@method, f).should be_true
+ end
+ end
+ end
+
+ it "calls #to_io to convert a non-IO object" do
+ io = mock('FileDirectoryIO')
+ io.should_receive(:to_io).and_return(STDIN)
+ @object.send(@method, io).should be_false
+ end
+end
diff --git a/spec/ruby/shared/file/executable.rb b/spec/ruby/shared/file/executable.rb
new file mode 100644
index 0000000000..baa156de98
--- /dev/null
+++ b/spec/ruby/shared/file/executable.rb
@@ -0,0 +1,83 @@
+describe :file_executable, shared: true do
+ before :each do
+ @file1 = tmp('temp1.txt')
+ @file2 = tmp('temp2.txt')
+
+ touch @file1
+ touch @file2
+
+ File.chmod(0755, @file1)
+ end
+
+ after :each do
+ rm_r @file1, @file2
+ end
+
+ platform_is_not :windows, :android do
+ it "returns true if named file is executable by the effective user id of the process, otherwise false" do
+ @object.send(@method, '/etc/passwd').should == false
+ @object.send(@method, @file1).should == true
+ @object.send(@method, @file2).should == false
+ end
+
+ it "returns true if the argument is an executable file" do
+ @object.send(@method, @file1).should == true
+ @object.send(@method, @file2).should == false
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(@file1)).should == true
+ end
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { @object.send(@method) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { @object.send(@method, 1) }.should raise_error(TypeError)
+ -> { @object.send(@method, nil) }.should raise_error(TypeError)
+ -> { @object.send(@method, false) }.should raise_error(TypeError)
+ end
+
+ platform_is_not :windows do
+ as_superuser do
+ context "when run by a superuser" do
+ before :each do
+ @file = tmp('temp3.txt')
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns true if file owner has permission to execute" do
+ File.chmod(0766, @file)
+ @object.send(@method, @file).should == true
+ end
+
+ it "returns true if group has permission to execute" do
+ File.chmod(0676, @file)
+ @object.send(@method, @file).should == true
+ end
+
+ it "returns true if other have permission to execute" do
+ File.chmod(0667, @file)
+ @object.send(@method, @file).should == true
+ end
+
+ it "return false if nobody has permission to execute" do
+ File.chmod(0666, @file)
+ @object.send(@method, @file).should == false
+ end
+ end
+ end
+ end
+end
+
+describe :file_executable_missing, shared: true do
+ it "returns false if the file does not exist" do
+ @object.send(@method, 'fake_file').should == false
+ end
+end
diff --git a/spec/ruby/shared/file/executable_real.rb b/spec/ruby/shared/file/executable_real.rb
new file mode 100644
index 0000000000..bf2734ea07
--- /dev/null
+++ b/spec/ruby/shared/file/executable_real.rb
@@ -0,0 +1,81 @@
+describe :file_executable_real, shared: true do
+ before :each do
+ @file1 = tmp('temp1.txt.exe')
+ @file2 = tmp('temp2.txt')
+
+ touch @file1
+ touch @file2
+
+ File.chmod(0755, @file1)
+ end
+
+ after :each do
+ rm_r @file1, @file2
+ end
+
+ platform_is_not :windows do
+ it "returns true if the file its an executable" do
+ @object.send(@method, @file1).should == true
+ @object.send(@method, @file2).should == false
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(@file1)).should == true
+ end
+ end
+
+ it "returns true if named file is readable by the real user id of the process, otherwise false" do
+ @object.send(@method, @file1).should == true
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { @object.send(@method) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { @object.send(@method, 1) }.should raise_error(TypeError)
+ -> { @object.send(@method, nil) }.should raise_error(TypeError)
+ -> { @object.send(@method, false) }.should raise_error(TypeError)
+ end
+
+ platform_is_not :windows do
+ as_real_superuser do
+ context "when run by a real superuser" do
+ before :each do
+ @file = tmp('temp3.txt')
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns true if file owner has permission to execute" do
+ File.chmod(0766, @file)
+ @object.send(@method, @file).should == true
+ end
+
+ it "returns true if group has permission to execute" do
+ File.chmod(0676, @file)
+ @object.send(@method, @file).should == true
+ end
+
+ it "returns true if other have permission to execute" do
+ File.chmod(0667, @file)
+ @object.send(@method, @file).should == true
+ end
+
+ it "return false if nobody has permission to execute" do
+ File.chmod(0666, @file)
+ @object.send(@method, @file).should == false
+ end
+ end
+ end
+ end
+end
+
+describe :file_executable_real_missing, shared: true do
+ it "returns false if the file does not exist" do
+ @object.send(@method, 'fake_file').should == false
+ end
+end
diff --git a/spec/ruby/shared/file/exist.rb b/spec/ruby/shared/file/exist.rb
new file mode 100644
index 0000000000..67424146c5
--- /dev/null
+++ b/spec/ruby/shared/file/exist.rb
@@ -0,0 +1,19 @@
+describe :file_exist, shared: true do
+ it "returns true if the file exist" do
+ @object.send(@method, __FILE__).should == true
+ @object.send(@method, 'a_fake_file').should == false
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { @object.send(@method) }.should raise_error(ArgumentError)
+ -> { @object.send(@method, __FILE__, __FILE__) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { @object.send(@method, nil) }.should raise_error(TypeError)
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(__FILE__)).should == true
+ end
+end
diff --git a/spec/ruby/shared/file/file.rb b/spec/ruby/shared/file/file.rb
new file mode 100644
index 0000000000..c1748a88b3
--- /dev/null
+++ b/spec/ruby/shared/file/file.rb
@@ -0,0 +1,45 @@
+describe :file_file, shared: true do
+ before :each do
+ platform_is :windows do
+ @null = "NUL"
+ @dir = "C:\\"
+ end
+
+ platform_is_not :windows do
+ @null = "/dev/null"
+ @dir = "/bin"
+ end
+
+ @file = tmp("test.txt")
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns true if the named file exists and is a regular file." do
+ @object.send(@method, @file).should == true
+ @object.send(@method, @dir).should == false
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(@file)).should == true
+ end
+
+ platform_is_not :windows do
+ it "returns true if the null device exists and is a regular file." do
+ @object.send(@method, @null).should == false # May fail on MS Windows
+ end
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { @object.send(@method) }.should raise_error(ArgumentError)
+ -> { @object.send(@method, @null, @file) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { @object.send(@method, nil) }.should raise_error(TypeError)
+ -> { @object.send(@method, 1) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/shared/file/grpowned.rb b/spec/ruby/shared/file/grpowned.rb
new file mode 100644
index 0000000000..24e84c28e3
--- /dev/null
+++ b/spec/ruby/shared/file/grpowned.rb
@@ -0,0 +1,39 @@
+describe :file_grpowned, shared: true do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.puts "file_content" }
+ File.chown(nil, Process.gid, @file) rescue nil
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ platform_is_not :windows do
+ it "returns true if the file exist" do
+ @object.send(@method, @file).should be_true
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(@file)).should be_true
+ end
+
+ it 'takes non primary groups into account' do
+ group = (Process.groups - [Process.egid]).first
+
+ if group
+ File.chown(nil, group, @file)
+
+ @object.send(@method, @file).should == true
+ else
+ skip "No supplementary groups"
+ end
+ end
+ end
+
+ platform_is :windows do
+ it "returns false if the file exist" do
+ @object.send(@method, @file).should be_false
+ end
+ end
+end
diff --git a/spec/ruby/shared/file/identical.rb b/spec/ruby/shared/file/identical.rb
new file mode 100644
index 0000000000..b7a2904839
--- /dev/null
+++ b/spec/ruby/shared/file/identical.rb
@@ -0,0 +1,51 @@
+describe :file_identical, shared: true do
+ before :each do
+ @file1 = tmp('file_identical.txt')
+ @file2 = tmp('file_identical2.txt')
+ @link = tmp('file_identical.lnk')
+ @non_exist = 'non_exist'
+
+ touch(@file1) { |f| f.puts "file1" }
+ touch(@file2) { |f| f.puts "file2" }
+
+ rm_r @link
+ begin
+ File.link(@file1, @link)
+ rescue Errno::EACCES
+ File.symlink(@file1, @link)
+ end
+ end
+
+ after :each do
+ rm_r @link, @file1, @file2
+ end
+
+ it "returns true for a file and its link" do
+ @object.send(@method, @file1, @link).should == true
+ end
+
+ it "returns false if any of the files doesn't exist" do
+ @object.send(@method, @file1, @non_exist).should be_false
+ @object.send(@method, @non_exist, @file1).should be_false
+ @object.send(@method, @non_exist, @non_exist).should be_false
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(@file1), mock_to_path(@link)).should == true
+ end
+
+ it "raises an ArgumentError if not passed two arguments" do
+ -> { @object.send(@method, @file1, @file2, @link) }.should raise_error(ArgumentError)
+ -> { @object.send(@method, @file1) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed String types" do
+ -> { @object.send(@method, 1,1) }.should raise_error(TypeError)
+ end
+
+ it "returns true if both named files are identical" do
+ @object.send(@method, @file1, @file1).should be_true
+ @object.send(@method, @link, @link).should be_true
+ @object.send(@method, @file1, @file2).should be_false
+ end
+end
diff --git a/spec/ruby/shared/file/owned.rb b/spec/ruby/shared/file/owned.rb
new file mode 100644
index 0000000000..4a08a4ed89
--- /dev/null
+++ b/spec/ruby/shared/file/owned.rb
@@ -0,0 +1,3 @@
+describe :file_owned, shared: true do
+ it "accepts an object that has a #to_path method"
+end
diff --git a/spec/ruby/shared/file/pipe.rb b/spec/ruby/shared/file/pipe.rb
new file mode 100644
index 0000000000..7e150b9167
--- /dev/null
+++ b/spec/ruby/shared/file/pipe.rb
@@ -0,0 +1,3 @@
+describe :file_pipe, shared: true do
+ it "accepts an object that has a #to_path method"
+end
diff --git a/spec/ruby/shared/file/readable.rb b/spec/ruby/shared/file/readable.rb
new file mode 100644
index 0000000000..7b45e23e36
--- /dev/null
+++ b/spec/ruby/shared/file/readable.rb
@@ -0,0 +1,49 @@
+describe :file_readable, shared: true do
+ before :each do
+ @file = tmp('i_exist')
+ platform_is :windows do
+ @file2 = File.join(ENV["WINDIR"], "system32/drivers/etc/services").tr(File::SEPARATOR, File::ALT_SEPARATOR)
+ end
+ platform_is_not :windows, :android do
+ @file2 = "/etc/passwd"
+ end
+ platform_is :android do
+ @file2 = "/system/bin/sh"
+ end
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns true if named file is readable by the effective user id of the process, otherwise false" do
+ @object.send(@method, @file2).should == true
+ File.open(@file,'w') { @object.send(@method, @file).should == true }
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(@file2)).should == true
+ end
+
+ platform_is_not :windows do
+ as_superuser do
+ context "when run by a superuser" do
+ it "returns true unconditionally" do
+ file = tmp('temp.txt')
+ touch file
+
+ File.chmod(0333, file)
+ @object.send(@method, file).should == true
+
+ rm_r file
+ end
+ end
+ end
+ end
+end
+
+describe :file_readable_missing, shared: true do
+ it "returns false if the file does not exist" do
+ @object.send(@method, 'fake_file').should == false
+ end
+end
diff --git a/spec/ruby/shared/file/readable_real.rb b/spec/ruby/shared/file/readable_real.rb
new file mode 100644
index 0000000000..32d38bc7a2
--- /dev/null
+++ b/spec/ruby/shared/file/readable_real.rb
@@ -0,0 +1,39 @@
+describe :file_readable_real, shared: true do
+ before :each do
+ @file = tmp('i_exist')
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns true if named file is readable by the real user id of the process, otherwise false" do
+ File.open(@file,'w') { @object.send(@method, @file).should == true }
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.open(@file,'w') { @object.send(@method, mock_to_path(@file)).should == true }
+ end
+
+ platform_is_not :windows do
+ as_real_superuser do
+ context "when run by a real superuser" do
+ it "returns true unconditionally" do
+ file = tmp('temp.txt')
+ touch file
+
+ File.chmod(0333, file)
+ @object.send(@method, file).should == true
+
+ rm_r file
+ end
+ end
+ end
+ end
+end
+
+describe :file_readable_real_missing, shared: true do
+ it "returns false if the file does not exist" do
+ @object.send(@method, 'fake_file').should == false
+ end
+end
diff --git a/spec/ruby/shared/file/setgid.rb b/spec/ruby/shared/file/setgid.rb
new file mode 100644
index 0000000000..9893795832
--- /dev/null
+++ b/spec/ruby/shared/file/setgid.rb
@@ -0,0 +1,2 @@
+describe :file_setgid, shared: true do
+end
diff --git a/spec/ruby/shared/file/setuid.rb b/spec/ruby/shared/file/setuid.rb
new file mode 100644
index 0000000000..6401674a94
--- /dev/null
+++ b/spec/ruby/shared/file/setuid.rb
@@ -0,0 +1,2 @@
+describe :file_setuid, shared: true do
+end
diff --git a/spec/ruby/shared/file/size.rb b/spec/ruby/shared/file/size.rb
new file mode 100644
index 0000000000..cba99fe6a5
--- /dev/null
+++ b/spec/ruby/shared/file/size.rb
@@ -0,0 +1,124 @@
+describe :file_size, shared: true do
+ before :each do
+ @exists = tmp('i_exist')
+ touch(@exists) { |f| f.write 'rubinius' }
+ end
+
+ after :each do
+ rm_r @exists
+ end
+
+ it "returns the size of the file if it exists and is not empty" do
+ @object.send(@method, @exists).should == 8
+ end
+
+ it "accepts a String-like (to_str) parameter" do
+ obj = mock("file")
+ obj.should_receive(:to_str).and_return(@exists)
+
+ @object.send(@method, obj).should == 8
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(@exists)).should == 8
+ end
+end
+
+describe :file_size_to_io, shared: true do
+ before :each do
+ @exists = tmp('i_exist')
+ touch(@exists) { |f| f.write 'rubinius' }
+ @file = File.open(@exists, 'r')
+ end
+
+ after :each do
+ @file.close unless @file.closed?
+ rm_r @exists
+ end
+
+ it "calls #to_io to convert the argument to an IO" do
+ obj = mock("io like")
+ obj.should_receive(:to_io).and_return(@file)
+
+ @object.send(@method, obj).should == 8
+ end
+end
+
+describe :file_size_raise_when_missing, shared: true do
+ before :each do
+ # TODO: missing_file
+ @missing = tmp("i_dont_exist")
+ rm_r @missing
+ end
+
+ after :each do
+ rm_r @missing
+ end
+
+ it "raises an error if file_name doesn't exist" do
+ -> {@object.send(@method, @missing)}.should raise_error(Errno::ENOENT)
+ end
+end
+
+describe :file_size_nil_when_missing, shared: true do
+ before :each do
+ # TODO: missing_file
+ @missing = tmp("i_dont_exist")
+ rm_r @missing
+ end
+
+ after :each do
+ rm_r @missing
+ end
+
+ it "returns nil if file_name doesn't exist or has 0 size" do
+ @object.send(@method, @missing).should == nil
+ end
+end
+
+describe :file_size_0_when_empty, shared: true do
+ before :each do
+ @empty = tmp("i_am_empty")
+ touch @empty
+ end
+
+ after :each do
+ rm_r @empty
+ end
+
+ it "returns 0 if the file is empty" do
+ @object.send(@method, @empty).should == 0
+ end
+end
+
+describe :file_size_nil_when_empty, shared: true do
+ before :each do
+ @empty = tmp("i_am_empt")
+ touch @empty
+ end
+
+ after :each do
+ rm_r @empty
+ end
+
+ it "returns nil if file_name is empty" do
+ @object.send(@method, @empty).should == nil
+ end
+end
+
+describe :file_size_with_file_argument, shared: true do
+ before :each do
+ @exists = tmp('i_exist')
+ touch(@exists) { |f| f.write 'rubinius' }
+ end
+
+ after :each do
+ rm_r @exists
+ end
+
+ it "accepts a File argument" do
+ File.open(@exists) do |f|
+ @object.send(@method, f).should == 8
+ end
+ end
+end
diff --git a/spec/ruby/shared/file/socket.rb b/spec/ruby/shared/file/socket.rb
new file mode 100644
index 0000000000..ef6c482d1c
--- /dev/null
+++ b/spec/ruby/shared/file/socket.rb
@@ -0,0 +1,33 @@
+describe :file_socket, shared: true do
+ it "returns false if the file is not a socket" do
+ filename = tmp("i_exist")
+ touch(filename)
+
+ @object.send(@method, filename).should == false
+
+ rm_r filename
+ end
+
+ it "returns true if the file is a socket" do
+ require 'socket'
+
+ # We need a really short name here.
+ # On Linux the path length is limited to 107, see unix(7).
+ name = tmp("s")
+ server = UNIXServer.new(name)
+
+ @object.send(@method, name).should == true
+
+ server.close
+ rm_r name
+ end
+
+ it "accepts an object that has a #to_path method" do
+ obj = Object.new
+ def obj.to_path
+ __FILE__
+ end
+
+ @object.send(@method, obj).should == false
+ end
+end
diff --git a/spec/ruby/shared/file/sticky.rb b/spec/ruby/shared/file/sticky.rb
new file mode 100644
index 0000000000..e07fa22fd7
--- /dev/null
+++ b/spec/ruby/shared/file/sticky.rb
@@ -0,0 +1,29 @@
+describe :file_sticky, shared: true do
+ before :each do
+ @dir = tmp('sticky_dir')
+ Dir.rmdir(@dir) if File.exist?(@dir)
+ end
+
+ after :each do
+ Dir.rmdir(@dir) if File.exist?(@dir)
+ end
+
+ platform_is_not :windows, :darwin, :freebsd, :netbsd, :openbsd, :aix do
+ it "returns true if the named file has the sticky bit, otherwise false" do
+ Dir.mkdir @dir, 01755
+
+ @object.send(@method, @dir).should == true
+ @object.send(@method, '/').should == false
+ end
+ end
+
+ it "accepts an object that has a #to_path method"
+end
+
+describe :file_sticky_missing, shared: true do
+ platform_is_not :windows do
+ it "returns false if the file dies not exist" do
+ @object.send(@method, 'fake_file').should == false
+ end
+ end
+end
diff --git a/spec/ruby/shared/file/symlink.rb b/spec/ruby/shared/file/symlink.rb
new file mode 100644
index 0000000000..d1c1dc94df
--- /dev/null
+++ b/spec/ruby/shared/file/symlink.rb
@@ -0,0 +1,46 @@
+describe :file_symlink, shared: true do
+ before :each do
+ @file = tmp("test.txt")
+ @link = tmp("test.lnk")
+
+ rm_r @link
+ touch @file
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ platform_is_not :windows do
+ it "returns true if the file is a link" do
+ File.symlink(@file, @link)
+ @object.send(@method, @link).should == true
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.symlink(@file, @link)
+ @object.send(@method, mock_to_path(@link)).should == true
+ end
+ end
+end
+
+describe :file_symlink_nonexistent, shared: true do
+ before :each do
+ @file = tmp("test.txt")
+ @link = tmp("test.lnk")
+
+ rm_r @link
+ touch @file
+ end
+
+ after :each do
+ rm_r @link
+ rm_r @file
+ end
+
+ platform_is_not :windows do
+ it "returns false if the file does not exist" do
+ @object.send(@method, "non_existent_link").should == false
+ end
+ end
+end
diff --git a/spec/ruby/shared/file/world_readable.rb b/spec/ruby/shared/file/world_readable.rb
new file mode 100644
index 0000000000..1dce7a580f
--- /dev/null
+++ b/spec/ruby/shared/file/world_readable.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe :file_world_readable, shared: true do
+
+ before :each do
+ @file = tmp('world-readable')
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ platform_is_not :windows do
+ it "returns nil if the file is chmod 600" do
+ File.chmod(0600, @file)
+ @object.world_readable?(@file).should be_nil
+ end
+
+ it "returns nil if the file is chmod 000" do
+ File.chmod(0000, @file)
+ @object.world_readable?(@file).should be_nil
+ end
+
+ it "returns nil if the file is chmod 700" do
+ File.chmod(0700, @file)
+ @object.world_readable?(@file).should be_nil
+ end
+ end
+
+ # We don't specify what the Integer is because it's system dependent
+ it "returns an Integer if the file is chmod 644" do
+ File.chmod(0644, @file)
+ @object.world_readable?(@file).should be_an_instance_of(Integer)
+ end
+
+ it "returns an Integer if the file is a directory and chmod 644" do
+ dir = tmp(rand().to_s + '-ww')
+ Dir.mkdir(dir)
+ Dir.should.exist?(dir)
+ File.chmod(0644, dir)
+ @object.world_readable?(dir).should be_an_instance_of(Integer)
+ Dir.rmdir(dir)
+ end
+
+ it "coerces the argument with #to_path" do
+ @object.world_readable?(mock_to_path(@file))
+ end
+end
diff --git a/spec/ruby/shared/file/world_writable.rb b/spec/ruby/shared/file/world_writable.rb
new file mode 100644
index 0000000000..7ed252dcf3
--- /dev/null
+++ b/spec/ruby/shared/file/world_writable.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe :file_world_writable, shared: true do
+
+ before :each do
+ @file = tmp('world-writable')
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ platform_is_not :windows do
+ it "returns nil if the file is chmod 600" do
+ File.chmod(0600, @file)
+ @object.world_writable?(@file).should be_nil
+ end
+
+ it "returns nil if the file is chmod 000" do
+ File.chmod(0000, @file)
+ @object.world_writable?(@file).should be_nil
+ end
+
+ it "returns nil if the file is chmod 700" do
+ File.chmod(0700, @file)
+ @object.world_writable?(@file).should be_nil
+ end
+
+ # We don't specify what the Integer is because it's system dependent
+ it "returns an Integer if the file is chmod 777" do
+ File.chmod(0777, @file)
+ @object.world_writable?(@file).should be_an_instance_of(Integer)
+ end
+
+ it "returns an Integer if the file is a directory and chmod 777" do
+ dir = tmp(rand().to_s + '-ww')
+ Dir.mkdir(dir)
+ Dir.should.exist?(dir)
+ File.chmod(0777, dir)
+ @object.world_writable?(dir).should be_an_instance_of(Integer)
+ Dir.rmdir(dir)
+ end
+ end
+
+ it "coerces the argument with #to_path" do
+ @object.world_writable?(mock_to_path(@file))
+ end
+end
diff --git a/spec/ruby/shared/file/writable.rb b/spec/ruby/shared/file/writable.rb
new file mode 100644
index 0000000000..65ea2c1781
--- /dev/null
+++ b/spec/ruby/shared/file/writable.rb
@@ -0,0 +1,44 @@
+describe :file_writable, shared: true do
+ before :each do
+ @file = tmp('i_exist')
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns true if named file is writable by the effective user id of the process, otherwise false" do
+ platform_is_not :windows, :android do
+ as_user do
+ @object.send(@method, "/etc/passwd").should == false
+ end
+ end
+ File.open(@file,'w') { @object.send(@method, @file).should == true }
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.open(@file,'w') { @object.send(@method, mock_to_path(@file)).should == true }
+ end
+
+ platform_is_not :windows do
+ as_superuser do
+ context "when run by a superuser" do
+ it "returns true unconditionally" do
+ file = tmp('temp.txt')
+ touch file
+
+ File.chmod(0555, file)
+ @object.send(@method, file).should == true
+
+ rm_r file
+ end
+ end
+ end
+ end
+end
+
+describe :file_writable_missing, shared: true do
+ it "returns false if the file does not exist" do
+ @object.send(@method, 'fake_file').should == false
+ end
+end
diff --git a/spec/ruby/shared/file/writable_real.rb b/spec/ruby/shared/file/writable_real.rb
new file mode 100644
index 0000000000..b4a0a58c6e
--- /dev/null
+++ b/spec/ruby/shared/file/writable_real.rb
@@ -0,0 +1,49 @@
+describe :file_writable_real, shared: true do
+ before :each do
+ @file = tmp('i_exist')
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns true if named file is writable by the real user id of the process, otherwise false" do
+ File.open(@file,'w') { @object.send(@method, @file).should == true }
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.open(@file,'w') { @object.send(@method, mock_to_path(@file)).should == true }
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { File.writable_real? }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { @object.send(@method, 1) }.should raise_error(TypeError)
+ -> { @object.send(@method, nil) }.should raise_error(TypeError)
+ -> { @object.send(@method, false) }.should raise_error(TypeError)
+ end
+
+ platform_is_not :windows do
+ as_real_superuser do
+ context "when run by a real superuser" do
+ it "returns true unconditionally" do
+ file = tmp('temp.txt')
+ touch file
+
+ File.chmod(0555, file)
+ @object.send(@method, file).should == true
+
+ rm_r file
+ end
+ end
+ end
+ end
+end
+
+describe :file_writable_real_missing, shared: true do
+ it "returns false if the file does not exist" do
+ @object.send(@method, 'fake_file').should == false
+ end
+end
diff --git a/spec/ruby/shared/file/zero.rb b/spec/ruby/shared/file/zero.rb
new file mode 100644
index 0000000000..6a9399a021
--- /dev/null
+++ b/spec/ruby/shared/file/zero.rb
@@ -0,0 +1,68 @@
+describe :file_zero, shared: true do
+ before :each do
+ @zero_file = tmp("test.txt")
+ @nonzero_file = tmp("test2.txt")
+ @dir = tmp("dir")
+
+ Dir.mkdir @dir
+ touch @zero_file
+ touch(@nonzero_file) { |f| f.puts "hello" }
+ end
+
+ after :each do
+ rm_r @zero_file, @nonzero_file
+ rm_r @dir
+ end
+
+ it "returns true if the file is empty" do
+ @object.send(@method, @zero_file).should == true
+ end
+
+ it "returns false if the file is not empty" do
+ @object.send(@method, @nonzero_file).should == false
+ end
+
+ it "accepts an object that has a #to_path method" do
+ @object.send(@method, mock_to_path(@zero_file)).should == true
+ end
+
+ platform_is :windows do
+ it "returns true for NUL" do
+ @object.send(@method, 'NUL').should == true
+ @object.send(@method, 'nul').should == true
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns true for /dev/null" do
+ @object.send(@method, File.realpath('/dev/null')).should == true
+ end
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { File.zero? }.should raise_error(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { @object.send(@method, nil) }.should raise_error(TypeError)
+ -> { @object.send(@method, true) }.should raise_error(TypeError)
+ -> { @object.send(@method, false) }.should raise_error(TypeError)
+ end
+
+ it "returns true inside a block opening a file if it is empty" do
+ File.open(@zero_file,'w') do
+ @object.send(@method, @zero_file).should == true
+ end
+ end
+
+ # See https://bugs.ruby-lang.org/issues/449 for background
+ it "returns true or false for a directory" do
+ @object.send(@method, @dir).should be_true_or_false
+ end
+end
+
+describe :file_zero_missing, shared: true do
+ it "returns false if the file does not exist" do
+ @object.send(@method, 'fake_file').should == false
+ end
+end
diff --git a/spec/ruby/shared/hash/key_error.rb b/spec/ruby/shared/hash/key_error.rb
new file mode 100644
index 0000000000..54dcb89e91
--- /dev/null
+++ b/spec/ruby/shared/hash/key_error.rb
@@ -0,0 +1,23 @@
+describe :key_error, shared: true do
+ it "raises a KeyError" do
+ -> {
+ @method.call(@object, 'foo')
+ }.should raise_error(KeyError)
+ end
+
+ it "sets the Hash as the receiver of KeyError" do
+ -> {
+ @method.call(@object, 'foo')
+ }.should raise_error(KeyError) { |err|
+ err.receiver.should equal(@object)
+ }
+ end
+
+ it "sets the unmatched key as the key of KeyError" do
+ -> {
+ @method.call(@object, 'foo')
+ }.should raise_error(KeyError) { |err|
+ err.key.to_s.should == 'foo'
+ }
+ end
+end
diff --git a/spec/ruby/shared/io/putc.rb b/spec/ruby/shared/io/putc.rb
new file mode 100644
index 0000000000..cdf18ac9fd
--- /dev/null
+++ b/spec/ruby/shared/io/putc.rb
@@ -0,0 +1,57 @@
+# encoding: binary
+describe :io_putc, shared: true do
+ after :each do
+ @io.close if @io && !@io.closed?
+ @io_object = nil
+ rm_r @name
+ end
+
+ describe "with an Integer argument" do
+ it "writes one character as a String" do
+ @io.should_receive(:write).with("A")
+ @io_object.send(@method, 65).should == 65
+ end
+
+ it "writes the low byte as a String" do
+ @io.should_receive(:write).with("A")
+ @io_object.send(@method, 0x2441).should == 0x2441
+ end
+ end
+
+ describe "with a String argument" do
+ it "writes one character" do
+ @io.should_receive(:write).with("B")
+ @io_object.send(@method, "B").should == "B"
+ end
+
+ it "writes the first character" do
+ @io.should_receive(:write).with("R")
+ @io_object.send(@method, "Ruby").should == "Ruby"
+ end
+ end
+
+ it "calls #to_int to convert an object to an Integer" do
+ obj = mock("kernel putc")
+ obj.should_receive(:to_int).and_return(65)
+
+ @io.should_receive(:write).with("A")
+ @io_object.send(@method, obj).should == obj
+ end
+
+ it "raises IOError on a closed stream" do
+ @io.close
+ -> { @io_object.send(@method, "a") }.should raise_error(IOError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { @io_object.send(@method, nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed false" do
+ -> { @io_object.send(@method, false) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when passed true" do
+ -> { @io_object.send(@method, true) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/shared/kernel/at_exit.rb b/spec/ruby/shared/kernel/at_exit.rb
new file mode 100644
index 0000000000..29db79bb39
--- /dev/null
+++ b/spec/ruby/shared/kernel/at_exit.rb
@@ -0,0 +1,76 @@
+describe :kernel_at_exit, shared: true do
+ it "runs after all other code" do
+ ruby_exe("#{@method} { print 5 }; print 6").should == "65"
+ end
+
+ it "runs in reverse order of registration" do
+ code = "#{@method} { print 4 }; #{@method} { print 5 }; print 6; #{@method} { print 7 }"
+ ruby_exe(code).should == "6754"
+ end
+
+ it "allows calling exit inside a handler" do
+ code = "#{@method} { print 3 }; #{@method} { print 4; exit; print 5 }; #{@method} { print 6 }"
+ ruby_exe(code).should == "643"
+ end
+
+ it "gives access to the last raised exception - global variables $! and $@" do
+ code = <<-EOC
+ #{@method} {
+ puts "The exception matches: \#{$! == $exception && $@ == $exception.backtrace} (message=\#{$!.message})"
+ }
+
+ begin
+ raise "foo"
+ rescue => $exception
+ raise
+ end
+ EOC
+
+ result = ruby_exe(code, args: "2>&1", exit_status: 1)
+ result.lines.should.include?("The exception matches: true (message=foo)\n")
+ end
+
+ it "gives access to an exception raised in a previous handler" do
+ code = "#{@method} { print '$!.message = ' + $!.message }; #{@method} { raise 'foo' }"
+ result = ruby_exe(code, args: "2>&1", exit_status: 1)
+ result.lines.should.include?("$!.message = foo")
+ end
+
+ it "both exceptions in a handler and in the main script are printed" do
+ code = "#{@method} { raise 'at_exit_error' }; raise 'main_script_error'"
+ result = ruby_exe(code, args: "2>&1", exit_status: 1)
+ result.should.include?('at_exit_error (RuntimeError)')
+ result.should.include?('main_script_error (RuntimeError)')
+ end
+
+ it "decides the exit status if both at_exit and the main script raise SystemExit" do
+ ruby_exe("#{@method} { exit 43 }; exit 42", args: "2>&1", exit_status: 43)
+ $?.exitstatus.should == 43
+ end
+
+ it "runs all handlers even if some raise exceptions" do
+ code = "#{@method} { STDERR.puts 'last' }; #{@method} { exit 43 }; #{@method} { STDERR.puts 'first' }; exit 42"
+ result = ruby_exe(code, args: "2>&1", exit_status: 43)
+ result.should == "first\nlast\n"
+ $?.exitstatus.should == 43
+ end
+
+ it "runs handlers even if the main script fails to parse" do
+ script = fixture(__FILE__, "#{@method}.rb")
+ result = ruby_exe('{', options: "-r#{script}", args: "2>&1", exit_status: 1)
+ $?.should_not.success?
+ result.should.include?("handler ran\n")
+
+ # it's tempting not to rely on error message and rely only on exception class name,
+ # but CRuby before 3.2 doesn't print class name for syntax error
+ result.should include_any_of("syntax error", "SyntaxError")
+ end
+
+ it "calls the nested handler right after the outer one if a handler is nested into another handler" do
+ ruby_exe(<<~ruby).should == "last\nbefore\nafter\nnested\nfirst\n"
+ #{@method} { puts :first }
+ #{@method} { puts :before; #{@method} { puts :nested }; puts :after };
+ #{@method} { puts :last }
+ ruby
+ end
+end
diff --git a/spec/ruby/shared/kernel/complex.rb b/spec/ruby/shared/kernel/complex.rb
new file mode 100644
index 0000000000..98ee0b2b3f
--- /dev/null
+++ b/spec/ruby/shared/kernel/complex.rb
@@ -0,0 +1,133 @@
+# Specs shared by Kernel#Complex() and String#to_c()
+describe :kernel_complex, shared: true do
+
+ it "returns a Complex object" do
+ @object.send(@method, '9').should be_an_instance_of(Complex)
+ end
+
+ it "understands integers" do
+ @object.send(@method, '20').should == Complex(20)
+ end
+
+ it "understands negative integers" do
+ @object.send(@method, '-3').should == Complex(-3)
+ end
+
+ it "understands fractions (numerator/denominator) for the real part" do
+ @object.send(@method, '2/3').should == Complex(Rational(2, 3))
+ end
+
+ it "understands fractions (numerator/denominator) for the imaginary part" do
+ @object.send(@method, '4+2/3i').should == Complex(4, Rational(2, 3))
+ end
+
+ it "understands negative fractions (-numerator/denominator) for the real part" do
+ @object.send(@method, '-2/3').should == Complex(Rational(-2, 3))
+ end
+
+ it "understands negative fractions (-numerator/denominator) for the imaginary part" do
+ @object.send(@method, '7-2/3i').should == Complex(7, Rational(-2, 3))
+ end
+
+ it "understands floats (a.b) for the real part" do
+ @object.send(@method, '2.3').should == Complex(2.3)
+ end
+
+ it "understands floats (a.b) for the imaginary part" do
+ @object.send(@method, '4+2.3i').should == Complex(4, 2.3)
+ end
+
+ it "understands negative floats (-a.b) for the real part" do
+ @object.send(@method, '-2.33').should == Complex(-2.33)
+ end
+
+ it "understands negative floats (-a.b) for the imaginary part" do
+ @object.send(@method, '7-28.771i').should == Complex(7, -28.771)
+ end
+
+ it "understands an integer followed by 'i' to mean that integer is the imaginary part" do
+ @object.send(@method, '35i').should == Complex(0,35)
+ end
+
+ it "understands a negative integer followed by 'i' to mean that negative integer is the imaginary part" do
+ @object.send(@method, '-29i').should == Complex(0,-29)
+ end
+
+ it "understands an 'i' by itself as denoting a complex number with an imaginary part of 1" do
+ @object.send(@method, 'i').should == Complex(0,1)
+ end
+
+ it "understands a '-i' by itself as denoting a complex number with an imaginary part of -1" do
+ @object.send(@method, '-i').should == Complex(0,-1)
+ end
+
+ it "understands 'a+bi' to mean a complex number with 'a' as the real part, 'b' as the imaginary" do
+ @object.send(@method, '79+4i').should == Complex(79,4)
+ end
+
+ it "understands 'a-bi' to mean a complex number with 'a' as the real part, '-b' as the imaginary" do
+ @object.send(@method, '79-4i').should == Complex(79,-4)
+ end
+
+ it "understands 'a+i' to mean a complex number with 'a' as the real part, 1i as the imaginary" do
+ @object.send(@method, '79+i').should == Complex(79, 1)
+ end
+
+ it "understands 'a-i' to mean a complex number with 'a' as the real part, -1i as the imaginary" do
+ @object.send(@method, '79-i').should == Complex(79, -1)
+ end
+
+ it "understands i, I, j, and J imaginary units" do
+ @object.send(@method, '79+4i').should == Complex(79, 4)
+ @object.send(@method, '79+4I').should == Complex(79, 4)
+ @object.send(@method, '79+4j').should == Complex(79, 4)
+ @object.send(@method, '79+4J').should == Complex(79, 4)
+ end
+
+ it "understands scientific notation for the real part" do
+ @object.send(@method, '2e3+4i').should == Complex(2e3,4)
+ end
+
+ it "understands negative scientific notation for the real part" do
+ @object.send(@method, '-2e3+4i').should == Complex(-2e3,4)
+ end
+
+ it "understands scientific notation for the imaginary part" do
+ @object.send(@method, '4+2e3i').should == Complex(4, 2e3)
+ end
+
+ it "understands negative scientific notation for the imaginary part" do
+ @object.send(@method, '4-2e3i').should == Complex(4, -2e3)
+ end
+
+ it "understands scientific notation for the real and imaginary part in the same String" do
+ @object.send(@method, '2e3+2e4i').should == Complex(2e3,2e4)
+ end
+
+ it "understands negative scientific notation for the real and imaginary part in the same String" do
+ @object.send(@method, '-2e3-2e4i').should == Complex(-2e3,-2e4)
+ end
+
+ it "understands scientific notation with e and E" do
+ @object.send(@method, '2e3+2e4i').should == Complex(2e3, 2e4)
+ @object.send(@method, '2E3+2E4i').should == Complex(2e3, 2e4)
+ end
+
+ it "understands 'm@a' to mean a complex number in polar form with 'm' as the modulus, 'a' as the argument" do
+ @object.send(@method, '79@4').should == Complex.polar(79, 4)
+ @object.send(@method, '-79@4').should == Complex.polar(-79, 4)
+ @object.send(@method, '79@-4').should == Complex.polar(79, -4)
+ end
+
+ it "ignores leading whitespaces" do
+ @object.send(@method, ' 79+4i').should == Complex(79, 4)
+ end
+
+ it "ignores trailing whitespaces" do
+ @object.send(@method, '79+4i ').should == Complex(79, 4)
+ end
+
+ it "understands _" do
+ @object.send(@method, '7_9+4_0i').should == Complex(79, 40)
+ end
+end
diff --git a/spec/ruby/shared/kernel/equal.rb b/spec/ruby/shared/kernel/equal.rb
new file mode 100644
index 0000000000..0a70aec639
--- /dev/null
+++ b/spec/ruby/shared/kernel/equal.rb
@@ -0,0 +1,54 @@
+# These examples hold for BasicObject#equal?, BasicObject#== and Kernel#eql?
+describe :object_equal, shared: true do
+ it "returns true if other is identical to self" do
+ obj = Object.new
+ obj.__send__(@method, obj).should be_true
+ end
+
+ it "returns false if other is not identical to self" do
+ a = Object.new
+ b = Object.new
+ a.__send__(@method, b).should be_false
+ end
+
+ it "returns true only if self and other are the same object" do
+ o1 = mock('o1')
+ o2 = mock('o2')
+ o1.__send__(@method, o1).should == true
+ o2.__send__(@method, o2).should == true
+ o1.__send__(@method, o2).should == false
+ end
+
+ it "returns true for the same immediate object" do
+ o1 = 1
+ o2 = :hola
+ 1.__send__(@method, o1).should == true
+ :hola.__send__(@method, o2).should == true
+ end
+
+ it "returns false for nil and any other object" do
+ o1 = mock('o1')
+ nil.__send__(@method, nil).should == true
+ o1.__send__(@method, nil).should == false
+ nil.__send__(@method, o1).should == false
+ end
+
+ it "returns false for objects of different classes" do
+ :hola.__send__(@method, 1).should == false
+ end
+
+ it "returns true only if self and other are the same boolean" do
+ true.__send__(@method, true).should == true
+ false.__send__(@method, false).should == true
+
+ true.__send__(@method, false).should == false
+ false.__send__(@method, true).should == false
+ end
+
+ it "returns true for integers of initially different ranges" do
+ big42 = (bignum_value * 42 / bignum_value)
+ 42.__send__(@method, big42).should == true
+ long42 = (1 << 35) * 42 / (1 << 35)
+ 42.__send__(@method, long42).should == true
+ end
+end
diff --git a/spec/ruby/shared/kernel/fixtures/END.rb b/spec/ruby/shared/kernel/fixtures/END.rb
new file mode 100644
index 0000000000..cc8ac17c36
--- /dev/null
+++ b/spec/ruby/shared/kernel/fixtures/END.rb
@@ -0,0 +1,3 @@
+END {
+ STDERR.puts "handler ran"
+}
diff --git a/spec/ruby/shared/kernel/fixtures/at_exit.rb b/spec/ruby/shared/kernel/fixtures/at_exit.rb
new file mode 100644
index 0000000000..e7bc8baf52
--- /dev/null
+++ b/spec/ruby/shared/kernel/fixtures/at_exit.rb
@@ -0,0 +1,3 @@
+at_exit do
+ STDERR.puts "handler ran"
+end
diff --git a/spec/ruby/shared/kernel/object_id.rb b/spec/ruby/shared/kernel/object_id.rb
new file mode 100644
index 0000000000..099df8ff94
--- /dev/null
+++ b/spec/ruby/shared/kernel/object_id.rb
@@ -0,0 +1,100 @@
+# These examples hold for both BasicObject#__id__ and Kernel#object_id.
+describe :object_id, shared: true do
+ it "returns an integer" do
+ o1 = @object.new
+ o1.__send__(@method).should be_kind_of(Integer)
+ end
+
+ it "returns the same value on all calls to id for a given object" do
+ o1 = @object.new
+ o1.__send__(@method).should == o1.__send__(@method)
+ end
+
+ it "returns different values for different objects" do
+ o1 = @object.new
+ o2 = @object.new
+ o1.__send__(@method).should_not == o2.__send__(@method)
+ end
+
+ it "returns the same value for two Fixnums with the same value" do
+ o1 = 1
+ o2 = 1
+ o1.send(@method).should == o2.send(@method)
+ end
+
+ it "returns the same value for two Symbol literals" do
+ o1 = :hello
+ o2 = :hello
+ o1.send(@method).should == o2.send(@method)
+ end
+
+ it "returns the same value for two true literals" do
+ o1 = true
+ o2 = true
+ o1.send(@method).should == o2.send(@method)
+ end
+
+ it "returns the same value for two false literals" do
+ o1 = false
+ o2 = false
+ o1.send(@method).should == o2.send(@method)
+ end
+
+ it "returns the same value for two nil literals" do
+ o1 = nil
+ o2 = nil
+ o1.send(@method).should == o2.send(@method)
+ end
+
+ it "returns a different value for two Bignum literals" do
+ o1 = 2e100.to_i
+ o2 = 2e100.to_i
+ o1.send(@method).should_not == o2.send(@method)
+ end
+
+ guard -> { "test".frozen? && "test".equal?("test") } do # --enable-frozen-string-literal in $RUBYOPT
+ it "returns the same value for two identical String literals" do
+ o1 = "hello"
+ o2 = "hello"
+ o1.send(@method).should == o2.send(@method)
+ end
+ end
+
+ guard -> { "test".frozen? && !"test".equal?("test") } do # chilled string literals
+ it "returns a different frozen value for two String literals" do
+ o1 = "hello"
+ o2 = "hello"
+ o1.send(@method).should_not == o2.send(@method)
+ o1.frozen?.should == true
+ o2.frozen?.should == true
+ end
+ end
+
+ guard -> { !"test".frozen? } do
+ it "returns a different value for two String literals" do
+ o1 = "hello"
+ o2 = "hello"
+ o1.send(@method).should_not == o2.send(@method)
+ end
+ end
+
+ it "returns a different value for an object and its dup" do
+ o1 = mock("object")
+ o2 = o1.dup
+ o1.send(@method).should_not == o2.send(@method)
+ end
+
+ it "returns a different value for two numbers near the 32 bit Fixnum limit" do
+ o1 = -1
+ o2 = 2 ** 30 - 1
+
+ o1.send(@method).should_not == o2.send(@method)
+ end
+
+ it "returns a different value for two numbers near the 64 bit Fixnum limit" do
+ o1 = -1
+ o2 = 2 ** 62 - 1
+
+ o1.send(@method).should_not == o2.send(@method)
+ end
+end
diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb
new file mode 100644
index 0000000000..2be06ea797
--- /dev/null
+++ b/spec/ruby/shared/kernel/raise.rb
@@ -0,0 +1,398 @@
+describe :kernel_raise, shared: true do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "aborts execution" do
+ -> do
+ @object.raise Exception, "abort"
+ ScratchPad.record :no_abort
+ end.should raise_error(Exception, "abort")
+
+ ScratchPad.recorded.should be_nil
+ end
+
+ it "accepts an exception that implements to_hash" do
+ custom_error = Class.new(StandardError) do
+ def to_hash
+ {}
+ end
+ end
+ error = custom_error.new
+ -> { @object.raise(error) }.should raise_error(custom_error)
+ end
+
+ it "allows the message parameter to be a hash" do
+ data_error = Class.new(StandardError) do
+ attr_reader :data
+ def initialize(data)
+ @data = data
+ end
+ end
+
+ -> { @object.raise(data_error, {data: 42}) }.should raise_error(data_error) do |ex|
+ ex.data.should == {data: 42}
+ end
+ end
+
+ # https://bugs.ruby-lang.org/issues/8257#note-36
+ it "allows extra keyword arguments for compatibility" do
+ data_error = Class.new(StandardError) do
+ attr_reader :data
+ def initialize(data)
+ @data = data
+ end
+ end
+
+ -> { @object.raise(data_error, data: 42) }.should raise_error(data_error) do |ex|
+ ex.data.should == {data: 42}
+ end
+ end
+
+ it "raises RuntimeError if no exception class is given" do
+ -> { @object.raise }.should raise_error(RuntimeError, "")
+ end
+
+ it "raises a given Exception instance" do
+ error = RuntimeError.new
+ -> { @object.raise(error) }.should raise_error(error)
+ end
+
+ it "raises a RuntimeError if string given" do
+ -> { @object.raise("a bad thing") }.should raise_error(RuntimeError, "a bad thing")
+ end
+
+ it "passes no arguments to the constructor when given only an exception class" do
+ klass = Class.new(Exception) do
+ def initialize
+ end
+ end
+ -> { @object.raise(klass) }.should raise_error(klass) { |e| e.message.should == klass.to_s }
+ end
+
+ it "raises a TypeError when passed a non-Exception object" do
+ -> { @object.raise(Object.new) }.should raise_error(TypeError, "exception class/object expected")
+ -> { @object.raise(Object.new, "message") }.should raise_error(TypeError, "exception class/object expected")
+ -> { @object.raise(Object.new, "message", []) }.should raise_error(TypeError, "exception class/object expected")
+ end
+
+ it "raises a TypeError when passed true" do
+ -> { @object.raise(true) }.should raise_error(TypeError, "exception class/object expected")
+ end
+
+ it "raises a TypeError when passed false" do
+ -> { @object.raise(false) }.should raise_error(TypeError, "exception class/object expected")
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { @object.raise(nil) }.should raise_error(TypeError, "exception class/object expected")
+ end
+
+ it "raises a TypeError when passed a message and an extra argument" do
+ -> { @object.raise("message", {cause: RuntimeError.new()}) }.should raise_error(TypeError, "exception class/object expected")
+ end
+
+ it "raises TypeError when passed a non-Exception object but it responds to #exception method that doesn't return an instance of Exception class" do
+ e = Object.new
+ def e.exception
+ Array
+ end
+
+ -> {
+ @object.raise e
+ }.should raise_error(TypeError, "exception object expected")
+ end
+
+ it "re-raises a previously rescued exception without overwriting the backtrace" do
+ exception = nil
+
+ begin
+ raise "raised"
+ rescue => exception
+ # Ignore.
+ end
+
+ backtrace = exception.backtrace
+
+ begin
+ raised_exception = @object.raise(exception)
+ rescue => raised_exception
+ # Ignore.
+ end
+
+ raised_exception.backtrace.should == backtrace
+ raised_exception.should == exception
+ end
+
+ it "allows Exception, message, and backtrace parameters" do
+ -> do
+ @object.raise(ArgumentError, "message", caller)
+ end.should raise_error(ArgumentError, "message")
+ end
+
+ ruby_version_is "3.4" do
+ locations = caller_locations(1, 2)
+ it "allows Exception, message, and backtrace_locations parameters" do
+ -> do
+ @object.raise(ArgumentError, "message", locations)
+ end.should raise_error(ArgumentError, "message") { |error|
+ error.backtrace_locations.map(&:to_s).should == locations.map(&:to_s)
+ }
+ end
+ end
+
+ ruby_version_is "4.0" do
+ it "allows cause keyword argument" do
+ cause = StandardError.new("original error")
+ result = nil
+
+ -> do
+ @object.raise("new error", cause: cause)
+ end.should raise_error(RuntimeError, "new error") do |error|
+ error.cause.should == cause
+ end
+ end
+
+ it "raises an ArgumentError when only cause is given" do
+ cause = StandardError.new("cause")
+ -> do
+ @object.raise(cause: cause)
+ end.should raise_error(ArgumentError, "only cause is given with no arguments")
+ end
+
+ it "raises an ArgumentError when only cause is given and is nil" do
+ -> do
+ @object.raise(cause: nil)
+ end.should raise_error(ArgumentError, "only cause is given with no arguments")
+ end
+
+ it "raises a TypeError when given cause is not an instance of Exception" do
+ cause = Object.new
+ -> do
+ @object.raise("message", cause: cause)
+ end.should raise_error(TypeError, "exception object expected")
+ end
+
+ it "doesn't set given cause when it equals the raised exception" do
+ cause = StandardError.new("cause")
+ result = nil
+
+ -> do
+ @object.raise(cause, cause: cause)
+ end.should raise_error(StandardError, "cause") do |error|
+ error.should == cause
+ error.cause.should == nil
+ end
+ end
+
+ it "accepts cause equal an exception" do
+ error = RuntimeError.new("message")
+ result = nil
+
+ -> do
+ @object.raise(error, cause: error)
+ end.should raise_error(RuntimeError, "message") do |e|
+ e.cause.should == nil
+ end
+ end
+
+ it "rejects circular causes" do
+ -> {
+ begin
+ raise "Error 1"
+ rescue => error1
+ begin
+ raise "Error 2"
+ rescue => error2
+ begin
+ raise "Error 3"
+ rescue => error3
+ @object.raise(error1, cause: error3)
+ end
+ end
+ end
+ }.should raise_error(ArgumentError, "circular causes")
+ end
+
+ it "supports exception class with message and cause" do
+ cause = StandardError.new("cause message")
+ result = nil
+
+ -> do
+ @object.raise(ArgumentError, "argument error message", cause: cause)
+ end.should raise_error(ArgumentError, "argument error message") do |error|
+ error.should be_kind_of(ArgumentError)
+ error.message.should == "argument error message"
+ error.cause.should == cause
+ end
+ end
+
+ it "supports exception class with message, backtrace and cause" do
+ cause = StandardError.new("cause message")
+ backtrace = ["line1", "line2"]
+ result = nil
+
+ -> do
+ @object.raise(ArgumentError, "argument error message", backtrace, cause: cause)
+ end.should raise_error(ArgumentError, "argument error message") do |error|
+ error.should be_kind_of(ArgumentError)
+ error.message.should == "argument error message"
+ error.cause.should == cause
+ error.backtrace.should == backtrace
+ end
+ end
+
+ it "supports automatic cause chaining" do
+ -> do
+ begin
+ raise "first error"
+ rescue
+ # No explicit cause - should chain automatically:
+ @object.raise("second error")
+ end
+ end.should raise_error(RuntimeError, "second error") do |error|
+ error.cause.should be_kind_of(RuntimeError)
+ error.cause.message.should == "first error"
+ end
+ end
+
+ it "supports cause: nil to prevent automatic cause chaining" do
+ -> do
+ begin
+ raise "first error"
+ rescue
+ # Explicit nil prevents chaining:
+ @object.raise("second error", cause: nil)
+ end
+ end.should raise_error(RuntimeError, "second error") do |error|
+ error.cause.should == nil
+ end
+ end
+ end
+end
+
+describe :kernel_raise_across_contexts, shared: true do
+ ruby_version_is "4.0" do
+ describe "with cause keyword argument" do
+ it "uses the cause from the calling context" do
+ original_cause = nil
+ result = nil
+
+ # We have no cause ($!) and we don't specify one explicitly either:
+ @object.raise("second error") do |&block|
+ begin
+ begin
+ raise "first error"
+ rescue => original_cause
+ # We have a cause here ($!) but we should ignore it:
+ block.call
+ end
+ rescue => result
+ # Ignore.
+ end
+ end
+
+ result.should be_kind_of(RuntimeError)
+ result.message.should == "second error"
+ result.cause.should == nil
+ end
+
+ it "accepts a cause keyword argument that overrides the last exception" do
+ original_cause = nil
+ override_cause = StandardError.new("override cause")
+ result = nil
+
+ begin
+ raise "outer error"
+ rescue
+ # We have an existing cause, but we want to override it:
+ @object.raise("second error", cause: override_cause) do |&block|
+ begin
+ begin
+ raise "first error"
+ rescue => original_cause
+ # We also have an existing cause here:
+ block.call
+ end
+ rescue => result
+ # Ignore.
+ end
+ end
+ end
+
+ result.should be_kind_of(RuntimeError)
+ result.message.should == "second error"
+ result.cause.should == override_cause
+ end
+
+ it "supports automatic cause chaining from calling context" do
+ result = nil
+
+ @object.raise("new error") do |&block|
+ begin
+ begin
+ raise "original error"
+ rescue
+ block.call # Let the context yield/sleep
+ end
+ rescue => result
+ # Ignore.
+ end
+ end
+
+ result.should be_kind_of(RuntimeError)
+ result.message.should == "new error"
+ # Calling context has no current exception:
+ result.cause.should == nil
+ end
+
+ it "supports explicit cause: nil to prevent cause chaining" do
+ result = nil
+
+ begin
+ raise "calling context error"
+ rescue
+ @object.raise("new error", cause: nil) do |&block|
+ begin
+ begin
+ raise "target context error"
+ rescue
+ block.call # Let the context yield/sleep
+ end
+ rescue => result
+ # Ignore.
+ end
+ end
+
+ result.should be_kind_of(RuntimeError)
+ result.message.should == "new error"
+ result.cause.should == nil
+ end
+ end
+
+ it "raises TypeError when cause is not an Exception" do
+ -> {
+ @object.raise("error", cause: "not an exception") do |&block|
+ begin
+ block.call # Let the context yield/sleep
+ rescue
+ # Ignore - we expect the TypeError to be raised in the calling context
+ end
+ end
+ }.should raise_error(TypeError, "exception object expected")
+ end
+
+ it "raises ArgumentError when only cause is given with no arguments" do
+ -> {
+ @object.raise(cause: StandardError.new("cause")) do |&block|
+ begin
+ block.call # Let the context yield/sleep
+ rescue
+ # Ignore - we expect the ArgumentError to be raised in the calling context
+ end
+ end
+ }.should raise_error(ArgumentError, "only cause is given with no arguments")
+ end
+ end
+ end
+end
diff --git a/spec/ruby/shared/process/abort.rb b/spec/ruby/shared/process/abort.rb
new file mode 100644
index 0000000000..935637e1c2
--- /dev/null
+++ b/spec/ruby/shared/process/abort.rb
@@ -0,0 +1,36 @@
+describe :process_abort, shared: true do
+ before :each do
+ @stderr, $stderr = $stderr, IOStub.new
+ end
+
+ after :each do
+ $stderr = @stderr
+ end
+
+ it "raises a SystemExit exception" do
+ -> { @object.abort }.should raise_error(SystemExit)
+ end
+
+ it "sets the exception message to the given message" do
+ -> { @object.abort "message" }.should raise_error { |e| e.message.should == "message" }
+ end
+
+ it "sets the exception status code of 1" do
+ -> { @object.abort }.should raise_error { |e| e.status.should == 1 }
+ end
+
+ it "prints the specified message to STDERR" do
+ -> { @object.abort "a message" }.should raise_error(SystemExit)
+ $stderr.should =~ /a message/
+ end
+
+ it "coerces the argument with #to_str" do
+ str = mock('to_str')
+ str.should_receive(:to_str).any_number_of_times.and_return("message")
+ -> { @object.abort str }.should raise_error(SystemExit, "message")
+ end
+
+ it "raises TypeError when given a non-String object" do
+ -> { @object.abort 123 }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/shared/process/exit.rb b/spec/ruby/shared/process/exit.rb
new file mode 100644
index 0000000000..1e073614a3
--- /dev/null
+++ b/spec/ruby/shared/process/exit.rb
@@ -0,0 +1,126 @@
+describe :process_exit, shared: true do
+ it "raises a SystemExit with status 0" do
+ -> { @object.exit }.should raise_error(SystemExit) { |e|
+ e.status.should == 0
+ }
+ end
+
+ it "raises a SystemExit with the specified status" do
+ [-2**16, -2**8, -8, -1, 0, 1 , 8, 2**8, 2**16].each do |value|
+ -> { @object.exit(value) }.should raise_error(SystemExit) { |e|
+ e.status.should == value
+ }
+ end
+ end
+
+ it "raises a SystemExit with the specified boolean status" do
+ { true => 0, false => 1 }.each do |value, status|
+ -> { @object.exit(value) }.should raise_error(SystemExit) { |e|
+ e.status.should == status
+ }
+ end
+ end
+
+ it "raises a SystemExit with message 'exit'" do
+ -> { @object.exit }.should raise_error(SystemExit) { |e|
+ e.message.should == "exit"
+ }
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('5')
+ obj.should_receive(:to_int).and_return(5)
+ -> { @object.exit(obj) }.should raise_error(SystemExit) { |e|
+ e.status.should == 5
+ }
+ end
+
+ it "converts the passed Float argument to an Integer" do
+ { -2.2 => -2, -0.1 => 0, 5.5 => 5, 827.999 => 827 }.each do |value, status|
+ -> { @object.exit(value) }.should raise_error(SystemExit) { |e|
+ e.status.should == status
+ }
+ end
+ end
+
+ it "raises TypeError if can't convert the argument to an Integer" do
+ -> { @object.exit(Object.new) }.should raise_error(TypeError)
+ -> { @object.exit('0') }.should raise_error(TypeError)
+ -> { @object.exit([0]) }.should raise_error(TypeError)
+ -> { @object.exit(nil) }.should raise_error(TypeError)
+ end
+
+ it "raises the SystemExit in the main thread if it reaches the top-level handler of another thread" do
+ ScratchPad.record []
+
+ ready = false
+ t = Thread.new {
+ Thread.pass until ready
+
+ begin
+ @object.exit 42
+ rescue SystemExit => e
+ ScratchPad << :in_thread
+ raise e
+ end
+ }
+
+ begin
+ ready = true
+ sleep
+ rescue SystemExit
+ ScratchPad << :in_main
+ end
+
+ ScratchPad.recorded.should == [:in_thread, :in_main]
+
+ # the thread also keeps the exception as its value
+ -> { t.value }.should raise_error(SystemExit)
+ end
+end
+
+describe :process_exit!, shared: true do
+ it "exits with the given status" do
+ out = ruby_exe("#{@object}.send(:exit!, 21)", args: '2>&1', exit_status: 21)
+ out.should == ""
+ $?.exitstatus.should == 21
+ end
+
+ it "exits when called from a thread" do
+ out = ruby_exe("Thread.new { #{@object}.send(:exit!, 21) }.join; sleep", args: '2>&1', exit_status: 21)
+ out.should == ""
+ $?.exitstatus.should == 21
+ end
+
+ it "exits when called from a fiber" do
+ out = ruby_exe("Fiber.new { #{@object}.send(:exit!, 21) }.resume", args: '2>&1', exit_status: 21)
+ out.should == ""
+ $?.exitstatus.should == 21
+ end
+
+ it "skips at_exit handlers" do
+ out = ruby_exe("at_exit { STDERR.puts 'at_exit' }; #{@object}.send(:exit!, 21)", args: '2>&1', exit_status: 21)
+ out.should == ""
+ $?.exitstatus.should == 21
+ end
+
+ it "skips ensure clauses" do
+ out = ruby_exe("begin; STDERR.puts 'before'; #{@object}.send(:exit!, 21); ensure; STDERR.puts 'ensure'; end", args: '2>&1', exit_status: 21)
+ out.should == "before\n"
+ $?.exitstatus.should == 21
+ end
+
+ it "overrides the original exception and exit status when called from #at_exit" do
+ code = <<-RUBY
+ at_exit do
+ STDERR.puts 'in at_exit'
+ STDERR.puts "$! is \#{$!.class}:\#{$!.message}"
+ #{@object}.send(:exit!, 21)
+ end
+ raise 'original error'
+ RUBY
+ out = ruby_exe(code, args: '2>&1', exit_status: 21)
+ out.should == "in at_exit\n$! is RuntimeError:original error\n"
+ $?.exitstatus.should == 21
+ end
+end
diff --git a/spec/ruby/shared/process/fork.rb b/spec/ruby/shared/process/fork.rb
new file mode 100644
index 0000000000..8dbb3d0da4
--- /dev/null
+++ b/spec/ruby/shared/process/fork.rb
@@ -0,0 +1,90 @@
+describe :process_fork, shared: true do
+ platform_is :windows do
+ it "returns false from #respond_to?" do
+ # Workaround for Kernel::Method being public and losing the "non-respond_to? magic"
+ mod = @object.class.name == "KernelSpecs::Method" ? Object.new : @object
+ mod.respond_to?(:fork).should be_false
+ mod.respond_to?(:fork, true).should be_false
+ end
+
+ it "raises a NotImplementedError when called" do
+ -> { @object.fork }.should raise_error(NotImplementedError)
+ end
+ end
+
+ platform_is_not :windows do
+ before :each do
+ @file = tmp('i_exist')
+ rm_r @file
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns status zero" do
+ pid = @object.fork { exit! 0 }
+ _, result = Process.wait2(pid)
+ result.exitstatus.should == 0
+ end
+
+ it "returns status zero" do
+ pid = @object.fork { exit 0 }
+ _, result = Process.wait2(pid)
+ result.exitstatus.should == 0
+ end
+
+ it "returns status zero" do
+ pid = @object.fork {}
+ _, result = Process.wait2(pid)
+ result.exitstatus.should == 0
+ end
+
+ it "returns status non-zero" do
+ pid = @object.fork { exit! 42 }
+ _, result = Process.wait2(pid)
+ result.exitstatus.should == 42
+ end
+
+ it "returns status non-zero" do
+ pid = @object.fork { exit 42 }
+ _, result = Process.wait2(pid)
+ result.exitstatus.should == 42
+ end
+
+ it "returns nil for the child process" do
+ child_id = @object.fork
+ if child_id == nil
+ touch(@file) { |f| f.write 'rubinius' }
+ Process.exit!
+ else
+ Process.waitpid(child_id)
+ end
+ File.should.exist?(@file)
+ end
+
+ it "runs a block in a child process" do
+ pid = @object.fork {
+ touch(@file) { |f| f.write 'rubinius' }
+ Process.exit!
+ }
+ Process.waitpid(pid)
+ File.should.exist?(@file)
+ end
+
+ it "marks threads from the parent as killed" do
+ t = Thread.new { sleep }
+ pid = @object.fork {
+ touch(@file) do |f|
+ f.write Thread.current.alive?
+ f.write t.alive?
+ end
+ Process.exit!
+ }
+ Process.waitpid(pid)
+ t.kill
+ t.join
+ File.read(@file).should == "truefalse"
+ end
+ end
+end
diff --git a/spec/ruby/shared/queue/clear.rb b/spec/ruby/shared/queue/clear.rb
new file mode 100644
index 0000000000..5db5a5b497
--- /dev/null
+++ b/spec/ruby/shared/queue/clear.rb
@@ -0,0 +1,12 @@
+describe :queue_clear, shared: true do
+ it "removes all objects from the queue" do
+ queue = @object.call
+ queue << Object.new
+ queue << 1
+ queue.empty?.should be_false
+ queue.clear
+ queue.empty?.should be_true
+ end
+
+ # TODO: test for atomicity of Queue#clear
+end
diff --git a/spec/ruby/shared/queue/close.rb b/spec/ruby/shared/queue/close.rb
new file mode 100644
index 0000000000..0e7c69acba
--- /dev/null
+++ b/spec/ruby/shared/queue/close.rb
@@ -0,0 +1,14 @@
+describe :queue_close, shared: true do
+ it "may be called multiple times" do
+ q = @object.call
+ q.close
+ q.closed?.should be_true
+ q.close # no effect
+ q.closed?.should be_true
+ end
+
+ it "returns self" do
+ q = @object.call
+ q.close.should == q
+ end
+end
diff --git a/spec/ruby/shared/queue/closed.rb b/spec/ruby/shared/queue/closed.rb
new file mode 100644
index 0000000000..b3cea0c524
--- /dev/null
+++ b/spec/ruby/shared/queue/closed.rb
@@ -0,0 +1,12 @@
+describe :queue_closed?, shared: true do
+ it "returns false initially" do
+ queue = @object.call
+ queue.closed?.should be_false
+ end
+
+ it "returns true when the queue is closed" do
+ queue = @object.call
+ queue.close
+ queue.closed?.should be_true
+ end
+end
diff --git a/spec/ruby/shared/queue/deque.rb b/spec/ruby/shared/queue/deque.rb
new file mode 100644
index 0000000000..a154da6274
--- /dev/null
+++ b/spec/ruby/shared/queue/deque.rb
@@ -0,0 +1,164 @@
+describe :queue_deq, shared: true do
+ it "removes an item from the queue" do
+ q = @object.call
+ q << Object.new
+ q.size.should == 1
+ q.send @method
+ q.size.should == 0
+ end
+
+ it "returns items in the order they were added" do
+ q = @object.call
+ q << 1
+ q << 2
+ q.send(@method).should == 1
+ q.send(@method).should == 2
+ end
+
+ it "blocks the thread until there are items in the queue" do
+ q = @object.call
+ v = 0
+
+ th = Thread.new do
+ q.send(@method)
+ v = 1
+ end
+
+ v.should == 0
+ q << Object.new
+ th.join
+ v.should == 1
+ end
+
+ it "removes an item from a closed queue" do
+ q = @object.call
+ q << 1
+ q.close
+ q.send(@method).should == 1
+ end
+
+ it "converts false-ish for non_blocking to boolean" do
+ q = @object.call
+ q << 1
+ q << 2
+
+ q.send(@method, false).should == 1
+ q.send(@method, nil).should == 2
+ end
+
+ it "returns nil for a closed empty queue" do
+ q = @object.call
+ q.close
+ q.send(@method).should == nil
+ end
+
+ it "returns nil for an empty queue that becomes closed" do
+ q = @object.call
+
+ t = Thread.new {
+ q.send(@method).should == nil
+ }
+
+ Thread.pass until t.status == "sleep" && q.num_waiting == 1
+ q.close
+ t.join
+ end
+
+ describe "with a timeout" do
+ it "returns an item if one is available in time" do
+ q = @object.call
+
+ t = Thread.new {
+ q.send(@method, timeout: TIME_TOLERANCE).should == 1
+ }
+ Thread.pass until t.status == "sleep" && q.num_waiting == 1
+ q << 1
+ t.join
+ end
+
+ it "returns nil if no item is available in time" do
+ q = @object.call
+
+ Thread.new {
+ q.send(@method, timeout: 0.001).should == nil
+ }.join
+ end
+
+ it "does nothing if the timeout is nil" do
+ q = @object.call
+ t = Thread.new {
+ q.send(@method, timeout: nil).should == 1
+ }
+ Thread.pass until t.status == "sleep" && q.num_waiting == 1
+ q << 1
+ t.join
+ end
+
+ it "immediately returns nil if no item is available and the timeout is 0" do
+ q = @object.call
+ q << 1
+ q.send(@method, timeout: 0).should == 1
+ q.send(@method, timeout: 0).should == nil
+ end
+
+ it "raise TypeError if timeout is not a valid numeric" do
+ q = @object.call
+ -> {
+ q.send(@method, timeout: "1")
+ }.should raise_error(TypeError, "no implicit conversion to float from string")
+
+ -> {
+ q.send(@method, timeout: false)
+ }.should raise_error(TypeError, "no implicit conversion to float from false")
+ end
+
+ it "raise ArgumentError if non_block = true is passed too" do
+ q = @object.call
+ -> {
+ q.send(@method, true, timeout: 1)
+ }.should raise_error(ArgumentError, "can't set a timeout if non_block is enabled")
+ end
+
+ it "returns nil for a closed empty queue" do
+ q = @object.call
+ q.close
+ q.send(@method, timeout: 0).should == nil
+ end
+ end
+
+ describe "in non-blocking mode" do
+ it "removes an item from the queue" do
+ q = @object.call
+ q << Object.new
+ q.size.should == 1
+ q.send(@method, true)
+ q.size.should == 0
+ end
+
+ it "raises a ThreadError if the queue is empty" do
+ q = @object.call
+ -> { q.send(@method, true) }.should raise_error(ThreadError)
+ end
+
+ it "removes an item from a closed queue" do
+ q = @object.call
+ q << 1
+ q.close
+ q.send(@method, true).should == 1
+ end
+
+ it "raises a ThreadError for a closed empty queue" do
+ q = @object.call
+ q.close
+ -> { q.send(@method, true) }.should raise_error(ThreadError)
+ end
+
+ it "converts true-ish non_blocking argument to true" do
+ q = @object.call
+
+ -> { q.send(@method, true) }.should raise_error(ThreadError)
+ -> { q.send(@method, 1) }.should raise_error(ThreadError)
+ -> { q.send(@method, "") }.should raise_error(ThreadError)
+ end
+ end
+end
diff --git a/spec/ruby/shared/queue/empty.rb b/spec/ruby/shared/queue/empty.rb
new file mode 100644
index 0000000000..4acd831d48
--- /dev/null
+++ b/spec/ruby/shared/queue/empty.rb
@@ -0,0 +1,12 @@
+describe :queue_empty?, shared: true do
+ it "returns true on an empty Queue" do
+ queue = @object.call
+ queue.empty?.should be_true
+ end
+
+ it "returns false when Queue is not empty" do
+ queue = @object.call
+ queue << Object.new
+ queue.empty?.should be_false
+ end
+end
diff --git a/spec/ruby/shared/queue/enque.rb b/spec/ruby/shared/queue/enque.rb
new file mode 100644
index 0000000000..8aba02e544
--- /dev/null
+++ b/spec/ruby/shared/queue/enque.rb
@@ -0,0 +1,18 @@
+describe :queue_enq, shared: true do
+ it "adds an element to the Queue" do
+ q = @object.call
+ q.size.should == 0
+ q.send @method, Object.new
+ q.size.should == 1
+ q.send @method, Object.new
+ q.size.should == 2
+ end
+
+ it "is an error for a closed queue" do
+ q = @object.call
+ q.close
+ -> {
+ q.send @method, Object.new
+ }.should raise_error(ClosedQueueError)
+ end
+end
diff --git a/spec/ruby/shared/queue/freeze.rb b/spec/ruby/shared/queue/freeze.rb
new file mode 100644
index 0000000000..4c506a4235
--- /dev/null
+++ b/spec/ruby/shared/queue/freeze.rb
@@ -0,0 +1,18 @@
+describe :queue_freeze, shared: true do
+ ruby_version_is ""..."3.3" do
+ it "can be frozen" do
+ queue = @object.call
+ queue.freeze
+ queue.should.frozen?
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "raises an exception when freezing" do
+ queue = @object.call
+ -> {
+ queue.freeze
+ }.should raise_error(TypeError, "cannot freeze #{queue}")
+ end
+ end
+end
diff --git a/spec/ruby/shared/queue/length.rb b/spec/ruby/shared/queue/length.rb
new file mode 100644
index 0000000000..a0143a4e19
--- /dev/null
+++ b/spec/ruby/shared/queue/length.rb
@@ -0,0 +1,9 @@
+describe :queue_length, shared: true do
+ it "returns the number of elements" do
+ q = @object.call
+ q.send(@method).should == 0
+ q << Object.new
+ q << Object.new
+ q.send(@method).should == 2
+ end
+end
diff --git a/spec/ruby/shared/queue/num_waiting.rb b/spec/ruby/shared/queue/num_waiting.rb
new file mode 100644
index 0000000000..b054951e45
--- /dev/null
+++ b/spec/ruby/shared/queue/num_waiting.rb
@@ -0,0 +1,16 @@
+describe :queue_num_waiting, shared: true do
+ it "reports the number of threads waiting on the queue" do
+ q = @object.call
+ threads = []
+
+ 5.times do |i|
+ q.num_waiting.should == i
+ t = Thread.new { q.deq }
+ Thread.pass until q.num_waiting == i+1
+ threads << t
+ end
+
+ threads.each { q.enq Object.new }
+ threads.each {|t| t.join }
+ end
+end
diff --git a/spec/ruby/shared/sizedqueue/enque.rb b/spec/ruby/shared/sizedqueue/enque.rb
new file mode 100644
index 0000000000..6804af3fb3
--- /dev/null
+++ b/spec/ruby/shared/sizedqueue/enque.rb
@@ -0,0 +1,129 @@
+describe :sizedqueue_enq, shared: true do
+ it "blocks if queued elements exceed size" do
+ q = @object.call(1)
+
+ q.size.should == 0
+ q.send(@method, :first_element)
+ q.size.should == 1
+
+ blocked_thread = Thread.new { q.send(@method, :second_element) }
+ sleep 0.01 until blocked_thread.stop?
+
+ q.size.should == 1
+ q.pop.should == :first_element
+
+ blocked_thread.join
+ q.size.should == 1
+ q.pop.should == :second_element
+ q.size.should == 0
+ end
+
+ it "raises a ThreadError if queued elements exceed size when not blocking" do
+ q = @object.call(2)
+
+ non_blocking = true
+ add_to_queue = -> { q.send(@method, Object.new, non_blocking) }
+
+ q.size.should == 0
+ add_to_queue.call
+ q.size.should == 1
+ add_to_queue.call
+ q.size.should == 2
+ add_to_queue.should raise_error(ThreadError)
+ end
+
+ it "interrupts enqueuing threads with ClosedQueueError when the queue is closed" do
+ q = @object.call(1)
+ q << 1
+
+ t = Thread.new {
+ -> { q.send(@method, 2) }.should raise_error(ClosedQueueError, "queue closed")
+ }
+
+ Thread.pass until q.num_waiting == 1
+
+ q.close
+
+ t.join
+ q.pop.should == 1
+ end
+
+ describe "with a timeout" do
+ it "returns self if the item was pushed in time" do
+ q = @object.call(1)
+ q << 1
+
+ t = Thread.new {
+ q.send(@method, 2, timeout: TIME_TOLERANCE).should == q
+ }
+ Thread.pass until t.status == "sleep" && q.num_waiting == 1
+ q.pop
+ t.join
+ end
+
+ it "does nothing if the timeout is nil" do
+ q = @object.call(1)
+ q << 1
+ t = Thread.new {
+ q.send(@method, 2, timeout: nil).should == q
+ }
+ t.join(0.2).should == nil
+ q.pop
+ t.join
+ end
+
+ it "returns nil if no space is available and timeout is 0" do
+ q = @object.call(1)
+ q.send(@method, 1, timeout: 0).should == q
+ q.send(@method, 2, timeout: 0).should == nil
+ end
+
+ it "returns nil if no space is available in time" do
+ q = @object.call(1)
+ q << 1
+ Thread.new {
+ q.send(@method, 2, timeout: 0.001).should == nil
+ }.join
+ end
+
+ it "raise TypeError if timeout is not a valid numeric" do
+ q = @object.call(1)
+ -> {
+ q.send(@method, 2, timeout: "1")
+ }.should raise_error(TypeError, "no implicit conversion to float from string")
+
+ -> {
+ q.send(@method, 2, timeout: false)
+ }.should raise_error(TypeError, "no implicit conversion to float from false")
+ end
+
+ it "raise ArgumentError if non_block = true is passed too" do
+ q = @object.call(1)
+ -> {
+ q.send(@method, 2, true, timeout: 1)
+ }.should raise_error(ArgumentError, "can't set a timeout if non_block is enabled")
+ end
+
+ it "raise ClosedQueueError when closed before enqueued" do
+ q = @object.call(1)
+ q.close
+ -> { q.send(@method, 2, timeout: 1) }.should raise_error(ClosedQueueError, "queue closed")
+ end
+
+ it "interrupts enqueuing threads with ClosedQueueError when the queue is closed" do
+ q = @object.call(1)
+ q << 1
+
+ t = Thread.new {
+ -> { q.send(@method, 1, timeout: TIME_TOLERANCE) }.should raise_error(ClosedQueueError, "queue closed")
+ }
+
+ Thread.pass until q.num_waiting == 1
+
+ q.close
+
+ t.join
+ q.pop.should == 1
+ end
+ end
+end
diff --git a/spec/ruby/shared/sizedqueue/max.rb b/spec/ruby/shared/sizedqueue/max.rb
new file mode 100644
index 0000000000..ea10d24be0
--- /dev/null
+++ b/spec/ruby/shared/sizedqueue/max.rb
@@ -0,0 +1,47 @@
+describe :sizedqueue_max, shared: true do
+ it "returns the size of the queue" do
+ q = @object.call(5)
+ q.max.should == 5
+ end
+end
+
+describe :sizedqueue_max=, shared: true do
+ it "sets the size of the queue" do
+ q = @object.call(5)
+ q.max.should == 5
+ q.max = 10
+ q.max.should == 10
+ end
+
+ it "does not remove items already in the queue beyond the maximum" do
+ q = @object.call(5)
+ q.enq 1
+ q.enq 2
+ q.enq 3
+ q.max = 2
+ (q.size > q.max).should be_true
+ q.deq.should == 1
+ q.deq.should == 2
+ q.deq.should == 3
+ end
+
+ it "raises a TypeError when given a non-numeric value" do
+ q = @object.call(5)
+ -> { q.max = "foo" }.should raise_error(TypeError)
+ -> { q.max = Object.new }.should raise_error(TypeError)
+ end
+
+ it "raises an argument error when set to zero" do
+ q = @object.call(5)
+ q.max.should == 5
+ -> { q.max = 0 }.should raise_error(ArgumentError)
+ q.max.should == 5
+ end
+
+ it "raises an argument error when set to a negative number" do
+ q = @object.call(5)
+ q.max.should == 5
+ -> { q.max = -1 }.should raise_error(ArgumentError)
+ q.max.should == 5
+ end
+end
diff --git a/spec/ruby/shared/sizedqueue/new.rb b/spec/ruby/shared/sizedqueue/new.rb
new file mode 100644
index 0000000000..2573194efb
--- /dev/null
+++ b/spec/ruby/shared/sizedqueue/new.rb
@@ -0,0 +1,23 @@
+describe :sizedqueue_new, shared: true do
+ it "raises a TypeError when the given argument doesn't respond to #to_int" do
+ -> { @object.call("12") }.should raise_error(TypeError)
+ -> { @object.call(Object.new) }.should raise_error(TypeError)
+
+ @object.call(12.9).max.should == 12
+ object = Object.new
+ object.define_singleton_method(:to_int) { 42 }
+ @object.call(object).max.should == 42
+ end
+
+ it "raises an argument error when no argument is given" do
+ -> { @object.call }.should raise_error(ArgumentError)
+ end
+
+ it "raises an argument error when the given argument is zero" do
+ -> { @object.call(0) }.should raise_error(ArgumentError)
+ end
+
+ it "raises an argument error when the given argument is negative" do
+ -> { @object.call(-1) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/shared/sizedqueue/num_waiting.rb b/spec/ruby/shared/sizedqueue/num_waiting.rb
new file mode 100644
index 0000000000..8c31e48ca5
--- /dev/null
+++ b/spec/ruby/shared/sizedqueue/num_waiting.rb
@@ -0,0 +1,12 @@
+describe :sizedqueue_num_waiting, shared: true do
+ it "reports the number of threads waiting to push" do
+ q = @object.call(1)
+ q.push(1)
+ t = Thread.new { q.push(2) }
+ sleep 0.05 until t.stop?
+ q.num_waiting.should == 1
+
+ q.pop
+ t.join
+ end
+end
diff --git a/spec/ruby/shared/string/end_with.rb b/spec/ruby/shared/string/end_with.rb
new file mode 100644
index 0000000000..08f43c1bce
--- /dev/null
+++ b/spec/ruby/shared/string/end_with.rb
@@ -0,0 +1,61 @@
+describe :end_with, shared: true do
+ # the @method should either be :to_s or :to_sym
+
+ it "returns true only if ends match" do
+ s = "hello".send(@method)
+ s.should.end_with?('o')
+ s.should.end_with?('llo')
+ end
+
+ it 'returns false if the end does not match' do
+ s = 'hello'.send(@method)
+ s.should_not.end_with?('ll')
+ end
+
+ it "returns true if the search string is empty" do
+ "hello".send(@method).should.end_with?("")
+ "".send(@method).should.end_with?("")
+ end
+
+ it "returns true only if any ending match" do
+ "hello".send(@method).should.end_with?('x', 'y', 'llo', 'z')
+ end
+
+ it "converts its argument using :to_str" do
+ s = "hello".send(@method)
+ find = mock('o')
+ find.should_receive(:to_str).and_return("o")
+ s.should.end_with?(find)
+ end
+
+ it "ignores arguments not convertible to string" do
+ "hello".send(@method).should_not.end_with?()
+ -> { "hello".send(@method).end_with?(1) }.should raise_error(TypeError)
+ -> { "hello".send(@method).end_with?(["o"]) }.should raise_error(TypeError)
+ -> { "hello".send(@method).end_with?(1, nil, "o") }.should raise_error(TypeError)
+ end
+
+ it "uses only the needed arguments" do
+ find = mock('h')
+ find.should_not_receive(:to_str)
+ "hello".send(@method).should.end_with?("o", find)
+ end
+
+ it "works for multibyte strings" do
+ "céréale".send(@method).should.end_with?("réale")
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are incompatible" do
+ pat = "ア".encode Encoding::EUC_JP
+ -> do
+ "あれ".send(@method).end_with?(pat)
+ end.should raise_error(Encoding::CompatibilityError)
+ end
+
+ it "checks that we are starting to match at the head of a character" do
+ "\xC3\xA9".send(@method).should_not.end_with?("\xA9")
+ "\xe3\x81\x82".send(@method).should_not.end_with?("\x82")
+ "\xd8\x00\xdc\x00".dup.force_encoding("UTF-16BE").send(@method).should_not.end_with?(
+ "\xdc\x00".dup.force_encoding("UTF-16BE"))
+ end
+end
diff --git a/spec/ruby/shared/string/start_with.rb b/spec/ruby/shared/string/start_with.rb
new file mode 100644
index 0000000000..4b947a3bbf
--- /dev/null
+++ b/spec/ruby/shared/string/start_with.rb
@@ -0,0 +1,84 @@
+describe :start_with, shared: true do
+ # the @method should either be :to_s or :to_sym
+
+ it "returns true only if beginning match" do
+ s = "hello".send(@method)
+ s.should.start_with?('h')
+ s.should.start_with?('hel')
+ s.should_not.start_with?('el')
+ end
+
+ it "returns true only if any beginning match" do
+ "hello".send(@method).should.start_with?('x', 'y', 'he', 'z')
+ end
+
+ it "returns true if the search string is empty" do
+ "hello".send(@method).should.start_with?("")
+ "".send(@method).should.start_with?("")
+ end
+
+ it "converts its argument using :to_str" do
+ s = "hello".send(@method)
+ find = mock('h')
+ find.should_receive(:to_str).and_return("h")
+ s.should.start_with?(find)
+ end
+
+ it "ignores arguments not convertible to string" do
+ "hello".send(@method).should_not.start_with?()
+ -> { "hello".send(@method).start_with?(1) }.should raise_error(TypeError)
+ -> { "hello".send(@method).start_with?(["h"]) }.should raise_error(TypeError)
+ -> { "hello".send(@method).start_with?(1, nil, "h") }.should raise_error(TypeError)
+ end
+
+ it "uses only the needed arguments" do
+ find = mock('h')
+ find.should_not_receive(:to_str)
+ "hello".send(@method).should.start_with?("h",find)
+ end
+
+ it "works for multibyte strings" do
+ "céréale".send(@method).should.start_with?("cér")
+ end
+
+ it "supports regexps" do
+ regexp = /[h1]/
+ "hello".send(@method).should.start_with?(regexp)
+ "1337".send(@method).should.start_with?(regexp)
+ "foxes are 1337".send(@method).should_not.start_with?(regexp)
+ "chunky\n12bacon".send(@method).should_not.start_with?(/12/)
+ end
+
+ it "supports regexps with ^ and $ modifiers" do
+ regexp1 = /^\d{2}/
+ regexp2 = /\d{2}$/
+ "12test".send(@method).should.start_with?(regexp1)
+ "test12".send(@method).should_not.start_with?(regexp1)
+ "12test".send(@method).should_not.start_with?(regexp2)
+ "test12".send(@method).should_not.start_with?(regexp2)
+ end
+
+ it "sets Regexp.last_match if it returns true" do
+ regexp = /test-(\d+)/
+ "test-1337".send(@method).start_with?(regexp).should be_true
+ Regexp.last_match.should_not be_nil
+ Regexp.last_match[1].should == "1337"
+ $1.should == "1337"
+
+ "test-asdf".send(@method).start_with?(regexp).should be_false
+ Regexp.last_match.should be_nil
+ $1.should be_nil
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "does not check that we are not matching part of a character" do
+ "\xC3\xA9".send(@method).should.start_with?("\xC3")
+ end
+ end
+
+ ruby_version_is "3.3" do # #19784
+ it "checks that we are not matching part of a character" do
+ "\xC3\xA9".send(@method).should_not.start_with?("\xC3")
+ end
+ end
+end
diff --git a/spec/ruby/shared/string/times.rb b/spec/ruby/shared/string/times.rb
new file mode 100644
index 0000000000..4814f894cf
--- /dev/null
+++ b/spec/ruby/shared/string/times.rb
@@ -0,0 +1,58 @@
+describe :string_times, shared: true do
+ class MyString < String; end
+
+ it "returns a new string containing count copies of self" do
+ @object.call("cool", 0).should == ""
+ @object.call("cool", 1).should == "cool"
+ @object.call("cool", 3).should == "coolcoolcool"
+ end
+
+ it "tries to convert the given argument to an integer using to_int" do
+ @object.call("cool", 3.1).should == "coolcoolcool"
+ @object.call("a", 3.999).should == "aaa"
+
+ a = mock('4')
+ a.should_receive(:to_int).and_return(4)
+
+ @object.call("a", a).should == "aaaa"
+ end
+
+ it "raises an ArgumentError when given integer is negative" do
+ -> { @object.call("cool", -3) }.should raise_error(ArgumentError)
+ -> { @object.call("cool", -3.14) }.should raise_error(ArgumentError)
+ -> { @object.call("cool", min_long) }.should raise_error(ArgumentError)
+ end
+
+ it "raises a RangeError when given integer is a Bignum" do
+ -> { @object.call("cool", 999999999999999999999) }.should raise_error(RangeError)
+ -> { @object.call("", 999999999999999999999) }.should raise_error(RangeError)
+ end
+
+ it "works with huge long values when string is empty" do
+ @object.call("", max_long).should == ""
+ end
+
+ it "returns String instances" do
+ @object.call(MyString.new("cool"), 0).should be_an_instance_of(String)
+ @object.call(MyString.new("cool"), 1).should be_an_instance_of(String)
+ @object.call(MyString.new("cool"), 2).should be_an_instance_of(String)
+ end
+
+ it "returns a String in the same encoding as self" do
+ str = "\xE3\x81\x82".dup.force_encoding Encoding::UTF_8
+ result = @object.call(str, 2)
+ result.encoding.should equal(Encoding::UTF_8)
+ end
+
+ platform_is c_long_size: 32 do
+ it "raises an ArgumentError if the length of the resulting string doesn't fit into a long" do
+ -> { @object.call("abc", (2 ** 31) - 1) }.should raise_error(ArgumentError)
+ end
+ end
+
+ platform_is c_long_size: 64 do
+ it "raises an ArgumentError if the length of the resulting string doesn't fit into a long" do
+ -> { @object.call("abc", (2 ** 63) - 1) }.should raise_error(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/shared/time/strftime_for_date.rb b/spec/ruby/shared/time/strftime_for_date.rb
new file mode 100644
index 0000000000..dbb124adc8
--- /dev/null
+++ b/spec/ruby/shared/time/strftime_for_date.rb
@@ -0,0 +1,273 @@
+# Shared for Time, Date and DateTime, testing only date components (smallest unit is day)
+
+describe :strftime_date, shared: true do
+ before :all do
+ @d200_4_6 = @new_date[200, 4, 6]
+ @d2000_4_6 = @new_date[2000, 4, 6]
+ @d2000_4_9 = @new_date[2000, 4, 9]
+ @d2000_4_10 = @new_date[2000, 4, 10]
+ @d2009_9_18 = @new_date[2009, 9, 18]
+ end
+
+ # Per conversion specifier, not combining
+ it "should be able to print the full day name" do
+ @d2000_4_6.strftime("%A").should == "Thursday"
+ @d2009_9_18.strftime('%A').should == 'Friday'
+ end
+
+ it "should be able to print the short day name" do
+ @d2000_4_6.strftime("%a").should == "Thu"
+ @d2009_9_18.strftime('%a').should == 'Fri'
+ end
+
+ it "should be able to print the full month name" do
+ @d2000_4_6.strftime("%B").should == "April"
+ @d2009_9_18.strftime('%B').should == 'September'
+ end
+
+ it "should be able to print the short month name" do
+ @d2000_4_6.strftime("%b").should == "Apr"
+ @d2000_4_6.strftime("%h").should == "Apr"
+ @d2000_4_6.strftime("%b").should == @d2000_4_6.strftime("%h")
+ @d2009_9_18.strftime('%b').should == 'Sep'
+ end
+
+ it "should be able to print the century" do
+ @d2000_4_6.strftime("%C").should == "20"
+ end
+
+ it "should be able to print the month day with leading zeroes" do
+ @d2000_4_6.strftime("%d").should == "06"
+ @d2009_9_18.strftime('%d').should == '18'
+ end
+
+ it "should be able to print the month day with leading spaces" do
+ @d2000_4_6.strftime("%e").should == " 6"
+ end
+
+ it "should be able to print the commercial year with leading zeroes" do
+ @d2000_4_6.strftime("%G").should == "2000"
+ @d200_4_6.strftime("%G").should == "0200"
+ end
+
+ it "should be able to print the commercial year with only two digits" do
+ @d2000_4_6.strftime("%g").should == "00"
+ @d200_4_6.strftime("%g").should == "00"
+ end
+
+ it "should be able to print the hour with leading zeroes (hour is always 00)" do
+ @d2000_4_6.strftime("%H").should == "00"
+ end
+
+ it "should be able to print the hour in 12 hour notation with leading zeroes" do
+ @d2000_4_6.strftime("%I").should == "12"
+ end
+
+ it "should be able to print the julian day with leading zeroes" do
+ @d2000_4_6.strftime("%j").should == "097"
+ @d2009_9_18.strftime('%j').should == '261'
+ end
+
+ it "should be able to print the hour in 24 hour notation with leading spaces" do
+ @d2000_4_6.strftime("%k").should == " 0"
+ end
+
+ it "should be able to print the hour in 12 hour notation with leading spaces" do
+ @d2000_4_6.strftime("%l").should == "12"
+ end
+
+ it "should be able to print the minutes with leading zeroes" do
+ @d2000_4_6.strftime("%M").should == "00"
+ end
+
+ it "should be able to print the month with leading zeroes" do
+ @d2000_4_6.strftime("%m").should == "04"
+ @d2009_9_18.strftime('%m').should == '09'
+ end
+
+ it "should be able to add a newline" do
+ @d2000_4_6.strftime("%n").should == "\n"
+ end
+
+ it "should be able to show AM/PM" do
+ @d2000_4_6.strftime("%P").should == "am"
+ end
+
+ it "should be able to show am/pm" do
+ @d2000_4_6.strftime("%p").should == "AM"
+ end
+
+ it "should be able to show the number of seconds with leading zeroes" do
+ @d2000_4_6.strftime("%S").should == "00"
+ end
+
+ it "should be able to show the number of seconds since the unix epoch for a date" do
+ @d2000_4_6.strftime("%s").should == "954979200"
+ end
+
+ it "should be able to add a tab" do
+ @d2000_4_6.strftime("%t").should == "\t"
+ end
+
+ it "should be able to show the week number with the week starting on Sunday (%U) and Monday (%W)" do
+ @d2000_4_6.strftime("%U").should == "14" # Thursday
+ @d2000_4_6.strftime("%W").should == "14"
+
+ @d2000_4_9.strftime("%U").should == "15" # Sunday
+ @d2000_4_9.strftime("%W").should == "14"
+
+ @d2000_4_10.strftime("%U").should == "15" # Monday
+ @d2000_4_10.strftime("%W").should == "15"
+
+ # start of the year
+ saturday_first = @new_date[2000,1,1]
+ saturday_first.strftime("%U").should == "00"
+ saturday_first.strftime("%W").should == "00"
+
+ sunday_second = @new_date[2000,1,2]
+ sunday_second.strftime("%U").should == "01"
+ sunday_second.strftime("%W").should == "00"
+
+ monday_third = @new_date[2000,1,3]
+ monday_third.strftime("%U").should == "01"
+ monday_third.strftime("%W").should == "01"
+
+ sunday_9th = @new_date[2000,1,9]
+ sunday_9th.strftime("%U").should == "02"
+ sunday_9th.strftime("%W").should == "01"
+
+ monday_10th = @new_date[2000,1,10]
+ monday_10th.strftime("%U").should == "02"
+ monday_10th.strftime("%W").should == "02"
+
+ # middle of the year
+ some_sunday = @new_date[2000,8,6]
+ some_sunday.strftime("%U").should == "32"
+ some_sunday.strftime("%W").should == "31"
+ some_monday = @new_date[2000,8,7]
+ some_monday.strftime("%U").should == "32"
+ some_monday.strftime("%W").should == "32"
+
+ # end of year, and start of next one
+ saturday_30th = @new_date[2000,12,30]
+ saturday_30th.strftime("%U").should == "52"
+ saturday_30th.strftime("%W").should == "52"
+
+ sunday_last = @new_date[2000,12,31]
+ sunday_last.strftime("%U").should == "53"
+ sunday_last.strftime("%W").should == "52"
+
+ monday_first = @new_date[2001,1,1]
+ monday_first.strftime("%U").should == "00"
+ monday_first.strftime("%W").should == "01"
+ end
+
+ it "should be able to show the commercial week day" do
+ @d2000_4_9.strftime("%u").should == "7"
+ @d2000_4_10.strftime("%u").should == "1"
+ end
+
+ it "should be able to show the commercial week with %V" do
+ @d2000_4_9.strftime("%V").should == "14"
+ @d2000_4_10.strftime("%V").should == "15"
+ end
+
+ # %W: see %U
+
+ it "should be able to show the week day" do
+ @d2000_4_9.strftime("%w").should == "0"
+ @d2000_4_10.strftime("%w").should == "1"
+ @d2009_9_18.strftime('%w').should == '5'
+ end
+
+ it "should be able to show the year in YYYY format" do
+ @d2000_4_9.strftime("%Y").should == "2000"
+ @d2009_9_18.strftime('%Y').should == '2009'
+ end
+
+ it "should be able to show the year in YY format" do
+ @d2000_4_9.strftime("%y").should == "00"
+ @d2009_9_18.strftime('%y').should == '09'
+ end
+
+ it "should be able to show the timezone of the date with a : separator" do
+ @d2000_4_9.strftime("%z").should == "+0000"
+ end
+
+ it "should be able to escape the % character" do
+ @d2000_4_9.strftime("%%").should == "%"
+ end
+
+ # Combining conversion specifiers
+ it "should be able to print the date in full" do
+ @d2000_4_6.strftime("%c").should == "Thu Apr 6 00:00:00 2000"
+ @d2000_4_6.strftime("%c").should == @d2000_4_6.strftime('%a %b %e %H:%M:%S %Y')
+ end
+
+ it "should be able to print the date with slashes" do
+ @d2000_4_6.strftime("%D").should == "04/06/00"
+ @d2000_4_6.strftime("%D").should == @d2000_4_6.strftime('%m/%d/%y')
+ end
+
+ it "should be able to print the date as YYYY-MM-DD" do
+ @d2000_4_6.strftime("%F").should == "2000-04-06"
+ @d2000_4_6.strftime("%F").should == @d2000_4_6.strftime('%Y-%m-%d')
+ end
+
+ it "should be able to show HH:MM for a date" do
+ @d2000_4_6.strftime("%R").should == "00:00"
+ @d2000_4_6.strftime("%R").should == @d2000_4_6.strftime('%H:%M')
+ end
+
+ it "should be able to show HH:MM:SS AM/PM for a date" do
+ @d2000_4_6.strftime("%r").should == "12:00:00 AM"
+ @d2000_4_6.strftime("%r").should == @d2000_4_6.strftime('%I:%M:%S %p')
+ end
+
+ it "should be able to show HH:MM:SS" do
+ @d2000_4_6.strftime("%T").should == "00:00:00"
+ @d2000_4_6.strftime("%T").should == @d2000_4_6.strftime('%H:%M:%S')
+ end
+
+ it "should be able to show HH:MM:SS" do
+ @d2000_4_6.strftime("%X").should == "00:00:00"
+ @d2000_4_6.strftime("%X").should == @d2000_4_6.strftime('%H:%M:%S')
+ end
+
+ it "should be able to show MM/DD/YY" do
+ @d2000_4_6.strftime("%x").should == "04/06/00"
+ @d2000_4_6.strftime("%x").should == @d2000_4_6.strftime('%m/%d/%y')
+ end
+
+ # GNU modificators
+ it "supports GNU modificators" do
+ time = @new_date[2001, 2, 3]
+
+ time.strftime('%^h').should == 'FEB'
+ time.strftime('%^_5h').should == ' FEB'
+ time.strftime('%0^5h').should == '00FEB'
+ time.strftime('%04m').should == '0002'
+ time.strftime('%0-^5h').should == 'FEB'
+ time.strftime('%_-^5h').should == 'FEB'
+ time.strftime('%^ha').should == 'FEBa'
+
+ time.strftime("%10h").should == ' Feb'
+ time.strftime("%^10h").should == ' FEB'
+ time.strftime("%_10h").should == ' Feb'
+ time.strftime("%_010h").should == '0000000Feb'
+ time.strftime("%0_10h").should == ' Feb'
+ time.strftime("%0_-10h").should == 'Feb'
+ time.strftime("%0-_10h").should == 'Feb'
+ end
+
+ it "supports the '-' modifier to drop leading zeros" do
+ @new_date[2001,3,22].strftime("%-m/%-d/%-y").should == "3/22/1"
+ end
+
+ it "passes the format string's encoding to the result string" do
+ result = @new_date[2010,3,8].strftime("%d. März %Y")
+
+ result.encoding.should == Encoding::UTF_8
+ result.should == "08. März 2010"
+ end
+end
diff --git a/spec/ruby/shared/time/strftime_for_time.rb b/spec/ruby/shared/time/strftime_for_time.rb
new file mode 100644
index 0000000000..9f4d08d72e
--- /dev/null
+++ b/spec/ruby/shared/time/strftime_for_time.rb
@@ -0,0 +1,181 @@
+# Shared for Time and DateTime, testing only time components (hours, minutes, seconds and smaller)
+
+describe :strftime_time, shared: true do
+ before :all do
+ @time = @new_time[2001, 2, 3, 4, 5, 6]
+ end
+
+ it "formats time according to the directives in the given format string" do
+ @new_time[1970, 1, 1].strftime("There is %M minutes in epoch").should == "There is 00 minutes in epoch"
+ end
+
+ # Per conversion specifier, not combining
+ it "returns the 24-based hour with %H" do
+ time = @new_time[2009, 9, 18, 18, 0, 0]
+ time.strftime('%H').should == '18'
+ end
+
+ it "returns the 12-based hour with %I" do
+ time = @new_time[2009, 9, 18, 18, 0, 0]
+ time.strftime('%I').should == '06'
+ end
+
+ it "supports 24-hr formatting with %l" do
+ time = @new_time[2004, 8, 26, 22, 38, 3]
+ time.strftime("%k").should == "22"
+ morning_time = @new_time[2004, 8, 26, 6, 38, 3]
+ morning_time.strftime("%k").should == " 6"
+ end
+
+ describe "with %L" do
+ it "formats the milliseconds of the second" do
+ @new_time[2009, 1, 1, 0, 0, Rational(100, 1000)].strftime("%L").should == "100"
+ @new_time[2009, 1, 1, 0, 0, Rational(10, 1000)].strftime("%L").should == "010"
+ @new_time[2009, 1, 1, 0, 0, Rational(1, 1000)].strftime("%L").should == "001"
+ @new_time[2009, 1, 1, 0, 0, Rational(1, 10000)].strftime("%L").should == "000"
+ end
+ end
+
+ it "supports 12-hr formatting with %l" do
+ time = @new_time[2004, 8, 26, 22, 38, 3]
+ time.strftime('%l').should == '10'
+ morning_time = @new_time[2004, 8, 26, 6, 38, 3]
+ morning_time.strftime('%l').should == ' 6'
+ end
+
+ it "returns the minute with %M" do
+ time = @new_time[2009, 9, 18, 12, 6, 0]
+ time.strftime('%M').should == '06'
+ end
+
+ describe "with %N" do
+ it "formats the nanoseconds of the second with %N" do
+ @new_time[2000, 4, 6, 0, 0, Rational(1234560, 1_000_000_000)].strftime("%N").should == "001234560"
+ end
+
+ it "formats the milliseconds of the second with %3N" do
+ @new_time[2000, 4, 6, 0, 0, Rational(50, 1000)].strftime("%3N").should == "050"
+ end
+
+ it "formats the microseconds of the second with %6N" do
+ @new_time[2000, 4, 6, 0, 0, Rational(42, 1000)].strftime("%6N").should == "042000"
+ end
+
+ it "formats the nanoseconds of the second with %9N" do
+ @new_time[2000, 4, 6, 0, 0, Rational(1234, 1_000_000)].strftime("%9N").should == "001234000"
+ end
+
+ it "formats the picoseconds of the second with %12N" do
+ @new_time[2000, 4, 6, 0, 0, Rational(999999999999, 1000_000_000_000)].strftime("%12N").should == "999999999999"
+ end
+ end
+
+ it "supports am/pm formatting with %P" do
+ time = @new_time[2004, 8, 26, 22, 38, 3]
+ time.strftime('%P').should == 'pm'
+ time = @new_time[2004, 8, 26, 11, 38, 3]
+ time.strftime('%P').should == 'am'
+ end
+
+ it "supports AM/PM formatting with %p" do
+ time = @new_time[2004, 8, 26, 22, 38, 3]
+ time.strftime('%p').should == 'PM'
+ time = @new_time[2004, 8, 26, 11, 38, 3]
+ time.strftime('%p').should == 'AM'
+ end
+
+ it "returns the second with %S" do
+ time = @new_time[2009, 9, 18, 12, 0, 6]
+ time.strftime('%S').should == '06'
+ end
+
+ it "should be able to show the number of seconds since the unix epoch" do
+ @new_time_in_zone["GMT", 0, 2005].strftime("%s").should == "1104537600"
+ end
+
+ it "returns the timezone with %Z" do
+ time = @new_time[2009, 9, 18, 12, 0, 0]
+ zone = time.zone
+ time.strftime("%Z").should == zone
+ end
+
+ describe "with %z" do
+ it "formats a UTC time offset as '+0000'" do
+ @new_time_in_zone["GMT", 0, 2005].strftime("%z").should == "+0000"
+ end
+
+ it "formats a local time with positive UTC offset as '+HHMM'" do
+ @new_time_in_zone["CET", 1, 2005].strftime("%z").should == "+0100"
+ end
+
+ it "formats a local time with negative UTC offset as '-HHMM'" do
+ @new_time_in_zone["PST", -8, 2005].strftime("%z").should == "-0800"
+ end
+
+ it "formats a time with fixed positive offset as '+HHMM'" do
+ @new_time_with_offset[2012, 1, 1, 0, 0, 0, 3660].strftime("%z").should == "+0101"
+ end
+
+ it "formats a time with fixed negative offset as '-HHMM'" do
+ @new_time_with_offset[2012, 1, 1, 0, 0, 0, -3660].strftime("%z").should == "-0101"
+ end
+
+ it "formats a time with fixed offset as '+/-HH:MM' with ':' specifier" do
+ @new_time_with_offset[2012, 1, 1, 0, 0, 0, 3660].strftime("%:z").should == "+01:01"
+ end
+
+ it "formats a time with fixed offset as '+/-HH:MM:SS' with '::' specifier" do
+ @new_time_with_offset[2012, 1, 1, 0, 0, 0, 3665].strftime("%::z").should == "+01:01:05"
+ end
+ end
+
+ # Combining conversion specifiers
+ it "should be able to print the time in full" do
+ @time.strftime("%c").should == "Sat Feb 3 04:05:06 2001"
+ @time.strftime("%c").should == @time.strftime('%a %b %e %H:%M:%S %Y')
+ end
+
+ it "should be able to show HH:MM" do
+ @time.strftime("%R").should == "04:05"
+ @time.strftime("%R").should == @time.strftime('%H:%M')
+ end
+
+ it "should be able to show HH:MM:SS AM/PM" do
+ @time.strftime("%r").should == "04:05:06 AM"
+ @time.strftime("%r").should == @time.strftime('%I:%M:%S %p')
+ end
+
+ it "supports HH:MM:SS formatting with %T" do
+ @time.strftime('%T').should == '04:05:06'
+ @time.strftime('%T').should == @time.strftime('%H:%M:%S')
+ end
+
+ it "supports HH:MM:SS formatting with %X" do
+ @time.strftime('%X').should == '04:05:06'
+ @time.strftime('%X').should == @time.strftime('%H:%M:%S')
+ end
+
+ # GNU modificators
+ it "supports the '-' modifier to drop leading zeros" do
+ time = @new_time[2001,1,1,14,01,42]
+ time.strftime("%-m/%-d/%-y %-I:%-M %p").should == "1/1/1 2:1 PM"
+
+ time = @new_time[2010,10,10,12,10,42]
+ time.strftime("%-m/%-d/%-y %-I:%-M %p").should == "10/10/10 12:10 PM"
+ end
+
+ it "supports the '-' modifier for padded format directives" do
+ time = @new_time[2010, 8, 8, 8, 10, 42]
+ time.strftime("%-e").should == "8"
+ time.strftime("%-k%p").should == "8AM"
+ time.strftime("%-l%p").should == "8AM"
+ end
+
+ it "should be able to show default Logger format" do
+ default_logger_format = "%Y-%m-%dT%H:%M:%S.%6N "
+ @new_time[2001, 2, 3, 4, 5, 6].strftime(default_logger_format).should == "2001-02-03T04:05:06.000000 "
+ @new_time[2001, 12, 3, 4, 5, 6 + 1/10r].strftime(default_logger_format).should == "2001-12-03T04:05:06.100000 "
+ @new_time[2001, 2, 13, 4, 5, 6 + 1/100r].strftime(default_logger_format).should == "2001-02-13T04:05:06.010000 "
+ @new_time[2001, 2, 3, 14, 5, 6 + 1/1000r].strftime(default_logger_format).should == "2001-02-03T14:05:06.001000 "
+ end
+end
diff --git a/spec/ruby/shared/time/yday.rb b/spec/ruby/shared/time/yday.rb
new file mode 100644
index 0000000000..f81c45261c
--- /dev/null
+++ b/spec/ruby/shared/time/yday.rb
@@ -0,0 +1,18 @@
+describe :time_yday, shared: true do
+ it 'returns the correct value for each day of each month' do
+ mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
+
+ yday = 1
+ mdays.each_with_index do |days, month|
+ days.times do |day|
+ @method.call(2014, month+1, day+1).should == yday
+ yday += 1
+ end
+ end
+ end
+
+ it 'supports leap years' do
+ @method.call(2016, 2, 29).should == 31 + 29
+ @method.call(2016, 3, 1).should == 31 + 29 + 1
+ end
+end
diff --git a/spec/ruby/shared/types/rb_num2dbl_fails.rb b/spec/ruby/shared/types/rb_num2dbl_fails.rb
new file mode 100644
index 0000000000..ec7cc11986
--- /dev/null
+++ b/spec/ruby/shared/types/rb_num2dbl_fails.rb
@@ -0,0 +1,17 @@
+#
+# Shared tests for rb_num2dbl related conversion failures.
+#
+# Usage example:
+# it_behaves_like :rb_num2dbl_fails, nil, -> v { o = A.new; o.foo(v) }
+#
+
+describe :rb_num2dbl_fails, shared: true do
+ it "fails if string is provided" do
+ -> { @object.call("123") }.should raise_error(TypeError, "no implicit conversion to float from string")
+ end
+
+ it "fails if boolean is provided" do
+ -> { @object.call(true) }.should raise_error(TypeError, "no implicit conversion to float from true")
+ -> { @object.call(false) }.should raise_error(TypeError, "no implicit conversion to float from false")
+ end
+end