Skip to content

Commit c296327

Browse files
committed
Replace StackBusters with TCO assertions (mbittarelli FTW)
1 parent e2e7f30 commit c296327

9 files changed

Lines changed: 64 additions & 187 deletions

test/test_helper.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
require "mocha/setup"
1313
require "tco_method"
1414

15-
require "test_helpers/vm_stack_helper"
16-
require "test_helpers/factorial_stack_buster_helper"
17-
require "test_helpers/vanilla_stack_buster_helper"
15+
require_relative "test_helpers/assertions"
1816

1917
# Use alternate shoulda-style DSL for tests
2018
class TCOMethod::TestCase < Minitest::Spec

test/test_helpers/assertions.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module TCOMethod
2+
module TestHelpers
3+
module Assertions
4+
def assert_tail_call_optimized(method, *args)
5+
is_tco = tail_call_optimized(method, *args)
6+
msg = "Expected method #{method.name} to be tail call optimized"
7+
assert is_tco, msg
8+
end
9+
10+
def refute_tail_call_optimized(method, *args)
11+
is_tco = tail_call_optimized(method, *args)
12+
msg = "Expected method #{method.name} not to be tail call optimized"
13+
refute is_tco, msg
14+
end
15+
16+
def tail_call_optimized?(method, *args)
17+
initial_length = nil
18+
method.call(*args) do
19+
if initial_length.nil?
20+
initial_length = caller.length
21+
else
22+
break initial_length == caller.length
23+
end
24+
end
25+
end
26+
end
27+
end
28+
end

test/test_helpers/factorial_stack_buster_helper.rb

Lines changed: 0 additions & 31 deletions
This file was deleted.

test/test_helpers/stack_busters/factorial_stack_buster.rb

Lines changed: 0 additions & 41 deletions
This file was deleted.

test/test_helpers/stack_busters/vanilla_stack_buster.rb

Lines changed: 0 additions & 25 deletions
This file was deleted.

test/test_helpers/vanilla_stack_buster_helper.rb

Lines changed: 0 additions & 16 deletions
This file was deleted.

test/test_helpers/vm_stack_helper.rb

Lines changed: 0 additions & 28 deletions
This file was deleted.

test/unit/mixin_test.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
require "test_helper"
22

33
class MixinTest < TCOMethod::TestCase
4-
include TCOMethod::TestHelpers::FactorialStackBusterHelper
5-
64
TestClass = Class.new { extend TCOMethod::Mixin }
75
TestModule = Module.new { extend TCOMethod::Mixin }
86

test/unit/tco_method_test.rb

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require "test_helper"
22

33
class TCOMethodTest < TCOMethod::TestCase
4-
include TCOMethod::TestHelpers::FactorialStackBusterHelper
4+
include TCOMethod::TestHelpers::Assertions
55

66
Subject = TCOMethod
77

@@ -14,26 +14,34 @@ class << self
1414

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

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

2729
define_method(:instance_block_method) { }
2830

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

3439
TestModule = Module.new(&test_subject_builder)
3540
TestClass = Class.new(&test_subject_builder)
3641

42+
# Grab source before it's recompiled for use later
43+
InstanceFibYielderSource = TestClass.instance_method(:instance_fib_yielder).source
44+
3745
subject { Subject }
3846

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

5967
should "compile the given code with tail call optimization" do
60-
FactorialEvalDummy = dummy_class = Class.new
68+
EvalDummy = dummy_class = Class.new
6169
subject.tco_eval(<<-CODE)
6270
class #{dummy_class.name}
63-
def factorial(n, acc = 1)
64-
n <= 1 ? acc : factorial(n - 1, n * acc)
65-
end
71+
#{InstanceFibYielderSource}
6672
end
6773
CODE
6874

69-
# Exceed maximum available stack depth by 100 for good measure
70-
factorial_seed = factorial_stack_buster_stack_depth_remaining + 100
71-
assert_unoptimized_factorial_stack_overflow(factorial_seed)
72-
73-
expected_result = iterative_factorial(factorial_seed)
74-
assert_equal expected_result, dummy_class.new.factorial(factorial_seed)
75+
fib_yielder = dummy_class.new.method(:instance_fib_yielder)
76+
assert tail_call_optimized?(fib_yielder, 5)
7577
end
7678
end
7779

@@ -119,15 +121,12 @@ def factorial(n, acc = 1)
119121
end
120122

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

128-
subject.call(method_owner, :module_factorial, :module)
129-
expected_result = iterative_factorial(factorial_seed)
130-
assert_equal expected_result, method_owner.module_factorial(factorial_seed)
127+
subject.call(method_owner, :module_fib_yielder, :module)
128+
fib_yielder = method_owner.method(:module_fib_yielder)
129+
assert tail_call_optimized?(fib_yielder, 5)
131130
end
132131
end
133132

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

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

148-
subject.call(method_owner, :class_factorial, method_owner_class)
149-
expected_result = iterative_factorial(factorial_seed)
150-
assert_equal expected_result, method_owner.class_factorial(factorial_seed)
144+
subject.call(method_owner, :class_fib_yielder, :module)
145+
fib_yielder = method_owner.method(:class_fib_yielder)
146+
assert tail_call_optimized?(fib_yielder, 5)
151147
end
152148
end
153149

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

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

169-
subject.call(method_owner, :instance_factorial, :instance)
170-
expected_result = iterative_factorial(factorial_seed)
171-
assert_equal expected_result, instance_class.new.instance_factorial(factorial_seed)
160+
fib_yielder = instance_class.new.method(:instance_fib_yielder)
161+
refute tail_call_optimized?(fib_yielder, 5)
162+
163+
subject.call(method_owner, :instance_fib_yielder, :instance)
164+
fib_yielder = instance_class.new.method(:instance_fib_yielder)
165+
assert tail_call_optimized?(fib_yielder, 5)
172166
end
173167
end
174168
end

0 commit comments

Comments
 (0)