Skip to content

Commit

Permalink
Merge 3e854e0 into 7d0c274
Browse files Browse the repository at this point in the history
  • Loading branch information
jbodah committed Jan 15, 2015
2 parents 7d0c274 + 3e854e0 commit 01ca8af
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 91 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Gemfile.lock
*.gem
coverage
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ end
```

## TODO
* doesn't work when there's a base level before_suite and describes
* generate fields filtered out of sandbox
* how does it interact with factory girl?
* best practices docs (db cleaner etc)
* general refactoring
* does it work with multiple before_suites?
* allow for multiple callbacks; separate code into classes; only one sandbox
104 changes: 63 additions & 41 deletions lib/minitest/suite_callbacks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,78 @@
# It just doesn't work at all with contexts & suite-wide setup
module Minitest::SuiteCallbacks
def self.extended(base)
base.class_eval do
class << self
attr_reader :sandbox
class << base
attr_reader :sandbox
attr_accessor :counter

# DSL method
def before_suite(&before_suite_proc)
@before_suite_proc = before_suite_proc
# Setup state before suite
context = self
original_singleton_run = method(:run)
define_singleton_method :run do |*args, &block|
context.setup_before_suite
original_singleton_run.call(*args, &block)
end

# TODO what if we call before_suite multiple times?
original_instance_run = instance_method(:run)
# Define before_suite on the test class context
def before_suite(&before_suite_proc)
# Setup state before suite
context = self
singleton_class.class_eval do
original_singleton_run = instance_method(:run)
# Wrap the run method on the singleton class of the test class
define_method :run do |*args, &block|
context.transfer_sandbox_state_to(self)
original_instance_run.bind(self).call(*args, &block)
# TODO this is kind of hacky; it only runs before_suite once
# I don't think this will support other use cases (e.g. multiple before_suites/nested before_suites)
context.instance_eval do
if @sandbox.nil?
@sandbox = new('sandbox')
@sandbox.instance_eval &before_suite_proc
end
end
original_singleton_run.bind(self).call(*args, &block)
end
end

def after_suite(&after_suite_proc)
context = self
original_singleton_run = method(:run)
define_singleton_method :run do |*args, &block|
original_singleton_run.call(*args, &block)
context.sandbox.instance_eval &after_suite_proc
end
# TODO what if we call before_suite multiple times?
original_instance_run = instance_method(:run)
define_method :run do |*args, &block|
context.transfer_sandbox_state_to(self)
original_instance_run.bind(self).call(*args, &block)
end
end

def setup_before_suite
@sandbox = new('sandbox')
@sandbox.instance_eval &@before_suite_proc
# TODO: because of how Minitest separates its suites, this method gets called after EACH suite,
# not after all related suites are finished
def after_suite(&after_suite_proc)
@counter = 0
context = self
singleton_class.class_eval do
original_singleton_run = instance_method(:run)
define_method :run do |*args, &block|
original_singleton_run.bind(self).call(*args, &block)
context.instance_eval do
@counter += 1
if has_run_all_related_suites?
@sandbox.instance_eval &after_suite_proc
end
end
end
end
end

def transfer_sandbox_state_to(dest_context)
# TODO: generate these
ivs_to_skip = [:@NAME, :@assertions, :@failures]
iv_map = {}
@sandbox.instance_variables.each do |iv|
next if ivs_to_skip.include?(iv)
iv_map[iv] = @sandbox.instance_variable_get(iv.to_sym)
end

dest_context.instance_eval do
iv_map.each do |k, v|
instance_variable_set k, v
end
def has_run_all_related_suites?
# Get the number of subclasses + the class itself
@counter == descendant_count + 1
end

def descendant_count
ObjectSpace.each_object(::Class).select{|k| k < self}.size
end

def transfer_sandbox_state_to(dest_context)
# TODO: generate these
ivs_to_skip = [:@NAME, :@assertions, :@failures]
iv_map = {}
@sandbox.instance_variables.each do |iv|
next if ivs_to_skip.include?(iv)
iv_map[iv] = @sandbox.instance_variable_get(iv.to_sym)
end

dest_context.instance_eval do
iv_map.each do |k, v|
instance_variable_set k, v
end
end
end
Expand Down
1 change: 1 addition & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
$LOAD_PATH.push 'lib', __FILE__

require 'minitest/autorun'
require 'minitest/pride'
require 'minitest/spec'
require 'minitest/suite_callbacks'

Expand Down
23 changes: 23 additions & 0 deletions test/unit/call_count_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require 'test_helper'

class CallCountTest < Minitest::Spec
before_suite_call_count = 0
after_suite_call_count = 0

before_suite do
before_suite_call_count += 1
end

after_suite do
after_suite_call_count += 1
assert after_suite_call_count == 1
end

(1..3).each do |i|
has_run = false
it "is called the appropriate number of times#{i}" do
assert before_suite_call_count == 1
assert after_suite_call_count == 0
end
end
end
33 changes: 33 additions & 0 deletions test/unit/inside_describe_blocks_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require 'test_helper'

class InsideDescribeBlocksTest < Minitest::Spec
describe 'a one-level block' do
before_suite do
@var = 'var'
end

after_suite do
assert @var == 'var'
end

it 'should share instance variables' do
assert @var == 'var'
end
end

describe 'a' do
describe 'nested block' do
before_suite do
assert @var2 = 'var2'
end

after_suite do
assert @var2 == 'var2'
end

it 'should share instance variables' do
assert @var2 == 'var2'
end
end
end
end
19 changes: 19 additions & 0 deletions test/unit/instance_method_access_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require 'test_helper'

class InstanceMethodAccessTest < Minitest::Spec
before_suite do
@instance_method_return_value = instance_method
end

after_suite do
assert instance_method == 'hi'
end

it 'has access to instance methods' do
assert true # test handled by callbacks
end

def instance_method
'hi'
end
end
15 changes: 15 additions & 0 deletions test/unit/instance_variable_scope_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'test_helper'

class InstanceVariableScopeTest < Minitest::Spec
before_suite do
@yo = 'yo'
end

after_suite do
assert @yo == 'yo'
end

it 'shares instances variables with the tests' do
assert @yo == 'yo'
end
end
25 changes: 25 additions & 0 deletions test/unit/outside_describe_block_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'test_helper'

class OutsideDescribeBlockTest < Minitest::Spec
@before_suite_call_count = 0
@after_suite_call_count = 0

class << self
attr_accessor :before_suite_call_count, :after_suite_call_count
end

before_suite do
OutsideDescribeBlockTest.before_suite_call_count += 1
end

after_suite do
OutsideDescribeBlockTest.after_suite_call_count += 1
end

describe 'a describe block' do
it 'should call top-level callbacks the right number of times' do
assert_equal 1, OutsideDescribeBlockTest.before_suite_call_count, 'expected before_suite to be correct'
assert_equal 0, OutsideDescribeBlockTest.after_suite_call_count, 'expected after_suite to be correct'
end
end
end
49 changes: 0 additions & 49 deletions test/unit/suite_callbacks_test.rb

This file was deleted.

0 comments on commit 01ca8af

Please sign in to comment.