diff --git a/core/enumerable/collect_concat_spec.rb b/core/enumerable/collect_concat_spec.rb index 59317cfe3..5024aadda 100644 --- a/core/enumerable/collect_concat_spec.rb +++ b/core/enumerable/collect_concat_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/collect_concat' describe "Enumerable#collect_concat" do - it_behaves_like :enumerable_collect_concat, :collect_concat + it "is an alias of Enumerable#flat_map" do + Enumerable.instance_method(:collect_concat).should == Enumerable.instance_method(:flat_map) + end end diff --git a/core/enumerable/collect_spec.rb b/core/enumerable/collect_spec.rb index cfa2895cc..319b1b263 100644 --- a/core/enumerable/collect_spec.rb +++ b/core/enumerable/collect_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/collect' describe "Enumerable#collect" do - it_behaves_like :enumerable_collect, :collect + it "is an alias of Enumerable#map" do + Enumerable.instance_method(:collect).should == Enumerable.instance_method(:map) + end end diff --git a/core/enumerable/detect_spec.rb b/core/enumerable/detect_spec.rb index 6959aadc4..0669c50c5 100644 --- a/core/enumerable/detect_spec.rb +++ b/core/enumerable/detect_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/find' describe "Enumerable#detect" do - it_behaves_like :enumerable_find, :detect + it "is an alias of Enumerable#find" do + Enumerable.instance_method(:detect).should == Enumerable.instance_method(:find) + end end diff --git a/core/enumerable/entries_spec.rb b/core/enumerable/entries_spec.rb index 2de4fc756..8cb29b7b4 100644 --- a/core/enumerable/entries_spec.rb +++ b/core/enumerable/entries_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/entries' describe "Enumerable#entries" do - it_behaves_like :enumerable_entries, :entries + it "is an alias of Enumerable#to_a" do + Enumerable.instance_method(:entries).should == Enumerable.instance_method(:to_a) + end end diff --git a/core/enumerable/filter_spec.rb b/core/enumerable/filter_spec.rb index 1c3a7e9ff..d075b3939 100644 --- a/core/enumerable/filter_spec.rb +++ b/core/enumerable/filter_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/find_all' describe "Enumerable#filter" do - it_behaves_like :enumerable_find_all, :filter + it "is an alias of Enumerable#select" do + Enumerable.instance_method(:filter).should == Enumerable.instance_method(:select) + end end diff --git a/core/enumerable/find_all_spec.rb b/core/enumerable/find_all_spec.rb index 9cd635f24..1095a1956 100644 --- a/core/enumerable/find_all_spec.rb +++ b/core/enumerable/find_all_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/find_all' describe "Enumerable#find_all" do - it_behaves_like :enumerable_find_all, :find_all + it "is an alias of Enumerable#select" do + Enumerable.instance_method(:find_all).should == Enumerable.instance_method(:select) + end end diff --git a/core/enumerable/find_spec.rb b/core/enumerable/find_spec.rb index 5ddebc05f..4ac4b75c4 100644 --- a/core/enumerable/find_spec.rb +++ b/core/enumerable/find_spec.rb @@ -1,7 +1,78 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/find' +require_relative 'shared/enumerable_enumeratorized' describe "Enumerable#find" do - it_behaves_like :enumerable_find, :find + before :each do + ScratchPad.record [] + @elements = [2, 4, 6, 8, 10] + @numerous = EnumerableSpecs::Numerous.new(*@elements) + @empty = [] + end + + it "passes each entry in enum to block while block when block is false" do + visited_elements = [] + @numerous.find do |element| + visited_elements << element + false + end + visited_elements.should == @elements + end + + it "returns nil when the block is false and there is no ifnone proc given" do + @numerous.find {|e| false }.should == nil + end + + it "returns the first element for which the block is not false" do + @elements.each do |element| + @numerous.find {|e| e > element - 1 }.should == element + end + end + + it "returns the value of the ifnone proc if the block is false" do + fail_proc = -> { "cheeseburgers" } + @numerous.find(fail_proc) {|e| false }.should == "cheeseburgers" + end + + it "doesn't call the ifnone proc if an element is found" do + fail_proc = -> { raise "This shouldn't have been called" } + @numerous.find(fail_proc) {|e| e == @elements.first }.should == 2 + end + + it "calls the ifnone proc only once when the block is false" do + times = 0 + fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" } + @numerous.find(fail_proc) {|e| false }.should == "cheeseburgers" + end + + it "calls the ifnone proc when there are no elements" do + fail_proc = -> { "yay" } + @empty.find(fail_proc) {|e| true}.should == "yay" + end + + it "ignores the ifnone argument when nil" do + @numerous.find(nil) {|e| false }.should == nil + end + + it "passes through the values yielded by #each_with_index" do + [:a, :b].each_with_index.find { |x, i| ScratchPad << [x, i]; nil } + ScratchPad.recorded.should == [[:a, 0], [:b, 1]] + end + + it "returns an enumerator when no block given" do + @numerous.find.should.instance_of?(Enumerator) + end + + it "passes the ifnone proc to the enumerator" do + times = 0 + fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" } + @numerous.find(fail_proc).each {|e| false }.should == "cheeseburgers" + end + + it "gathers whole arrays as elements when each yields multiple" do + multi = EnumerableSpecs::YieldsMulti.new + multi.find {|e| e == [1, 2] }.should == [1, 2] + end + + it_behaves_like :enumerable_enumeratorized_with_unknown_size, :find end diff --git a/core/enumerable/flat_map_spec.rb b/core/enumerable/flat_map_spec.rb index bd07eab6c..ef50cb269 100644 --- a/core/enumerable/flat_map_spec.rb +++ b/core/enumerable/flat_map_spec.rb @@ -1,7 +1,56 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/collect_concat' +require_relative 'shared/enumerable_enumeratorized' describe "Enumerable#flat_map" do - it_behaves_like :enumerable_collect_concat, :flat_map + it "yields elements to the block and flattens one level" do + numerous = EnumerableSpecs::Numerous.new(1, [2, 3], [4, [5, 6]], {foo: :bar}) + numerous.flat_map { |i| i }.should == [1, 2, 3, 4, [5, 6], {foo: :bar}] + end + + it "appends non-Array elements that do not define #to_ary" do + obj = mock("to_ary undefined") + + numerous = EnumerableSpecs::Numerous.new(1, obj, 2) + numerous.flat_map { |i| i }.should == [1, obj, 2] + end + + it "concatenates the result of calling #to_ary if it returns an Array" do + obj = mock("to_ary defined") + obj.should_receive(:to_ary).and_return([:a, :b]) + + numerous = EnumerableSpecs::Numerous.new(1, obj, 2) + numerous.flat_map { |i| i }.should == [1, :a, :b, 2] + end + + it "does not call #to_a" do + obj = mock("to_ary undefined") + obj.should_not_receive(:to_a) + + numerous = EnumerableSpecs::Numerous.new(1, obj, 2) + numerous.flat_map { |i| i }.should == [1, obj, 2] + end + + it "appends an element that defines #to_ary that returns nil" do + obj = mock("to_ary defined") + obj.should_receive(:to_ary).and_return(nil) + + numerous = EnumerableSpecs::Numerous.new(1, obj, 2) + numerous.flat_map { |i| i }.should == [1, obj, 2] + end + + it "raises a TypeError if an element defining #to_ary does not return an Array or nil" do + obj = mock("to_ary defined") + obj.should_receive(:to_ary).and_return("array") + + -> { [1, obj, 3].flat_map { |i| i } }.should.raise(TypeError) + end + + it "returns an enumerator when no block given" do + enum = EnumerableSpecs::Numerous.new(1, 2).flat_map + enum.should.instance_of?(Enumerator) + enum.each{ |i| [i] * i }.should == [1, 2, 2] + end + + it_behaves_like :enumerable_enumeratorized_with_origin_size, :flat_map end diff --git a/core/enumerable/include_spec.rb b/core/enumerable/include_spec.rb index dab1b0445..d59b35148 100644 --- a/core/enumerable/include_spec.rb +++ b/core/enumerable/include_spec.rb @@ -1,7 +1,36 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/include' describe "Enumerable#include?" do - it_behaves_like :enumerable_include, :include? + it "returns true if any element == argument for numbers" do + class EnumerableSpecIncludeP; def ==(obj) obj == 5; end; end + + elements = (0..5).to_a + EnumerableSpecs::Numerous.new(*elements).include?(5).should == true + EnumerableSpecs::Numerous.new(*elements).include?(10).should == false + EnumerableSpecs::Numerous.new(*elements).include?(EnumerableSpecIncludeP.new).should == true + end + + it "returns true if any element == argument for other objects" do + class EnumerableSpecIncludeP11; def ==(obj); obj == '11'; end; end + + elements = ('0'..'5').to_a + [EnumerableSpecIncludeP11.new] + EnumerableSpecs::Numerous.new(*elements).include?('5').should == true + EnumerableSpecs::Numerous.new(*elements).include?('10').should == false + EnumerableSpecs::Numerous.new(*elements).include?(EnumerableSpecIncludeP11.new).should == true + EnumerableSpecs::Numerous.new(*elements).include?('11').should == true + end + + + it "returns true if any member of enum equals obj when == compare different classes (legacy rubycon)" do + # equality is tested with == + EnumerableSpecs::Numerous.new(2,4,6,8,10).include?(2.0).should == true + EnumerableSpecs::Numerous.new(2,4,[6,8],10).include?([6, 8]).should == true + EnumerableSpecs::Numerous.new(2,4,[6,8],10).include?([6.0, 8.0]).should == true + end + + it "gathers whole arrays as elements when each yields multiple" do + multi = EnumerableSpecs::YieldsMulti.new + multi.include?([1,2]).should == true + end end diff --git a/core/enumerable/inject_spec.rb b/core/enumerable/inject_spec.rb index e1fe21614..10de32139 100644 --- a/core/enumerable/inject_spec.rb +++ b/core/enumerable/inject_spec.rb @@ -1,7 +1,144 @@ require_relative '../../spec_helper' +require_relative '../array/shared/iterable_and_tolerating_size_increasing' require_relative 'fixtures/classes' -require_relative 'shared/inject' describe "Enumerable#inject" do - it_behaves_like :enumerable_inject, :inject + it "with argument takes a block with an accumulator (with argument as initial value) and the current element. Value of block becomes new accumulator" do + a = [] + EnumerableSpecs::Numerous.new.inject(0) { |memo, i| a << [memo, i]; i } + a.should == [[0, 2], [2, 5], [5, 3], [3, 6], [6, 1], [1, 4]] + EnumerableSpecs::EachDefiner.new(true, true, true).inject(nil) {|result, i| i && result}.should == nil + end + + it "produces an array of the accumulator and the argument when given a block with a *arg" do + a = [] + [1,2].inject(0) {|*args| a << args; args[0] + args[1]} + a.should == [[0, 1], [1, 2]] + end + + it "can take two argument" do + EnumerableSpecs::Numerous.new(1, 2, 3).inject(10, :-).should == 4 + EnumerableSpecs::Numerous.new(1, 2, 3).inject(10, "-").should == 4 + + [1, 2, 3].inject(10, :-).should == 4 + [1, 2, 3].inject(10, "-").should == 4 + end + + it "converts non-Symbol method name argument to String with #to_str if two arguments" do + name = Object.new + def name.to_str; "-"; end + + EnumerableSpecs::Numerous.new(1, 2, 3).inject(10, name).should == 4 + [1, 2, 3].inject(10, name).should == 4 + end + + it "raises TypeError when the second argument is not Symbol or String and it cannot be converted to String if two arguments" do + -> { EnumerableSpecs::Numerous.new(1, 2, 3).inject(10, Object.new) }.should.raise(TypeError, /is not a symbol nor a string/) + -> { [1, 2, 3].inject(10, Object.new) }.should.raise(TypeError, /is not a symbol nor a string/) + end + + it "ignores the block if two arguments" do + -> { + EnumerableSpecs::Numerous.new(1, 2, 3).inject(10, :-) { raise "we never get here"}.should == 4 + }.should complain(/#{__FILE__}:#{__LINE__-1}: warning: given block not used/, verbose: true) + + -> { + [1, 2, 3].inject(10, :-) { raise "we never get here"}.should == 4 + }.should complain(/#{__FILE__}:#{__LINE__-1}: warning: given block not used/, verbose: true) + end + + it "does not warn when given a Symbol with $VERBOSE true" do + -> { + [1, 2].inject(0, :+) + [1, 2].inject(:+) + EnumerableSpecs::Numerous.new(1, 2).inject(0, :+) + EnumerableSpecs::Numerous.new(1, 2).inject(:+) + }.should_not complain(verbose: true) + end + + it "can take a symbol argument" do + EnumerableSpecs::Numerous.new(10, 1, 2, 3).inject(:-).should == 4 + [10, 1, 2, 3].inject(:-).should == 4 + end + + it "can take a String argument" do + EnumerableSpecs::Numerous.new(10, 1, 2, 3).inject("-").should == 4 + [10, 1, 2, 3].inject("-").should == 4 + end + + it "converts non-Symbol method name argument to String with #to_str" do + name = Object.new + def name.to_str; "-"; end + + EnumerableSpecs::Numerous.new(10, 1, 2, 3).inject(name).should == 4 + [10, 1, 2, 3].inject(name).should == 4 + end + + it "raises TypeError when passed not Symbol or String method name argument and it cannot be converted to String" do + -> { EnumerableSpecs::Numerous.new(10, 1, 2, 3).inject(Object.new) }.should.raise(TypeError, /is not a symbol nor a string/) + -> { [10, 1, 2, 3].inject(Object.new) }.should.raise(TypeError, /is not a symbol nor a string/) + end + + it "without argument takes a block with an accumulator (with first element as initial value) and the current element. Value of block becomes new accumulator" do + a = [] + EnumerableSpecs::Numerous.new.inject { |memo, i| a << [memo, i]; i } + a.should == [[2, 5], [5, 3], [3, 6], [6, 1], [1, 4]] + end + + it "gathers whole arrays as elements when each yields multiple" do + multi = EnumerableSpecs::YieldsMulti.new + multi.inject([]) {|acc, e| acc << e }.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]] + end + + it "with inject arguments(legacy rubycon)" do + # with inject argument + EnumerableSpecs::EachDefiner.new().inject(1) {|acc,x| 999 }.should == 1 + EnumerableSpecs::EachDefiner.new(2).inject(1) {|acc,x| 999 }.should == 999 + EnumerableSpecs::EachDefiner.new(2).inject(1) {|acc,x| acc }.should == 1 + EnumerableSpecs::EachDefiner.new(2).inject(1) {|acc,x| x }.should == 2 + + EnumerableSpecs::EachDefiner.new(1,2,3,4).inject(100) {|acc,x| acc + x }.should == 110 + EnumerableSpecs::EachDefiner.new(1,2,3,4).inject(100) {|acc,x| acc * x }.should == 2400 + + EnumerableSpecs::EachDefiner.new('a','b','c').inject("z") {|result, i| i+result}.should == "cbaz" + end + + it "without inject arguments(legacy rubycon)" do + # no inject argument + EnumerableSpecs::EachDefiner.new(2).inject {|acc,x| 999 }.should == 2 + EnumerableSpecs::EachDefiner.new(2).inject {|acc,x| acc }.should == 2 + EnumerableSpecs::EachDefiner.new(2).inject {|acc,x| x }.should == 2 + + EnumerableSpecs::EachDefiner.new(1,2,3,4).inject {|acc,x| acc + x }.should == 10 + EnumerableSpecs::EachDefiner.new(1,2,3,4).inject {|acc,x| acc * x }.should == 24 + + EnumerableSpecs::EachDefiner.new('a','b','c').inject {|result, i| i+result}.should == "cba" + EnumerableSpecs::EachDefiner.new(3, 4, 5).inject {|result, i| result*i}.should == 60 + EnumerableSpecs::EachDefiner.new([1], 2, 'a','b').inject {|r,i| r< { [1,2].inject }.should.raise(ArgumentError) + -> { {one: 1, two: 2}.inject }.should.raise(ArgumentError) + end end diff --git a/core/enumerable/map_spec.rb b/core/enumerable/map_spec.rb index 98a70781c..e6447f5c2 100644 --- a/core/enumerable/map_spec.rb +++ b/core/enumerable/map_spec.rb @@ -1,7 +1,109 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/collect' +require_relative 'shared/enumerable_enumeratorized' describe "Enumerable#map" do - it_behaves_like :enumerable_collect, :map + before :each do + ScratchPad.record [] + end + + it "returns a new array with the results of passing each element to block" do + entries = [0, 1, 3, 4, 5, 6] + numerous = EnumerableSpecs::Numerous.new(*entries) + numerous.map { |i| i % 2 }.should == [0, 1, 1, 0, 1, 0] + numerous.map { |i| i }.should == entries + end + + it "passes through the values yielded by #each_with_index" do + [:a, :b].each_with_index.map { |x, i| ScratchPad << [x, i]; nil } + ScratchPad.recorded.should == [[:a, 0], [:b, 1]] + end + + it "gathers initial args as elements when each yields multiple" do + multi = EnumerableSpecs::YieldsMulti.new + multi.map {|e| e}.should == [1,3,6] + end + + it "only yields increasing values for a Range" do + (1..0).map { |x| x }.should == [] + (1..1).map { |x| x }.should == [1] + (1..2).map { |x| x }.should == [1, 2] + end + + it "returns an enumerator when no block given" do + enum = EnumerableSpecs::Numerous.new.map + enum.should.instance_of?(Enumerator) + enum.each { |i| -i }.should == [-2, -5, -3, -6, -1, -4] + end + + it "reports the same arity as the given block" do + entries = [0, 1, 3, 4, 5, 6] + numerous = EnumerableSpecs::Numerous.new(*entries) + + def numerous.each(&block) + ScratchPad << block.arity + super + end + + numerous.map { |a, b| a % 2 }.should == [0, 1, 1, 0, 1, 0] + ScratchPad.recorded.should == [2] + ScratchPad.clear + ScratchPad.record [] + numerous.map { |i| i }.should == entries + ScratchPad.recorded.should == [1] + end + + it "yields an Array of 2 elements for a Hash when block arity is 1" do + c = Class.new do + def register(a) + ScratchPad << a + end + end + m = c.new.method(:register) + + ScratchPad.record [] + { 1 => 'a', 2 => 'b' }.map(&m) + ScratchPad.recorded.should == [[1, 'a'], [2, 'b']] + end + + it "yields 2 arguments for a Hash when block arity is 2" do + c = Class.new do + def register(a, b) + ScratchPad << [a, b] + end + end + m = c.new.method(:register) + + ScratchPad.record [] + { 1 => 'a', 2 => 'b' }.map(&m) + ScratchPad.recorded.should == [[1, 'a'], [2, 'b']] + end + + it "raises an error for a Hash when an arity enforcing block of arity >2 is passed in" do + c = Class.new do + def register(a, b, c) + end + end + m = c.new.method(:register) + + -> do + { 1 => 'a', 2 => 'b' }.map(&m) + end.should.raise(ArgumentError) + end + + it "calls the each method on sub-classes" do + c = Class.new(Hash) do + def each + ScratchPad << 'in each' + super + end + end + h = c.new + h[1] = 'a' + ScratchPad.record [] + h.map { |k,v| v } + ScratchPad.recorded.should == ['in each'] + end + + it_behaves_like :enumerable_enumeratorized_with_origin_size, :map end diff --git a/core/enumerable/member_spec.rb b/core/enumerable/member_spec.rb index 1fe3cebd2..be06880eb 100644 --- a/core/enumerable/member_spec.rb +++ b/core/enumerable/member_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/include' describe "Enumerable#member?" do - it_behaves_like :enumerable_include, :member? + it "is an alias of Enumerable#include?" do + Enumerable.instance_method(:member?).should == Enumerable.instance_method(:include?) + end end diff --git a/core/enumerable/reduce_spec.rb b/core/enumerable/reduce_spec.rb index bc8691c1b..40452b66a 100644 --- a/core/enumerable/reduce_spec.rb +++ b/core/enumerable/reduce_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/inject' describe "Enumerable#reduce" do - it_behaves_like :enumerable_inject, :reduce + it "is an alias of Enumerable#inject" do + Enumerable.instance_method(:reduce).should == Enumerable.instance_method(:inject) + end end diff --git a/core/enumerable/select_spec.rb b/core/enumerable/select_spec.rb index 7fc61926f..a53c228a4 100644 --- a/core/enumerable/select_spec.rb +++ b/core/enumerable/select_spec.rb @@ -1,7 +1,33 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/find_all' +require_relative 'shared/enumerable_enumeratorized' describe "Enumerable#select" do - it_behaves_like :enumerable_find_all, :select + before :each do + ScratchPad.record [] + @elements = (1..10).to_a + @numerous = EnumerableSpecs::Numerous.new(*@elements) + end + + it "returns all elements for which the block is not false" do + @numerous.select {|i| i % 3 == 0 }.should == [3, 6, 9] + @numerous.select {|i| true }.should == @elements + @numerous.select {|i| false }.should == [] + end + + it "returns an enumerator when no block given" do + @numerous.select.should.instance_of?(Enumerator) + end + + it "passes through the values yielded by #each_with_index" do + [:a, :b].each_with_index.select { |x, i| ScratchPad << [x, i] } + ScratchPad.recorded.should == [[:a, 0], [:b, 1]] + end + + it "gathers whole arrays as elements when each yields multiple" do + multi = EnumerableSpecs::YieldsMulti.new + multi.select {|e| e == [3, 4, 5] }.should == [[3, 4, 5]] + end + + it_behaves_like :enumerable_enumeratorized_with_origin_size, :select end diff --git a/core/enumerable/shared/collect.rb b/core/enumerable/shared/collect.rb deleted file mode 100644 index 4696d3245..000000000 --- a/core/enumerable/shared/collect.rb +++ /dev/null @@ -1,107 +0,0 @@ -require_relative 'enumerable_enumeratorized' - -describe :enumerable_collect, shared: true do - before :each do - ScratchPad.record [] - end - - it "returns a new array with the results of passing each element to block" do - entries = [0, 1, 3, 4, 5, 6] - numerous = EnumerableSpecs::Numerous.new(*entries) - numerous.send(@method) { |i| i % 2 }.should == [0, 1, 1, 0, 1, 0] - numerous.send(@method) { |i| i }.should == entries - end - - it "passes through the values yielded by #each_with_index" do - [:a, :b].each_with_index.send(@method) { |x, i| ScratchPad << [x, i]; nil } - ScratchPad.recorded.should == [[:a, 0], [:b, 1]] - end - - it "gathers initial args as elements when each yields multiple" do - multi = EnumerableSpecs::YieldsMulti.new - multi.send(@method) {|e| e}.should == [1,3,6] - end - - it "only yields increasing values for a Range" do - (1..0).send(@method) { |x| x }.should == [] - (1..1).send(@method) { |x| x }.should == [1] - (1..2).send(@method) { |x| x }.should == [1, 2] - end - - it "returns an enumerator when no block given" do - enum = EnumerableSpecs::Numerous.new.send(@method) - enum.should.instance_of?(Enumerator) - enum.each { |i| -i }.should == [-2, -5, -3, -6, -1, -4] - end - - it "reports the same arity as the given block" do - entries = [0, 1, 3, 4, 5, 6] - numerous = EnumerableSpecs::Numerous.new(*entries) - - def numerous.each(&block) - ScratchPad << block.arity - super - end - - numerous.send(@method) { |a, b| a % 2 }.should == [0, 1, 1, 0, 1, 0] - ScratchPad.recorded.should == [2] - ScratchPad.clear - ScratchPad.record [] - numerous.send(@method) { |i| i }.should == entries - ScratchPad.recorded.should == [1] - end - - it "yields an Array of 2 elements for a Hash when block arity is 1" do - c = Class.new do - def register(a) - ScratchPad << a - end - end - m = c.new.method(:register) - - ScratchPad.record [] - { 1 => 'a', 2 => 'b' }.map(&m) - ScratchPad.recorded.should == [[1, 'a'], [2, 'b']] - end - - it "yields 2 arguments for a Hash when block arity is 2" do - c = Class.new do - def register(a, b) - ScratchPad << [a, b] - end - end - m = c.new.method(:register) - - ScratchPad.record [] - { 1 => 'a', 2 => 'b' }.map(&m) - ScratchPad.recorded.should == [[1, 'a'], [2, 'b']] - end - - it "raises an error for a Hash when an arity enforcing block of arity >2 is passed in" do - c = Class.new do - def register(a, b, c) - end - end - m = c.new.method(:register) - - -> do - { 1 => 'a', 2 => 'b' }.map(&m) - end.should.raise(ArgumentError) - end - - it "calls the each method on sub-classes" do - c = Class.new(Hash) do - def each - ScratchPad << 'in each' - super - end - end - h = c.new - h[1] = 'a' - ScratchPad.record [] - h.send(@method) { |k,v| v } - ScratchPad.recorded.should == ['in each'] - end - - it_should_behave_like :enumerable_enumeratorized_with_origin_size -end diff --git a/core/enumerable/shared/collect_concat.rb b/core/enumerable/shared/collect_concat.rb deleted file mode 100644 index 1694e3fdc..000000000 --- a/core/enumerable/shared/collect_concat.rb +++ /dev/null @@ -1,54 +0,0 @@ -require_relative 'enumerable_enumeratorized' - -describe :enumerable_collect_concat, shared: true do - it "yields elements to the block and flattens one level" do - numerous = EnumerableSpecs::Numerous.new(1, [2, 3], [4, [5, 6]], {foo: :bar}) - numerous.send(@method) { |i| i }.should == [1, 2, 3, 4, [5, 6], {foo: :bar}] - end - - it "appends non-Array elements that do not define #to_ary" do - obj = mock("to_ary undefined") - - numerous = EnumerableSpecs::Numerous.new(1, obj, 2) - numerous.send(@method) { |i| i }.should == [1, obj, 2] - end - - it "concatenates the result of calling #to_ary if it returns an Array" do - obj = mock("to_ary defined") - obj.should_receive(:to_ary).and_return([:a, :b]) - - numerous = EnumerableSpecs::Numerous.new(1, obj, 2) - numerous.send(@method) { |i| i }.should == [1, :a, :b, 2] - end - - it "does not call #to_a" do - obj = mock("to_ary undefined") - obj.should_not_receive(:to_a) - - numerous = EnumerableSpecs::Numerous.new(1, obj, 2) - numerous.send(@method) { |i| i }.should == [1, obj, 2] - end - - it "appends an element that defines #to_ary that returns nil" do - obj = mock("to_ary defined") - obj.should_receive(:to_ary).and_return(nil) - - numerous = EnumerableSpecs::Numerous.new(1, obj, 2) - numerous.send(@method) { |i| i }.should == [1, obj, 2] - end - - it "raises a TypeError if an element defining #to_ary does not return an Array or nil" do - obj = mock("to_ary defined") - obj.should_receive(:to_ary).and_return("array") - - -> { [1, obj, 3].send(@method) { |i| i } }.should.raise(TypeError) - end - - it "returns an enumerator when no block given" do - enum = EnumerableSpecs::Numerous.new(1, 2).send(@method) - enum.should.instance_of?(Enumerator) - enum.each{ |i| [i] * i }.should == [1, 2, 2] - end - - it_should_behave_like :enumerable_enumeratorized_with_origin_size -end diff --git a/core/enumerable/shared/entries.rb b/core/enumerable/shared/entries.rb deleted file mode 100644 index e32eb23d2..000000000 --- a/core/enumerable/shared/entries.rb +++ /dev/null @@ -1,16 +0,0 @@ -describe :enumerable_entries, shared: true do - it "returns an array containing the elements" do - numerous = EnumerableSpecs::Numerous.new(1, nil, 'a', 2, false, true) - numerous.send(@method).should == [1, nil, "a", 2, false, true] - end - - it "passes through the values yielded by #each_with_index" do - [:a, :b].each_with_index.send(@method).should == [[:a, 0], [:b, 1]] - end - - it "passes arguments to each" do - count = EnumerableSpecs::EachCounter.new(1, 2, 3) - count.send(@method, :hello, "world").should == [1, 2, 3] - count.arguments_passed.should == [:hello, "world"] - end -end diff --git a/core/enumerable/shared/find.rb b/core/enumerable/shared/find.rb deleted file mode 100644 index cdff64041..000000000 --- a/core/enumerable/shared/find.rb +++ /dev/null @@ -1,77 +0,0 @@ -require_relative 'enumerable_enumeratorized' - -describe :enumerable_find, shared: true do - # #detect and #find are aliases, so we only need one function - before :each do - ScratchPad.record [] - @elements = [2, 4, 6, 8, 10] - @numerous = EnumerableSpecs::Numerous.new(*@elements) - @empty = [] - end - - it "passes each entry in enum to block while block when block is false" do - visited_elements = [] - @numerous.send(@method) do |element| - visited_elements << element - false - end - visited_elements.should == @elements - end - - it "returns nil when the block is false and there is no ifnone proc given" do - @numerous.send(@method) {|e| false }.should == nil - end - - it "returns the first element for which the block is not false" do - @elements.each do |element| - @numerous.send(@method) {|e| e > element - 1 }.should == element - end - end - - it "returns the value of the ifnone proc if the block is false" do - fail_proc = -> { "cheeseburgers" } - @numerous.send(@method, fail_proc) {|e| false }.should == "cheeseburgers" - end - - it "doesn't call the ifnone proc if an element is found" do - fail_proc = -> { raise "This shouldn't have been called" } - @numerous.send(@method, fail_proc) {|e| e == @elements.first }.should == 2 - end - - it "calls the ifnone proc only once when the block is false" do - times = 0 - fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" } - @numerous.send(@method, fail_proc) {|e| false }.should == "cheeseburgers" - end - - it "calls the ifnone proc when there are no elements" do - fail_proc = -> { "yay" } - @empty.send(@method, fail_proc) {|e| true}.should == "yay" - end - - it "ignores the ifnone argument when nil" do - @numerous.send(@method, nil) {|e| false }.should == nil - end - - it "passes through the values yielded by #each_with_index" do - [:a, :b].each_with_index.send(@method) { |x, i| ScratchPad << [x, i]; nil } - ScratchPad.recorded.should == [[:a, 0], [:b, 1]] - end - - it "returns an enumerator when no block given" do - @numerous.send(@method).should.instance_of?(Enumerator) - end - - it "passes the ifnone proc to the enumerator" do - times = 0 - fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" } - @numerous.send(@method, fail_proc).each {|e| false }.should == "cheeseburgers" - end - - it "gathers whole arrays as elements when each yields multiple" do - multi = EnumerableSpecs::YieldsMulti.new - multi.send(@method) {|e| e == [1, 2] }.should == [1, 2] - end - - it_should_behave_like :enumerable_enumeratorized_with_unknown_size -end diff --git a/core/enumerable/shared/find_all.rb b/core/enumerable/shared/find_all.rb deleted file mode 100644 index 27f01de6e..000000000 --- a/core/enumerable/shared/find_all.rb +++ /dev/null @@ -1,31 +0,0 @@ -require_relative 'enumerable_enumeratorized' - -describe :enumerable_find_all, shared: true do - before :each do - ScratchPad.record [] - @elements = (1..10).to_a - @numerous = EnumerableSpecs::Numerous.new(*@elements) - end - - it "returns all elements for which the block is not false" do - @numerous.send(@method) {|i| i % 3 == 0 }.should == [3, 6, 9] - @numerous.send(@method) {|i| true }.should == @elements - @numerous.send(@method) {|i| false }.should == [] - end - - it "returns an enumerator when no block given" do - @numerous.send(@method).should.instance_of?(Enumerator) - end - - it "passes through the values yielded by #each_with_index" do - [:a, :b].each_with_index.send(@method) { |x, i| ScratchPad << [x, i] } - ScratchPad.recorded.should == [[:a, 0], [:b, 1]] - end - - it "gathers whole arrays as elements when each yields multiple" do - multi = EnumerableSpecs::YieldsMulti.new - multi.send(@method) {|e| e == [3, 4, 5] }.should == [[3, 4, 5]] - end - - it_should_behave_like :enumerable_enumeratorized_with_origin_size -end diff --git a/core/enumerable/shared/include.rb b/core/enumerable/shared/include.rb deleted file mode 100644 index ea250f032..000000000 --- a/core/enumerable/shared/include.rb +++ /dev/null @@ -1,34 +0,0 @@ -describe :enumerable_include, shared: true do - it "returns true if any element == argument for numbers" do - class EnumerableSpecIncludeP; def ==(obj) obj == 5; end; end - - elements = (0..5).to_a - EnumerableSpecs::Numerous.new(*elements).send(@method,5).should == true - EnumerableSpecs::Numerous.new(*elements).send(@method,10).should == false - EnumerableSpecs::Numerous.new(*elements).send(@method,EnumerableSpecIncludeP.new).should == true - end - - it "returns true if any element == argument for other objects" do - class EnumerableSpecIncludeP11; def ==(obj); obj == '11'; end; end - - elements = ('0'..'5').to_a + [EnumerableSpecIncludeP11.new] - EnumerableSpecs::Numerous.new(*elements).send(@method,'5').should == true - EnumerableSpecs::Numerous.new(*elements).send(@method,'10').should == false - EnumerableSpecs::Numerous.new(*elements).send(@method,EnumerableSpecIncludeP11.new).should == true - EnumerableSpecs::Numerous.new(*elements).send(@method,'11').should == true - end - - - it "returns true if any member of enum equals obj when == compare different classes (legacy rubycon)" do - # equality is tested with == - EnumerableSpecs::Numerous.new(2,4,6,8,10).send(@method, 2.0).should == true - EnumerableSpecs::Numerous.new(2,4,[6,8],10).send(@method, [6, 8]).should == true - EnumerableSpecs::Numerous.new(2,4,[6,8],10).send(@method, [6.0, 8.0]).should == true - end - - it "gathers whole arrays as elements when each yields multiple" do - multi = EnumerableSpecs::YieldsMulti.new - multi.send(@method, [1,2]).should == true - end - -end diff --git a/core/enumerable/shared/inject.rb b/core/enumerable/shared/inject.rb deleted file mode 100644 index 7da4f0ca9..000000000 --- a/core/enumerable/shared/inject.rb +++ /dev/null @@ -1,142 +0,0 @@ -require_relative '../../array/shared/iterable_and_tolerating_size_increasing' - -describe :enumerable_inject, shared: true do - it "with argument takes a block with an accumulator (with argument as initial value) and the current element. Value of block becomes new accumulator" do - a = [] - EnumerableSpecs::Numerous.new.send(@method, 0) { |memo, i| a << [memo, i]; i } - a.should == [[0, 2], [2, 5], [5, 3], [3, 6], [6, 1], [1, 4]] - EnumerableSpecs::EachDefiner.new(true, true, true).send(@method, nil) {|result, i| i && result}.should == nil - end - - it "produces an array of the accumulator and the argument when given a block with a *arg" do - a = [] - [1,2].send(@method, 0) {|*args| a << args; args[0] + args[1]} - a.should == [[0, 1], [1, 2]] - end - - it "can take two argument" do - EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, :-).should == 4 - EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, "-").should == 4 - - [1, 2, 3].send(@method, 10, :-).should == 4 - [1, 2, 3].send(@method, 10, "-").should == 4 - end - - it "converts non-Symbol method name argument to String with #to_str if two arguments" do - name = Object.new - def name.to_str; "-"; end - - EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, name).should == 4 - [1, 2, 3].send(@method, 10, name).should == 4 - end - - it "raises TypeError when the second argument is not Symbol or String and it cannot be converted to String if two arguments" do - -> { EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, Object.new) }.should.raise(TypeError, /is not a symbol nor a string/) - -> { [1, 2, 3].send(@method, 10, Object.new) }.should.raise(TypeError, /is not a symbol nor a string/) - end - - it "ignores the block if two arguments" do - -> { - EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, :-) { raise "we never get here"}.should == 4 - }.should complain(/#{__FILE__}:#{__LINE__-1}: warning: given block not used/, verbose: true) - - -> { - [1, 2, 3].send(@method, 10, :-) { raise "we never get here"}.should == 4 - }.should complain(/#{__FILE__}:#{__LINE__-1}: warning: given block not used/, verbose: true) - end - - it "does not warn when given a Symbol with $VERBOSE true" do - -> { - [1, 2].send(@method, 0, :+) - [1, 2].send(@method, :+) - EnumerableSpecs::Numerous.new(1, 2).send(@method, 0, :+) - EnumerableSpecs::Numerous.new(1, 2).send(@method, :+) - }.should_not complain(verbose: true) - end - - it "can take a symbol argument" do - EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, :-).should == 4 - [10, 1, 2, 3].send(@method, :-).should == 4 - end - - it "can take a String argument" do - EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, "-").should == 4 - [10, 1, 2, 3].send(@method, "-").should == 4 - end - - it "converts non-Symbol method name argument to String with #to_str" do - name = Object.new - def name.to_str; "-"; end - - EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, name).should == 4 - [10, 1, 2, 3].send(@method, name).should == 4 - end - - it "raises TypeError when passed not Symbol or String method name argument and it cannot be converted to String" do - -> { EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, Object.new) }.should.raise(TypeError, /is not a symbol nor a string/) - -> { [10, 1, 2, 3].send(@method, Object.new) }.should.raise(TypeError, /is not a symbol nor a string/) - end - - it "without argument takes a block with an accumulator (with first element as initial value) and the current element. Value of block becomes new accumulator" do - a = [] - EnumerableSpecs::Numerous.new.send(@method) { |memo, i| a << [memo, i]; i } - a.should == [[2, 5], [5, 3], [3, 6], [6, 1], [1, 4]] - end - - it "gathers whole arrays as elements when each yields multiple" do - multi = EnumerableSpecs::YieldsMulti.new - multi.send(@method, []) {|acc, e| acc << e }.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]] - end - - it "with inject arguments(legacy rubycon)" do - # with inject argument - EnumerableSpecs::EachDefiner.new().send(@method, 1) {|acc,x| 999 }.should == 1 - EnumerableSpecs::EachDefiner.new(2).send(@method, 1) {|acc,x| 999 }.should == 999 - EnumerableSpecs::EachDefiner.new(2).send(@method, 1) {|acc,x| acc }.should == 1 - EnumerableSpecs::EachDefiner.new(2).send(@method, 1) {|acc,x| x }.should == 2 - - EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method, 100) {|acc,x| acc + x }.should == 110 - EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method, 100) {|acc,x| acc * x }.should == 2400 - - EnumerableSpecs::EachDefiner.new('a','b','c').send(@method, "z") {|result, i| i+result}.should == "cbaz" - end - - it "without inject arguments(legacy rubycon)" do - # no inject argument - EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| 999 }.should == 2 - EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| acc }.should == 2 - EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| x }.should == 2 - - EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method) {|acc,x| acc + x }.should == 10 - EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method) {|acc,x| acc * x }.should == 24 - - EnumerableSpecs::EachDefiner.new('a','b','c').send(@method) {|result, i| i+result}.should == "cba" - EnumerableSpecs::EachDefiner.new(3, 4, 5).send(@method) {|result, i| result*i}.should == 60 - EnumerableSpecs::EachDefiner.new([1], 2, 'a','b').send(@method){|r,i| r< { [1,2].send(@method) }.should.raise(ArgumentError) - -> { {one: 1, two: 2}.send(@method) }.should.raise(ArgumentError) - end -end diff --git a/core/enumerable/to_a_spec.rb b/core/enumerable/to_a_spec.rb index 723f92257..f1796070f 100644 --- a/core/enumerable/to_a_spec.rb +++ b/core/enumerable/to_a_spec.rb @@ -1,7 +1,19 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/entries' describe "Enumerable#to_a" do - it_behaves_like :enumerable_entries, :to_a + it "returns an array containing the elements" do + numerous = EnumerableSpecs::Numerous.new(1, nil, 'a', 2, false, true) + numerous.to_a.should == [1, nil, "a", 2, false, true] + end + + it "passes through the values yielded by #each_with_index" do + [:a, :b].each_with_index.to_a.should == [[:a, 0], [:b, 1]] + end + + it "passes arguments to each" do + count = EnumerableSpecs::EachCounter.new(1, 2, 3) + count.to_a(:hello, "world").should == [1, 2, 3] + count.arguments_passed.should == [:hello, "world"] + end end diff --git a/core/enumerator/each_with_object_spec.rb b/core/enumerator/each_with_object_spec.rb index 84a45ae89..0e0a4496f 100644 --- a/core/enumerator/each_with_object_spec.rb +++ b/core/enumerator/each_with_object_spec.rb @@ -1,6 +1,42 @@ require_relative '../../spec_helper' -require_relative 'shared/with_object' describe "Enumerator#each_with_object" do - it_behaves_like :enum_with_object, :each_with_object + before :each do + @enum = [:a, :b].to_enum + @memo = '' + @block_params = @enum.each_with_object(@memo).to_a + end + + it "receives an argument" do + @enum.method(:each_with_object).arity.should == 1 + end + + context "with block" do + it "returns the given object" do + ret = @enum.each_with_object(@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.each_with_object(@memo) + ret.should.instance_of?(Enumerator) + ret.should_not.equal?(@enum) + end + end end diff --git a/core/enumerator/enum_for_spec.rb b/core/enumerator/enum_for_spec.rb deleted file mode 100644 index fbdf98545..000000000 --- a/core/enumerator/enum_for_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -require_relative '../../spec_helper' -require_relative 'shared/enum_for' - -describe "Enumerator#enum_for" do - it_behaves_like :enum_for, :enum_for -end diff --git a/core/enumerator/lazy/collect_concat_spec.rb b/core/enumerator/lazy/collect_concat_spec.rb index 8765bb219..d9fd576e4 100644 --- a/core/enumerator/lazy/collect_concat_spec.rb +++ b/core/enumerator/lazy/collect_concat_spec.rb @@ -1,8 +1,8 @@ -# -*- encoding: us-ascii -*- - require_relative '../../../spec_helper' -require_relative 'shared/collect_concat' describe "Enumerator::Lazy#collect_concat" do - it_behaves_like :enumerator_lazy_collect_concat, :collect_concat + it "is an alias of Enumerator::Lazy#flat_map" do + Enumerator::Lazy.instance_method(:collect_concat).should == + Enumerator::Lazy.instance_method(:flat_map) + end end diff --git a/core/enumerator/lazy/collect_spec.rb b/core/enumerator/lazy/collect_spec.rb index 14b79ce16..53a477e05 100644 --- a/core/enumerator/lazy/collect_spec.rb +++ b/core/enumerator/lazy/collect_spec.rb @@ -1,8 +1,8 @@ -# -*- encoding: us-ascii -*- - require_relative '../../../spec_helper' -require_relative 'shared/collect' describe "Enumerator::Lazy#collect" do - it_behaves_like :enumerator_lazy_collect, :collect + it "is an alias of Enumerator::Lazy#map" do + Enumerator::Lazy.instance_method(:collect).should == + Enumerator::Lazy.instance_method(:map) + end end diff --git a/core/enumerator/lazy/enum_for_spec.rb b/core/enumerator/lazy/enum_for_spec.rb index 7e7783f6f..b40c5d991 100644 --- a/core/enumerator/lazy/enum_for_spec.rb +++ b/core/enumerator/lazy/enum_for_spec.rb @@ -1,8 +1,8 @@ -# -*- encoding: us-ascii -*- - require_relative '../../../spec_helper' -require_relative 'shared/to_enum' describe "Enumerator::Lazy#enum_for" do - it_behaves_like :enumerator_lazy_to_enum, :enum_for + it "is an alias of Enumerator::Lazy#to_enum" do + Enumerator::Lazy.instance_method(:enum_for).should == + Enumerator::Lazy.instance_method(:to_enum) + end end diff --git a/core/enumerator/lazy/filter_spec.rb b/core/enumerator/lazy/filter_spec.rb index 43128241e..3ca5376fa 100644 --- a/core/enumerator/lazy/filter_spec.rb +++ b/core/enumerator/lazy/filter_spec.rb @@ -1,6 +1,8 @@ require_relative '../../../spec_helper' -require_relative 'shared/select' describe "Enumerator::Lazy#filter" do - it_behaves_like :enumerator_lazy_select, :filter + it "is an alias of Enumerator::Lazy#select" do + Enumerator::Lazy.instance_method(:filter).should == + Enumerator::Lazy.instance_method(:select) + end end diff --git a/core/enumerator/lazy/find_all_spec.rb b/core/enumerator/lazy/find_all_spec.rb index 8b05c5380..64930dc61 100644 --- a/core/enumerator/lazy/find_all_spec.rb +++ b/core/enumerator/lazy/find_all_spec.rb @@ -1,8 +1,8 @@ -# -*- encoding: us-ascii -*- - require_relative '../../../spec_helper' -require_relative 'shared/select' describe "Enumerator::Lazy#find_all" do - it_behaves_like :enumerator_lazy_select, :find_all + it "is an alias of Enumerator::Lazy#select" do + Enumerator::Lazy.instance_method(:find_all).should == + Enumerator::Lazy.instance_method(:select) + end end diff --git a/core/enumerator/lazy/flat_map_spec.rb b/core/enumerator/lazy/flat_map_spec.rb index 5dcaa8bfa..609bf95b9 100644 --- a/core/enumerator/lazy/flat_map_spec.rb +++ b/core/enumerator/lazy/flat_map_spec.rb @@ -1,10 +1,78 @@ -# -*- encoding: us-ascii -*- - require_relative '../../../spec_helper' -require_relative 'shared/collect_concat' +require_relative 'fixtures/classes' describe "Enumerator::Lazy#flat_map" do - it_behaves_like :enumerator_lazy_collect_concat, :flat_map + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.flat_map {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.flat_map { true }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.flat_map { |n| (n * 10).to_s }.first(6).should == %w[0 10 20 30 40 50] + + @eventsmixed.flat_map {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + + it "flattens elements when the given block returned an array or responding to .each and .force" do + (0..Float::INFINITY).lazy.flat_map { |n| (n * 10).to_s.chars }.first(6).should == %w[0 1 0 2 0 3] + (0..Float::INFINITY).lazy.flat_map { |n| (n * 10).to_s.each_char }.first(6).all? { |o| o.instance_of? Enumerator }.should == true + (0..Float::INFINITY).lazy.flat_map { |n| (n * 10).to_s.each_char.lazy }.first(6).should == %w[0 1 0 2 0 3] + end + end + + it "calls the block with initial values when yield with multiple arguments" do + yields = [] + @yieldsmixed.flat_map { |v| yields << v }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields + end + + it "raises an ArgumentError when not given a block" do + -> { @yieldsmixed.flat_map }.should.raise(ArgumentError) + end + + describe "on a nested Lazy" do + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.take(50) {}.flat_map {}.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.map {|n| n * 10 }.flat_map { |n| n.to_s }.first(6).should == %w[0 10 20 30 40 50] + + @eventsmixed.flat_map {}.flat_map {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + + it "flattens elements when the given block returned an array or responding to .each and .force" do + (0..Float::INFINITY).lazy.map {|n| n * 10 }.flat_map { |n| n.to_s.chars }.first(6).should == %w[0 1 0 2 0 3] + (0..Float::INFINITY).lazy.map {|n| n * 10 }.flat_map { |n| n.to_s.each_char }.first(6).all? { |o| o.instance_of? Enumerator }.should == true + (0..Float::INFINITY).lazy.map {|n| n * 10 }.flat_map { |n| n.to_s.each_char.lazy }.first(6).should == %w[0 1 0 2 0 3] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.flat_map { |n| [-n, +n] }.first(200).should == + s.first(100).flat_map { |n| [-n, +n] }.to_a + end it "properly unwraps nested yields" do s = Enumerator.new do |y| loop do y << [1, 2] end end diff --git a/core/enumerator/lazy/map_spec.rb b/core/enumerator/lazy/map_spec.rb index 5cb998f5f..2c7f8efab 100644 --- a/core/enumerator/lazy/map_spec.rb +++ b/core/enumerator/lazy/map_spec.rb @@ -1,10 +1,62 @@ -# -*- encoding: us-ascii -*- - require_relative '../../../spec_helper' -require_relative 'shared/collect' +require_relative 'fixtures/classes' describe "Enumerator::Lazy#map" do - it_behaves_like :enumerator_lazy_collect, :map + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.map {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "keeps size" do + Enumerator::Lazy.new(Object.new, 100) {}.map {}.size.should == 100 + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.map(&:succ).first(3).should == [1, 2, 3] + + @eventsmixed.map {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + + it "calls the block with initial values when yield with multiple arguments" do + yields = [] + @yieldsmixed.map { |v| yields << v }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields + end + + describe "on a nested Lazy" do + it "keeps size" do + Enumerator::Lazy.new(Object.new, 100) {}.map {}.map {}.size.should == 100 + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.map(&:succ).map(&:succ).first(3).should == [2, 3, 4] + + @eventsmixed.map {}.map {}.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.map { |n| n }.first(100).should == + s.first(100).map { |n| n }.to_a + end it "doesn't unwrap Arrays" do Enumerator.new {|y| y.yield([1])}.lazy.to_a.should == [[1]] diff --git a/core/enumerator/lazy/select_spec.rb b/core/enumerator/lazy/select_spec.rb index 3773d8f0a..29c8f1bd8 100644 --- a/core/enumerator/lazy/select_spec.rb +++ b/core/enumerator/lazy/select_spec.rb @@ -1,10 +1,66 @@ -# -*- encoding: us-ascii -*- - require_relative '../../../spec_helper' -require_relative 'shared/select' +require_relative 'fixtures/classes' describe "Enumerator::Lazy#select" do - it_behaves_like :enumerator_lazy_select, :select + before :each do + @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy + @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @yieldsmixed.select {} + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@yieldsmixed) + end + + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.select { true }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.select(&:even?).first(3).should == [0, 2, 4] + + @eventsmixed.select { true }.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + + it "calls the block with a gathered array when yield with multiple arguments" do + yields = [] + @yieldsmixed.select { |v| yields << v }.force + yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields + end + + it "raises an ArgumentError when not given a block" do + -> { @yieldsmixed.select }.should.raise(ArgumentError) + end + + describe "on a nested Lazy" do + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.take(50) {}.select { true }.size.should == nil + end + + describe "when the returned lazy enumerator is evaluated by Enumerable#first" do + it "stops after specified times" do + (0..Float::INFINITY).lazy.select { |n| n > 5 }.select(&:even?).first(3).should == [6, 8, 10] + + @eventsmixed.select { true }.select { true }.first(1) + ScratchPad.recorded.should == [:before_yield] + end + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.select { |n| true }.first(100).should == + s.first(100).select { |n| true } + end it "doesn't pre-evaluate the next element" do eval_count = 0 diff --git a/core/enumerator/lazy/shared/collect.rb b/core/enumerator/lazy/shared/collect.rb deleted file mode 100644 index 0ed04c8e0..000000000 --- a/core/enumerator/lazy/shared/collect.rb +++ /dev/null @@ -1,62 +0,0 @@ -# -*- encoding: us-ascii -*- - -require_relative '../../../../spec_helper' -require_relative '../fixtures/classes' - -describe :enumerator_lazy_collect, shared: true do - before :each do - @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy - @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy - ScratchPad.record [] - end - - after :each do - ScratchPad.clear - end - - it "returns a new instance of Enumerator::Lazy" do - ret = @yieldsmixed.send(@method) {} - ret.should.instance_of?(Enumerator::Lazy) - ret.should_not.equal?(@yieldsmixed) - end - - it "keeps size" do - Enumerator::Lazy.new(Object.new, 100) {}.send(@method) {}.size.should == 100 - end - - describe "when the returned lazy enumerator is evaluated by Enumerable#first" do - it "stops after specified times" do - (0..Float::INFINITY).lazy.send(@method, &:succ).first(3).should == [1, 2, 3] - - @eventsmixed.send(@method) {}.first(1) - ScratchPad.recorded.should == [:before_yield] - end - end - - it "calls the block with initial values when yield with multiple arguments" do - yields = [] - @yieldsmixed.send(@method) { |v| yields << v }.force - yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields - end - - describe "on a nested Lazy" do - it "keeps size" do - Enumerator::Lazy.new(Object.new, 100) {}.send(@method) {}.send(@method) {}.size.should == 100 - end - - describe "when the returned lazy enumerator is evaluated by Enumerable#first" do - it "stops after specified times" do - (0..Float::INFINITY).lazy.send(@method, &:succ).send(@method, &:succ).first(3).should == [2, 3, 4] - - @eventsmixed.send(@method) {}.send(@method) {}.first(1) - ScratchPad.recorded.should == [:before_yield] - end - end - end - - it "works with an infinite enumerable" do - s = 0..Float::INFINITY - s.lazy.send(@method) { |n| n }.first(100).should == - s.first(100).send(@method) { |n| n }.to_a - end -end diff --git a/core/enumerator/lazy/shared/collect_concat.rb b/core/enumerator/lazy/shared/collect_concat.rb deleted file mode 100644 index 685e6d059..000000000 --- a/core/enumerator/lazy/shared/collect_concat.rb +++ /dev/null @@ -1,78 +0,0 @@ -# -*- encoding: us-ascii -*- - -require_relative '../../../../spec_helper' -require_relative '../fixtures/classes' - -describe :enumerator_lazy_collect_concat, shared: true do - before :each do - @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy - @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy - ScratchPad.record [] - end - - after :each do - ScratchPad.clear - end - - it "returns a new instance of Enumerator::Lazy" do - ret = @yieldsmixed.send(@method) {} - ret.should.instance_of?(Enumerator::Lazy) - ret.should_not.equal?(@yieldsmixed) - end - - it "sets #size to nil" do - Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { true }.size.should == nil - end - - describe "when the returned lazy enumerator is evaluated by Enumerable#first" do - it "stops after specified times" do - (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s }.first(6).should == %w[0 10 20 30 40 50] - - @eventsmixed.send(@method) {}.first(1) - ScratchPad.recorded.should == [:before_yield] - end - - it "flattens elements when the given block returned an array or responding to .each and .force" do - (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.chars }.first(6).should == %w[0 1 0 2 0 3] - (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.each_char }.first(6).all? { |o| o.instance_of? Enumerator }.should == true - (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.each_char.lazy }.first(6).should == %w[0 1 0 2 0 3] - end - end - - it "calls the block with initial values when yield with multiple arguments" do - yields = [] - @yieldsmixed.send(@method) { |v| yields << v }.force - yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields - end - - it "raises an ArgumentError when not given a block" do - -> { @yieldsmixed.send(@method) }.should.raise(ArgumentError) - end - - describe "on a nested Lazy" do - it "sets #size to nil" do - Enumerator::Lazy.new(Object.new, 100) {}.take(50) {}.send(@method) {}.size.should == nil - end - - describe "when the returned lazy enumerator is evaluated by Enumerable#first" do - it "stops after specified times" do - (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s }.first(6).should == %w[0 10 20 30 40 50] - - @eventsmixed.send(@method) {}.send(@method) {}.first(1) - ScratchPad.recorded.should == [:before_yield] - end - - it "flattens elements when the given block returned an array or responding to .each and .force" do - (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.chars }.first(6).should == %w[0 1 0 2 0 3] - (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.each_char }.first(6).all? { |o| o.instance_of? Enumerator }.should == true - (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.each_char.lazy }.first(6).should == %w[0 1 0 2 0 3] - end - end - end - - it "works with an infinite enumerable" do - s = 0..Float::INFINITY - s.lazy.send(@method) { |n| [-n, +n] }.first(200).should == - s.first(100).send(@method) { |n| [-n, +n] }.to_a - end -end diff --git a/core/enumerator/lazy/shared/select.rb b/core/enumerator/lazy/shared/select.rb deleted file mode 100644 index 2d1517662..000000000 --- a/core/enumerator/lazy/shared/select.rb +++ /dev/null @@ -1,66 +0,0 @@ -# -*- encoding: us-ascii -*- - -require_relative '../../../../spec_helper' -require_relative '../fixtures/classes' - -describe :enumerator_lazy_select, shared: true do - before :each do - @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy - @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy - ScratchPad.record [] - end - - after :each do - ScratchPad.clear - end - - it "returns a new instance of Enumerator::Lazy" do - ret = @yieldsmixed.send(@method) {} - ret.should.instance_of?(Enumerator::Lazy) - ret.should_not.equal?(@yieldsmixed) - end - - it "sets #size to nil" do - Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { true }.size.should == nil - end - - describe "when the returned lazy enumerator is evaluated by Enumerable#first" do - it "stops after specified times" do - (0..Float::INFINITY).lazy.send(@method, &:even?).first(3).should == [0, 2, 4] - - @eventsmixed.send(@method) { true }.first(1) - ScratchPad.recorded.should == [:before_yield] - end - end - - it "calls the block with a gathered array when yield with multiple arguments" do - yields = [] - @yieldsmixed.send(@method) { |v| yields << v }.force - yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields - end - - it "raises an ArgumentError when not given a block" do - -> { @yieldsmixed.send(@method) }.should.raise(ArgumentError) - end - - describe "on a nested Lazy" do - it "sets #size to nil" do - Enumerator::Lazy.new(Object.new, 100) {}.take(50) {}.send(@method) { true }.size.should == nil - end - - describe "when the returned lazy enumerator is evaluated by Enumerable#first" do - it "stops after specified times" do - (0..Float::INFINITY).lazy.send(@method) { |n| n > 5 }.send(@method, &:even?).first(3).should == [6, 8, 10] - - @eventsmixed.send(@method) { true }.send(@method) { true }.first(1) - ScratchPad.recorded.should == [:before_yield] - end - end - end - - it "works with an infinite enumerable" do - s = 0..Float::INFINITY - s.lazy.send(@method) { |n| true }.first(100).should == - s.first(100).send(@method) { |n| true } - end -end diff --git a/core/enumerator/lazy/shared/to_enum.rb b/core/enumerator/lazy/shared/to_enum.rb deleted file mode 100644 index 75b9aefe8..000000000 --- a/core/enumerator/lazy/shared/to_enum.rb +++ /dev/null @@ -1,55 +0,0 @@ -# -*- encoding: us-ascii -*- - -require_relative '../../../../spec_helper' - -describe :enumerator_lazy_to_enum, shared: true do - before :each do - @infinite = (0..Float::INFINITY).lazy - end - - it "requires multiple arguments" do - Enumerator::Lazy.instance_method(@method).arity.should < 0 - end - - it "returns a new instance of Enumerator::Lazy" do - ret = @infinite.send @method - ret.should.instance_of?(Enumerator::Lazy) - ret.should_not.equal?(@infinite) - end - - it "sets #size to nil when not given a block" do - Enumerator::Lazy.new(Object.new, 100) {}.send(@method).size.should == nil - end - - it "sets given block to size when given a block" do - Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { 30 }.size.should == 30 - end - - it "generates a lazy enumerator from the given name" do - @infinite.send(@method, :with_index, 10).first(3).should == [[0, 10], [1, 11], [2, 12]] - end - - it "passes given arguments to wrapped method" do - @infinite.send(@method, :each_slice, 2).map { |assoc| assoc.first * assoc.last }.first(4).should == [0, 6, 20, 42] - end - - it "used by some parent's methods though returning Lazy" do - { each_with_index: [], - with_index: [], - cycle: [1], - each_with_object: [Object.new], - with_object: [Object.new], - each_slice: [2], - each_entry: [], - each_cons: [2] - }.each_pair do |method, args| - @infinite.send(method, *args).should.instance_of?(Enumerator::Lazy) - end - end - - it "works with an infinite enumerable" do - s = 0..Float::INFINITY - s.lazy.send(@method, :with_index).first(100).should == - s.first(100).to_enum.send(@method, :with_index).to_a - end -end diff --git a/core/enumerator/lazy/to_enum_spec.rb b/core/enumerator/lazy/to_enum_spec.rb index 210e5294b..c0233d60f 100644 --- a/core/enumerator/lazy/to_enum_spec.rb +++ b/core/enumerator/lazy/to_enum_spec.rb @@ -1,8 +1,54 @@ -# -*- encoding: us-ascii -*- - require_relative '../../../spec_helper' -require_relative 'shared/to_enum' +require_relative 'fixtures/classes' describe "Enumerator::Lazy#to_enum" do - it_behaves_like :enumerator_lazy_to_enum, :to_enum + before :each do + @infinite = (0..Float::INFINITY).lazy + end + + it "requires multiple arguments" do + Enumerator::Lazy.instance_method(:to_enum).arity.should < 0 + end + + it "returns a new instance of Enumerator::Lazy" do + ret = @infinite.to_enum + ret.should.instance_of?(Enumerator::Lazy) + ret.should_not.equal?(@infinite) + end + + it "sets #size to nil when not given a block" do + Enumerator::Lazy.new(Object.new, 100) {}.to_enum.size.should == nil + end + + it "sets given block to size when given a block" do + Enumerator::Lazy.new(Object.new, 100) {}.to_enum { 30 }.size.should == 30 + end + + it "generates a lazy enumerator from the given name" do + @infinite.to_enum(:with_index, 10).first(3).should == [[0, 10], [1, 11], [2, 12]] + end + + it "passes given arguments to wrapped method" do + @infinite.to_enum(:each_slice, 2).map { |assoc| assoc.first * assoc.last }.first(4).should == [0, 6, 20, 42] + end + + it "used by some parent's methods though returning Lazy" do + { each_with_index: [], + with_index: [], + cycle: [1], + each_with_object: [Object.new], + with_object: [Object.new], + each_slice: [2], + each_entry: [], + each_cons: [2] + }.each_pair do |method, args| + @infinite.send(method, *args).should.instance_of?(Enumerator::Lazy) + end + end + + it "works with an infinite enumerable" do + s = 0..Float::INFINITY + s.lazy.to_enum(:with_index).first(100).should == + s.first(100).to_enum.to_enum(:with_index).to_a + end end diff --git a/core/enumerator/shared/enum_for.rb b/core/enumerator/shared/enum_for.rb deleted file mode 100644 index 4388103ec..000000000 --- a/core/enumerator/shared/enum_for.rb +++ /dev/null @@ -1,57 +0,0 @@ -describe :enum_for, shared: true do - it "is defined in Kernel" do - Kernel.method_defined?(@method).should == true - end - - it "returns a new enumerator" do - "abc".send(@method).should.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 "sets regexp matches in the caller" do - "wawa".send(@method, :scan, /./).map {|o| $& }.should == ["w", "a", "w", "a"] - a = [] - "wawa".send(@method, :scan, /./).each {|o| a << $& } - a.should == ["w", "a", "w", "a"] - 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.empty? - - enum.size - ScratchPad.recorded.should == [:called] - end -end diff --git a/core/enumerator/shared/with_object.rb b/core/enumerator/shared/with_object.rb deleted file mode 100644 index 50d4f24eb..000000000 --- a/core/enumerator/shared/with_object.rb +++ /dev/null @@ -1,42 +0,0 @@ -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.instance_of?(Enumerator) - ret.should_not.equal?(@enum) - end - end -end diff --git a/core/enumerator/to_enum_spec.rb b/core/enumerator/to_enum_spec.rb deleted file mode 100644 index 7fb73d0c3..000000000 --- a/core/enumerator/to_enum_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -require_relative '../../spec_helper' -require_relative 'shared/enum_for' - -describe "Enumerator#to_enum" do - it_behaves_like :enum_for, :to_enum -end diff --git a/core/enumerator/with_object_spec.rb b/core/enumerator/with_object_spec.rb index 58031fd76..790be66a1 100644 --- a/core/enumerator/with_object_spec.rb +++ b/core/enumerator/with_object_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/with_object' describe "Enumerator#with_object" do - it_behaves_like :enum_with_object, :with_object + it "is an alias of Enumerator#each_with_object" do + Enumerator.instance_method(:with_object).should == Enumerator.instance_method(:each_with_object) + end end diff --git a/core/env/each_pair_spec.rb b/core/env/each_pair_spec.rb index 2d7ed5faa..1acd2fbb0 100644 --- a/core/env/each_pair_spec.rb +++ b/core/env/each_pair_spec.rb @@ -1,6 +1,63 @@ require_relative 'spec_helper' -require_relative 'shared/each' +require_relative '../enumerable/shared/enumeratorized' describe "ENV.each_pair" do - it_behaves_like :env_each, :each_pair + it "returns each pair" do + orig = ENV.to_hash + e = [] + begin + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "boo" + ENV.each_pair { |k, v| e << [k, v] }.should.equal?(ENV) + e.should.include?(["foo", "bar"]) + e.should.include?(["baz", "boo"]) + ensure + ENV.replace orig + end + end + + it "returns an Enumerator if called without a block" do + enum = ENV.each_pair + enum.should.instance_of?(Enumerator) + enum.each do |name, value| + ENV[name].should == value + end + end + + it_behaves_like :enumeratorized_with_origin_size, :each_pair, ENV + + describe "with encoding" do + before :each do + @external = Encoding.default_external + @internal = Encoding.default_internal + + Encoding.default_external = Encoding::BINARY + end + + after :each do + Encoding.default_external = @external + Encoding.default_internal = @internal + end + + it "uses the locale encoding when Encoding.default_internal is nil" do + Encoding.default_internal = nil + + ENV.each_pair do |key, value| + key.should.be_locale_env + value.should.be_locale_env + end + end + + it "transcodes from the locale encoding to Encoding.default_internal if set" do + Encoding.default_internal = internal = Encoding::IBM437 + + ENV.each_pair do |key, value| + key.encoding.should.equal?(internal) + if value.ascii_only? + value.encoding.should.equal?(internal) + end + end + end + end end diff --git a/core/env/each_spec.rb b/core/env/each_spec.rb index d1e06f55b..166a0b4fc 100644 --- a/core/env/each_spec.rb +++ b/core/env/each_spec.rb @@ -1,6 +1,7 @@ require_relative 'spec_helper' -require_relative 'shared/each' describe "ENV.each" do - it_behaves_like :env_each, :each + it "is an alias of ENV.each_pair" do + ENV.method(:each).should == ENV.method(:each_pair) + end end diff --git a/core/env/element_set_spec.rb b/core/env/element_set_spec.rb index 26dfee1ad..ca5d46800 100644 --- a/core/env/element_set_spec.rb +++ b/core/env/element_set_spec.rb @@ -1,6 +1,62 @@ require_relative '../../spec_helper' -require_relative 'shared/store' describe "ENV.[]=" do - it_behaves_like :env_store, :[]= + before :each do + @saved_foo = ENV["foo"] + end + + after :each do + ENV["foo"] = @saved_foo + end + + it "sets the environment variable to the given value" do + ENV["foo"] = "bar" + ENV["foo"].should == "bar" + end + + it "returns the value" do + value = "bar" + ENV.send(:[]=, "foo", value).should.equal?(value) + end + + it "deletes the environment variable when the value is nil" do + ENV["foo"] = "bar" + ENV["foo"] = nil + ENV.key?("foo").should == false + end + + it "coerces the key argument with #to_str" do + k = mock("key") + k.should_receive(:to_str).and_return("foo") + ENV[k] = "bar" + ENV["foo"].should == "bar" + end + + it "coerces the value argument with #to_str" do + v = mock("value") + v.should_receive(:to_str).and_return("bar") + ENV["foo"] = v + ENV["foo"].should == "bar" + end + + it "raises TypeError when the key is not coercible to String" do + -> { ENV[Object.new] = "bar" }.should.raise(TypeError, "no implicit conversion of Object into String") + end + + it "raises TypeError when the value is not coercible to String" do + -> { ENV["foo"] = Object.new }.should.raise(TypeError, "no implicit conversion of Object into String") + end + + it "raises Errno::EINVAL when the key contains the '=' character" do + -> { ENV["foo="] = "bar" }.should.raise(Errno::EINVAL) + end + + it "raises Errno::EINVAL when the key is an empty string" do + -> { ENV[""] = "bar" }.should.raise(Errno::EINVAL) + end + + it "does nothing when the key is not a valid environment variable key and the value is nil" do + ENV["foo="] = nil + ENV.key?("foo=").should == false + end end diff --git a/core/env/filter_spec.rb b/core/env/filter_spec.rb index 52f8b79a0..54997bfda 100644 --- a/core/env/filter_spec.rb +++ b/core/env/filter_spec.rb @@ -1,13 +1,13 @@ require_relative '../../spec_helper' -require_relative '../enumerable/shared/enumeratorized' -require_relative 'shared/select' describe "ENV.filter!" do - it_behaves_like :env_select!, :filter! - it_behaves_like :enumeratorized_with_origin_size, :filter!, ENV + it "is an alias of ENV.select!" do + ENV.method(:filter!).should == ENV.method(:select!) + end end describe "ENV.filter" do - it_behaves_like :env_select, :filter - it_behaves_like :enumeratorized_with_origin_size, :filter, ENV + it "is an alias of ENV.select" do + ENV.method(:filter).should == ENV.method(:select) + end end diff --git a/core/env/has_key_spec.rb b/core/env/has_key_spec.rb index 798668105..7feeec8df 100644 --- a/core/env/has_key_spec.rb +++ b/core/env/has_key_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/include' describe "ENV.has_key?" do - it_behaves_like :env_include, :has_key? + it "is an alias of ENV.include?" do + ENV.method(:has_key?).should == ENV.method(:include?) + end end diff --git a/core/env/has_value_spec.rb b/core/env/has_value_spec.rb index a2bf3eb87..b76ec0dc5 100644 --- a/core/env/has_value_spec.rb +++ b/core/env/has_value_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/value' describe "ENV.has_value?" do - it_behaves_like :env_value, :has_value? + it "is an alias of ENV.value?" do + ENV.method(:has_value?).should == ENV.method(:value?) + end end diff --git a/core/env/include_spec.rb b/core/env/include_spec.rb index 3975f095a..8e68aa3bf 100644 --- a/core/env/include_spec.rb +++ b/core/env/include_spec.rb @@ -1,6 +1,32 @@ require_relative '../../spec_helper' -require_relative 'shared/include' describe "ENV.include?" do - it_behaves_like :env_include, :include? + before :each do + @saved_foo = ENV["foo"] + end + + after :each do + ENV["foo"] = @saved_foo + end + + it "returns true if ENV has the key" do + ENV["foo"] = "bar" + ENV.include?( "foo").should == true + end + + it "returns false if ENV doesn't include the key" do + ENV.delete("foo") + ENV.include?( "foo").should == false + end + + it "coerces the key with #to_str" do + ENV["foo"] = "bar" + k = mock('key') + k.should_receive(:to_str).and_return("foo") + ENV.include?( k).should == true + end + + it "raises TypeError if the argument is not a String and does not respond to #to_str" do + -> { ENV.include?( Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String") + end end diff --git a/core/env/key_spec.rb b/core/env/key_spec.rb index 677cf3521..56f5f609a 100644 --- a/core/env/key_spec.rb +++ b/core/env/key_spec.rb @@ -1,8 +1,9 @@ require_relative '../../spec_helper' -require_relative 'shared/include' describe "ENV.key?" do - it_behaves_like :env_include, :key? + it "is an alias of ENV.include?" do + ENV.method(:key?).should == ENV.method(:include?) + end end describe "ENV.key" do diff --git a/core/env/length_spec.rb b/core/env/length_spec.rb index c6f906289..6baac6f7a 100644 --- a/core/env/length_spec.rb +++ b/core/env/length_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/length' describe "ENV.length" do - it_behaves_like :env_length, :length + it "is an alias of ENV.size" do + ENV.method(:length).should == ENV.method(:size) + end end diff --git a/core/env/member_spec.rb b/core/env/member_spec.rb index 9119022ae..f062d4119 100644 --- a/core/env/member_spec.rb +++ b/core/env/member_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/include' describe "ENV.member?" do - it_behaves_like :env_include, :member? + it "is an alias of ENV.include?" do + ENV.method(:member?).should == ENV.method(:include?) + end end diff --git a/core/env/merge_spec.rb b/core/env/merge_spec.rb index f10662cf7..78231afbb 100644 --- a/core/env/merge_spec.rb +++ b/core/env/merge_spec.rb @@ -1,6 +1,106 @@ require_relative '../../spec_helper' -require_relative 'shared/update' describe "ENV.merge!" do - it_behaves_like :env_update, :merge! + before :each do + @saved_foo = ENV["foo"] + @saved_bar = ENV["bar"] + end + + after :each do + ENV["foo"] = @saved_foo + ENV["bar"] = @saved_bar + end + + it "adds the parameter hash to ENV, returning ENV" do + ENV.merge!("foo" => "0", "bar" => "1").should.equal?(ENV) + ENV["foo"].should == "0" + ENV["bar"].should == "1" + end + + it "adds the multiple parameter hashes to ENV, returning ENV" do + ENV.merge!({"foo" => "multi1"}, {"bar" => "multi2"}).should.equal?(ENV) + ENV["foo"].should == "multi1" + ENV["bar"].should == "multi2" + end + + it "returns ENV when no block given" do + ENV.merge!({"foo" => "0", "bar" => "1"}).should.equal?(ENV) + end + + it "yields key, the old value and the new value when replacing an entry" do + ENV.merge!({"foo" => "0", "bar" => "3"}) + a = [] + ENV.merge!({"foo" => "1", "bar" => "4"}) do |key, old, new| + a << [key, old, new] + new + end + a[0].should == ["foo", "0", "1"] + a[1].should == ["bar", "3", "4"] + end + + it "yields key, the old value and the new value when replacing an entry" do + ENV.merge!({"foo" => "0", "bar" => "3"}) + ENV.merge!({"foo" => "1", "bar" => "4"}) do |key, old, new| + (new.to_i + 1).to_s + end + ENV["foo"].should == "2" + ENV["bar"].should == "5" + end + + # BUG: https://bugs.ruby-lang.org/issues/16192 + it "does not evaluate the block when the name is new" do + ENV.delete("bar") + ENV.merge!({"foo" => "0"}) + ENV.merge!("bar" => "1") { |key, old, new| fail "Should not get here" } + ENV["bar"].should == "1" + end + + # BUG: https://bugs.ruby-lang.org/issues/16192 + it "does not use the block's return value as the value when the name is new" do + ENV.delete("bar") + ENV.merge!({"foo" => "0"}) + ENV.merge!("bar" => "1") { |key, old, new| "Should not use this value" } + ENV["foo"].should == "0" + ENV["bar"].should == "1" + end + + it "returns ENV when block given" do + ENV.merge!({"foo" => "0", "bar" => "1"}){}.should.equal?(ENV) + end + + it "raises TypeError when a name is not coercible to String" do + -> { ENV.merge!(Object.new => "0") }.should.raise(TypeError, "no implicit conversion of Object into String") + end + + it "raises TypeError when a value is not coercible to String" do + -> { ENV.merge!("foo" => Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String") + end + + it "raises Errno::EINVAL when a name contains the '=' character" do + -> { ENV.merge!("foo=" => "bar") }.should.raise(Errno::EINVAL) + end + + it "raises Errno::EINVAL when a name is an empty string" do + -> { ENV.merge!("" => "bar") }.should.raise(Errno::EINVAL) + end + + it "updates good data preceding an error" do + ENV["foo"] = "0" + begin + ENV.merge!({"foo" => "2", Object.new => "1"}) + rescue TypeError + ensure + ENV["foo"].should == "2" + end + end + + it "does not update good data following an error" do + ENV["foo"] = "0" + begin + ENV.merge!({Object.new => "1", "foo" => "2"}) + rescue TypeError + ensure + ENV["foo"].should == "0" + end + end end diff --git a/core/env/select_spec.rb b/core/env/select_spec.rb index c3a76f443..2b92d6155 100644 --- a/core/env/select_spec.rb +++ b/core/env/select_spec.rb @@ -1,13 +1,68 @@ require_relative '../../spec_helper' require_relative '../enumerable/shared/enumeratorized' -require_relative 'shared/select' describe "ENV.select!" do - it_behaves_like :env_select!, :select! it_behaves_like :enumeratorized_with_origin_size, :select!, ENV + + before :each do + @saved_foo = ENV["foo"] + end + + after :each do + ENV["foo"] = @saved_foo + end + + it "removes environment variables for which the block returns false" do + ENV["foo"] = "bar" + ENV.select! { |k, v| k != "foo" } + ENV["foo"].should == nil + end + + it "returns self if any changes were made" do + ENV["foo"] = "bar" + (ENV.select! { |k, v| k != "foo" }).should == ENV + end + + it "returns nil if no changes were made" do + (ENV.select! { true }).should == nil + end + + it "returns an Enumerator if called without a block" do + ENV.select!.should.instance_of?(Enumerator) + end + + it "selects via the enumerator" do + enum = ENV.select! + ENV["foo"] = "bar" + enum.each { |k, v| k != "foo" } + ENV["foo"].should == nil + end end describe "ENV.select" do - it_behaves_like :env_select, :select it_behaves_like :enumeratorized_with_origin_size, :select, ENV + + before :each do + @saved_foo = ENV["foo"] + end + + after :each do + ENV["foo"] = @saved_foo + end + + it "returns a Hash of names and values for which block returns true" do + ENV["foo"] = "bar" + (ENV.select { |k, v| k == "foo" }).should == { "foo" => "bar" } + end + + it "returns an Enumerator when no block is given" do + enum = ENV.select + enum.should.instance_of?(Enumerator) + end + + it "selects via the enumerator" do + enum = ENV.select + ENV["foo"] = "bar" + enum.each { |k, v| k == "foo" }.should == { "foo" => "bar"} + end end diff --git a/core/env/shared/each.rb b/core/env/shared/each.rb deleted file mode 100644 index 0661ca924..000000000 --- a/core/env/shared/each.rb +++ /dev/null @@ -1,65 +0,0 @@ -require_relative '../../enumerable/shared/enumeratorized' - -describe :env_each, shared: true do - it "returns each pair" do - orig = ENV.to_hash - e = [] - begin - ENV.clear - ENV["foo"] = "bar" - ENV["baz"] = "boo" - ENV.send(@method) { |k, v| e << [k, v] }.should.equal?(ENV) - e.should.include?(["foo", "bar"]) - e.should.include?(["baz", "boo"]) - ensure - ENV.replace orig - end - end - - it "returns an Enumerator if called without a block" do - enum = ENV.send(@method) - enum.should.instance_of?(Enumerator) - enum.each do |name, value| - ENV[name].should == value - end - end - - before :all do - @object = ENV - end - it_should_behave_like :enumeratorized_with_origin_size - - describe "with encoding" do - before :each do - @external = Encoding.default_external - @internal = Encoding.default_internal - - Encoding.default_external = Encoding::BINARY - end - - after :each do - Encoding.default_external = @external - Encoding.default_internal = @internal - end - - it "uses the locale encoding when Encoding.default_internal is nil" do - Encoding.default_internal = nil - - ENV.send(@method) do |key, value| - key.should.be_locale_env - value.should.be_locale_env - end - end - - it "transcodes from the locale encoding to Encoding.default_internal if set" do - Encoding.default_internal = internal = Encoding::IBM437 - - ENV.send(@method) do |key, value| - key.encoding.should.equal?(internal) - if value.ascii_only? - value.encoding.should.equal?(internal) - end - end - end - end -end diff --git a/core/env/shared/include.rb b/core/env/shared/include.rb deleted file mode 100644 index ceca02e3e..000000000 --- a/core/env/shared/include.rb +++ /dev/null @@ -1,30 +0,0 @@ -describe :env_include, shared: true do - before :each do - @saved_foo = ENV["foo"] - end - - after :each do - ENV["foo"] = @saved_foo - end - - it "returns true if ENV has the key" do - ENV["foo"] = "bar" - ENV.send(@method, "foo").should == true - end - - it "returns false if ENV doesn't include the key" do - ENV.delete("foo") - ENV.send(@method, "foo").should == false - end - - it "coerces the key with #to_str" do - ENV["foo"] = "bar" - k = mock('key') - k.should_receive(:to_str).and_return("foo") - ENV.send(@method, k).should == true - end - - it "raises TypeError if the argument is not a String and does not respond to #to_str" do - -> { ENV.send(@method, Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String") - end -end diff --git a/core/env/shared/length.rb b/core/env/shared/length.rb deleted file mode 100644 index 6d788a3f4..000000000 --- a/core/env/shared/length.rb +++ /dev/null @@ -1,13 +0,0 @@ -describe :env_length, shared: true do - it "returns the number of ENV entries" do - orig = ENV.to_hash - begin - ENV.clear - ENV["foo"] = "bar" - ENV["baz"] = "boo" - ENV.send(@method).should == 2 - ensure - ENV.replace orig - end - end -end diff --git a/core/env/shared/select.rb b/core/env/shared/select.rb deleted file mode 100644 index 8ec648a63..000000000 --- a/core/env/shared/select.rb +++ /dev/null @@ -1,61 +0,0 @@ -describe :env_select, shared: true do - before :each do - @saved_foo = ENV["foo"] - end - - after :each do - ENV["foo"] = @saved_foo - end - - it "returns a Hash of names and values for which block return true" do - ENV["foo"] = "bar" - (ENV.send(@method) { |k, v| k == "foo" }).should == { "foo" => "bar" } - end - - it "returns an Enumerator when no block is given" do - enum = ENV.send(@method) - enum.should.instance_of?(Enumerator) - end - - it "selects via the enumerator" do - enum = ENV.send(@method) - ENV["foo"] = "bar" - enum.each { |k, v| k == "foo" }.should == { "foo" => "bar"} - end -end - -describe :env_select!, shared: true do - before :each do - @saved_foo = ENV["foo"] - end - - after :each do - ENV["foo"] = @saved_foo - end - - it "removes environment variables for which the block returns true" do - ENV["foo"] = "bar" - ENV.send(@method) { |k, v| k != "foo" } - ENV["foo"].should == nil - end - - it "returns self if any changes were made" do - ENV["foo"] = "bar" - (ENV.send(@method) { |k, v| k != "foo" }).should == ENV - end - - it "returns nil if no changes were made" do - (ENV.send(@method) { true }).should == nil - end - - it "returns an Enumerator if called without a block" do - ENV.send(@method).should.instance_of?(Enumerator) - end - - it "selects via the enumerator" do - enum = ENV.send(@method) - ENV["foo"] = "bar" - enum.each { |k, v| k != "foo" } - ENV["foo"].should == nil - end -end diff --git a/core/env/shared/store.rb b/core/env/shared/store.rb deleted file mode 100644 index 388208a8a..000000000 --- a/core/env/shared/store.rb +++ /dev/null @@ -1,60 +0,0 @@ -describe :env_store, shared: true do - before :each do - @saved_foo = ENV["foo"] - end - - after :each do - ENV["foo"] = @saved_foo - end - - it "sets the environment variable to the given value" do - ENV.send(@method, "foo", "bar") - ENV["foo"].should == "bar" - end - - it "returns the value" do - value = "bar" - ENV.send(@method, "foo", value).should.equal?(value) - end - - it "deletes the environment variable when the value is nil" do - ENV["foo"] = "bar" - ENV.send(@method, "foo", nil) - ENV.key?("foo").should == false - end - - it "coerces the key argument with #to_str" do - k = mock("key") - k.should_receive(:to_str).and_return("foo") - ENV.send(@method, k, "bar") - ENV["foo"].should == "bar" - end - - it "coerces the value argument with #to_str" do - v = mock("value") - v.should_receive(:to_str).and_return("bar") - ENV.send(@method, "foo", v) - ENV["foo"].should == "bar" - end - - it "raises TypeError when the key is not coercible to String" do - -> { ENV.send(@method, Object.new, "bar") }.should.raise(TypeError, "no implicit conversion of Object into String") - end - - it "raises TypeError when the value is not coercible to String" do - -> { ENV.send(@method, "foo", Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String") - end - - it "raises Errno::EINVAL when the key contains the '=' character" do - -> { ENV.send(@method, "foo=", "bar") }.should.raise(Errno::EINVAL) - end - - it "raises Errno::EINVAL when the key is an empty string" do - -> { ENV.send(@method, "", "bar") }.should.raise(Errno::EINVAL) - end - - it "does nothing when the key is not a valid environment variable key and the value is nil" do - ENV.send(@method, "foo=", nil) - ENV.key?("foo=").should == false - end -end diff --git a/core/env/shared/update.rb b/core/env/shared/update.rb deleted file mode 100644 index 112cc2505..000000000 --- a/core/env/shared/update.rb +++ /dev/null @@ -1,104 +0,0 @@ -describe :env_update, shared: true do - before :each do - @saved_foo = ENV["foo"] - @saved_bar = ENV["bar"] - end - - after :each do - ENV["foo"] = @saved_foo - ENV["bar"] = @saved_bar - end - - it "adds the parameter hash to ENV, returning ENV" do - ENV.send(@method, "foo" => "0", "bar" => "1").should.equal?(ENV) - ENV["foo"].should == "0" - ENV["bar"].should == "1" - end - - it "adds the multiple parameter hashes to ENV, returning ENV" do - ENV.send(@method, {"foo" => "multi1"}, {"bar" => "multi2"}).should.equal?(ENV) - ENV["foo"].should == "multi1" - ENV["bar"].should == "multi2" - end - - it "returns ENV when no block given" do - ENV.send(@method, {"foo" => "0", "bar" => "1"}).should.equal?(ENV) - end - - it "yields key, the old value and the new value when replacing an entry" do - ENV.send @method, {"foo" => "0", "bar" => "3"} - a = [] - ENV.send @method, {"foo" => "1", "bar" => "4"} do |key, old, new| - a << [key, old, new] - new - end - a[0].should == ["foo", "0", "1"] - a[1].should == ["bar", "3", "4"] - end - - it "yields key, the old value and the new value when replacing an entry" do - ENV.send @method, {"foo" => "0", "bar" => "3"} - ENV.send @method, {"foo" => "1", "bar" => "4"} do |key, old, new| - (new.to_i + 1).to_s - end - ENV["foo"].should == "2" - ENV["bar"].should == "5" - end - - # BUG: https://bugs.ruby-lang.org/issues/16192 - it "does not evaluate the block when the name is new" do - ENV.delete("bar") - ENV.send @method, {"foo" => "0"} - ENV.send(@method, "bar" => "1") { |key, old, new| fail "Should not get here" } - ENV["bar"].should == "1" - end - - # BUG: https://bugs.ruby-lang.org/issues/16192 - it "does not use the block's return value as the value when the name is new" do - ENV.delete("bar") - ENV.send @method, {"foo" => "0"} - ENV.send(@method, "bar" => "1") { |key, old, new| "Should not use this value" } - ENV["foo"].should == "0" - ENV["bar"].should == "1" - end - - it "returns ENV when block given" do - ENV.send(@method, {"foo" => "0", "bar" => "1"}){}.should.equal?(ENV) - end - - it "raises TypeError when a name is not coercible to String" do - -> { ENV.send @method, Object.new => "0" }.should.raise(TypeError, "no implicit conversion of Object into String") - end - - it "raises TypeError when a value is not coercible to String" do - -> { ENV.send @method, "foo" => Object.new }.should.raise(TypeError, "no implicit conversion of Object into String") - end - - it "raises Errno::EINVAL when a name contains the '=' character" do - -> { ENV.send(@method, "foo=" => "bar") }.should.raise(Errno::EINVAL) - end - - it "raises Errno::EINVAL when a name is an empty string" do - -> { ENV.send(@method, "" => "bar") }.should.raise(Errno::EINVAL) - end - - it "updates good data preceding an error" do - ENV["foo"] = "0" - begin - ENV.send @method, {"foo" => "2", Object.new => "1"} - rescue TypeError - ensure - ENV["foo"].should == "2" - end - end - - it "does not update good data following an error" do - ENV["foo"] = "0" - begin - ENV.send @method, {Object.new => "1", "foo" => "2"} - rescue TypeError - ensure - ENV["foo"].should == "0" - end - end -end diff --git a/core/env/shared/value.rb b/core/env/shared/value.rb deleted file mode 100644 index c2b502546..000000000 --- a/core/env/shared/value.rb +++ /dev/null @@ -1,29 +0,0 @@ -describe :env_value, shared: true do - before :each do - @saved_foo = ENV["foo"] - end - - after :each do - ENV["foo"] = @saved_foo - end - - it "returns true if ENV has the value" do - ENV["foo"] = "bar" - ENV.send(@method, "bar").should == true - end - - it "returns false if ENV doesn't have the value" do - ENV.send(@method, "foo").should == false - end - - it "coerces the value element with #to_str" do - ENV["foo"] = "bar" - v = mock('value') - v.should_receive(:to_str).and_return("bar") - ENV.send(@method, v).should == true - end - - it "returns nil if the argument is not a String and does not respond to #to_str" do - ENV.send(@method, Object.new).should == nil - end -end diff --git a/core/env/size_spec.rb b/core/env/size_spec.rb index 7c8072481..9c6de20df 100644 --- a/core/env/size_spec.rb +++ b/core/env/size_spec.rb @@ -1,6 +1,15 @@ require_relative '../../spec_helper' -require_relative 'shared/length' describe "ENV.size" do - it_behaves_like :env_length, :size + it "returns the number of ENV entries" do + orig = ENV.to_hash + begin + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "boo" + ENV.size.should == 2 + ensure + ENV.replace orig + end + end end diff --git a/core/env/store_spec.rb b/core/env/store_spec.rb index b4700e0a9..a5fd7e1e2 100644 --- a/core/env/store_spec.rb +++ b/core/env/store_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/store' describe "ENV.store" do - it_behaves_like :env_store, :store + it "is an alias of ENV.[]=" do + ENV.method(:store).should == ENV.method(:[]=) + end end diff --git a/core/env/update_spec.rb b/core/env/update_spec.rb index 95a8a2eb4..44d05d617 100644 --- a/core/env/update_spec.rb +++ b/core/env/update_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/update' describe "ENV.update" do - it_behaves_like :env_update, :update + it "is an alias of ENV.merge!" do + ENV.method(:update).should == ENV.method(:merge!) + end end diff --git a/core/env/value_spec.rb b/core/env/value_spec.rb index 906e86ab3..c732cfbd1 100644 --- a/core/env/value_spec.rb +++ b/core/env/value_spec.rb @@ -1,6 +1,31 @@ require_relative '../../spec_helper' -require_relative 'shared/value' describe "ENV.value?" do - it_behaves_like :env_value, :value? + before :each do + @saved_foo = ENV["foo"] + end + + after :each do + ENV["foo"] = @saved_foo + end + + it "returns true if ENV has the value" do + ENV["foo"] = "bar" + ENV.value?("bar").should == true + end + + it "returns false if ENV doesn't have the value" do + ENV.value?("foo").should == false + end + + it "coerces the value element with #to_str" do + ENV["foo"] = "bar" + v = mock('value') + v.should_receive(:to_str).and_return("bar") + ENV.value?(v).should == true + end + + it "returns nil if the argument is not a String and does not respond to #to_str" do + ENV.value?(Object.new).should == nil + end end diff --git a/core/fiber/scheduler_spec.rb b/core/fiber/scheduler_spec.rb index 15a03c147..2a517ba93 100644 --- a/core/fiber/scheduler_spec.rb +++ b/core/fiber/scheduler_spec.rb @@ -1,8 +1,5 @@ require_relative '../../spec_helper' -require_relative 'shared/scheduler' - -require "fiber" describe "Fiber.scheduler" do - it_behaves_like :scheduler, :scheduler + it "is already tested in Fiber.set_scheduler" end diff --git a/core/fiber/set_scheduler_spec.rb b/core/fiber/set_scheduler_spec.rb index 82f6acbe8..b34aff873 100644 --- a/core/fiber/set_scheduler_spec.rb +++ b/core/fiber/set_scheduler_spec.rb @@ -1,8 +1,55 @@ require_relative '../../spec_helper' -require_relative 'shared/scheduler' require "fiber" describe "Fiber.scheduler" do - it_behaves_like :scheduler, :set_scheduler + it "validates the scheduler for required methods" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + required_methods.each do |missing_method| + scheduler = Object.new + required_methods.difference([missing_method]).each do |method| + scheduler.define_singleton_method(method) {} + end + -> { + suppress_warning { Fiber.set_scheduler(scheduler) } + }.should.raise(ArgumentError, /Scheduler must implement ##{missing_method}/) + end + end + + it "can set and get the scheduler" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + scheduler = Object.new + required_methods.each do |method| + scheduler.define_singleton_method(method) {} + end + suppress_warning { Fiber.set_scheduler(scheduler) } + Fiber.scheduler.should == scheduler + end + + it "returns the scheduler after setting it" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + scheduler = Object.new + required_methods.each do |method| + scheduler.define_singleton_method(method) {} + end + result = suppress_warning { Fiber.set_scheduler(scheduler) } + result.should == scheduler + end + + it "can remove the scheduler" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + scheduler = Object.new + required_methods.each do |method| + scheduler.define_singleton_method(method) {} + end + suppress_warning { Fiber.set_scheduler(scheduler) } + Fiber.set_scheduler(nil) + Fiber.scheduler.should == nil + end + + it "can assign a nil scheduler multiple times" do + Fiber.set_scheduler(nil) + Fiber.set_scheduler(nil) + Fiber.scheduler.should == nil + end end diff --git a/core/fiber/shared/scheduler.rb b/core/fiber/shared/scheduler.rb deleted file mode 100644 index 04bdded53..000000000 --- a/core/fiber/shared/scheduler.rb +++ /dev/null @@ -1,51 +0,0 @@ -describe :scheduler, shared: true do - it "validates the scheduler for required methods" do - required_methods = [:block, :unblock, :kernel_sleep, :io_wait] - required_methods.each do |missing_method| - scheduler = Object.new - required_methods.difference([missing_method]).each do |method| - scheduler.define_singleton_method(method) {} - end - -> { - suppress_warning { Fiber.set_scheduler(scheduler) } - }.should.raise(ArgumentError, /Scheduler must implement ##{missing_method}/) - end - end - - it "can set and get the scheduler" do - required_methods = [:block, :unblock, :kernel_sleep, :io_wait] - scheduler = Object.new - required_methods.each do |method| - scheduler.define_singleton_method(method) {} - end - suppress_warning { Fiber.set_scheduler(scheduler) } - Fiber.scheduler.should == scheduler - end - - it "returns the scheduler after setting it" do - required_methods = [:block, :unblock, :kernel_sleep, :io_wait] - scheduler = Object.new - required_methods.each do |method| - scheduler.define_singleton_method(method) {} - end - result = suppress_warning { Fiber.set_scheduler(scheduler) } - result.should == scheduler - end - - it "can remove the scheduler" do - required_methods = [:block, :unblock, :kernel_sleep, :io_wait] - scheduler = Object.new - required_methods.each do |method| - scheduler.define_singleton_method(method) {} - end - suppress_warning { Fiber.set_scheduler(scheduler) } - Fiber.set_scheduler(nil) - Fiber.scheduler.should == nil - end - - it "can assign a nil scheduler multiple times" do - Fiber.set_scheduler(nil) - Fiber.set_scheduler(nil) - Fiber.scheduler.should == nil - end -end diff --git a/core/file/delete_spec.rb b/core/file/delete_spec.rb index 409849994..7149b8a37 100644 --- a/core/file/delete_spec.rb +++ b/core/file/delete_spec.rb @@ -1,6 +1,63 @@ require_relative '../../spec_helper' -require_relative 'shared/unlink' describe "File.delete" do - it_behaves_like :file_unlink, :delete + before :each do + @file1 = tmp('test.txt') + @file2 = tmp('test2.txt') + + touch @file1 + touch @file2 + end + + after :each do + File.delete(@file1) if File.exist?(@file1) + File.delete(@file2) if File.exist?(@file2) + + @file1 = nil + @file2 = nil + end + + it "returns 0 when called without arguments" do + File.delete.should == 0 + end + + it "deletes a single file" do + File.delete(@file1).should == 1 + File.should_not.exist?(@file1) + end + + it "deletes multiple files" do + File.delete(@file1, @file2).should == 2 + File.should_not.exist?(@file1) + File.should_not.exist?(@file2) + end + + it "raises a TypeError if not passed a String type" do + -> { File.delete(1) }.should.raise(TypeError) + end + + it "raises an Errno::ENOENT when the given file doesn't exist" do + -> { File.delete('bogus') }.should.raise(Errno::ENOENT) + end + + it "coerces a given parameter into a string if possible" do + mock = mock("to_str") + mock.should_receive(:to_str).and_return(@file1) + File.delete(mock).should == 1 + end + + it "accepts an object that has a #to_path method" do + File.delete(mock_to_path(@file1)).should == 1 + end + + platform_is :windows do + it "allows deleting an open file with File::SHARE_DELETE" do + path = tmp("share_delete.txt") + File.open(path, mode: File::CREAT | File::WRONLY | File::BINARY | File::SHARE_DELETE) do |f| + File.should.exist?(path) + File.delete(path) + end + File.should_not.exist?(path) + end + end end diff --git a/core/file/fnmatch_spec.rb b/core/file/fnmatch_spec.rb index a1b7fa12b..44a143bdd 100644 --- a/core/file/fnmatch_spec.rb +++ b/core/file/fnmatch_spec.rb @@ -1,10 +1,302 @@ require_relative '../../spec_helper' -require_relative 'shared/fnmatch' describe "File.fnmatch" do - it_behaves_like :file_fnmatch, :fnmatch + it "matches entire strings" do + File.fnmatch('cat', 'cat').should == true + end + + it "does not match partial strings" do + File.fnmatch('cat', 'category').should == false + end + + it "does not support { } patterns by default" do + File.fnmatch('c{at,ub}s', 'cats').should == false + File.fnmatch('c{at,ub}s', 'c{at,ub}s').should == true + end + + it "supports some { } patterns when File::FNM_EXTGLOB is passed" do + File.fnmatch("{a,b}", "a", File::FNM_EXTGLOB).should == true + File.fnmatch("{a,b}", "b", File::FNM_EXTGLOB).should == true + File.fnmatch("c{at,ub}s", "cats", File::FNM_EXTGLOB).should == true + File.fnmatch("c{at,ub}s", "cubs", File::FNM_EXTGLOB).should == true + File.fnmatch("-c{at,ub}s-", "-cats-", File::FNM_EXTGLOB).should == true + File.fnmatch("-c{at,ub}s-", "-cubs-", File::FNM_EXTGLOB).should == true + File.fnmatch("{a,b,c}{d,e,f}{g,h}", "adg", File::FNM_EXTGLOB).should == true + File.fnmatch("{a,b,c}{d,e,f}{g,h}", "bdg", File::FNM_EXTGLOB).should == true + File.fnmatch("{a,b,c}{d,e,f}{g,h}", "ceh", File::FNM_EXTGLOB).should == true + File.fnmatch("{aa,bb,cc,dd}", "aa", File::FNM_EXTGLOB).should == true + File.fnmatch("{aa,bb,cc,dd}", "bb", File::FNM_EXTGLOB).should == true + File.fnmatch("{aa,bb,cc,dd}", "cc", File::FNM_EXTGLOB).should == true + File.fnmatch("{aa,bb,cc,dd}", "dd", File::FNM_EXTGLOB).should == true + File.fnmatch("{1,5{a,b{c,d}}}", "1", File::FNM_EXTGLOB).should == true + File.fnmatch("{1,5{a,b{c,d}}}", "5a", File::FNM_EXTGLOB).should == true + File.fnmatch("{1,5{a,b{c,d}}}", "5bc", File::FNM_EXTGLOB).should == true + File.fnmatch("{1,5{a,b{c,d}}}", "5bd", File::FNM_EXTGLOB).should == true + File.fnmatch("\\\\{a\\,b,b\\}c}", "\\a,b", File::FNM_EXTGLOB).should == true + File.fnmatch("\\\\{a\\,b,b\\}c}", "\\b}c", File::FNM_EXTGLOB).should == true + end + + it "doesn't support some { } patterns even when File::FNM_EXTGLOB is passed" do + File.fnmatch("a{0..3}b", "a0b", File::FNM_EXTGLOB).should == false + File.fnmatch("a{0..3}b", "a1b", File::FNM_EXTGLOB).should == false + File.fnmatch("a{0..3}b", "a2b", File::FNM_EXTGLOB).should == false + File.fnmatch("a{0..3}b", "a3b", File::FNM_EXTGLOB).should == false + File.fnmatch("{0..12}", "0", File::FNM_EXTGLOB).should == false + File.fnmatch("{0..12}", "6", File::FNM_EXTGLOB).should == false + File.fnmatch("{0..12}", "12", File::FNM_EXTGLOB).should == false + File.fnmatch("{3..-2}", "3", File::FNM_EXTGLOB).should == false + File.fnmatch("{3..-2}", "0", File::FNM_EXTGLOB).should == false + File.fnmatch("{3..-2}", "-2", File::FNM_EXTGLOB).should == false + File.fnmatch("{a..g}", "a", File::FNM_EXTGLOB).should == false + File.fnmatch("{a..g}", "d", File::FNM_EXTGLOB).should == false + File.fnmatch("{a..g}", "g", File::FNM_EXTGLOB).should == false + File.fnmatch("{g..a}", "a", File::FNM_EXTGLOB).should == false + File.fnmatch("{g..a}", "d", File::FNM_EXTGLOB).should == false + File.fnmatch("{g..a}", "g", File::FNM_EXTGLOB).should == false + File.fnmatch("escaping: {{,\\,,\\},\\{}", "escaping: {", File::FNM_EXTGLOB).should == false + File.fnmatch("escaping: {{,\\,,\\},\\{}", "escaping: ,", File::FNM_EXTGLOB).should == false + File.fnmatch("escaping: {{,\\,,\\},\\{}", "escaping: }", File::FNM_EXTGLOB).should == false + File.fnmatch("escaping: {{,\\,,\\},\\{}", "escaping: {", File::FNM_EXTGLOB).should == false + end + + it "doesn't match an extra } when File::FNM_EXTGLOB is passed" do + File.fnmatch('c{at,ub}}s', 'cats', File::FNM_EXTGLOB).should == false + end + + it "matches when both FNM_EXTGLOB and FNM_PATHNAME are passed" do + File.fnmatch("?.md", "a.md", File::FNM_EXTGLOB | File::FNM_PATHNAME).should == true + end + + it "matches a single character for each ? character" do + File.fnmatch('c?t', 'cat').should == true + File.fnmatch('c??t', 'cat').should == false + end + + it "matches zero or more characters for each * character" do + File.fnmatch('c*', 'cats').should == true + File.fnmatch('c*t', 'c/a/b/t').should == true + end + + it "does not match unterminated range of characters" do + File.fnmatch('abc[de', 'abcd').should == false + end + + it "does not match unterminated range of characters as a literal" do + File.fnmatch('abc[de', 'abc[de').should == false + end + + it "matches ranges of characters using bracket expression (e.g. [a-z])" do + File.fnmatch('ca[a-z]', 'cat').should == true + end + + it "matches ranges of characters using bracket expression, taking case into account" do + File.fnmatch('[a-z]', 'D').should == false + File.fnmatch('[^a-z]', 'D').should == true + File.fnmatch('[A-Z]', 'd').should == false + File.fnmatch('[^A-Z]', 'd').should == true + File.fnmatch('[a-z]', 'D', File::FNM_CASEFOLD).should == true + end + + it "does not match characters outside of the range of the bracket expression" do + File.fnmatch('ca[x-z]', 'cat').should == false + File.fnmatch('/ca[s][s-t]/rul[a-b]/[z]he/[x-Z]orld', '/cats/rule/the/World').should == false + end + + it "matches ranges of characters using exclusive bracket expression (e.g. [^t] or [!t])" do + File.fnmatch('ca[^t]', 'cat').should == false + File.fnmatch('ca[^t]', 'cas').should == true + File.fnmatch('ca[!t]', 'cat').should == false + end + + it "matches characters with a case sensitive comparison" do + File.fnmatch('cat', 'CAT').should == false + end + + it "matches characters with case insensitive comparison when flags includes FNM_CASEFOLD" do + File.fnmatch('cat', 'CAT', File::FNM_CASEFOLD).should == true + end + + platform_is_not :windows do + it "doesn't match case sensitive characters on platforms with case sensitive paths, when flags include FNM_SYSCASE" do + File.fnmatch('cat', 'CAT', File::FNM_SYSCASE).should == false + end + end + + platform_is :windows do + it "matches case sensitive characters on platforms with case insensitive paths, when flags include FNM_SYSCASE" do + File.fnmatch('cat', 'CAT', File::FNM_SYSCASE).should == true + end + end + + it "matches wildcard with characters when flags includes FNM_PATHNAME" do + File.fnmatch('*a', 'aa', File::FNM_PATHNAME).should == true + File.fnmatch('a*', 'aa', File::FNM_PATHNAME).should == true + File.fnmatch('a*', 'aaa', File::FNM_PATHNAME).should == true + File.fnmatch('*a', 'aaa', File::FNM_PATHNAME).should == true + end + + it "does not match '/' characters with ? or * when flags includes FNM_PATHNAME" do + File.fnmatch('?', '/', File::FNM_PATHNAME).should == false + File.fnmatch('*', '/', File::FNM_PATHNAME).should == false + end + + it "does not match '/' characters inside bracket expressions when flags includes FNM_PATHNAME" do + File.fnmatch('[/]', '/', File::FNM_PATHNAME).should == false + end + + it "matches literal ? or * in path when pattern includes \\? or \\*" do + File.fnmatch('\?', '?').should == true + File.fnmatch('\?', 'a').should == false + + File.fnmatch('\*', '*').should == true + File.fnmatch('\*', 'a').should == false + end + + it "matches literal character (e.g. 'a') in path when pattern includes escaped character (e.g. \\a)" do + File.fnmatch('\a', 'a').should == true + File.fnmatch('this\b', 'thisb').should == true + end + + it "matches '\\' characters in path when flags includes FNM_NOESCAPE" do + File.fnmatch('\a', '\a', File::FNM_NOESCAPE).should == true + File.fnmatch('\a', 'a', File::FNM_NOESCAPE).should == false + File.fnmatch('\[foo\]\[bar\]', '[foo][bar]', File::FNM_NOESCAPE).should == false + end + + it "escapes special characters inside bracket expression" do + File.fnmatch('[\?]', '?').should == true + File.fnmatch('[\*]', '*').should == true + end + + it "does not match leading periods in filenames with wildcards by default" do + File.should_not.fnmatch('*', '.profile') + File.should.fnmatch('*', 'home/.profile') + File.should.fnmatch('*/*', 'home/.profile') + File.should_not.fnmatch('*/*', 'dave/.profile', File::FNM_PATHNAME) + end + + it "matches patterns with leading periods to dotfiles" do + File.fnmatch('.*', '.profile').should == true + File.fnmatch('.*', '.profile', File::FNM_PATHNAME).should == true + File.fnmatch(".*file", "nondotfile").should == false + File.fnmatch(".*file", "nondotfile", File::FNM_PATHNAME).should == false + end + + it "does not match directories with leading periods by default with FNM_PATHNAME" do + File.fnmatch('.*', '.directory/nondotfile', File::FNM_PATHNAME).should == false + File.fnmatch('.*', '.directory/.profile', File::FNM_PATHNAME).should == false + File.fnmatch('.*', 'foo/.directory/nondotfile', File::FNM_PATHNAME).should == false + File.fnmatch('.*', 'foo/.directory/.profile', File::FNM_PATHNAME).should == false + File.fnmatch('**/.dotfile', '.dotsubdir/.dotfile', File::FNM_PATHNAME).should == false + end + + it "matches leading periods in filenames when flags includes FNM_DOTMATCH" do + File.fnmatch('*', '.profile', File::FNM_DOTMATCH).should == true + File.fnmatch('*', 'home/.profile', File::FNM_DOTMATCH).should == true + end + + it "matches multiple directories with ** and *" do + files = '**/*.rb' + File.fnmatch(files, 'main.rb').should == false + File.fnmatch(files, './main.rb').should == false + File.fnmatch(files, 'lib/song.rb').should == true + File.fnmatch('**.rb', 'main.rb').should == true + File.fnmatch('**.rb', './main.rb').should == false + File.fnmatch('**.rb', 'lib/song.rb').should == true + File.fnmatch('*', 'dave/.profile').should == true + end + + it "matches multiple directories with ** when flags includes File::FNM_PATHNAME" do + files = '**/*.rb' + flags = File::FNM_PATHNAME + + File.fnmatch(files, 'main.rb', flags).should == true + File.fnmatch(files, 'one/two/three/main.rb', flags).should == true + File.fnmatch(files, './main.rb', flags).should == false + + flags = File::FNM_PATHNAME | File::FNM_DOTMATCH + + File.fnmatch(files, './main.rb', flags).should == true + File.fnmatch(files, 'one/two/.main.rb', flags).should == true + + File.fnmatch("**/best/*", 'lib/my/best/song.rb').should == true + end + + it "returns false if '/' in pattern do not match '/' in path when flags includes FNM_PATHNAME" do + pattern = '*/*' + File.fnmatch(pattern, 'dave/.profile', File::FNM_PATHNAME).should == false + + pattern = '**/foo' + File.fnmatch(pattern, 'a/.b/c/foo', File::FNM_PATHNAME).should == false + end + + it "returns true if '/' in pattern match '/' in path when flags includes FNM_PATHNAME" do + pattern = '*/*' + File.fnmatch(pattern, 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true + + pattern = '**/foo' + File.fnmatch(pattern, 'a/b/c/foo', File::FNM_PATHNAME).should == true + File.fnmatch(pattern, '/a/b/c/foo', File::FNM_PATHNAME).should == true + File.fnmatch(pattern, 'c:/a/b/c/foo', File::FNM_PATHNAME).should == true + File.fnmatch(pattern, 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true + end + + it "has special handling for ./ when using * and FNM_PATHNAME" do + File.fnmatch('./*', '.', File::FNM_PATHNAME).should == false + File.fnmatch('./*', './', File::FNM_PATHNAME).should == true + File.fnmatch('./*/', './', File::FNM_PATHNAME).should == false + File.fnmatch('./**', './', File::FNM_PATHNAME).should == true + File.fnmatch('./**/', './', File::FNM_PATHNAME).should == true + File.fnmatch('./*', '.', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == false + File.fnmatch('./*', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true + File.fnmatch('./*/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == false + File.fnmatch('./**', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true + File.fnmatch('./**/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true + end + + it "matches **/* with FNM_PATHNAME to recurse directories" do + File.fnmatch('nested/**/*', 'nested/subdir', File::FNM_PATHNAME).should == true + File.fnmatch('nested/**/*', 'nested/subdir/file', File::FNM_PATHNAME).should == true + File.fnmatch('nested/**/*', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true + File.fnmatch('nested/**/*', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true + end + + it "matches ** with FNM_PATHNAME only in current directory" do + File.fnmatch('nested/**', 'nested/subdir', File::FNM_PATHNAME).should == true + File.fnmatch('nested/**', 'nested/subdir/file', File::FNM_PATHNAME).should == false + File.fnmatch('nested/**', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true + File.fnmatch('nested/**', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == false + end + + it "accepts an object that has a #to_path method" do + File.fnmatch('\*', mock_to_path('a')).should == false + end + + it "raises a TypeError if the first and second arguments are not string-like" do + -> { File.fnmatch(nil, nil, 0, 0) }.should.raise(ArgumentError) + -> { File.fnmatch(1, 'some/thing') }.should.raise(TypeError) + -> { File.fnmatch('some/thing', 1) }.should.raise(TypeError) + -> { File.fnmatch(1, 1) }.should.raise(TypeError) + end + + it "raises a TypeError if the third argument is not an Integer" do + -> { File.fnmatch("*/place", "path/to/file", "flags") }.should.raise(TypeError) + -> { File.fnmatch("*/place", "path/to/file", nil) }.should.raise(TypeError) + end + + it "does not raise a TypeError if the third argument can be coerced to an Integer" do + flags = mock("flags") + flags.should_receive(:to_int).and_return(10) + -> { File.fnmatch("*/place", "path/to/file", flags) }.should_not.raise + end + + it "matches multibyte characters" do + File.fnmatch("*/ä/ø/ñ", "a/ä/ø/ñ").should == true + end end describe "File.fnmatch?" do - it_behaves_like :file_fnmatch, :fnmatch? + it "is an alias of File.fnmatch" do + File.method(:fnmatch?).should == File.method(:fnmatch) + end end diff --git a/core/file/path_spec.rb b/core/file/path_spec.rb index ce78dbeed..f3b9b56db 100644 --- a/core/file/path_spec.rb +++ b/core/file/path_spec.rb @@ -1,8 +1,9 @@ require_relative '../../spec_helper' -require_relative 'shared/path' describe "File#path" do - it_behaves_like :file_path, :path + it "is an alias of File#to_path" do + File.instance_method(:path).should == File.instance_method(:to_path) + end end describe "File.path" do diff --git a/core/file/shared/fnmatch.rb b/core/file/shared/fnmatch.rb deleted file mode 100644 index b9140d027..000000000 --- a/core/file/shared/fnmatch.rb +++ /dev/null @@ -1,294 +0,0 @@ -describe :file_fnmatch, shared: true do - it "matches entire strings" do - File.send(@method, 'cat', 'cat').should == true - end - - it "does not match partial strings" do - File.send(@method, 'cat', 'category').should == false - end - - it "does not support { } patterns by default" do - File.send(@method, 'c{at,ub}s', 'cats').should == false - File.send(@method, 'c{at,ub}s', 'c{at,ub}s').should == true - end - - it "supports some { } patterns when File::FNM_EXTGLOB is passed" do - File.send(@method, "{a,b}", "a", File::FNM_EXTGLOB).should == true - File.send(@method, "{a,b}", "b", File::FNM_EXTGLOB).should == true - File.send(@method, "c{at,ub}s", "cats", File::FNM_EXTGLOB).should == true - File.send(@method, "c{at,ub}s", "cubs", File::FNM_EXTGLOB).should == true - File.send(@method, "-c{at,ub}s-", "-cats-", File::FNM_EXTGLOB).should == true - File.send(@method, "-c{at,ub}s-", "-cubs-", File::FNM_EXTGLOB).should == true - File.send(@method, "{a,b,c}{d,e,f}{g,h}", "adg", File::FNM_EXTGLOB).should == true - File.send(@method, "{a,b,c}{d,e,f}{g,h}", "bdg", File::FNM_EXTGLOB).should == true - File.send(@method, "{a,b,c}{d,e,f}{g,h}", "ceh", File::FNM_EXTGLOB).should == true - File.send(@method, "{aa,bb,cc,dd}", "aa", File::FNM_EXTGLOB).should == true - File.send(@method, "{aa,bb,cc,dd}", "bb", File::FNM_EXTGLOB).should == true - File.send(@method, "{aa,bb,cc,dd}", "cc", File::FNM_EXTGLOB).should == true - File.send(@method, "{aa,bb,cc,dd}", "dd", File::FNM_EXTGLOB).should == true - File.send(@method, "{1,5{a,b{c,d}}}", "1", File::FNM_EXTGLOB).should == true - File.send(@method, "{1,5{a,b{c,d}}}", "5a", File::FNM_EXTGLOB).should == true - File.send(@method, "{1,5{a,b{c,d}}}", "5bc", File::FNM_EXTGLOB).should == true - File.send(@method, "{1,5{a,b{c,d}}}", "5bd", File::FNM_EXTGLOB).should == true - File.send(@method, "\\\\{a\\,b,b\\}c}", "\\a,b", File::FNM_EXTGLOB).should == true - File.send(@method, "\\\\{a\\,b,b\\}c}", "\\b}c", File::FNM_EXTGLOB).should == true - end - - it "doesn't support some { } patterns even when File::FNM_EXTGLOB is passed" do - File.send(@method, "a{0..3}b", "a0b", File::FNM_EXTGLOB).should == false - File.send(@method, "a{0..3}b", "a1b", File::FNM_EXTGLOB).should == false - File.send(@method, "a{0..3}b", "a2b", File::FNM_EXTGLOB).should == false - File.send(@method, "a{0..3}b", "a3b", File::FNM_EXTGLOB).should == false - File.send(@method, "{0..12}", "0", File::FNM_EXTGLOB).should == false - File.send(@method, "{0..12}", "6", File::FNM_EXTGLOB).should == false - File.send(@method, "{0..12}", "12", File::FNM_EXTGLOB).should == false - File.send(@method, "{3..-2}", "3", File::FNM_EXTGLOB).should == false - File.send(@method, "{3..-2}", "0", File::FNM_EXTGLOB).should == false - File.send(@method, "{3..-2}", "-2", File::FNM_EXTGLOB).should == false - File.send(@method, "{a..g}", "a", File::FNM_EXTGLOB).should == false - File.send(@method, "{a..g}", "d", File::FNM_EXTGLOB).should == false - File.send(@method, "{a..g}", "g", File::FNM_EXTGLOB).should == false - File.send(@method, "{g..a}", "a", File::FNM_EXTGLOB).should == false - File.send(@method, "{g..a}", "d", File::FNM_EXTGLOB).should == false - File.send(@method, "{g..a}", "g", File::FNM_EXTGLOB).should == false - File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: {", File::FNM_EXTGLOB).should == false - File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: ,", File::FNM_EXTGLOB).should == false - File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: }", File::FNM_EXTGLOB).should == false - File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: {", File::FNM_EXTGLOB).should == false - end - - it "doesn't match an extra } when File::FNM_EXTGLOB is passed" do - File.send(@method, 'c{at,ub}}s', 'cats', File::FNM_EXTGLOB).should == false - end - - it "matches when both FNM_EXTGLOB and FNM_PATHNAME are passed" do - File.send(@method, "?.md", "a.md", File::FNM_EXTGLOB | File::FNM_PATHNAME).should == true - end - - it "matches a single character for each ? character" do - File.send(@method, 'c?t', 'cat').should == true - File.send(@method, 'c??t', 'cat').should == false - end - - it "matches zero or more characters for each * character" do - File.send(@method, 'c*', 'cats').should == true - File.send(@method, 'c*t', 'c/a/b/t').should == true - end - - it "does not match unterminated range of characters" do - File.send(@method, 'abc[de', 'abcd').should == false - end - - it "does not match unterminated range of characters as a literal" do - File.send(@method, 'abc[de', 'abc[de').should == false - end - - it "matches ranges of characters using bracket expression (e.g. [a-z])" do - File.send(@method, 'ca[a-z]', 'cat').should == true - end - - it "matches ranges of characters using bracket expression, taking case into account" do - File.send(@method, '[a-z]', 'D').should == false - File.send(@method, '[^a-z]', 'D').should == true - File.send(@method, '[A-Z]', 'd').should == false - File.send(@method, '[^A-Z]', 'd').should == true - File.send(@method, '[a-z]', 'D', File::FNM_CASEFOLD).should == true - end - - it "does not match characters outside of the range of the bracket expression" do - File.send(@method, 'ca[x-z]', 'cat').should == false - File.send(@method, '/ca[s][s-t]/rul[a-b]/[z]he/[x-Z]orld', '/cats/rule/the/World').should == false - end - - it "matches ranges of characters using exclusive bracket expression (e.g. [^t] or [!t])" do - File.send(@method, 'ca[^t]', 'cat').should == false - File.send(@method, 'ca[^t]', 'cas').should == true - File.send(@method, 'ca[!t]', 'cat').should == false - end - - it "matches characters with a case sensitive comparison" do - File.send(@method, 'cat', 'CAT').should == false - end - - it "matches characters with case insensitive comparison when flags includes FNM_CASEFOLD" do - File.send(@method, 'cat', 'CAT', File::FNM_CASEFOLD).should == true - end - - platform_is_not :windows do - it "doesn't match case sensitive characters on platforms with case sensitive paths, when flags include FNM_SYSCASE" do - File.send(@method, 'cat', 'CAT', File::FNM_SYSCASE).should == false - end - end - - platform_is :windows do - it "matches case sensitive characters on platforms with case insensitive paths, when flags include FNM_SYSCASE" do - File.send(@method, 'cat', 'CAT', File::FNM_SYSCASE).should == true - end - end - - it "matches wildcard with characters when flags includes FNM_PATHNAME" do - File.send(@method, '*a', 'aa', File::FNM_PATHNAME).should == true - File.send(@method, 'a*', 'aa', File::FNM_PATHNAME).should == true - File.send(@method, 'a*', 'aaa', File::FNM_PATHNAME).should == true - File.send(@method, '*a', 'aaa', File::FNM_PATHNAME).should == true - end - - it "does not match '/' characters with ? or * when flags includes FNM_PATHNAME" do - File.send(@method, '?', '/', File::FNM_PATHNAME).should == false - File.send(@method, '*', '/', File::FNM_PATHNAME).should == false - end - - it "does not match '/' characters inside bracket expressions when flags includes FNM_PATHNAME" do - File.send(@method, '[/]', '/', File::FNM_PATHNAME).should == false - end - - it "matches literal ? or * in path when pattern includes \\? or \\*" do - File.send(@method, '\?', '?').should == true - File.send(@method, '\?', 'a').should == false - - File.send(@method, '\*', '*').should == true - File.send(@method, '\*', 'a').should == false - end - - it "matches literal character (e.g. 'a') in path when pattern includes escaped character (e.g. \\a)" do - File.send(@method, '\a', 'a').should == true - File.send(@method, 'this\b', 'thisb').should == true - end - - it "matches '\\' characters in path when flags includes FNM_NOESACPE" do - File.send(@method, '\a', '\a', File::FNM_NOESCAPE).should == true - File.send(@method, '\a', 'a', File::FNM_NOESCAPE).should == false - File.send(@method, '\[foo\]\[bar\]', '[foo][bar]', File::FNM_NOESCAPE).should == false - end - - it "escapes special characters inside bracket expression" do - File.send(@method, '[\?]', '?').should == true - File.send(@method, '[\*]', '*').should == true - end - - it "does not match leading periods in filenames with wildcards by default" do - File.should_not.send(@method, '*', '.profile') - File.should.send(@method, '*', 'home/.profile') - File.should.send(@method, '*/*', 'home/.profile') - File.should_not.send(@method, '*/*', 'dave/.profile', File::FNM_PATHNAME) - end - - it "matches patterns with leading periods to dotfiles" do - File.send(@method, '.*', '.profile').should == true - File.send(@method, '.*', '.profile', File::FNM_PATHNAME).should == true - File.send(@method, ".*file", "nondotfile").should == false - File.send(@method, ".*file", "nondotfile", File::FNM_PATHNAME).should == false - end - - it "does not match directories with leading periods by default with FNM_PATHNAME" do - File.send(@method, '.*', '.directory/nondotfile', File::FNM_PATHNAME).should == false - File.send(@method, '.*', '.directory/.profile', File::FNM_PATHNAME).should == false - File.send(@method, '.*', 'foo/.directory/nondotfile', File::FNM_PATHNAME).should == false - File.send(@method, '.*', 'foo/.directory/.profile', File::FNM_PATHNAME).should == false - File.send(@method, '**/.dotfile', '.dotsubdir/.dotfile', File::FNM_PATHNAME).should == false - end - - it "matches leading periods in filenames when flags includes FNM_DOTMATCH" do - File.send(@method, '*', '.profile', File::FNM_DOTMATCH).should == true - File.send(@method, '*', 'home/.profile', File::FNM_DOTMATCH).should == true - end - - it "matches multiple directories with ** and *" do - files = '**/*.rb' - File.send(@method, files, 'main.rb').should == false - File.send(@method, files, './main.rb').should == false - File.send(@method, files, 'lib/song.rb').should == true - File.send(@method, '**.rb', 'main.rb').should == true - File.send(@method, '**.rb', './main.rb').should == false - File.send(@method, '**.rb', 'lib/song.rb').should == true - File.send(@method, '*', 'dave/.profile').should == true - end - - it "matches multiple directories with ** when flags includes File::FNM_PATHNAME" do - files = '**/*.rb' - flags = File::FNM_PATHNAME - - File.send(@method, files, 'main.rb', flags).should == true - File.send(@method, files, 'one/two/three/main.rb', flags).should == true - File.send(@method, files, './main.rb', flags).should == false - - flags = File::FNM_PATHNAME | File::FNM_DOTMATCH - - File.send(@method, files, './main.rb', flags).should == true - File.send(@method, files, 'one/two/.main.rb', flags).should == true - - File.send(@method, "**/best/*", 'lib/my/best/song.rb').should == true - end - - it "returns false if '/' in pattern do not match '/' in path when flags includes FNM_PATHNAME" do - pattern = '*/*' - File.send(@method, pattern, 'dave/.profile', File::FNM_PATHNAME).should == false - - pattern = '**/foo' - File.send(@method, pattern, 'a/.b/c/foo', File::FNM_PATHNAME).should == false - end - - it "returns true if '/' in pattern match '/' in path when flags includes FNM_PATHNAME" do - pattern = '*/*' - File.send(@method, pattern, 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true - - pattern = '**/foo' - File.send(@method, pattern, 'a/b/c/foo', File::FNM_PATHNAME).should == true - File.send(@method, pattern, '/a/b/c/foo', File::FNM_PATHNAME).should == true - File.send(@method, pattern, 'c:/a/b/c/foo', File::FNM_PATHNAME).should == true - File.send(@method, pattern, 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true - end - - it "has special handling for ./ when using * and FNM_PATHNAME" do - File.send(@method, './*', '.', File::FNM_PATHNAME).should == false - File.send(@method, './*', './', File::FNM_PATHNAME).should == true - File.send(@method, './*/', './', File::FNM_PATHNAME).should == false - File.send(@method, './**', './', File::FNM_PATHNAME).should == true - File.send(@method, './**/', './', File::FNM_PATHNAME).should == true - File.send(@method, './*', '.', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == false - File.send(@method, './*', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true - File.send(@method, './*/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == false - File.send(@method, './**', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true - File.send(@method, './**/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true - end - - it "matches **/* with FNM_PATHNAME to recurse directories" do - File.send(@method, 'nested/**/*', 'nested/subdir', File::FNM_PATHNAME).should == true - File.send(@method, 'nested/**/*', 'nested/subdir/file', File::FNM_PATHNAME).should == true - File.send(@method, 'nested/**/*', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true - File.send(@method, 'nested/**/*', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true - end - - it "matches ** with FNM_PATHNAME only in current directory" do - File.send(@method, 'nested/**', 'nested/subdir', File::FNM_PATHNAME).should == true - File.send(@method, 'nested/**', 'nested/subdir/file', File::FNM_PATHNAME).should == false - File.send(@method, 'nested/**', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true - File.send(@method, 'nested/**', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == false - end - - it "accepts an object that has a #to_path method" do - File.send(@method, '\*', mock_to_path('a')).should == false - end - - it "raises a TypeError if the first and second arguments are not string-like" do - -> { File.send(@method, nil, nil, 0, 0) }.should.raise(ArgumentError) - -> { File.send(@method, 1, 'some/thing') }.should.raise(TypeError) - -> { File.send(@method, 'some/thing', 1) }.should.raise(TypeError) - -> { File.send(@method, 1, 1) }.should.raise(TypeError) - end - - it "raises a TypeError if the third argument is not an Integer" do - -> { File.send(@method, "*/place", "path/to/file", "flags") }.should.raise(TypeError) - -> { File.send(@method, "*/place", "path/to/file", nil) }.should.raise(TypeError) - end - - it "does not raise a TypeError if the third argument can be coerced to an Integer" do - flags = mock("flags") - flags.should_receive(:to_int).and_return(10) - -> { File.send(@method, "*/place", "path/to/file", flags) }.should_not.raise - end - - it "matches multibyte characters" do - File.fnmatch("*/ä/ø/ñ", "a/ä/ø/ñ").should == true - end -end diff --git a/core/file/shared/path.rb b/core/file/shared/path.rb deleted file mode 100644 index 6c6f7d423..000000000 --- a/core/file/shared/path.rb +++ /dev/null @@ -1,82 +0,0 @@ -describe :file_path, shared: true do - before :each do - @path = tmp("file_to_path") - @name = File.basename(@path) - touch @path - end - - after :each do - @file.close if @file and !@file.closed? - rm_r @path - end - - it "returns a String" do - @file = File.new @path - @file.send(@method).should.instance_of?(String) - end - - it "returns a different String on every call" do - @file = File.new @path - path1 = @file.send(@method) - path2 = @file.send(@method) - path1.should == path2 - path1.should_not.equal?(path2) - end - - it "returns a mutable String" do - @file = File.new @path.dup.freeze - path = @file.send(@method) - path.should == @path - path.should_not.frozen? - path << "test" - @file.send(@method).should == @path - end - - it "calls to_str on argument and returns exact value" do - path = mock('path') - path.should_receive(:to_str).and_return(@path) - @file = File.new path - @file.send(@method).should == @path - end - - it "does not normalise the path it returns" do - Dir.chdir(tmp("")) do - unorm = "./#{@name}" - @file = File.new unorm - @file.send(@method).should == unorm - end - end - - it "does not canonicalize the path it returns" do - dir = File.basename tmp("") - path = "#{tmp("")}../#{dir}/#{@name}" - @file = File.new path - @file.send(@method).should == path - end - - it "does not absolute-ise the path it returns" do - Dir.chdir(tmp("")) do - @file = File.new @name - @file.send(@method).should == @name - end - end - - it "preserves the encoding of the path" do - path = @path.force_encoding("euc-jp") - @file = File.new path - @file.send(@method).encoding.should == Encoding.find("euc-jp") - end - - platform_is :linux do - guard -> { defined?(File::TMPFILE) } do - before :each do - @dir = tmp("tmpfilespec") - mkdir_p @dir - end - - after :each do - rm_r @dir - end - end - end -end diff --git a/core/file/shared/unlink.rb b/core/file/shared/unlink.rb deleted file mode 100644 index 0032907ba..000000000 --- a/core/file/shared/unlink.rb +++ /dev/null @@ -1,61 +0,0 @@ -describe :file_unlink, shared: true do - before :each do - @file1 = tmp('test.txt') - @file2 = tmp('test2.txt') - - touch @file1 - touch @file2 - end - - after :each do - File.send(@method, @file1) if File.exist?(@file1) - File.send(@method, @file2) if File.exist?(@file2) - - @file1 = nil - @file2 = nil - end - - it "returns 0 when called without arguments" do - File.send(@method).should == 0 - end - - it "deletes a single file" do - File.send(@method, @file1).should == 1 - File.should_not.exist?(@file1) - end - - it "deletes multiple files" do - File.send(@method, @file1, @file2).should == 2 - File.should_not.exist?(@file1) - File.should_not.exist?(@file2) - end - - it "raises a TypeError if not passed a String type" do - -> { File.send(@method, 1) }.should.raise(TypeError) - end - - it "raises an Errno::ENOENT when the given file doesn't exist" do - -> { File.send(@method, 'bogus') }.should.raise(Errno::ENOENT) - end - - it "coerces a given parameter into a string if possible" do - mock = mock("to_str") - mock.should_receive(:to_str).and_return(@file1) - File.send(@method, mock).should == 1 - end - - it "accepts an object that has a #to_path method" do - File.send(@method, mock_to_path(@file1)).should == 1 - end - - platform_is :windows do - it "allows deleting an open file with File::SHARE_DELETE" do - path = tmp("share_delete.txt") - File.open(path, mode: File::CREAT | File::WRONLY | File::BINARY | File::SHARE_DELETE) do |f| - File.should.exist?(path) - File.send(@method, path) - end - File.should_not.exist?(path) - end - end -end diff --git a/core/file/to_path_spec.rb b/core/file/to_path_spec.rb index 6d168a065..b968d3b2b 100644 --- a/core/file/to_path_spec.rb +++ b/core/file/to_path_spec.rb @@ -1,6 +1,84 @@ require_relative '../../spec_helper' -require_relative 'shared/path' describe "File#to_path" do - it_behaves_like :file_path, :to_path + before :each do + @path = tmp("file_to_path") + @name = File.basename(@path) + touch @path + end + + after :each do + @file.close if @file and !@file.closed? + rm_r @path + end + + it "returns a String" do + @file = File.new @path + @file.to_path.should.instance_of?(String) + end + + it "returns a different String on every call" do + @file = File.new @path + path1 = @file.to_path + path2 = @file.to_path + path1.should == path2 + path1.should_not.equal?(path2) + end + + it "returns a mutable String" do + @file = File.new @path.dup.freeze + path = @file.to_path + path.should == @path + path.should_not.frozen? + path << "test" + @file.to_path.should == @path + end + + it "calls to_str on argument and returns exact value" do + path = mock('path') + path.should_receive(:to_str).and_return(@path) + @file = File.new path + @file.to_path.should == @path + end + + it "does not normalise the path it returns" do + Dir.chdir(tmp("")) do + unorm = "./#{@name}" + @file = File.new unorm + @file.to_path.should == unorm + end + end + + it "does not canonicalize the path it returns" do + dir = File.basename tmp("") + path = "#{tmp("")}../#{dir}/#{@name}" + @file = File.new path + @file.to_path.should == path + end + + it "does not absolute-ise the path it returns" do + Dir.chdir(tmp("")) do + @file = File.new @name + @file.to_path.should == @name + end + end + + it "preserves the encoding of the path" do + path = @path.force_encoding("euc-jp") + @file = File.new path + @file.to_path.encoding.should == Encoding.find("euc-jp") + end + + platform_is :linux do + guard -> { defined?(File::TMPFILE) } do + before :each do + @dir = tmp("tmpfilespec") + mkdir_p @dir + end + + after :each do + rm_r @dir + end + end + end end diff --git a/core/file/unlink_spec.rb b/core/file/unlink_spec.rb index 28872d55e..db0c08f3a 100644 --- a/core/file/unlink_spec.rb +++ b/core/file/unlink_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/unlink' describe "File.unlink" do - it_behaves_like :file_unlink, :unlink + it "is an alias of File.delete" do + File.method(:unlink).should == File.method(:delete) + end end diff --git a/core/float/angle_spec.rb b/core/float/angle_spec.rb index c07249aa9..ac182c5b7 100644 --- a/core/float/angle_spec.rb +++ b/core/float/angle_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/arg' describe "Float#angle" do - it_behaves_like :float_arg, :angle + it "is an alias of Float#arg" do + Float.instance_method(:angle).should == Float.instance_method(:arg) + end end diff --git a/core/float/arg_spec.rb b/core/float/arg_spec.rb index d3a50668f..c9c602bbf 100644 --- a/core/float/arg_spec.rb +++ b/core/float/arg_spec.rb @@ -1,6 +1,38 @@ require_relative '../../spec_helper' -require_relative 'shared/arg' describe "Float#arg" do - it_behaves_like :float_arg, :arg + it "returns NaN if NaN" do + f = nan_value + f.arg.nan?.should == true + end + + it "returns self if NaN" do + f = nan_value + f.arg.should.equal?(f) + end + + it "returns 0 if positive" do + 1.0.arg.should == 0 + end + + it "returns 0 if +0.0" do + 0.0.arg.should == 0 + end + + it "returns 0 if +Infinity" do + infinity_value.arg.should == 0 + end + + it "returns Pi if negative" do + (-1.0).arg.should == Math::PI + end + + # This was established in r23960 + it "returns Pi if -0.0" do + (-0.0).arg.should == Math::PI + end + + it "returns Pi if -Infinity" do + (-infinity_value).arg.should == Math::PI + end end diff --git a/core/float/case_compare_spec.rb b/core/float/case_compare_spec.rb index b902fbea1..c82803642 100644 --- a/core/float/case_compare_spec.rb +++ b/core/float/case_compare_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/equal' describe "Float#===" do - it_behaves_like :float_equal, :=== + it "is an alias of Float#==" do + Float.instance_method(:===).should == Float.instance_method(:==) + end end diff --git a/core/float/equal_value_spec.rb b/core/float/equal_value_spec.rb index 03eea5108..37d0e162d 100644 --- a/core/float/equal_value_spec.rb +++ b/core/float/equal_value_spec.rb @@ -1,6 +1,40 @@ require_relative '../../spec_helper' -require_relative 'shared/equal' describe "Float#==" do - it_behaves_like :float_equal, :== + it "returns true if self has the same value as other" do + (1.0 == 1).should == true + (2.71828 == 1.428).should == false + (-4.2 == 4.2).should == false + end + + it "calls 'other == self' if coercion fails" do + x = mock('other') + def x.==(other) + 2.0 == other + end + + (1.0 == x).should == false + (2.0 == x).should == true + end + + it "returns false if one side is NaN" do + [1.0, 42, bignum_value].each { |n| + (nan_value == n).should == false + (n == nan_value).should == false + } + end + + it "handles positive infinity" do + [1.0, 42, bignum_value].each { |n| + (infinity_value == n).should == false + (n == infinity_value).should == false + } + end + + it "handles negative infinity" do + [1.0, 42, bignum_value].each { |n| + ((-infinity_value) == n).should == false + (n == -infinity_value).should == false + } + end end diff --git a/core/float/fdiv_spec.rb b/core/float/fdiv_spec.rb index be25ee283..8a3ead488 100644 --- a/core/float/fdiv_spec.rb +++ b/core/float/fdiv_spec.rb @@ -1,6 +1,61 @@ require_relative '../../spec_helper' -require_relative 'shared/quo' describe "Float#fdiv" do - it_behaves_like :float_quo, :fdiv + it "performs floating-point division between self and an Integer" do + 8.9.fdiv(7).should == 1.2714285714285716 + end + + it "performs floating-point division between self and an Integer" do + 8.9.fdiv(9999999999999**9).should == 8.900000000008011e-117 + end + + it "performs floating-point division between self and a Float" do + 2827.22.fdiv(872.111111).should == 3.2418116961704433 + end + + it "returns NaN when the argument is NaN" do + -1819.999999.fdiv(nan_value).nan?.should == true + 11109.1981271.fdiv(nan_value).nan?.should == true + end + + it "returns Infinity when the argument is 0.0" do + 2827.22.fdiv(0.0).infinite?.should == 1 + end + + it "returns -Infinity when the argument is 0.0 and self is negative" do + -48229.282.fdiv(0.0).infinite?.should == -1 + end + + it "returns Infinity when the argument is 0" do + 2827.22.fdiv(0).infinite?.should == 1 + end + + it "returns -Infinity when the argument is 0 and self is negative" do + -48229.282.fdiv(0).infinite?.should == -1 + end + + it "returns 0.0 when the argument is Infinity" do + 47292.2821.fdiv(infinity_value).should == 0.0 + end + + it "returns -0.0 when the argument is -Infinity" do + 1.9999918.fdiv(-infinity_value).should == -0.0 + end + + it "performs floating-point division between self and a Rational" do + 74620.09.fdiv(Rational(2,3)).should == 111930.135 + end + + it "performs floating-point division between self and a Complex" do + 74620.09.fdiv(Complex(8,2)).should == Complex( + 8778.834117647059, -2194.7085294117646) + end + + it "raises a TypeError when argument isn't numeric" do + -> { 27292.2.fdiv(mock('non-numeric')) }.should.raise(TypeError) + end + + it "raises an ArgumentError when passed multiple arguments" do + -> { 272.221.fdiv(6,0.2) }.should.raise(ArgumentError) + end end diff --git a/core/float/inspect_spec.rb b/core/float/inspect_spec.rb index 4be1927d8..3827167c1 100644 --- a/core/float/inspect_spec.rb +++ b/core/float/inspect_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/to_s' describe "Float#inspect" do - it_behaves_like :float_to_s, :inspect + it "is an alias of Float#to_s" do + Float.instance_method(:inspect).should == Float.instance_method(:to_s) + end end diff --git a/core/float/magnitude_spec.rb b/core/float/magnitude_spec.rb index 7cdd8ef28..4a753267e 100644 --- a/core/float/magnitude_spec.rb +++ b/core/float/magnitude_spec.rb @@ -2,5 +2,13 @@ require_relative 'shared/abs' describe "Float#magnitude" do - it_behaves_like :float_abs, :magnitude + ruby_version_is ""..."3.4" do + it_behaves_like :float_abs, :magnitude + end + + ruby_version_is "3.4" do + it "is an alias of Float#abs" do + Float.instance_method(:magnitude).should == Float.instance_method(:abs) + end + end end diff --git a/core/float/modulo_spec.rb b/core/float/modulo_spec.rb index 8ae80a0b0..8b7aedf82 100644 --- a/core/float/modulo_spec.rb +++ b/core/float/modulo_spec.rb @@ -1,10 +1,56 @@ require_relative '../../spec_helper' -require_relative 'shared/modulo' describe "Float#%" do - it_behaves_like :float_modulo, :% + it "returns self modulo other" do + (6543.21 % 137).should be_close(104.21, TOLERANCE) + (5667.19 % bignum_value).should be_close(5667.19, TOLERANCE) + (6543.21 % 137.24).should be_close(92.9299999999996, TOLERANCE) + + (-1.0 % 1).should == 0 + end + + it "returns self when modulus is +Infinity" do + (4.2 % Float::INFINITY).should == 4.2 + end + + it "returns -Infinity when modulus is -Infinity" do + (4.2 % -Float::INFINITY).should == -Float::INFINITY + end + + it "returns NaN when called on NaN or Infinities" do + (Float::NAN % 42).should.nan? + (Float::INFINITY % 42).should.nan? + (-Float::INFINITY % 42).should.nan? + end + + it "returns NaN when modulus is NaN" do + (4.2 % Float::NAN).should.nan? + end + + it "returns -0.0 when called on -0.0 with a non zero modulus" do + r = -0.0 % 42 + r.should == 0 + (1/r).should < 0 + + r = -0.0 % Float::INFINITY + r.should == 0 + (1/r).should < 0 + end + + it "tries to coerce the modulus" do + obj = mock("modulus") + obj.should_receive(:coerce).with(1.25).and_return([1.25, 0.5]) + (1.25 % obj).should == 0.25 + end + + it "raises a ZeroDivisionError if other is zero" do + -> { 1.0 % 0 }.should.raise(ZeroDivisionError) + -> { 1.0 % 0.0 }.should.raise(ZeroDivisionError) + end end describe "Float#modulo" do - it_behaves_like :float_modulo, :modulo + it "is an alias of Float#%" do + Float.instance_method(:modulo).should == Float.instance_method(:%) + end end diff --git a/core/float/phase_spec.rb b/core/float/phase_spec.rb index 2aa84024b..ad112ac9f 100644 --- a/core/float/phase_spec.rb +++ b/core/float/phase_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/arg' describe "Float#phase" do - it_behaves_like :float_arg, :phase + it "is an alias of Float#arg" do + Float.instance_method(:phase).should == Float.instance_method(:arg) + end end diff --git a/core/float/quo_spec.rb b/core/float/quo_spec.rb index b5c64f9d8..0e9a7a0a0 100644 --- a/core/float/quo_spec.rb +++ b/core/float/quo_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/quo' describe "Float#quo" do - it_behaves_like :float_quo, :quo + it "is an alias of Float#fdiv" do + Float.instance_method(:quo).should == Float.instance_method(:fdiv) + end end diff --git a/core/float/shared/arg.rb b/core/float/shared/arg.rb deleted file mode 100644 index de0024313..000000000 --- a/core/float/shared/arg.rb +++ /dev/null @@ -1,36 +0,0 @@ -describe :float_arg, shared: true do - it "returns NaN if NaN" do - f = nan_value - f.send(@method).nan?.should == true - end - - it "returns self if NaN" do - f = nan_value - f.send(@method).should.equal?(f) - end - - it "returns 0 if positive" do - 1.0.send(@method).should == 0 - end - - it "returns 0 if +0.0" do - 0.0.send(@method).should == 0 - end - - it "returns 0 if +Infinity" do - infinity_value.send(@method).should == 0 - end - - it "returns Pi if negative" do - (-1.0).send(@method).should == Math::PI - end - - # This was established in r23960 - it "returns Pi if -0.0" do - (-0.0).send(@method).should == Math::PI - end - - it "returns Pi if -Infinity" do - (-infinity_value).send(@method).should == Math::PI - end -end diff --git a/core/float/shared/equal.rb b/core/float/shared/equal.rb deleted file mode 100644 index 4d524e1cf..000000000 --- a/core/float/shared/equal.rb +++ /dev/null @@ -1,38 +0,0 @@ -describe :float_equal, shared: true do - it "returns true if self has the same value as other" do - 1.0.send(@method, 1).should == true - 2.71828.send(@method, 1.428).should == false - -4.2.send(@method, 4.2).should == false - end - - it "calls 'other == self' if coercion fails" do - x = mock('other') - def x.==(other) - 2.0 == other - end - - 1.0.send(@method, x).should == false - 2.0.send(@method, x).should == true - end - - it "returns false if one side is NaN" do - [1.0, 42, bignum_value].each { |n| - (nan_value.send(@method, n)).should == false - (n.send(@method, nan_value)).should == false - } - end - - it "handles positive infinity" do - [1.0, 42, bignum_value].each { |n| - (infinity_value.send(@method, n)).should == false - (n.send(@method, infinity_value)).should == false - } - end - - it "handles negative infinity" do - [1.0, 42, bignum_value].each { |n| - ((-infinity_value).send(@method, n)).should == false - (n.send(@method, -infinity_value)).should == false - } - end -end diff --git a/core/float/shared/modulo.rb b/core/float/shared/modulo.rb deleted file mode 100644 index 1efee1476..000000000 --- a/core/float/shared/modulo.rb +++ /dev/null @@ -1,48 +0,0 @@ -describe :float_modulo, shared: true do - it "returns self modulo other" do - 6543.21.send(@method, 137).should be_close(104.21, TOLERANCE) - 5667.19.send(@method, bignum_value).should be_close(5667.19, TOLERANCE) - 6543.21.send(@method, 137.24).should be_close(92.9299999999996, TOLERANCE) - - -1.0.send(@method, 1).should == 0 - end - - it "returns self when modulus is +Infinity" do - 4.2.send(@method, Float::INFINITY).should == 4.2 - end - - it "returns -Infinity when modulus is -Infinity" do - 4.2.send(@method, -Float::INFINITY).should == -Float::INFINITY - end - - it "returns NaN when called on NaN or Infinities" do - Float::NAN.send(@method, 42).should.nan? - Float::INFINITY.send(@method, 42).should.nan? - (-Float::INFINITY).send(@method, 42).should.nan? - end - - it "returns NaN when modulus is NaN" do - 4.2.send(@method, Float::NAN).should.nan? - end - - it "returns -0.0 when called on -0.0 with a non zero modulus" do - r = (-0.0).send(@method, 42) - r.should == 0 - (1/r).should < 0 - - r = (-0.0).send(@method, Float::INFINITY) - r.should == 0 - (1/r).should < 0 - end - - it "tries to coerce the modulus" do - obj = mock("modulus") - obj.should_receive(:coerce).with(1.25).and_return([1.25, 0.5]) - (1.25 % obj).should == 0.25 - end - - it "raises a ZeroDivisionError if other is zero" do - -> { 1.0.send(@method, 0) }.should.raise(ZeroDivisionError) - -> { 1.0.send(@method, 0.0) }.should.raise(ZeroDivisionError) - end -end diff --git a/core/float/shared/quo.rb b/core/float/shared/quo.rb deleted file mode 100644 index 930187aaf..000000000 --- a/core/float/shared/quo.rb +++ /dev/null @@ -1,59 +0,0 @@ -describe :float_quo, shared: true do - it "performs floating-point division between self and an Integer" do - 8.9.send(@method, 7).should == 1.2714285714285716 - end - - it "performs floating-point division between self and an Integer" do - 8.9.send(@method, 9999999999999**9).should == 8.900000000008011e-117 - end - - it "performs floating-point division between self and a Float" do - 2827.22.send(@method, 872.111111).should == 3.2418116961704433 - end - - it "returns NaN when the argument is NaN" do - -1819.999999.send(@method, nan_value).nan?.should == true - 11109.1981271.send(@method, nan_value).nan?.should == true - end - - it "returns Infinity when the argument is 0.0" do - 2827.22.send(@method, 0.0).infinite?.should == 1 - end - - it "returns -Infinity when the argument is 0.0 and self is negative" do - -48229.282.send(@method, 0.0).infinite?.should == -1 - end - - it "returns Infinity when the argument is 0" do - 2827.22.send(@method, 0).infinite?.should == 1 - end - - it "returns -Infinity when the argument is 0 and self is negative" do - -48229.282.send(@method, 0).infinite?.should == -1 - end - - it "returns 0.0 when the argument is Infinity" do - 47292.2821.send(@method, infinity_value).should == 0.0 - end - - it "returns -0.0 when the argument is -Infinity" do - 1.9999918.send(@method, -infinity_value).should == -0.0 - end - - it "performs floating-point division between self and a Rational" do - 74620.09.send(@method, Rational(2,3)).should == 111930.135 - end - - it "performs floating-point division between self and a Complex" do - 74620.09.send(@method, Complex(8,2)).should == Complex( - 8778.834117647059, -2194.7085294117646) - end - - it "raises a TypeError when argument isn't numeric" do - -> { 27292.2.send(@method, mock('non-numeric')) }.should.raise(TypeError) - end - - it "raises an ArgumentError when passed multiple arguments" do - -> { 272.221.send(@method, 6,0.2) }.should.raise(ArgumentError) - end -end diff --git a/core/float/shared/to_s.rb b/core/float/shared/to_s.rb deleted file mode 100644 index 81ffdd9e8..000000000 --- a/core/float/shared/to_s.rb +++ /dev/null @@ -1,308 +0,0 @@ -describe :float_to_s, shared: true do - it "returns 'NaN' for NaN" do - nan_value().send(@method).should == 'NaN' - end - - it "returns 'Infinity' for positive infinity" do - infinity_value().send(@method).should == 'Infinity' - end - - it "returns '-Infinity' for negative infinity" do - (-infinity_value()).send(@method).should == '-Infinity' - end - - it "returns '0.0' for 0.0" do - 0.0.send(@method).should == "0.0" - end - - platform_is_not :openbsd do - it "emits '-' for -0.0" do - -0.0.send(@method).should == "-0.0" - end - end - - it "emits a '-' for negative values" do - -3.14.send(@method).should == "-3.14" - end - - it "emits a trailing '.0' for a whole number" do - 50.0.send(@method).should == "50.0" - end - - it "emits a trailing '.0' for the mantissa in e format" do - 1.0e20.send(@method).should == "1.0e+20" - end - - it "uses non-e format for a positive value with fractional part having 5 significant figures" do - 0.0001.send(@method).should == "0.0001" - end - - it "uses non-e format for a negative value with fractional part having 5 significant figures" do - -0.0001.send(@method).should == "-0.0001" - end - - it "uses e format for a positive value with fractional part having 6 significant figures" do - 0.00001.send(@method).should == "1.0e-05" - end - - it "uses e format for a negative value with fractional part having 6 significant figures" do - -0.00001.send(@method).should == "-1.0e-05" - end - - it "uses non-e format for a positive value with whole part having 15 significant figures" do - 10000000000000.0.send(@method).should == "10000000000000.0" - end - - it "uses non-e format for a negative value with whole part having 15 significant figures" do - -10000000000000.0.send(@method).should == "-10000000000000.0" - end - - it "uses non-e format for a positive value with whole part having 16 significant figures" do - 100000000000000.0.send(@method).should == "100000000000000.0" - end - - it "uses non-e format for a negative value with whole part having 16 significant figures" do - -100000000000000.0.send(@method).should == "-100000000000000.0" - end - - it "uses e format for a positive value with whole part having 18 significant figures" do - 10000000000000000.0.send(@method).should == "1.0e+16" - end - - it "uses e format for a negative value with whole part having 18 significant figures" do - -10000000000000000.0.send(@method).should == "-1.0e+16" - end - - it "uses e format for a positive value with whole part having 17 significant figures" do - 1000000000000000.0.send(@method).should == "1.0e+15" - end - - it "uses e format for a negative value with whole part having 17 significant figures" do - -1000000000000000.0.send(@method).should == "-1.0e+15" - end - - # #3273 - it "outputs the minimal, unique form necessary to recreate the value" do - value = 0.21611564636388508 - string = "0.21611564636388508" - - value.send(@method).should == string - string.to_f.should == value - end - - it "outputs the minimal, unique form to represent the value" do - 0.56.send(@method).should == "0.56" - end - - describe "matches" do - it "random examples in all ranges" do - # 50.times do - # bytes = (0...8).map { rand(256) } - # string = bytes.pack('C8') - # float = string.unpack('D').first - # puts "#{'%.20g' % float}.send(@method).should == #{float.send(@method).inspect}" - # end - - 2.5540217314354050325e+163.send(@method).should == "2.554021731435405e+163" - 2.5492588360356597544e-172.send(@method).should == "2.5492588360356598e-172" - 1.742770260934704852e-82.send(@method).should == "1.7427702609347049e-82" - 6.2108093676180883209e-104.send(@method).should == "6.210809367618088e-104" - -3.3448803488331067402e-143.send(@method).should == "-3.3448803488331067e-143" - -2.2740074343500832557e-168.send(@method).should == "-2.2740074343500833e-168" - 7.0587971678048535732e+191.send(@method).should == "7.058797167804854e+191" - -284438.88327586348169.send(@method).should == "-284438.8832758635" - 3.953272468476091301e+105.send(@method).should == "3.9532724684760913e+105" - -3.6361359552959847853e+100.send(@method).should == "-3.636135955295985e+100" - -1.3222325865575206185e-31.send(@method).should == "-1.3222325865575206e-31" - 1.1440138916932761366e+130.send(@method).should == "1.1440138916932761e+130" - 4.8750891560387561157e-286.send(@method).should == "4.875089156038756e-286" - 5.6101113356591453525e-257.send(@method).should == "5.610111335659145e-257" - -3.829644279545809575e-100.send(@method).should == "-3.8296442795458096e-100" - 1.5342839401396406117e-194.send(@method).should == "1.5342839401396406e-194" - 2.2284972755169921402e-144.send(@method).should == "2.228497275516992e-144" - 2.1825655917065601737e-61.send(@method).should == "2.1825655917065602e-61" - -2.6672271363524338322e-62.send(@method).should == "-2.667227136352434e-62" - -1.9257995160119059415e+21.send(@method).should == "-1.925799516011906e+21" - -8.9096732962887121718e-198.send(@method).should == "-8.909673296288712e-198" - 2.0202075376548644959e-90.send(@method).should == "2.0202075376548645e-90" - -7.7341602581786258961e-266.send(@method).should == "-7.734160258178626e-266" - 3.5134482598733635046e+98.send(@method).should == "3.5134482598733635e+98" - -2.124411722371029134e+154.send(@method).should == "-2.124411722371029e+154" - -4.573908787355718687e+110.send(@method).should == "-4.573908787355719e+110" - -1.9344425934170969879e-232.send(@method).should == "-1.934442593417097e-232" - -1.3274227399979271095e+171.send(@method).should == "-1.3274227399979271e+171" - 9.3495270482104442383e-283.send(@method).should == "9.349527048210444e-283" - -4.2046059371986483233e+307.send(@method).should == "-4.2046059371986483e+307" - 3.6133547278583543004e-117.send(@method).should == "3.613354727858354e-117" - 4.9247416523566613499e-08.send(@method).should == "4.9247416523566613e-08" - 1.6936145488250064007e-71.send(@method).should == "1.6936145488250064e-71" - 2.4455483206829433098e+96.send(@method).should == "2.4455483206829433e+96" - 7.9797449851436455384e+124.send(@method).should == "7.979744985143646e+124" - -1.3873689634457876774e-129.send(@method).should == "-1.3873689634457877e-129" - 3.9761102037533483075e+284.send(@method).should == "3.976110203753348e+284" - -4.2819791952139402486e-303.send(@method).should == "-4.28197919521394e-303" - -5.7981017546689831298e-116.send(@method).should == "-5.798101754668983e-116" - -3.953266497860534199e-28.send(@method).should == "-3.953266497860534e-28" - -2.0659852720290440959e-243.send(@method).should == "-2.065985272029044e-243" - 8.9670488995878688018e-05.send(@method).should == "8.967048899587869e-05" - -1.2317943708113061768e-98.send(@method).should == "-1.2317943708113062e-98" - -3.8930768307633080463e+248.send(@method).should == "-3.893076830763308e+248" - 6.5854032671803925627e-239.send(@method).should == "6.5854032671803926e-239" - 4.6257022188980878952e+177.send(@method).should == "4.625702218898088e+177" - -1.9397155125507235603e-187.send(@method).should == "-1.9397155125507236e-187" - 8.5752156951245705056e+117.send(@method).should == "8.57521569512457e+117" - -2.4784875958162501671e-132.send(@method).should == "-2.4784875958162502e-132" - -4.4125691841230058457e-203.send(@method).should == "-4.412569184123006e-203" - end - - it "random examples in human ranges" do - # 50.times do - # formatted = '' - # rand(1..3).times do - # formatted << rand(10).to_s - # end - # formatted << '.' - # rand(1..9).times do - # formatted << rand(10).to_s - # end - # float = formatted.to_f - # puts "#{'%.20f' % float}.send(@method).should == #{float.send(@method).inspect}" - # end - - 5.17869899999999994122.send(@method).should == "5.178699" - 905.62695729999995819526.send(@method).should == "905.6269573" - 62.75999999999999801048.send(@method).should == "62.76" - 6.93856795800000014651.send(@method).should == "6.938567958" - 4.95999999999999996447.send(@method).should == "4.96" - 32.77993899999999882766.send(@method).should == "32.779939" - 544.12756779999995160324.send(@method).should == "544.1275678" - 66.25801119999999855281.send(@method).should == "66.2580112" - 7.90000000000000035527.send(@method).should == "7.9" - 5.93100000000000004974.send(@method).should == "5.931" - 5.21229313600000043749.send(@method).should == "5.212293136" - 503.44173809000000119340.send(@method).should == "503.44173809" - 79.26000000000000511591.send(@method).should == "79.26" - 8.51524999999999998579.send(@method).should == "8.51525" - 174.00000000000000000000.send(@method).should == "174.0" - 50.39580000000000126192.send(@method).should == "50.3958" - 35.28999999999999914735.send(@method).should == "35.29" - 5.43136675399999990788.send(@method).should == "5.431366754" - 654.07680000000004838512.send(@method).should == "654.0768" - 6.07423700000000010846.send(@method).should == "6.074237" - 102.25779799999999397642.send(@method).should == "102.257798" - 5.08129999999999970584.send(@method).should == "5.0813" - 6.00000000000000000000.send(@method).should == "6.0" - 8.30000000000000071054.send(@method).should == "8.3" - 32.68345999999999662577.send(@method).should == "32.68346" - 581.11170000000004165486.send(@method).should == "581.1117" - 76.31342999999999676675.send(@method).should == "76.31343" - 438.30826000000001840817.send(@method).should == "438.30826" - 482.06631994000002805478.send(@method).should == "482.06631994" - 55.92721026899999969828.send(@method).should == "55.927210269" - 4.00000000000000000000.send(@method).should == "4.0" - 55.86693999999999959982.send(@method).should == "55.86694" - 787.98299999999994724931.send(@method).should == "787.983" - 5.73810511000000023074.send(@method).should == "5.73810511" - 74.51926810000000500622.send(@method).should == "74.5192681" - 892.89999999999997726263.send(@method).should == "892.9" - 68.27299999999999613465.send(@method).should == "68.273" - 904.10000000000002273737.send(@method).should == "904.1" - 5.23200000000000020606.send(@method).should == "5.232" - 4.09628000000000014325.send(@method).should == "4.09628" - 46.05152633699999853434.send(@method).should == "46.051526337" - 142.12884990599999923688.send(@method).should == "142.128849906" - 3.83057023500000015659.send(@method).should == "3.830570235" - 11.81684594699999912848.send(@method).should == "11.816845947" - 80.50000000000000000000.send(@method).should == "80.5" - 382.18215010000000120272.send(@method).should == "382.1821501" - 55.38444606899999911320.send(@method).should == "55.384446069" - 5.78000000000000024869.send(@method).should == "5.78" - 2.88244999999999995666.send(@method).should == "2.88245" - 43.27709999999999723741.send(@method).should == "43.2771" - end - - it "random values from divisions" do - (1.0 / 7).send(@method).should == "0.14285714285714285" - - # 50.times do - # a = rand(10) - # b = rand(10) - # c = rand(10) - # d = rand(10) - # expression = "#{a}.#{b} / #{c}.#{d}" - # puts " (#{expression}).send(@method).should == #{eval(expression).send(@method).inspect}" - # end - - (1.1 / 7.1).send(@method).should == "0.15492957746478875" - (6.5 / 8.8).send(@method).should == "0.7386363636363635" - (4.8 / 4.3).send(@method).should == "1.1162790697674418" - (4.0 / 1.9).send(@method).should == "2.1052631578947367" - (9.1 / 0.8).send(@method).should == "11.374999999999998" - (5.3 / 7.5).send(@method).should == "0.7066666666666667" - (2.8 / 1.8).send(@method).should == "1.5555555555555554" - (2.1 / 2.5).send(@method).should == "0.8400000000000001" - (3.5 / 6.0).send(@method).should == "0.5833333333333334" - (4.6 / 0.3).send(@method).should == "15.333333333333332" - (0.6 / 2.4).send(@method).should == "0.25" - (1.3 / 9.1).send(@method).should == "0.14285714285714288" - (0.3 / 5.0).send(@method).should == "0.06" - (5.0 / 4.2).send(@method).should == "1.1904761904761905" - (3.0 / 2.0).send(@method).should == "1.5" - (6.3 / 2.0).send(@method).should == "3.15" - (5.4 / 6.0).send(@method).should == "0.9" - (9.6 / 8.1).send(@method).should == "1.1851851851851851" - (8.7 / 1.6).send(@method).should == "5.437499999999999" - (1.9 / 7.8).send(@method).should == "0.24358974358974358" - (0.5 / 2.1).send(@method).should == "0.23809523809523808" - (9.3 / 5.8).send(@method).should == "1.6034482758620692" - (2.7 / 8.0).send(@method).should == "0.3375" - (9.7 / 7.8).send(@method).should == "1.2435897435897436" - (8.1 / 2.4).send(@method).should == "3.375" - (7.7 / 2.7).send(@method).should == "2.8518518518518516" - (7.9 / 1.7).send(@method).should == "4.647058823529412" - (6.5 / 8.2).send(@method).should == "0.7926829268292683" - (7.8 / 9.6).send(@method).should == "0.8125" - (2.2 / 4.6).send(@method).should == "0.47826086956521746" - (0.0 / 1.0).send(@method).should == "0.0" - (8.3 / 2.9).send(@method).should == "2.8620689655172415" - (3.1 / 6.1).send(@method).should == "0.5081967213114754" - (2.8 / 7.8).send(@method).should == "0.358974358974359" - (8.0 / 0.1).send(@method).should == "80.0" - (1.7 / 6.4).send(@method).should == "0.265625" - (1.8 / 5.4).send(@method).should == "0.3333333333333333" - (8.0 / 5.8).send(@method).should == "1.3793103448275863" - (5.2 / 4.1).send(@method).should == "1.2682926829268295" - (9.8 / 5.8).send(@method).should == "1.6896551724137934" - (5.4 / 9.5).send(@method).should == "0.5684210526315789" - (8.4 / 4.9).send(@method).should == "1.7142857142857142" - (1.7 / 3.5).send(@method).should == "0.4857142857142857" - (1.2 / 5.1).send(@method).should == "0.23529411764705882" - (1.4 / 2.0).send(@method).should == "0.7" - (4.8 / 8.0).send(@method).should == "0.6" - (9.0 / 2.5).send(@method).should == "3.6" - (0.2 / 0.6).send(@method).should == "0.33333333333333337" - (7.8 / 5.2).send(@method).should == "1.5" - (9.5 / 5.5).send(@method).should == "1.7272727272727273" - end - end - - describe 'encoding' do - before :each do - @internal = Encoding.default_internal - end - - after :each do - Encoding.default_internal = @internal - end - - it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do - Encoding.default_internal = nil - 1.23.send(@method).encoding.should.equal?(Encoding::US_ASCII) - end - - it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do - Encoding.default_internal = Encoding::IBM437 - 5.47.send(@method).encoding.should.equal?(Encoding::US_ASCII) - end - end -end diff --git a/core/float/to_int_spec.rb b/core/float/to_int_spec.rb index 084a58b43..ff70d508f 100644 --- a/core/float/to_int_spec.rb +++ b/core/float/to_int_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/to_i' describe "Float#to_int" do - it_behaves_like :float_to_i, :to_int + it "is an alias of Float#to_i" do + Float.instance_method(:to_int).should == Float.instance_method(:to_i) + end end diff --git a/core/float/to_s_spec.rb b/core/float/to_s_spec.rb index 6727a883f..3fd64581c 100644 --- a/core/float/to_s_spec.rb +++ b/core/float/to_s_spec.rb @@ -1,6 +1,310 @@ require_relative '../../spec_helper' -require_relative 'shared/to_s' describe "Float#to_s" do - it_behaves_like :float_to_s, :to_s + it "returns 'NaN' for NaN" do + nan_value().to_s.should == 'NaN' + end + + it "returns 'Infinity' for positive infinity" do + infinity_value().to_s.should == 'Infinity' + end + + it "returns '-Infinity' for negative infinity" do + (-infinity_value()).to_s.should == '-Infinity' + end + + it "returns '0.0' for 0.0" do + 0.0.to_s.should == "0.0" + end + + platform_is_not :openbsd do + it "emits '-' for -0.0" do + -0.0.to_s.should == "-0.0" + end + end + + it "emits a '-' for negative values" do + -3.14.to_s.should == "-3.14" + end + + it "emits a trailing '.0' for a whole number" do + 50.0.to_s.should == "50.0" + end + + it "emits a trailing '.0' for the mantissa in e format" do + 1.0e20.to_s.should == "1.0e+20" + end + + it "uses non-e format for a positive value with fractional part having 5 significant figures" do + 0.0001.to_s.should == "0.0001" + end + + it "uses non-e format for a negative value with fractional part having 5 significant figures" do + -0.0001.to_s.should == "-0.0001" + end + + it "uses e format for a positive value with fractional part having 6 significant figures" do + 0.00001.to_s.should == "1.0e-05" + end + + it "uses e format for a negative value with fractional part having 6 significant figures" do + -0.00001.to_s.should == "-1.0e-05" + end + + it "uses non-e format for a positive value with whole part having 15 significant figures" do + 10000000000000.0.to_s.should == "10000000000000.0" + end + + it "uses non-e format for a negative value with whole part having 15 significant figures" do + -10000000000000.0.to_s.should == "-10000000000000.0" + end + + it "uses non-e format for a positive value with whole part having 16 significant figures" do + 100000000000000.0.to_s.should == "100000000000000.0" + end + + it "uses non-e format for a negative value with whole part having 16 significant figures" do + -100000000000000.0.to_s.should == "-100000000000000.0" + end + + it "uses e format for a positive value with whole part having 18 significant figures" do + 10000000000000000.0.to_s.should == "1.0e+16" + end + + it "uses e format for a negative value with whole part having 18 significant figures" do + -10000000000000000.0.to_s.should == "-1.0e+16" + end + + it "uses e format for a positive value with whole part having 17 significant figures" do + 1000000000000000.0.to_s.should == "1.0e+15" + end + + it "uses e format for a negative value with whole part having 17 significant figures" do + -1000000000000000.0.to_s.should == "-1.0e+15" + end + + # #3273 + it "outputs the minimal, unique form necessary to recreate the value" do + value = 0.21611564636388508 + string = "0.21611564636388508" + + value.to_s.should == string + string.to_f.should == value + end + + it "outputs the minimal, unique form to represent the value" do + 0.56.to_s.should == "0.56" + end + + describe "matches" do + it "random examples in all ranges" do + # 50.times do + # bytes = (0...8).map { rand(256) } + # string = bytes.pack('C8') + # float = string.unpack('D').first + # puts "#{'%.20g' % float}.to_s.should == #{float.to_s.inspect}" + # end + + 2.5540217314354050325e+163.to_s.should == "2.554021731435405e+163" + 2.5492588360356597544e-172.to_s.should == "2.5492588360356598e-172" + 1.742770260934704852e-82.to_s.should == "1.7427702609347049e-82" + 6.2108093676180883209e-104.to_s.should == "6.210809367618088e-104" + -3.3448803488331067402e-143.to_s.should == "-3.3448803488331067e-143" + -2.2740074343500832557e-168.to_s.should == "-2.2740074343500833e-168" + 7.0587971678048535732e+191.to_s.should == "7.058797167804854e+191" + -284438.88327586348169.to_s.should == "-284438.8832758635" + 3.953272468476091301e+105.to_s.should == "3.9532724684760913e+105" + -3.6361359552959847853e+100.to_s.should == "-3.636135955295985e+100" + -1.3222325865575206185e-31.to_s.should == "-1.3222325865575206e-31" + 1.1440138916932761366e+130.to_s.should == "1.1440138916932761e+130" + 4.8750891560387561157e-286.to_s.should == "4.875089156038756e-286" + 5.6101113356591453525e-257.to_s.should == "5.610111335659145e-257" + -3.829644279545809575e-100.to_s.should == "-3.8296442795458096e-100" + 1.5342839401396406117e-194.to_s.should == "1.5342839401396406e-194" + 2.2284972755169921402e-144.to_s.should == "2.228497275516992e-144" + 2.1825655917065601737e-61.to_s.should == "2.1825655917065602e-61" + -2.6672271363524338322e-62.to_s.should == "-2.667227136352434e-62" + -1.9257995160119059415e+21.to_s.should == "-1.925799516011906e+21" + -8.9096732962887121718e-198.to_s.should == "-8.909673296288712e-198" + 2.0202075376548644959e-90.to_s.should == "2.0202075376548645e-90" + -7.7341602581786258961e-266.to_s.should == "-7.734160258178626e-266" + 3.5134482598733635046e+98.to_s.should == "3.5134482598733635e+98" + -2.124411722371029134e+154.to_s.should == "-2.124411722371029e+154" + -4.573908787355718687e+110.to_s.should == "-4.573908787355719e+110" + -1.9344425934170969879e-232.to_s.should == "-1.934442593417097e-232" + -1.3274227399979271095e+171.to_s.should == "-1.3274227399979271e+171" + 9.3495270482104442383e-283.to_s.should == "9.349527048210444e-283" + -4.2046059371986483233e+307.to_s.should == "-4.2046059371986483e+307" + 3.6133547278583543004e-117.to_s.should == "3.613354727858354e-117" + 4.9247416523566613499e-08.to_s.should == "4.9247416523566613e-08" + 1.6936145488250064007e-71.to_s.should == "1.6936145488250064e-71" + 2.4455483206829433098e+96.to_s.should == "2.4455483206829433e+96" + 7.9797449851436455384e+124.to_s.should == "7.979744985143646e+124" + -1.3873689634457876774e-129.to_s.should == "-1.3873689634457877e-129" + 3.9761102037533483075e+284.to_s.should == "3.976110203753348e+284" + -4.2819791952139402486e-303.to_s.should == "-4.28197919521394e-303" + -5.7981017546689831298e-116.to_s.should == "-5.798101754668983e-116" + -3.953266497860534199e-28.to_s.should == "-3.953266497860534e-28" + -2.0659852720290440959e-243.to_s.should == "-2.065985272029044e-243" + 8.9670488995878688018e-05.to_s.should == "8.967048899587869e-05" + -1.2317943708113061768e-98.to_s.should == "-1.2317943708113062e-98" + -3.8930768307633080463e+248.to_s.should == "-3.893076830763308e+248" + 6.5854032671803925627e-239.to_s.should == "6.5854032671803926e-239" + 4.6257022188980878952e+177.to_s.should == "4.625702218898088e+177" + -1.9397155125507235603e-187.to_s.should == "-1.9397155125507236e-187" + 8.5752156951245705056e+117.to_s.should == "8.57521569512457e+117" + -2.4784875958162501671e-132.to_s.should == "-2.4784875958162502e-132" + -4.4125691841230058457e-203.to_s.should == "-4.412569184123006e-203" + end + + it "random examples in human ranges" do + # 50.times do + # formatted = '' + # rand(1..3).times do + # formatted << rand(10).to_s + # end + # formatted << '.' + # rand(1..9).times do + # formatted << rand(10).to_s + # end + # float = formatted.to_f + # puts "#{'%.20f' % float}.to_s.should == #{float.to_s.inspect}" + # end + + 5.17869899999999994122.to_s.should == "5.178699" + 905.62695729999995819526.to_s.should == "905.6269573" + 62.75999999999999801048.to_s.should == "62.76" + 6.93856795800000014651.to_s.should == "6.938567958" + 4.95999999999999996447.to_s.should == "4.96" + 32.77993899999999882766.to_s.should == "32.779939" + 544.12756779999995160324.to_s.should == "544.1275678" + 66.25801119999999855281.to_s.should == "66.2580112" + 7.90000000000000035527.to_s.should == "7.9" + 5.93100000000000004974.to_s.should == "5.931" + 5.21229313600000043749.to_s.should == "5.212293136" + 503.44173809000000119340.to_s.should == "503.44173809" + 79.26000000000000511591.to_s.should == "79.26" + 8.51524999999999998579.to_s.should == "8.51525" + 174.00000000000000000000.to_s.should == "174.0" + 50.39580000000000126192.to_s.should == "50.3958" + 35.28999999999999914735.to_s.should == "35.29" + 5.43136675399999990788.to_s.should == "5.431366754" + 654.07680000000004838512.to_s.should == "654.0768" + 6.07423700000000010846.to_s.should == "6.074237" + 102.25779799999999397642.to_s.should == "102.257798" + 5.08129999999999970584.to_s.should == "5.0813" + 6.00000000000000000000.to_s.should == "6.0" + 8.30000000000000071054.to_s.should == "8.3" + 32.68345999999999662577.to_s.should == "32.68346" + 581.11170000000004165486.to_s.should == "581.1117" + 76.31342999999999676675.to_s.should == "76.31343" + 438.30826000000001840817.to_s.should == "438.30826" + 482.06631994000002805478.to_s.should == "482.06631994" + 55.92721026899999969828.to_s.should == "55.927210269" + 4.00000000000000000000.to_s.should == "4.0" + 55.86693999999999959982.to_s.should == "55.86694" + 787.98299999999994724931.to_s.should == "787.983" + 5.73810511000000023074.to_s.should == "5.73810511" + 74.51926810000000500622.to_s.should == "74.5192681" + 892.89999999999997726263.to_s.should == "892.9" + 68.27299999999999613465.to_s.should == "68.273" + 904.10000000000002273737.to_s.should == "904.1" + 5.23200000000000020606.to_s.should == "5.232" + 4.09628000000000014325.to_s.should == "4.09628" + 46.05152633699999853434.to_s.should == "46.051526337" + 142.12884990599999923688.to_s.should == "142.128849906" + 3.83057023500000015659.to_s.should == "3.830570235" + 11.81684594699999912848.to_s.should == "11.816845947" + 80.50000000000000000000.to_s.should == "80.5" + 382.18215010000000120272.to_s.should == "382.1821501" + 55.38444606899999911320.to_s.should == "55.384446069" + 5.78000000000000024869.to_s.should == "5.78" + 2.88244999999999995666.to_s.should == "2.88245" + 43.27709999999999723741.to_s.should == "43.2771" + end + + it "random values from divisions" do + (1.0 / 7).to_s.should == "0.14285714285714285" + + # 50.times do + # a = rand(10) + # b = rand(10) + # c = rand(10) + # d = rand(10) + # expression = "#{a}.#{b} / #{c}.#{d}" + # puts " (#{expression}).to_s.should == #{eval(expression).to_s.inspect}" + # end + + (1.1 / 7.1).to_s.should == "0.15492957746478875" + (6.5 / 8.8).to_s.should == "0.7386363636363635" + (4.8 / 4.3).to_s.should == "1.1162790697674418" + (4.0 / 1.9).to_s.should == "2.1052631578947367" + (9.1 / 0.8).to_s.should == "11.374999999999998" + (5.3 / 7.5).to_s.should == "0.7066666666666667" + (2.8 / 1.8).to_s.should == "1.5555555555555554" + (2.1 / 2.5).to_s.should == "0.8400000000000001" + (3.5 / 6.0).to_s.should == "0.5833333333333334" + (4.6 / 0.3).to_s.should == "15.333333333333332" + (0.6 / 2.4).to_s.should == "0.25" + (1.3 / 9.1).to_s.should == "0.14285714285714288" + (0.3 / 5.0).to_s.should == "0.06" + (5.0 / 4.2).to_s.should == "1.1904761904761905" + (3.0 / 2.0).to_s.should == "1.5" + (6.3 / 2.0).to_s.should == "3.15" + (5.4 / 6.0).to_s.should == "0.9" + (9.6 / 8.1).to_s.should == "1.1851851851851851" + (8.7 / 1.6).to_s.should == "5.437499999999999" + (1.9 / 7.8).to_s.should == "0.24358974358974358" + (0.5 / 2.1).to_s.should == "0.23809523809523808" + (9.3 / 5.8).to_s.should == "1.6034482758620692" + (2.7 / 8.0).to_s.should == "0.3375" + (9.7 / 7.8).to_s.should == "1.2435897435897436" + (8.1 / 2.4).to_s.should == "3.375" + (7.7 / 2.7).to_s.should == "2.8518518518518516" + (7.9 / 1.7).to_s.should == "4.647058823529412" + (6.5 / 8.2).to_s.should == "0.7926829268292683" + (7.8 / 9.6).to_s.should == "0.8125" + (2.2 / 4.6).to_s.should == "0.47826086956521746" + (0.0 / 1.0).to_s.should == "0.0" + (8.3 / 2.9).to_s.should == "2.8620689655172415" + (3.1 / 6.1).to_s.should == "0.5081967213114754" + (2.8 / 7.8).to_s.should == "0.358974358974359" + (8.0 / 0.1).to_s.should == "80.0" + (1.7 / 6.4).to_s.should == "0.265625" + (1.8 / 5.4).to_s.should == "0.3333333333333333" + (8.0 / 5.8).to_s.should == "1.3793103448275863" + (5.2 / 4.1).to_s.should == "1.2682926829268295" + (9.8 / 5.8).to_s.should == "1.6896551724137934" + (5.4 / 9.5).to_s.should == "0.5684210526315789" + (8.4 / 4.9).to_s.should == "1.7142857142857142" + (1.7 / 3.5).to_s.should == "0.4857142857142857" + (1.2 / 5.1).to_s.should == "0.23529411764705882" + (1.4 / 2.0).to_s.should == "0.7" + (4.8 / 8.0).to_s.should == "0.6" + (9.0 / 2.5).to_s.should == "3.6" + (0.2 / 0.6).to_s.should == "0.33333333333333337" + (7.8 / 5.2).to_s.should == "1.5" + (9.5 / 5.5).to_s.should == "1.7272727272727273" + end + end + + describe 'encoding' do + before :each do + @internal = Encoding.default_internal + end + + after :each do + Encoding.default_internal = @internal + end + + it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do + Encoding.default_internal = nil + 1.23.to_s.encoding.should.equal?(Encoding::US_ASCII) + end + + it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do + Encoding.default_internal = Encoding::IBM437 + 5.47.to_s.encoding.should.equal?(Encoding::US_ASCII) + end + end end diff --git a/core/integer/abs_spec.rb b/core/integer/abs_spec.rb index c40356db1..768eebdce 100644 --- a/core/integer/abs_spec.rb +++ b/core/integer/abs_spec.rb @@ -1,6 +1,20 @@ require_relative '../../spec_helper' -require_relative 'shared/abs' describe "Integer#abs" do - it_behaves_like :integer_abs, :abs + context "fixnum" do + it "returns self's absolute fixnum value" do + { 0 => [0, -0, +0], 2 => [2, -2, +2], 100 => [100, -100, +100] }.each do |key, values| + values.each do |value| + value.abs.should == key + end + end + end + end + + context "bignum" do + it "returns the absolute bignum value" do + bignum_value(39).abs.should == 18446744073709551655 + (-bignum_value(18)).abs.should == 18446744073709551634 + end + end end diff --git a/core/integer/case_compare_spec.rb b/core/integer/case_compare_spec.rb index e5dde2c64..1e0c6cb41 100644 --- a/core/integer/case_compare_spec.rb +++ b/core/integer/case_compare_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/equal' describe "Integer#===" do - it_behaves_like :integer_equal, :=== + it "is an alias of Integer#==" do + Integer.instance_method(:===).should == Integer.instance_method(:==) + end end diff --git a/core/integer/equal_value_spec.rb b/core/integer/equal_value_spec.rb index 67a73713a..dc7355226 100644 --- a/core/integer/equal_value_spec.rb +++ b/core/integer/equal_value_spec.rb @@ -1,6 +1,65 @@ require_relative '../../spec_helper' -require_relative 'shared/equal' describe "Integer#==" do - it_behaves_like :integer_equal, :== + context "fixnum" do + it "returns true if self has the same value as other" do + (1 == 1).should == true + (9 == 5).should == false + + # Actually, these call Float#==, Integer#== etc. + (9 == 9.0).should == true + (9 == 9.01).should == false + + (10 == bignum_value).should == false + end + + it "calls 'other == self' if the given argument is not an Integer" do + (1 == '*').should == false + + obj = mock('one other') + obj.should_receive(:==).any_number_of_times.and_return(false) + (1 == obj).should == false + + obj = mock('another') + obj.should_receive(:==).any_number_of_times.and_return(true) + (2 == obj).should == true + end + end + + context "bignum" do + before :each do + @bignum = bignum_value + end + + it "returns true if self has the same value as the given argument" do + (@bignum == @bignum).should == true + (@bignum == @bignum.to_f).should == true + + (@bignum == @bignum + 1).should == false + ((@bignum + 1) == @bignum).should == false + + (@bignum == 9).should == false + (@bignum == 9.01).should == false + + (@bignum == bignum_value(10)).should == false + end + + it "calls 'other == self' if the given argument is not an Integer" do + obj = mock('not integer') + obj.should_receive(:==).and_return(true) + (@bignum == obj).should == true + end + + it "returns the result of 'other == self' as a boolean" do + obj = mock('not integer') + obj.should_receive(:==).exactly(2).times.and_return("woot", nil) + (@bignum == obj).should == true + (@bignum == obj).should == false + end + + it "does not lose precision when comparing with a Float" do + ((bignum_value(1) == bignum_value.to_f)).should == false + ((bignum_value == bignum_value.to_f)).should == true + end + end end diff --git a/core/integer/magnitude_spec.rb b/core/integer/magnitude_spec.rb index 48cf1a853..000e5be7f 100644 --- a/core/integer/magnitude_spec.rb +++ b/core/integer/magnitude_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/abs' describe "Integer#magnitude" do - it_behaves_like :integer_abs, :magnitude + it "is an alias of Integer#abs" do + Integer.instance_method(:magnitude).should == Integer.instance_method(:abs) + end end diff --git a/core/integer/modulo_spec.rb b/core/integer/modulo_spec.rb index e263338e3..2680f4951 100644 --- a/core/integer/modulo_spec.rb +++ b/core/integer/modulo_spec.rb @@ -1,10 +1,122 @@ require_relative '../../spec_helper' -require_relative 'shared/modulo' describe "Integer#%" do - it_behaves_like :integer_modulo, :% + context "fixnum" do + it "returns the modulus obtained from dividing self by the given argument" do + # test all possible combinations: + # - integer/double/bignum argument + # - positive/negative argument + # - positive/negative self + # - self greater/smaller than argument + + (13 % 4).should == 1 + (4 % 13).should == 4 + + (13 % 4.0).should == 1 + (4 % 13.0).should == 4 + + (-200 % 256).should == 56 + (-1000 % 512).should == 24 + + (-200 % -256).should == -200 + (-1000 % -512).should == -488 + + (200 % -256).should == -56 + (1000 % -512).should == -24 + + (13 % -4.0).should == -3.0 + (4 % -13.0).should == -9.0 + + (-13 % -4.0).should == -1.0 + (-4 % -13.0).should == -4.0 + + (-13 % 4.0).should == 3.0 + (-4 % 13.0).should == 9.0 + + (1 % 2.0).should == 1.0 + (200 % bignum_value).should == 200 + + (4 % bignum_value(10)).should == 4 + (4 % -bignum_value(10)).should == -18446744073709551622 + (-4 % bignum_value(10)).should == 18446744073709551622 + (-4 % -bignum_value(10)).should == -4 + end + + it "raises a ZeroDivisionError when the given argument is 0" do + -> { 13 % 0 }.should.raise(ZeroDivisionError) + -> { 0 % 0 }.should.raise(ZeroDivisionError) + -> { -10 % 0 }.should.raise(ZeroDivisionError) + end + + it "raises a ZeroDivisionError when the given argument is 0 and a Float" do + -> { 0 % 0.0 }.should.raise(ZeroDivisionError) + -> { 10 % 0.0 }.should.raise(ZeroDivisionError) + -> { -10 % 0.0 }.should.raise(ZeroDivisionError) + end + + it "raises a TypeError when given a non-Integer" do + -> { + (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10) + 13 % obj + }.should.raise(TypeError) + -> { 13 % "10" }.should.raise(TypeError) + -> { 13 % :symbol }.should.raise(TypeError) + end + end + + context "bignum" do + before :each do + @bignum = bignum_value(10) + end + + it "returns the modulus obtained from dividing self by the given argument" do + # test all possible combinations: + # - integer/double/bignum argument + # - positive/negative argument + # - positive/negative self + # - self greater/smaller than argument + + (@bignum % 5).should == 1 + (@bignum % -5).should == -4 + (-@bignum % 5).should == 4 + (-@bignum % -5).should == -1 + + (@bignum % 2.22).should be_close(1.5603603603605034, TOLERANCE) + (@bignum % -2.22).should be_close(-0.6596396396394968, TOLERANCE) + (-@bignum % 2.22).should be_close(0.6596396396394968, TOLERANCE) + (-@bignum % -2.22).should be_close(-1.5603603603605034, TOLERANCE) + + (@bignum % (@bignum + 10)).should == 18446744073709551626 + (@bignum % -(@bignum + 10)).should == -10 + (-@bignum % (@bignum + 10)).should == 10 + (-@bignum % -(@bignum + 10)).should == -18446744073709551626 + + ((@bignum + 10) % @bignum).should == 10 + ((@bignum + 10) % -@bignum).should == -18446744073709551616 + (-(@bignum + 10) % @bignum).should == 18446744073709551616 + (-(@bignum + 10) % -@bignum).should == -10 + end + + it "raises a ZeroDivisionError when the given argument is 0" do + -> { @bignum % 0 }.should.raise(ZeroDivisionError) + -> { -@bignum % 0 }.should.raise(ZeroDivisionError) + end + + it "raises a ZeroDivisionError when the given argument is 0 and a Float" do + -> { @bignum % 0.0 }.should.raise(ZeroDivisionError) + -> { -@bignum % 0.0 }.should.raise(ZeroDivisionError) + end + + it "raises a TypeError when given a non-Integer" do + -> { @bignum % mock('10') }.should.raise(TypeError) + -> { @bignum % "10" }.should.raise(TypeError) + -> { @bignum % :symbol }.should.raise(TypeError) + end + end end describe "Integer#modulo" do - it_behaves_like :integer_modulo, :modulo + it "is an alias of Integer#%" do + Integer.instance_method(:modulo).should == Integer.instance_method(:%) + end end diff --git a/core/integer/next_spec.rb b/core/integer/next_spec.rb index 63c4e6789..da54dec45 100644 --- a/core/integer/next_spec.rb +++ b/core/integer/next_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/next' describe "Integer#next" do - it_behaves_like :integer_next, :next + it "is an alias of Integer#succ" do + Integer.instance_method(:next).should == Integer.instance_method(:succ) + end end diff --git a/core/integer/shared/abs.rb b/core/integer/shared/abs.rb deleted file mode 100644 index 43844c906..000000000 --- a/core/integer/shared/abs.rb +++ /dev/null @@ -1,18 +0,0 @@ -describe :integer_abs, shared: true do - context "fixnum" do - it "returns self's absolute fixnum value" do - { 0 => [0, -0, +0], 2 => [2, -2, +2], 100 => [100, -100, +100] }.each do |key, values| - values.each do |value| - value.send(@method).should == key - end - end - end - end - - context "bignum" do - it "returns the absolute bignum value" do - bignum_value(39).send(@method).should == 18446744073709551655 - (-bignum_value(18)).send(@method).should == 18446744073709551634 - end - end -end diff --git a/core/integer/shared/equal.rb b/core/integer/shared/equal.rb deleted file mode 100644 index c621ba3f8..000000000 --- a/core/integer/shared/equal.rb +++ /dev/null @@ -1,63 +0,0 @@ -describe :integer_equal, shared: true do - context "fixnum" do - it "returns true if self has the same value as other" do - 1.send(@method, 1).should == true - 9.send(@method, 5).should == false - - # Actually, these call Float#==, Integer#== etc. - 9.send(@method, 9.0).should == true - 9.send(@method, 9.01).should == false - - 10.send(@method, bignum_value).should == false - end - - it "calls 'other == self' if the given argument is not an Integer" do - 1.send(@method, '*').should == false - - obj = mock('one other') - obj.should_receive(:==).any_number_of_times.and_return(false) - 1.send(@method, obj).should == false - - obj = mock('another') - obj.should_receive(:==).any_number_of_times.and_return(true) - 2.send(@method, obj).should == true - end - end - - context "bignum" do - before :each do - @bignum = bignum_value - end - - it "returns true if self has the same value as the given argument" do - @bignum.send(@method, @bignum).should == true - @bignum.send(@method, @bignum.to_f).should == true - - @bignum.send(@method, @bignum + 1).should == false - (@bignum + 1).send(@method, @bignum).should == false - - @bignum.send(@method, 9).should == false - @bignum.send(@method, 9.01).should == false - - @bignum.send(@method, bignum_value(10)).should == false - end - - it "calls 'other == self' if the given argument is not an Integer" do - obj = mock('not integer') - obj.should_receive(:==).and_return(true) - @bignum.send(@method, obj).should == true - end - - it "returns the result of 'other == self' as a boolean" do - obj = mock('not integer') - obj.should_receive(:==).exactly(2).times.and_return("woot", nil) - @bignum.send(@method, obj).should == true - @bignum.send(@method, obj).should == false - end - - it "does not lose precision when comparing with a Float" do - (bignum_value(1).send(@method, bignum_value.to_f)).should == false - (bignum_value.send(@method, bignum_value.to_f)).should == true - end - end -end diff --git a/core/integer/shared/modulo.rb b/core/integer/shared/modulo.rb deleted file mode 100644 index d0b5e26ed..000000000 --- a/core/integer/shared/modulo.rb +++ /dev/null @@ -1,114 +0,0 @@ -describe :integer_modulo, shared: true do - context "fixnum" do - it "returns the modulus obtained from dividing self by the given argument" do - # test all possible combinations: - # - integer/double/bignum argument - # - positive/negative argument - # - positive/negative self - # - self greater/smaller than argument - - 13.send(@method, 4).should == 1 - 4.send(@method, 13).should == 4 - - 13.send(@method, 4.0).should == 1 - 4.send(@method, 13.0).should == 4 - - (-200).send(@method, 256).should == 56 - (-1000).send(@method, 512).should == 24 - - (-200).send(@method, -256).should == -200 - (-1000).send(@method, -512).should == -488 - - (200).send(@method, -256).should == -56 - (1000).send(@method, -512).should == -24 - - 13.send(@method, -4.0).should == -3.0 - 4.send(@method, -13.0).should == -9.0 - - -13.send(@method, -4.0).should == -1.0 - -4.send(@method, -13.0).should == -4.0 - - -13.send(@method, 4.0).should == 3.0 - -4.send(@method, 13.0).should == 9.0 - - 1.send(@method, 2.0).should == 1.0 - 200.send(@method, bignum_value).should == 200 - - 4.send(@method, bignum_value(10)).should == 4 - 4.send(@method, -bignum_value(10)).should == -18446744073709551622 - -4.send(@method, bignum_value(10)).should == 18446744073709551622 - -4.send(@method, -bignum_value(10)).should == -4 - end - - it "raises a ZeroDivisionError when the given argument is 0" do - -> { 13.send(@method, 0) }.should.raise(ZeroDivisionError) - -> { 0.send(@method, 0) }.should.raise(ZeroDivisionError) - -> { -10.send(@method, 0) }.should.raise(ZeroDivisionError) - end - - it "raises a ZeroDivisionError when the given argument is 0 and a Float" do - -> { 0.send(@method, 0.0) }.should.raise(ZeroDivisionError) - -> { 10.send(@method, 0.0) }.should.raise(ZeroDivisionError) - -> { -10.send(@method, 0.0) }.should.raise(ZeroDivisionError) - end - - it "raises a TypeError when given a non-Integer" do - -> { - (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10) - 13.send(@method, obj) - }.should.raise(TypeError) - -> { 13.send(@method, "10") }.should.raise(TypeError) - -> { 13.send(@method, :symbol) }.should.raise(TypeError) - end - end - - context "bignum" do - before :each do - @bignum = bignum_value(10) - end - - it "returns the modulus obtained from dividing self by the given argument" do - # test all possible combinations: - # - integer/double/bignum argument - # - positive/negative argument - # - positive/negative self - # - self greater/smaller than argument - - @bignum.send(@method, 5).should == 1 - @bignum.send(@method, -5).should == -4 - (-@bignum).send(@method, 5).should == 4 - (-@bignum).send(@method, -5).should == -1 - - @bignum.send(@method, 2.22).should be_close(1.5603603603605034, TOLERANCE) - @bignum.send(@method, -2.22).should be_close(-0.6596396396394968, TOLERANCE) - (-@bignum).send(@method, 2.22).should be_close(0.6596396396394968, TOLERANCE) - (-@bignum).send(@method, -2.22).should be_close(-1.5603603603605034, TOLERANCE) - - @bignum.send(@method, @bignum + 10).should == 18446744073709551626 - @bignum.send(@method, -(@bignum + 10)).should == -10 - (-@bignum).send(@method, @bignum + 10).should == 10 - (-@bignum).send(@method, -(@bignum + 10)).should == -18446744073709551626 - - (@bignum + 10).send(@method, @bignum).should == 10 - (@bignum + 10).send(@method, -@bignum).should == -18446744073709551616 - (-(@bignum + 10)).send(@method, @bignum).should == 18446744073709551616 - (-(@bignum + 10)).send(@method, -@bignum).should == -10 - end - - it "raises a ZeroDivisionError when the given argument is 0" do - -> { @bignum.send(@method, 0) }.should.raise(ZeroDivisionError) - -> { (-@bignum).send(@method, 0) }.should.raise(ZeroDivisionError) - end - - it "raises a ZeroDivisionError when the given argument is 0 and a Float" do - -> { @bignum.send(@method, 0.0) }.should.raise(ZeroDivisionError) - -> { -@bignum.send(@method, 0.0) }.should.raise(ZeroDivisionError) - end - - it "raises a TypeError when given a non-Integer" do - -> { @bignum.send(@method, mock('10')) }.should.raise(TypeError) - -> { @bignum.send(@method, "10") }.should.raise(TypeError) - -> { @bignum.send(@method, :symbol) }.should.raise(TypeError) - end - end -end diff --git a/core/integer/shared/next.rb b/core/integer/shared/next.rb deleted file mode 100644 index 85b83d696..000000000 --- a/core/integer/shared/next.rb +++ /dev/null @@ -1,25 +0,0 @@ -describe :integer_next, shared: true do - it "returns the next larger positive Fixnum" do - 2.send(@method).should == 3 - end - - it "returns the next larger negative Fixnum" do - (-2).send(@method).should == -1 - end - - it "returns the next larger positive Bignum" do - bignum_value.send(@method).should == bignum_value(1) - end - - it "returns the next larger negative Bignum" do - (-bignum_value(1)).send(@method).should == -bignum_value - end - - it "overflows a Fixnum to a Bignum" do - fixnum_max.send(@method).should == fixnum_max + 1 - end - - it "underflows a Bignum to a Fixnum" do - (fixnum_min - 1).send(@method).should == fixnum_min - end -end diff --git a/core/integer/succ_spec.rb b/core/integer/succ_spec.rb index 9ae9a14fe..2201a4c76 100644 --- a/core/integer/succ_spec.rb +++ b/core/integer/succ_spec.rb @@ -1,6 +1,27 @@ require_relative '../../spec_helper' -require_relative 'shared/next' describe "Integer#succ" do - it_behaves_like :integer_next, :succ + it "returns the next larger positive Fixnum" do + 2.succ.should == 3 + end + + it "returns the next larger negative Fixnum" do + (-2).succ.should == -1 + end + + it "returns the next larger positive Bignum" do + bignum_value.succ.should == bignum_value(1) + end + + it "returns the next larger negative Bignum" do + (-bignum_value(1)).succ.should == -bignum_value + end + + it "overflows a Fixnum to a Bignum" do + fixnum_max.succ.should == fixnum_max + 1 + end + + it "underflows a Bignum to a Fixnum" do + (fixnum_min - 1).succ.should == fixnum_min + end end diff --git a/core/io/each_char_spec.rb b/core/io/each_char_spec.rb index 5d460a1e7..7c1ca4f06 100644 --- a/core/io/each_char_spec.rb +++ b/core/io/each_char_spec.rb @@ -1,12 +1,75 @@ -# -*- encoding: utf-8 -*- require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/chars' describe "IO#each_char" do - it_behaves_like :io_chars, :each_char + before :each do + @io = IOSpecs.io_fixture "lines.txt" + ScratchPad.record [] + end + + after :each do + @io.close unless @io.closed? + end + + it "yields each character" do + @io.readline.should == "Voici la ligne une.\n" + + count = 0 + @io.each_char do |c| + ScratchPad << c + break if 4 < count += 1 + end + + ScratchPad.recorded.should == ["Q", "u", "i", " ", "è"] + end + + describe "when no block is given" do + it "returns an Enumerator" do + enum = @io.each_char + enum.should.instance_of?(Enumerator) + enum.first(5).should == ["V", "o", "i", "c", "i"] + end + + describe "returned Enumerator" do + describe "size" do + it "should return nil" do + @io.each_char.size.should == nil + end + end + end + end + + it "returns itself" do + @io.each_char { |c| }.should.equal?(@io) + end + + it "returns an enumerator for a closed stream" do + IOSpecs.closed_io.each_char.should.instance_of?(Enumerator) + end + + it "raises an IOError when an enumerator created on a closed stream is accessed" do + -> { IOSpecs.closed_io.each_char.first }.should.raise(IOError) + end + + it "raises IOError on closed stream" do + -> { IOSpecs.closed_io.each_char {} }.should.raise(IOError) + end end describe "IO#each_char" do - it_behaves_like :io_chars_empty, :each_char + before :each do + @name = tmp("io_each_char") + @io = new_io @name, "w+:utf-8" + ScratchPad.record [] + end + + after :each do + @io.close unless @io.closed? + rm_r @name + end + + it "does not yield any characters on an empty stream" do + @io.each_char { |c| ScratchPad << c } + ScratchPad.recorded.should == [] + end end diff --git a/core/io/each_codepoint_spec.rb b/core/io/each_codepoint_spec.rb index 26cc87fc0..758a52498 100644 --- a/core/io/each_codepoint_spec.rb +++ b/core/io/each_codepoint_spec.rb @@ -1,10 +1,57 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/codepoints' # See redmine #1667 describe "IO#each_codepoint" do - it_behaves_like :io_codepoints, :each_codepoint + before :each do + @io = IOSpecs.io_fixture "lines.txt" + @enum = @io.each_codepoint + end + + after :each do + @io.close + end + + describe "when no block is given" do + it "returns an Enumerator" do + @enum.should.instance_of?(Enumerator) + end + + describe "returned Enumerator" do + describe "size" do + it "should return nil" do + @enum.size.should == nil + end + end + end + end + + it "yields each codepoint" do + @enum.first(25).should == [ + 86, 111, 105, 99, 105, 32, 108, 97, 32, 108, 105, 103, 110, + 101, 32, 117, 110, 101, 46, 10, 81, 117, 105, 32, 232 + ] + end + + it "yields each codepoint starting from the current position" do + @io.pos = 130 + @enum.to_a.should == [101, 32, 115, 105, 120, 46, 10] + end + + it "raises an error if reading invalid sequence" do + @io.pos = 60 # inside of a multibyte sequence + -> { @enum.first }.should.raise(ArgumentError) + end + + it "does not change $_" do + $_ = "test" + @enum.to_a + $_.should == "test" + end + + it "raises an IOError when self is not readable" do + -> { IOSpecs.closed_io.each_codepoint.to_a }.should.raise(IOError) + end end describe "IO#each_codepoint" do diff --git a/core/io/each_line_spec.rb b/core/io/each_line_spec.rb index 58d26b325..bcda4040b 100644 --- a/core/io/each_line_spec.rb +++ b/core/io/each_line_spec.rb @@ -1,11 +1,251 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/each' describe "IO#each_line" do - it_behaves_like :io_each, :each_line + before :each do + @io = IOSpecs.io_fixture "lines.txt" + ScratchPad.record [] + end + + after :each do + @io.close if @io + end + + describe "with no separator" do + it "yields each line to the passed block" do + @io.each_line { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.lines + end + + it "yields each line starting from the current position" do + @io.pos = 41 + @io.each_line { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.lines[2..-1] + end + + it "returns self" do + @io.each_line { |l| l }.should.equal?(@io) + end + + it "does not change $_" do + $_ = "test" + @io.each_line { |s| s } + $_.should == "test" + end + + it "raises an IOError when self is not readable" do + -> { IOSpecs.closed_io.each_line {} }.should.raise(IOError) + end + + it "makes line count accessible via lineno" do + @io.each_line { ScratchPad << @io.lineno } + ScratchPad.recorded.should == [ 1,2,3,4,5,6,7,8,9 ] + end + + it "makes line count accessible via $." do + @io.each_line { ScratchPad << $. } + ScratchPad.recorded.should == [ 1,2,3,4,5,6,7,8,9 ] + end + + describe "when no block is given" do + it "returns an Enumerator" do + enum = @io.each_line + enum.should.instance_of?(Enumerator) + + enum.each { |l| ScratchPad << l } + ScratchPad.recorded.should == IOSpecs.lines + end + + describe "returned Enumerator" do + describe "size" do + it "should return nil" do + @io.each_line.size.should == nil + end + end + end + end + end + + describe "with limit" do + describe "when limit is 0" do + it "raises an ArgumentError" do + # must pass block so Enumerator is evaluated and raises + -> { @io.each_line(0){} }.should.raise(ArgumentError) + end + end + + it "does not accept Integers that don't fit in a C off_t" do + -> { @io.each_line(2**128){} }.should.raise(RangeError) + end + end + + describe "when passed a String containing one space as a separator" do + it "uses the passed argument as the line separator" do + @io.each_line(" ") { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.lines_space_separator + end + + it "does not change $_" do + $_ = "test" + @io.each_line(" ") { |s| } + $_.should == "test" + end + + it "tries to convert the passed separator to a String using #to_str" do + obj = mock("to_str") + obj.stub!(:to_str).and_return(" ") + + @io.each_line(obj) { |l| ScratchPad << l } + ScratchPad.recorded.should == IOSpecs.lines_space_separator + end + end + + describe "when passed nil as a separator" do + it "yields self's content starting from the current position when the passed separator is nil" do + @io.pos = 100 + @io.each_line(nil) { |s| ScratchPad << s } + ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"] + end + end + + describe "when passed an empty String as a separator" do + it "yields each paragraph" do + @io.each_line("") { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.paragraphs + end + + it "discards leading newlines" do + @io.readline + @io.readline + @io.each_line("") { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.paragraphs[1..-1] + end + end + + describe "with both separator and limit" do + describe "when no block is given" do + it "returns an Enumerator" do + enum = @io.each_line(nil, 1024) + enum.should.instance_of?(Enumerator) + + enum.each { |l| ScratchPad << l } + ScratchPad.recorded.should == [IOSpecs.lines.join] + end + + describe "returned Enumerator" do + describe "size" do + it "should return nil" do + @io.each_line(nil, 1024).size.should == nil + end + end + end + end + + describe "when a block is given" do + it "accepts an empty block" do + @io.each_line(nil, 1024) {}.should.equal?(@io) + end + + describe "when passed nil as a separator" do + it "yields self's content starting from the current position when the passed separator is nil" do + @io.pos = 100 + @io.each_line(nil, 1024) { |s| ScratchPad << s } + ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"] + end + end + + describe "when passed an empty String as a separator" do + it "yields each paragraph" do + @io.each_line("", 1024) { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.paragraphs + end + + it "discards leading newlines" do + @io.readline + @io.readline + @io.each_line("", 1024) { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.paragraphs[1..-1] + end + end + end + end + + describe "when passed chomp" do + it "yields each line without trailing newline characters to the passed block" do + @io.each_line(chomp: true) { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.lines_without_newline_characters + end + + it "raises exception when options passed as Hash" do + -> { + @io.each_line({ chomp: true }) { |s| } + }.should.raise(TypeError) + + -> { + @io.each_line("\n", 1, { chomp: true }) { |s| } + }.should.raise(ArgumentError, "wrong number of arguments (given 3, expected 0..2)") + end + end + + describe "when passed chomp and a separator" do + it "yields each line without separator to the passed block" do + @io.each_line(" ", chomp: true) { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.lines_space_separator_without_trailing_spaces + end + end + + describe "when passed chomp and empty line as a separator" do + it "yields each paragraph without trailing new line characters" do + @io.each_line("", 1024, chomp: true) { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.paragraphs_without_trailing_new_line_characters + end + end + + describe "when passed chomp and nil as a separator" do + it "yields self's content" do + @io.pos = 100 + @io.each_line(nil, chomp: true) { |s| ScratchPad << s } + ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"] + end + end + + describe "when passed chomp, nil as a separator, and a limit" do + it "yields each line of limit size without truncating trailing new line character" do + # 43 - is a size of the 1st paragraph in the file + @io.each_line(nil, 43, chomp: true) { |s| ScratchPad << s } + + ScratchPad.recorded.should == [ + "Voici la ligne une.\nQui è la linea due.\n\n\n", + "Aquí está la línea tres.\n" + "Hier ist Zeile ", + "vier.\n\nEstá aqui a linha cinco.\nHere is li", + "ne six.\n" + ] + end + end + + describe "when passed too many arguments" do + it "raises ArgumentError" do + -> { + @io.each_line("", 1, "excess argument", chomp: true) {} + }.should.raise(ArgumentError) + end + end end describe "IO#each_line" do - it_behaves_like :io_each_default_separator, :each_line + before :each do + @io = IOSpecs.io_fixture "lines.txt" + ScratchPad.record [] + suppress_warning {@sep, $/ = $/, " "} + end + + after :each do + @io.close if @io + suppress_warning {$/ = @sep} + end + + it "uses $/ as the default line separator" do + @io.each_line { |s| ScratchPad << s } + ScratchPad.recorded.should == IOSpecs.lines_space_separator + end end diff --git a/core/io/each_spec.rb b/core/io/each_spec.rb index 91ecbd19c..594052256 100644 --- a/core/io/each_spec.rb +++ b/core/io/each_spec.rb @@ -1,11 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/each' describe "IO#each" do - it_behaves_like :io_each, :each -end - -describe "IO#each" do - it_behaves_like :io_each_default_separator, :each + it "is an alias of IO#each_line" do + IO.instance_method(:each).should == IO.instance_method(:each_line) + end end diff --git a/core/io/isatty_spec.rb b/core/io/isatty_spec.rb index 3b6c69b19..60b97d21b 100644 --- a/core/io/isatty_spec.rb +++ b/core/io/isatty_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/tty' describe "IO#isatty" do - it_behaves_like :io_tty, :isatty + it "is an alias of IO#tty?" do + IO.instance_method(:isatty).should == IO.instance_method(:tty?) + end end diff --git a/core/io/pos_spec.rb b/core/io/pos_spec.rb index e6cda2643..bbe25ce97 100644 --- a/core/io/pos_spec.rb +++ b/core/io/pos_spec.rb @@ -3,7 +3,37 @@ require_relative 'shared/pos' describe "IO#pos" do - it_behaves_like :io_pos, :pos + before :each do + @fname = tmp('test.txt') + File.open(@fname, 'w') { |f| f.write "123" } + end + + after :each do + rm_r @fname + end + + it "gets the offset" do + File.open @fname do |f| + f.pos.should == 0 + f.read 1 + f.pos.should == 1 + f.read 2 + f.pos.should == 3 + end + end + + it "raises IOError on closed stream" do + -> { IOSpecs.closed_io.pos }.should.raise(IOError) + end + + it "resets #eof?" do + open @fname do |io| + io.read 1 + io.read 1 + io.pos + io.should_not.eof? + end + end end describe "IO#pos=" do diff --git a/core/io/shared/chars.rb b/core/io/shared/chars.rb deleted file mode 100644 index efd4d5ee1..000000000 --- a/core/io/shared/chars.rb +++ /dev/null @@ -1,73 +0,0 @@ -# -*- encoding: utf-8 -*- -describe :io_chars, shared: true do - before :each do - @io = IOSpecs.io_fixture "lines.txt" - ScratchPad.record [] - end - - after :each do - @io.close unless @io.closed? - end - - it "yields each character" do - @io.readline.should == "Voici la ligne une.\n" - - count = 0 - @io.send(@method) do |c| - ScratchPad << c - break if 4 < count += 1 - end - - ScratchPad.recorded.should == ["Q", "u", "i", " ", "è"] - end - - describe "when no block is given" do - it "returns an Enumerator" do - enum = @io.send(@method) - enum.should.instance_of?(Enumerator) - enum.first(5).should == ["V", "o", "i", "c", "i"] - end - - describe "returned Enumerator" do - describe "size" do - it "should return nil" do - @io.send(@method).size.should == nil - end - end - end - end - - it "returns itself" do - @io.send(@method) { |c| }.should.equal?(@io) - end - - it "returns an enumerator for a closed stream" do - IOSpecs.closed_io.send(@method).should.instance_of?(Enumerator) - end - - it "raises an IOError when an enumerator created on a closed stream is accessed" do - -> { IOSpecs.closed_io.send(@method).first }.should.raise(IOError) - end - - it "raises IOError on closed stream" do - -> { IOSpecs.closed_io.send(@method) {} }.should.raise(IOError) - end -end - -describe :io_chars_empty, shared: true do - before :each do - @name = tmp("io_each_char") - @io = new_io @name, "w+:utf-8" - ScratchPad.record [] - end - - after :each do - @io.close unless @io.closed? - rm_r @name - end - - it "does not yield any characters on an empty stream" do - @io.send(@method) { |c| ScratchPad << c } - ScratchPad.recorded.should == [] - end -end diff --git a/core/io/shared/codepoints.rb b/core/io/shared/codepoints.rb deleted file mode 100644 index 21c756986..000000000 --- a/core/io/shared/codepoints.rb +++ /dev/null @@ -1,54 +0,0 @@ -# -*- encoding: utf-8 -*- -require_relative '../fixtures/classes' - -describe :io_codepoints, shared: true do - before :each do - @io = IOSpecs.io_fixture "lines.txt" - @enum = @io.send(@method) - end - - after :each do - @io.close - end - - describe "when no block is given" do - it "returns an Enumerator" do - @enum.should.instance_of?(Enumerator) - end - - describe "returned Enumerator" do - describe "size" do - it "should return nil" do - @enum.size.should == nil - end - end - end - end - - it "yields each codepoint" do - @enum.first(25).should == [ - 86, 111, 105, 99, 105, 32, 108, 97, 32, 108, 105, 103, 110, - 101, 32, 117, 110, 101, 46, 10, 81, 117, 105, 32, 232 - ] - end - - it "yields each codepoint starting from the current position" do - @io.pos = 130 - @enum.to_a.should == [101, 32, 115, 105, 120, 46, 10] - end - - it "raises an error if reading invalid sequence" do - @io.pos = 60 # inside of a multibyte sequence - -> { @enum.first }.should.raise(ArgumentError) - end - - it "does not change $_" do - $_ = "test" - @enum.to_a - $_.should == "test" - end - - it "raises an IOError when self is not readable" do - -> { IOSpecs.closed_io.send(@method).to_a }.should.raise(IOError) - end -end diff --git a/core/io/shared/each.rb b/core/io/shared/each.rb deleted file mode 100644 index ae60c3506..000000000 --- a/core/io/shared/each.rb +++ /dev/null @@ -1,251 +0,0 @@ -# -*- encoding: utf-8 -*- -require_relative '../fixtures/classes' - -describe :io_each, shared: true do - before :each do - @io = IOSpecs.io_fixture "lines.txt" - ScratchPad.record [] - end - - after :each do - @io.close if @io - end - - describe "with no separator" do - it "yields each line to the passed block" do - @io.send(@method) { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.lines - end - - it "yields each line starting from the current position" do - @io.pos = 41 - @io.send(@method) { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.lines[2..-1] - end - - it "returns self" do - @io.send(@method) { |l| l }.should.equal?(@io) - end - - it "does not change $_" do - $_ = "test" - @io.send(@method) { |s| s } - $_.should == "test" - end - - it "raises an IOError when self is not readable" do - -> { IOSpecs.closed_io.send(@method) {} }.should.raise(IOError) - end - - it "makes line count accessible via lineno" do - @io.send(@method) { ScratchPad << @io.lineno } - ScratchPad.recorded.should == [ 1,2,3,4,5,6,7,8,9 ] - end - - it "makes line count accessible via $." do - @io.send(@method) { ScratchPad << $. } - ScratchPad.recorded.should == [ 1,2,3,4,5,6,7,8,9 ] - end - - describe "when no block is given" do - it "returns an Enumerator" do - enum = @io.send(@method) - enum.should.instance_of?(Enumerator) - - enum.each { |l| ScratchPad << l } - ScratchPad.recorded.should == IOSpecs.lines - end - - describe "returned Enumerator" do - describe "size" do - it "should return nil" do - @io.send(@method).size.should == nil - end - end - end - end - end - - describe "with limit" do - describe "when limit is 0" do - it "raises an ArgumentError" do - # must pass block so Enumerator is evaluated and raises - -> { @io.send(@method, 0){} }.should.raise(ArgumentError) - end - end - - it "does not accept Integers that don't fit in a C off_t" do - -> { @io.send(@method, 2**128){} }.should.raise(RangeError) - end - end - - describe "when passed a String containing one space as a separator" do - it "uses the passed argument as the line separator" do - @io.send(@method, " ") { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.lines_space_separator - end - - it "does not change $_" do - $_ = "test" - @io.send(@method, " ") { |s| } - $_.should == "test" - end - - it "tries to convert the passed separator to a String using #to_str" do - obj = mock("to_str") - obj.stub!(:to_str).and_return(" ") - - @io.send(@method, obj) { |l| ScratchPad << l } - ScratchPad.recorded.should == IOSpecs.lines_space_separator - end - end - - describe "when passed nil as a separator" do - it "yields self's content starting from the current position when the passed separator is nil" do - @io.pos = 100 - @io.send(@method, nil) { |s| ScratchPad << s } - ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"] - end - end - - describe "when passed an empty String as a separator" do - it "yields each paragraph" do - @io.send(@method, "") { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.paragraphs - end - - it "discards leading newlines" do - @io.readline - @io.readline - @io.send(@method, "") { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.paragraphs[1..-1] - end - end - - describe "with both separator and limit" do - describe "when no block is given" do - it "returns an Enumerator" do - enum = @io.send(@method, nil, 1024) - enum.should.instance_of?(Enumerator) - - enum.each { |l| ScratchPad << l } - ScratchPad.recorded.should == [IOSpecs.lines.join] - end - - describe "returned Enumerator" do - describe "size" do - it "should return nil" do - @io.send(@method, nil, 1024).size.should == nil - end - end - end - end - - describe "when a block is given" do - it "accepts an empty block" do - @io.send(@method, nil, 1024) {}.should.equal?(@io) - end - - describe "when passed nil as a separator" do - it "yields self's content starting from the current position when the passed separator is nil" do - @io.pos = 100 - @io.send(@method, nil, 1024) { |s| ScratchPad << s } - ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"] - end - end - - describe "when passed an empty String as a separator" do - it "yields each paragraph" do - @io.send(@method, "", 1024) { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.paragraphs - end - - it "discards leading newlines" do - @io.readline - @io.readline - @io.send(@method, "", 1024) { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.paragraphs[1..-1] - end - end - end - end - - describe "when passed chomp" do - it "yields each line without trailing newline characters to the passed block" do - @io.send(@method, chomp: true) { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.lines_without_newline_characters - end - - it "raises exception when options passed as Hash" do - -> { - @io.send(@method, { chomp: true }) { |s| } - }.should.raise(TypeError) - - -> { - @io.send(@method, "\n", 1, { chomp: true }) { |s| } - }.should.raise(ArgumentError, "wrong number of arguments (given 3, expected 0..2)") - end - end - - describe "when passed chomp and a separator" do - it "yields each line without separator to the passed block" do - @io.send(@method, " ", chomp: true) { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.lines_space_separator_without_trailing_spaces - end - end - - describe "when passed chomp and empty line as a separator" do - it "yields each paragraph without trailing new line characters" do - @io.send(@method, "", 1024, chomp: true) { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.paragraphs_without_trailing_new_line_characters - end - end - - describe "when passed chomp and nil as a separator" do - it "yields self's content" do - @io.pos = 100 - @io.send(@method, nil, chomp: true) { |s| ScratchPad << s } - ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"] - end - end - - describe "when passed chomp, nil as a separator, and a limit" do - it "yields each line of limit size without truncating trailing new line character" do - # 43 - is a size of the 1st paragraph in the file - @io.send(@method, nil, 43, chomp: true) { |s| ScratchPad << s } - - ScratchPad.recorded.should == [ - "Voici la ligne une.\nQui è la linea due.\n\n\n", - "Aquí está la línea tres.\n" + "Hier ist Zeile ", - "vier.\n\nEstá aqui a linha cinco.\nHere is li", - "ne six.\n" - ] - end - end - - describe "when passed too many arguments" do - it "raises ArgumentError" do - -> { - @io.send(@method, "", 1, "excess argument", chomp: true) {} - }.should.raise(ArgumentError) - end - end -end - -describe :io_each_default_separator, shared: true do - before :each do - @io = IOSpecs.io_fixture "lines.txt" - ScratchPad.record [] - suppress_warning {@sep, $/ = $/, " "} - end - - after :each do - @io.close if @io - suppress_warning {$/ = @sep} - end - - it "uses $/ as the default line separator" do - @io.send(@method) { |s| ScratchPad << s } - ScratchPad.recorded.should == IOSpecs.lines_space_separator - end -end diff --git a/core/io/shared/pos.rb b/core/io/shared/pos.rb index f4d040586..450058e15 100644 --- a/core/io/shared/pos.rb +++ b/core/io/shared/pos.rb @@ -1,37 +1,3 @@ -describe :io_pos, shared: true do - before :each do - @fname = tmp('test.txt') - File.open(@fname, 'w') { |f| f.write "123" } - end - - after :each do - rm_r @fname - end - - it "gets the offset" do - File.open @fname do |f| - f.send(@method).should == 0 - f.read 1 - f.send(@method).should == 1 - f.read 2 - f.send(@method).should == 3 - end - end - - it "raises IOError on closed stream" do - -> { IOSpecs.closed_io.send(@method) }.should.raise(IOError) - end - - it "resets #eof?" do - open @fname do |io| - io.read 1 - io.read 1 - io.send(@method) - io.should_not.eof? - end - end -end - describe :io_set_pos, shared: true do before :each do @fname = tmp('test.txt') diff --git a/core/io/shared/tty.rb b/core/io/shared/tty.rb deleted file mode 100644 index 1dc0e9573..000000000 --- a/core/io/shared/tty.rb +++ /dev/null @@ -1,24 +0,0 @@ -require_relative '../fixtures/classes' - -describe :io_tty, shared: true do - platform_is_not :windows do - it "returns true if this stream is a terminal device (TTY)" do - begin - # check to enabled tty - File.open('/dev/tty') {} - rescue Errno::ENXIO - skip "workaround for not configured environment like OS X" - else - File.open('/dev/tty') { |f| f.send(@method) }.should == true - end - end - end - - it "returns false if this stream is not a terminal device (TTY)" do - File.open(__FILE__) { |f| f.send(@method) }.should == false - end - - it "raises IOError on closed stream" do - -> { IOSpecs.closed_io.send @method }.should.raise(IOError) - end -end diff --git a/core/io/tell_spec.rb b/core/io/tell_spec.rb index 0d6c6b02d..a6b51adc1 100644 --- a/core/io/tell_spec.rb +++ b/core/io/tell_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/pos' describe "IO#tell" do - it_behaves_like :io_pos, :tell + it "is an alias of IO#pos" do + IO.instance_method(:tell).should == IO.instance_method(:pos) + end end diff --git a/core/io/tty_spec.rb b/core/io/tty_spec.rb index 3b76c6d2b..e1848a176 100644 --- a/core/io/tty_spec.rb +++ b/core/io/tty_spec.rb @@ -1,6 +1,25 @@ require_relative '../../spec_helper' -require_relative 'shared/tty' +require_relative 'fixtures/classes' describe "IO#tty?" do - it_behaves_like :io_tty, :tty? + platform_is_not :windows do + it "returns true if this stream is a terminal device (TTY)" do + begin + # check to enabled tty + File.open('/dev/tty') {} + rescue Errno::ENXIO + skip "workaround for not configured environment like OS X" + else + File.open('/dev/tty') { |f| f.tty? }.should == true + end + end + end + + it "returns false if this stream is not a terminal device (TTY)" do + File.open(__FILE__) { |f| f.tty? }.should == false + end + + it "raises IOError on closed stream" do + -> { IOSpecs.closed_io.tty? }.should.raise(IOError) + end end diff --git a/core/kernel/clone_spec.rb b/core/kernel/clone_spec.rb index 4ddb23d6e..80e7a78ab 100644 --- a/core/kernel/clone_spec.rb +++ b/core/kernel/clone_spec.rb @@ -10,12 +10,6 @@ @obj = KernelSpecs::Duplicate.new 1, :a end - it "calls #initialize_copy on the new instance" do - clone = @obj.clone - ScratchPad.recorded.should_not == @obj.object_id - ScratchPad.recorded.should == clone.object_id - end - it "uses the internal allocator and does not call #allocate" do klass = Class.new instance = klass.new diff --git a/core/kernel/dup_spec.rb b/core/kernel/dup_spec.rb index 99de5e373..fa4ca7147 100644 --- a/core/kernel/dup_spec.rb +++ b/core/kernel/dup_spec.rb @@ -10,12 +10,6 @@ @obj = KernelSpecs::Duplicate.new 1, :a end - it "calls #initialize_copy on the new instance" do - dup = @obj.dup - ScratchPad.recorded.should_not == @obj.object_id - ScratchPad.recorded.should == dup.object_id - end - it "uses the internal allocator and does not call #allocate" do klass = Class.new instance = klass.new diff --git a/core/kernel/enum_for_spec.rb b/core/kernel/enum_for_spec.rb index 0092e2046..ef0fb64e6 100644 --- a/core/kernel/enum_for_spec.rb +++ b/core/kernel/enum_for_spec.rb @@ -1,5 +1,7 @@ require_relative '../../spec_helper' describe "Kernel#enum_for" do - it "needs to be reviewed for spec completeness" + it "is an alias of Kernel#to_enum" do + Kernel.instance_method(:enum_for).should == Kernel.instance_method(:to_enum) + end end diff --git a/core/kernel/is_a_spec.rb b/core/kernel/is_a_spec.rb index bd8c96529..ff36a769c 100644 --- a/core/kernel/is_a_spec.rb +++ b/core/kernel/is_a_spec.rb @@ -1,6 +1,56 @@ require_relative '../../spec_helper' -require_relative 'shared/kind_of' +require_relative 'fixtures/classes' describe "Kernel#is_a?" do - it_behaves_like :kernel_kind_of, :is_a? + before :each do + @o = KernelSpecs::KindaClass.new + end + + it "returns true if given class is the object's class" do + @o.is_a?(KernelSpecs::KindaClass).should == true + end + + it "returns true if given class is an ancestor of the object's class" do + @o.is_a?(KernelSpecs::AncestorClass).should == true + @o.is_a?(String).should == true + @o.is_a?(Object).should == true + end + + it "returns false if the given class is not object's class nor an ancestor" do + @o.is_a?(Array).should == false + end + + it "returns true if given a Module that is included in object's class" do + @o.is_a?(KernelSpecs::MyModule).should == true + end + + it "returns true if given a Module that is included one of object's ancestors only" do + @o.is_a?(KernelSpecs::AncestorModule).should == true + end + + it "returns true if given a Module that object has been extended with" do + @o.is_a?(KernelSpecs::MyExtensionModule).should == true + end + + it "returns true if given a Module that object has been prepended with" do + @o.is_a?(KernelSpecs::MyPrependedModule).should == true + end + + it "returns false if given a Module not included nor prepended in object's class nor ancestors" do + @o.is_a?(KernelSpecs::SomeOtherModule).should == false + end + + it "raises a TypeError if given an object that is not a Class nor a Module" do + -> { @o.is_a?(1) }.should.raise(TypeError) + -> { @o.is_a?('KindaClass') }.should.raise(TypeError) + -> { @o.is_a?(:KindaClass) }.should.raise(TypeError) + -> { @o.is_a?(Object.new) }.should.raise(TypeError) + end + + it "does not take into account `class` method overriding" do + def @o.class; Integer; end + + @o.is_a?(Integer).should == false + @o.is_a?(KernelSpecs::KindaClass).should == true + end end diff --git a/core/kernel/kind_of_spec.rb b/core/kernel/kind_of_spec.rb index c988edccb..7fcc72543 100644 --- a/core/kernel/kind_of_spec.rb +++ b/core/kernel/kind_of_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/kind_of' describe "Kernel#kind_of?" do - it_behaves_like :kernel_kind_of, :kind_of? + it "is an alias of Kernel#is_a?" do + Kernel.instance_method(:kind_of?).should == Kernel.instance_method(:is_a?) + end end diff --git a/core/kernel/shared/kind_of.rb b/core/kernel/shared/kind_of.rb deleted file mode 100644 index a5e0eab08..000000000 --- a/core/kernel/shared/kind_of.rb +++ /dev/null @@ -1,55 +0,0 @@ -require_relative '../fixtures/classes' - -describe :kernel_kind_of, shared: true do - before :each do - @o = KernelSpecs::KindaClass.new - end - - it "returns true if given class is the object's class" do - @o.send(@method, KernelSpecs::KindaClass).should == true - end - - it "returns true if given class is an ancestor of the object's class" do - @o.send(@method, KernelSpecs::AncestorClass).should == true - @o.send(@method, String).should == true - @o.send(@method, Object).should == true - end - - it "returns false if the given class is not object's class nor an ancestor" do - @o.send(@method, Array).should == false - end - - it "returns true if given a Module that is included in object's class" do - @o.send(@method, KernelSpecs::MyModule).should == true - end - - it "returns true if given a Module that is included one of object's ancestors only" do - @o.send(@method, KernelSpecs::AncestorModule).should == true - end - - it "returns true if given a Module that object has been extended with" do - @o.send(@method, KernelSpecs::MyExtensionModule).should == true - end - - it "returns true if given a Module that object has been prepended with" do - @o.send(@method, KernelSpecs::MyPrependedModule).should == true - end - - it "returns false if given a Module not included nor prepended in object's class nor ancestors" do - @o.send(@method, KernelSpecs::SomeOtherModule).should == false - end - - it "raises a TypeError if given an object that is not a Class nor a Module" do - -> { @o.send(@method, 1) }.should.raise(TypeError) - -> { @o.send(@method, 'KindaClass') }.should.raise(TypeError) - -> { @o.send(@method, :KindaClass) }.should.raise(TypeError) - -> { @o.send(@method, Object.new) }.should.raise(TypeError) - end - - it "does not take into account `class` method overriding" do - def @o.class; Integer; end - - @o.send(@method, Integer).should == false - @o.send(@method, KernelSpecs::KindaClass).should == true - end -end diff --git a/core/kernel/then_spec.rb b/core/kernel/then_spec.rb index 8109a2960..bda5a6966 100644 --- a/core/kernel/then_spec.rb +++ b/core/kernel/then_spec.rb @@ -2,5 +2,13 @@ require_relative 'shared/then' describe "Kernel#then" do - it_behaves_like :kernel_then, :then + ruby_version_is ""..."3.4" do + it_behaves_like :kernel_then, :then + end + + ruby_version_is "3.4" do + it "is an alias of Kernel#yield_self" do + Kernel.instance_method(:then).should == Kernel.instance_method(:yield_self) + end + end end diff --git a/core/kernel/to_enum_spec.rb b/core/kernel/to_enum_spec.rb index 9d9945450..1cee26b69 100644 --- a/core/kernel/to_enum_spec.rb +++ b/core/kernel/to_enum_spec.rb @@ -1,5 +1,59 @@ require_relative '../../spec_helper' describe "Kernel#to_enum" do - it "needs to be reviewed for spec completeness" + it "is defined in Kernel" do + Kernel.method_defined?(:to_enum).should == true + end + + it "returns a new enumerator" do + "abc".to_enum.should.instance_of?(Enumerator) + end + + it "defaults the first argument to :each" do + enum = [1,2].to_enum + enum.map { |v| v }.should == [1,2].each { |v| v } + end + + it "sets regexp matches in the caller" do + "wawa".to_enum(:scan, /./).map {|o| $& }.should == ["w", "a", "w", "a"] + a = [] + "wawa".to_enum(:scan, /./).each {|o| a << $& } + a.should == ["w", "a", "w", "a"] + 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.to_enum + 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.to_enum { 100 }.size.should == 100 + end + + it "defers the evaluation of the passed block until #size is called" do + ScratchPad.record [] + + enum = Object.new.to_enum do + ScratchPad << :called + 100 + end + + ScratchPad.recorded.should.empty? + + enum.size + ScratchPad.recorded.should == [:called] + end end diff --git a/core/marshal/load_spec.rb b/core/marshal/load_spec.rb index a5bdfbf52..f5a05f8e5 100644 --- a/core/marshal/load_spec.rb +++ b/core/marshal/load_spec.rb @@ -1,6 +1,1291 @@ +# encoding: binary require_relative '../../spec_helper' -require_relative 'shared/load' +require_relative 'fixtures/marshal_data' describe "Marshal.load" do - it_behaves_like :marshal_load, :load + before :all do + @num_self_class = 1 + end + + it "raises an ArgumentError when the dumped data is truncated" do + obj = {first: 1, second: 2, third: 3} + -> { Marshal.load(Marshal.dump(obj)[0, 5]) }.should.raise(ArgumentError, "marshal data too short") + end + + it "raises an ArgumentError when the argument is empty String" do + -> { Marshal.load("") }.should.raise(ArgumentError, "marshal data too short") + end + + it "raises an ArgumentError when the dumped class is missing" do + Object.send(:const_set, :KaBoom, Class.new) + kaboom = Marshal.dump(KaBoom.new) + Object.send(:remove_const, :KaBoom) + + -> { Marshal.load(kaboom) }.should.raise(ArgumentError) + end + + describe "when called with freeze: true" do + it "returns frozen strings" do + string = Marshal.load(Marshal.dump("foo"), freeze: true) + string.should == "foo" + string.should.frozen? + + utf8_string = "foo".encode(Encoding::UTF_8) + string = Marshal.load(Marshal.dump(utf8_string), freeze: true) + string.should == utf8_string + string.should.frozen? + end + + it "returns frozen arrays" do + array = Marshal.load(Marshal.dump([1, 2, 3]), freeze: true) + array.should == [1, 2, 3] + array.should.frozen? + end + + it "returns frozen hashes" do + hash = Marshal.load(Marshal.dump({foo: 42}), freeze: true) + hash.should == {foo: 42} + hash.should.frozen? + end + + it "returns frozen regexps" do + regexp = Marshal.load(Marshal.dump(/foo/), freeze: true) + regexp.should == /foo/ + regexp.should.frozen? + end + + it "returns frozen structs" do + struct = Marshal.load(Marshal.dump(MarshalSpec::StructToDump.new(1, 2)), freeze: true) + struct.should == MarshalSpec::StructToDump.new(1, 2) + struct.should.frozen? + end + + it "returns frozen objects" do + source_object = Object.new + + object = Marshal.load(Marshal.dump(source_object), freeze: true) + object.should.frozen? + end + + describe "deep freezing" do + it "returns hashes with frozen keys and values" do + key = Object.new + value = Object.new + source_object = {key => value} + + hash = Marshal.load(Marshal.dump(source_object), freeze: true) + hash.size.should == 1 + hash.keys[0].should.frozen? + hash.values[0].should.frozen? + end + + it "returns arrays with frozen elements" do + object = Object.new + source_object = [object] + + array = Marshal.load(Marshal.dump(source_object), freeze: true) + array.size.should == 1 + array[0].should.frozen? + end + + it "returns structs with frozen members" do + object1 = Object.new + object2 = Object.new + source_object = MarshalSpec::StructToDump.new(object1, object2) + + struct = Marshal.load(Marshal.dump(source_object), freeze: true) + struct.a.should.frozen? + struct.b.should.frozen? + end + + it "returns objects with frozen instance variables" do + source_object = Object.new + instance_variable = Object.new + source_object.instance_variable_set(:@a, instance_variable) + + object = Marshal.load(Marshal.dump(source_object), freeze: true) + object.instance_variable_get(:@a).should != nil + object.instance_variable_get(:@a).should.frozen? + end + + it "deduplicates frozen strings" do + source_object = ["foo" + "bar", "foobar"] + object = Marshal.load(Marshal.dump(source_object), freeze: true) + + object[0].should.equal?(object[1]) + end + end + + it "does not freeze modules" do + object = Marshal.load(Marshal.dump(Kernel), freeze: true) + object.should_not.frozen? + Kernel.should_not.frozen? + end + + it "does not freeze classes" do + object = Marshal.load(Marshal.dump(Object), freeze: true) + object.should_not.frozen? + Object.should_not.frozen? + end + + it "does freeze extended objects" do + object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", freeze: true) + object.should.frozen? + end + + it "does freeze extended objects with instance variables" do + object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x06:\n@ivarT", freeze: true) + object.should.frozen? + end + + it "returns frozen object having #_dump method" do + object = Marshal.load(Marshal.dump(UserDefined.new), freeze: true) + object.should.frozen? + end + + it "returns frozen object responding to #marshal_dump and #marshal_load" do + object = Marshal.load(Marshal.dump(UserMarshal.new), freeze: true) + object.should.frozen? + end + + it "returns frozen object extended by a module" do + object = Object.new + object.extend(MarshalSpec::ModuleToExtendBy) + + object = Marshal.load(Marshal.dump(object), freeze: true) + object.should.frozen? + end + + it "does not call freeze method" do + object = MarshalSpec::ObjectWithFreezeRaisingException.new + object = Marshal.load(Marshal.dump(object), freeze: true) + object.should.frozen? + end + + it "returns frozen object even if object does not respond to freeze method" do + object = MarshalSpec::ObjectWithoutFreeze.new + object = Marshal.load(Marshal.dump(object), freeze: true) + object.should.frozen? + end + + it "returns a frozen object when is an instance of String/Array/Regexp/Hash subclass and has instance variables" do + source_object = UserString.new + source_object.instance_variable_set(:@foo, "bar") + + object = Marshal.load(Marshal.dump(source_object), freeze: true) + object.should.frozen? + end + + describe "when called with a proc" do + it "call the proc with frozen objects" do + arr = [] + s = +'hi' + s.instance_variable_set(:@foo, 5) + st = Struct.new("Brittle", :a).new + st.instance_variable_set(:@clue, 'none') + st.a = 0.0 + h = Hash.new('def') + h['nine'] = 9 + a = [:a, :b, :c] + a.instance_variable_set(:@two, 2) + obj = [s, 10, s, s, st, a] + obj.instance_variable_set(:@zoo, 'ant') + proc = Proc.new { |o| arr << o; o} + + Marshal.load( + "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", + proc, + freeze: true, + ) + + arr.should == [ + false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st, + :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]], + ] + + arr.each do |v| + v.should.frozen? + end + + Struct.send(:remove_const, :Brittle) + end + + it "does not freeze the object returned by the proc" do + string = Marshal.load(Marshal.dump("foo"), proc { |o| o.upcase }, freeze: true) + string.should == "FOO" + string.should_not.frozen? + end + end + end + + describe "when called with a proc" do + it "call the proc with fully initialized strings" do + utf8_string = "foo".encode(Encoding::UTF_8) + Marshal.load(Marshal.dump(utf8_string), proc { |arg| + if arg.is_a?(String) + arg.should == utf8_string + arg.encoding.should == Encoding::UTF_8 + end + arg + }) + end + + it "no longer mutate the object after it was passed to the proc" do + string = Marshal.load(Marshal.dump("foo"), :freeze.to_proc) + string.should.frozen? + end + + it "call the proc with extended objects" do + objs = [] + obj = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", Proc.new { |o| objs << o; o }) + objs.should == [obj] + end + + it "returns the value of the proc" do + Marshal.load(Marshal.dump([1,2]), proc { [3,4] }).should == [3,4] + end + + it "calls the proc for recursively visited data" do + a = [1] + a << a + ret = [] + Marshal.load(Marshal.dump(a), proc { |arg| ret << arg.inspect; arg }) + ret[0].should == 1.inspect + ret[1].should == a.inspect + ret.size.should == 2 + end + + it "loads an Array with proc" do + arr = [] + s = +'hi' + s.instance_variable_set(:@foo, 5) + st = Struct.new("Brittle", :a).new + st.instance_variable_set(:@clue, 'none') + st.a = 0.0 + h = Hash.new('def') + h['nine'] = 9 + a = [:a, :b, :c] + a.instance_variable_set(:@two, 2) + obj = [s, 10, s, s, st, a] + obj.instance_variable_set(:@zoo, 'ant') + proc = Proc.new { |o| arr << o.dup; o} + + Marshal.load("\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc) + + arr.should == [ + false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st, + :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]], + ] + Struct.send(:remove_const, :Brittle) + end + end + + describe "when called with nil for the proc argument" do + it "behaves as if no proc argument was passed" do + a = [1] + a << a + b = Marshal.load(Marshal.dump(a), nil) + b.should == a + end + end + + describe "when called on objects with custom _dump methods" do + it "does not set instance variables of an object with user-defined _dump/_load" do + # this string represents: <#UserPreviouslyDefinedWithInitializedIvar @field2=7 @field1=6> + dump_str = "\004\bu:-UserPreviouslyDefinedWithInitializedIvar\a:\f@field2i\f:\f@field1i\v" + + UserPreviouslyDefinedWithInitializedIvar.should_receive(:_load).and_return(UserPreviouslyDefinedWithInitializedIvar.new) + marshaled_obj = Marshal.load(dump_str) + + marshaled_obj.should.instance_of?(UserPreviouslyDefinedWithInitializedIvar) + marshaled_obj.field1.should == nil + marshaled_obj.field2.should == nil + end + + it "loads the String in non US-ASCII and non UTF-8 encoding" do + source_object = UserDefinedString.new("a".encode("windows-1251")) + object = Marshal.load(Marshal.dump(source_object)) + object.string.should == "a".encode("windows-1251") + end + + it "loads the String in multibyte encoding" do + source_object = UserDefinedString.new("a".encode("utf-32le")) + object = Marshal.load(Marshal.dump(source_object)) + object.string.should == "a".encode("utf-32le") + end + + describe "that returns an immediate value" do + it "loads an array containing an instance of the object, followed by multiple instances of another object" do + str = "string" + + # this string represents: [<#UserDefinedImmediate A>, <#String "string">, <#String "string">] + marshaled_obj = Marshal.load("\004\b[\bu:\031UserDefinedImmediate\000\"\vstring@\a") + + marshaled_obj.should == [nil, str, str] + end + + it "loads any structure with multiple references to the same object, followed by multiple instances of another object" do + str = "string" + + # this string represents: {a: <#UserDefinedImmediate A>, b: <#UserDefinedImmediate A>, c: <#String "string">, d: <#String "string">} + hash_dump = "\x04\b{\t:\x06aIu:\x19UserDefinedImmediate\x00\x06:\x06ET:\x06b@\x06:\x06cI\"\vstring\x06;\aT:\x06d@\a" + + marshaled_obj = Marshal.load(hash_dump) + marshaled_obj.should == {a: nil, b: nil, c: str, d: str} + + # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate A>, <#String "string">, <#String "string">] + array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ET@\x06I\"\vstring\x06;\x06T@\a" + + marshaled_obj = Marshal.load(array_dump) + marshaled_obj.should == [nil, nil, str, str] + end + + it "loads an array containing references to multiple instances of the object, followed by multiple instances of another object" do + str = "string" + + # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate B>, <#String "string">, <#String "string">] + array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ETIu;\x00\x00\x06;\x06TI\"\vstring\x06;\x06T@\b" + + marshaled_obj = Marshal.load(array_dump) + marshaled_obj.should == [nil, nil, str, str] + end + end + end + + it "loads an array containing objects having _dump method, and with proc" do + arr = [] + myproc = Proc.new { |o| arr << o.dup; o } + o1 = UserDefined.new; + o2 = UserDefinedWithIvar.new + obj = [o1, o2, o1, o2] + + Marshal.load("\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc) + + arr[0].should == o1 + arr[1].should == o2 + arr[2].should == obj + arr.size.should == 3 + end + + it "loads an array containing objects having marshal_dump method, and with proc" do + arr = [] + proc = Proc.new { |o| arr << o.dup; o } + o1 = UserMarshal.new + o2 = UserMarshalWithIvar.new + + Marshal.load("\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc) + + arr[0].should == 'stuff' + arr[1].should == o1 + arr[2].should == 'my data' + arr[3].should == ['my data'] + arr[4].should == o2 + arr[5].should == [o1, o2, o1, o2] + + arr.size.should == 6 + end + + it "assigns classes to nested subclasses of Array correctly" do + arr = ArraySub.new(ArraySub.new) + arr_dump = Marshal.dump(arr) + Marshal.load(arr_dump).class.should == ArraySub + end + + it "loads subclasses of Array with overridden << and push correctly" do + arr = ArraySubPush.new + arr[0] = '1' + arr_dump = Marshal.dump(arr) + Marshal.load(arr_dump).should == arr + end + + it "raises a TypeError with bad Marshal version" do + marshal_data = +'\xff\xff' + marshal_data[0] = (Marshal::MAJOR_VERSION).chr + marshal_data[1] = (Marshal::MINOR_VERSION + 1).chr + + -> { Marshal.load(marshal_data) }.should.raise(TypeError) + + marshal_data = +'\xff\xff' + marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr + marshal_data[1] = (Marshal::MINOR_VERSION).chr + + -> { Marshal.load(marshal_data) }.should.raise(TypeError) + end + + it "raises EOFError on loading an empty file" do + temp_file = tmp("marshal.rubyspec.tmp.#{Process.pid}") + file = File.new(temp_file, "w+") + begin + -> { Marshal.load(file) }.should.raise(EOFError) + ensure + file.close + rm_r temp_file + end + end + + # Note: Ruby 1.9 should be compatible with older marshal format + MarshalSpec::DATA.each do |description, (object, marshal, attributes)| + it "loads a #{description}" do + Marshal.load(marshal).should == object + end + end + + MarshalSpec::DATA_19.each do |description, (object, marshal, attributes)| + it "loads a #{description}" do + Marshal.load(marshal).should == object + end + end + + describe "for an Array" do + it "loads an array containing the same objects" do + s = 'oh' + b = 'hi' + r = // + d = [b, :no, s, :go] + c = String + f = 1.0 + + o1 = UserMarshalWithIvar.new; o2 = UserMarshal.new + + obj = [:so, 'hello', 100, :so, :so, d, :so, o2, :so, :no, o2, + :go, c, nil, Struct::Pyramid.new, f, :go, :no, s, b, r, + :so, 'huh', o1, true, b, b, 99, r, b, s, :so, f, c, :no, o1, d] + + Marshal.load("\004\b[*:\aso\"\nhelloii;\000;\000[\t\"\ahi:\ano\"\aoh:\ago;\000U:\020UserMarshal\"\nstuff;\000;\006@\n;\ac\vString0S:\024Struct::Pyramid\000f\0061;\a;\006@\t@\b/\000\000;\000\"\bhuhU:\030UserMarshalWithIvar[\006\"\fmy dataT@\b@\bih@\017@\b@\t;\000@\016@\f;\006@\021@\a").should == + obj + end + + it "loads an array having ivar" do + s = +'well' + s.instance_variable_set(:@foo, 10) + obj = ['5', s, 'hi'].extend(Meths, MethsMore) + obj.instance_variable_set(:@mix, s) + new_obj = Marshal.load("\004\bI[\b\"\0065I\"\twell\006:\t@fooi\017\"\ahi\006:\t@mix@\a") + new_obj.should == obj + new_obj.instance_variable_get(:@mix).should.equal? new_obj[1] + new_obj[1].instance_variable_get(:@foo).should == 10 + end + + it "loads an extended Array object containing a user-marshaled object" do + obj = [UserMarshal.new, UserMarshal.new].extend(Meths) + dump = "\x04\be:\nMeths[\ao:\x10UserMarshal\x06:\n@dataI\"\nstuff\x06:\x06ETo;\x06\x06;\aI\"\nstuff\x06;\bT" + new_obj = Marshal.load(dump) + + new_obj.should == obj + obj_ancestors = class << obj; ancestors[1..-1]; end + new_obj_ancestors = class << new_obj; ancestors[1..-1]; end + obj_ancestors.should == new_obj_ancestors + end + end + + describe "for a Hash" do + it "loads an extended_user_hash with a parameter to initialize" do + obj = UserHashInitParams.new(:abc).extend(Meths) + + new_obj = Marshal.load("\004\bIe:\nMethsC:\027UserHashInitParams{\000\006:\a@a:\babc") + + new_obj.should == obj + new_obj_metaclass_ancestors = class << new_obj; ancestors; end + new_obj_metaclass_ancestors[@num_self_class].should == Meths + new_obj_metaclass_ancestors[@num_self_class+1].should == UserHashInitParams + end + + it "loads an extended hash object containing a user-marshaled object" do + obj = {a: UserMarshal.new}.extend(Meths) + + new_obj = Marshal.load("\004\be:\nMeths{\006:\006aU:\020UserMarshal\"\nstuff") + + new_obj.should == obj + new_obj_metaclass_ancestors = class << new_obj; ancestors; end + new_obj_metaclass_ancestors[@num_self_class].should == Meths + new_obj_metaclass_ancestors[@num_self_class+1].should == Hash + end + + it "preserves hash ivars when hash contains a string having ivar" do + s = +'string' + s.instance_variable_set :@string_ivar, 'string ivar' + h = { key: s } + h.instance_variable_set :@hash_ivar, 'hash ivar' + + unmarshalled = Marshal.load(Marshal.dump(h)) + unmarshalled.instance_variable_get(:@hash_ivar).should == 'hash ivar' + unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar' + end + + it "preserves compare_by_identity behaviour" do + h = { a: 1 } + h.compare_by_identity + unmarshalled = Marshal.load(Marshal.dump(h)) + unmarshalled.should.compare_by_identity? + + h = { a: 1 } + unmarshalled = Marshal.load(Marshal.dump(h)) + unmarshalled.should_not.compare_by_identity? + end + + it "preserves compare_by_identity behaviour for a Hash subclass" do + h = UserHash.new({ a: 1 }) + h.compare_by_identity + unmarshalled = Marshal.load(Marshal.dump(h)) + unmarshalled.should.compare_by_identity? + + h = UserHash.new({ a: 1 }) + unmarshalled = Marshal.load(Marshal.dump(h)) + unmarshalled.should_not.compare_by_identity? + end + + it "allocates an instance of the proper class when Hash subclass with compare_by_identity behaviour" do + h = UserHash.new({ a: 1 }) + h.compare_by_identity + + unmarshalled = Marshal.load(Marshal.dump(h)) + unmarshalled.should.kind_of?(UserHash) + end + end + + describe "for a Symbol" do + it "loads a Symbol" do + sym = Marshal.load("\004\b:\vsymbol") + sym.should == :symbol + sym.encoding.should == Encoding::US_ASCII + end + + it "loads a big Symbol" do + sym = ('big' * 100).to_sym + Marshal.load("\004\b:\002,\001#{'big' * 100}").should == sym + end + + it "loads an encoded Symbol" do + s = "\u2192" + + sym = Marshal.load("\x04\bI:\b\xE2\x86\x92\x06:\x06ET") + sym.should == s.encode("utf-8").to_sym + sym.encoding.should == Encoding::UTF_8 + + sym = Marshal.load("\x04\bI:\t\xFE\xFF!\x92\x06:\rencoding\"\vUTF-16") + sym.should == s.encode("utf-16").to_sym + sym.encoding.should == Encoding::UTF_16 + + sym = Marshal.load("\x04\bI:\a\x92!\x06:\rencoding\"\rUTF-16LE") + sym.should == s.encode("utf-16le").to_sym + sym.encoding.should == Encoding::UTF_16LE + + sym = Marshal.load("\x04\bI:\a!\x92\x06:\rencoding\"\rUTF-16BE") + sym.should == s.encode("utf-16be").to_sym + sym.encoding.should == Encoding::UTF_16BE + + sym = Marshal.load("\x04\bI:\a\xA2\xAA\x06:\rencoding\"\vEUC-JP") + sym.should == s.encode("euc-jp").to_sym + sym.encoding.should == Encoding::EUC_JP + + sym = Marshal.load("\x04\bI:\a\x81\xA8\x06:\rencoding\"\x10Windows-31J") + sym.should == s.encode("sjis").to_sym + sym.encoding.should == Encoding::SJIS + end + + it "loads a binary encoded Symbol" do + s = "\u2192".dup.force_encoding("binary").to_sym + sym = Marshal.load("\x04\b:\b\xE2\x86\x92") + sym.should == s + sym.encoding.should == Encoding::BINARY + end + + it "loads multiple Symbols sharing the same encoding" do + # Note that the encoding is a link for the second Symbol + symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET" + symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T" + dump = "\x04\b[\a#{symbol1}#{symbol2}" + value = Marshal.load(dump) + value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8] + expected = [ + "€a".dup.force_encoding(Encoding::UTF_8).to_sym, + "€b".dup.force_encoding(Encoding::UTF_8).to_sym + ] + value.should == expected + + value = Marshal.load("\x04\b[\b#{symbol1}#{symbol2};\x00") + value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8] + value.should == [*expected, expected[0]] + end + + it "raises ArgumentError when end of byte sequence reached before symbol characters end" do + Marshal.dump(:hello).should == "\x04\b:\nhello" + + -> { + Marshal.load("\x04\b:\nhel") + }.should.raise(ArgumentError, "marshal data too short") + end + end + + describe "for a String" do + it "loads a string having ivar with ref to self" do + obj = +'hi' + obj.instance_variable_set(:@self, obj) + Marshal.load("\004\bI\"\ahi\006:\n@self@\000").should == obj + end + + it "loads a string through StringIO stream" do + require 'stringio' + obj = "This is a string which should be unmarshalled through StringIO stream!" + Marshal.load(StringIO.new(Marshal.dump(obj))).should == obj + end + + it "sets binmode if it is loading through StringIO stream" do + io = StringIO.new("\004\b:\vsymbol") + def io.binmode; raise "binmode"; end + -> { Marshal.load(io) }.should.raise(RuntimeError, "binmode") + end + + it "loads a string with an ivar" do + str = Marshal.load("\x04\bI\"\x00\x06:\t@fooI\"\bbar\x06:\x06EF") + str.instance_variable_get("@foo").should == "bar" + end + + it "loads a String subclass with custom constructor" do + str = Marshal.load("\x04\bC: UserCustomConstructorString\"\x00") + str.should.instance_of?(UserCustomConstructorString) + end + + it "loads a US-ASCII String" do + str = "abc".dup.force_encoding("us-ascii") + data = "\x04\bI\"\babc\x06:\x06EF" + result = Marshal.load(data) + result.should == str + result.encoding.should.equal?(Encoding::US_ASCII) + end + + it "loads a UTF-8 String" do + str = "\x6d\xc3\xb6\x68\x72\x65".dup.force_encoding("utf-8") + data = "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET" + result = Marshal.load(data) + result.should == str + result.encoding.should.equal?(Encoding::UTF_8) + end + + it "loads a String in another encoding" do + str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".dup.force_encoding("utf-16le") + data = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE" + result = Marshal.load(data) + result.should == str + result.encoding.should.equal?(Encoding::UTF_16LE) + end + + it "loads a String as BINARY if no encoding is specified at the end" do + str = "\xC3\xB8".dup.force_encoding("BINARY") + data = "\x04\b\"\a\xC3\xB8".dup.force_encoding("UTF-8") + result = Marshal.load(data) + result.encoding.should == Encoding::BINARY + result.should == str + end + + it "raises ArgumentError when end of byte sequence reached before string characters end" do + Marshal.dump("hello").should == "\x04\b\"\nhello" + + -> { + Marshal.load("\x04\b\"\nhel") + }.should.raise(ArgumentError, "marshal data too short") + end + end + + describe "for a Struct" do + it "loads a extended_struct having fields with same objects" do + s = 'hi' + obj = Struct.new("Extended", :a, :b).new.extend(Meths) + dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0" + Marshal.load(dump).should == obj + + obj.a = [:a, s] + obj.b = [:Meths, s] + dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a" + Marshal.load(dump).should == obj + Struct.send(:remove_const, :Extended) + end + + it "loads a struct having ivar" do + obj = Struct.new("Thick").new + obj.instance_variable_set(:@foo, 5) + reloaded = Marshal.load("\004\bIS:\022Struct::Thick\000\006:\t@fooi\n") + reloaded.should == obj + reloaded.instance_variable_get(:@foo).should == 5 + Struct.send(:remove_const, :Thick) + end + + it "loads a struct having fields" do + obj = Struct.new("Ure1", :a, :b).new + Marshal.load("\004\bS:\021Struct::Ure1\a:\006a0:\006b0").should == obj + Struct.send(:remove_const, :Ure1) + end + + it "does not call initialize on the unmarshaled struct" do + threadlocal_key = MarshalSpec::StructWithUserInitialize::THREADLOCAL_KEY + + s = MarshalSpec::StructWithUserInitialize.new('foo') + Thread.current[threadlocal_key].should == ['foo'] + s.a.should == 'foo' + + Thread.current[threadlocal_key] = nil + + dumped = Marshal.dump(s) + loaded = Marshal.load(dumped) + + Thread.current[threadlocal_key].should == nil + loaded.a.should == 'foo' + end + end + + describe "for a Data" do + it "loads a Data" do + obj = MarshalSpec::DataSpec::Measure.new(100, 'km') + dumped = "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm" + Marshal.dump(obj).should == dumped + + Marshal.load(dumped).should == obj + end + + it "loads an extended Data" do + obj = MarshalSpec::DataSpec::MeasureExtended.new(100, "km") + dumped = "\x04\bS:+MarshalSpec::DataSpec::MeasureExtended\a:\vamountii:\tunit\"\akm" + Marshal.dump(obj).should == dumped + + Marshal.load(dumped).should == obj + end + + it "returns a frozen object" do + obj = MarshalSpec::DataSpec::Measure.new(100, 'km') + dumped = "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm" + Marshal.dump(obj).should == dumped + + Marshal.load(dumped).should.frozen? + end + end + + describe "for an Exception" do + it "loads a marshalled exception with no message" do + obj = Exception.new + loaded = Marshal.load("\004\bo:\016Exception\a:\abt0:\tmesg0") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace + loaded = Marshal.load("\x04\bo:\x0EException\a:\tmesg0:\abt0") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace + end + + it "loads a marshalled exception with a message" do + obj = Exception.new("foo") + loaded = Marshal.load("\004\bo:\016Exception\a:\abt0:\tmesg\"\bfoo") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace + loaded = Marshal.load("\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt0") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace + end + + it "loads a marshalled exception with a backtrace" do + obj = Exception.new("foo") + obj.set_backtrace(["foo/bar.rb:10"]) + loaded = Marshal.load("\004\bo:\016Exception\a:\abt[\006\"\022foo/bar.rb:10:\tmesg\"\bfoo") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace + loaded = Marshal.load("\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt[\x06I\"\x12foo/bar.rb:10\x06;\aF") + loaded.message.should == obj.message + loaded.backtrace.should == obj.backtrace + end + + it "loads an marshalled exception with ivars" do + s = 'hi' + arr = [:so, :so, s, s] + obj = Exception.new("foo") + obj.instance_variable_set :@arr, arr + + loaded = Marshal.load("\x04\bo:\x0EException\b:\tmesg\"\bfoo:\abt0:\t@arr[\t:\aso;\t\"\ahi@\b") + new_arr = loaded.instance_variable_get :@arr + + loaded.message.should == obj.message + new_arr.should == arr + end + end + + describe "for an Object" do + it "loads an object" do + Marshal.load("\004\bo:\vObject\000").should.is_a?(Object) + end + + it "loads an extended Object" do + obj = Object.new.extend(Meths) + + new_obj = Marshal.load("\004\be:\nMethso:\vObject\000") + + new_obj.class.should == obj.class + new_obj_metaclass_ancestors = class << new_obj; ancestors; end + new_obj_metaclass_ancestors[@num_self_class, 2].should == [Meths, Object] + end + + it "loads an object having ivar" do + s = 'hi' + arr = [:so, :so, s, s] + obj = Object.new + obj.instance_variable_set :@str, arr + + new_obj = Marshal.load("\004\bo:\vObject\006:\t@str[\t:\aso;\a\"\ahi@\a") + new_str = new_obj.instance_variable_get :@str + + new_str.should == arr + end + + it "loads an Object with a non-US-ASCII instance variable" do + ivar = "@é".dup.force_encoding(Encoding::UTF_8).to_sym + obj = Marshal.load("\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06") + obj.instance_variables.should == [ivar] + obj.instance_variables[0].encoding.should == Encoding::UTF_8 + obj.instance_variable_get(ivar).should == 1 + end + + it "raises ArgumentError if the object from an 'o' stream is not dumpable as 'o' type user class" do + -> do + Marshal.load("\x04\bo:\tFile\001\001:\001\005@path\"\x10/etc/passwd") + end.should.raise(ArgumentError) + end + + it "raises ArgumentError when end of byte sequence reached before class name end" do + Marshal.dump(Object.new).should == "\x04\bo:\vObject\x00" + + -> { + Marshal.load("\x04\bo:\vObj") + }.should.raise(ArgumentError, "marshal data too short") + end + end + + describe "for an object responding to #marshal_dump and #marshal_load" do + it "loads a user-marshaled object" do + obj = UserMarshal.new + obj.data = :data + value = [obj, :data] + dump = Marshal.dump(value) + dump.should == "\x04\b[\aU:\x10UserMarshal:\tdata;\x06" + reloaded = Marshal.load(dump) + reloaded.should == value + end + end + + describe "for a user object" do + it "loads a user-marshaled extended object" do + obj = UserMarshal.new.extend(Meths) + + new_obj = Marshal.load("\004\bU:\020UserMarshal\"\nstuff") + + new_obj.should == obj + new_obj_metaclass_ancestors = class << new_obj; ancestors; end + new_obj_metaclass_ancestors[@num_self_class].should == UserMarshal + end + + it "loads a UserObject" do + Marshal.load("\004\bo:\017UserObject\000").should.is_a?(UserObject) + end + + describe "that extends a core type other than Object or BasicObject" do + after :each do + MarshalSpec.reset_swapped_class + end + + it "raises ArgumentError if the resulting class does not extend the same type" do + MarshalSpec.set_swapped_class(Class.new(Hash)) + data = Marshal.dump(MarshalSpec::SwappedClass.new) + + MarshalSpec.set_swapped_class(Class.new(Array)) + -> { Marshal.load(data) }.should.raise(ArgumentError) + + MarshalSpec.set_swapped_class(Class.new) + -> { Marshal.load(data) }.should.raise(ArgumentError) + end + end + end + + describe "for a Regexp" do + ruby_version_is "4.1" do + it "raises FrozenError for an extended Regexp" do + -> { + Marshal.load("\004\be:\nMethse:\016MethsMore/\n[a-z]\000") + }.should.raise(FrozenError) + end + + it "raises FrozenError when regexp has instance variables" do + -> { + Marshal.load("\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/") + }.should.raise(FrozenError) + end + end + + ruby_version_is ""..."4.1" do + it "loads an extended Regexp" do + obj = /[a-z]/.dup.extend(Meths, MethsMore) + new_obj = Marshal.load("\004\be:\nMethse:\016MethsMore/\n[a-z]\000") + + new_obj.should == obj + new_obj_metaclass_ancestors = class << new_obj; ancestors; end + new_obj_metaclass_ancestors[@num_self_class, 3].should == + [Meths, MethsMore, Regexp] + end + + it "restore the regexp instance variables" do + obj = Regexp.new("hello") + obj.instance_variable_set(:@regexp_ivar, [42]) + + new_obj = Marshal.load("\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/") + new_obj.instance_variables.should == [:@regexp_ivar] + new_obj.instance_variable_get(:@regexp_ivar).should == [42] + end + end + + it "loads a Regexp subclass instance variables" do + obj = UserRegexp.new('abc') + obj.instance_variable_set(:@noise, 'much') + + new_obj = Marshal.load(Marshal.dump(obj)) + + new_obj.should == obj + new_obj.instance_variable_get(:@noise).should == 'much' + new_obj_metaclass_ancestors = class << new_obj; ancestors; end + new_obj_metaclass_ancestors[@num_self_class, 2].should == + [UserRegexp, Regexp] + end + + it "loads a Regexp subclass instance variables when it is extended with a module" do + obj = UserRegexp.new('').extend(Meths) + obj.instance_variable_set(:@noise, 'much') + + new_obj = Marshal.load("\004\bIe:\nMethsC:\017UserRegexp/\000\000\006:\v@noise\"\tmuch") + + new_obj.should == obj + new_obj.instance_variable_get(:@noise).should == 'much' + new_obj_metaclass_ancestors = class << new_obj; ancestors; end + new_obj_metaclass_ancestors[@num_self_class, 3].should == + [Meths, UserRegexp, Regexp] + end + + it "preserves Regexp encoding" do + source_object = Regexp.new("a".encode("utf-32le")) + regexp = Marshal.load(Marshal.dump(source_object)) + + regexp.encoding.should == Encoding::UTF_32LE + regexp.source.should == "a".encode("utf-32le") + end + + it "raises ArgumentError when end of byte sequence reached before source string end" do + Marshal.dump(/hello world/).should == "\x04\bI/\x10hello world\x00\x06:\x06EF" + + -> { + Marshal.load("\x04\bI/\x10hel") + }.should.raise(ArgumentError, "marshal data too short") + end + end + + describe "for a Float" do + it "loads a Float NaN" do + obj = 0.0 / 0.0 + Marshal.load("\004\bf\bnan").to_s.should == obj.to_s + end + + it "loads a Float 1.3" do + Marshal.load("\004\bf\v1.3\000\314\315").should == 1.3 + end + + it "loads a Float -5.1867345e-22" do + obj = -5.1867345e-22 + Marshal.load("\004\bf\037-5.1867345000000008e-22\000\203_").should be_close(obj, 1e-30) + end + + it "loads a Float 1.1867345e+22" do + obj = 1.1867345e+22 + Marshal.load("\004\bf\0361.1867344999999999e+22\000\344@").should == obj + end + + it "raises ArgumentError when end of byte sequence reached before float string representation end" do + Marshal.dump(1.3).should == "\x04\bf\b1.3" + + -> { + Marshal.load("\004\bf\v1") + }.should.raise(ArgumentError, "marshal data too short") + end + end + + describe "for an Integer" do + it "loads 0" do + Marshal.load("\004\bi\000").should == 0 + Marshal.load("\004\bi\005").should == 0 + end + + it "loads an Integer 8" do + Marshal.load("\004\bi\r" ).should == 8 + end + + it "loads and Integer -8" do + Marshal.load("\004\bi\363" ).should == -8 + end + + it "loads an Integer 1234" do + Marshal.load("\004\bi\002\322\004").should == 1234 + end + + it "loads an Integer -1234" do + Marshal.load("\004\bi\376.\373").should == -1234 + end + + it "loads an Integer 4611686018427387903" do + Marshal.load("\004\bl+\t\377\377\377\377\377\377\377?").should == 4611686018427387903 + end + + it "loads an Integer -4611686018427387903" do + Marshal.load("\004\bl-\t\377\377\377\377\377\377\377?").should == -4611686018427387903 + end + + it "loads an Integer 2361183241434822606847" do + Marshal.load("\004\bl+\n\377\377\377\377\377\377\377\377\177\000").should == 2361183241434822606847 + end + + it "loads an Integer -2361183241434822606847" do + Marshal.load("\004\bl-\n\377\377\377\377\377\377\377\377\177\000").should == -2361183241434822606847 + end + + it "raises ArgumentError if the input is too short" do + ["\004\bi", + "\004\bi\001", + "\004\bi\002", + "\004\bi\002\0", + "\004\bi\003", + "\004\bi\003\0", + "\004\bi\003\0\0", + "\004\bi\004", + "\004\bi\004\0", + "\004\bi\004\0\0", + "\004\bi\004\0\0\0"].each do |invalid| + -> { Marshal.load(invalid) }.should.raise(ArgumentError) + end + end + + if 0.size == 8 # for platforms like x86_64 + it "roundtrips 4611686018427387903 from dump/load correctly" do + Marshal.load(Marshal.dump(4611686018427387903)).should == 4611686018427387903 + end + end + end + + describe "for a Rational" do + it "loads" do + r = Marshal.load(Marshal.dump(Rational(1, 3))) + r.should == Rational(1, 3) + r.should.frozen? + end + end + + describe "for a Complex" do + it "loads" do + c = Marshal.load(Marshal.dump(Complex(4, 3))) + c.should == Complex(4, 3) + c.should.frozen? + end + end + + describe "for a Bignum" do + platform_is c_long_size: 64 do + context "that is Bignum on 32-bit platforms but Fixnum on 64-bit" do + it "dumps a Fixnum" do + val = Marshal.load("\004\bl+\ab:wU") + val.should == 1433877090 + val.class.should == Integer + end + + it "dumps an array containing multiple references to the Bignum as an array of Fixnum" do + arr = Marshal.load("\004\b[\al+\a\223BwU@\006") + arr.should == [1433879187, 1433879187] + arr.each { |v| v.class.should == Integer } + end + end + end + end + + describe "for a Time" do + it "loads" do + Marshal.load(Marshal.dump(Time.at(1))).should == Time.at(1) + end + + it "loads serialized instance variables" do + t = Time.new + t.instance_variable_set(:@foo, 'bar') + + Marshal.load(Marshal.dump(t)).instance_variable_get(:@foo).should == 'bar' + end + + it "loads Time objects stored as links" do + t = Time.new + + t1, t2 = Marshal.load(Marshal.dump([t, t])) + t1.should.equal? t2 + end + + it "keeps the local zone" do + with_timezone 'AST', 3 do + t = Time.local(2012, 1, 1) + Marshal.load(Marshal.dump(t)).zone.should == t.zone + end + end + + it "keeps UTC zone" do + t = Time.now.utc + t2 = Marshal.load(Marshal.dump(t)) + t2.should.utc? + end + + it "keeps the zone" do + t = nil + + with_timezone 'AST', 4 do + t = Time.local(2012, 1, 1) + end + + with_timezone 'EET', -2 do + Marshal.load(Marshal.dump(t)).zone.should == 'AST' + end + end + + it "keeps utc offset" do + t = Time.new(2007,11,1,15,25,0, "+09:00") + t2 = Marshal.load(Marshal.dump(t)) + t2.utc_offset.should == 32400 + end + + it "keeps nanoseconds" do + t = Time.now + Marshal.load(Marshal.dump(t)).nsec.should == t.nsec + end + + it "does not add any additional instance variable" do + t = Time.now + t2 = Marshal.load(Marshal.dump(t)) + t2.instance_variables.should.empty? + end + end + + describe "for nil" do + it "loads" do + Marshal.load("\x04\b0").should == nil + end + end + + describe "for true" do + it "loads" do + Marshal.load("\x04\bT").should == true + end + end + + describe "for false" do + it "loads" do + Marshal.load("\x04\bF").should == false + end + end + + describe "for a Class" do + it "loads" do + Marshal.load("\x04\bc\vString").should == String + end + + it "raises ArgumentError if given the name of a non-Module" do + -> { Marshal.load("\x04\bc\vKernel") }.should.raise(ArgumentError) + end + + it "raises ArgumentError if given a nonexistent class" do + -> { Marshal.load("\x04\bc\vStrung") }.should.raise(ArgumentError) + end + + it "raises ArgumentError when end of byte sequence reached before class name end" do + Marshal.dump(String).should == "\x04\bc\vString" + + -> { + Marshal.load("\x04\bc\vStr") + }.should.raise(ArgumentError, "marshal data too short") + end + end + + describe "for a Module" do + it "loads a module" do + Marshal.load("\x04\bm\vKernel").should == Kernel + end + + it "raises ArgumentError if given the name of a non-Class" do + -> { Marshal.load("\x04\bm\vString") }.should.raise(ArgumentError) + end + + it "loads an old module" do + Marshal.load("\x04\bM\vKernel").should == Kernel + end + + it "raises ArgumentError when end of byte sequence reached before module name end" do + Marshal.dump(Kernel).should == "\x04\bm\vKernel" + + -> { + Marshal.load("\x04\bm\vKer") + }.should.raise(ArgumentError, "marshal data too short") + end + end + + describe "for a wrapped C pointer" do + it "loads" do + class DumpableDir < Dir + def _dump_data + path + end + def _load_data path + initialize(path) + end + end + + data = "\x04\bd:\x10DumpableDirI\"\x06.\x06:\x06ET" + + dir = Marshal.load(data) + begin + dir.path.should == '.' + ensure + dir.close + end + end + + it "raises TypeError when the local class is missing _load_data" do + class UnloadableDumpableDir < Dir + def _dump_data + path + end + # no _load_data + end + + data = "\x04\bd:\x1AUnloadableDumpableDirI\"\x06.\x06:\x06ET" + + -> { Marshal.load(data) }.should.raise(TypeError) + end + + it "raises ArgumentError when the local class is a regular object" do + data = "\004\bd:\020UserDefined\0" + + -> { Marshal.load(data) }.should.raise(ArgumentError) + end + end + + describe "when a class does not exist in the namespace" do + before :each do + NamespaceTest.send(:const_set, :SameName, Class.new) + @data = Marshal.dump(NamespaceTest::SameName.new) + NamespaceTest.send(:remove_const, :SameName) + end + + it "raises an ArgumentError" do + message = "undefined class/module NamespaceTest::SameName" + -> { Marshal.load(@data) }.should.raise(ArgumentError, message) + end + end + + it "raises an ArgumentError with full constant name when the dumped constant is missing" do + NamespaceTest.send(:const_set, :KaBoom, Class.new) + @data = Marshal.dump(NamespaceTest::KaBoom.new) + NamespaceTest.send(:remove_const, :KaBoom) + + -> { Marshal.load(@data) }.should.raise(ArgumentError, /NamespaceTest::KaBoom/) + end end diff --git a/core/marshal/restore_spec.rb b/core/marshal/restore_spec.rb index 7e75d7dea..3135f881d 100644 --- a/core/marshal/restore_spec.rb +++ b/core/marshal/restore_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/load' describe "Marshal.restore" do - it_behaves_like :marshal_load, :restore + it "is an alias of Marshal.load" do + Marshal.method(:restore).should == Marshal.method(:load) + end end diff --git a/core/marshal/shared/load.rb b/core/marshal/shared/load.rb deleted file mode 100644 index 02c8e7f0b..000000000 --- a/core/marshal/shared/load.rb +++ /dev/null @@ -1,1291 +0,0 @@ -# encoding: binary -require_relative '../fixtures/marshal_data' - -describe :marshal_load, shared: true do - before :all do - @num_self_class = 1 - end - - it "raises an ArgumentError when the dumped data is truncated" do - obj = {first: 1, second: 2, third: 3} - -> { Marshal.send(@method, Marshal.dump(obj)[0, 5]) }.should.raise(ArgumentError, "marshal data too short") - end - - it "raises an ArgumentError when the argument is empty String" do - -> { Marshal.send(@method, "") }.should.raise(ArgumentError, "marshal data too short") - end - - it "raises an ArgumentError when the dumped class is missing" do - Object.send(:const_set, :KaBoom, Class.new) - kaboom = Marshal.dump(KaBoom.new) - Object.send(:remove_const, :KaBoom) - - -> { Marshal.send(@method, kaboom) }.should.raise(ArgumentError) - end - - describe "when called with freeze: true" do - it "returns frozen strings" do - string = Marshal.send(@method, Marshal.dump("foo"), freeze: true) - string.should == "foo" - string.should.frozen? - - utf8_string = "foo".encode(Encoding::UTF_8) - string = Marshal.send(@method, Marshal.dump(utf8_string), freeze: true) - string.should == utf8_string - string.should.frozen? - end - - it "returns frozen arrays" do - array = Marshal.send(@method, Marshal.dump([1, 2, 3]), freeze: true) - array.should == [1, 2, 3] - array.should.frozen? - end - - it "returns frozen hashes" do - hash = Marshal.send(@method, Marshal.dump({foo: 42}), freeze: true) - hash.should == {foo: 42} - hash.should.frozen? - end - - it "returns frozen regexps" do - regexp = Marshal.send(@method, Marshal.dump(/foo/), freeze: true) - regexp.should == /foo/ - regexp.should.frozen? - end - - it "returns frozen structs" do - struct = Marshal.send(@method, Marshal.dump(MarshalSpec::StructToDump.new(1, 2)), freeze: true) - struct.should == MarshalSpec::StructToDump.new(1, 2) - struct.should.frozen? - end - - it "returns frozen objects" do - source_object = Object.new - - object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - object.should.frozen? - end - - describe "deep freezing" do - it "returns hashes with frozen keys and values" do - key = Object.new - value = Object.new - source_object = {key => value} - - hash = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - hash.size.should == 1 - hash.keys[0].should.frozen? - hash.values[0].should.frozen? - end - - it "returns arrays with frozen elements" do - object = Object.new - source_object = [object] - - array = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - array.size.should == 1 - array[0].should.frozen? - end - - it "returns structs with frozen members" do - object1 = Object.new - object2 = Object.new - source_object = MarshalSpec::StructToDump.new(object1, object2) - - struct = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - struct.a.should.frozen? - struct.b.should.frozen? - end - - it "returns objects with frozen instance variables" do - source_object = Object.new - instance_variable = Object.new - source_object.instance_variable_set(:@a, instance_variable) - - object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - object.instance_variable_get(:@a).should != nil - object.instance_variable_get(:@a).should.frozen? - end - - it "deduplicates frozen strings" do - source_object = ["foo" + "bar", "foobar"] - object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - - object[0].should.equal?(object[1]) - end - end - - it "does not freeze modules" do - object = Marshal.send(@method, Marshal.dump(Kernel), freeze: true) - object.should_not.frozen? - Kernel.should_not.frozen? - end - - it "does not freeze classes" do - object = Marshal.send(@method, Marshal.dump(Object), freeze: true) - object.should_not.frozen? - Object.should_not.frozen? - end - - it "does freeze extended objects" do - object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", freeze: true) - object.should.frozen? - end - - it "does freeze extended objects with instance variables" do - object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x06:\n@ivarT", freeze: true) - object.should.frozen? - end - - it "returns frozen object having #_dump method" do - object = Marshal.send(@method, Marshal.dump(UserDefined.new), freeze: true) - object.should.frozen? - end - - it "returns frozen object responding to #marshal_dump and #marshal_load" do - object = Marshal.send(@method, Marshal.dump(UserMarshal.new), freeze: true) - object.should.frozen? - end - - it "returns frozen object extended by a module" do - object = Object.new - object.extend(MarshalSpec::ModuleToExtendBy) - - object = Marshal.send(@method, Marshal.dump(object), freeze: true) - object.should.frozen? - end - - it "does not call freeze method" do - object = MarshalSpec::ObjectWithFreezeRaisingException.new - object = Marshal.send(@method, Marshal.dump(object), freeze: true) - object.should.frozen? - end - - it "returns frozen object even if object does not respond to freeze method" do - object = MarshalSpec::ObjectWithoutFreeze.new - object = Marshal.send(@method, Marshal.dump(object), freeze: true) - object.should.frozen? - end - - it "returns a frozen object when is an instance of String/Array/Regexp/Hash subclass and has instance variables" do - source_object = UserString.new - source_object.instance_variable_set(:@foo, "bar") - - object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - object.should.frozen? - end - - describe "when called with a proc" do - it "call the proc with frozen objects" do - arr = [] - s = +'hi' - s.instance_variable_set(:@foo, 5) - st = Struct.new("Brittle", :a).new - st.instance_variable_set(:@clue, 'none') - st.a = 0.0 - h = Hash.new('def') - h['nine'] = 9 - a = [:a, :b, :c] - a.instance_variable_set(:@two, 2) - obj = [s, 10, s, s, st, a] - obj.instance_variable_set(:@zoo, 'ant') - proc = Proc.new { |o| arr << o; o} - - Marshal.send( - @method, - "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", - proc, - freeze: true, - ) - - arr.should == [ - false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st, - :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]], - ] - - arr.each do |v| - v.should.frozen? - end - - Struct.send(:remove_const, :Brittle) - end - - it "does not freeze the object returned by the proc" do - string = Marshal.send(@method, Marshal.dump("foo"), proc { |o| o.upcase }, freeze: true) - string.should == "FOO" - string.should_not.frozen? - end - end - end - - describe "when called with a proc" do - it "call the proc with fully initialized strings" do - utf8_string = "foo".encode(Encoding::UTF_8) - Marshal.send(@method, Marshal.dump(utf8_string), proc { |arg| - if arg.is_a?(String) - arg.should == utf8_string - arg.encoding.should == Encoding::UTF_8 - end - arg - }) - end - - it "no longer mutate the object after it was passed to the proc" do - string = Marshal.load(Marshal.dump("foo"), :freeze.to_proc) - string.should.frozen? - end - - it "call the proc with extended objects" do - objs = [] - obj = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", Proc.new { |o| objs << o; o }) - objs.should == [obj] - end - - it "returns the value of the proc" do - Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should == [3,4] - end - - it "calls the proc for recursively visited data" do - a = [1] - a << a - ret = [] - Marshal.send(@method, Marshal.dump(a), proc { |arg| ret << arg.inspect; arg }) - ret[0].should == 1.inspect - ret[1].should == a.inspect - ret.size.should == 2 - end - - it "loads an Array with proc" do - arr = [] - s = +'hi' - s.instance_variable_set(:@foo, 5) - st = Struct.new("Brittle", :a).new - st.instance_variable_set(:@clue, 'none') - st.a = 0.0 - h = Hash.new('def') - h['nine'] = 9 - a = [:a, :b, :c] - a.instance_variable_set(:@two, 2) - obj = [s, 10, s, s, st, a] - obj.instance_variable_set(:@zoo, 'ant') - proc = Proc.new { |o| arr << o.dup; o} - - Marshal.send(@method, "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc) - - arr.should == [ - false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st, - :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]], - ] - Struct.send(:remove_const, :Brittle) - end - end - - describe "when called with nil for the proc argument" do - it "behaves as if no proc argument was passed" do - a = [1] - a << a - b = Marshal.send(@method, Marshal.dump(a), nil) - b.should == a - end - end - - describe "when called on objects with custom _dump methods" do - it "does not set instance variables of an object with user-defined _dump/_load" do - # this string represents: <#UserPreviouslyDefinedWithInitializedIvar @field2=7 @field1=6> - dump_str = "\004\bu:-UserPreviouslyDefinedWithInitializedIvar\a:\f@field2i\f:\f@field1i\v" - - UserPreviouslyDefinedWithInitializedIvar.should_receive(:_load).and_return(UserPreviouslyDefinedWithInitializedIvar.new) - marshaled_obj = Marshal.send(@method, dump_str) - - marshaled_obj.should.instance_of?(UserPreviouslyDefinedWithInitializedIvar) - marshaled_obj.field1.should == nil - marshaled_obj.field2.should == nil - end - - it "loads the String in non US-ASCII and non UTF-8 encoding" do - source_object = UserDefinedString.new("a".encode("windows-1251")) - object = Marshal.send(@method, Marshal.dump(source_object)) - object.string.should == "a".encode("windows-1251") - end - - it "loads the String in multibyte encoding" do - source_object = UserDefinedString.new("a".encode("utf-32le")) - object = Marshal.send(@method, Marshal.dump(source_object)) - object.string.should == "a".encode("utf-32le") - end - - describe "that returns an immediate value" do - it "loads an array containing an instance of the object, followed by multiple instances of another object" do - str = "string" - - # this string represents: [<#UserDefinedImmediate A>, <#String "string">, <#String "string">] - marshaled_obj = Marshal.send(@method, "\004\b[\bu:\031UserDefinedImmediate\000\"\vstring@\a") - - marshaled_obj.should == [nil, str, str] - end - - it "loads any structure with multiple references to the same object, followed by multiple instances of another object" do - str = "string" - - # this string represents: {a: <#UserDefinedImmediate A>, b: <#UserDefinedImmediate A>, c: <#String "string">, d: <#String "string">} - hash_dump = "\x04\b{\t:\x06aIu:\x19UserDefinedImmediate\x00\x06:\x06ET:\x06b@\x06:\x06cI\"\vstring\x06;\aT:\x06d@\a" - - marshaled_obj = Marshal.send(@method, hash_dump) - marshaled_obj.should == {a: nil, b: nil, c: str, d: str} - - # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate A>, <#String "string">, <#String "string">] - array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ET@\x06I\"\vstring\x06;\x06T@\a" - - marshaled_obj = Marshal.send(@method, array_dump) - marshaled_obj.should == [nil, nil, str, str] - end - - it "loads an array containing references to multiple instances of the object, followed by multiple instances of another object" do - str = "string" - - # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate B>, <#String "string">, <#String "string">] - array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ETIu;\x00\x00\x06;\x06TI\"\vstring\x06;\x06T@\b" - - marshaled_obj = Marshal.send(@method, array_dump) - marshaled_obj.should == [nil, nil, str, str] - end - end - end - - it "loads an array containing objects having _dump method, and with proc" do - arr = [] - myproc = Proc.new { |o| arr << o.dup; o } - o1 = UserDefined.new; - o2 = UserDefinedWithIvar.new - obj = [o1, o2, o1, o2] - - Marshal.send(@method, "\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc) - - arr[0].should == o1 - arr[1].should == o2 - arr[2].should == obj - arr.size.should == 3 - end - - it "loads an array containing objects having marshal_dump method, and with proc" do - arr = [] - proc = Proc.new { |o| arr << o.dup; o } - o1 = UserMarshal.new - o2 = UserMarshalWithIvar.new - - Marshal.send(@method, "\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc) - - arr[0].should == 'stuff' - arr[1].should == o1 - arr[2].should == 'my data' - arr[3].should == ['my data'] - arr[4].should == o2 - arr[5].should == [o1, o2, o1, o2] - - arr.size.should == 6 - end - - it "assigns classes to nested subclasses of Array correctly" do - arr = ArraySub.new(ArraySub.new) - arr_dump = Marshal.dump(arr) - Marshal.send(@method, arr_dump).class.should == ArraySub - end - - it "loads subclasses of Array with overridden << and push correctly" do - arr = ArraySubPush.new - arr[0] = '1' - arr_dump = Marshal.dump(arr) - Marshal.send(@method, arr_dump).should == arr - end - - it "raises a TypeError with bad Marshal version" do - marshal_data = +'\xff\xff' - marshal_data[0] = (Marshal::MAJOR_VERSION).chr - marshal_data[1] = (Marshal::MINOR_VERSION + 1).chr - - -> { Marshal.send(@method, marshal_data) }.should.raise(TypeError) - - marshal_data = +'\xff\xff' - marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr - marshal_data[1] = (Marshal::MINOR_VERSION).chr - - -> { Marshal.send(@method, marshal_data) }.should.raise(TypeError) - end - - it "raises EOFError on loading an empty file" do - temp_file = tmp("marshal.rubyspec.tmp.#{Process.pid}") - file = File.new(temp_file, "w+") - begin - -> { Marshal.send(@method, file) }.should.raise(EOFError) - ensure - file.close - rm_r temp_file - end - end - - # Note: Ruby 1.9 should be compatible with older marshal format - MarshalSpec::DATA.each do |description, (object, marshal, attributes)| - it "loads a #{description}" do - Marshal.send(@method, marshal).should == object - end - end - - MarshalSpec::DATA_19.each do |description, (object, marshal, attributes)| - it "loads a #{description}" do - Marshal.send(@method, marshal).should == object - end - end - - describe "for an Array" do - it "loads an array containing the same objects" do - s = 'oh' - b = 'hi' - r = // - d = [b, :no, s, :go] - c = String - f = 1.0 - - o1 = UserMarshalWithIvar.new; o2 = UserMarshal.new - - obj = [:so, 'hello', 100, :so, :so, d, :so, o2, :so, :no, o2, - :go, c, nil, Struct::Pyramid.new, f, :go, :no, s, b, r, - :so, 'huh', o1, true, b, b, 99, r, b, s, :so, f, c, :no, o1, d] - - Marshal.send(@method, "\004\b[*:\aso\"\nhelloii;\000;\000[\t\"\ahi:\ano\"\aoh:\ago;\000U:\020UserMarshal\"\nstuff;\000;\006@\n;\ac\vString0S:\024Struct::Pyramid\000f\0061;\a;\006@\t@\b/\000\000;\000\"\bhuhU:\030UserMarshalWithIvar[\006\"\fmy dataT@\b@\bih@\017@\b@\t;\000@\016@\f;\006@\021@\a").should == - obj - end - - it "loads an array having ivar" do - s = +'well' - s.instance_variable_set(:@foo, 10) - obj = ['5', s, 'hi'].extend(Meths, MethsMore) - obj.instance_variable_set(:@mix, s) - new_obj = Marshal.send(@method, "\004\bI[\b\"\0065I\"\twell\006:\t@fooi\017\"\ahi\006:\t@mix@\a") - new_obj.should == obj - new_obj.instance_variable_get(:@mix).should.equal? new_obj[1] - new_obj[1].instance_variable_get(:@foo).should == 10 - end - - it "loads an extended Array object containing a user-marshaled object" do - obj = [UserMarshal.new, UserMarshal.new].extend(Meths) - dump = "\x04\be:\nMeths[\ao:\x10UserMarshal\x06:\n@dataI\"\nstuff\x06:\x06ETo;\x06\x06;\aI\"\nstuff\x06;\bT" - new_obj = Marshal.send(@method, dump) - - new_obj.should == obj - obj_ancestors = class << obj; ancestors[1..-1]; end - new_obj_ancestors = class << new_obj; ancestors[1..-1]; end - obj_ancestors.should == new_obj_ancestors - end - end - - describe "for a Hash" do - it "loads an extended_user_hash with a parameter to initialize" do - obj = UserHashInitParams.new(:abc).extend(Meths) - - new_obj = Marshal.send(@method, "\004\bIe:\nMethsC:\027UserHashInitParams{\000\006:\a@a:\babc") - - new_obj.should == obj - new_obj_metaclass_ancestors = class << new_obj; ancestors; end - new_obj_metaclass_ancestors[@num_self_class].should == Meths - new_obj_metaclass_ancestors[@num_self_class+1].should == UserHashInitParams - end - - it "loads an extended hash object containing a user-marshaled object" do - obj = {a: UserMarshal.new}.extend(Meths) - - new_obj = Marshal.send(@method, "\004\be:\nMeths{\006:\006aU:\020UserMarshal\"\nstuff") - - new_obj.should == obj - new_obj_metaclass_ancestors = class << new_obj; ancestors; end - new_obj_metaclass_ancestors[@num_self_class].should == Meths - new_obj_metaclass_ancestors[@num_self_class+1].should == Hash - end - - it "preserves hash ivars when hash contains a string having ivar" do - s = +'string' - s.instance_variable_set :@string_ivar, 'string ivar' - h = { key: s } - h.instance_variable_set :@hash_ivar, 'hash ivar' - - unmarshalled = Marshal.send(@method, Marshal.dump(h)) - unmarshalled.instance_variable_get(:@hash_ivar).should == 'hash ivar' - unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar' - end - - it "preserves compare_by_identity behaviour" do - h = { a: 1 } - h.compare_by_identity - unmarshalled = Marshal.send(@method, Marshal.dump(h)) - unmarshalled.should.compare_by_identity? - - h = { a: 1 } - unmarshalled = Marshal.send(@method, Marshal.dump(h)) - unmarshalled.should_not.compare_by_identity? - end - - it "preserves compare_by_identity behaviour for a Hash subclass" do - h = UserHash.new({ a: 1 }) - h.compare_by_identity - unmarshalled = Marshal.send(@method, Marshal.dump(h)) - unmarshalled.should.compare_by_identity? - - h = UserHash.new({ a: 1 }) - unmarshalled = Marshal.send(@method, Marshal.dump(h)) - unmarshalled.should_not.compare_by_identity? - end - - it "allocates an instance of the proper class when Hash subclass with compare_by_identity behaviour" do - h = UserHash.new({ a: 1 }) - h.compare_by_identity - - unmarshalled = Marshal.send(@method, Marshal.dump(h)) - unmarshalled.should.kind_of?(UserHash) - end - end - - describe "for a Symbol" do - it "loads a Symbol" do - sym = Marshal.send(@method, "\004\b:\vsymbol") - sym.should == :symbol - sym.encoding.should == Encoding::US_ASCII - end - - it "loads a big Symbol" do - sym = ('big' * 100).to_sym - Marshal.send(@method, "\004\b:\002,\001#{'big' * 100}").should == sym - end - - it "loads an encoded Symbol" do - s = "\u2192" - - sym = Marshal.send(@method, "\x04\bI:\b\xE2\x86\x92\x06:\x06ET") - sym.should == s.encode("utf-8").to_sym - sym.encoding.should == Encoding::UTF_8 - - sym = Marshal.send(@method, "\x04\bI:\t\xFE\xFF!\x92\x06:\rencoding\"\vUTF-16") - sym.should == s.encode("utf-16").to_sym - sym.encoding.should == Encoding::UTF_16 - - sym = Marshal.send(@method, "\x04\bI:\a\x92!\x06:\rencoding\"\rUTF-16LE") - sym.should == s.encode("utf-16le").to_sym - sym.encoding.should == Encoding::UTF_16LE - - sym = Marshal.send(@method, "\x04\bI:\a!\x92\x06:\rencoding\"\rUTF-16BE") - sym.should == s.encode("utf-16be").to_sym - sym.encoding.should == Encoding::UTF_16BE - - sym = Marshal.send(@method, "\x04\bI:\a\xA2\xAA\x06:\rencoding\"\vEUC-JP") - sym.should == s.encode("euc-jp").to_sym - sym.encoding.should == Encoding::EUC_JP - - sym = Marshal.send(@method, "\x04\bI:\a\x81\xA8\x06:\rencoding\"\x10Windows-31J") - sym.should == s.encode("sjis").to_sym - sym.encoding.should == Encoding::SJIS - end - - it "loads a binary encoded Symbol" do - s = "\u2192".dup.force_encoding("binary").to_sym - sym = Marshal.send(@method, "\x04\b:\b\xE2\x86\x92") - sym.should == s - sym.encoding.should == Encoding::BINARY - end - - it "loads multiple Symbols sharing the same encoding" do - # Note that the encoding is a link for the second Symbol - symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET" - symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T" - dump = "\x04\b[\a#{symbol1}#{symbol2}" - value = Marshal.send(@method, dump) - value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8] - expected = [ - "€a".dup.force_encoding(Encoding::UTF_8).to_sym, - "€b".dup.force_encoding(Encoding::UTF_8).to_sym - ] - value.should == expected - - value = Marshal.send(@method, "\x04\b[\b#{symbol1}#{symbol2};\x00") - value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8] - value.should == [*expected, expected[0]] - end - - it "raises ArgumentError when end of byte sequence reached before symbol characters end" do - Marshal.dump(:hello).should == "\x04\b:\nhello" - - -> { - Marshal.send(@method, "\x04\b:\nhel") - }.should.raise(ArgumentError, "marshal data too short") - end - end - - describe "for a String" do - it "loads a string having ivar with ref to self" do - obj = +'hi' - obj.instance_variable_set(:@self, obj) - Marshal.send(@method, "\004\bI\"\ahi\006:\n@self@\000").should == obj - end - - it "loads a string through StringIO stream" do - require 'stringio' - obj = "This is a string which should be unmarshalled through StringIO stream!" - Marshal.send(@method, StringIO.new(Marshal.dump(obj))).should == obj - end - - it "sets binmode if it is loading through StringIO stream" do - io = StringIO.new("\004\b:\vsymbol") - def io.binmode; raise "binmode"; end - -> { Marshal.load(io) }.should.raise(RuntimeError, "binmode") - end - - it "loads a string with an ivar" do - str = Marshal.send(@method, "\x04\bI\"\x00\x06:\t@fooI\"\bbar\x06:\x06EF") - str.instance_variable_get("@foo").should == "bar" - end - - it "loads a String subclass with custom constructor" do - str = Marshal.send(@method, "\x04\bC: UserCustomConstructorString\"\x00") - str.should.instance_of?(UserCustomConstructorString) - end - - it "loads a US-ASCII String" do - str = "abc".dup.force_encoding("us-ascii") - data = "\x04\bI\"\babc\x06:\x06EF" - result = Marshal.send(@method, data) - result.should == str - result.encoding.should.equal?(Encoding::US_ASCII) - end - - it "loads a UTF-8 String" do - str = "\x6d\xc3\xb6\x68\x72\x65".dup.force_encoding("utf-8") - data = "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET" - result = Marshal.send(@method, data) - result.should == str - result.encoding.should.equal?(Encoding::UTF_8) - end - - it "loads a String in another encoding" do - str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".dup.force_encoding("utf-16le") - data = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE" - result = Marshal.send(@method, data) - result.should == str - result.encoding.should.equal?(Encoding::UTF_16LE) - end - - it "loads a String as BINARY if no encoding is specified at the end" do - str = "\xC3\xB8".dup.force_encoding("BINARY") - data = "\x04\b\"\a\xC3\xB8".dup.force_encoding("UTF-8") - result = Marshal.send(@method, data) - result.encoding.should == Encoding::BINARY - result.should == str - end - - it "raises ArgumentError when end of byte sequence reached before string characters end" do - Marshal.dump("hello").should == "\x04\b\"\nhello" - - -> { - Marshal.send(@method, "\x04\b\"\nhel") - }.should.raise(ArgumentError, "marshal data too short") - end - end - - describe "for a Struct" do - it "loads a extended_struct having fields with same objects" do - s = 'hi' - obj = Struct.new("Extended", :a, :b).new.extend(Meths) - dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0" - Marshal.send(@method, dump).should == obj - - obj.a = [:a, s] - obj.b = [:Meths, s] - dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a" - Marshal.send(@method, dump).should == obj - Struct.send(:remove_const, :Extended) - end - - it "loads a struct having ivar" do - obj = Struct.new("Thick").new - obj.instance_variable_set(:@foo, 5) - reloaded = Marshal.send(@method, "\004\bIS:\022Struct::Thick\000\006:\t@fooi\n") - reloaded.should == obj - reloaded.instance_variable_get(:@foo).should == 5 - Struct.send(:remove_const, :Thick) - end - - it "loads a struct having fields" do - obj = Struct.new("Ure1", :a, :b).new - Marshal.send(@method, "\004\bS:\021Struct::Ure1\a:\006a0:\006b0").should == obj - Struct.send(:remove_const, :Ure1) - end - - it "does not call initialize on the unmarshaled struct" do - threadlocal_key = MarshalSpec::StructWithUserInitialize::THREADLOCAL_KEY - - s = MarshalSpec::StructWithUserInitialize.new('foo') - Thread.current[threadlocal_key].should == ['foo'] - s.a.should == 'foo' - - Thread.current[threadlocal_key] = nil - - dumped = Marshal.dump(s) - loaded = Marshal.send(@method, dumped) - - Thread.current[threadlocal_key].should == nil - loaded.a.should == 'foo' - end - end - - describe "for a Data" do - it "loads a Data" do - obj = MarshalSpec::DataSpec::Measure.new(100, 'km') - dumped = "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm" - Marshal.dump(obj).should == dumped - - Marshal.send(@method, dumped).should == obj - end - - it "loads an extended Data" do - obj = MarshalSpec::DataSpec::MeasureExtended.new(100, "km") - dumped = "\x04\bS:+MarshalSpec::DataSpec::MeasureExtended\a:\vamountii:\tunit\"\akm" - Marshal.dump(obj).should == dumped - - Marshal.send(@method, dumped).should == obj - end - - it "returns a frozen object" do - obj = MarshalSpec::DataSpec::Measure.new(100, 'km') - dumped = "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm" - Marshal.dump(obj).should == dumped - - Marshal.send(@method, dumped).should.frozen? - end - end - - describe "for an Exception" do - it "loads a marshalled exception with no message" do - obj = Exception.new - loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg0") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesg0:\abt0") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - end - - it "loads a marshalled exception with a message" do - obj = Exception.new("foo") - loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg\"\bfoo") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt0") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - end - - it "loads a marshalled exception with a backtrace" do - obj = Exception.new("foo") - obj.set_backtrace(["foo/bar.rb:10"]) - loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt[\006\"\022foo/bar.rb:10:\tmesg\"\bfoo") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt[\x06I\"\x12foo/bar.rb:10\x06;\aF") - loaded.message.should == obj.message - loaded.backtrace.should == obj.backtrace - end - - it "loads an marshalled exception with ivars" do - s = 'hi' - arr = [:so, :so, s, s] - obj = Exception.new("foo") - obj.instance_variable_set :@arr, arr - - loaded = Marshal.send(@method, "\x04\bo:\x0EException\b:\tmesg\"\bfoo:\abt0:\t@arr[\t:\aso;\t\"\ahi@\b") - new_arr = loaded.instance_variable_get :@arr - - loaded.message.should == obj.message - new_arr.should == arr - end - end - - describe "for an Object" do - it "loads an object" do - Marshal.send(@method, "\004\bo:\vObject\000").should.is_a?(Object) - end - - it "loads an extended Object" do - obj = Object.new.extend(Meths) - - new_obj = Marshal.send(@method, "\004\be:\nMethso:\vObject\000") - - new_obj.class.should == obj.class - new_obj_metaclass_ancestors = class << new_obj; ancestors; end - new_obj_metaclass_ancestors[@num_self_class, 2].should == [Meths, Object] - end - - it "loads an object having ivar" do - s = 'hi' - arr = [:so, :so, s, s] - obj = Object.new - obj.instance_variable_set :@str, arr - - new_obj = Marshal.send(@method, "\004\bo:\vObject\006:\t@str[\t:\aso;\a\"\ahi@\a") - new_str = new_obj.instance_variable_get :@str - - new_str.should == arr - end - - it "loads an Object with a non-US-ASCII instance variable" do - ivar = "@é".dup.force_encoding(Encoding::UTF_8).to_sym - obj = Marshal.send(@method, "\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06") - obj.instance_variables.should == [ivar] - obj.instance_variables[0].encoding.should == Encoding::UTF_8 - obj.instance_variable_get(ivar).should == 1 - end - - it "raises ArgumentError if the object from an 'o' stream is not dumpable as 'o' type user class" do - -> do - Marshal.send(@method, "\x04\bo:\tFile\001\001:\001\005@path\"\x10/etc/passwd") - end.should.raise(ArgumentError) - end - - it "raises ArgumentError when end of byte sequence reached before class name end" do - Marshal.dump(Object.new).should == "\x04\bo:\vObject\x00" - - -> { - Marshal.send(@method, "\x04\bo:\vObj") - }.should.raise(ArgumentError, "marshal data too short") - end - end - - describe "for an object responding to #marshal_dump and #marshal_load" do - it "loads a user-marshaled object" do - obj = UserMarshal.new - obj.data = :data - value = [obj, :data] - dump = Marshal.dump(value) - dump.should == "\x04\b[\aU:\x10UserMarshal:\tdata;\x06" - reloaded = Marshal.load(dump) - reloaded.should == value - end - end - - describe "for a user object" do - it "loads a user-marshaled extended object" do - obj = UserMarshal.new.extend(Meths) - - new_obj = Marshal.send(@method, "\004\bU:\020UserMarshal\"\nstuff") - - new_obj.should == obj - new_obj_metaclass_ancestors = class << new_obj; ancestors; end - new_obj_metaclass_ancestors[@num_self_class].should == UserMarshal - end - - it "loads a UserObject" do - Marshal.send(@method, "\004\bo:\017UserObject\000").should.is_a?(UserObject) - end - - describe "that extends a core type other than Object or BasicObject" do - after :each do - MarshalSpec.reset_swapped_class - end - - it "raises ArgumentError if the resulting class does not extend the same type" do - MarshalSpec.set_swapped_class(Class.new(Hash)) - data = Marshal.dump(MarshalSpec::SwappedClass.new) - - MarshalSpec.set_swapped_class(Class.new(Array)) - -> { Marshal.send(@method, data) }.should.raise(ArgumentError) - - MarshalSpec.set_swapped_class(Class.new) - -> { Marshal.send(@method, data) }.should.raise(ArgumentError) - end - end - end - - describe "for a Regexp" do - ruby_version_is "4.1" do - it "raises FrozenError for an extended Regexp" do - -> { - Marshal.send(@method, "\004\be:\nMethse:\016MethsMore/\n[a-z]\000") - }.should.raise(FrozenError) - end - - it "raises FrozenError when regexp has instance variables" do - -> { - Marshal.send(@method, "\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/") - }.should.raise(FrozenError) - end - end - - ruby_version_is ""..."4.1" do - it "loads an extended Regexp" do - obj = /[a-z]/.dup.extend(Meths, MethsMore) - new_obj = Marshal.send(@method, "\004\be:\nMethse:\016MethsMore/\n[a-z]\000") - - new_obj.should == obj - new_obj_metaclass_ancestors = class << new_obj; ancestors; end - new_obj_metaclass_ancestors[@num_self_class, 3].should == - [Meths, MethsMore, Regexp] - end - - it "restore the regexp instance variables" do - obj = Regexp.new("hello") - obj.instance_variable_set(:@regexp_ivar, [42]) - - new_obj = Marshal.send(@method, "\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/") - new_obj.instance_variables.should == [:@regexp_ivar] - new_obj.instance_variable_get(:@regexp_ivar).should == [42] - end - end - - it "loads a Regexp subclass instance variables" do - obj = UserRegexp.new('abc') - obj.instance_variable_set(:@noise, 'much') - - new_obj = Marshal.send(@method, Marshal.dump(obj)) - - new_obj.should == obj - new_obj.instance_variable_get(:@noise).should == 'much' - new_obj_metaclass_ancestors = class << new_obj; ancestors; end - new_obj_metaclass_ancestors[@num_self_class, 2].should == - [UserRegexp, Regexp] - end - - it "loads a Regexp subclass instance variables when it is extended with a module" do - obj = UserRegexp.new('').extend(Meths) - obj.instance_variable_set(:@noise, 'much') - - new_obj = Marshal.send(@method, "\004\bIe:\nMethsC:\017UserRegexp/\000\000\006:\v@noise\"\tmuch") - - new_obj.should == obj - new_obj.instance_variable_get(:@noise).should == 'much' - new_obj_metaclass_ancestors = class << new_obj; ancestors; end - new_obj_metaclass_ancestors[@num_self_class, 3].should == - [Meths, UserRegexp, Regexp] - end - - it "preserves Regexp encoding" do - source_object = Regexp.new("a".encode("utf-32le")) - regexp = Marshal.send(@method, Marshal.dump(source_object)) - - regexp.encoding.should == Encoding::UTF_32LE - regexp.source.should == "a".encode("utf-32le") - end - - it "raises ArgumentError when end of byte sequence reached before source string end" do - Marshal.dump(/hello world/).should == "\x04\bI/\x10hello world\x00\x06:\x06EF" - - -> { - Marshal.send(@method, "\x04\bI/\x10hel") - }.should.raise(ArgumentError, "marshal data too short") - end - end - - describe "for a Float" do - it "loads a Float NaN" do - obj = 0.0 / 0.0 - Marshal.send(@method, "\004\bf\bnan").to_s.should == obj.to_s - end - - it "loads a Float 1.3" do - Marshal.send(@method, "\004\bf\v1.3\000\314\315").should == 1.3 - end - - it "loads a Float -5.1867345e-22" do - obj = -5.1867345e-22 - Marshal.send(@method, "\004\bf\037-5.1867345000000008e-22\000\203_").should be_close(obj, 1e-30) - end - - it "loads a Float 1.1867345e+22" do - obj = 1.1867345e+22 - Marshal.send(@method, "\004\bf\0361.1867344999999999e+22\000\344@").should == obj - end - - it "raises ArgumentError when end of byte sequence reached before float string representation end" do - Marshal.dump(1.3).should == "\x04\bf\b1.3" - - -> { - Marshal.send(@method, "\004\bf\v1") - }.should.raise(ArgumentError, "marshal data too short") - end - end - - describe "for an Integer" do - it "loads 0" do - Marshal.send(@method, "\004\bi\000").should == 0 - Marshal.send(@method, "\004\bi\005").should == 0 - end - - it "loads an Integer 8" do - Marshal.send(@method, "\004\bi\r" ).should == 8 - end - - it "loads and Integer -8" do - Marshal.send(@method, "\004\bi\363" ).should == -8 - end - - it "loads an Integer 1234" do - Marshal.send(@method, "\004\bi\002\322\004").should == 1234 - end - - it "loads an Integer -1234" do - Marshal.send(@method, "\004\bi\376.\373").should == -1234 - end - - it "loads an Integer 4611686018427387903" do - Marshal.send(@method, "\004\bl+\t\377\377\377\377\377\377\377?").should == 4611686018427387903 - end - - it "loads an Integer -4611686018427387903" do - Marshal.send(@method, "\004\bl-\t\377\377\377\377\377\377\377?").should == -4611686018427387903 - end - - it "loads an Integer 2361183241434822606847" do - Marshal.send(@method, "\004\bl+\n\377\377\377\377\377\377\377\377\177\000").should == 2361183241434822606847 - end - - it "loads an Integer -2361183241434822606847" do - Marshal.send(@method, "\004\bl-\n\377\377\377\377\377\377\377\377\177\000").should == -2361183241434822606847 - end - - it "raises ArgumentError if the input is too short" do - ["\004\bi", - "\004\bi\001", - "\004\bi\002", - "\004\bi\002\0", - "\004\bi\003", - "\004\bi\003\0", - "\004\bi\003\0\0", - "\004\bi\004", - "\004\bi\004\0", - "\004\bi\004\0\0", - "\004\bi\004\0\0\0"].each do |invalid| - -> { Marshal.send(@method, invalid) }.should.raise(ArgumentError) - end - end - - if 0.size == 8 # for platforms like x86_64 - it "roundtrips 4611686018427387903 from dump/load correctly" do - Marshal.send(@method, Marshal.dump(4611686018427387903)).should == 4611686018427387903 - end - end - end - - describe "for a Rational" do - it "loads" do - r = Marshal.send(@method, Marshal.dump(Rational(1, 3))) - r.should == Rational(1, 3) - r.should.frozen? - end - end - - describe "for a Complex" do - it "loads" do - c = Marshal.send(@method, Marshal.dump(Complex(4, 3))) - c.should == Complex(4, 3) - c.should.frozen? - end - end - - describe "for a Bignum" do - platform_is c_long_size: 64 do - context "that is Bignum on 32-bit platforms but Fixnum on 64-bit" do - it "dumps a Fixnum" do - val = Marshal.send(@method, "\004\bl+\ab:wU") - val.should == 1433877090 - val.class.should == Integer - end - - it "dumps an array containing multiple references to the Bignum as an array of Fixnum" do - arr = Marshal.send(@method, "\004\b[\al+\a\223BwU@\006") - arr.should == [1433879187, 1433879187] - arr.each { |v| v.class.should == Integer } - end - end - end - end - - describe "for a Time" do - it "loads" do - Marshal.send(@method, Marshal.dump(Time.at(1))).should == Time.at(1) - end - - it "loads serialized instance variables" do - t = Time.new - t.instance_variable_set(:@foo, 'bar') - - Marshal.send(@method, Marshal.dump(t)).instance_variable_get(:@foo).should == 'bar' - end - - it "loads Time objects stored as links" do - t = Time.new - - t1, t2 = Marshal.send(@method, Marshal.dump([t, t])) - t1.should.equal? t2 - end - - it "keeps the local zone" do - with_timezone 'AST', 3 do - t = Time.local(2012, 1, 1) - Marshal.send(@method, Marshal.dump(t)).zone.should == t.zone - end - end - - it "keeps UTC zone" do - t = Time.now.utc - t2 = Marshal.send(@method, Marshal.dump(t)) - t2.should.utc? - end - - it "keeps the zone" do - t = nil - - with_timezone 'AST', 4 do - t = Time.local(2012, 1, 1) - end - - with_timezone 'EET', -2 do - Marshal.send(@method, Marshal.dump(t)).zone.should == 'AST' - end - end - - it "keeps utc offset" do - t = Time.new(2007,11,1,15,25,0, "+09:00") - t2 = Marshal.send(@method, Marshal.dump(t)) - t2.utc_offset.should == 32400 - end - - it "keeps nanoseconds" do - t = Time.now - Marshal.send(@method, Marshal.dump(t)).nsec.should == t.nsec - end - - it "does not add any additional instance variable" do - t = Time.now - t2 = Marshal.send(@method, Marshal.dump(t)) - t2.instance_variables.should.empty? - end - end - - describe "for nil" do - it "loads" do - Marshal.send(@method, "\x04\b0").should == nil - end - end - - describe "for true" do - it "loads" do - Marshal.send(@method, "\x04\bT").should == true - end - end - - describe "for false" do - it "loads" do - Marshal.send(@method, "\x04\bF").should == false - end - end - - describe "for a Class" do - it "loads" do - Marshal.send(@method, "\x04\bc\vString").should == String - end - - it "raises ArgumentError if given the name of a non-Module" do - -> { Marshal.send(@method, "\x04\bc\vKernel") }.should.raise(ArgumentError) - end - - it "raises ArgumentError if given a nonexistent class" do - -> { Marshal.send(@method, "\x04\bc\vStrung") }.should.raise(ArgumentError) - end - - it "raises ArgumentError when end of byte sequence reached before class name end" do - Marshal.dump(String).should == "\x04\bc\vString" - - -> { - Marshal.send(@method, "\x04\bc\vStr") - }.should.raise(ArgumentError, "marshal data too short") - end - end - - describe "for a Module" do - it "loads a module" do - Marshal.send(@method, "\x04\bm\vKernel").should == Kernel - end - - it "raises ArgumentError if given the name of a non-Class" do - -> { Marshal.send(@method, "\x04\bm\vString") }.should.raise(ArgumentError) - end - - it "loads an old module" do - Marshal.send(@method, "\x04\bM\vKernel").should == Kernel - end - - it "raises ArgumentError when end of byte sequence reached before module name end" do - Marshal.dump(Kernel).should == "\x04\bm\vKernel" - - -> { - Marshal.send(@method, "\x04\bm\vKer") - }.should.raise(ArgumentError, "marshal data too short") - end - end - - describe "for a wrapped C pointer" do - it "loads" do - class DumpableDir < Dir - def _dump_data - path - end - def _load_data path - initialize(path) - end - end - - data = "\x04\bd:\x10DumpableDirI\"\x06.\x06:\x06ET" - - dir = Marshal.send(@method, data) - begin - dir.path.should == '.' - ensure - dir.close - end - end - - it "raises TypeError when the local class is missing _load_data" do - class UnloadableDumpableDir < Dir - def _dump_data - path - end - # no _load_data - end - - data = "\x04\bd:\x1AUnloadableDumpableDirI\"\x06.\x06:\x06ET" - - -> { Marshal.send(@method, data) }.should.raise(TypeError) - end - - it "raises ArgumentError when the local class is a regular object" do - data = "\004\bd:\020UserDefined\0" - - -> { Marshal.send(@method, data) }.should.raise(ArgumentError) - end - end - - describe "when a class does not exist in the namespace" do - before :each do - NamespaceTest.send(:const_set, :SameName, Class.new) - @data = Marshal.dump(NamespaceTest::SameName.new) - NamespaceTest.send(:remove_const, :SameName) - end - - it "raises an ArgumentError" do - message = "undefined class/module NamespaceTest::SameName" - -> { Marshal.send(@method, @data) }.should.raise(ArgumentError, message) - end - end - - it "raises an ArgumentError with full constant name when the dumped constant is missing" do - NamespaceTest.send(:const_set, :KaBoom, Class.new) - @data = Marshal.dump(NamespaceTest::KaBoom.new) - NamespaceTest.send(:remove_const, :KaBoom) - - -> { Marshal.send(@method, @data) }.should.raise(ArgumentError, /NamespaceTest::KaBoom/) - end -end diff --git a/core/matchdata/captures_spec.rb b/core/matchdata/captures_spec.rb index f829a2548..fdbc1c434 100644 --- a/core/matchdata/captures_spec.rb +++ b/core/matchdata/captures_spec.rb @@ -1,6 +1,13 @@ require_relative '../../spec_helper' -require_relative 'shared/captures' +require_relative 'fixtures/classes' describe "MatchData#captures" do - it_behaves_like :matchdata_captures, :captures + it "returns an array of the match captures" do + /(.)(.)(\d+)(\d)/.match("THX1138.").captures.should == ["H","X","113","8"] + end + + it "returns instances of String when given a String subclass" do + str = MatchDataSpecs::MyString.new("THX1138: The Movie") + /(.)(.)(\d+)(\d)/.match(str).captures.each { |c| c.should.instance_of?(String) } + end end diff --git a/core/matchdata/deconstruct_spec.rb b/core/matchdata/deconstruct_spec.rb index c55095665..13ebd1848 100644 --- a/core/matchdata/deconstruct_spec.rb +++ b/core/matchdata/deconstruct_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/captures' describe "MatchData#deconstruct" do - it_behaves_like :matchdata_captures, :deconstruct + it "is an alias of MatchData#captures" do + MatchData.instance_method(:deconstruct).should == MatchData.instance_method(:captures) + end end diff --git a/core/matchdata/eql_spec.rb b/core/matchdata/eql_spec.rb index 1d9666ebe..bc7123b05 100644 --- a/core/matchdata/eql_spec.rb +++ b/core/matchdata/eql_spec.rb @@ -1,6 +1,26 @@ require_relative '../../spec_helper' -require_relative 'shared/eql' describe "MatchData#eql?" do - it_behaves_like :matchdata_eql, :eql? + it "returns true if both operands have equal target strings, patterns, and match positions" do + a = 'haystack'.match(/hay/) + b = 'haystack'.match(/hay/) + a.eql?(b).should == true + end + + it "returns false if the operands have different target strings" do + a = 'hay'.match(/hay/) + b = 'haystack'.match(/hay/) + a.eql?(b).should == false + end + + it "returns false if the operands have different patterns" do + a = 'haystack'.match(/h.y/) + b = 'haystack'.match(/hay/) + a.eql?(b).should == false + end + + it "returns false if the argument is not a MatchData object" do + a = 'haystack'.match(/hay/) + a.eql?(Object.new).should == false + end end diff --git a/core/matchdata/equal_value_spec.rb b/core/matchdata/equal_value_spec.rb index a58f1277e..e3abb59a1 100644 --- a/core/matchdata/equal_value_spec.rb +++ b/core/matchdata/equal_value_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/eql' describe "MatchData#==" do - it_behaves_like :matchdata_eql, :== + it "is an alias of MatchData#eql?" do + MatchData.instance_method(:==).should == MatchData.instance_method(:eql?) + end end diff --git a/core/matchdata/length_spec.rb b/core/matchdata/length_spec.rb index 39df36df4..72d4d80ce 100644 --- a/core/matchdata/length_spec.rb +++ b/core/matchdata/length_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/length' describe "MatchData#length" do - it_behaves_like :matchdata_length, :length + it "is an alias of MatchData#size" do + MatchData.instance_method(:length).should == MatchData.instance_method(:size) + end end diff --git a/core/matchdata/shared/captures.rb b/core/matchdata/shared/captures.rb deleted file mode 100644 index de5870f54..000000000 --- a/core/matchdata/shared/captures.rb +++ /dev/null @@ -1,13 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' - -describe :matchdata_captures, shared: true do - it "returns an array of the match captures" do - /(.)(.)(\d+)(\d)/.match("THX1138.").send(@method).should == ["H","X","113","8"] - end - - it "returns instances of String when given a String subclass" do - str = MatchDataSpecs::MyString.new("THX1138: The Movie") - /(.)(.)(\d+)(\d)/.match(str).send(@method).each { |c| c.should.instance_of?(String) } - end -end diff --git a/core/matchdata/shared/eql.rb b/core/matchdata/shared/eql.rb deleted file mode 100644 index e4bb8797b..000000000 --- a/core/matchdata/shared/eql.rb +++ /dev/null @@ -1,26 +0,0 @@ -require_relative '../../../spec_helper' - -describe :matchdata_eql, shared: true do - it "returns true if both operands have equal target strings, patterns, and match positions" do - a = 'haystack'.match(/hay/) - b = 'haystack'.match(/hay/) - a.send(@method, b).should == true - end - - it "returns false if the operands have different target strings" do - a = 'hay'.match(/hay/) - b = 'haystack'.match(/hay/) - a.send(@method, b).should == false - end - - it "returns false if the operands have different patterns" do - a = 'haystack'.match(/h.y/) - b = 'haystack'.match(/hay/) - a.send(@method, b).should == false - end - - it "returns false if the argument is not a MatchData object" do - a = 'haystack'.match(/hay/) - a.send(@method, Object.new).should == false - end -end diff --git a/core/matchdata/shared/length.rb b/core/matchdata/shared/length.rb deleted file mode 100644 index 6312a7ed4..000000000 --- a/core/matchdata/shared/length.rb +++ /dev/null @@ -1,5 +0,0 @@ -describe :matchdata_length, shared: true do - it "length should return the number of elements in the match array" do - /(.)(.)(\d+)(\d)/.match("THX1138.").send(@method).should == 5 - end -end diff --git a/core/matchdata/size_spec.rb b/core/matchdata/size_spec.rb index b4965db3b..f93ded72c 100644 --- a/core/matchdata/size_spec.rb +++ b/core/matchdata/size_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/length' describe "MatchData#size" do - it_behaves_like :matchdata_length, :size + it "should return the number of elements in the match array" do + /(.)(.)(\d+)(\d)/.match("THX1138.").size.should == 5 + end end diff --git a/core/method/call_spec.rb b/core/method/call_spec.rb index 6d997325f..cb11545e9 100644 --- a/core/method/call_spec.rb +++ b/core/method/call_spec.rb @@ -1,7 +1,54 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/call' describe "Method#call" do - it_behaves_like :method_call, :call + it "invokes the method with the specified arguments, returning the method's return value" do + m = 12.method("+") + m.call(3).should == 15 + m.call(20).should == 32 + + m = MethodSpecs::Methods.new.method(:attr=) + m.call(42).should == 42 + end + + it "raises an ArgumentError when given incorrect number of arguments" do + -> { + MethodSpecs::Methods.new.method(:two_req).call(1, 2, 3) + }.should.raise(ArgumentError) + -> { + MethodSpecs::Methods.new.method(:two_req).call(1) + }.should.raise(ArgumentError) + end + + describe "for a Method generated by respond_to_missing?" do + it "invokes method_missing with the specified arguments and returns the result" do + @m = MethodSpecs::Methods.new + meth = @m.method(:handled_via_method_missing) + meth.call(:argument).should == [:argument] + end + + it "invokes method_missing with the method name and the specified arguments" do + @m = MethodSpecs::Methods.new + meth = @m.method(:handled_via_method_missing) + + @m.should_receive(:method_missing).with(:handled_via_method_missing, :argument) + meth.call(:argument) + end + + it "invokes method_missing dynamically" do + @m = MethodSpecs::Methods.new + meth = @m.method(:handled_via_method_missing) + + def @m.method_missing(*); :changed; end + meth.call(:argument).should == :changed + end + + it "does not call the original method name even if it now exists" do + @m = MethodSpecs::Methods.new + meth = @m.method(:handled_via_method_missing) + + def @m.handled_via_method_missing(*); :not_called; end + meth.call(:argument).should == [:argument] + end + end end diff --git a/core/method/case_compare_spec.rb b/core/method/case_compare_spec.rb index a78953e8a..771fea1ce 100644 --- a/core/method/case_compare_spec.rb +++ b/core/method/case_compare_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/call' describe "Method#===" do - it_behaves_like :method_call, :=== + it "is an alias of Method#call" do + Method.instance_method(:===).should == Method.instance_method(:call) + end end diff --git a/core/method/element_reference_spec.rb b/core/method/element_reference_spec.rb index aa6c54d1c..65c13cf32 100644 --- a/core/method/element_reference_spec.rb +++ b/core/method/element_reference_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/call' describe "Method#[]" do - it_behaves_like :method_call, :[] + it "is an alias of Method#call" do + Method.instance_method(:[]).should == Method.instance_method(:call) + end end diff --git a/core/method/eql_spec.rb b/core/method/eql_spec.rb index b97c9e4db..81fd086bd 100644 --- a/core/method/eql_spec.rb +++ b/core/method/eql_spec.rb @@ -1,6 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/eql' describe "Method#eql?" do - it_behaves_like :method_equal, :eql? + it "is an alias of Method#==" do + Method.instance_method(:eql?).should == Method.instance_method(:==) + end end diff --git a/core/method/equal_value_spec.rb b/core/method/equal_value_spec.rb index 0431d0c5f..ca9ef9f10 100644 --- a/core/method/equal_value_spec.rb +++ b/core/method/equal_value_spec.rb @@ -1,6 +1,94 @@ require_relative '../../spec_helper' -require_relative 'shared/eql' +require_relative 'fixtures/classes' describe "Method#==" do - it_behaves_like :method_equal, :== + before :each do + @m = MethodSpecs::Methods.new + @m_foo = @m.method(:foo) + @m2 = MethodSpecs::Methods.new + @a = MethodSpecs::A.new + end + + it "returns true if methods are the same" do + m2 = @m.method(:foo) + + (@m_foo == @m_foo).should == true + (@m_foo == m2).should == true + end + + it "returns true on aliased methods" do + m_bar = @m.method(:bar) + + (m_bar == @m_foo).should == true + end + + it "returns true if the two core methods are aliases" do + s = "hello" + a = s.method(:size) + b = s.method(:length) + (a == b).should == true + end + + it "returns false on a method which is neither aliased nor the same method" do + m2 = @m.method(:zero) + + (@m_foo == m2).should == false + end + + it "returns false for a method which is not bound to the same object" do + m2_foo = @m2.method(:foo) + a_baz = @a.method(:baz) + + (@m_foo == m2_foo).should == false + (@m_foo == a_baz).should == false + end + + it "returns false if the two methods are bound to the same object but were defined independently" do + m2 = @m.method(:same_as_foo) + (@m_foo == m2).should == false + end + + it "returns true if a method was defined using the other one" do + MethodSpecs::Methods.send :define_method, :defined_foo, MethodSpecs::Methods.instance_method(:foo) + m2 = @m.method(:defined_foo) + (@m_foo == m2).should == true + end + + it "returns false if comparing a method defined via define_method and def" do + defn = @m.method(:zero) + defined = @m.method(:zero_defined_method) + + (defn == defined).should == false + (defined == defn).should == false + end + + describe 'missing methods' do + it "returns true for the same method missing" do + miss1 = @m.method(:handled_via_method_missing) + miss1bis = @m.method(:handled_via_method_missing) + miss2 = @m.method(:also_handled) + + (miss1 == miss1bis).should == true + (miss1 == miss2).should == false + end + + it 'calls respond_to_missing? with true to include private methods' do + @m.should_receive(:respond_to_missing?).with(:some_missing_method, true).and_return(true) + @m.method(:some_missing_method) + end + end + + it "returns false if the two methods are bound to different objects, have the same names, and identical bodies" do + a = MethodSpecs::Eql.instance_method(:same_body) + b = MethodSpecs::Eql2.instance_method(:same_body) + (a == b).should == false + end + + it "returns false if the argument is not a Method object" do + (String.instance_method(:size) == 7).should == false + end + + it "returns false if the argument is an unbound version of self" do + (method(:load) == method(:load).unbind).should == false + end end diff --git a/core/method/inspect_spec.rb b/core/method/inspect_spec.rb index 97ff2d8c1..5eb8850ff 100644 --- a/core/method/inspect_spec.rb +++ b/core/method/inspect_spec.rb @@ -1,8 +1,7 @@ require_relative '../../spec_helper' -require_relative 'shared/to_s' -require_relative 'shared/aliased_inspect' describe "Method#inspect" do - it_behaves_like :method_to_s, :inspect - it_behaves_like :method_to_s_aliased, :inspect, -> meth { meth } + it "is an alias of Method#to_s" do + Method.instance_method(:inspect).should == Method.instance_method(:to_s) + end end diff --git a/core/method/shared/call.rb b/core/method/shared/call.rb deleted file mode 100644 index 41ee2b06c..000000000 --- a/core/method/shared/call.rb +++ /dev/null @@ -1,51 +0,0 @@ -describe :method_call, shared: true do - it "invokes the method with the specified arguments, returning the method's return value" do - m = 12.method("+") - m.send(@method, 3).should == 15 - m.send(@method, 20).should == 32 - - m = MethodSpecs::Methods.new.method(:attr=) - m.send(@method, 42).should == 42 - end - - it "raises an ArgumentError when given incorrect number of arguments" do - -> { - MethodSpecs::Methods.new.method(:two_req).send(@method, 1, 2, 3) - }.should.raise(ArgumentError) - -> { - MethodSpecs::Methods.new.method(:two_req).send(@method, 1) - }.should.raise(ArgumentError) - end - - describe "for a Method generated by respond_to_missing?" do - it "invokes method_missing with the specified arguments and returns the result" do - @m = MethodSpecs::Methods.new - meth = @m.method(:handled_via_method_missing) - meth.send(@method, :argument).should == [:argument] - end - - it "invokes method_missing with the method name and the specified arguments" do - @m = MethodSpecs::Methods.new - meth = @m.method(:handled_via_method_missing) - - @m.should_receive(:method_missing).with(:handled_via_method_missing, :argument) - meth.send(@method, :argument) - end - - it "invokes method_missing dynamically" do - @m = MethodSpecs::Methods.new - meth = @m.method(:handled_via_method_missing) - - def @m.method_missing(*); :changed; end - meth.send(@method, :argument).should == :changed - end - - it "does not call the original method name even if it now exists" do - @m = MethodSpecs::Methods.new - meth = @m.method(:handled_via_method_missing) - - def @m.handled_via_method_missing(*); :not_called; end - meth.send(@method, :argument).should == [:argument] - end - end -end diff --git a/core/method/shared/eql.rb b/core/method/shared/eql.rb deleted file mode 100644 index 3c340202b..000000000 --- a/core/method/shared/eql.rb +++ /dev/null @@ -1,94 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' - -describe :method_equal, shared: true do - before :each do - @m = MethodSpecs::Methods.new - @m_foo = @m.method(:foo) - @m2 = MethodSpecs::Methods.new - @a = MethodSpecs::A.new - end - - it "returns true if methods are the same" do - m2 = @m.method(:foo) - - @m_foo.send(@method, @m_foo).should == true - @m_foo.send(@method, m2).should == true - end - - it "returns true on aliased methods" do - m_bar = @m.method(:bar) - - m_bar.send(@method, @m_foo).should == true - end - - it "returns true if the two core methods are aliases" do - s = "hello" - a = s.method(:size) - b = s.method(:length) - a.send(@method, b).should == true - end - - it "returns false on a method which is neither aliased nor the same method" do - m2 = @m.method(:zero) - - @m_foo.send(@method, m2).should == false - end - - it "returns false for a method which is not bound to the same object" do - m2_foo = @m2.method(:foo) - a_baz = @a.method(:baz) - - @m_foo.send(@method, m2_foo).should == false - @m_foo.send(@method, a_baz).should == false - end - - it "returns false if the two methods are bound to the same object but were defined independently" do - m2 = @m.method(:same_as_foo) - @m_foo.send(@method, m2).should == false - end - - it "returns true if a method was defined using the other one" do - MethodSpecs::Methods.send :define_method, :defined_foo, MethodSpecs::Methods.instance_method(:foo) - m2 = @m.method(:defined_foo) - @m_foo.send(@method, m2).should == true - end - - it "returns false if comparing a method defined via define_method and def" do - defn = @m.method(:zero) - defined = @m.method(:zero_defined_method) - - defn.send(@method, defined).should == false - defined.send(@method, defn).should == false - end - - describe 'missing methods' do - it "returns true for the same method missing" do - miss1 = @m.method(:handled_via_method_missing) - miss1bis = @m.method(:handled_via_method_missing) - miss2 = @m.method(:also_handled) - - miss1.send(@method, miss1bis).should == true - miss1.send(@method, miss2).should == false - end - - it 'calls respond_to_missing? with true to include private methods' do - @m.should_receive(:respond_to_missing?).with(:some_missing_method, true).and_return(true) - @m.method(:some_missing_method) - end - end - - it "returns false if the two methods are bound to different objects, have the same names, and identical bodies" do - a = MethodSpecs::Eql.instance_method(:same_body) - b = MethodSpecs::Eql2.instance_method(:same_body) - a.send(@method, b).should == false - end - - it "returns false if the argument is not a Method object" do - String.instance_method(:size).send(@method, 7).should == false - end - - it "returns false if the argument is an unbound version of self" do - method(:load).send(@method, method(:load).unbind).should == false - end -end diff --git a/core/module/class_eval_spec.rb b/core/module/class_eval_spec.rb index c6665d5af..0c190ceaf 100644 --- a/core/module/class_eval_spec.rb +++ b/core/module/class_eval_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/class_eval' describe "Module#class_eval" do - it_behaves_like :module_class_eval, :class_eval + it "is an alias of Module#module_eval" do + Module.instance_method(:class_eval).should == Module.instance_method(:module_eval) + end end diff --git a/core/module/class_exec_spec.rb b/core/module/class_exec_spec.rb index 4acd0169a..d47a6ba98 100644 --- a/core/module/class_exec_spec.rb +++ b/core/module/class_exec_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' -require_relative 'shared/class_exec' describe "Module#class_exec" do - it_behaves_like :module_class_exec, :class_exec + it "is an alias of Module#module_exec" do + Module.instance_method(:class_exec).should == Module.instance_method(:module_exec) + end end diff --git a/core/module/module_eval_spec.rb b/core/module/module_eval_spec.rb index e9e9fda28..bcd51ca19 100644 --- a/core/module/module_eval_spec.rb +++ b/core/module/module_eval_spec.rb @@ -1,7 +1,175 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/class_eval' describe "Module#module_eval" do - it_behaves_like :module_class_eval, :module_eval + # TODO: This should probably be replaced with a "should behave like" that uses + # the many scoping/binding specs from kernel/eval_spec, since most of those + # behaviors are the same for instance_eval. See also module_eval/class_eval. + + it "evaluates a given string in the context of self" do + ModuleSpecs.module_eval("self").should == ModuleSpecs + ModuleSpecs.module_eval("1 + 1").should == 2 + end + + it "does not add defined methods to other classes" do + FalseClass.module_eval do + def foo + 'foo' + end + end + -> {42.foo}.should.raise(NoMethodError) + end + + it "resolves constants in the caller scope" do + ModuleSpecs::ClassEvalTest.get_constant_from_scope.should == ModuleSpecs::Lookup + end + + it "resolves constants in the caller scope ignoring send" do + ModuleSpecs::ClassEvalTest.get_constant_from_scope_with_send(:module_eval).should == ModuleSpecs::Lookup + end + + it "resolves constants in the receiver's scope" do + ModuleSpecs.module_eval("Lookup").should == ModuleSpecs::Lookup + ModuleSpecs.module_eval("Lookup::LOOKIE").should == ModuleSpecs::Lookup::LOOKIE + end + + it "defines constants in the receiver's scope" do + ModuleSpecs.module_eval("module NewEvaluatedModule;end") + ModuleSpecs.const_defined?(:NewEvaluatedModule, false).should == true + end + + it "evaluates a given block in the context of self" do + ModuleSpecs.module_eval { self }.should == ModuleSpecs + ModuleSpecs.module_eval { 1 + 1 }.should == 2 + end + + it "passes the module as the first argument of the block" do + given = nil + ModuleSpecs.module_eval do |block_parameter| + given = block_parameter + end + given.should.equal? ModuleSpecs + end + + it "uses the optional filename and lineno parameters for error messages" do + ModuleSpecs.module_eval("[__FILE__, __LINE__]", "test", 102).should == ["test", 102] + end + + it "uses the caller location as default filename" do + ModuleSpecs.module_eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] + end + + it "converts a non-string filename to a string using to_str" do + (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) + ModuleSpecs.module_eval("1+1", file) + + (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) + ModuleSpecs.module_eval("1+1", file, 15) + end + + it "raises a TypeError when the given filename can't be converted to string using to_str" do + (file = mock('123')).should_receive(:to_str).and_return(123) + -> { ModuleSpecs.module_eval("1+1", file) }.should raise_consistent_error(TypeError, /can't convert MockObject into String/) + end + + it "converts non string eval-string to string using to_str" do + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") + ModuleSpecs.module_eval(o).should == 2 + + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") + ModuleSpecs.module_eval(o, "file.rb").should == 2 + + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") + ModuleSpecs.module_eval(o, "file.rb", 15).should == 2 + end + + it "raises a TypeError when the given eval-string can't be converted to string using to_str" do + o = mock('x') + -> { ModuleSpecs.module_eval(o) }.should.raise(TypeError, "no implicit conversion of MockObject into String") + + (o = mock('123')).should_receive(:to_str).and_return(123) + -> { ModuleSpecs.module_eval(o) }.should raise_consistent_error(TypeError, /can't convert MockObject into String/) + end + + it "raises an ArgumentError when no arguments and no block are given" do + -> { ModuleSpecs.module_eval }.should.raise(ArgumentError, "wrong number of arguments (given 0, expected 1..3)") + end + + it "raises an ArgumentError when more than 3 arguments are given" do + -> { + ModuleSpecs.module_eval("1 + 1", "some file", 0, "bogus") + }.should.raise(ArgumentError, "wrong number of arguments (given 4, expected 1..3)") + end + + it "raises an ArgumentError when a block and normal arguments are given" do + -> { + ModuleSpecs.module_eval("1 + 1") { 1 + 1 } + }.should.raise(ArgumentError, "wrong number of arguments (given 1, expected 0)") + end + + # This case was found because Rubinius was caching the compiled + # version of the string and not duping the methods within the + # eval, causing the method addition to change the static scope + # of the shared CompiledCode. + it "adds methods respecting the lexical constant scope" do + code = "def self.attribute; C; end" + + a = Class.new do + self::C = "A" + end + + b = Class.new do + self::C = "B" + end + + a.module_eval(code) + b.module_eval(code) + + a.attribute.should == "A" + b.attribute.should == "B" + end + + it "activates refinements from the eval scope" do + refinery = Module.new do + refine ModuleSpecs::NamedClass do + def foo + "bar" + end + end + end + + mid = :module_eval + result = nil + + Class.new do + using refinery + + result = send(mid, "ModuleSpecs::NamedClass.new.foo") + end + + result.should == "bar" + end + + it "activates refinements from the eval scope with block" do + refinery = Module.new do + refine ModuleSpecs::NamedClass do + def foo + "bar" + end + end + end + + mid = :module_eval + result = nil + + Class.new do + using refinery + + result = send(mid) do + ModuleSpecs::NamedClass.new.foo + end + end + + result.should == "bar" + end end diff --git a/core/module/module_exec_spec.rb b/core/module/module_exec_spec.rb index 47cdf7ef5..6b36e8a02 100644 --- a/core/module/module_exec_spec.rb +++ b/core/module/module_exec_spec.rb @@ -1,7 +1,38 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/class_exec' describe "Module#module_exec" do - it_behaves_like :module_class_exec, :module_exec + it "does not add defined methods to other classes" do + FalseClass.module_exec do + def foo + 'foo' + end + end + -> {42.foo}.should.raise(NoMethodError) + end + + it "defines method in the receiver's scope" do + ModuleSpecs::Subclass.module_exec { def foo; end } + ModuleSpecs::Subclass.new.respond_to?(:foo).should == true + end + + it "evaluates a given block in the context of self" do + ModuleSpecs::Subclass.module_exec { self }.should == ModuleSpecs::Subclass + ModuleSpecs::Subclass.new.module_exec { 1 + 1 }.should == 2 + end + + it "raises a LocalJumpError when no block is given" do + -> { ModuleSpecs::Subclass.module_exec }.should.raise(LocalJumpError) + end + + it "passes arguments to the block" do + a = ModuleSpecs::Subclass + a.module_exec(1) { |b| b }.should.equal?(1) + end + + describe "with optional argument" do + it "does not destructure a single array argument" do + ModuleSpecs::Subclass.module_exec([1, 2, 3]) { |a = 99| a }.should == [1, 2, 3] + end + end end diff --git a/core/module/shared/class_eval.rb b/core/module/shared/class_eval.rb deleted file mode 100644 index ee2860449..000000000 --- a/core/module/shared/class_eval.rb +++ /dev/null @@ -1,172 +0,0 @@ -describe :module_class_eval, shared: true do - # TODO: This should probably be replaced with a "should behave like" that uses - # the many scoping/binding specs from kernel/eval_spec, since most of those - # behaviors are the same for instance_eval. See also module_eval/class_eval. - - it "evaluates a given string in the context of self" do - ModuleSpecs.send(@method, "self").should == ModuleSpecs - ModuleSpecs.send(@method, "1 + 1").should == 2 - end - - it "does not add defined methods to other classes" do - FalseClass.send(@method) do - def foo - 'foo' - end - end - -> {42.foo}.should.raise(NoMethodError) - end - - it "resolves constants in the caller scope" do - ModuleSpecs::ClassEvalTest.get_constant_from_scope.should == ModuleSpecs::Lookup - end - - it "resolves constants in the caller scope ignoring send" do - ModuleSpecs::ClassEvalTest.get_constant_from_scope_with_send(@method).should == ModuleSpecs::Lookup - end - - it "resolves constants in the receiver's scope" do - ModuleSpecs.send(@method, "Lookup").should == ModuleSpecs::Lookup - ModuleSpecs.send(@method, "Lookup::LOOKIE").should == ModuleSpecs::Lookup::LOOKIE - end - - it "defines constants in the receiver's scope" do - ModuleSpecs.send(@method, "module NewEvaluatedModule;end") - ModuleSpecs.const_defined?(:NewEvaluatedModule, false).should == true - end - - it "evaluates a given block in the context of self" do - ModuleSpecs.send(@method) { self }.should == ModuleSpecs - ModuleSpecs.send(@method) { 1 + 1 }.should == 2 - end - - it "passes the module as the first argument of the block" do - given = nil - ModuleSpecs.send(@method) do |block_parameter| - given = block_parameter - end - given.should.equal? ModuleSpecs - end - - it "uses the optional filename and lineno parameters for error messages" do - ModuleSpecs.send(@method, "[__FILE__, __LINE__]", "test", 102).should == ["test", 102] - end - - it "uses the caller location as default filename" do - ModuleSpecs.send(@method, "[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] - end - - it "converts a non-string filename to a string using to_str" do - (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) - ModuleSpecs.send(@method, "1+1", file) - - (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) - ModuleSpecs.send(@method, "1+1", file, 15) - end - - it "raises a TypeError when the given filename can't be converted to string using to_str" do - (file = mock('123')).should_receive(:to_str).and_return(123) - -> { ModuleSpecs.send(@method, "1+1", file) }.should raise_consistent_error(TypeError, /can't convert MockObject into String/) - end - - it "converts non string eval-string to string using to_str" do - (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") - ModuleSpecs.send(@method, o).should == 2 - - (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") - ModuleSpecs.send(@method, o, "file.rb").should == 2 - - (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") - ModuleSpecs.send(@method, o, "file.rb", 15).should == 2 - end - - it "raises a TypeError when the given eval-string can't be converted to string using to_str" do - o = mock('x') - -> { ModuleSpecs.send(@method, o) }.should.raise(TypeError, "no implicit conversion of MockObject into String") - - (o = mock('123')).should_receive(:to_str).and_return(123) - -> { ModuleSpecs.send(@method, o) }.should raise_consistent_error(TypeError, /can't convert MockObject into String/) - end - - it "raises an ArgumentError when no arguments and no block are given" do - -> { ModuleSpecs.send(@method) }.should.raise(ArgumentError, "wrong number of arguments (given 0, expected 1..3)") - end - - it "raises an ArgumentError when more than 3 arguments are given" do - -> { - ModuleSpecs.send(@method, "1 + 1", "some file", 0, "bogus") - }.should.raise(ArgumentError, "wrong number of arguments (given 4, expected 1..3)") - end - - it "raises an ArgumentError when a block and normal arguments are given" do - -> { - ModuleSpecs.send(@method, "1 + 1") { 1 + 1 } - }.should.raise(ArgumentError, "wrong number of arguments (given 1, expected 0)") - end - - # This case was found because Rubinius was caching the compiled - # version of the string and not duping the methods within the - # eval, causing the method addition to change the static scope - # of the shared CompiledCode. - it "adds methods respecting the lexical constant scope" do - code = "def self.attribute; C; end" - - a = Class.new do - self::C = "A" - end - - b = Class.new do - self::C = "B" - end - - a.send @method, code - b.send @method, code - - a.attribute.should == "A" - b.attribute.should == "B" - end - - it "activates refinements from the eval scope" do - refinery = Module.new do - refine ModuleSpecs::NamedClass do - def foo - "bar" - end - end - end - - mid = @method - result = nil - - Class.new do - using refinery - - result = send(mid, "ModuleSpecs::NamedClass.new.foo") - end - - result.should == "bar" - end - - it "activates refinements from the eval scope with block" do - refinery = Module.new do - refine ModuleSpecs::NamedClass do - def foo - "bar" - end - end - end - - mid = @method - result = nil - - Class.new do - using refinery - - result = send(mid) do - ModuleSpecs::NamedClass.new.foo - end - end - - result.should == "bar" - end -end diff --git a/core/module/shared/class_exec.rb b/core/module/shared/class_exec.rb deleted file mode 100644 index e51af1966..000000000 --- a/core/module/shared/class_exec.rb +++ /dev/null @@ -1,35 +0,0 @@ -describe :module_class_exec, shared: true do - it "does not add defined methods to other classes" do - FalseClass.send(@method) do - def foo - 'foo' - end - end - -> {42.foo}.should.raise(NoMethodError) - end - - it "defines method in the receiver's scope" do - ModuleSpecs::Subclass.send(@method) { def foo; end } - ModuleSpecs::Subclass.new.respond_to?(:foo).should == true - end - - it "evaluates a given block in the context of self" do - ModuleSpecs::Subclass.send(@method) { self }.should == ModuleSpecs::Subclass - ModuleSpecs::Subclass.new.send(@method) { 1 + 1 }.should == 2 - end - - it "raises a LocalJumpError when no block is given" do - -> { ModuleSpecs::Subclass.send(@method) }.should.raise(LocalJumpError) - end - - it "passes arguments to the block" do - a = ModuleSpecs::Subclass - a.send(@method, 1) { |b| b }.should.equal?(1) - end - - describe "with optional argument" do - it "does not destructure a single array argument" do - ModuleSpecs::Subclass.send(@method, [1, 2, 3]) { |a = 99| a }.should == [1, 2, 3] - end - end -end