Skip to content

Commit

Permalink
Replace StackBusters with TCO assertions (mbittarelli FTW)
Browse files Browse the repository at this point in the history
  • Loading branch information
tdg5 committed Mar 18, 2015
1 parent e2e7f30 commit c296327
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 187 deletions.
4 changes: 1 addition & 3 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
require "mocha/setup"
require "tco_method"

require "test_helpers/vm_stack_helper"
require "test_helpers/factorial_stack_buster_helper"
require "test_helpers/vanilla_stack_buster_helper"
require_relative "test_helpers/assertions"

# Use alternate shoulda-style DSL for tests
class TCOMethod::TestCase < Minitest::Spec
Expand Down
28 changes: 28 additions & 0 deletions test/test_helpers/assertions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module TCOMethod
module TestHelpers
module Assertions
def assert_tail_call_optimized(method, *args)
is_tco = tail_call_optimized(method, *args)
msg = "Expected method #{method.name} to be tail call optimized"
assert is_tco, msg
end

def refute_tail_call_optimized(method, *args)
is_tco = tail_call_optimized(method, *args)
msg = "Expected method #{method.name} not to be tail call optimized"
refute is_tco, msg
end

def tail_call_optimized?(method, *args)
initial_length = nil
method.call(*args) do
if initial_length.nil?
initial_length = caller.length
else
break initial_length == caller.length
end
end
end
end
end
end
31 changes: 0 additions & 31 deletions test/test_helpers/factorial_stack_buster_helper.rb

This file was deleted.

41 changes: 0 additions & 41 deletions test/test_helpers/stack_busters/factorial_stack_buster.rb

This file was deleted.

25 changes: 0 additions & 25 deletions test/test_helpers/stack_busters/vanilla_stack_buster.rb

This file was deleted.

16 changes: 0 additions & 16 deletions test/test_helpers/vanilla_stack_buster_helper.rb

This file was deleted.

28 changes: 0 additions & 28 deletions test/test_helpers/vm_stack_helper.rb

This file was deleted.

2 changes: 0 additions & 2 deletions test/unit/mixin_test.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
require "test_helper"

class MixinTest < TCOMethod::TestCase
include TCOMethod::TestHelpers::FactorialStackBusterHelper

TestClass = Class.new { extend TCOMethod::Mixin }
TestModule = Module.new { extend TCOMethod::Mixin }

Expand Down
76 changes: 35 additions & 41 deletions test/unit/tco_method_test.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require "test_helper"

class TCOMethodTest < TCOMethod::TestCase
include TCOMethod::TestHelpers::FactorialStackBusterHelper
include TCOMethod::TestHelpers::Assertions

Subject = TCOMethod

Expand All @@ -14,26 +14,34 @@ class << self

# Equivalent to the below, but provides a target for verifying that
# tco_module_method works on Classes and tco_class_method works on Modules.
def self.module_factorial(n, acc = 1)
n <= 1 ? acc : module_factorial(n - 1, n * acc)
def self.module_fib_yielder(index, back_one = 1, back_two = 0, &block)
yield back_two if index > 0
index < 1 ? back_two : module_fib_yielder(index - 1, back_one + back_two, back_one, &block)
end

# Equivalent to the above, but provides a target for verifying that
# tco_module_method works on Classes and tco_class_method works on Modules.
def self.class_factorial(n, acc = 1)
n <= 1 ? acc : class_factorial(n - 1, n * acc)
def self.class_fib_yielder(index, back_one = 1, back_two = 0, &block)
yield back_two if index > 0
index < 1 ? back_two : class_fib_yielder(index - 1, back_one + back_two, back_one, &block)
end

define_method(:instance_block_method) { }

def instance_factorial(n, acc = 1)
n <= 1 ? acc : instance_factorial(n - 1, n * acc)
# Equivalent to the above, but provides a target for verifying that
# instance methods work for both Classes and Modules
def instance_fib_yielder(index, back_one = 1, back_two = 0, &block)
yield back_two if index > 0
index < 1 ? back_two : instance_fib_yielder(index - 1, back_one + back_two, back_one, &block)
end
end

