Skip to content

Commit

Permalink
Merge pull request #243 from KnapsackPro/require-rails-helper
Browse files Browse the repository at this point in the history
Require rails_helper before configuring RSpec
  • Loading branch information
Pacyfik committed Mar 11, 2024
2 parents 3e02c29 + 80e389d commit 15101b8
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 12 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

### 7.0.1

* fix(RSpec): conditionally adds `--require rails_helper` to cli arguments of `KnapsackPro::Runners::Queue::RSpecRunner`. Version 7.0.0 introduced some fundamental changes, namely fetching, loading and running batches of specs **after** executing suite hooks, so that such hooks are only ran once, not before every batch. As a result, if `rails_helper` is only required in spec files, which is the RSpec default, instead of e.g. in `.rspec`, then some `before(:suite)` hooks, e.g. defined by gems, are registered after suite hooks had already been executed by the test suite. By comparison, RSpec loads all the spec files **before** executing `before(:suite)` hooks.

PR with the above changes: https://github.com/KnapsackPro/knapsack_pro-ruby/pull/243

https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v7.0.0...v7.0.1

### 7.0.0

* __(breaking change)__ RSpec in Queue Mode:
Expand Down
8 changes: 8 additions & 0 deletions lib/knapsack_pro/adapters/rspec_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ def self.has_format_option?(cli_args)
!!parsed_options(cli_args)&.[](:formatters)
end

def self.has_require_rails_helper_option?(cli_args)
(parsed_options(cli_args)&.[](:requires) || []).include?("rails_helper")
end

def self.order_option(cli_args)
parsed_options(cli_args)&.[](:order)
end
Expand All @@ -76,6 +80,10 @@ def self.parse_file_path(id)
id.match(/\A(.*?)(?:\[([\d\s:,]+)\])?\z/).captures.first
end

def self.rails_helper_exists?(test_dir)
File.exist?("#{test_dir}/rails_helper.rb")
end

# private
def self.top_level_group(example)
group = example.metadata[:example_group]
Expand Down
10 changes: 9 additions & 1 deletion lib/knapsack_pro/pure/queue/rspec_pure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ def args_with_seed_option_added_when_viable(order_option, seed, args)
args + ['--seed', seed.value]
end

def prepare_cli_args(args, has_format_option, test_dir)
def prepare_cli_args(args, has_format_option, has_require_rails_helper_option, rails_helper_exists, test_dir)
(args || '').split
.yield_self { args_with_at_least_one_formatter(_1, has_format_option) }
.yield_self { args_with_require_rails_helper_if_needed(_1, has_require_rails_helper_option, rails_helper_exists) }
.yield_self { args_with_default_options(_1, test_dir) }
end

Expand Down Expand Up @@ -75,6 +76,13 @@ def args_with_at_least_one_formatter(cli_args, has_format_option)
cli_args + ['--format', 'progress']
end

def args_with_require_rails_helper_if_needed(cli_args, has_require_rails_helper_option, rails_helper_exists)
return cli_args if has_require_rails_helper_option
return cli_args unless rails_helper_exists

cli_args + ['--require', 'rails_helper']
end

def args_with_default_options(cli_args, test_dir)
new_cli_args = cli_args + [
'--default-path', test_dir,
Expand Down
7 changes: 5 additions & 2 deletions lib/knapsack_pro/runners/queue/rspec_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ def initialize(adapter_class, rspec_pure, args, stream_error, stream_out)
super(adapter_class)
@adapter_class = adapter_class
@rspec_pure = rspec_pure
has_format_option = @adapter_class.has_format_option?((args || '').split)
@cli_args = rspec_pure.prepare_cli_args(args, has_format_option, test_dir)
args_array = (args || '').split
has_format_option = @adapter_class.has_format_option?(args_array)
has_require_rails_helper_option = @adapter_class.has_require_rails_helper_option?(args_array)
rails_helper_exists = @adapter_class.rails_helper_exists?(test_dir)
@cli_args = rspec_pure.prepare_cli_args(args, has_format_option, has_require_rails_helper_option, rails_helper_exists, test_dir)
@stream_error = stream_error
@stream_out = stream_out
@node_test_file_paths = []
Expand Down
173 changes: 173 additions & 0 deletions spec/integration/runners/queue/rspec_runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ def generate_specs(spec_helper, rspec_options, spec_batches)
)
end

def create_rails_helper_file(rails_helper)
rails_helper_path = "#{SPEC_DIRECTORY}/rails_helper.rb"
File.open(rails_helper_path, 'w') { |file| file.write(rails_helper) }
end

def stub_spec_batches(batched_tests)
ENV['TEST__SPEC_BATCHES'] = batched_tests.to_json
end
Expand Down Expand Up @@ -327,6 +332,174 @@ def log_command_result(stdout, stderr, status)
end
end

context 'when rails_helper file does not exist' do
it 'does not require the rails_helper file when running RSpec' do
rspec_options = ''

spec_helper = <<~SPEC
require 'knapsack_pro'
KnapsackPro::Adapters::RSpecAdapter.bind
SPEC

spec_a = Spec.new('a_spec.rb', <<~SPEC)
describe 'A_describe' do
it 'A1 test example' do
expect(1).to eq 1
end
end
SPEC

