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)