require 'test_helper' require 'memoist' class MemoistTest < Test::Unit::TestCase class CallCounter def initialize @calls = {} end def call(method_name) @calls[method_name] ||= 0 @calls[method_name] += 1 end def count(method_name) @calls[method_name] ||= 0 end end class Person extend Memoist def initialize @counter = CallCounter.new end def name_calls @counter.count(:name) end def student_name_calls @counter.count(:student_name) end def name_query_calls @counter.count(:name?) end def is_developer_calls @counter.count(:is_developer?) end def age_calls @counter.count(:age) end def name @counter.call(:name) "Josh" end def name? @counter.call(:name?) true end memoize :name? def update(name) "Joshua" end memoize :update def age @counter.call(:age) nil end memoize :name, :age protected def memoize_protected_test 'protected' end memoize :memoize_protected_test private def is_developer? @counter.call(:is_developer?) "Yes" end memoize :is_developer? end class Student < Person def name @counter.call(:student_name) "Student #{super}" end memoize :name, :identifier => :student end class Company attr_reader :name_calls def initialize @name_calls = 0 end def name @name_calls += 1 "37signals" end end module Rates extend Memoist attr_reader :sales_tax_calls def sales_tax(price) @sales_tax_calls ||= 0 @sales_tax_calls += 1 price * 0.1025 end memoize :sales_tax end class Calculator extend Memoist include Rates attr_reader :fib_calls def initialize @fib_calls = 0 end def fib(n) @fib_calls += 1 if n == 0 || n == 1 n else fib(n - 1) + fib(n - 2) end end memoize :fib def add_or_subtract(i, j, add) if add i + j else i - j end end memoize :add_or_subtract def counter @count ||= 0 @count += 1 end memoize :counter end def setup @person = Person.new @calculator = Calculator.new end def test_memoization assert_equal "Josh", @person.name assert_equal 1, @person.name_calls 3.times { assert_equal "Josh", @person.name } assert_equal 1, @person.name_calls end def test_memoization_with_punctuation assert_equal true, @person.name? assert_nothing_raised(NameError) do @person.memoize_all @person.unmemoize_all end end def test_memoization_flush_with_punctuation assert_equal true, @person.name? @person.flush_cache(:name?) 3.times { assert_equal true, @person.name? } assert_equal 2, @person.name_query_calls end def test_memoization_with_nil_value assert_equal nil, @person.age assert_equal 1, @person.age_calls 3.times { assert_equal nil, @person.age } assert_equal 1, @person.age_calls end def test_reloadable assert_equal 1, @calculator.counter assert_equal 2, @calculator.counter(:reload) assert_equal 2, @calculator.counter assert_equal 3, @calculator.counter(true) assert_equal 3, @calculator.counter end def test_flush_cache assert_equal 1, @calculator.counter assert @calculator.instance_variable_get(:@_memoized_counter) @calculator.flush_cache(:counter) assert_nil @calculator.instance_variable_get(:@_memoized_counter) assert !@calculator.instance_variable_defined?(:@_memoized_counter) assert_equal 2, @calculator.counter end def test_unmemoize_all assert_equal 1, @calculator.counter assert @calculator.instance_variable_get(:@_memoized_counter) @calculator.unmemoize_all assert_nil @calculator.instance_variable_get(:@_memoized_counter) assert !@calculator.instance_variable_defined?(:@_memoized_counter) assert_equal 2, @calculator.counter end def test_memoize_all @calculator.memoize_all assert @calculator.instance_variable_defined?(:@_memoized_counter) end def test_memoization_cache_is_different_for_each_instance assert_equal 1, @calculator.counter assert_equal 2, @calculator.counter(:reload) assert_equal 1, Calculator.new.counter end def test_memoized_is_not_affected_by_freeze @person.freeze assert_equal "Josh", @person.name assert_equal "Joshua", @person.update("Joshua") end def test_memoization_with_args assert_equal 55, @calculator.fib(10) assert_equal 11, @calculator.fib_calls end def test_reloadable_with_args assert_equal 55, @calculator.fib(10) assert_equal 11, @calculator.fib_calls assert_equal 55, @calculator.fib(10, :reload) assert_equal 12, @calculator.fib_calls assert_equal 55, @calculator.fib(10, true) assert_equal 13, @calculator.fib_calls end def test_memoization_with_boolean_arg assert_equal 4, @calculator.add_or_subtract(2, 2, true) assert_equal 2, @calculator.add_or_subtract(4, 2, false) end def test_object_memoization [Company.new, Company.new, Company.new].each do |company| company.extend Memoist company.memoize :name assert_equal "37signals", company.name assert_equal 1, company.name_calls assert_equal "37signals", company.name assert_equal 1, company.name_calls end end def test_memoized_module_methods assert_equal 1.025, @calculator.sales_tax(10) assert_equal 1, @calculator.sales_tax_calls assert_equal 1.025, @calculator.sales_tax(10) assert_equal 1, @calculator.sales_tax_calls assert_equal 2.5625, @calculator.sales_tax(25) assert_equal 2, @calculator.sales_tax_calls end def test_object_memoized_module_methods company = Company.new company.extend(Rates) assert_equal 1.025, company.sales_tax(10) assert_equal 1, company.sales_tax_calls assert_equal 1.025, company.sales_tax(10) assert_equal 1, company.sales_tax_calls assert_equal 2.5625, company.sales_tax(25) assert_equal 2, company.sales_tax_calls end def test_double_memoization assert_raise(Memoist::AlreadyMemoizedError) { Person.memoize :name } person = Person.new person.extend Memoist assert_raise(Memoist::AlreadyMemoizedError) { person.memoize :name } company = Company.new company.extend Memoist company.memoize :name assert_raise(Memoist::AlreadyMemoizedError) { company.memoize :name } end def test_double_memoization_with_identifier assert_nothing_raised { Person.memoize :name, :identifier => :again } end def test_memoization_with_a_subclass student = Student.new student.name student.name assert_equal 1, student.student_name_calls assert_equal 1, student.name_calls end def test_protected_method_memoization person = Person.new assert_raise(NoMethodError) { person.memoize_protected_test } assert_equal "protected", person.send(:memoize_protected_test) end def test_private_method_memoization person = Person.new assert_raise(NoMethodError) { person.is_developer? } assert_equal "Yes", person.send(:is_developer?) assert_equal 1, person.is_developer_calls assert_equal "Yes", person.send(:is_developer?) assert_equal 1, person.is_developer_calls end end