spec/calculator_spec.rb in dentaku-3.2.0 vs spec/calculator_spec.rb in dentaku-3.2.1

- old
+ new

@@ -1,8 +1,7 @@ require 'spec_helper' require 'dentaku' - describe Dentaku::Calculator do let(:calculator) { described_class.new } let(:with_memory) { described_class.new.store(apples: 3) } let(:with_aliases) { described_class.new(aliases: { round: ['rrround'] }) } let(:without_nested_data) { described_class.new(nested_data_support: false) } @@ -37,12 +36,76 @@ expect(calculator.evaluate('x * y', x: '.123', y: '100')).to eq(12.3) expect(calculator.evaluate('a/b', a: '10', b: '2')).to eq(5) expect(calculator.evaluate('t + 1*24*60*60', t: Time.local(2017, 1, 1))).to eq(Time.local(2017, 1, 2)) expect(calculator.evaluate("2 | 3 * 9")).to eq (27) expect(calculator.evaluate("2 & 3 * 9")).to eq (2) + expect(calculator.evaluate("5%")).to eq (0.05) end + describe 'evaluate' do + it 'returns nil when formula has error' do + expect(calculator.evaluate('1 + + 1')).to be_nil + end + + it 'suppresses unbound variable errors' do + expect(calculator.evaluate('AND(a,b)')).to be_nil + expect(calculator.evaluate('IF(a, 1, 0)')).to be_nil + expect(calculator.evaluate('MAX(a,b)')).to be_nil + expect(calculator.evaluate('MIN(a,b)')).to be_nil + expect(calculator.evaluate('NOT(a)')).to be_nil + expect(calculator.evaluate('OR(a,b)')).to be_nil + expect(calculator.evaluate('ROUND(a)')).to be_nil + expect(calculator.evaluate('ROUNDDOWN(a)')).to be_nil + expect(calculator.evaluate('ROUNDUP(a)')).to be_nil + expect(calculator.evaluate('SUM(a,b)')).to be_nil + end + + it 'suppresses numeric coercion errors' do + expect(calculator.evaluate('MAX(a,b)', a: nil, b: nil)).to be_nil + expect(calculator.evaluate('MIN(a,b)', a: nil, b: nil)).to be_nil + expect(calculator.evaluate('ROUND(a)', a: nil)).to be_nil + expect(calculator.evaluate('ROUNDDOWN(a)', a: nil)).to be_nil + expect(calculator.evaluate('ROUNDUP(a)', a: nil)).to be_nil + expect(calculator.evaluate('SUM(a,b)', a: nil, b: nil)).to be_nil + end + + it 'treats explicit nil as logical false' do + expect(calculator.evaluate('AND(a,b)', a: nil, b: nil)).to be_falsy + expect(calculator.evaluate('IF(a,1,0)', a: nil, b: nil)).to eq(0) + expect(calculator.evaluate('NOT(a)', a: nil, b: nil)).to be_truthy + expect(calculator.evaluate('OR(a,b)', a: nil, b: nil)).to be_falsy + end + end + + describe 'evaluate!' do + it 'raises exception when formula has error' do + expect { calculator.evaluate!('1 + + 1') }.to raise_error(Dentaku::ParseError) + end + + it 'raises unbound variable errors' do + expect { calculator.evaluate!('AND(a,b)') }.to raise_error(Dentaku::UnboundVariableError) + expect { calculator.evaluate!('IF(a, 1, 0)') }.to raise_error(Dentaku::UnboundVariableError) + expect { calculator.evaluate!('MAX(a,b)') }.to raise_error(Dentaku::UnboundVariableError) + expect { calculator.evaluate!('MIN(a,b)') }.to raise_error(Dentaku::UnboundVariableError) + expect { calculator.evaluate!('NOT(a)') }.to raise_error(Dentaku::UnboundVariableError) + expect { calculator.evaluate!('OR(a,b)') }.to raise_error(Dentaku::UnboundVariableError) + expect { calculator.evaluate!('ROUND(a)') }.to raise_error(Dentaku::UnboundVariableError) + expect { calculator.evaluate!('ROUNDDOWN(a)') }.to raise_error(Dentaku::UnboundVariableError) + expect { calculator.evaluate!('ROUNDUP(a)') }.to raise_error(Dentaku::UnboundVariableError) + expect { calculator.evaluate!('SUM(a,b)') }.to raise_error(Dentaku::UnboundVariableError) + end + + it 'raises numeric coersion errors' do + expect { calculator.evaluate!('MAX(a,b)', a: nil, b: nil) }.to raise_error(Dentaku::ArgumentError) + expect { calculator.evaluate!('MIN(a,b)', a: nil, b: nil) }.to raise_error(Dentaku::ArgumentError) + expect { calculator.evaluate!('ROUND(a)', a: nil) }.to raise_error(Dentaku::ArgumentError) + expect { calculator.evaluate!('ROUNDDOWN(a)', a: nil) }.to raise_error(Dentaku::ArgumentError) + expect { calculator.evaluate!('ROUNDUP(a)', a: nil) }.to raise_error(Dentaku::ArgumentError) + expect { calculator.evaluate!('SUM(a,b)', a: nil, b: nil) }.to raise_error(Dentaku::ArgumentError) + end + end + it 'supports unicode characters in identifiers' do expect(calculator.evaluate("ρ * 2", ρ: 2)).to eq (4) end describe 'memory' do @@ -82,11 +145,11 @@ expect(calculator.evaluate!('a[0]')).to eq 1 expect(calculator.evaluate!('a[x]', x: 1)).to eq 2 expect(calculator.evaluate!('a[x+1]', x: 1)).to eq 3 end - it 'evalutates arrays' do + it 'evaluates arrays' do expect(calculator.evaluate([1, 2, 3])).to eq([1, 2, 3]) end end describe 'dependencies' do @@ -158,10 +221,19 @@ width: "length * 2", ) expect(result[:weight]).to eq 130.368 end + + it 'raises an exception if there are cyclic dependencies' do + expect { + calculator.solve!( + make_money: "have_money", + have_money: "make_money" + ) + }.to raise_error(TSort::Cyclic) + end end describe 'solve' do it "returns :undefined when variables are unbound" do expressions = {more_apples: "apples + 1"} @@ -195,9 +267,22 @@ expect(result).to eq( conditional: 0, ratio: :undefined, d: 0, ) + end + + it 'returns undefined if there are cyclic dependencies' do + expect { + result = calculator.solve( + make_money: "have_money", + have_money: "make_money" + ) + expect(result).to eq( + make_money: :undefined, + have_money: :undefined + ) + }.not_to raise_error end end it 'evaluates a statement with no variables' do expect(calculator.evaluate('5+3')).to eq(8)