spec_b = Spec.new('b_spec.rb', <<~SPEC)
describe 'B_describe' do
it 'B1 test example' do
expect(1).to eq 1
end
end
SPEC

spec_c = Spec.new('c_spec.rb', <<~SPEC)
describe 'C_describe' do
it 'C1 test example' do
expect(1).to eq 1
end
end
SPEC

generate_specs(spec_helper, rspec_options, [
[spec_a, spec_b],
[spec_c],
])

actual = subject

expect(actual.stdout).to_not include('--require rails_helper')

expect(actual.exit_code).to eq 0
end
end

context 'when rails_helper file exists' do
it 'requires the rails_helper file when running RSpec and runs hooks defined within it' do
rspec_options = ''

spec_helper = <<~SPEC
require 'knapsack_pro'
KnapsackPro::Adapters::RSpecAdapter.bind
SPEC

spec_a = Spec.new('a_spec.rb', <<~SPEC)
describe 'A_describe' do
it 'A1 test example' do
expect(1).to eq 1
end
end
SPEC

spec_b = Spec.new('b_spec.rb', <<~SPEC)
describe 'B_describe' do
it 'B1 test example' do
expect(1).to eq 1
end
end
SPEC

spec_c = Spec.new('c_spec.rb', <<~SPEC)
describe 'C_describe' do
it 'C1 test example' do
expect(1).to eq 1
end
end
SPEC

generate_specs(spec_helper, rspec_options, [
[spec_a, spec_b],
[spec_c],
])

rails_helper = <<~SPEC
RSpec.configure do |config|
config.before(:suite) do
puts 'RSpec_before_suite_hook_from_rails_helper'
end
config.after(:suite) do
puts 'RSpec_after_suite_hook_from_rails_helper'
end
end
SPEC

create_rails_helper_file(rails_helper)

actual = subject

expect(actual.stdout).to include('--require rails_helper')
expect(actual.stdout.scan(/RSpec_before_suite_hook_from_rails_helper/).size).to eq 1
expect(actual.stdout.scan(/RSpec_after_suite_hook_from_rails_helper/).size).to eq 1

expect(actual.exit_code).to eq 0
end

it 'runs suite hooks defined in rails_helper only once, even if file is required multiple times' do
rspec_options = ''

spec_helper = <<~SPEC
require 'knapsack_pro'
KnapsackPro::Adapters::RSpecAdapter.bind
SPEC

spec_a = Spec.new('a_spec.rb', <<~SPEC)
require 'rails_helper'
describe 'A_describe' do
it 'A1 test example' do
expect(1).to eq 1
end
end
SPEC

spec_b = Spec.new('b_spec.rb', <<~SPEC)
require 'rails_helper'
describe 'B_describe' do
it 'B1 test example' do
expect(1).to eq 1
end
end
SPEC

spec_c = Spec.new('c_spec.rb', <<~SPEC)
require 'rails_helper'
describe 'C_describe' do
it 'C1 test example' do
expect(1).to eq 1
end
end
SPEC

generate_specs(spec_helper, rspec_options, [
[spec_a, spec_b],
[spec_c],
])

rails_helper = <<~SPEC
RSpec.configure do |config|
config.before(:suite) do
puts 'RSpec_before_suite_hook_from_rails_helper'
end
config.after(:suite) do
puts 'RSpec_after_suite_hook_from_rails_helper'
end
end
SPEC

create_rails_helper_file(rails_helper)

actual = subject

expect(actual.stdout.scan(/RSpec_before_suite_hook_from_rails_helper/).size).to eq 1
expect(actual.stdout.scan(/RSpec_after_suite_hook_from_rails_helper/).size).to eq 1

expect(actual.exit_code).to eq 0
end
end

context 'when hooks are defined' do
it 'calls RSpec before/after hooks only once for multiple batches of tests' do
rspec_options = ''
Expand Down
54 changes: 54 additions & 0 deletions spec/knapsack_pro/adapters/rspec_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,60 @@
end
end

describe '.has_require_rails_helper_option?' do
subject { described_class.has_require_rails_helper_option?(cli_args) }

context 'when require option is provided as -r' do
let(:cli_args) { ['-r', 'rails_helper'] }

it { expect(subject).to be true }
end

context 'when require option is provided as --require' do
let(:cli_args) { ['--require', 'rails_helper'] }

it { expect(subject).to be true }
end

context 'when require option is provided without delimiter' do
let(:cli_args) { ['-rrails_helper'] }

it { expect(subject).to be true }
end

context 'when require option is not provided' do
let(:cli_args) { ['--fake', 'value'] }

it { expect(subject).to be false }
end
end

describe '.rails_helper_exists?' do
subject { described_class.rails_helper_exists?(test_dir) }

let(:test_dir) { 'spec_fake' }

context 'when rails_helper exists' do
before do
File.open("#{test_dir}/rails_helper.rb", 'w')
end

after do
FileUtils.rm("#{test_dir}/rails_helper.rb")
end

it { expect(subject).to be true }
end

context 'when rails_helper does not exist' do
before do
FileUtils.rm_f("#{test_dir}/rails_helper.rb")
end

it { expect(subject).to be false }
end
end

describe '.order_option' do
subject { described_class.order_option(cli_args) }

Expand Down

0 comments on commit 15101b8

Please sign in to comment.