mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
496 lines
12 KiB
Ruby
496 lines
12 KiB
Ruby
require_relative '../spec_helper'
|
|
require_relative '../fixtures/constants'
|
|
|
|
describe 'Optional variable assignments' do
|
|
describe 'using ||=' do
|
|
describe 'using a single variable' do
|
|
it 'assigns a new variable' do
|
|
a ||= 10
|
|
|
|
a.should == 10
|
|
end
|
|
|
|
it 're-assigns an existing variable set to false' do
|
|
a = false
|
|
a ||= 10
|
|
|
|
a.should == 10
|
|
end
|
|
|
|
it 're-assigns an existing variable set to nil' do
|
|
a = nil
|
|
a ||= 10
|
|
|
|
a.should == 10
|
|
end
|
|
|
|
it 'does not re-assign a variable with a truthy value' do
|
|
a = 10
|
|
a ||= 20
|
|
|
|
a.should == 10
|
|
end
|
|
|
|
it 'does not evaluate the right side when not needed' do
|
|
a = 10
|
|
a ||= raise('should not be executed')
|
|
a.should == 10
|
|
end
|
|
|
|
it 'does not re-assign a variable with a truthy value when using an inline rescue' do
|
|
a = 10
|
|
a ||= 20 rescue 30
|
|
|
|
a.should == 10
|
|
end
|
|
|
|
it 'returns the new value if set to false' do
|
|
a = false
|
|
|
|
(a ||= 20).should == 20
|
|
end
|
|
|
|
it 'returns the original value if truthy' do
|
|
a = 10
|
|
|
|
(a ||= 20).should == 10
|
|
end
|
|
end
|
|
|
|
describe 'using a accessor' do
|
|
before do
|
|
klass = Class.new { attr_accessor :b }
|
|
@a = klass.new
|
|
end
|
|
|
|
it 'assigns a new variable' do
|
|
@a.b ||= 10
|
|
|
|
@a.b.should == 10
|
|
end
|
|
|
|
it 're-assigns an existing variable set to false' do
|
|
@a.b = false
|
|
@a.b ||= 10
|
|
|
|
@a.b.should == 10
|
|
end
|
|
|
|
it 're-assigns an existing variable set to nil' do
|
|
@a.b = nil
|
|
@a.b ||= 10
|
|
|
|
@a.b.should == 10
|
|
end
|
|
|
|
it 'does not re-assign a variable with a truthy value' do
|
|
@a.b = 10
|
|
@a.b ||= 20
|
|
|
|
@a.b.should == 10
|
|
end
|
|
|
|
it 'does not evaluate the right side when not needed' do
|
|
@a.b = 10
|
|
@a.b ||= raise('should not be executed')
|
|
@a.b.should == 10
|
|
end
|
|
|
|
it 'does not re-assign a variable with a truthy value when using an inline rescue' do
|
|
@a.b = 10
|
|
@a.b ||= 20 rescue 30
|
|
|
|
@a.b.should == 10
|
|
end
|
|
|
|
it 'returns the new value if set to false' do
|
|
def @a.b=(x)
|
|
:v
|
|
end
|
|
|
|
@a.b = false
|
|
(@a.b ||= 20).should == 20
|
|
end
|
|
|
|
it 'returns the original value if truthy' do
|
|
def @a.b=(x)
|
|
@b = x
|
|
:v
|
|
end
|
|
|
|
@a.b = 10
|
|
(@a.b ||= 20).should == 10
|
|
end
|
|
|
|
it 'works when writer is private' do
|
|
klass = Class.new do
|
|
def t
|
|
self.b = false
|
|
(self.b ||= 10).should == 10
|
|
(self.b ||= 20).should == 10
|
|
end
|
|
|
|
def b
|
|
@b
|
|
end
|
|
|
|
def b=(x)
|
|
@b = x
|
|
:v
|
|
end
|
|
|
|
private :b=
|
|
end
|
|
|
|
klass.new.t
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
describe 'using &&=' do
|
|
describe 'using a single variable' do
|
|
it 'leaves new variable unassigned' do
|
|
a &&= 10
|
|
|
|
a.should == nil
|
|
end
|
|
|
|
it 'leaves false' do
|
|
a = false
|
|
a &&= 10
|
|
|
|
a.should == false
|
|
end
|
|
|
|
it 'leaves nil' do
|
|
a = nil
|
|
a &&= 10
|
|
|
|
a.should == nil
|
|
end
|
|
|
|
it 'does not evaluate the right side when not needed' do
|
|
a = nil
|
|
a &&= raise('should not be executed')
|
|
a.should == nil
|
|
end
|
|
|
|
it 'does re-assign a variable with a truthy value' do
|
|
a = 10
|
|
a &&= 20
|
|
|
|
a.should == 20
|
|
end
|
|
|
|
it 'does re-assign a variable with a truthy value when using an inline rescue' do
|
|
a = 10
|
|
a &&= 20 rescue 30
|
|
|
|
a.should == 20
|
|
end
|
|
end
|
|
|
|
describe 'using a single variable' do
|
|
before do
|
|
klass = Class.new { attr_accessor :b }
|
|
@a = klass.new
|
|
end
|
|
|
|
it 'leaves new variable unassigned' do
|
|
@a.b &&= 10
|
|
|
|
@a.b.should == nil
|
|
end
|
|
|
|
it 'leaves false' do
|
|
@a.b = false
|
|
@a.b &&= 10
|
|
|
|
@a.b.should == false
|
|
end
|
|
|
|
it 'leaves nil' do
|
|
@a.b = nil
|
|
@a.b &&= 10
|
|
|
|
@a.b.should == nil
|
|
end
|
|
|
|
it 'does not evaluate the right side when not needed' do
|
|
@a.b = nil
|
|
@a.b &&= raise('should not be executed')
|
|
@a.b.should == nil
|
|
end
|
|
|
|
it 'does re-assign a variable with a truthy value' do
|
|
@a.b = 10
|
|
@a.b &&= 20
|
|
|
|
@a.b.should == 20
|
|
end
|
|
|
|
it 'does re-assign a variable with a truthy value when using an inline rescue' do
|
|
@a.b = 10
|
|
@a.b &&= 20 rescue 30
|
|
|
|
@a.b.should == 20
|
|
end
|
|
end
|
|
|
|
describe 'using a #[]' do
|
|
before do
|
|
@a = {}
|
|
klass = Class.new do
|
|
def [](k)
|
|
@hash ||= {}
|
|
@hash[k]
|
|
end
|
|
|
|
def []=(k, v)
|
|
@hash ||= {}
|
|
@hash[k] = v
|
|
7
|
|
end
|
|
end
|
|
@b = klass.new
|
|
end
|
|
|
|
it 'leaves new variable unassigned' do
|
|
@a[:k] &&= 10
|
|
|
|
@a.key?(:k).should == false
|
|
end
|
|
|
|
it 'leaves false' do
|
|
@a[:k] = false
|
|
@a[:k] &&= 10
|
|
|
|
@a[:k].should == false
|
|
end
|
|
|
|
it 'leaves nil' do
|
|
@a[:k] = nil
|
|
@a[:k] &&= 10
|
|
|
|
@a[:k].should == nil
|
|
end
|
|
|
|
it 'does not evaluate the right side when not needed' do
|
|
@a[:k] = nil
|
|
@a[:k] &&= raise('should not be executed')
|
|
@a[:k].should == nil
|
|
end
|
|
|
|
it 'does re-assign a variable with a truthy value' do
|
|
@a[:k] = 10
|
|
@a[:k] &&= 20
|
|
|
|
@a[:k].should == 20
|
|
end
|
|
|
|
it 'does re-assign a variable with a truthy value when using an inline rescue' do
|
|
@a[:k] = 10
|
|
@a[:k] &&= 20 rescue 30
|
|
|
|
@a[:k].should == 20
|
|
end
|
|
|
|
it 'returns the assigned value, not the result of the []= method with ||=' do
|
|
(@b[:k] ||= 12).should == 12
|
|
end
|
|
|
|
it 'correctly handles a splatted argument for the index' do
|
|
(@b[*[:k]] ||= 12).should == 12
|
|
end
|
|
|
|
it "evaluates the index precisely once" do
|
|
ary = [:x, :y]
|
|
@a[:x] = 15
|
|
@a[ary.pop] ||= 25
|
|
ary.should == [:x]
|
|
@a.should == { x: 15, y: 25 }
|
|
end
|
|
|
|
it "evaluates the index arguments in the correct order" do
|
|
ary = Class.new(Array) do
|
|
def [](x, y)
|
|
super(x + 3 * y)
|
|
end
|
|
|
|
def []=(x, y, value)
|
|
super(x + 3 * y, value)
|
|
end
|
|
end.new
|
|
ary[0, 0] = 1
|
|
ary[1, 0] = 1
|
|
ary[2, 0] = nil
|
|
ary[3, 0] = 1
|
|
ary[4, 0] = 1
|
|
ary[5, 0] = 1
|
|
ary[6, 0] = nil
|
|
|
|
foo = [0, 2]
|
|
|
|
ary[foo.pop, foo.pop] ||= 2
|
|
|
|
ary[2, 0].should == 2
|
|
ary[6, 0].should == nil
|
|
end
|
|
|
|
it 'returns the assigned value, not the result of the []= method with +=' do
|
|
@b[:k] = 17
|
|
(@b[:k] += 12).should == 29
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'using compounded constants' do
|
|
before :each do
|
|
Object.send(:remove_const, :A) if defined? Object::A
|
|
end
|
|
|
|
after :each do
|
|
Object.send(:remove_const, :A) if defined? Object::A
|
|
end
|
|
|
|
it 'with ||= assignments' do
|
|
Object::A ||= 10
|
|
Object::A.should == 10
|
|
end
|
|
|
|
it 'with ||= do not reassign' do
|
|
Object::A = 20
|
|
Object::A ||= 10
|
|
Object::A.should == 20
|
|
end
|
|
|
|
it 'with &&= assignments' do
|
|
Object::A = 20
|
|
-> {
|
|
Object::A &&= 10
|
|
}.should complain(/already initialized constant/)
|
|
Object::A.should == 10
|
|
end
|
|
|
|
it 'with &&= assignments will fail with non-existent constants' do
|
|
-> { Object::A &&= 10 }.should raise_error(NameError)
|
|
end
|
|
|
|
it 'with operator assignments' do
|
|
Object::A = 20
|
|
-> {
|
|
Object::A += 10
|
|
}.should complain(/already initialized constant/)
|
|
Object::A.should == 30
|
|
end
|
|
|
|
it 'with operator assignments will fail with non-existent constants' do
|
|
-> { Object::A += 10 }.should raise_error(NameError)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'Optional constant assignment' do
|
|
describe 'with ||=' do
|
|
it "assigns a scoped constant if previously undefined" do
|
|
ConstantSpecs.should_not have_constant(:OpAssignUndefined)
|
|
module ConstantSpecs
|
|
OpAssignUndefined ||= 42
|
|
end
|
|
ConstantSpecs::OpAssignUndefined.should == 42
|
|
ConstantSpecs::OpAssignUndefinedOutside ||= 42
|
|
ConstantSpecs::OpAssignUndefinedOutside.should == 42
|
|
ConstantSpecs.send(:remove_const, :OpAssignUndefined)
|
|
ConstantSpecs.send(:remove_const, :OpAssignUndefinedOutside)
|
|
end
|
|
|
|
it "assigns a global constant if previously undefined" do
|
|
OpAssignGlobalUndefined ||= 42
|
|
::OpAssignGlobalUndefinedExplicitScope ||= 42
|
|
OpAssignGlobalUndefined.should == 42
|
|
::OpAssignGlobalUndefinedExplicitScope.should == 42
|
|
Object.send :remove_const, :OpAssignGlobalUndefined
|
|
Object.send :remove_const, :OpAssignGlobalUndefinedExplicitScope
|
|
end
|
|
|
|
it 'correctly defines non-existing constants' do
|
|
ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT1 ||= :assigned
|
|
ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT1.should == :assigned
|
|
end
|
|
|
|
it 'correctly overwrites nil constants' do
|
|
suppress_warning do # already initialized constant
|
|
ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1 = nil
|
|
ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1 ||= :assigned
|
|
ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT1.should == :assigned
|
|
end
|
|
end
|
|
|
|
it 'causes side-effects of the module part to be applied only once (for undefined constant)' do
|
|
x = 0
|
|
(x += 1; ConstantSpecs::ClassA)::OR_ASSIGNED_CONSTANT2 ||= :assigned
|
|
x.should == 1
|
|
ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT2.should == :assigned
|
|
end
|
|
|
|
it 'causes side-effects of the module part to be applied (for nil constant)' do
|
|
suppress_warning do # already initialized constant
|
|
ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT2 = nil
|
|
x = 0
|
|
(x += 1; ConstantSpecs::ClassA)::NIL_OR_ASSIGNED_CONSTANT2 ||= :assigned
|
|
x.should == 1
|
|
ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT2.should == :assigned
|
|
end
|
|
end
|
|
|
|
it 'does not evaluate the right-hand side if the module part raises an exception (for undefined constant)' do
|
|
x = 0
|
|
y = 0
|
|
|
|
-> {
|
|
(x += 1; raise Exception; ConstantSpecs::ClassA)::OR_ASSIGNED_CONSTANT3 ||= (y += 1; :assigned)
|
|
}.should raise_error(Exception)
|
|
|
|
x.should == 1
|
|
y.should == 0
|
|
defined?(ConstantSpecs::ClassA::OR_ASSIGNED_CONSTANT3).should == nil
|
|
end
|
|
|
|
it 'does not evaluate the right-hand side if the module part raises an exception (for nil constant)' do
|
|
ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT3 = nil
|
|
x = 0
|
|
y = 0
|
|
|
|
-> {
|
|
(x += 1; raise Exception; ConstantSpecs::ClassA)::NIL_OR_ASSIGNED_CONSTANT3 ||= (y += 1; :assigned)
|
|
}.should raise_error(Exception)
|
|
|
|
x.should == 1
|
|
y.should == 0
|
|
ConstantSpecs::ClassA::NIL_OR_ASSIGNED_CONSTANT3.should == nil
|
|
end
|
|
end
|
|
|
|
describe "with &&=" do
|
|
it "re-assigns a scoped constant if already true" do
|
|
module ConstantSpecs
|
|
OpAssignTrue = true
|
|
end
|
|
suppress_warning do
|
|
ConstantSpecs::OpAssignTrue &&= 1
|
|
end
|
|
ConstantSpecs::OpAssignTrue.should == 1
|
|
ConstantSpecs.send :remove_const, :OpAssignTrue
|
|
end
|
|
|
|
it "leaves scoped constant if not true" do
|
|
module ConstantSpecs
|
|
OpAssignFalse = false
|
|
end
|
|
ConstantSpecs::OpAssignFalse &&= 1
|
|
ConstantSpecs::OpAssignFalse.should == false
|
|
ConstantSpecs.send :remove_const, :OpAssignFalse
|
|
end
|
|
end
|
|
end
|