# frozen_string_literal: true require 'minitest/autorun' require 'minitest/pride' require_relative '../lib/mutual_recursion' class InventoryTest < Minitest::Test include MutualRecursion def one(x, y = 0) return terminal_value(y) if x.negative? tail_call { two(x, y + 1) } end def two(x, y = 0) tail_call { one(x - 1, y) } end def direct(x, y = 0) return terminal_value(y) if x.negative? tail_call { direct(x - 1, y + 1) } end def lambda_returning terminal_value(-> { 24 }) end def proc_returning(x, y = 0) return terminal_value(proc { "|#{y}|" }) if x.negative? tail_call { proc_returning(x - 1, y + 1) } end def inauthentic_tail_call tail_call do Class.new do attr_reader :value, :block def initialize @value = 99 @block = proc { MutualRecursion.terminal_value(100) } end end.new end end def test_terminal_value tail = terminal_value(42) assert_equal(42, tail.invoke) end def test_single_recursion tail = tail_call { terminal_value(42) } assert_equal(42, tail.invoke) end def test_several_recursions tail = tail_call { tail_call { tail_call { terminal_value(42) } } } assert_equal(42, tail.invoke) end def test_mutual_tail_recursion tail_one = one(50_000) tail_two = two(50_000) assert_equal(50_001, tail_one.invoke) assert_equal(50_000, tail_two.invoke) end def test_direct_tail_recursion tail = direct(50_000) assert_equal(50_001, tail.invoke) end def test_recursive_function_can_return_lambda tail = lambda_returning assert_kind_of(Proc, tail.invoke) end def test_recursive_function_can_return_proc tail = proc_returning(20) assert_kind_of(Proc, tail.invoke) end def test_inauthentic_tail_call_detected tail = inauthentic_tail_call assert_raises(MissingTailCallError) { tail.invoke } end def test_module_functions tail = MutualRecursion.tail_call { MutualRecursion.terminal_value(42) } assert_equal(42, tail.invoke) end end