summaryrefslogtreecommitdiff
path: root/spec/ruby/core/range/minmax_spec.rb
blob: 1db9bfce38f6943aead71fb85d0ce8100ac332cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
require_relative '../../spec_helper'

# These specs use Range.new instead of the literal notation for beginless Ranges so they parse fine on Ruby < 2.7
describe 'Range#minmax' do
  before(:each) do
    @x = mock('x')
    @y = mock('y')

    @x.should_receive(:<=>).with(@y).any_number_of_times.and_return(-1) # x < y
    @x.should_receive(:<=>).with(@x).any_number_of_times.and_return(0) # x == x
    @y.should_receive(:<=>).with(@x).any_number_of_times.and_return(1) # y > x
    @y.should_receive(:<=>).with(@y).any_number_of_times.and_return(0) # y == y
  end

  describe 'on an inclusive range' do
    ruby_version_is ''...'2.7' do
      it 'should try to iterate endlessly on an endless range' do
        @x.should_receive(:succ).once.and_return(@y)
        range = (@x..)

        -> { range.minmax }.should raise_error(NoMethodError, /^undefined method `succ' for/)
      end
    end

    ruby_version_is '2.7' do
      it 'should raise RangeError on an endless range without iterating the range' do
        @x.should_not_receive(:succ)

        range = (@x..)

        -> { range.minmax }.should raise_error(RangeError, 'cannot get the maximum of endless range')
      end

      it 'raises RangeError or ArgumentError on a beginless range' do
        range = Range.new(nil, @x)

        -> { range.minmax }.should raise_error(StandardError) { |e|
          if RangeError === e
            # error from #min
            -> { raise e }.should raise_error(RangeError, 'cannot get the minimum of beginless range')
          else
            # error from #max
            -> { raise e }.should raise_error(ArgumentError, 'comparison of NilClass with MockObject failed')
          end
        }
      end
    end

    it 'should return beginning of range if beginning and end are equal without iterating the range' do
      @x.should_not_receive(:succ)

      (@x..@x).minmax.should == [@x, @x]
    end

    it 'should return nil pair if beginning is greater than end without iterating the range' do
      @y.should_not_receive(:succ)

      (@y..@x).minmax.should == [nil, nil]
    end

    ruby_version_is ''...'2.7' do
      it 'should return the minimum and maximum values for a non-numeric range by iterating the range' do
        @x.should_receive(:succ).once.and_return(@y)

        (@x..@y).minmax.should == [@x, @y]
      end
    end

    ruby_version_is '2.7' do
      it 'should return the minimum and maximum values for a non-numeric range without iterating the range' do
        @x.should_not_receive(:succ)

        (@x..@y).minmax.should == [@x, @y]
      end
    end

    it 'should return the minimum and maximum values for a numeric range' do
      (1..3).minmax.should == [1, 3]
    end

    ruby_version_is '2.7' do
      it 'should return the minimum and maximum values for a numeric range without iterating the range' do
        # We cannot set expectations on integers,
        # so we "prevent" iteration by picking a value that would iterate until the spec times out.
        range_end = Float::INFINITY

        (1..range_end).minmax.should == [1, range_end]
      end
    end

    it 'should return the minimum and maximum values according to the provided block by iterating the range' do
      @x.should_receive(:succ).once.and_return(@y)

      (@x..@y).minmax { |x, y| - (x <=> y) }.should == [@y, @x]
    end
  end

  describe 'on an exclusive range' do
    ruby_version_is ''...'2.7' do
      # Endless ranges introduced in 2.6
      it 'should try to iterate endlessly on an endless range' do
        @x.should_receive(:succ).once.and_return(@y)
        range = (@x...)

        -> { range.minmax }.should raise_error(NoMethodError, /^undefined method `succ' for/)
      end
    end

    ruby_version_is '2.7' do
      it 'should raise RangeError on an endless range' do
        @x.should_not_receive(:succ)
        range = (@x...)

        -> { range.minmax }.should raise_error(RangeError, 'cannot get the maximum of endless range')
      end

      it 'should raise RangeError on a beginless range' do
        range = Range.new(nil, @x, true)

        -> { range.minmax }.should raise_error(RangeError,
          /cannot get the maximum of beginless range with custom comparison method|cannot get the minimum of beginless range/)
      end
    end

    ruby_bug "#17014", "2.7.0"..."3.0" do
      it 'should return nil pair if beginning and end are equal without iterating the range' do
        @x.should_not_receive(:succ)

        (@x...@x).minmax.should == [nil, nil]
      end

      it 'should return nil pair if beginning is greater than end without iterating the range' do
        @y.should_not_receive(:succ)

        (@y...@x).minmax.should == [nil, nil]
      end

      it 'should return the minimum and maximum values for a non-numeric range by iterating the range' do
        @x.should_receive(:succ).once.and_return(@y)

        (@x...@y).minmax.should == [@x, @x]
      end
    end

    it 'should return the minimum and maximum values for a numeric range' do
      (1...3).minmax.should == [1, 2]
    end

    ruby_version_is '2.7' do
      it 'should return the minimum and maximum values for a numeric range without iterating the range' do
        # We cannot set expectations on integers,
        # so we "prevent" iteration by picking a value that would iterate until the spec times out.
        range_end = bignum_value

        (1...range_end).minmax.should == [1, range_end - 1]
      end

      it 'raises TypeError if the end value is not an integer' do
        range = (0...Float::INFINITY)
        -> { range.minmax }.should raise_error(TypeError, 'cannot exclude non Integer end value')
      end
    end

    it 'should return the minimum and maximum values according to the provided block by iterating the range' do
      @x.should_receive(:succ).once.and_return(@y)

      (@x...@y).minmax { |x, y| - (x <=> y) }.should == [@x, @x]
    end
  end
end