diff options
Diffstat (limited to 'spec/ruby/shared')
90 files changed, 4085 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..0759dd9606 --- /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 + lambda { @object.no_such_method }.should raise_error(NoMethodError) + end + + it "raises a NoMethodError when a protected method is called" do + lambda { @object.method_protected }.should raise_error(NoMethodError) + end + + it "raises a NoMethodError when a private method is called" do + lambda { @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 + lambda { @object.no_such_method }.should raise_error(NoMethodError) + end + + it "raises a NoMethodError when a protected method is called" do + lambda { @object.method_protected }.should raise_error(NoMethodError) + end + + it "raises a NoMethodError when a private method is called" do + lambda { @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 + lambda { @object.new.no_such_method }.should raise_error(NoMethodError) + end + + it "raises a NoMethodError when a protected method is called" do + lambda { @object.new.method_protected }.should raise_error(NoMethodError) + end + + it "raises a NoMethodError when a private method is called" do + lambda { @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..2b79ab4c2c --- /dev/null +++ b/spec/ruby/shared/basicobject/send.rb @@ -0,0 +1,117 @@ +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 + lambda { 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 + lambda { SendSpecs::Foo.send(@method, :baz) }.should raise_error(NameError) + end + + it "raises an ArgumentError if no arguments are given" do + class SendSpecs::Foo; end + lambda { 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 + + lambda { 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 + + lambda { 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 +end diff --git a/spec/ruby/shared/enumerator/each.rb b/spec/ruby/shared/enumerator/each.rb new file mode 100644 index 0000000000..bbab86fed7 --- /dev/null +++ b/spec/ruby/shared/enumerator/each.rb @@ -0,0 +1,89 @@ +# -*- encoding: us-ascii -*- + +describe :enum_each, shared: true do + before :each do + object_each_with_arguments = Object.new + def object_each_with_arguments.each_with_arguments(arg, *args) + yield arg, *args + :method_returned + end + + @enum_with_arguments = object_each_with_arguments.to_enum(:each_with_arguments, :arg0, :arg1, :arg2) + + @enum_with_yielder = Enumerator.new {|y| y.yield :ok} + end + + it "yields each element of self to the given block" do + acc = [] + [1,2,3].to_enum.each {|e| acc << e } + acc.should == [1,2,3] + end + + it "calls #each on the object given in the constructor by default" do + each = mock('each') + each.should_receive(:each) + each.to_enum.each {|e| e } + end + + it "calls #each on the underlying object until it's exhausted" do + each = mock('each') + each.should_receive(:each).and_yield(1).and_yield(2).and_yield(3) + acc = [] + each.to_enum.each {|e| acc << e } + acc.should == [1,2,3] + end + + it "calls the method given in the constructor instead of #each" do + each = mock('peach') + each.should_receive(:peach) + each.to_enum(:peach).each {|e| e } + end + + it "calls the method given in the constructor until it's exhausted" do + each = mock('each') + each.should_receive(:each).and_yield(1).and_yield(2).and_yield(3) + acc = [] + each.to_enum.each {|e| acc << e } + acc.should == [1,2,3] + end + + it "raises a NoMethodError if the object doesn't respond to #each" do + enum = Object.new.to_enum + lambda do + enum.each { |e| e } + end.should raise_error(NoMethodError) + end + + it "returns self if not given arguments and not given a block" do + @enum_with_arguments.each.should equal(@enum_with_arguments) + + @enum_with_yielder.each.should equal(@enum_with_yielder) + end + + it "returns the same value from receiver.each if block is given" do + @enum_with_arguments.each {}.should equal(:method_returned) + end + + it "passes given arguments at initialized to receiver.each" do + @enum_with_arguments.each.to_a.should == [[:arg0, :arg1, :arg2]] + end + + it "requires multiple arguments" do + Enumerator.instance_method(:each).arity.should < 0 + end + + it "appends given arguments to receiver.each" do + @enum_with_arguments.each(:each0, :each1).to_a.should == [[:arg0, :arg1, :arg2, :each0, :each1]] + @enum_with_arguments.each(:each2, :each3).to_a.should == [[:arg0, :arg1, :arg2, :each2, :each3]] + end + + it "returns the same value from receiver.each if block and arguments are given" do + @enum_with_arguments.each(:each1, :each2) {}.should equal(:method_returned) + end + + it "returns new Enumerator if given arguments but not given a block" do + ret = @enum_with_arguments.each 1 + ret.should be_an_instance_of(Enumerator) + ret.should_not equal(@enum_with_arguments) + end +end diff --git a/spec/ruby/shared/enumerator/enum_cons.rb b/spec/ruby/shared/enumerator/enum_cons.rb new file mode 100644 index 0000000000..fec0f4766e --- /dev/null +++ b/spec/ruby/shared/enumerator/enum_cons.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/enumerator/classes' + +describe :enum_cons, shared: true do + it "returns an enumerator of the receiver with iteration of each_cons for each array of n concecutive elements" do + a = [] + enum = EnumSpecs::Numerous.new.enum_cons(3) + enum.each {|x| a << x} + enum.should be_an_instance_of(Enumerator) + a.should == [[2, 5, 3], [5, 3, 6], [3, 6, 1], [6, 1, 4]] + end +end diff --git a/spec/ruby/shared/enumerator/enum_for.rb b/spec/ruby/shared/enumerator/enum_for.rb new file mode 100644 index 0000000000..9030ffbd7d --- /dev/null +++ b/spec/ruby/shared/enumerator/enum_for.rb @@ -0,0 +1,50 @@ +describe :enum_for, shared: true do + it "is defined in Kernel" do + Kernel.method_defined?(@method).should be_true + end + + it "returns a new enumerator" do + "abc".send(@method).should be_an_instance_of(Enumerator) + end + + it "defaults the first argument to :each" do + enum = [1,2].send(@method) + enum.map { |v| v }.should == [1,2].each { |v| v } + end + + it "exposes multi-arg yields as an array" do + o = Object.new + def o.each + yield :a + yield :b1, :b2 + yield [:c] + yield :d1, :d2 + yield :e1, :e2, :e3 + end + + enum = o.send(@method) + enum.next.should == :a + enum.next.should == [:b1, :b2] + enum.next.should == [:c] + enum.next.should == [:d1, :d2] + enum.next.should == [:e1, :e2, :e3] + end + + it "uses the passed block's value to calculate the size of the enumerator" do + Object.new.enum_for { 100 }.size.should == 100 + end + + it "defers the evaluation of the passed block until #size is called" do + ScratchPad.record [] + + enum = Object.new.enum_for do + ScratchPad << :called + 100 + end + + ScratchPad.recorded.should be_empty + + enum.size + ScratchPad.recorded.should == [:called] + end +end diff --git a/spec/ruby/shared/enumerator/new.rb b/spec/ruby/shared/enumerator/new.rb new file mode 100644 index 0000000000..2eb39e0abd --- /dev/null +++ b/spec/ruby/shared/enumerator/new.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' + +describe :enum_new, shared: true do + it "creates a new custom enumerator with the given object, iterator and arguments" do + enum = Enumerator.new(1, :upto, 3) + enum.should be_an_instance_of(Enumerator) + end + + it "creates a new custom enumerator that responds to #each" do + enum = Enumerator.new(1, :upto, 3) + enum.respond_to?(:each).should == true + end + + it "creates a new custom enumerator that runs correctly" do + Enumerator.new(1, :upto, 3).map{|x|x}.should == [1,2,3] + end + + it "aliases the second argument to :each" do + Enumerator.new(1..2).to_a.should == Enumerator.new(1..2, :each).to_a + end + + it "doesn't check for the presence of the iterator method" do + Enumerator.new(nil).should be_an_instance_of(Enumerator) + end + + it "uses the latest define iterator method" do + class StrangeEach + def each + yield :foo + end + end + enum = Enumerator.new(StrangeEach.new) + enum.to_a.should == [:foo] + class StrangeEach + def each + yield :bar + end + end + enum.to_a.should == [:bar] + end + +end diff --git a/spec/ruby/shared/enumerator/next.rb b/spec/ruby/shared/enumerator/next.rb new file mode 100644 index 0000000000..dd9a2a0f81 --- /dev/null +++ b/spec/ruby/shared/enumerator/next.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' + +describe :enum_next, shared: true do + + before :each do + @enum = 1.upto(3) + end + + it "returns the next element of the enumeration" do + @enum.next.should == 1 + @enum.next.should == 2 + @enum.next.should == 3 + end + + it "raises a StopIteration exception at the end of the stream" do + 3.times { @enum.next } + lambda { @enum.next }.should raise_error(StopIteration) + end + + it "cannot be called again until the enumerator is rewound" do + 3.times { @enum.next } + lambda { @enum.next }.should raise_error(StopIteration) + lambda { @enum.next }.should raise_error(StopIteration) + lambda { @enum.next }.should raise_error(StopIteration) + @enum.rewind + @enum.next.should == 1 + end +end diff --git a/spec/ruby/shared/enumerator/rewind.rb b/spec/ruby/shared/enumerator/rewind.rb new file mode 100644 index 0000000000..a3c14ec496 --- /dev/null +++ b/spec/ruby/shared/enumerator/rewind.rb @@ -0,0 +1,39 @@ +require_relative '../../spec_helper' + +describe :enum_rewind, shared: true do + + before :each do + @enum = 1.upto(3) + end + + it "resets the enumerator to its initial state" do + @enum.next.should == 1 + @enum.next.should == 2 + @enum.rewind + @enum.next.should == 1 + end + + it "returns self" do + @enum.rewind.should == @enum + end + + it "has no effect on a new enumerator" do + @enum.rewind + @enum.next.should == 1 + end + + it "has no effect if called multiple, consecutive times" do + @enum.next.should == 1 + @enum.rewind + @enum.rewind + @enum.next.should == 1 + end + + it "works with peek to reset the position" do + @enum.next + @enum.next + @enum.rewind + @enum.next + @enum.peek.should == 2 + end +end diff --git a/spec/ruby/shared/enumerator/with_index.rb b/spec/ruby/shared/enumerator/with_index.rb new file mode 100644 index 0000000000..4f459bd9a8 --- /dev/null +++ b/spec/ruby/shared/enumerator/with_index.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' + +describe :enum_with_index, shared: true do + + require_relative '../../fixtures/enumerator/classes' + + before :each do + @enum = [1, 2, 3, 4].to_enum + end + + it "passes each element and its index to block" do + @a = [] + @enum.send(@method) { |o, i| @a << [o, i] } + @a.should == [[1, 0], [2, 1], [3, 2], [4, 3]] + end + + it "returns the object being enumerated when given a block" do + [1, 2, 3, 4].should == @enum.send(@method) { |o, i| :glark } + end + + it "binds splat arguments properly" do + acc = [] + @enum.send(@method) { |*b| c,d = b; acc << c; acc << d } + [1, 0, 2, 1, 3, 2, 4, 3].should == acc + end + + it "returns an enumerator if no block is supplied" do + ewi = @enum.send(@method) + ewi.should be_an_instance_of(Enumerator) + ewi.to_a.should == [[1, 0], [2, 1], [3, 2], [4, 3]] + end +end diff --git a/spec/ruby/shared/enumerator/with_object.rb b/spec/ruby/shared/enumerator/with_object.rb new file mode 100644 index 0000000000..c2e3a79366 --- /dev/null +++ b/spec/ruby/shared/enumerator/with_object.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' + +describe :enum_with_object, shared: true do + before :each do + @enum = [:a, :b].to_enum + @memo = '' + @block_params = @enum.send(@method, @memo).to_a + end + + it "receives an argument" do + @enum.method(@method).arity.should == 1 + end + + context "with block" do + it "returns the given object" do + ret = @enum.send(@method, @memo) do |elm, memo| + # nothing + end + ret.should equal(@memo) + end + + context "the block parameter" do + it "passes each element to first parameter" do + @block_params[0][0].should equal(:a) + @block_params[1][0].should equal(:b) + end + + it "passes the given object to last parameter" do + @block_params[0][1].should equal(@memo) + @block_params[1][1].should equal(@memo) + end + end + end + + context "without block" do + it "returns new Enumerator" do + ret = @enum.send(@method, @memo) + ret.should be_an_instance_of(Enumerator) + ret.should_not equal(@enum) + end + end +end diff --git a/spec/ruby/shared/fiber/resume.rb b/spec/ruby/shared/fiber/resume.rb new file mode 100644 index 0000000000..e2d30d781b --- /dev/null +++ b/spec/ruby/shared/fiber/resume.rb @@ -0,0 +1,79 @@ +describe :fiber_resume, shared: true do + it "can be invoked from the root Fiber" do + fiber = Fiber.new { :fiber } + fiber.send(@method).should == :fiber + end + + it "raises a FiberError if invoked from a different Thread" do + fiber = Fiber.new { 42 } + Thread.new do + -> { + fiber.send(@method) + }.should raise_error(FiberError) + end.join + + # Check the Fiber can still be used + fiber.send(@method).should == 42 + end + + it "passes control to the beginning of the block on first invocation" do + invoked = false + fiber = Fiber.new { invoked = true } + fiber.send(@method) + invoked.should be_true + end + + it "returns the last value encountered on first invocation" do + fiber = Fiber.new { 1+1; true } + fiber.send(@method).should be_true + end + + it "runs until the end of the block" do + obj = mock('obj') + obj.should_receive(:do).once + fiber = Fiber.new { 1 + 2; a = "glark"; obj.do } + fiber.send(@method) + end + + it "runs until Fiber.yield" do + obj = mock('obj') + obj.should_not_receive(:do) + fiber = Fiber.new { 1 + 2; Fiber.yield; obj.do } + fiber.send(@method) + end + + it "resumes from the last call to Fiber.yield on subsequent invocations" do + fiber = Fiber.new { Fiber.yield :first; :second } + fiber.send(@method).should == :first + fiber.send(@method).should == :second + end + + it "accepts any number of arguments" do + fiber = Fiber.new { |a| } + lambda { fiber.send(@method, *(1..10).to_a) }.should_not raise_error + end + + it "sets the block parameters to its arguments on the first invocation" do + first = mock('first') + first.should_receive(:arg).with(:first).twice + fiber = Fiber.new { |arg| first.arg arg; Fiber.yield; first.arg arg; } + fiber.send(@method, :first) + fiber.send(@method, :second) + end + + it "raises a FiberError if the Fiber is dead" do + fiber = Fiber.new { true } + fiber.send(@method) + lambda { fiber.send(@method) }.should raise_error(FiberError) + end + + it "raises a LocalJumpError if the block includes a return statement" do + fiber = Fiber.new { return; } + lambda { fiber.send(@method) }.should raise_error(LocalJumpError) + end + + it "raises a LocalJumpError if the block includes a break statement" do + fiber = Fiber.new { break; } + lambda { fiber.send(@method) }.should raise_error(LocalJumpError) + 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..67e939bf16 --- /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 + lambda { @object.send(@method, 1) }.should raise_error(TypeError) + lambda { @object.send(@method, bignum_value) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed nil" do + lambda { @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..5b21fb0d97 --- /dev/null +++ b/spec/ruby/shared/file/executable.rb @@ -0,0 +1,48 @@ +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 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 + lambda { @object.send(@method) }.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed a String type" do + lambda { @object.send(@method, 1) }.should raise_error(TypeError) + lambda { @object.send(@method, nil) }.should raise_error(TypeError) + lambda { @object.send(@method, false) }.should raise_error(TypeError) + 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..2b1bf8f585 --- /dev/null +++ b/spec/ruby/shared/file/executable_real.rb @@ -0,0 +1,46 @@ +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 + lambda { @object.send(@method) }.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed a String type" do + lambda { @object.send(@method, 1) }.should raise_error(TypeError) + lambda { @object.send(@method, nil) }.should raise_error(TypeError) + lambda { @object.send(@method, false) }.should raise_error(TypeError) + 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..1557f01a82 --- /dev/null +++ b/spec/ruby/shared/file/exist.rb @@ -0,0 +1,24 @@ +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 "returns true if the file exist using the alias exists?" 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 + lambda { @object.send(@method) }.should raise_error(ArgumentError) + lambda { @object.send(@method, __FILE__, __FILE__) }.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed a String type" do + lambda { @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..095bd63fff --- /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 + lambda { @object.send(@method) }.should raise_error(ArgumentError) + lambda { @object.send(@method, @null, @file) }.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed a String type" do + lambda { @object.send(@method, nil) }.should raise_error(TypeError) + lambda { @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..91a6483030 --- /dev/null +++ b/spec/ruby/shared/file/grpowned.rb @@ -0,0 +1,40 @@ +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 + # No supplementary groups + 1.should == 1 + 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..e89cd309ea --- /dev/null +++ b/spec/ruby/shared/file/identical.rb @@ -0,0 +1,47 @@ +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 + File.link(@file1, @link) + 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 + lambda { @object.send(@method, @file1, @file2, @link) }.should raise_error(ArgumentError) + lambda { @object.send(@method, @file1) }.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed String types" do + lambda { @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..74f58caaff --- /dev/null +++ b/spec/ruby/shared/file/readable.rb @@ -0,0 +1,30 @@ +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 do + @file2 = "/etc/passwd" + 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 +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..b6e53ac76d --- /dev/null +++ b/spec/ruby/shared/file/readable_real.rb @@ -0,0 +1,23 @@ +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 +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..bb95190fc0 --- /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 + lambda {@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..55a1cfd284 --- /dev/null +++ b/spec/ruby/shared/file/socket.rb @@ -0,0 +1,3 @@ +describe :file_socket, shared: true do + it "accepts an object that has a #to_path method" +end diff --git a/spec/ruby/shared/file/sticky.rb b/spec/ruby/shared/file/sticky.rb new file mode 100644 index 0000000000..38bb6ed26b --- /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, :solaris, :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..0fd5a28397 --- /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 Fixnum is because it's system dependent + it "returns a Fixnum if the file is chmod 644" do + File.chmod(0644, @file) + @object.world_readable?(@file).should be_an_instance_of(Fixnum) + end + + it "returns a Fixnum if the file is a directory and chmod 644" do + dir = rand().to_s + '-ww' + Dir.mkdir(dir) + Dir.exist?(dir).should be_true + File.chmod(0644, dir) + @object.world_readable?(dir).should be_an_instance_of(Fixnum) + 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..da8af0bc2a --- /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 Fixnum is because it's system dependent + it "returns a Fixnum if the file is chmod 777" do + File.chmod(0777, @file) + @object.world_writable?(@file).should be_an_instance_of(Fixnum) + end + + it "returns a Fixnum if the file is a directory and chmod 777" do + dir = rand().to_s + '-ww' + Dir.mkdir(dir) + Dir.exist?(dir).should be_true + File.chmod(0777, dir) + @object.world_writable?(dir).should be_an_instance_of(Fixnum) + 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..902d545da1 --- /dev/null +++ b/spec/ruby/shared/file/writable.rb @@ -0,0 +1,28 @@ +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 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 +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..3730befb7a --- /dev/null +++ b/spec/ruby/shared/file/writable_real.rb @@ -0,0 +1,33 @@ +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 + lambda { File.writable_real? }.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed a String type" do + lambda { @object.send(@method, 1) }.should raise_error(TypeError) + lambda { @object.send(@method, nil) }.should raise_error(TypeError) + lambda { @object.send(@method, false) }.should raise_error(TypeError) + 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..cf014d4722 --- /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 + lambda { File.zero? }.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed a String type" do + lambda { @object.send(@method, nil) }.should raise_error(TypeError) + lambda { @object.send(@method, true) }.should raise_error(TypeError) + lambda { @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..061c88c483 --- /dev/null +++ b/spec/ruby/shared/hash/key_error.rb @@ -0,0 +1,25 @@ +describe :key_error, shared: true do + it "raises a KeyError" do + -> { + @method.call(@object, 'foo') + }.should raise_error(KeyError) + end + + ruby_version_is "2.5" do + 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 +end diff --git a/spec/ruby/shared/io/putc.rb b/spec/ruby/shared/io/putc.rb new file mode 100644 index 0000000000..5f620f183f --- /dev/null +++ b/spec/ruby/shared/io/putc.rb @@ -0,0 +1,57 @@ +# -*- encoding: ascii-8bit -*- +describe :io_putc, shared: true do + after :each do + @io.close if @io && !@io.closed? + @io_object = nil + rm_r @name + end + + describe "with a Fixnum 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 + lambda { @io_object.send(@method, "a") }.should raise_error(IOError) + end + + it "raises a TypeError when passed nil" do + lambda { @io_object.send(@method, nil) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed false" do + lambda { @io_object.send(@method, false) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed true" do + lambda { @io_object.send(@method, true) }.should raise_error(TypeError) + 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/object_id.rb b/spec/ruby/shared/kernel/object_id.rb new file mode 100644 index 0000000000..7acdb27554 --- /dev/null +++ b/spec/ruby/shared/kernel/object_id.rb @@ -0,0 +1,80 @@ +# 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 + + it "returns a different value for two String literals" do + o1 = "hello" + o2 = "hello" + o1.send(@method).should_not == o2.send(@method) + 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..3cff55ac6e --- /dev/null +++ b/spec/ruby/shared/kernel/raise.rb @@ -0,0 +1,85 @@ +describe :kernel_raise, shared: true do + before :each do + ScratchPad.clear + end + + it "aborts execution" do + lambda do + @object.raise Exception, "abort" + ScratchPad.record :no_abort + end.should raise_error(Exception, "abort") + + ScratchPad.recorded.should be_nil + end + + it "raises RuntimeError if no exception class is given" do + lambda { @object.raise }.should raise_error(RuntimeError) + end + + it "raises a given Exception instance" do + error = RuntimeError.new + lambda { @object.raise(error) }.should raise_error(error) + end + + it "raises a RuntimeError if string given" do + lambda { @object.raise("a bad thing") }.should raise_error(RuntimeError) + end + + it "raises a TypeError when passed a non-Exception object" do + lambda { @object.raise(Object.new) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed true" do + lambda { @object.raise(true) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed false" do + lambda { @object.raise(false) }.should raise_error(TypeError) + end + + it "raises a TypeError when passed nil" do + lambda { @object.raise(nil) }.should raise_error(TypeError) + end + + it "re-raises the previously rescued exception if no exception is specified" do + lambda do + begin + @object.raise Exception, "outer" + ScratchPad.record :no_abort + rescue + begin + @object.raise StandardError, "inner" + rescue + end + + @object.raise + ScratchPad.record :no_reraise + end + end.should raise_error(Exception, "outer") + + ScratchPad.recorded.should be_nil + end + + it "re-raises a previously rescued exception without overwriting the backtrace" do + begin + initial_raise_line = __LINE__; @object.raise 'raised' + rescue => raised + begin + raise_again_line = __LINE__; @object.raise raised + rescue => raised_again + # This spec is written using #backtrace and matching the line number + # from the string, as backtrace_locations is a more advanced + # method that is not always supported by implementations. + + raised_again.backtrace.first.should include("#{__FILE__}:#{initial_raise_line}:") + raised_again.backtrace.first.should_not include("#{__FILE__}:#{raise_again_line}:") + end + end + end + + it "allows Exception, message, and backtrace parameters" do + lambda do + @object.raise(ArgumentError, "message", caller) + end.should raise_error(ArgumentError, "message") + end +end diff --git a/spec/ruby/shared/math/atanh.rb b/spec/ruby/shared/math/atanh.rb new file mode 100644 index 0000000000..1d1a6ebd74 --- /dev/null +++ b/spec/ruby/shared/math/atanh.rb @@ -0,0 +1,44 @@ +describe :math_atanh_base, shared: true do + it "returns a float" do + @object.send(@method, 0.5).should be_an_instance_of(Float) + end + + it "returns the inverse hyperbolic tangent of the argument" do + @object.send(@method, 0.0).should == 0.0 + @object.send(@method, -0.0).should == -0.0 + @object.send(@method, 0.5).should be_close(0.549306144334055, TOLERANCE) + @object.send(@method, -0.2).should be_close(-0.202732554054082, TOLERANCE) + end + + it "raises a TypeError if the argument is nil" do + lambda { @object.send(@method, nil) }.should raise_error(TypeError) + end + + it "raises a TypeError if the argument is not a Numeric" do + lambda { @object.send(@method, "test") }.should raise_error(TypeError) + end + + it "returns Infinity if x == 1.0" do + @object.send(@method, 1.0).should == Float::INFINITY + end + + it "return -Infinity if x == -1.0" do + @object.send(@method, -1.0).should == -Float::INFINITY + end +end + +describe :math_atanh_private, shared: true do + it "is a private instance method" do + Math.should have_private_instance_method(@method) + end +end + +describe :math_atanh_no_complex, shared: true do + it "raises a Math::DomainError for arguments greater than 1.0" do + lambda { @object.send(@method, 1.0 + Float::EPSILON) }.should raise_error(Math::DomainError) + end + + it "raises a Math::DomainError for arguments less than -1.0" do + lambda { @object.send(@method, -1.0 - Float::EPSILON) }.should raise_error(Math::DomainError) + end +end diff --git a/spec/ruby/shared/process/abort.rb b/spec/ruby/shared/process/abort.rb new file mode 100644 index 0000000000..1a25aeffc2 --- /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 + lambda { @object.abort }.should raise_error(SystemExit) + end + + it "sets the exception message to the given message" do + lambda { @object.abort "message" }.should raise_error { |e| e.message.should == "message" } + end + + it "sets the exception status code of 1" do + lambda { @object.abort }.should raise_error { |e| e.status.should == 1 } + end + + it "prints the specified message to STDERR" do + lambda { @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") + lambda { @object.abort str }.should raise_error(SystemExit, "message") + end + + it "raises TypeError when given a non-String object" do + lambda { @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..7d567c8195 --- /dev/null +++ b/spec/ruby/shared/process/exit.rb @@ -0,0 +1,94 @@ +describe :process_exit, shared: true do + it "raises a SystemExit with status 0" do + lambda { @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| + lambda { @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| + lambda { @object.exit(value) }.should raise_error(SystemExit) { |e| + e.status.should == status + } + end + 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) + lambda { @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| + lambda { @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 + lambda { @object.exit(Object.new) }.should raise_error(TypeError) + lambda { @object.exit('0') }.should raise_error(TypeError) + lambda { @object.exit([0]) }.should raise_error(TypeError) + lambda { @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 + lambda { 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') + 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') + 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') + out.should == "" + $?.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..c2c2aee9bf --- /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 + lambda { @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 = Process.fork { exit! 0 } + _, result = Process.wait2(pid) + result.exitstatus.should == 0 + end + + it "returns status zero" do + pid = Process.fork { exit 0 } + _, result = Process.wait2(pid) + result.exitstatus.should == 0 + end + + it "returns status zero" do + pid = Process.fork {} + _, result = Process.wait2(pid) + result.exitstatus.should == 0 + end + + it "returns status non-zero" do + pid = Process.fork { exit! 42 } + _, result = Process.wait2(pid) + result.exitstatus.should == 42 + end + + it "returns status non-zero" do + pid = Process.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.exist?(@file).should == true + 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.exist?(@file).should == true + 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..3590f367ba --- /dev/null +++ b/spec/ruby/shared/queue/deque.rb @@ -0,0 +1,85 @@ +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 "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 "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 + lambda { 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 + lambda { q.send(@method, true) }.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..ad413e1f46 --- /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 + lambda { + q.send @method, Object.new + }.should raise_error(ClosedQueueError) + 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/rational/Rational.rb b/spec/ruby/shared/rational/Rational.rb new file mode 100644 index 0000000000..752225269e --- /dev/null +++ b/spec/ruby/shared/rational/Rational.rb @@ -0,0 +1,103 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/rational' + +describe :kernel_Rational, shared: true do + describe "passed Integer" do + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + it "returns a new Rational number with 1 as the denominator" do + Rational(1).should eql(Rational(1, 1)) + Rational(-3).should eql(Rational(-3, 1)) + Rational(bignum_value).should eql(Rational(bignum_value, 1)) + end + end + end + + describe "passed two integers" do + it "returns a new Rational number" do + rat = Rational(1, 2) + rat.numerator.should == 1 + rat.denominator.should == 2 + rat.should be_an_instance_of(Rational) + + rat = Rational(-3, -5) + rat.numerator.should == 3 + rat.denominator.should == 5 + rat.should be_an_instance_of(Rational) + + rat = Rational(bignum_value, 3) + rat.numerator.should == bignum_value + rat.denominator.should == 3 + rat.should be_an_instance_of(Rational) + end + + it "reduces the Rational" do + rat = Rational(2, 4) + rat.numerator.should == 1 + rat.denominator.should == 2 + + rat = Rational(3, 9) + rat.numerator.should == 1 + rat.denominator.should == 3 + end + end + + describe "when passed a String" do + it "converts the String to a Rational using the same method as String#to_r" do + r = Rational(13, 25) + s_r = ".52".to_r + r_s = Rational(".52") + + r_s.should == r + r_s.should == s_r + end + + it "scales the Rational value of the first argument by the Rational value of the second" do + Rational(".52", ".6").should == Rational(13, 15) + Rational(".52", "1.6").should == Rational(13, 40) + end + + it "does not use the same method as Float#to_r" do + r = Rational(3, 5) + f_r = 0.6.to_r + r_s = Rational("0.6") + + r_s.should == r + r_s.should_not == f_r + end + + describe "when passed a Numeric" do + it "calls #to_r to convert the first argument to a Rational" do + num = RationalSpecs::SubNumeric.new(2) + + Rational(num).should == Rational(2) + end + end + + describe "when passed a Complex" do + it "returns a Rational from the real part if the imaginary part is 0" do + Rational(Complex(1, 0)).should == Rational(1) + end + + it "raises a RangeError if the imaginary part is not 0" do + lambda { Rational(Complex(1, 2)) }.should raise_error(RangeError) + end + end + + it "raises a TypeError if the first argument is nil" do + lambda { Rational(nil) }.should raise_error(TypeError) + end + + it "raises a TypeError if the second argument is nil" do + lambda { Rational(1, nil) }.should raise_error(TypeError) + end + + it "raises a TypeError if the first argument is a Symbol" do + lambda { Rational(:sym) }.should raise_error(TypeError) + end + + it "raises a TypeError if the second argument is a Symbol" do + lambda { Rational(1, :sym) }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/shared/rational/abs.rb b/spec/ruby/shared/rational/abs.rb new file mode 100644 index 0000000000..8beb20da7e --- /dev/null +++ b/spec/ruby/shared/rational/abs.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' + +describe :rational_abs, shared: true do + it "returns self's absolute value" do + Rational(3, 4).send(@method).should == Rational(3, 4) + Rational(-3, 4).send(@method).should == Rational(3, 4) + Rational(3, -4).send(@method).should == Rational(3, 4) + + Rational(bignum_value, -bignum_value).send(@method).should == Rational(bignum_value, bignum_value) + end +end diff --git a/spec/ruby/shared/rational/arithmetic_exception_in_coerce.rb b/spec/ruby/shared/rational/arithmetic_exception_in_coerce.rb new file mode 100644 index 0000000000..9377814732 --- /dev/null +++ b/spec/ruby/shared/rational/arithmetic_exception_in_coerce.rb @@ -0,0 +1,33 @@ +require_relative '../../fixtures/rational' + +describe :rational_arithmetic_exception_in_coerce, shared: true do + ruby_version_is ""..."2.5" do + it "rescues exception (StandardError and subclasses) raised in other#coerce and raises TypeError" do + b = mock("numeric with failed #coerce") + b.should_receive(:coerce).and_raise(RationalSpecs::CoerceError) + + # e.g. Rational(3, 4) + b + -> { Rational(3, 4).send(@method, b) }.should raise_error(TypeError, /MockObject can't be coerced into Rational/) + end + + it "does not rescue Exception and StandardError siblings raised in other#coerce" do + [Exception, NoMemoryError].each do |exception| + b = mock("numeric with failed #coerce") + b.should_receive(:coerce).and_raise(exception) + + # e.g. Rational(3, 4) + b + -> { Rational(3, 4).send(@method, b) }.should raise_error(exception) + end + end + end + + ruby_version_is "2.5" do + it "does not rescue exception raised in other#coerce" do + b = mock("numeric with failed #coerce") + b.should_receive(:coerce).and_raise(RationalSpecs::CoerceError) + + # e.g. Rational(3, 4) + b + -> { Rational(3, 4).send(@method, b) }.should raise_error(RationalSpecs::CoerceError) + end + end +end diff --git a/spec/ruby/shared/rational/ceil.rb b/spec/ruby/shared/rational/ceil.rb new file mode 100644 index 0000000000..f1cf60d2be --- /dev/null +++ b/spec/ruby/shared/rational/ceil.rb @@ -0,0 +1,45 @@ +require_relative '../../spec_helper' + +describe :rational_ceil, shared: true do + before do + @rational = Rational(2200, 7) + end + + describe "with no arguments (precision = 0)" do + it "returns an Integer" do + @rational.ceil.should be_kind_of(Integer) + end + + it "returns the truncated value toward positive infinity" do + @rational.ceil.should == 315 + Rational(1, 2).ceil.should == 1 + Rational(-1, 2).ceil.should == 0 + end + end + + describe "with a precision < 0" do + it "returns an Integer" do + @rational.ceil(-2).should be_kind_of(Integer) + @rational.ceil(-1).should be_kind_of(Integer) + end + + it "moves the truncation point n decimal places left" do + @rational.ceil(-3).should == 1000 + @rational.ceil(-2).should == 400 + @rational.ceil(-1).should == 320 + end + end + + describe "with precision > 0" do + it "returns a Rational" do + @rational.ceil(1).should be_kind_of(Rational) + @rational.ceil(2).should be_kind_of(Rational) + end + + it "moves the truncation point n decimal places right" do + @rational.ceil(1).should == Rational(3143, 10) + @rational.ceil(2).should == Rational(31429, 100) + @rational.ceil(3).should == Rational(157143, 500) + end + end +end diff --git a/spec/ruby/shared/rational/coerce.rb b/spec/ruby/shared/rational/coerce.rb new file mode 100644 index 0000000000..0d6c440993 --- /dev/null +++ b/spec/ruby/shared/rational/coerce.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' + +describe :rational_coerce, shared: true do + it "returns the passed argument, self as Float, when given a Float" do + result = Rational(3, 4).coerce(1.0) + result.should == [1.0, 0.75] + result.first.is_a?(Float).should be_true + result.last.is_a?(Float).should be_true + end + + it "returns the passed argument, self as Rational, when given an Integer" do + result = Rational(3, 4).coerce(10) + result.should == [Rational(10, 1), Rational(3, 4)] + result.first.is_a?(Rational).should be_true + result.last.is_a?(Rational).should be_true + end + + it "returns [argument, self] when given a Rational" do + Rational(3, 7).coerce(Rational(9, 2)).should == [Rational(9, 2), Rational(3, 7)] + end +end diff --git a/spec/ruby/shared/rational/comparison.rb b/spec/ruby/shared/rational/comparison.rb new file mode 100644 index 0000000000..0c8b3d0ac1 --- /dev/null +++ b/spec/ruby/shared/rational/comparison.rb @@ -0,0 +1,117 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/rational' + +describe :rational_cmp_rat, shared: true do + it "returns 1 when self is greater than the passed argument" do + (Rational(4, 4) <=> Rational(3, 4)).should equal(1) + (Rational(-3, 4) <=> Rational(-4, 4)).should equal(1) + end + + it "returns 0 when self is equal to the passed argument" do + (Rational(4, 4) <=> Rational(4, 4)).should equal(0) + (Rational(-3, 4) <=> Rational(-3, 4)).should equal(0) + end + + it "returns -1 when self is less than the passed argument" do + (Rational(3, 4) <=> Rational(4, 4)).should equal(-1) + (Rational(-4, 4) <=> Rational(-3, 4)).should equal(-1) + end +end + +describe :rational_cmp_int, shared: true do + it "returns 1 when self is greater than the passed argument" do + (Rational(4, 4) <=> 0).should equal(1) + (Rational(4, 4) <=> -10).should equal(1) + (Rational(-3, 4) <=> -1).should equal(1) + end + + it "returns 0 when self is equal to the passed argument" do + (Rational(4, 4) <=> 1).should equal(0) + (Rational(-8, 4) <=> -2).should equal(0) + end + + it "returns -1 when self is less than the passed argument" do + (Rational(3, 4) <=> 1).should equal(-1) + (Rational(-4, 4) <=> 0).should equal(-1) + end +end + +describe :rational_cmp_float, shared: true do + it "returns 1 when self is greater than the passed argument" do + (Rational(4, 4) <=> 0.5).should equal(1) + (Rational(4, 4) <=> -1.5).should equal(1) + (Rational(-3, 4) <=> -0.8).should equal(1) + end + + it "returns 0 when self is equal to the passed argument" do + (Rational(4, 4) <=> 1.0).should equal(0) + (Rational(-6, 4) <=> -1.5).should equal(0) + end + + it "returns -1 when self is less than the passed argument" do + (Rational(3, 4) <=> 1.2).should equal(-1) + (Rational(-4, 4) <=> 0.5).should equal(-1) + end +end + +describe :rational_cmp_coerce, shared: true do + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational <=> obj + end + + it "calls #<=> on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:<=>).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + (rational <=> obj).should == :result + end +end + +describe :rational_cmp_coerce_exception, shared: true do + ruby_version_is ""..."2.5" do + it "rescues exception (StandardError and subclasses) raised in other#coerce and returns nil" do + b = mock("numeric with failed #coerce") + b.should_receive(:coerce).and_raise(RationalSpecs::CoerceError) + + -> { + (Rational(3, 4) <=> b).should == nil + }.should complain(/Numerical comparison operators will no more rescue exceptions of #coerce/) + end + + it "does not rescue Exception and StandardError siblings raised in other#coerce" do + [Exception, NoMemoryError].each do |exception| + b = mock("numeric with failed #coerce") + b.should_receive(:coerce).and_raise(exception) + + -> { Rational(3, 4) <=> b }.should raise_error(exception) + end + end + end + + ruby_version_is "2.5" do + it "does not rescue exception raised in other#coerce" do + b = mock("numeric with failed #coerce") + b.should_receive(:coerce).and_raise(RationalSpecs::CoerceError) + + -> { Rational(3, 4) <=> b }.should raise_error(RationalSpecs::CoerceError) + end + end +end + +describe :rational_cmp_other, shared: true do + it "returns nil" do + (Rational <=> mock("Object")).should be_nil + end +end diff --git a/spec/ruby/shared/rational/denominator.rb b/spec/ruby/shared/rational/denominator.rb new file mode 100644 index 0000000000..10d46aacb3 --- /dev/null +++ b/spec/ruby/shared/rational/denominator.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' + +describe :rational_denominator, shared: true do + it "returns the denominator" do + Rational(3, 4).denominator.should equal(4) + Rational(3, -4).denominator.should equal(4) + + Rational(1, bignum_value).denominator.should == bignum_value + end + + it "returns 1 if no denominator was given" do + Rational(80).denominator.should == 1 + end +end diff --git a/spec/ruby/shared/rational/div.rb b/spec/ruby/shared/rational/div.rb new file mode 100644 index 0000000000..2bf9b80eb5 --- /dev/null +++ b/spec/ruby/shared/rational/div.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' + +describe :rational_div_rat, shared: true do + it "performs integer division and returns the result" do + Rational(2, 3).div(Rational(2, 3)).should == 1 + Rational(-2, 9).div(Rational(-9, 2)).should == 0 + end + + it "raises a ZeroDivisionError when the argument has a numerator of 0" do + lambda { Rational(3, 4).div(Rational(0, 3)) }.should raise_error(ZeroDivisionError) + end + + it "raises a ZeroDivisionError when the argument has a numerator of 0.0" do + lambda { Rational(3, 4).div(Rational(0.0, 3)) }.should raise_error(ZeroDivisionError) + end +end + +describe :rational_div_float, shared: true do + it "performs integer division and returns the result" do + Rational(2, 3).div(30.333).should == 0 + Rational(2, 9).div(Rational(-8.6)).should == -1 + Rational(3.12).div(0.5).should == 6 + end + + it "raises a ZeroDivisionError when the argument is 0.0" do + lambda { Rational(3, 4).div(0.0) }.should raise_error(ZeroDivisionError) + end +end + +describe :rational_div_int, shared: true do + it "performs integer division and returns the result" do + Rational(2, 1).div(1).should == 2 + Rational(25, 5).div(-50).should == -1 + end + + it "raises a ZeroDivisionError when the argument is 0" do + lambda { Rational(3, 4).div(0) }.should raise_error(ZeroDivisionError) + end +end + +describe :rational_div, shared: true do + it "returns an Integer" do + Rational(229, 21).div(82).should be_kind_of(Integer) + end + + it "raises an ArgumentError if passed more than one argument" do + lambda { Rational(3, 4).div(2,3) }.should raise_error(ArgumentError) + end + + # See http://redmine.ruby-lang.org/issues/show/1648 + it "raises a TypeError if passed a non-numeric argument" do + lambda { Rational(3, 4).div([]) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/shared/rational/divide.rb b/spec/ruby/shared/rational/divide.rb new file mode 100644 index 0000000000..86d30c39d8 --- /dev/null +++ b/spec/ruby/shared/rational/divide.rb @@ -0,0 +1,71 @@ +require_relative '../../spec_helper' + +describe :rational_divide_rat, shared: true do + it "returns self divided by other as a Rational" do + Rational(3, 4).send(@method, Rational(3, 4)).should eql(Rational(1, 1)) + Rational(2, 4).send(@method, Rational(1, 4)).should eql(Rational(2, 1)) + + Rational(2, 4).send(@method, 2).should == Rational(1, 4) + Rational(6, 7).send(@method, -2).should == Rational(-3, 7) + end + + it "raises a ZeroDivisionError when passed a Rational with a numerator of 0" do + lambda { Rational(3, 4).send(@method, Rational(0, 1)) }.should raise_error(ZeroDivisionError) + end +end + +describe :rational_divide_int, shared: true do + it "returns self divided by other as a Rational" do + Rational(3, 4).send(@method, 2).should eql(Rational(3, 8)) + Rational(2, 4).send(@method, 2).should eql(Rational(1, 4)) + Rational(6, 7).send(@method, -2).should eql(Rational(-3, 7)) + end + + it "raises a ZeroDivisionError when passed 0" do + lambda { Rational(3, 4).send(@method, 0) }.should raise_error(ZeroDivisionError) + end +end + +describe :rational_divide_float, shared: true do + it "returns self divided by other as a Float" do + Rational(3, 4).send(@method, 0.75).should eql(1.0) + Rational(3, 4).send(@method, 0.25).should eql(3.0) + Rational(3, 4).send(@method, 0.3).should eql(2.5) + + Rational(-3, 4).send(@method, 0.3).should eql(-2.5) + Rational(3, -4).send(@method, 0.3).should eql(-2.5) + Rational(3, 4).send(@method, -0.3).should eql(-2.5) + end + + it "returns infinity when passed 0" do + Rational(3, 4).send(@method, 0.0).infinite?.should eql(1) + Rational(-3, -4).send(@method, 0.0).infinite?.should eql(1) + + Rational(-3, 4).send(@method, 0.0).infinite?.should eql(-1) + Rational(3, -4).send(@method, 0.0).infinite?.should eql(-1) + end +end + +describe :rational_divide, shared: true do + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational.send(@method, obj) + end + + it "calls #/ on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:/).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + rational.send(@method, obj).should == :result + end +end diff --git a/spec/ruby/shared/rational/divmod.rb b/spec/ruby/shared/rational/divmod.rb new file mode 100644 index 0000000000..9433674007 --- /dev/null +++ b/spec/ruby/shared/rational/divmod.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' + +describe :rational_divmod_rat, shared: true do + it "returns the quotient as Integer and the remainder as Rational" do + Rational(7, 4).divmod(Rational(1, 2)).should eql([3, Rational(1, 4)]) + Rational(7, 4).divmod(Rational(-1, 2)).should eql([-4, Rational(-1, 4)]) + Rational(0, 4).divmod(Rational(4, 3)).should eql([0, Rational(0, 1)]) + + Rational(bignum_value, 4).divmod(Rational(4, 3)).should eql([1729382256910270464, Rational(0, 1)]) + end + + it "raises a ZeroDivisonError when passed a Rational with a numerator of 0" do + lambda { Rational(7, 4).divmod(Rational(0, 3)) }.should raise_error(ZeroDivisionError) + end +end + +describe :rational_divmod_int, shared: true do + it "returns the quotient as Integer and the remainder as Rational" do + Rational(7, 4).divmod(2).should eql([0, Rational(7, 4)]) + Rational(7, 4).divmod(-2).should eql([-1, Rational(-1, 4)]) + + Rational(bignum_value, 4).divmod(3).should == [768614336404564650, Rational(2, 1)] + end + + it "raises a ZeroDivisionError when passed 0" do + lambda { Rational(7, 4).divmod(0) }.should raise_error(ZeroDivisionError) + end +end + +describe :rational_divmod_float, shared: true do + it "returns the quotient as Integer and the remainder as Float" do + Rational(7, 4).divmod(0.5).should eql([3, 0.25]) + end + + it "returns the quotient as Integer and the remainder as Float" do + Rational(7, 4).divmod(-0.5).should eql([-4, -0.25]) + end + + it "raises a ZeroDivisionError when passed 0" do + lambda { Rational(7, 4).divmod(0.0) }.should raise_error(ZeroDivisionError) + end +end diff --git a/spec/ruby/shared/rational/equal_value.rb b/spec/ruby/shared/rational/equal_value.rb new file mode 100644 index 0000000000..b2e7e09415 --- /dev/null +++ b/spec/ruby/shared/rational/equal_value.rb @@ -0,0 +1,39 @@ +require_relative '../../spec_helper' + +describe :rational_equal_value_rat, shared: true do + it "returns true if self has the same numerator and denominator as the passed argument" do + (Rational(3, 4) == Rational(3, 4)).should be_true + (Rational(-3, -4) == Rational(3, 4)).should be_true + (Rational(-4, 5) == Rational(4, -5)).should be_true + + (Rational(bignum_value, 3) == Rational(bignum_value, 3)).should be_true + (Rational(-bignum_value, 3) == Rational(bignum_value, -3)).should be_true + end +end + +describe :rational_equal_value_int, shared: true do + it "returns true if self has the passed argument as numerator and a denominator of 1" do + # Rational(x, y) reduces x and y automatically + (Rational(4, 2) == 2).should be_true + (Rational(-4, 2) == -2).should be_true + (Rational(4, -2) == -2).should be_true + end +end + +describe :rational_equal_value_float, shared: true do + it "converts self to a Float and compares it with the passed argument" do + (Rational(3, 4) == 0.75).should be_true + (Rational(4, 2) == 2.0).should be_true + (Rational(-4, 2) == -2.0).should be_true + (Rational(4, -2) == -2.0).should be_true + end +end + +describe :rational_equal_value, shared: true do + it "returns the result of calling #== with self on the passed argument" do + obj = mock("Object") + obj.should_receive(:==).and_return(:result) + + (Rational(3, 4) == obj).should_not be_false + end +end diff --git a/spec/ruby/shared/rational/exponent.rb b/spec/ruby/shared/rational/exponent.rb new file mode 100644 index 0000000000..ac8237c291 --- /dev/null +++ b/spec/ruby/shared/rational/exponent.rb @@ -0,0 +1,178 @@ +require_relative '../../spec_helper' + +describe :rational_exponent, shared: true do + describe "when passed Rational" do + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + it "returns Rational(1) if the exponent is Rational(0)" do + (Rational(0) ** Rational(0)).should eql(Rational(1)) + (Rational(1) ** Rational(0)).should eql(Rational(1)) + (Rational(3, 4) ** Rational(0)).should eql(Rational(1)) + (Rational(-1) ** Rational(0)).should eql(Rational(1)) + (Rational(-3, 4) ** Rational(0)).should eql(Rational(1)) + (Rational(bignum_value) ** Rational(0)).should eql(Rational(1)) + (Rational(-bignum_value) ** Rational(0)).should eql(Rational(1)) + end + + it "returns self raised to the argument as a Rational if the exponent's denominator is 1" do + (Rational(3, 4) ** Rational(1, 1)).should eql(Rational(3, 4)) + (Rational(3, 4) ** Rational(2, 1)).should eql(Rational(9, 16)) + (Rational(3, 4) ** Rational(-1, 1)).should eql(Rational(4, 3)) + (Rational(3, 4) ** Rational(-2, 1)).should eql(Rational(16, 9)) + end + + it "returns self raised to the argument as a Float if the exponent's denominator is not 1" do + (Rational(3, 4) ** Rational(4, 3)).should be_close(0.681420222312052, TOLERANCE) + (Rational(3, 4) ** Rational(-4, 3)).should be_close(1.46752322173095, TOLERANCE) + (Rational(3, 4) ** Rational(4, -3)).should be_close(1.46752322173095, TOLERANCE) + end + + it "returns a complex number when self is negative and the passed argument is not 0" do + (Rational(-3, 4) ** Rational(-4, 3)).should be_close(Complex(-0.7337616108654732, 1.2709123906625817), TOLERANCE) + end + end + end + + describe "when passed Integer" do + it "returns the Rational value of self raised to the passed argument" do + (Rational(3, 4) ** 4).should == Rational(81, 256) + (Rational(3, 4) ** -4).should == Rational(256, 81) + (Rational(-3, 4) ** -4).should == Rational(256, 81) + (Rational(3, -4) ** -4).should == Rational(256, 81) + + (Rational(bignum_value, 4) ** 4).should == Rational(28269553036454149273332760011886696253239742350009903329945699220681916416, 1) + (Rational(3, bignum_value) ** -4).should == Rational(7237005577332262213973186563042994240829374041602535252466099000494570602496, 81) + (Rational(-bignum_value, 4) ** -4).should == Rational(1, 28269553036454149273332760011886696253239742350009903329945699220681916416) + (Rational(3, -bignum_value) ** -4).should == Rational(7237005577332262213973186563042994240829374041602535252466099000494570602496, 81) + end + + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + it "returns Rational(1, 1) when the passed argument is 0" do + (Rational(3, 4) ** 0).should eql(Rational(1, 1)) + (Rational(-3, 4) ** 0).should eql(Rational(1, 1)) + (Rational(3, -4) ** 0).should eql(Rational(1, 1)) + + (Rational(bignum_value, 4) ** 0).should eql(Rational(1, 1)) + (Rational(3, -bignum_value) ** 0).should eql(Rational(1, 1)) + end + end + end + + describe "when passed Bignum" do + # #5713 + it "returns Rational(0) when self is Rational(0) and the exponent is positive" do + (Rational(0) ** bignum_value).should eql(Rational(0)) + end + + it "raises ZeroDivisionError when self is Rational(0) and the exponent is negative" do + lambda { Rational(0) ** -bignum_value }.should raise_error(ZeroDivisionError) + end + + it "returns Rational(1) when self is Rational(1)" do + (Rational(1) ** bignum_value).should eql(Rational(1)) + (Rational(1) ** -bignum_value).should eql(Rational(1)) + end + + it "returns Rational(1) when self is Rational(-1) and the exponent is positive and even" do + (Rational(-1) ** bignum_value(0)).should eql(Rational(1)) + (Rational(-1) ** bignum_value(2)).should eql(Rational(1)) + end + + it "returns Rational(-1) when self is Rational(-1) and the exponent is positive and odd" do + (Rational(-1) ** bignum_value(1)).should eql(Rational(-1)) + (Rational(-1) ** bignum_value(3)).should eql(Rational(-1)) + end + + it "returns positive Infinity when self is > 1" do + (Rational(2) ** bignum_value).infinite?.should == 1 + (Rational(fixnum_max) ** bignum_value).infinite?.should == 1 + end + + it "returns 0.0 when self is > 1 and the exponent is negative" do + (Rational(2) ** -bignum_value).should eql(0.0) + (Rational(fixnum_max) ** -bignum_value).should eql(0.0) + end + + # Fails on linux due to pow() bugs in glibc: http://sources.redhat.com/bugzilla/show_bug.cgi?id=3866 + platform_is_not :linux do + it "returns positive Infinity when self < -1" do + (Rational(-2) ** bignum_value).infinite?.should == 1 + (Rational(-2) ** (bignum_value + 1)).infinite?.should == 1 + (Rational(fixnum_min) ** bignum_value).infinite?.should == 1 + end + + it "returns 0.0 when self is < -1 and the exponent is negative" do + (Rational(-2) ** -bignum_value).should eql(0.0) + (Rational(fixnum_min) ** -bignum_value).should eql(0.0) + end + end + end + + describe "when passed Float" do + it "returns self converted to Float and raised to the passed argument" do + (Rational(3, 1) ** 3.0).should eql(27.0) + (Rational(3, 1) ** 1.5).should be_close(5.19615242270663, TOLERANCE) + (Rational(3, 1) ** -1.5).should be_close(0.192450089729875, TOLERANCE) + end + + it "returns a complex number if self is negative and the passed argument is not 0" do + (Rational(-3, 2) ** 1.5).should be_close(Complex(0.0, -1.8371173070873836), TOLERANCE) + (Rational(3, -2) ** 1.5).should be_close(Complex(0.0, -1.8371173070873836), TOLERANCE) + (Rational(3, -2) ** -1.5).should be_close(Complex(0.0, 0.5443310539518174), TOLERANCE) + end + + it "returns Complex(1.0) when the passed argument is 0.0" do + (Rational(3, 4) ** 0.0).should == Complex(1.0) + (Rational(-3, 4) ** 0.0).should == Complex(1.0) + (Rational(-3, 4) ** 0.0).should == Complex(1.0) + end + end + + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational ** obj + end + + it "calls #** on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:**).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + (rational ** obj).should == :result + end + + it "raises ZeroDivisionError for Rational(0, 1) passed a negative Integer" do + [-1, -4, -9999].each do |exponent| + lambda { Rational(0, 1) ** exponent }.should raise_error(ZeroDivisionError, "divided by 0") + end + end + + it "raises ZeroDivisionError for Rational(0, 1) passed a negative Rational with denominator 1" do + [Rational(-1, 1), Rational(-3, 1)].each do |exponent| + lambda { Rational(0, 1) ** exponent }.should raise_error(ZeroDivisionError, "divided by 0") + end + end + + # #7513 + it "raises ZeroDivisionError for Rational(0, 1) passed a negative Rational" do + lambda { Rational(0, 1) ** Rational(-3, 2) }.should raise_error(ZeroDivisionError, "divided by 0") + end + + platform_is_not :solaris do # See https://github.com/ruby/spec/issues/134 + it "returns Infinity for Rational(0, 1) passed a negative Float" do + [-1.0, -3.0, -3.14].each do |exponent| + (Rational(0, 1) ** exponent).infinite?.should == 1 + end + end + end +end diff --git a/spec/ruby/shared/rational/fdiv.rb b/spec/ruby/shared/rational/fdiv.rb new file mode 100644 index 0000000000..6911ade8ac --- /dev/null +++ b/spec/ruby/shared/rational/fdiv.rb @@ -0,0 +1,5 @@ +require_relative '../../spec_helper' + +describe :rational_fdiv, shared: true do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/shared/rational/floor.rb b/spec/ruby/shared/rational/floor.rb new file mode 100644 index 0000000000..ddf7fdbd17 --- /dev/null +++ b/spec/ruby/shared/rational/floor.rb @@ -0,0 +1,45 @@ +require_relative '../../spec_helper' + +describe :rational_floor, shared: true do + before do + @rational = Rational(2200, 7) + end + + describe "with no arguments (precision = 0)" do + it "returns an integer" do + @rational.floor.should be_kind_of(Integer) + end + + it "returns the truncated value toward negative infinity" do + @rational.floor.should == 314 + Rational(1, 2).floor.should == 0 + Rational(-1, 2).floor.should == -1 + end + end + + describe "with a precision < 0" do + it "returns an integer" do + @rational.floor(-2).should be_kind_of(Integer) + @rational.floor(-1).should be_kind_of(Integer) + end + + it "moves the truncation point n decimal places left" do + @rational.floor(-3).should == 0 + @rational.floor(-2).should == 300 + @rational.floor(-1).should == 310 + end + end + + describe "with a precision > 0" do + it "returns a Rational" do + @rational.floor(1).should be_kind_of(Rational) + @rational.floor(2).should be_kind_of(Rational) + end + + it "moves the truncation point n decimal places right" do + @rational.floor(1).should == Rational(1571, 5) + @rational.floor(2).should == Rational(7857, 25) + @rational.floor(3).should == Rational(62857, 200) + end + end +end diff --git a/spec/ruby/shared/rational/hash.rb b/spec/ruby/shared/rational/hash.rb new file mode 100644 index 0000000000..50f21cec20 --- /dev/null +++ b/spec/ruby/shared/rational/hash.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' + +describe :rational_hash, shared: true do + # BUG: Rational(2, 3).hash == Rational(3, 2).hash + it "is static" do + Rational(2, 3).hash.should == Rational(2, 3).hash + Rational(2, 4).hash.should_not == Rational(2, 3).hash + end +end diff --git a/spec/ruby/shared/rational/inspect.rb b/spec/ruby/shared/rational/inspect.rb new file mode 100644 index 0000000000..19691a2f25 --- /dev/null +++ b/spec/ruby/shared/rational/inspect.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' + +describe :rational_inspect, shared: true do + it "returns a string representation of self" do + Rational(3, 4).inspect.should == "(3/4)" + Rational(-5, 8).inspect.should == "(-5/8)" + Rational(-1, -2).inspect.should == "(1/2)" + + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + Rational(bignum_value, 1).inspect.should == "(#{bignum_value}/1)" + end + end +end diff --git a/spec/ruby/shared/rational/marshal_dump.rb b/spec/ruby/shared/rational/marshal_dump.rb new file mode 100644 index 0000000000..09782b45a5 --- /dev/null +++ b/spec/ruby/shared/rational/marshal_dump.rb @@ -0,0 +1,5 @@ +require_relative '../../spec_helper' + +describe :rational_marshal_dump, shared: true do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/shared/rational/marshal_load.rb b/spec/ruby/shared/rational/marshal_load.rb new file mode 100644 index 0000000000..20bdd6fdf4 --- /dev/null +++ b/spec/ruby/shared/rational/marshal_load.rb @@ -0,0 +1,5 @@ +require_relative '../../spec_helper' + +describe :rational_marshal_load, shared: true do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/shared/rational/minus.rb b/spec/ruby/shared/rational/minus.rb new file mode 100644 index 0000000000..0a0946fdb9 --- /dev/null +++ b/spec/ruby/shared/rational/minus.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' + +describe :rational_minus_rat, shared: true do + it "returns the result of subtracting other from self as a Rational" do + (Rational(3, 4) - Rational(0, 1)).should eql(Rational(3, 4)) + (Rational(3, 4) - Rational(1, 4)).should eql(Rational(1, 2)) + + (Rational(3, 4) - Rational(2, 1)).should eql(Rational(-5, 4)) + end +end + +describe :rational_minus_int, shared: true do + it "returns the result of subtracting other from self as a Rational" do + (Rational(3, 4) - 1).should eql(Rational(-1, 4)) + (Rational(3, 4) - 2).should eql(Rational(-5, 4)) + end +end + +describe :rational_minus_float, shared: true do + it "returns the result of subtracting other from self as a Float" do + (Rational(3, 4) - 0.2).should eql(0.55) + (Rational(3, 4) - 2.5).should eql(-1.75) + end +end + +describe :rational_minus, shared: true do + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational - obj + end + + it "calls #- on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:-).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + (rational - obj).should == :result + end +end diff --git a/spec/ruby/shared/rational/modulo.rb b/spec/ruby/shared/rational/modulo.rb new file mode 100644 index 0000000000..39abaed5fe --- /dev/null +++ b/spec/ruby/shared/rational/modulo.rb @@ -0,0 +1,43 @@ +require_relative '../../spec_helper' + +describe :rational_modulo, shared: true do + it "returns the remainder when this value is divided by other" do + Rational(2, 3).send(@method, Rational(2, 3)).should == Rational(0, 1) + Rational(4, 3).send(@method, Rational(2, 3)).should == Rational(0, 1) + Rational(2, -3).send(@method, Rational(-2, 3)).should == Rational(0, 1) + Rational(0, -1).send(@method, -1).should == Rational(0, 1) + + Rational(7, 4).send(@method, Rational(1, 2)).should == Rational(1, 4) + Rational(7, 4).send(@method, 1).should == Rational(3, 4) + Rational(7, 4).send(@method, Rational(1, 7)).should == Rational(1, 28) + + Rational(3, 4).send(@method, -1).should == Rational(-1, 4) + Rational(1, -5).send(@method, -1).should == Rational(-1, 5) + end + + it "returns a Float value when the argument is Float" do + Rational(7, 4).send(@method, 1.0).should be_kind_of(Float) + Rational(7, 4).send(@method, 1.0).should == 0.75 + Rational(7, 4).send(@method, 0.26).should be_close(0.19, 0.0001) + end + + it "raises ZeroDivisionError on zero denominator" do + lambda { + Rational(3, 5).send(@method, Rational(0, 1)) + }.should raise_error(ZeroDivisionError) + + lambda { + Rational(0, 1).send(@method, Rational(0, 1)) + }.should raise_error(ZeroDivisionError) + + lambda { + Rational(3, 5).send(@method, 0) + }.should raise_error(ZeroDivisionError) + end + + it "raises a ZeroDivisionError when the argument is 0.0" do + lambda { + Rational(3, 5).send(@method, 0.0) + }.should raise_error(ZeroDivisionError) + end +end diff --git a/spec/ruby/shared/rational/multiply.rb b/spec/ruby/shared/rational/multiply.rb new file mode 100644 index 0000000000..9c861cf79d --- /dev/null +++ b/spec/ruby/shared/rational/multiply.rb @@ -0,0 +1,62 @@ +require_relative '../../spec_helper' + +describe :rational_multiply_rat, shared: true do + it "returns self divided by other as a Rational" do + (Rational(3, 4) * Rational(3, 4)).should eql(Rational(9, 16)) + (Rational(2, 4) * Rational(1, 4)).should eql(Rational(1, 8)) + + (Rational(3, 4) * Rational(0, 1)).should eql(Rational(0, 4)) + end +end + +describe :rational_multiply_int, shared: true do + it "returns self divided by other as a Rational" do + (Rational(3, 4) * 2).should eql(Rational(3, 2)) + (Rational(2, 4) * 2).should eql(Rational(1, 1)) + (Rational(6, 7) * -2).should eql(Rational(-12, 7)) + + (Rational(3, 4) * 0).should eql(Rational(0, 4)) + end +end + +describe :rational_multiply_float, shared: true do + it "returns self divided by other as a Float" do + (Rational(3, 4) * 0.75).should eql(0.5625) + (Rational(3, 4) * 0.25).should eql(0.1875) + (Rational(3, 4) * 0.3).should be_close(0.225, TOLERANCE) + + (Rational(-3, 4) * 0.3).should be_close(-0.225, TOLERANCE) + (Rational(3, -4) * 0.3).should be_close(-0.225, TOLERANCE) + (Rational(3, 4) * -0.3).should be_close(-0.225, TOLERANCE) + + (Rational(3, 4) * 0.0).should eql(0.0) + (Rational(-3, -4) * 0.0).should eql(0.0) + + (Rational(-3, 4) * 0.0).should eql(0.0) + (Rational(3, -4) * 0.0).should eql(0.0) + end +end + +describe :rational_multiply, shared: true do + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational * obj + end + + it "calls #* on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:*).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + (rational * obj).should == :result + end +end diff --git a/spec/ruby/shared/rational/numerator.rb b/spec/ruby/shared/rational/numerator.rb new file mode 100644 index 0000000000..50d768168c --- /dev/null +++ b/spec/ruby/shared/rational/numerator.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +describe :rational_numerator, shared: true do + it "returns the numerator" do + Rational(3, 4).numerator.should equal(3) + Rational(3, -4).numerator.should equal(-3) + + Rational(bignum_value, 1).numerator.should == bignum_value + end +end diff --git a/spec/ruby/shared/rational/plus.rb b/spec/ruby/shared/rational/plus.rb new file mode 100644 index 0000000000..b126360ee4 --- /dev/null +++ b/spec/ruby/shared/rational/plus.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' + +describe :rational_plus_rat, shared: true do + it "returns the result of subtracting other from self as a Rational" do + (Rational(3, 4) + Rational(0, 1)).should eql(Rational(3, 4)) + (Rational(3, 4) + Rational(1, 4)).should eql(Rational(1, 1)) + + (Rational(3, 4) + Rational(2, 1)).should eql(Rational(11, 4)) + end +end + +describe :rational_plus_int, shared: true do + it "returns the result of subtracting other from self as a Rational" do + (Rational(3, 4) + 1).should eql(Rational(7, 4)) + (Rational(3, 4) + 2).should eql(Rational(11, 4)) + end +end + +describe :rational_plus_float, shared: true do + it "returns the result of subtracting other from self as a Float" do + (Rational(3, 4) + 0.2).should eql(0.95) + (Rational(3, 4) + 2.5).should eql(3.25) + end +end + +describe :rational_plus, shared: true do + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational + obj + end + + it "calls #+ on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:+).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + (rational + obj).should == :result + end +end diff --git a/spec/ruby/shared/rational/quo.rb b/spec/ruby/shared/rational/quo.rb new file mode 100644 index 0000000000..53b32fed2f --- /dev/null +++ b/spec/ruby/shared/rational/quo.rb @@ -0,0 +1,5 @@ +require_relative '../../spec_helper' + +describe :rational_quo, shared: true do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/shared/rational/remainder.rb b/spec/ruby/shared/rational/remainder.rb new file mode 100644 index 0000000000..dd907608db --- /dev/null +++ b/spec/ruby/shared/rational/remainder.rb @@ -0,0 +1,5 @@ +require_relative '../../spec_helper' + +describe :rational_remainder, shared: true do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/shared/rational/round.rb b/spec/ruby/shared/rational/round.rb new file mode 100644 index 0000000000..36ed476350 --- /dev/null +++ b/spec/ruby/shared/rational/round.rb @@ -0,0 +1,99 @@ +require_relative '../../spec_helper' + +describe :rational_round, shared: true do + before do + @rational = Rational(2200, 7) + end + + describe "with no arguments (precision = 0)" do + it "returns an integer" do + @rational.round.should be_kind_of(Integer) + Rational(0, 1).round(0).should be_kind_of(Integer) + Rational(124, 1).round(0).should be_kind_of(Integer) + end + + it "returns the truncated value toward the nearest integer" do + @rational.round.should == 314 + Rational(0, 1).round(0).should == 0 + Rational(2, 1).round(0).should == 2 + end + + it "returns the rounded value toward the nearest integer" do + Rational(1, 2).round.should == 1 + Rational(-1, 2).round.should == -1 + Rational(3, 2).round.should == 2 + Rational(-3, 2).round.should == -2 + Rational(5, 2).round.should == 3 + Rational(-5, 2).round.should == -3 + end + end + + describe "with a precision < 0" do + it "returns an integer" do + @rational.round(-2).should be_kind_of(Integer) + @rational.round(-1).should be_kind_of(Integer) + Rational(0, 1).round(-1).should be_kind_of(Integer) + Rational(2, 1).round(-1).should be_kind_of(Integer) + end + + it "moves the truncation point n decimal places left" do + @rational.round(-3).should == 0 + @rational.round(-2).should == 300 + @rational.round(-1).should == 310 + end + end + + describe "with a precision > 0" do + it "returns a Rational" do + @rational.round(1).should be_kind_of(Rational) + @rational.round(2).should be_kind_of(Rational) + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + Rational(0, 1).round(1).should be_kind_of(Rational) + Rational(2, 1).round(1).should be_kind_of(Rational) + end + end + + it "moves the truncation point n decimal places right" do + @rational.round(1).should == Rational(3143, 10) + @rational.round(2).should == Rational(31429, 100) + @rational.round(3).should == Rational(157143, 500) + Rational(0, 1).round(1).should == Rational(0, 1) + Rational(2, 1).round(1).should == Rational(2, 1) + end + + it "doesn't alter the value if the precision is too great" do + Rational(3, 2).round(10).should == Rational(3, 2).round(20) + end + + # #6605 + it "doesn't fail when rounding to an absurdly large positive precision" do + Rational(3, 2).round(2_097_171).should == Rational(3, 2) + end + end + + ruby_version_is "2.4" do + describe "with half option" do + it "returns an Integer when precision is not passed" do + Rational(10, 4).round(half: :up).should == 3 + Rational(10, 4).round(half: :down).should == 2 + Rational(10, 4).round(half: :even).should == 2 + Rational(-10, 4).round(half: :up).should == -3 + Rational(-10, 4).round(half: :down).should == -2 + Rational(-10, 4).round(half: :even).should == -2 + end + + it "returns a Rational when the precision is greater than 0" do + Rational(25, 100).round(1, half: :up).should == Rational(3, 10) + Rational(25, 100).round(1, half: :down).should == Rational(1, 5) + Rational(25, 100).round(1, half: :even).should == Rational(1, 5) + Rational(35, 100).round(1, half: :up).should == Rational(2, 5) + Rational(35, 100).round(1, half: :down).should == Rational(3, 10) + Rational(35, 100).round(1, half: :even).should == Rational(2, 5) + Rational(-25, 100).round(1, half: :up).should == Rational(-3, 10) + Rational(-25, 100).round(1, half: :down).should == Rational(-1, 5) + Rational(-25, 100).round(1, half: :even).should == Rational(-1, 5) + end + end + end +end diff --git a/spec/ruby/shared/rational/to_f.rb b/spec/ruby/shared/rational/to_f.rb new file mode 100644 index 0000000000..56e0b61d68 --- /dev/null +++ b/spec/ruby/shared/rational/to_f.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +describe :rational_to_f, shared: true do + it "returns self converted to a Float" do + Rational(3, 4).to_f.should eql(0.75) + Rational(3, -4).to_f.should eql(-0.75) + Rational(-1, 4).to_f.should eql(-0.25) + Rational(-1, -4).to_f.should eql(0.25) + end +end diff --git a/spec/ruby/shared/rational/to_i.rb b/spec/ruby/shared/rational/to_i.rb new file mode 100644 index 0000000000..9be1183aa4 --- /dev/null +++ b/spec/ruby/shared/rational/to_i.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' + +describe :rational_to_i, shared: true do + it "converts self to an Integer by truncation" do + Rational(7, 4).to_i.should eql(1) + Rational(11, 4).to_i.should eql(2) + end + + it "converts self to an Integer by truncation" do + Rational(-7, 4).to_i.should eql(-1) + end +end diff --git a/spec/ruby/shared/rational/to_r.rb b/spec/ruby/shared/rational/to_r.rb new file mode 100644 index 0000000000..372c086850 --- /dev/null +++ b/spec/ruby/shared/rational/to_r.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' + +describe :rational_to_r, shared: true do + it "returns self" do + a = Rational(3, 4) + a.to_r.should equal(a) + + a = Rational(bignum_value, 4) + a.to_r.should equal(a) + end +end diff --git a/spec/ruby/shared/rational/to_s.rb b/spec/ruby/shared/rational/to_s.rb new file mode 100644 index 0000000000..e90c6e5e39 --- /dev/null +++ b/spec/ruby/shared/rational/to_s.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' + +describe :rational_to_s, shared: true do + it "returns a string representation of self" do + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + Rational(1, 1).to_s.should == "1/1" + Rational(2, 1).to_s.should == "2/1" + end + Rational(1, 2).to_s.should == "1/2" + Rational(-1, 3).to_s.should == "-1/3" + Rational(1, -3).to_s.should == "-1/3" + end +end diff --git a/spec/ruby/shared/rational/truncate.rb b/spec/ruby/shared/rational/truncate.rb new file mode 100644 index 0000000000..761dd3113a --- /dev/null +++ b/spec/ruby/shared/rational/truncate.rb @@ -0,0 +1,45 @@ +require_relative '../../spec_helper' + +describe :rational_truncate, shared: true do + before do + @rational = Rational(2200, 7) + end + + describe "with no arguments (precision = 0)" do + it "returns an integer" do + @rational.truncate.should be_kind_of(Integer) + end + + it "returns the truncated value toward 0" do + @rational.truncate.should == 314 + Rational(1, 2).truncate.should == 0 + Rational(-1, 2).truncate.should == 0 + end + end + + describe "with a precision < 0" do + it "returns an integer" do + @rational.truncate(-2).should be_kind_of(Integer) + @rational.truncate(-1).should be_kind_of(Integer) + end + + it "moves the truncation point n decimal places left" do + @rational.truncate(-3).should == 0 + @rational.truncate(-2).should == 300 + @rational.truncate(-1).should == 310 + end + end + + describe "with a precision > 0" do + it "returns a Rational" do + @rational.truncate(1).should be_kind_of(Rational) + @rational.truncate(2).should be_kind_of(Rational) + end + + it "moves the truncation point n decimal places right" do + @rational.truncate(1).should == Rational(1571, 5) + @rational.truncate(2).should == Rational(7857, 25) + @rational.truncate(3).should == Rational(62857, 200) + end + end +end diff --git a/spec/ruby/shared/sizedqueue/enque.rb b/spec/ruby/shared/sizedqueue/enque.rb new file mode 100644 index 0000000000..b724101269 --- /dev/null +++ b/spec/ruby/shared/sizedqueue/enque.rb @@ -0,0 +1,50 @@ +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 = lambda { 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 { + lambda { q.send(@method, 2) }.should raise_error(ClosedQueueError) + } + + Thread.pass until q.num_waiting == 1 + + q.close + + t.join + q.pop.should == 1 + end +end diff --git a/spec/ruby/shared/sizedqueue/max.rb b/spec/ruby/shared/sizedqueue/max.rb new file mode 100644 index 0000000000..cd4b47f1c1 --- /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) + lambda { q.max = "foo" }.should raise_error(TypeError) + lambda { 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 + lambda { 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 + lambda { 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..4439f2a9c6 --- /dev/null +++ b/spec/ruby/shared/sizedqueue/new.rb @@ -0,0 +1,18 @@ +describe :sizedqueue_new, shared: true do + it "raises a TypeError when the given argument is not Numeric" do + lambda { @object.call("foo") }.should raise_error(TypeError) + lambda { @object.call(Object.new) }.should raise_error(TypeError) + end + + it "raises an argument error when no argument is given" do + lambda { @object.call }.should raise_error(ArgumentError) + end + + it "raises an argument error when the given argument is zero" do + lambda { @object.call(0) }.should raise_error(ArgumentError) + end + + it "raises an argument error when the given argument is negative" do + lambda { @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/times.rb b/spec/ruby/shared/string/times.rb new file mode 100644 index 0000000000..c44a76c9b7 --- /dev/null +++ b/spec/ruby/shared/string/times.rb @@ -0,0 +1,64 @@ +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 + lambda { @object.call("cool", -3) }.should raise_error(ArgumentError) + lambda { @object.call("cool", -3.14) }.should raise_error(ArgumentError) + end + + it "raises a RangeError when given integer is a Bignum" do + lambda { @object.call("cool", 999999999999999999999) }.should raise_error(RangeError) + end + + it "returns subclass instances" do + @object.call(MyString.new("cool"), 0).should be_an_instance_of(MyString) + @object.call(MyString.new("cool"), 1).should be_an_instance_of(MyString) + @object.call(MyString.new("cool"), 2).should be_an_instance_of(MyString) + end + + it "always taints the result when self is tainted" do + ["", "OK", MyString.new(""), MyString.new("OK")].each do |str| + str.taint + + [0, 1, 2].each do |arg| + @object.call(str, arg).tainted?.should == true + end + end + end + + with_feature :encoding do + it "returns a String in the same encoding as self" do + str = "\xE3\x81\x82".force_encoding Encoding::UTF_8 + result = @object.call(str, 2) + result.encoding.should equal(Encoding::UTF_8) + end + end + + platform_is wordsize: 32 do + it "raises an ArgumentError if the length of the resulting string doesn't fit into a long" do + lambda { @object.call("abc", (2 ** 31) - 1) }.should raise_error(ArgumentError) + end + end + + platform_is wordsize: 64 do + it "raises an ArgumentError if the length of the resulting string doesn't fit into a long" do + lambda { @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..f126c5a323 --- /dev/null +++ b/spec/ruby/shared/time/strftime_for_date.rb @@ -0,0 +1,275 @@ +# 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 + + with_feature :encoding do + 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 +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..49cd09051a --- /dev/null +++ b/spec/ruby/shared/time/strftime_for_time.rb @@ -0,0 +1,173 @@ +# 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 +end |