TestModule = Module.new(&test_subject_builder)
TestClass = Class.new(&test_subject_builder)

# Grab source before it's recompiled for use later
InstanceFibYielderSource = TestClass.instance_method(:instance_fib_yielder).source

subject { Subject }

context Subject.name do
Expand All @@ -57,21 +65,15 @@ def instance_factorial(n, acc = 1)
end

should "compile the given code with tail call optimization" do
FactorialEvalDummy = dummy_class = Class.new
EvalDummy = dummy_class = Class.new
subject.tco_eval(<<-CODE)
class #{dummy_class.name}
def factorial(n, acc = 1)
n <= 1 ? acc : factorial(n - 1, n * acc)
end
#{InstanceFibYielderSource}
end
CODE

# Exceed maximum available stack depth by 100 for good measure
factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
assert_unoptimized_factorial_stack_overflow(factorial_seed)

expected_result = iterative_factorial(factorial_seed)
assert_equal expected_result, dummy_class.new.factorial(factorial_seed)
fib_yielder = dummy_class.new.method(:instance_fib_yielder)
assert tail_call_optimized?(fib_yielder, 5)
end
end

Expand Down Expand Up @@ -119,15 +121,12 @@ def factorial(n, acc = 1)
end

should "re-compile the given method with tail call optimization" do
# Exceed maximum available stack depth by 100 for good measure
factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
assert_raises(SystemStackError) do
method_owner.module_factorial(factorial_seed)
end
fib_yielder = method_owner.method(:module_fib_yielder)
refute tail_call_optimized?(fib_yielder, 5)

subject.call(method_owner, :module_factorial, :module)
expected_result = iterative_factorial(factorial_seed)
assert_equal expected_result, method_owner.module_factorial(factorial_seed)
subject.call(method_owner, :module_fib_yielder, :module)
fib_yielder = method_owner.method(:module_fib_yielder)
assert tail_call_optimized?(fib_yielder, 5)
end
end

Expand All @@ -139,15 +138,12 @@ def factorial(n, acc = 1)
end

should "re-compile the given method with tail call optimization" do
# Exceed maximum available stack depth by 100 for good measure
factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
assert_raises(SystemStackError) do
method_owner.class_factorial(factorial_seed)
end
fib_yielder = method_owner.method(:class_fib_yielder)
refute tail_call_optimized?(fib_yielder, 5)

subject.call(method_owner, :class_factorial, method_owner_class)
expected_result = iterative_factorial(factorial_seed)
assert_equal expected_result, method_owner.class_factorial(factorial_seed)
subject.call(method_owner, :class_fib_yielder, :module)
fib_yielder = method_owner.method(:class_fib_yielder)
assert tail_call_optimized?(fib_yielder, 5)
end
end

Expand All @@ -159,16 +155,14 @@ def factorial(n, acc = 1)
end

should "re-compile the given method with tail call optimization" do
# Exceed maximum available stack depth by 100 for good measure
factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
instance_class = instance_class_for_receiver(method_owner)
assert_raises(SystemStackError) do
instance_class.new.instance_factorial(factorial_seed)
end

subject.call(method_owner, :instance_factorial, :instance)
expected_result = iterative_factorial(factorial_seed)
assert_equal expected_result, instance_class.new.instance_factorial(factorial_seed)
fib_yielder = instance_class.new.method(:instance_fib_yielder)
refute tail_call_optimized?(fib_yielder, 5)

subject.call(method_owner, :instance_fib_yielder, :instance)
fib_yielder = instance_class.new.method(:instance_fib_yielder)
assert tail_call_optimized?(fib_yielder, 5)
end
end
end
Expand Down

0 comments on commit c296327

Please sign in to comment.