Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions lib/fixture_kit/adapters/minitest_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@
module FixtureKit
class MinitestAdapter < FixtureKit::Adapter
TEST_NAME = "fixture kit cache pregeneration"
TEST_METHOD = :"test_#{TEST_NAME.tr(" ", "_")}"

def execute(&block)
test_class = build_test_class
test_method = test_class.test(TEST_NAME) do
test = test_class.new(TEST_METHOD)
# Minitest runs a test by sending its name to the instance, so defining it
# here leaves the reused class untouched and the block dies with the test.
test.define_singleton_method(TEST_METHOD) do
block.call(self)
pass
end

result = test_class.new(test_method).run
result = test.run
return if result.passed?

raise result.failures.first.error
Expand All @@ -27,8 +30,11 @@ def identifier_for(identifier)

private

def build_test_class
Class.new(ActiveSupport::TestCase) do
# Reused rather than built per generation: including
# ActiveRecord::TestFixtures appends the class to ActiveSupport's
# :active_record_fixtures load hooks, and that never shrinks.
def test_class
@test_class ||= Class.new(ActiveSupport::TestCase) do
::Minitest::Runnable.runnables.delete(self)
include(::ActiveRecord::TestFixtures)
end
Expand Down
15 changes: 10 additions & 5 deletions lib/fixture_kit/adapters/rspec_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ class RSpecAdapter < FixtureKit::Adapter
def execute(&block)
previous_example = ::RSpec.current_example
previous_scope = ::RSpec.current_scope
example_group = build_example_group
example = example_group.example { block.call(self) }
group = example_group
example = group.example { block.call(self) }
succeeded =
begin
example.run(example_group.new, ::RSpec::Core::NullReporter)
example.run(group.new, ::RSpec::Core::NullReporter)
ensure
# The group is reused, and the example it holds retains this block.
group.examples.clear
::RSpec.current_example = previous_example
::RSpec.current_scope = previous_scope
end
Expand All @@ -27,8 +29,11 @@ def identifier_for(identifier)

private

def build_example_group
::RSpec::Core::ExampleGroup.subclass(
# Reused rather than built per generation: rspec-rails includes
# ActiveRecord::TestFixtures into every group, which appends it to
# ActiveSupport's :active_record_fixtures load hooks, and that never shrinks.
def example_group
@example_group ||= ::RSpec::Core::ExampleGroup.subclass(
::RSpec::Core::ExampleGroup,
"FixtureKit",
[],
Expand Down
44 changes: 34 additions & 10 deletions spec/unit/minitest_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,48 @@

expect(captured_test_case_class).to be < ActiveSupport::TestCase
expect(captured_test_case_class.included_modules).to include(ActiveRecord::TestFixtures)
expect(captured_test_case_class.public_instance_methods(false).map(&:to_s))
.to include("test_fixture_kit_cache_pregeneration")
end

it "builds a fresh harness class for each execute on the same adapter instance" do
captured_test_case_classes = []
it "defines the pregeneration test method on the instance, not the harness class" do
harness_class = nil
defined_on_instance = nil

allow(Minitest::Runnable.runnables).to receive(:delete).and_wrap_original do |original, test_case_class|
captured_test_case_classes << test_case_class
original.call(test_case_class)
described_class.new.execute do |context|
harness_class = context.class
defined_on_instance = context.singleton_class
.instance_methods(false)
.include?(described_class::TEST_METHOD)
end

expect(defined_on_instance).to be(true)
expect(harness_class.method_defined?(described_class::TEST_METHOD)).to be(false)
end

it "reuses one harness class across executes on the same adapter instance" do
harness_classes = []
adapter = described_class.new

adapter.execute { |context| harness_classes << context.class }
adapter.execute { |context| harness_classes << context.class }

expect(harness_classes.size).to eq(2)
expect(harness_classes.first).to equal(harness_classes.last)
end

it "leaves no per-generation state on the reused harness class" do
adapter = described_class.new
harness_class = nil
adapter.execute { |context| harness_class = context.class }
snapshot = lambda do
[harness_class.instance_variables.sort, harness_class.public_instance_methods(false).sort]
end
baseline = snapshot.call

adapter.execute { nil }
adapter.execute { nil }
expect { adapter.execute { raise "harness exploded" } }
.to raise_error(RuntimeError, "harness exploded")

expect(captured_test_case_classes.size).to be >= 2
expect(captured_test_case_classes[-2]).not_to equal(captured_test_case_classes[-1])
expect(snapshot.call).to eq(baseline)
end

it "does not leak harness test cases into minitest runnables" do
Expand Down
31 changes: 31 additions & 0 deletions spec/unit/rspec_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,36 @@
end.to raise_error(RuntimeError, "harness exploded")
end

it "reuses one harness example group across executes" do
harness_groups = []
adapter = described_class.new

adapter.execute { |context| harness_groups << context.class }
adapter.execute { |context| harness_groups << context.class }

expect(harness_groups.size).to eq(2)
expect(harness_groups.first).to equal(harness_groups.last)
end

it "assigns only one RSpec::ExampleGroups constant no matter how many times it runs" do
adapter = described_class.new
adapter.execute { nil }
constants_after_first = RSpec::ExampleGroups.constants.grep(/\AFixtureKit/).size

3.times { adapter.execute { nil } }

expect(RSpec::ExampleGroups.constants.grep(/\AFixtureKit/).size)
.to eq(constants_after_first)
end

it "does not retain the harness example after running" do
harness_group = nil

described_class.new.execute { |context| harness_group = context.class }

expect(harness_group.examples).to be_empty
end

it "re-raises example.exception when execute returns false" do
example_group = double("example_group")
example = instance_double(RSpec::Core::Example)
Expand All @@ -66,6 +96,7 @@

allow(::RSpec::Core::ExampleGroup).to receive(:subclass).and_return(example_group)
allow(example_group).to receive(:example).and_return(example)
allow(example_group).to receive(:examples).and_return([])
allow(example_group).to receive(:new).and_return(instance)
allow(example).to receive(:run).with(instance, RSpec::Core::NullReporter).and_return(false)
allow(example).to receive(:exception).and_return(failure)
Expand Down
Loading